# HG changeset patch # User Nathan Walp # Date 1076839898 0 # Node ID b5dbd18397163163d7530ce9088173c50486ec30 # Parent 7b57c3bd9db607e66c9da4833e2b6cdd1ff44bb8 [gaim-migrate @ 8985] this is jabber new-style file transfer receive support. this doesn't do much error checking or handling, but I managed to send pictures to myself from Exodus on my laptop in the living room, which would have taken twice as long were it not for VNC. i said i was going to bed 1, 2, and 3 hours ago. i should go to bed. committer: Tailor Script diff -r 7b57c3bd9db6 -r b5dbd1839716 src/ft.c --- a/src/ft.c Sun Feb 15 08:51:32 2004 +0000 +++ b/src/ft.c Sun Feb 15 10:11:38 2004 +0000 @@ -500,12 +500,8 @@ if (condition & GAIM_INPUT_READ) { r = gaim_xfer_read(xfer, &buffer); - if (r > 0) { + if (r > 0) fwrite(buffer, 1, r, xfer->dest_fp); - } else { - gaim_xfer_cancel_remote(xfer); - return; - } } else { size_t s = MIN(gaim_xfer_get_bytes_remaining(xfer), 4096); diff -r 7b57c3bd9db6 -r b5dbd1839716 src/protocols/jabber/iq.c --- a/src/protocols/jabber/iq.c Sun Feb 15 08:51:32 2004 +0000 +++ b/src/protocols/jabber/iq.c Sun Feb 15 10:11:38 2004 +0000 @@ -267,17 +267,16 @@ SUPPORT_FEATURE("jabber:iq:time") SUPPORT_FEATURE("jabber:iq:version") SUPPORT_FEATURE("jabber:x:conference") - /* SUPPORT_FEATURE("http://jabber.org/protocol/bytestreams") - */ SUPPORT_FEATURE("http://jabber.org/protocol/disco#info") SUPPORT_FEATURE("http://jabber.org/protocol/disco#items") +#if 0 + SUPPORT_FEATURE("http://jabber.org/protocol/ibb") +#endif SUPPORT_FEATURE("http://jabber.org/protocol/muc") SUPPORT_FEATURE("http://jabber.org/protocol/muc#user") - /* SUPPORT_FEATURE("http://jabber.org/protocol/si") SUPPORT_FEATURE("http://jabber.org/protocol/si/profile/file-transfer") - */ SUPPORT_FEATURE("http://jabber.org/protocol/xhtml-im") jabber_iq_send(iq); @@ -411,6 +410,9 @@ } else if(!strcmp(xmlns, "jabber:iq:oob")) { jabber_oob_parse(js, packet); return; + } else if(!strcmp(xmlns, "http://jabber.org/protocol/bytestreams")) { + jabber_bytestreams_parse(js, packet); + return; } } else if(!strcmp(type, "get")) { if(!strcmp(xmlns, "jabber:iq:last")) { @@ -441,6 +443,13 @@ return; } } + } else { + xmlnode *si; + if((si = xmlnode_get_child(packet, "si")) && (xmlns = xmlnode_get_attrib(si, "xmlns")) && + !strcmp(xmlns, "http://jabber.org/protocol/si")) { + jabber_si_parse(js, packet); + return; + } } /* If we got here, no pre-defined handlers got it, lets see if a special diff -r 7b57c3bd9db6 -r b5dbd1839716 src/protocols/jabber/oob.c --- a/src/protocols/jabber/oob.c Sun Feb 15 08:51:32 2004 +0000 +++ b/src/protocols/jabber/oob.c Sun Feb 15 10:11:38 2004 +0000 @@ -117,9 +117,6 @@ return 0; } -static void jabber_oob_xfer_cancel_send(GaimXfer *xfer) { -} - static void jabber_oob_xfer_cancel_recv(GaimXfer *xfer) { JabberOOBXfer *jox = xfer->data; JabberIq *iq; @@ -173,7 +170,6 @@ gaim_xfer_set_init_fnc(xfer, jabber_oob_xfer_init); gaim_xfer_set_end_fnc(xfer, jabber_oob_xfer_end); - gaim_xfer_set_cancel_send_fnc(xfer, jabber_oob_xfer_cancel_send); gaim_xfer_set_cancel_recv_fnc(xfer, jabber_oob_xfer_cancel_recv); gaim_xfer_set_read_fnc(xfer, jabber_oob_xfer_read); gaim_xfer_set_start_fnc(xfer, jabber_oob_xfer_start); diff -r 7b57c3bd9db6 -r b5dbd1839716 src/protocols/jabber/si.c --- a/src/protocols/jabber/si.c Sun Feb 15 08:51:32 2004 +0000 +++ b/src/protocols/jabber/si.c Sun Feb 15 10:11:38 2004 +0000 @@ -23,6 +23,7 @@ #include "ft.h" #include "network.h" #include "notify.h" +#include "sha.h" #include "util.h" #include "buddy.h" @@ -32,39 +33,308 @@ #include "si.h" -static GaimXfer *jabber_si_xfer_find_by_id(JabberStream *js, const char *id) +struct bytestreams_streamhost { + char *jid; + char *host; + int port; +}; + +typedef struct _JabberSIXfer { + JabberStream *js; + + char *stream_id; + char *iq_id; + + enum { + STREAM_METHOD_UNKNOWN = 0, + STREAM_METHOD_BYTESTREAMS = 2 << 1, + STREAM_METHOD_IBB = 2 << 2, + STREAM_METHOD_UNSUPPORTED = 2 << 31 + } stream_method; + + GList *streamhosts; + GaimProxyInfo *gpi; +} JabberSIXfer; + +static GaimXfer* +jabber_si_xfer_find(JabberStream *js, const char *sid, const char *from) { GList *xfers; - if(!id) + if(!sid || !from) return NULL; for(xfers = js->file_transfers; xfers; xfers = xfers->next) { GaimXfer *xfer = xfers->data; JabberSIXfer *jsx = xfer->data; - - if(!strcmp(jsx->id, id)) + if(!strcmp(jsx->stream_id, sid) && !strcmp(xfer->who, from)) return xfer; } return NULL; } -static void -jabber_si_xfer_ibb_start(JabberStream *js, xmlnode *packet, gpointer data) { + +static void jabber_si_bytestreams_attempt_connect(GaimXfer *xfer); + +static void jabber_si_bytestreams_connect_cb(gpointer data, gint source, GaimInputCondition cond) +{ GaimXfer *xfer = data; JabberSIXfer *jsx = xfer->data; + JabberIq *iq; + xmlnode *query, *su; + struct bytestreams_streamhost *streamhost = jsx->streamhosts->data; - /* Make sure we didn't get an error back */ + gaim_proxy_info_destroy(jsx->gpi); + + if(source < 0) { + jsx->streamhosts = g_list_remove(jsx->streamhosts, streamhost); + g_free(streamhost->jid); + g_free(streamhost->host); + 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); + + jabber_iq_send(iq); + + gaim_xfer_start(xfer, source, NULL, -1); +} + +static void jabber_si_bytestreams_attempt_connect(GaimXfer *xfer) +{ + JabberSIXfer *jsx = xfer->data; + struct bytestreams_streamhost *streamhost; + char *dstaddr, *p; + int i; + unsigned char hashval[20]; + + if(!jsx->streamhosts) { + JabberIq *iq = jabber_iq_new(jsx->js, JABBER_IQ_ERROR); + xmlnode *error, *condition; + + if(jsx->iq_id) + jabber_iq_set_id(iq, jsx->iq_id); + + xmlnode_set_attrib(iq->node, "to", xfer->who); + error = xmlnode_new_child(iq->node, "error"); + xmlnode_set_attrib(error, "code", "404"); + xmlnode_set_attrib(error, "type", "cancel"); + condition = xmlnode_new_child(error, "condition"); + xmlnode_set_attrib(condition, "xmlns", "urn:ietf:params:xml:ns:xmpp-stanzas"); + xmlnode_new_child(condition, "item-not-found"); + + jabber_iq_send(iq); + + gaim_xfer_cancel_local(xfer); + + return; + } + + streamhost = jsx->streamhosts->data; - /* XXX: OK, here we need to set up a g_idle thing to send messages - * until our eyes bleed, but do it without interfering with normal - * gaim operations. When we're done, we have to send a like - * we sent the to start this damn thing. If we're really - * fortunate, Exodus or someone else will implement something to test - * against soon */ + jsx->gpi = gaim_proxy_info_new(); + gaim_proxy_info_set_type(jsx->gpi, GAIM_PROXY_SOCKS5); + gaim_proxy_info_set_host(jsx->gpi, streamhost->host); + gaim_proxy_info_set_port(jsx->gpi, streamhost->port); + + dstaddr = g_strdup_printf("%s%s%s@%s/%s", jsx->stream_id, xfer->who, jsx->js->user->node, + jsx->js->user->domain, jsx->js->user->resource); + shaBlock((unsigned char *)dstaddr, strlen(dstaddr), hashval); + g_free(dstaddr); + dstaddr = g_malloc(41); + p = dstaddr; + for(i=0; i<20; i++, p+=2) + snprintf(p, 3, "%02x", hashval[i]); + + gaim_proxy_connect_socks5(jsx->gpi, dstaddr, 0, jabber_si_bytestreams_connect_cb, xfer); + g_free(dstaddr); +} + +void jabber_bytestreams_parse(JabberStream *js, xmlnode *packet) +{ + GaimXfer *xfer; + JabberSIXfer *jsx; + xmlnode *query, *streamhost; + const char *sid, *from; + + if(!(from = xmlnode_get_attrib(packet, "from"))) + return; + + if(!(query = xmlnode_get_child(packet, "query"))) + return; + + if(!(sid = xmlnode_get_attrib(query, "sid"))) + return; + + if(!(xfer = jabber_si_xfer_find(js, sid, from))) + return; + + jsx = xfer->data; + if(jsx->iq_id) + g_free(jsx->iq_id); + jsx->iq_id = g_strdup(xmlnode_get_attrib(packet, "id")); + + for(streamhost = xmlnode_get_child(query, "streamhost"); streamhost; + streamhost = xmlnode_get_next_twin(streamhost)) { + const char *jid, *host, *port; + int portnum; + + if((jid = xmlnode_get_attrib(streamhost, "jid")) && + (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); + sh->jid = g_strdup(jid); + sh->host = g_strdup(host); + sh->port = portnum; + jsx->streamhosts = g_list_append(jsx->streamhosts, sh); + } + } + + jabber_si_bytestreams_attempt_connect(xfer); } +static void jabber_si_xfer_init(GaimXfer *xfer) +{ + JabberSIXfer *jsx = xfer->data; + JabberIq *iq; + xmlnode *si, *feature, *x, *field, *value; + + iq = jabber_iq_new(jsx->js, JABBER_IQ_RESULT); + xmlnode_set_attrib(iq->node, "to", xfer->who); + if(jsx->iq_id) + jabber_iq_set_id(iq, jsx->iq_id); + + si = xmlnode_new_child(iq->node, "si"); + xmlnode_set_attrib(si, "xmlns", "http://jabber.org/protocol/si"); + + feature = xmlnode_new_child(si, "feature"); + xmlnode_set_attrib(feature, "xmlns", "http://jabber.org/protocol/feature-neg"); + + x = xmlnode_new_child(feature, "x"); + xmlnode_set_attrib(x, "xmlns", "jabber:x:data"); + xmlnode_set_attrib(x, "type", "form"); + + field = xmlnode_new_child(x, "field"); + xmlnode_set_attrib(field, "var", "stream-method"); + + value = xmlnode_new_child(field, "value"); + if(jsx->stream_method & STREAM_METHOD_BYTESTREAMS) + xmlnode_insert_data(value, "http://jabber.org/protocol/bytestreams", -1); + /* + else if(jsx->stream_method & STREAM_METHOD_IBB) + xmlnode_insert_data(value, "http://jabber.org/protocol/ibb", -1); + */ + + jabber_iq_send(iq); +} + +static ssize_t jabber_si_xfer_read(char **buffer, GaimXfer *xfer) { + char buf; + + if(read(xfer->fd, &buf, 1) == 1) { + if(buf == 0x00) + gaim_xfer_set_read_fnc(xfer, NULL); + } else { + gaim_debug_error("jabber", "Read error on bytestream transfer!\n"); + gaim_xfer_cancel_local(xfer); + } + + return 0; +} + + +void jabber_si_parse(JabberStream *js, xmlnode *packet) +{ + JabberSIXfer *jsx; + GaimXfer *xfer; + xmlnode *si, *file, *feature, *x, *field, *option, *value; + const char *stream_id, *filename, *filesize_c, *profile; + size_t filesize = 0; + + if(!(si = xmlnode_get_child(packet, "si"))) + return; + + if(!(profile = xmlnode_get_attrib(si, "profile")) || + strcmp(profile, "http://jabber.org/protocol/si/profile/file-transfer")) + return; + + if(!(stream_id = xmlnode_get_attrib(si, "id"))) + return; + + if(!(file = xmlnode_get_child(si, "file"))) + return; + + if(!(filename = xmlnode_get_attrib(file, "name"))) + return; + + if((filesize_c = xmlnode_get_attrib(file, "size"))) + filesize = atoi(filesize_c); + + if(!(feature = xmlnode_get_child(si, "feature"))) + return; + + if(!(x = xmlnode_get_child_with_namespace(feature, "x", "jabber:x:data"))) + return; + + jsx = g_new0(JabberSIXfer, 1); + + for(field = xmlnode_get_child(x, "field"); field; field = xmlnode_get_next_twin(field)) { + const char *var = xmlnode_get_attrib(field, "var"); + if(var && !strcmp(var, "stream-method")) { + for(option = xmlnode_get_child(field, "option"); option; + option = xmlnode_get_next_twin(option)) { + if((value = xmlnode_get_child(option, "value"))) { + char *val; + if((val = xmlnode_get_data(value))) { + if(!strcmp(val, "http://jabber.org/protocol/bytestreams")) { + jsx->stream_method |= STREAM_METHOD_BYTESTREAMS; + /* + } else if(!strcmp(val, "http://jabber.org/protocol/ibb")) { + jsx->stream_method |= STREAM_METHOD_IBB; + */ + } + g_free(val); + } + } + } + } + } + + if(jsx->stream_method == STREAM_METHOD_UNKNOWN) { + g_free(jsx); + return; + } + + jsx->js = js; + jsx->stream_id = g_strdup(stream_id); + jsx->iq_id = g_strdup(xmlnode_get_attrib(packet, "id")); + + xfer = gaim_xfer_new(js->gc->account, GAIM_XFER_RECEIVE, + xmlnode_get_attrib(packet, "from")); + xfer->data = jsx; + + gaim_xfer_set_filename(xfer, filename); + if(filesize > 0) + gaim_xfer_set_size(xfer, filesize); + + gaim_xfer_set_init_fnc(xfer, jabber_si_xfer_init); + gaim_xfer_set_read_fnc(xfer, jabber_si_xfer_read); + + js->file_transfers = g_list_append(js->file_transfers, xfer); + + gaim_xfer_request(xfer); +} + +#if 0 void jabber_si_parse(JabberStream *js, xmlnode *packet) { GaimXfer *xfer; @@ -267,3 +537,4 @@ gaim_debug(GAIM_DEBUG_INFO, "jabber", "in jabber_si_xfer_ack\n"); } +#endif diff -r 7b57c3bd9db6 -r b5dbd1839716 src/protocols/jabber/si.h --- a/src/protocols/jabber/si.h Sun Feb 15 08:51:32 2004 +0000 +++ b/src/protocols/jabber/si.h Sun Feb 15 10:11:38 2004 +0000 @@ -26,28 +26,7 @@ #include "jabber.h" -typedef struct _JabberSIXfer { - JabberStream *js; - - char *id; - char *resource; - - enum { - STREAM_METHOD_UNKNOWN, - STREAM_METHOD_BYTESTREAMS, - STREAM_METHOD_IBB, - STREAM_METHOD_UNSUPPORTED - } stream_method; -} JabberSIXfer; - +void jabber_bytestreams_parse(JabberStream *js, xmlnode *packet); void jabber_si_parse(JabberStream *js, xmlnode *packet); -void jabber_si_xfer_init(GaimXfer *xfer); -void jabber_si_xfer_start(GaimXfer *xfer); -void jabber_si_xfer_end(GaimXfer *xfer); -void jabber_si_xfer_cancel_send(GaimXfer *xfer); -void jabber_si_xfer_cancel_recv(GaimXfer *xfer); -void jabber_si_xfer_ack(GaimXfer *xfer, const char *buffer, size_t size); - - #endif /* _GAIM_JABBER_SI_H_ */ diff -r 7b57c3bd9db6 -r b5dbd1839716 src/proxy.c --- a/src/proxy.c Sun Feb 15 08:51:32 2004 +0000 +++ b/src/proxy.c Sun Feb 15 10:11:38 2004 +0000 @@ -1314,7 +1314,7 @@ gaim_input_remove(phb->inpa); gaim_debug(GAIM_DEBUG_INFO, "socks5 proxy", "Able to read again.\n"); - if (read(source, buf, 10) < 10) { + if (read(source, buf, 4) < 4) { gaim_debug(GAIM_DEBUG_WARNING, "socks5 proxy", "or not...\n"); close(source); @@ -1346,6 +1346,25 @@ return; } + /* Skip past BND.ADDR */ + switch(buf[3]) { + case 0x01: /* the address is a version-4 IP address, with a length of 4 octets */ + lseek(source, 4, SEEK_CUR); + break; + case 0x03: /* the address field contains a fully-qualified domain name. The first + octet of the address field contains the number of octets of name that + follow, there is no terminating NUL octet. */ + read(source, buf+4, 1); + lseek(source, buf[4], SEEK_CUR); + break; + case 0x04: /* the address is a version-6 IP address, with a length of 16 octets */ + lseek(source, 16, SEEK_CUR); + break; + } + + /* Skip past BND.PORT */ + lseek(source, 2, SEEK_CUR); + if (phb->account == NULL || gaim_account_get_connection(phb->account) != NULL) { @@ -1763,6 +1782,24 @@ connection_host_resolved, phb); } +int +gaim_proxy_connect_socks5(GaimProxyInfo *gpi, const char *host, int port, + GaimInputFunction func, gpointer data) +{ + struct PHB *phb; + + phb = g_new0(struct PHB, 1); + phb->gpi = gpi; + phb->func = func; + phb->data = data; + phb->host = g_strdup(host); + phb->port = port; + + return gaim_gethostbyname_async(gaim_proxy_info_get_host(gpi), gaim_proxy_info_get_port(gpi), + connection_host_resolved, phb); +} + + static void proxy_pref_cb(const char *name, GaimPrefType type, gpointer value, gpointer data) diff -r 7b57c3bd9db6 -r b5dbd1839716 src/proxy.h --- a/src/proxy.h Sun Feb 15 08:51:32 2004 +0000 +++ b/src/proxy.h Sun Feb 15 10:11:38 2004 +0000 @@ -238,6 +238,20 @@ int gaim_proxy_connect(GaimAccount *account, const char *host, int port, GaimInputFunction func, gpointer data); +/** + * Makes a connection through a SOCKS5 proxy. + * + * @param gpi The GaimProxyInfo specifying the proxy settings + * @param host The destination host. + * @param port The destination port. + * @param func The input handler function. + * @param data User-defined data. + * + * @return The socket handle. + */ +int gaim_proxy_connect_socks5(GaimProxyInfo *gpi, const char *host, int port, + GaimInputFunction func, gpointer data); + /*@}*/ #ifdef __cplusplus diff -r 7b57c3bd9db6 -r b5dbd1839716 src/xmlnode.c --- a/src/xmlnode.c Sun Feb 15 08:51:32 2004 +0000 +++ b/src/xmlnode.c Sun Feb 15 10:11:38 2004 +0000 @@ -183,7 +183,7 @@ } xmlnode* -xmlnode_get_child(xmlnode *parent, const char *name) +xmlnode_get_child_with_namespace(xmlnode *parent, const char *name, const char *ns) { xmlnode *x, *ret = NULL; char **names; @@ -196,19 +196,30 @@ child_name = names[1]; for(x = parent->child; x; x = x->next) { - if(x->type == XMLNODE_TYPE_TAG && name && !strcmp(parent_name, x->name)) { + const char *xmlns = NULL; + if(ns) + xmlns = xmlnode_get_attrib(x, "xmlns"); + + if(x->type == XMLNODE_TYPE_TAG && name && !strcmp(parent_name, x->name) + && (!ns || (xmlns && !strcmp(ns, xmlns)))) { ret = x; break; } } if(child_name && ret) - ret = xmlnode_get_child(x, child_name); + ret = xmlnode_get_child(ret, child_name); g_strfreev(names); return ret; } +xmlnode* +xmlnode_get_child(xmlnode *parent, const char *name) +{ + return xmlnode_get_child_with_namespace(parent, name, NULL); +} + char * xmlnode_get_data(xmlnode *node) { @@ -416,12 +427,18 @@ xmlnode *xmlnode_get_next_twin(xmlnode *node) { xmlnode *sibling; + const char *ns = xmlnode_get_attrib(node, "xmlns"); g_return_val_if_fail(node != NULL, NULL); g_return_val_if_fail(node->type == XMLNODE_TYPE_TAG, NULL); for(sibling = node->next; sibling; sibling = sibling->next) { - if(sibling->type == XMLNODE_TYPE_TAG && !strcmp(node->name, sibling->name)) + const char *xmlns; + if(ns) + xmlns = xmlnode_get_attrib(sibling, "xmlns"); + + if(sibling->type == XMLNODE_TYPE_TAG && !strcmp(node->name, sibling->name) && + (!ns || (xmlns && !strcmp(ns, xmlns)))) return sibling; } diff -r 7b57c3bd9db6 -r b5dbd1839716 src/xmlnode.h --- a/src/xmlnode.h Sun Feb 15 08:51:32 2004 +0000 +++ b/src/xmlnode.h Sun Feb 15 10:11:38 2004 +0000 @@ -46,6 +46,7 @@ xmlnode *xmlnode_new_child(xmlnode *parent, const char *name); void xmlnode_insert_child(xmlnode *parent, xmlnode *child); xmlnode *xmlnode_get_child(xmlnode *parent, const char *name); +xmlnode *xmlnode_get_child_with_namespace(xmlnode *parent, const char *name, const char *xmlns); xmlnode *xmlnode_get_next_twin(xmlnode *node); void xmlnode_insert_data(xmlnode *parent, const char *data, size_t size); char *xmlnode_get_data(xmlnode *node);