Mercurial > pidgin
changeset 21603:a4b6854737d5
Implement more of XEP-0065 to support sending files through a proxy. To avoid adding strings this close to a release, it only supports using a proxy that is discovered from the server, but we'll include an account option to manually specify a ft proxy in the next release. Lots of this is based on a patch from galt - Fixes #3730, #116, #1768
author | Daniel Atallah <daniel.atallah@gmail.com> |
---|---|
date | Wed, 21 Nov 2007 05:22:39 +0000 |
parents | 53fee49ce1c5 |
children | 2c45b94ab722 |
files | ChangeLog libpurple/protocols/jabber/disco.c libpurple/protocols/jabber/iq.c libpurple/protocols/jabber/jabber.c libpurple/protocols/jabber/jabber.h libpurple/protocols/jabber/libxmpp.c libpurple/protocols/jabber/si.c |
diffstat | 7 files changed, 375 insertions(+), 78 deletions(-) [+] |
line wrap: on
line diff
--- a/ChangeLog Wed Nov 21 02:53:27 2007 +0000 +++ b/ChangeLog Wed Nov 21 05:22:39 2007 +0000 @@ -16,6 +16,11 @@ implementation. * XMPP password changes that return errors no longer cause the saved password to be changed. + * XMPP file transfer support has been enhanced to support sending + files through a proxy when the server supports discovering a + a bytestream proxy. This should make file transfers much more + reliable. The next release will add support for manually specifying + a proxy when the server doesn't advertise one. Pidgin: * If a plugin says it can't be unloaded, we now display an error and
--- a/libpurple/protocols/jabber/disco.c Wed Nov 21 02:53:27 2007 +0000 +++ b/libpurple/protocols/jabber/disco.c Wed Nov 21 05:22:39 2007 +0000 @@ -44,6 +44,37 @@ xmlnode_set_attrib(feature, "var", x); \ } +static void +jabber_disco_bytestream_server_cb(JabberStream *js, xmlnode *packet, gpointer data) { + JabberBytestreamsStreamhost *sh = data; + const char *from = xmlnode_get_attrib(packet, "from"); + xmlnode *query = xmlnode_get_child_with_namespace(packet, "query", + "http://jabber.org/protocol/bytestreams"); + + if (from && !strcmp(from, sh->jid) && query != NULL) { + xmlnode *sh_node = xmlnode_get_child(query, "streamhost"); + if (sh_node) { + const char *jid = xmlnode_get_attrib(sh_node, "jid"); + const char *port = xmlnode_get_attrib(sh_node, "port"); + + + if (jid == NULL || strcmp(jid, from) != 0) + purple_debug_error("jabber", "Invalid jid(%s) for bytestream.\n", + jid ? jid : "(null)"); + + sh->host = g_strdup(xmlnode_get_attrib(sh_node, "host")); + sh->zeroconf = g_strdup(xmlnode_get_attrib(sh_node, "zeroconf")); + if (port != NULL) + sh->port = atoi(port); + } + } + + purple_debug_info("jabber", "Discovered bytestream proxy server: " + "jid='%s' host='%s' port='%d' zeroconf='%s'\n", + from ? from : "", sh->host ? sh->host : "", + sh->port, sh->zeroconf ? sh->zeroconf : ""); +} + void jabber_disco_info_parse(JabberStream *js, xmlnode *packet) { const char *from = xmlnode_get_attrib(packet, "from"); @@ -191,10 +222,26 @@ if(!strcmp(category, "conference") && !strcmp(type, "text")) { /* we found a groupchat or MUC server, add it to the list */ /* XXX: actually check for protocol/muc or gc-1.0 support */ - js->chat_servers = g_list_append(js->chat_servers, g_strdup(from)); + js->chat_servers = g_list_prepend(js->chat_servers, g_strdup(from)); } else if(!strcmp(category, "directory") && !strcmp(type, "user")) { /* we found a JUD */ - js->user_directories = g_list_append(js->user_directories, g_strdup(from)); + js->user_directories = g_list_prepend(js->user_directories, g_strdup(from)); + } else if(!strcmp(category, "proxy") && !strcmp(type, "bytestreams")) { + /* This is a bytestream proxy */ + JabberIq *iq; + JabberBytestreamsStreamhost *sh; + + purple_debug_info("jabber", "Found bytestream proxy server: %s\n", from); + + sh = g_new0(JabberBytestreamsStreamhost, 1); + sh->jid = g_strdup(from); + js->bs_proxies = g_list_prepend(js->bs_proxies, sh); + + iq = jabber_iq_new_query(js, JABBER_IQ_GET, + "http://jabber.org/protocol/bytestreams"); + xmlnode_set_attrib(iq->node, "to", sh->jid); + jabber_iq_set_callback(iq, jabber_disco_bytestream_server_cb, sh); + jabber_iq_send(iq); } } else if(!strcmp(child->name, "feature")) { @@ -344,8 +391,8 @@ g_free(js->server_name); js->server_name = g_strdup(name); if (!strcmp(name, "Google Talk")) { - purple_debug_info("jabber", "Google Talk!\n"); - js->googletalk = TRUE; + purple_debug_info("jabber", "Google Talk!\n"); + js->googletalk = TRUE; } } @@ -422,8 +469,7 @@ jabber_iq_set_callback(iq, jabber_disco_server_items_result_cb, NULL); jabber_iq_send(iq); - iq = jabber_iq_new_query(js, JABBER_IQ_GET, - "http://jabber.org/protocol/disco#info"); + iq = jabber_iq_new_query(js, JABBER_IQ_GET, "http://jabber.org/protocol/disco#info"); xmlnode_set_attrib(iq->node, "to", js->user->domain); jabber_iq_set_callback(iq, jabber_disco_server_info_result_cb, NULL); jabber_iq_send(iq);
--- a/libpurple/protocols/jabber/iq.c Wed Nov 21 02:53:27 2007 +0000 +++ b/libpurple/protocols/jabber/iq.c Wed Nov 21 05:22:39 2007 +0000 @@ -399,5 +399,6 @@ void jabber_iq_uninit(void) { g_hash_table_destroy(iq_handlers); + iq_handlers = NULL; }
--- a/libpurple/protocols/jabber/jabber.c Wed Nov 21 02:53:27 2007 +0000 +++ b/libpurple/protocols/jabber/jabber.c Wed Nov 21 05:22:39 2007 +0000 @@ -1234,20 +1234,31 @@ g_hash_table_destroy(js->buddies); if(js->chats) g_hash_table_destroy(js->chats); + while(js->chat_servers) { g_free(js->chat_servers->data); js->chat_servers = g_list_delete_link(js->chat_servers, js->chat_servers); } + while(js->user_directories) { g_free(js->user_directories->data); js->user_directories = g_list_delete_link(js->user_directories, js->user_directories); } - if(js->stream_id) - g_free(js->stream_id); + + while(js->bs_proxies) { + JabberBytestreamsStreamhost *sh = js->bs_proxies->data; + g_free(sh->jid); + g_free(sh->host); + g_free(sh->zeroconf); + g_free(sh); + js->bs_proxies = g_list_delete_link(js->bs_proxies, js->bs_proxies); + } + + g_free(js->stream_id); if(js->user) jabber_id_free(js->user); - if(js->avatar_hash) - g_free(js->avatar_hash); + g_free(js->avatar_hash); + purple_circ_buffer_destroy(js->write_buffer); if(js->writeh) purple_input_remove(js->writeh); @@ -1256,11 +1267,9 @@ sasl_dispose(&js->sasl); if(js->sasl_mechs) g_string_free(js->sasl_mechs, TRUE); - if(js->sasl_cb) - g_free(js->sasl_cb); + g_free(js->sasl_cb); #endif - if(js->serverFQDN) - g_free(js->serverFQDN); + g_free(js->serverFQDN); while(js->commands) { JabberAdHocCommands *cmd = js->commands->data; g_free(cmd->jid); @@ -1272,21 +1281,14 @@ g_free(js->server_name); g_free(js->gmail_last_time); g_free(js->gmail_last_tid); - if(js->old_msg) - g_free(js->old_msg); - if(js->old_avatarhash) - g_free(js->old_avatarhash); - if(js->old_artist) - g_free(js->old_artist); - if(js->old_title) - g_free(js->old_title); - if(js->old_source) - g_free(js->old_source); - if(js->old_uri) - g_free(js->old_uri); - if(js->old_track) - g_free(js->old_track); - + g_free(js->old_msg); + g_free(js->old_avatarhash); + g_free(js->old_artist); + g_free(js->old_title); + g_free(js->old_source); + g_free(js->old_uri); + g_free(js->old_track); + g_free(js); gc->proto_data = NULL;
--- a/libpurple/protocols/jabber/jabber.h Wed Nov 21 02:53:27 2007 +0000 +++ b/libpurple/protocols/jabber/jabber.h Wed Nov 21 05:22:39 2007 +0000 @@ -117,7 +117,7 @@ GHashTable *disco_callbacks; int next_id; - + GList *bs_proxies; GList *oob_file_transfers; GList *file_transfers; @@ -146,7 +146,7 @@ char *gmail_last_time; char *gmail_last_tid; - char *serverFQDN; + char *serverFQDN; /* OK, this stays at the end of the struct, so plugins can depend * on the rest of the stuff being in the right place @@ -202,6 +202,13 @@ JabberFeatureEnabled *is_enabled; } JabberFeature; +typedef struct _JabberBytestreamsStreamhost { + char *jid; + char *host; + int port; + char *zeroconf; +} JabberBytestreamsStreamhost; + /* what kind of additional features as returned from disco#info are supported? */ extern GList *jabber_features;
--- a/libpurple/protocols/jabber/libxmpp.c Wed Nov 21 02:53:27 2007 +0000 +++ b/libpurple/protocols/jabber/libxmpp.c Wed Nov 21 05:22:39 2007 +0000 @@ -233,7 +233,15 @@ prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option); - +#if 0 /* TODO: Enable this when we're string unfrozen */ + option = purple_account_option_string_new(_("File transfer proxies"), + "ft_proxies", + /* TODO: Is this an acceptable default? */ + "proxy.jabber.org:7777"); + prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, + option); +#endif + jabber_init_plugin(plugin); purple_prefs_remove("/plugins/prpl/jabber");
--- a/libpurple/protocols/jabber/si.c Wed Nov 21 02:53:27 2007 +0000 +++ b/libpurple/protocols/jabber/si.c Wed Nov 21 05:22:39 2007 +0000 @@ -36,19 +36,14 @@ #include "iq.h" #include "si.h" -#include "si.h" - -struct bytestreams_streamhost { - char *jid; - char *host; - int port; -}; +#define STREAMHOST_CONNECT_TIMEOUT 15 typedef struct _JabberSIXfer { JabberStream *js; PurpleProxyConnectData *connect_data; PurpleNetworkListenData *listen_data; + guint connect_timeout; gboolean accepted; @@ -99,39 +94,82 @@ JabberSIXfer *jsx = xfer->data; JabberIq *iq; xmlnode *query, *su; - struct bytestreams_streamhost *streamhost = jsx->streamhosts->data; + JabberBytestreamsStreamhost *streamhost = jsx->streamhosts->data; purple_proxy_info_destroy(jsx->gpi); + jsx->gpi = NULL; jsx->connect_data = NULL; + if (jsx->connect_timeout > 0) + purple_timeout_remove(jsx->connect_timeout); + jsx->connect_timeout = 0; + if(source < 0) { purple_debug_warning("jabber", "si connection failed, jid was %s, host was %s, error was %s\n", - streamhost->jid, streamhost->host, error_message); + streamhost->jid, streamhost->host, + error_message ? error_message : "(null)"); jsx->streamhosts = g_list_remove(jsx->streamhosts, streamhost); g_free(streamhost->jid); g_free(streamhost->host); + g_free(streamhost->zeroconf); g_free(streamhost); jabber_si_bytestreams_attempt_connect(xfer); return; } - iq = jabber_iq_new_query(jsx->js, JABBER_IQ_RESULT, "http://jabber.org/protocol/bytestreams"); - xmlnode_set_attrib(iq->node, "to", xfer->who); - jabber_iq_set_id(iq, jsx->iq_id); - query = xmlnode_get_child(iq->node, "query"); - su = xmlnode_new_child(query, "streamhost-used"); - xmlnode_set_attrib(su, "jid", streamhost->jid); + /* unknown file transfer type is assumed to be RECEIVE */ + if(xfer->type == PURPLE_XFER_SEND) + { + xmlnode *activate; + iq = jabber_iq_new_query(jsx->js, JABBER_IQ_SET, "http://jabber.org/protocol/bytestreams"); + xmlnode_set_attrib(iq->node, "to", streamhost->jid); + query = xmlnode_get_child(iq->node, "query"); + xmlnode_set_attrib(query, "sid", jsx->stream_id); + activate = xmlnode_new_child(query, "activate"); + xmlnode_insert_data(activate, xfer->who, -1); + + /* TODO: We need to wait for an activation result before starting */ + } + else + { + iq = jabber_iq_new_query(jsx->js, JABBER_IQ_RESULT, "http://jabber.org/protocol/bytestreams"); + xmlnode_set_attrib(iq->node, "to", xfer->who); + jabber_iq_set_id(iq, jsx->iq_id); + query = xmlnode_get_child(iq->node, "query"); + su = xmlnode_new_child(query, "streamhost-used"); + xmlnode_set_attrib(su, "jid", streamhost->jid); + } jabber_iq_send(iq); purple_xfer_start(xfer, source, NULL, -1); } +static gboolean +connect_timeout_cb(gpointer data) +{ + PurpleXfer *xfer = data; + JabberSIXfer *jsx = xfer->data; + + purple_debug_info("jabber", "Streamhost connection timeout of %d seconds exceeded.\n", STREAMHOST_CONNECT_TIMEOUT); + + jsx->connect_timeout = 0; + + if (jsx->connect_data != NULL) + purple_proxy_connect_cancel(jsx->connect_data); + jsx->connect_data = NULL; + + /* Trigger the connect error manually */ + jabber_si_bytestreams_connect_cb(xfer, -1, "Timeout Exceeded."); + + return FALSE; +} + static void jabber_si_bytestreams_attempt_connect(PurpleXfer *xfer) { JabberSIXfer *jsx = xfer->data; - struct bytestreams_streamhost *streamhost; + JabberBytestreamsStreamhost *streamhost; char *dstaddr, *p; int i; unsigned char hashval[20]; @@ -160,18 +198,28 @@ streamhost = jsx->streamhosts->data; + jsx->connect_data = NULL; + if (jsx->gpi != NULL) + purple_proxy_info_destroy(jsx->gpi); + jsx->gpi = NULL; + dstjid = jabber_id_new(xfer->who); - if(dstjid != NULL) { + /* TODO: Deal with zeroconf */ + + if(dstjid != NULL && streamhost->host && streamhost->port > 0) { jsx->gpi = purple_proxy_info_new(); purple_proxy_info_set_type(jsx->gpi, PURPLE_PROXY_SOCKS5); purple_proxy_info_set_host(jsx->gpi, streamhost->host); purple_proxy_info_set_port(jsx->gpi, streamhost->port); - - - dstaddr = g_strdup_printf("%s%s@%s/%s%s@%s/%s", jsx->stream_id, dstjid->node, dstjid->domain, dstjid->resource, jsx->js->user->node, - jsx->js->user->domain, jsx->js->user->resource); + /* unknown file transfer type is assumed to be RECEIVE */ + if(xfer->type == PURPLE_XFER_SEND) + dstaddr = g_strdup_printf("%s%s@%s/%s%s@%s/%s", jsx->stream_id, jsx->js->user->node, jsx->js->user->domain, + jsx->js->user->resource, dstjid->node, dstjid->domain, dstjid->resource); + else + dstaddr = g_strdup_printf("%s%s@%s/%s%s@%s/%s", jsx->stream_id, dstjid->node, dstjid->domain, dstjid->resource, + jsx->js->user->node, jsx->js->user->domain, jsx->js->user->resource); purple_cipher_digest_region("sha1", (guchar *)dstaddr, strlen(dstaddr), sizeof(hashval), hashval, NULL); @@ -186,6 +234,11 @@ jabber_si_bytestreams_connect_cb, xfer); g_free(dstaddr); + /* When selecting a streamhost, timeout after STREAMHOST_CONNECT_TIMEOUT seconds, otherwise it takes forever */ + if (xfer->type != PURPLE_XFER_SEND && jsx->connect_data != NULL) + jsx->connect_timeout = purple_timeout_add_seconds( + STREAMHOST_CONNECT_TIMEOUT, connect_timeout_cb, xfer); + jabber_id_free(dstjid); } @@ -194,6 +247,7 @@ jsx->streamhosts = g_list_remove(jsx->streamhosts, streamhost); g_free(streamhost->jid); g_free(streamhost->host); + g_free(streamhost->zeroconf); g_free(streamhost); jabber_si_bytestreams_attempt_connect(xfer); } @@ -232,17 +286,19 @@ for(streamhost = xmlnode_get_child(query, "streamhost"); streamhost; streamhost = xmlnode_get_next_twin(streamhost)) { - const char *jid, *host, *port; - int portnum; + const char *jid, *host = NULL, *port, *zeroconf; + int portnum = 0; if((jid = xmlnode_get_attrib(streamhost, "jid")) && - (host = xmlnode_get_attrib(streamhost, "host")) && + ((zeroconf = xmlnode_get_attrib(streamhost, "zeroconf")) || + ((host = xmlnode_get_attrib(streamhost, "host")) && (port = xmlnode_get_attrib(streamhost, "port")) && - (portnum = atoi(port))) { - struct bytestreams_streamhost *sh = g_new0(struct bytestreams_streamhost, 1); + (portnum = atoi(port))))) { + JabberBytestreamsStreamhost *sh = g_new0(JabberBytestreamsStreamhost, 1); sh->jid = g_strdup(jid); sh->host = g_strdup(host); sh->port = portnum; + sh->zeroconf = g_strdup(zeroconf); jsx->streamhosts = g_list_append(jsx->streamhosts, sh); } } @@ -351,7 +407,7 @@ jsx->js->user->resource, xfer->who); purple_cipher_digest_region("sha1", (guchar *)dstaddr, strlen(dstaddr), - sizeof(hashval), hashval, NULL); + sizeof(hashval), hashval, NULL); g_free(dstaddr); dstaddr = g_malloc(41); p = dstaddr; @@ -363,9 +419,12 @@ purple_debug_error("jabber", "someone connected with the wrong info!\n"); close(source); purple_xfer_cancel_remote(xfer); + g_free(dstaddr); return; } + g_free(dstaddr); + g_free(jsx->rxqueue); host = purple_network_get_my_ip(jsx->js->fd); @@ -523,6 +582,32 @@ source, PURPLE_INPUT_WRITE); } +static gint +jabber_si_compare_jid(gconstpointer a, gconstpointer b) +{ + const JabberBytestreamsStreamhost *sh = a; + + if(!a) + return -1; + + return strcmp(sh->jid, (char *)b); +} + + +static void +jabber_si_free_streamhost(gpointer data, gpointer user_data) +{ + JabberBytestreamsStreamhost *sh = data; + + if(!data) + return; + + g_free(sh->jid); + g_free(sh->host); + g_free(sh->zeroconf); + g_free(sh); +} + static void jabber_si_xfer_bytestreams_send_connected_cb(gpointer data, gint source, PurpleInputCondition cond) @@ -537,6 +622,7 @@ return; else if(acceptfd == -1) { purple_debug_warning("jabber", "accept: %s\n", g_strerror(errno)); + /* TODO: This should cancel the ft */ return; } @@ -544,7 +630,61 @@ close(source); xfer->watcher = purple_input_add(acceptfd, PURPLE_INPUT_READ, - jabber_si_xfer_bytestreams_send_read_cb, xfer); + jabber_si_xfer_bytestreams_send_read_cb, xfer); +} + +static void +jabber_si_connect_proxy_cb(JabberStream *js, xmlnode *packet, + gpointer data) +{ + PurpleXfer *xfer = data; + JabberSIXfer *jsx = xfer->data; + xmlnode *query, *streamhost_used; + const char *from, *type, *jid; + GList *matched; + + /* TODO: This need to send errors if we don't see what we're looking for */ + + /* In the case of a direct file transfer, this is expected to return */ + if(!jsx) + return; + + if(!(type = xmlnode_get_attrib(packet, "type")) || strcmp(type, "result")) + return; + + if(!(from = xmlnode_get_attrib(packet, "from"))) + return; + + if(!(query = xmlnode_get_child(packet, "query"))) + return; + + if(!(streamhost_used = xmlnode_get_child(query, "streamhost-used"))) + return; + + if(!(jid = xmlnode_get_attrib(streamhost_used, "jid"))) + return; + + if(!(matched = g_list_find_custom(jsx->streamhosts, jid, jabber_si_compare_jid))) + { + gchar *my_jid = g_strdup_printf("%s@%s/%s", jsx->js->user->node, + jsx->js->user->domain, jsx->js->user->resource); + if (!strcmp(jid, my_jid)) + purple_debug_info("jabber", "Got local SOCKS5 streamhost-used.\n"); + else + purple_debug_info("jabber", "streamhost-used does not match any proxy that was offered to target\n"); + g_free(my_jid); + return; + } + + /* TODO: Clean up the local SOCKS5 proxy - it isn't going to be used.*/ + + jsx->streamhosts = g_list_remove_link(jsx->streamhosts, matched); + g_list_foreach(jsx->streamhosts, jabber_si_free_streamhost, NULL); + g_list_free(jsx->streamhosts); + + jsx->streamhosts = matched; + + jabber_si_bytestreams_attempt_connect(xfer); } static void @@ -554,7 +694,10 @@ JabberSIXfer *jsx; JabberIq *iq; xmlnode *query, *streamhost; - char *jid, *port; + char *jid, port[6]; + const char *local_ip, *public_ip, *ft_proxies; + GList *tmp; + JabberBytestreamsStreamhost *sh, *sh2; jsx = xfer->data; jsx->listen_data = NULL; @@ -578,27 +721,106 @@ xmlnode_set_attrib(query, "sid", jsx->stream_id); - streamhost = xmlnode_new_child(query, "streamhost"); jid = g_strdup_printf("%s@%s/%s", jsx->js->user->node, jsx->js->user->domain, jsx->js->user->resource); - xmlnode_set_attrib(streamhost, "jid", jid); + xfer->local_port = purple_network_get_port_from_fd(sock); + g_snprintf(port, sizeof(port), "%hu", xfer->local_port); + + /* TODO: Should there be an option to not use the local host as a ft proxy? + * (to prevent revealing IP address, etc.) */ + + /* Include the localhost's IP (for in-network transfers) */ + local_ip = purple_network_get_local_system_ip(jsx->js->fd); + if (strcmp(local_ip, "0.0.0.0") != 0) + { + streamhost = xmlnode_new_child(query, "streamhost"); + xmlnode_set_attrib(streamhost, "jid", jid); + xmlnode_set_attrib(streamhost, "host", local_ip); + xmlnode_set_attrib(streamhost, "port", port); + } + + /* Include the public IP (assuming that there is a port mapped somehow) */ + /* TODO: Check that it isn't the same as above and is a valid IP */ + public_ip = purple_network_get_my_ip(jsx->js->fd); + if (strcmp(public_ip, local_ip) != 0) + { + streamhost = xmlnode_new_child(query, "streamhost"); + xmlnode_set_attrib(streamhost, "jid", jid); + xmlnode_set_attrib(streamhost, "host", public_ip); + xmlnode_set_attrib(streamhost, "port", port); + } + g_free(jid); - /* XXX: shouldn't we use the public IP or something? here */ - xmlnode_set_attrib(streamhost, "host", - purple_network_get_my_ip(jsx->js->fd)); - xfer->local_port = purple_network_get_port_from_fd(sock); - port = g_strdup_printf("%hu", xfer->local_port); - xmlnode_set_attrib(streamhost, "port", port); - g_free(port); - + /* The listener for the local proxy */ xfer->watcher = purple_input_add(sock, PURPLE_INPUT_READ, jabber_si_xfer_bytestreams_send_connected_cb, xfer); - /* XXX: insert proxies here */ + /* insert proxies here */ + ft_proxies = purple_account_get_string(xfer->account, "ft_proxies", NULL); + if (ft_proxies) { + int i, portnum; + char *tmp; + gchar **ft_proxy_list = g_strsplit(ft_proxies, ",", 0); + + g_list_foreach(jsx->streamhosts, jabber_si_free_streamhost, NULL); + g_list_free(jsx->streamhosts); + jsx->streamhosts = NULL; + + for(i = 0; ft_proxy_list[i]; i++) { + g_strstrip(ft_proxy_list[i]); + if(!(*ft_proxy_list[i])) + continue; + + if((tmp = strchr(ft_proxy_list[i], ':'))) { + portnum = atoi(tmp + 1); + *tmp = '\0'; + } else + portnum = 7777; + + g_snprintf(port, sizeof(port), "%hu", portnum); + + streamhost = xmlnode_new_child(query, "streamhost"); + xmlnode_set_attrib(streamhost, "jid", ft_proxy_list[i]); + xmlnode_set_attrib(streamhost, "host", ft_proxy_list[i]); + xmlnode_set_attrib(streamhost, "port", port); - /* XXX: callback to find out which streamhost they used, or see if they - * screwed it up */ + sh = g_new0(JabberBytestreamsStreamhost, 1); + sh->jid = g_strdup(ft_proxy_list[i]); + sh->host = g_strdup(ft_proxy_list[i]); + sh->port = portnum; + + jsx->streamhosts = g_list_prepend(jsx->streamhosts, sh); + } + + g_strfreev(ft_proxy_list); + } + + for (tmp = jsx->js->bs_proxies; tmp; tmp = tmp->next) { + sh = tmp->data; + + /* TODO: deal with zeroconf proxies */ + + if (!(sh->host && sh->port > 0)) + continue; + + streamhost = xmlnode_new_child(query, "streamhost"); + xmlnode_set_attrib(streamhost, "jid", sh->jid); + xmlnode_set_attrib(streamhost, "host", sh->host); + g_snprintf(port, sizeof(port), "%hu", sh->port); + xmlnode_set_attrib(streamhost, "port", port); + + sh2 = g_new0(JabberBytestreamsStreamhost, 1); + sh2->jid = g_strdup(sh->jid); + sh2->host = g_strdup(sh->host); + sh2->zeroconf = g_strdup(sh->zeroconf); + sh2->port = sh->port; + + jsx->streamhosts = g_list_prepend(jsx->streamhosts, sh2); + } + + jabber_iq_set_callback(iq, jabber_si_connect_proxy_cb, xfer); + jabber_iq_send(iq); } @@ -688,8 +910,7 @@ /* maybe later we'll do hash and date attribs */ feature = xmlnode_new_child(si, "feature"); - xmlnode_set_namespace(feature, - "http://jabber.org/protocol/feature-neg"); + xmlnode_set_namespace(feature, "http://jabber.org/protocol/feature-neg"); x = xmlnode_new_child(feature, "x"); xmlnode_set_namespace(x, "jabber:x:data"); xmlnode_set_attrib(x, "type", "form"); @@ -698,8 +919,7 @@ xmlnode_set_attrib(field, "type", "list-single"); option = xmlnode_new_child(field, "option"); value = xmlnode_new_child(option, "value"); - xmlnode_insert_data(value, "http://jabber.org/protocol/bytestreams", - -1); + xmlnode_insert_data(value, "http://jabber.org/protocol/bytestreams", -1); /* option = xmlnode_new_child(field, "option"); value = xmlnode_new_child(option, "value"); @@ -729,6 +949,14 @@ if (jsx->iq_id != NULL) jabber_iq_remove_callback_by_id(js, jsx->iq_id); + if (jsx->connect_timeout > 0) + purple_timeout_remove(jsx->connect_timeout); + + if (jsx->streamhosts) { + g_list_foreach(jsx->streamhosts, jabber_si_free_streamhost, NULL); + g_list_free(jsx->streamhosts); + } + g_free(jsx->stream_id); g_free(jsx->iq_id); /* XXX: free other stuff */