From 5510abf69022d8393e6c5fc3d1456cc47f96fa4a Mon Sep 17 00:00:00 2001 From: Brendan Taylor Date: Mon, 27 Sep 2010 10:41:13 -0600 Subject: move cookie handling into a subclass of SoupCookieJar this cleans up the code and slightly improves efficiency. --- src/callbacks.c | 36 +------ src/callbacks.h | 5 +- src/cookie-jar.c | 289 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/cookie-jar.h | 33 +++++++ src/uzbl-core.c | 173 +-------------------------------- src/uzbl-core.h | 9 +- 6 files changed, 332 insertions(+), 213 deletions(-) create mode 100644 src/cookie-jar.c create mode 100644 src/cookie-jar.h (limited to 'src') diff --git a/src/callbacks.c b/src/callbacks.c index d2352b2..0fbf589 100644 --- a/src/callbacks.c +++ b/src/callbacks.c @@ -831,38 +831,6 @@ populate_popup_cb(WebKitWebView *v, GtkMenu *m, void *c) { } void -save_cookies_js(SoupCookieJar *jar, SoupCookie *old_cookie, SoupCookie *new_cookie, gpointer user_data) { - (void) jar; - (void) user_data; - (void) old_cookie; - char *scheme; - GString *s; - - if(new_cookie != NULL) { - scheme = (new_cookie->secure == TRUE) ? "https" : "http"; - - s = g_string_new(""); - g_string_printf(s, "PUT '%s' '%s' '%s' '%s=%s'", scheme, new_cookie->domain, new_cookie->path, new_cookie->name, new_cookie->value); - run_handler(uzbl.behave.cookie_handler, s->str); - g_string_free(s, TRUE); - } -} - -void -save_cookies_http(SoupMessage *msg, gpointer user_data) { - (void) user_data; - GSList *ck; - char *cookie; - - for(ck = soup_cookies_from_response(msg); ck; ck = ck->next){ - cookie = soup_cookie_to_set_cookie_header(ck->data); - SoupURI *soup_uri = soup_message_get_uri(msg); - GString *s = g_string_new(""); - g_string_printf(s, "PUT '%s' '%s' '%s' '%s'", soup_uri->scheme, soup_uri->host, soup_uri->path, cookie); - run_handler(uzbl.behave.cookie_handler, s->str); - g_free (cookie); - g_string_free(s, TRUE); - } - - g_slist_free(ck); +cmd_set_cookie_handler() { + uzbl_cookie_jar_set_handler(uzbl.net.soup_cookie_jar, uzbl.behave.cookie_handler); } diff --git a/src/callbacks.h b/src/callbacks.h index fdd08e4..0b89f96 100644 --- a/src/callbacks.h +++ b/src/callbacks.h @@ -212,7 +212,4 @@ gboolean focus_cb(GtkWidget* window, GdkEventFocus* event, void *ud); void -save_cookies_js(SoupCookieJar *jar, SoupCookie *old_cookie, SoupCookie *new_cookie, gpointer user_data); - -void -save_cookies_http(SoupMessage *msg, gpointer user_data); +cmd_set_cookie_handler(); diff --git a/src/cookie-jar.c b/src/cookie-jar.c new file mode 100644 index 0000000..875fdec --- /dev/null +++ b/src/cookie-jar.c @@ -0,0 +1,289 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "cookie-jar.h" +#include "uzbl-core.h" + +static void +uzbl_cookie_jar_session_feature_init(SoupSessionFeatureInterface *iface, gpointer user_data); + +G_DEFINE_TYPE_WITH_CODE (UzblCookieJar, soup_cookie_jar_socket, SOUP_TYPE_COOKIE_JAR, + G_IMPLEMENT_INTERFACE (SOUP_TYPE_SESSION_FEATURE, uzbl_cookie_jar_session_feature_init)) + +static void request_started (SoupSessionFeature *feature, SoupSession *session, SoupMessage *msg, SoupSocket *socket); +static void changed(SoupCookieJar *jar, SoupCookie *old_cookie, SoupCookie *new_cookie); + +static void setup_handler(UzblCookieJar *jar); + +static void connect_cookie_socket(UzblCookieJar *jar); +static void disconnect_cookie_socket(UzblCookieJar *jar); + +static gchar *do_socket_request(UzblCookieJar *jar, gchar *request, int request_len); + +static bool has_socket_handler(UzblCookieJar *jar) { + return jar->socket_path != NULL; +} + +static void +soup_cookie_jar_socket_init(UzblCookieJar *jar) { + jar->socket_path = NULL; + jar->connection_fd = -1; +} + +static void +finalize(GObject *object) { + disconnect_cookie_socket(UZBL_COOKIE_JAR(object)); + G_OBJECT_CLASS(soup_cookie_jar_socket_parent_class)->finalize(object); +} + +static void +soup_cookie_jar_socket_class_init(UzblCookieJarClass *socket_class) { + G_OBJECT_CLASS(socket_class)->finalize = finalize; + SOUP_COOKIE_JAR_CLASS(socket_class)->changed = changed; +} + +/* override SoupCookieJar's request_started handler */ +static void +uzbl_cookie_jar_session_feature_init(SoupSessionFeatureInterface *iface, gpointer user_data) { + (void) user_data; + iface->request_started = request_started; +} + +UzblCookieJar *uzbl_cookie_jar_new() { + return g_object_new(UZBL_TYPE_COOKIE_JAR, NULL); +} + +void +uzbl_cookie_jar_set_handler(UzblCookieJar *jar, const gchar* handler) { + jar->handler = handler; + setup_handler(jar); +} + +char *get_cookies(UzblCookieJar *jar, SoupURI *uri) { + gchar *result; + GString *s = g_string_new ("GET"); + + if(has_socket_handler(jar)) { + g_string_append_c(s, 0); /* null-terminate the GET */ + g_string_append_len(s, uri->scheme, strlen(uri->scheme)+1); + g_string_append_len(s, uri->host, strlen(uri->host)+1 ); + g_string_append_len(s, uri->path, strlen(uri->path)+1 ); + + result = do_socket_request(jar, s->str, s->len); + /* try it again; older cookie daemons closed the connection after each request */ + if(result == NULL) + result = do_socket_request(jar, s->str, s->len); + } else { + g_string_append_printf(s, " '%s' '%s' '%s'", uri->scheme, uri->host, uri->path); + + run_handler(jar->handler, s->str); + result = g_strdup(uzbl.comm.sync_stdout); + } + g_string_free(s, TRUE); + return result; +} + +/* this is a duplicate of SoupCookieJar's request_started that uses our get_cookies instead */ +static void +request_started(SoupSessionFeature *feature, SoupSession *session, + SoupMessage *msg, SoupSocket *socket) { + (void) session; (void) socket; + + UzblCookieJar *jar = UZBL_COOKIE_JAR (feature); + SoupURI *uri = soup_message_get_uri(msg); + + gchar *cookies = get_cookies(jar, uri); + + if (cookies && *cookies != 0) { + const gchar *next_cookie_start = cookies; + + /* add the cookie data that we just obtained from the cookie handler + to the cookie jar so that javascript has access to them. + we set this flag so that we don't trigger the PUT handler. */ + jar->in_get_callback = true; + do { + SoupCookie *soup_cookie = soup_cookie_parse(next_cookie_start, uri); + if(soup_cookie) + soup_cookie_jar_add_cookie(SOUP_COOKIE_JAR(uzbl.net.soup_cookie_jar), soup_cookie); + next_cookie_start = strchr(next_cookie_start, ';'); + } while(next_cookie_start++ != NULL); + jar->in_get_callback = false; + + soup_message_headers_replace (msg->request_headers, "Cookie", cookies); + + g_free (cookies); + } else { + soup_message_headers_remove (msg->request_headers, "Cookie"); + } +} + +static void +changed(SoupCookieJar *jar, SoupCookie *old_cookie, SoupCookie *new_cookie) { + (void) old_cookie; + + UzblCookieJar *uzbl_jar = UZBL_COOKIE_JAR(jar); + + /* when Uzbl begins an HTTP request, it GETs cookies from the handler + and then adds them to the cookie jar so that javascript can access + these cookies. this causes a 'changed' callback, which we don't want + to do anything, so we just return. + + (if SoupCookieJar let us override soup_cookie_jar_get_cookies we + wouldn't have to do this.) */ + if(uzbl_jar->in_get_callback) + return; + + /* the cookie daemon is only interested in new cookies and changed + ones, it can take care of deleting expired cookies on its own. */ + if(!new_cookie) + return; + + GString *s = g_string_new ("PUT"); + + gchar *scheme = (new_cookie->secure == TRUE) ? "https" : "http"; + if(has_socket_handler(uzbl_jar)) { + g_string_append_c(s, 0); /* null-terminate the PUT */ + g_string_append_len(s, scheme, strlen(scheme)+1); + g_string_append_len(s, new_cookie->domain, strlen(new_cookie->domain)+1 ); + g_string_append_len(s, new_cookie->path, strlen(new_cookie->path)+1 ); + g_string_append_printf(s, "%s=%s", new_cookie->name, new_cookie->value); + + gchar *result = do_socket_request(uzbl_jar, s->str, s->len+1); + /* try it again; older cookie daemons closed the connection after each request */ + if(!result) + result = do_socket_request(uzbl_jar, s->str, s->len+1); + } else { + g_string_append_printf(s, " '%s' '%s' '%s' '%s=%s'", scheme, new_cookie->domain, new_cookie->path, new_cookie->name, new_cookie->value); + + run_handler(uzbl_jar->handler, s->str); + } + + g_string_free(s, TRUE); +} + +static void +setup_handler(UzblCookieJar *jar) { + if(strncmp(jar->handler, "talk_to_socket", strlen("talk_to_socket")) == 0) { + /* extract the socket path from the handler. */ + jar->socket_path = jar->handler + strlen("talk_to_socket"); + while(isspace(*jar->socket_path)) + jar->socket_path++; + if(*jar->socket_path == 0) + return; /* there was no path specified. */ + disconnect_cookie_socket(jar); + connect_cookie_socket(jar); + } else { + jar->socket_path = NULL; + } +} + +static void +connect_cookie_socket(UzblCookieJar *jar) { + struct sockaddr_un sa; + int fd; + + g_strlcpy(sa.sun_path, jar->socket_path, sizeof(sa.sun_path)); + sa.sun_family = AF_UNIX; + + /* create socket file descriptor and connect it to path */ + fd = socket(AF_UNIX, SOCK_SEQPACKET, 0); + if(fd == -1) { + g_printerr("connect_cookie_socket: creating socket failed (%s)\n", strerror(errno)); + return; + } + + if(connect(fd, (struct sockaddr*)&sa, sizeof(sa))) { + g_printerr("connect_cookie_socket: connect failed (%s)\n", strerror(errno)); + close(fd); + return; + } + + /* successful connection! */ + jar->connection_fd = fd; +} + +static void +disconnect_cookie_socket(UzblCookieJar *jar) { + if(jar->connection_fd > 0) { + close(jar->connection_fd); + jar->connection_fd = -1; + } +} + +static gchar *do_socket_request(UzblCookieJar *jar, gchar *request, int request_length) { + int len; + ssize_t ret; + struct pollfd pfd; + gchar *result = NULL; + + if(jar->connection_fd < 0) + connect_cookie_socket(jar); /* connection was lost, reconnect */ + + /* write request */ + ret = write(jar->connection_fd, request, request_length); + if(ret == -1) { + g_printerr("talk_to_socket: write failed (%s)\n", strerror(errno)); + disconnect_cookie_socket(jar); + return NULL; + } + + /* wait for a response, with a 500ms timeout */ + pfd.fd = jar->connection_fd; + pfd.events = POLLIN; + while(1) { + ret = poll(&pfd, 1, 500); + if(ret == 1) break; + if(ret == 0) errno = ETIMEDOUT; + if(errno == EINTR) continue; + g_printerr("talk_to_socket: poll failed while waiting for input (%s)\n", + strerror(errno)); + disconnect_cookie_socket(jar); + return NULL; + } + + /* get length of response */ + if(ioctl(jar->connection_fd, FIONREAD, &len) == -1) { + g_printerr("talk_to_socket: cannot find daemon response length, " + "ioctl failed (%s)\n", strerror(errno)); + disconnect_cookie_socket(jar); + return NULL; + } + + /* there was an empty response. */ + if(len == 0) + return g_strdup(""); + + /* there is a response, read it */ + result = g_malloc(len + 1); + if(!result) { + g_printerr("talk_to_socket: failed to allocate %d bytes\n", len); + return NULL; + } + result[len] = 0; /* ensure result is null terminated */ + + gchar *p = result; + while(len > 0) { + ret = read(jar->connection_fd, p, len); + if(ret == -1) { + g_printerr("talk_to_socket: failed to read from socket (%s)\n", + strerror(errno)); + disconnect_cookie_socket(jar); + g_free(result); + return NULL; + } else { + len -= ret; + p += ret; + } + } + + return result; +} diff --git a/src/cookie-jar.h b/src/cookie-jar.h new file mode 100644 index 0000000..80af00e --- /dev/null +++ b/src/cookie-jar.h @@ -0,0 +1,33 @@ +#ifndef UZBL_COOKIE_JAR_H +#define UZBL_COOKIE_JAR_H + +#include + +#define UZBL_TYPE_COOKIE_JAR (soup_cookie_jar_socket_get_type ()) +#define UZBL_COOKIE_JAR(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), UZBL_TYPE_COOKIE_JAR, UzblCookieJar)) +#define UZBL_COOKIE_JAR_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), UZBL_TYPE_COOKIE_JAR, UzblCookieJarClass)) + +typedef struct { + SoupCookieJar parent; + + const gchar *handler; + + const gchar *socket_path; + int connection_fd; + + gboolean in_get_callback; +} UzblCookieJar; + +typedef struct { + SoupCookieJarClass parent_class; +} UzblCookieJarClass; + +UzblCookieJar *uzbl_cookie_jar_new(); + +void +uzbl_cookie_jar_set_handler(UzblCookieJar *jar, const gchar *handler); + +char +*get_cookies(UzblCookieJar *jar, SoupURI *uri); + +#endif diff --git a/src/uzbl-core.c b/src/uzbl-core.c index 7be98c3..12d20ae 100644 --- a/src/uzbl-core.c +++ b/src/uzbl-core.c @@ -98,7 +98,7 @@ const struct var_name_to_ptr_t { { "title_format_short", PTR_V_STR(uzbl.behave.title_format_short, 1, NULL)}, { "icon", PTR_V_STR(uzbl.gui.icon, 1, set_icon)}, { "forward_keys", PTR_V_INT(uzbl.behave.forward_keys, 1, NULL)}, - { "cookie_handler", PTR_V_STR(uzbl.behave.cookie_handler, 1, NULL)}, + { "cookie_handler", PTR_V_STR(uzbl.behave.cookie_handler, 1, cmd_set_cookie_handler)}, { "authentication_handler", PTR_V_STR(uzbl.behave.authentication_handler, 1, set_authentication_handler)}, { "scheme_handler", PTR_V_STR(uzbl.behave.scheme_handler, 1, NULL)}, { "fifo_dir", PTR_V_STR(uzbl.behave.fifo_dir, 1, cmd_fifo_dir)}, @@ -679,7 +679,6 @@ struct {const char *key; CommandInfo value;} cmdlist[] = { "sync_spawn", {spawn_sync, 0} }, // needed for cookie handler { "sh", {spawn_sh, 0} }, { "sync_sh", {spawn_sh_sync, 0} }, // needed for cookie handler - { "talk_to_socket", {talk_to_socket, 0} }, { "exit", {close_uzbl, 0} }, { "search", {search_forward_text, TRUE} }, { "search_reverse", {search_reverse_text, TRUE} }, @@ -1431,118 +1430,6 @@ spawn_sh_sync(WebKitWebView *web_view, GArray *argv, GString *result) { g_strfreev (cmd); } -void -talk_to_socket(WebKitWebView *web_view, GArray *argv, GString *result) { - (void)web_view; (void)result; - - int fd, len; - struct sockaddr_un sa; - char* sockpath; - ssize_t ret; - struct pollfd pfd; - struct iovec* iov; - guint i; - - if(uzbl.comm.sync_stdout) uzbl.comm.sync_stdout = strfree(uzbl.comm.sync_stdout); - - /* This function could be optimised by storing a hash table of socket paths - and associated connected file descriptors rather than closing and - re-opening for every call. Also we could launch a script if socket connect - fails. */ - - /* First element argv[0] is path to socket. Following elements are tokens to - write to the socket. We write them as a single packet with each token - separated by an ASCII nul (\0). */ - if(argv->len < 2) { - g_printerr("talk_to_socket called with only %d args (need at least two).\n", - (int)argv->len); - return; - } - - /* copy socket path, null terminate result */ - sockpath = g_array_index(argv, char*, 0); - g_strlcpy(sa.sun_path, sockpath, sizeof(sa.sun_path)); - sa.sun_family = AF_UNIX; - - /* create socket file descriptor and connect it to path */ - fd = socket(AF_UNIX, SOCK_SEQPACKET, 0); - if(fd == -1) { - g_printerr("talk_to_socket: creating socket failed (%s)\n", strerror(errno)); - return; - } - if(connect(fd, (struct sockaddr*)&sa, sizeof(sa))) { - g_printerr("talk_to_socket: connect failed (%s)\n", strerror(errno)); - close(fd); - return; - } - - /* build request vector */ - iov = g_malloc(sizeof(struct iovec) * (argv->len - 1)); - if(!iov) { - g_printerr("talk_to_socket: unable to allocated memory for token vector\n"); - close(fd); - return; - } - for(i = 1; i < argv->len; ++i) { - iov[i - 1].iov_base = g_array_index(argv, char*, i); - iov[i - 1].iov_len = strlen(iov[i - 1].iov_base) + 1; /* string plus \0 */ - } - - /* write request */ - ret = writev(fd, iov, argv->len - 1); - g_free(iov); - if(ret == -1) { - g_printerr("talk_to_socket: write failed (%s)\n", strerror(errno)); - close(fd); - return; - } - - /* wait for a response, with a 500ms timeout */ - pfd.fd = fd; - pfd.events = POLLIN; - while(1) { - ret = poll(&pfd, 1, 500); - if(ret == 1) break; - if(ret == 0) errno = ETIMEDOUT; - if(errno == EINTR) continue; - g_printerr("talk_to_socket: poll failed while waiting for input (%s)\n", - strerror(errno)); - close(fd); - return; - } - - /* get length of response */ - if(ioctl(fd, FIONREAD, &len) == -1) { - g_printerr("talk_to_socket: cannot find daemon response length, " - "ioctl failed (%s)\n", strerror(errno)); - close(fd); - return; - } - - /* if there is a response, read it */ - if(len) { - uzbl.comm.sync_stdout = g_malloc(len + 1); - if(!uzbl.comm.sync_stdout) { - g_printerr("talk_to_socket: failed to allocate %d bytes\n", len); - close(fd); - return; - } - uzbl.comm.sync_stdout[len] = 0; /* ensure result is null terminated */ - - ret = read(fd, uzbl.comm.sync_stdout, len); - if(ret == -1) { - g_printerr("talk_to_socket: failed to read from socket (%s)\n", - strerror(errno)); - close(fd); - return; - } - } - - /* clean up */ - close(fd); - return; -} - void parse_command(const char *cmd, const char *param, GString *result) { CommandInfo *c; @@ -2125,8 +2012,7 @@ inject_handler_args(const gchar *actname, const gchar *origargs, const gchar *ne if ((g_strcmp0(actname, "spawn") == 0) || (g_strcmp0(actname, "sh") == 0) || (g_strcmp0(actname, "sync_spawn") == 0) || - (g_strcmp0(actname, "sync_sh") == 0) || - (g_strcmp0(actname, "talk_to_socket") == 0)) { + (g_strcmp0(actname, "sync_sh") == 0)) { guint i; GString *a = g_string_new(""); gchar **spawnparts = split_quoted(origargs, FALSE); @@ -2313,7 +2199,6 @@ settings_init () { if(s->connect_socket_names) init_connect_socket(); - g_signal_connect_after(n->soup_session, "request-started", G_CALLBACK(handle_cookies), NULL); g_signal_connect(n->soup_session, "authenticate", G_CALLBACK(handle_authentication), NULL); } @@ -2373,57 +2258,6 @@ void handle_authentication (SoupSession *session, SoupMessage *msg, SoupAuth *au } } -void handle_cookies (SoupSession *session, SoupMessage *msg, gpointer user_data) { - (void) session; - (void) user_data; - - soup_message_add_header_handler(msg, "got-headers", "Set-Cookie", G_CALLBACK(save_cookies_http), NULL); - GString *s = g_string_new (""); - SoupURI * soup_uri = soup_message_get_uri(msg); - g_string_printf(s, "GET '%s' '%s' '%s'", soup_uri->scheme, soup_uri->host, soup_uri->path); - - if(uzbl.behave.cookie_handler) - run_handler(uzbl.behave.cookie_handler, s->str); - - if(uzbl.behave.cookie_handler && - uzbl.comm.sync_stdout && strcmp (uzbl.comm.sync_stdout, "") != 0) { - char *p = strchr(uzbl.comm.sync_stdout, '\n' ); - if ( p != NULL ) *p = '\0'; - soup_message_headers_replace (msg->request_headers, "Cookie", (const char *) uzbl.comm.sync_stdout); - - int len = strlen(uzbl.comm.sync_stdout); - - if(len > 0) { - SoupCookie *soup_cookie; - char *cookies = (char *) g_malloc(len+1); - strncpy(cookies, uzbl.comm.sync_stdout, len+1); - - /* Disconnect to avoid recursion */ - g_object_disconnect(G_OBJECT(uzbl.net.soup_cookie_jar), "any_signal", G_CALLBACK(save_cookies_js), NULL, NULL); - - p = cookies - 1; - while(p != NULL) { - p = p + 1; - soup_cookie = soup_cookie_parse((const char *) p, soup_uri); - if (soup_cookie) { - if(soup_cookie->domain == NULL) - soup_cookie->domain = soup_uri->host; - soup_cookie_jar_add_cookie(uzbl.net.soup_cookie_jar, soup_cookie); - } - p = strchr(p, ';'); - } - - g_object_connect(G_OBJECT(uzbl.net.soup_cookie_jar), "signal::changed", G_CALLBACK(save_cookies_js), NULL, NULL); - g_free(cookies); - } - } - - if (uzbl.comm.sync_stdout) - uzbl.comm.sync_stdout = strfree(uzbl.comm.sync_stdout); - - g_string_free(s, TRUE); -} - void dump_var_hash(gpointer k, gpointer v, gpointer ud) { (void) ud; @@ -2522,9 +2356,8 @@ initialize(int argc, char *argv[]) { uzbl.net.soup_session = webkit_get_default_session(); - uzbl.net.soup_cookie_jar = soup_cookie_jar_new(); + uzbl.net.soup_cookie_jar = uzbl_cookie_jar_new(); soup_session_add_feature(uzbl.net.soup_session, SOUP_SESSION_FEATURE(uzbl.net.soup_cookie_jar)); - g_object_connect(G_OBJECT(uzbl.net.soup_cookie_jar), "signal::changed", G_CALLBACK(save_cookies_js), NULL, NULL); for(i=0; sigs[i]; i++) { if(setup_signal(sigs[i], catch_signal) == SIG_ERR) diff --git a/src/uzbl-core.h b/src/uzbl-core.h index 5760423..b4b251d 100644 --- a/src/uzbl-core.h +++ b/src/uzbl-core.h @@ -40,6 +40,8 @@ #include #include +#include "cookie-jar.h" + #define LENGTH(x) (sizeof x / sizeof x[0]) /* gui elements */ @@ -104,8 +106,8 @@ typedef struct { /* networking */ typedef struct { - SoupSession *soup_session; - SoupCookieJar *soup_cookie_jar; + SoupSession *soup_session; + UzblCookieJar *soup_cookie_jar; SoupLogger *soup_logger; char *proxy_url; char *useragent; @@ -277,9 +279,6 @@ gboolean run_command(const gchar *command, const guint npre, const gchar **args, const gboolean sync, char **output_stdout); -void -talk_to_socket(WebKitWebView *web_view, GArray *argv, GString *result); - void spawn(WebKitWebView *web_view, GArray *argv, GString *result); -- cgit v1.2.3