# HG changeset patch # User Paul Aurich # Date 1228435184 0 # Node ID 17b60b844803796485e1bf2934b669ba1bbe85ec # Parent 7de1f124f95ac6f35d3dfa243d7b2f87db1a0b25 Jabber BOSH: more fixes. Clean up some more of the structures and leaks Add jabber_bosh_(un)?init functions Properly send raw data (and add a _close function that terminates the stream) Normalize HTTP headers Throw a few more connection errors diff -r 7de1f124f95a -r 17b60b844803 libpurple/protocols/jabber/bosh.c --- a/libpurple/protocols/jabber/bosh.c Mon Dec 01 05:47:04 2008 +0000 +++ b/libpurple/protocols/jabber/bosh.c Thu Dec 04 23:59:44 2008 +0000 @@ -19,6 +19,7 @@ * */ #include "internal.h" +#include "core.h" #include "cipher.h" #include "debug.h" #include "prpl.h" @@ -37,20 +38,19 @@ typedef void (*PurpleBOSHConnectionConnectFunction)(PurpleBOSHConnection *conn); typedef void (*PurpleBOSHConnectionReceiveFunction)(PurpleBOSHConnection *conn, xmlnode *node); +static char *bosh_useragent = NULL; + struct _PurpleBOSHConnection { /* decoded URL */ char *host; int port; char *path; - char *user; - char *passwd; int rid; char *sid; int wait; JabberStream *js; - PurpleAccount *account; gboolean pipelining; PurpleHTTPConnection *conn_a; PurpleHTTPConnection *conn_b; @@ -64,16 +64,14 @@ int fd; char *host; int port; - int handle; int ie_handle; - PurpleConnection *conn; GQueue *requests; PurpleHTTPResponse *current_response; char *current_data; int current_len; - int pih; + int pih; /* what? */ PurpleHTTPConnectionConnectFunction connect_cb; PurpleHTTPConnectionConnectFunction disconnect_cb; void *userdata; @@ -108,17 +106,38 @@ static PurpleHTTPConnection* jabber_bosh_http_connection_init(const char *host, int port); static void jabber_bosh_http_connection_connect(PurpleHTTPConnection *conn); static void jabber_bosh_http_connection_send_request(PurpleHTTPConnection *conn, PurpleHTTPRequest *req); -static void jabber_bosh_http_connection_clean(PurpleHTTPConnection *conn); +static void jabber_bosh_http_connection_destroy(PurpleHTTPConnection *conn); -static void jabber_bosh_http_request_init(PurpleHTTPRequest *req, const char *method, const char *path, PurpleHTTPRequestCallback cb, void *userdata); +static PurpleHTTPRequest* jabber_bosh_http_request_init(const char *method, const char *path, PurpleHTTPRequestCallback cb, void *userdata); static void jabber_bosh_http_request_add_to_header(PurpleHTTPRequest *req, const char *field, const char *value); static void jabber_bosh_http_request_set_data(PurpleHTTPRequest *req, char *data, int len); -static void jabber_bosh_http_request_clean(PurpleHTTPRequest *req); +static void jabber_bosh_http_request_destroy(PurpleHTTPRequest *req); + +static PurpleHTTPResponse* jabber_bosh_http_response_init(void); +static void jabber_bosh_http_response_destroy(PurpleHTTPResponse *res); + +void jabber_bosh_init(void) +{ + GHashTable *ui_info = purple_core_get_ui_info(); + const char *ui_version = NULL; + + if (ui_info) + ui_version = g_hash_table_lookup(ui_info, "version"); -static void jabber_bosh_http_response_init(PurpleHTTPResponse *res); -static void jabber_bosh_http_response_clean(PurpleHTTPResponse *res); + if (ui_version) + bosh_useragent = g_strdup_printf("%s (libpurple " VERSION ")", ui_version); + else + bosh_useragent = g_strdup("libpurple " VERSION); +} -PurpleBOSHConnection* jabber_bosh_connection_init(JabberStream *js, const char *url) { +void jabber_bosh_uninit(void) +{ + g_free(bosh_useragent); + bosh_useragent = NULL; +} + +PurpleBOSHConnection* jabber_bosh_connection_init(JabberStream *js, const char *url) +{ PurpleBOSHConnection *conn; char *host, *path, *user, *passwd; int port; @@ -132,16 +151,18 @@ conn->host = host; conn->port = port; conn->path = path; - conn->user = user; - conn->passwd = passwd; conn->pipelining = TRUE; - if (conn->user || conn->passwd) { - purple_debug_info("jabber", "Ignoring unsupported BOSH HTTP " - "Authentication username and password.\n"); + if ((user && user[0] != '\0') || (passwd && passwd[0] != '\0')) { + purple_debug_info("jabber", "Ignoring unexpected username and password " + "in BOSH URL.\n"); } + g_free(user); + g_free(passwd); + conn->js = js; + /* FIXME: This doesn't seem very random */ conn->rid = rand() % 100000 + 1728679472; conn->ready = FALSE; conn->conn_a = jabber_bosh_http_connection_init(conn->host, conn->port); @@ -150,6 +171,35 @@ return conn; } +void jabber_bosh_connection_destroy(PurpleBOSHConnection *conn) +{ + g_free(conn->host); + g_free(conn->path); + + if (conn->conn_a) + jabber_bosh_http_connection_destroy(conn->conn_a); + if (conn->conn_b) + jabber_bosh_http_connection_destroy(conn->conn_b); + + g_free(conn); +} + +void jabber_bosh_connection_close(PurpleBOSHConnection *conn) +{ + xmlnode *packet = xmlnode_new("body"); + char *tmp; + + xmlnode_set_attrib(packet, "type", "terminate"); + xmlnode_set_attrib(packet, "xmlns", "http://jabber.org/protocol/httpbind"); + xmlnode_set_attrib(packet, "sid", conn->sid); + tmp = g_strdup_printf("%d", ++conn->rid); + xmlnode_set_attrib(packet, "rid", tmp); + g_free(tmp); + + jabber_bosh_connection_send_native(conn, packet); + xmlnode_free(packet); +} + static void jabber_bosh_connection_stream_restart(PurpleBOSHConnection *conn) { xmlnode *restart = xmlnode_new("body"); char *tmp = NULL; @@ -164,6 +214,7 @@ xmlnode_set_attrib(restart, "xmlns:xmpp", "urn:xmpp:xbosh"); jabber_bosh_connection_send_native(conn, restart); + xmlnode_free(restart); } static gboolean jabber_bosh_connection_error_check(PurpleBOSHConnection *conn, xmlnode *node) { @@ -242,20 +293,22 @@ int major = atoi(version); int minor = atoi(dot + 1); + purple_debug_info("jabber", "BOSH connection manager version %s\n", version); + if (major > 1 || (major == 1 && minor >= 6)) { xmlnode *packet = xmlnode_get_child(node, "features"); conn->js->use_bosh = TRUE; conn->receive_cb = jabber_bosh_connection_auth_response; jabber_stream_features_parse(conn->js, packet); } else { - purple_debug_info("jabber", "Unsupported version of BOSH protocol. The connection manager must at least support version 1.6!\n"); - /* XXX This *must* handle this by killing the connection and - * reporting an error. */ + purple_connection_error_reason(conn->js->gc, + PURPLE_CONNECTION_ERROR_NETWORK_ERROR, + _("Unsupported version of BOSH protocol")); } g_free(version); } else { - purple_debug_info("jabber", "Missing version in session creation response!\n"); + purple_debug_info("jabber", "Missing version in BOSH initiation\n"); } } @@ -279,6 +332,7 @@ conn->receive_cb = jabber_bosh_connection_boot_response; jabber_bosh_connection_send_native(conn, init); + xmlnode_free(init); } static void jabber_bosh_connection_http_received_cb(PurpleHTTPRequest *req, PurpleHTTPResponse *res, void *userdata) { @@ -312,16 +366,35 @@ if (conn->ready == TRUE) xmlnode_set_attrib(node, "xmlns", "jabber:client"); } jabber_bosh_connection_send_native(conn, packet); + xmlnode_free(packet); +} + +void jabber_bosh_connection_send_raw(PurpleBOSHConnection *conn, + const char *data, int len) +{ + xmlnode *node = xmlnode_from_str(data, len); + if (node) { + jabber_bosh_connection_send(conn, node); + xmlnode_free(node); + } else { + /* + * This best emulates what a normal XMPP server would do + * if you send bad XML. + */ + purple_connection_error_reason(conn->js->gc, + PURPLE_CONNECTION_ERROR_NETWORK_ERROR, + _("Cannot send malformed XML")); + } } static void jabber_bosh_connection_send_native(PurpleBOSHConnection *conn, xmlnode *node) { - PurpleHTTPRequest *request = g_new0(PurpleHTTPRequest, 1); + PurpleHTTPRequest *request; char *txt = xmlnode_to_formatted_str(node, NULL); printf("\njabber_bosh_connection_send\n%s\n", txt); g_free(txt); - jabber_bosh_http_request_init(request, "POST", g_strdup_printf("/%s", conn->path), jabber_bosh_connection_http_received_cb, conn); + request = jabber_bosh_http_request_init("POST", g_strdup_printf("/%s", conn->path), jabber_bosh_connection_http_received_cb, conn); jabber_bosh_http_request_add_to_header(request, "Content-Encoding", "text/xml; charset=utf-8"); request->data = xmlnode_to_str(node, &(request->data_len)); jabber_bosh_http_request_add_to_header(request, "Content-Length", g_strdup_printf("%d", (int)strlen(request->data))); @@ -377,7 +450,7 @@ value = beginning + 1; } else if (*beginning == '\r') { *beginning = 0; - g_hash_table_replace(header, g_strdup(field), g_strdup(value)); + g_hash_table_replace(header, g_ascii_strdown(field, -1), g_strdup(value)); value = field = 0; ++beginning; } @@ -415,11 +488,11 @@ /* check for header footer */ char *found = g_strstr_len(conn->current_data, conn->current_len, "\r\n\r\n"); if (found) { - // new response - response = conn->current_response = g_new0(PurpleHTTPResponse, 1); - jabber_bosh_http_response_init(response); + /* New response */ + response = conn->current_response = jabber_bosh_http_response_init(); jabber_bosh_http_connection_receive_parse_header(response, &(conn->current_data), &(conn->current_len)); - response->data_len = atoi(g_hash_table_lookup(response->header, "Content-Length")); + /* XXX: Crash if there is no Content-Length header */ + response->data_len = atoi(g_hash_table_lookup(response->header, "content-length")); } else { printf("\nDid not receive HTTP header\n"); } @@ -445,61 +518,97 @@ if (request->cb) request->cb(request, response, conn->userdata); else purple_debug_info("jabber", "missing request callback!\n"); - jabber_bosh_http_request_clean(request); - jabber_bosh_http_response_clean(response); + jabber_bosh_http_request_destroy(request); + jabber_bosh_http_response_destroy(response); conn->current_response = NULL; - g_free(request); - g_free(response); } else { purple_debug_info("jabber", "received HTTP response but haven't requested anything yet.\n"); } } } } else if (len == 0) { - purple_input_remove(conn->ie_handle); - if (conn->disconnect_cb) conn->disconnect_cb(conn); + if (conn->ie_handle) { + purple_input_remove(conn->ie_handle); + conn->ie_handle = 0; + } + if (conn->disconnect_cb) + conn->disconnect_cb(conn); } else { purple_debug_info("jabber", "jabber_bosh_http_connection_receive: problem receiving data (%d)\n", len); } } -PurpleHTTPConnection *jabber_bosh_http_connection_init(const char *host, int port) +static PurpleHTTPConnection* jabber_bosh_http_connection_init(const char *host, int port) { PurpleHTTPConnection *conn = g_new0(PurpleHTTPConnection, 1); conn->host = g_strdup(host); conn->port = port; + conn->fd = -1; conn->requests = g_queue_new(); return conn; } -void jabber_bosh_http_connection_clean(PurpleHTTPConnection *conn) { - g_queue_free(conn->requests); +static void jabber_bosh_http_connection_destroy(PurpleHTTPConnection *conn) +{ + g_free(conn->current_data); + g_free(conn->host); + + if (conn->requests) { + g_queue_foreach(conn->requests, (GFunc)jabber_bosh_http_request_destroy, NULL); + g_queue_free(conn->requests); + } + + if (conn->current_response) + jabber_bosh_http_response_destroy(conn->current_response); + + if (conn->ie_handle) + purple_input_remove(conn->ie_handle); + if (conn->fd > 0) + close(conn->fd); + + g_free(conn); } -static void jabber_bosh_http_connection_callback(gpointer data, gint source, const gchar *error) { +static void jabber_bosh_http_connection_callback(gpointer data, gint source, const gchar *error) +{ PurpleHTTPConnection *conn = data; + PurpleBOSHConnection *bosh_conn = conn->userdata; + PurpleConnection *gc = bosh_conn->js->gc; + if (source < 0) { - purple_debug_info("jabber", "Couldn't connect becasue of: %s\n", error); + gchar *tmp; + tmp = g_strdup_printf(_("Could not establish a connection with the server:\n%s"), + error); + purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, tmp); + g_free(tmp); return; } + conn->fd = source; - if (conn->connect_cb) conn->connect_cb(conn); - else purple_debug_info("jabber", "No connect callback for HTTP connection.\n"); - conn->ie_handle = purple_input_add(conn->fd, PURPLE_INPUT_READ, jabber_bosh_http_connection_receive, conn); + + if (conn->connect_cb) + conn->connect_cb(conn); + + conn->ie_handle = purple_input_add(conn->fd, PURPLE_INPUT_READ, + jabber_bosh_http_connection_receive, conn); } -void jabber_bosh_http_connection_connect(PurpleHTTPConnection *conn) { +static void jabber_bosh_http_connection_connect(PurpleHTTPConnection *conn) +{ PurpleBOSHConnection *bosh_conn = conn->userdata; PurpleConnection *gc = bosh_conn->js->gc; PurpleAccount *account = purple_connection_get_account(gc); - if((purple_proxy_connect(&(conn->handle), account, conn->host, conn->port, jabber_bosh_http_connection_callback, conn)) == NULL) { - purple_debug_info("jabber", "Unable to connect to %s.\n", conn->host); + if ((purple_proxy_connect(conn, account, conn->host, conn->port, jabber_bosh_http_connection_callback, conn)) == NULL) { + purple_connection_error_reason(gc, + PURPLE_CONNECTION_ERROR_NETWORK_ERROR, + _("Unable to create socket")); } } -static void jabber_bosh_http_connection_send_request_add_field_to_string(gpointer key, gpointer value, gpointer user_data) { +static void jabber_bosh_http_connection_send_request_add_field_to_string(gpointer key, gpointer value, gpointer user_data) +{ char **ppacket = user_data; char *tmp = *ppacket; char *field = key; @@ -512,7 +621,8 @@ char *packet; char *tmp; jabber_bosh_http_request_add_to_header(req, "Host", conn->host); - jabber_bosh_http_request_add_to_header(req, "User-Agent", "libpurple"); + jabber_bosh_http_request_add_to_header(req, "User-Agent", bosh_useragent); + packet = tmp = g_strdup_printf("%s %s HTTP/1.1\r\n", req->method, req->path); g_hash_table_foreach(req->header, jabber_bosh_http_connection_send_request_add_field_to_string, &packet); tmp = packet; @@ -522,12 +632,16 @@ g_queue_push_tail(conn->requests, req); } -void jabber_bosh_http_request_init(PurpleHTTPRequest *req, const char *method, const char *path, PurpleHTTPRequestCallback cb, void *userdata) { +static PurpleHTTPRequest* jabber_bosh_http_request_init(const char *method, + const char *path, PurpleHTTPRequestCallback cb, void *userdata) +{ + PurpleHTTPRequest *req = g_new(PurpleHTTPRequest, 1); req->method = g_strdup(method); req->path = g_strdup(path); req->cb = cb; req->userdata = userdata; - req->header = g_hash_table_new(g_str_hash, g_str_equal); + req->header = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free); + return req; } static void jabber_bosh_http_request_add_to_header(PurpleHTTPRequest *req, const char *field, const char *value) { @@ -541,20 +655,25 @@ req->data_len = len; } -void jabber_bosh_http_request_clean(PurpleHTTPRequest *req) { +static void jabber_bosh_http_request_destroy(PurpleHTTPRequest *req) +{ g_hash_table_destroy(req->header); g_free(req->method); g_free(req->path); g_free(req->data); + g_free(req); } -void jabber_bosh_http_response_init(PurpleHTTPResponse *res) { - res->status = 0; - res->header = g_hash_table_new(g_str_hash, g_str_equal); +static PurpleHTTPResponse* jabber_bosh_http_response_init(void) +{ + PurpleHTTPResponse *res = g_new0(PurpleHTTPResponse, 1); + res->header = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free); + return res; } - -void jabber_bosh_http_response_clean(PurpleHTTPResponse *res) { +static void jabber_bosh_http_response_destroy(PurpleHTTPResponse *res) +{ g_hash_table_destroy(res->header); g_free(res->data); + g_free(res); } diff -r 7de1f124f95a -r 17b60b844803 libpurple/protocols/jabber/bosh.h --- a/libpurple/protocols/jabber/bosh.h Mon Dec 01 05:47:04 2008 +0000 +++ b/libpurple/protocols/jabber/bosh.h Thu Dec 04 23:59:44 2008 +0000 @@ -26,7 +26,14 @@ #include "jabber.h" +void jabber_bosh_init(void); +void jabber_bosh_uninit(void); + PurpleBOSHConnection* jabber_bosh_connection_init(JabberStream *js, const char *url); +void jabber_bosh_connection_destroy(PurpleBOSHConnection *conn); + void jabber_bosh_connection_connect(PurpleBOSHConnection *conn); +void jabber_bosh_connection_close(PurpleBOSHConnection *conn); void jabber_bosh_connection_send(PurpleBOSHConnection *conn, xmlnode *node); +void jabber_bosh_connection_send_raw(PurpleBOSHConnection *conn, const char *data, int len); #endif /* _PURPLE_JABBER_BOSH_H_ */ diff -r 7de1f124f95a -r 17b60b844803 libpurple/protocols/jabber/jabber.c --- a/libpurple/protocols/jabber/jabber.c Mon Dec 01 05:47:04 2008 +0000 +++ b/libpurple/protocols/jabber/jabber.c Thu Dec 04 23:59:44 2008 +0000 @@ -391,16 +391,10 @@ if (len == -1) len = strlen(data); - if (js->use_bosh) { - xmlnode *xnode = xmlnode_from_str(data, len); - if (xnode) jabber_bosh_connection_send(js->bosh, xnode); - else { - purple_connection_error_reason(js->gc, PURPLE_CONNECTION_ERROR_OTHER_ERROR, - _("Someone tried to send non-XML in a Jabber world.")); - } - } else { + if (js->use_bosh) + jabber_bosh_connection_send_raw(js->bosh, data, len); + else do_jabber_send_raw(js, data, len); - } } int jabber_prpl_send_raw(PurpleConnection *gc, const char *buf, int len) @@ -421,9 +415,13 @@ if(NULL == packet) return; - txt = xmlnode_to_str(packet, &len); - jabber_send_raw(js, txt, len); - g_free(txt); + if (js->use_bosh) + jabber_bosh_connection_send(js->bosh, packet); + else { + txt = xmlnode_to_str(packet, &len); + jabber_send_raw(js, txt, len); + g_free(txt); + } } static void jabber_pong_cb(JabberStream *js, xmlnode *packet, gpointer unused) @@ -592,6 +590,7 @@ if (!strcmp(token[0], "_xmpp-client-xbosh")) { purple_debug_info("jabber","Found alternative connection method using %s at %s.\n", token[0], token[1]); js->bosh = jabber_bosh_connection_init(js, token[1]); + js->use_bosh = TRUE; g_strfreev(token); break; } @@ -1342,8 +1341,12 @@ * if we were forcibly disconnected because it will crash * on some SSL backends. */ - if (!gc->disconnect_timeout) - jabber_send_raw(js, "", -1); + if (!gc->disconnect_timeout) { + if (js->use_bosh) + jabber_bosh_connection_close(js->bosh); + else + jabber_send_raw(js, "", -1); + } if (js->srv_query_data) purple_srv_cancel(js->srv_query_data); @@ -1359,6 +1362,9 @@ close(js->fd); } + if (js->bosh) + jabber_bosh_connection_destroy(js->bosh); + jabber_buddy_remove_all_pending_buddy_info_requests(js); jabber_parser_free(js); diff -r 7de1f124f95a -r 17b60b844803 libpurple/protocols/jabber/libxmpp.c --- a/libpurple/protocols/jabber/libxmpp.c Mon Dec 01 05:47:04 2008 +0000 +++ b/libpurple/protocols/jabber/libxmpp.c Thu Dec 04 23:59:44 2008 +0000 @@ -150,6 +150,7 @@ purple_signal_unregister(plugin, "jabber-sending-text"); /* reverse order of init_plugin */ + jabber_bosh_uninit(); jabber_data_uninit(); /* PEP things should be uninit via jabber_pep_uninit, not here */ jabber_pep_uninit(); @@ -293,6 +294,7 @@ /* PEP things should be init via jabber_pep_init, not here */ jabber_pep_init(); jabber_data_init(); + jabber_bosh_init(); #warning implement adding and retrieving own features via IPC API