# HG changeset patch # User Nathan Walp # Date 1067568238 0 # Node ID b250288fa94848c48502e0a3f33077d097e32d3e # Parent c6ff3c1fb1b36202c811abd6c12329f88c9c2b24 [gaim-migrate @ 7990] this would be the non-working start of file transfer (the real way) for jabber also approximately eleventy billion jabber tweaks committer: Tailor Script diff -r c6ff3c1fb1b3 -r b250288fa948 src/protocols/jabber/JEPS --- a/src/protocols/jabber/JEPS Fri Oct 31 01:38:46 2003 +0000 +++ b/src/protocols/jabber/JEPS Fri Oct 31 02:43:58 2003 +0000 @@ -6,14 +6,18 @@ Presence (Invisible) 0022: DONE (replace?) Message Events -0030: NEED +0030: DONE Service Discovery 0045: DONE Multi-User Chat +0047: DONE + In-Band Bytestreams 0054: DONE (ugly) vCard 0060: NEED Pub-Sub +0065: DONE + SOCKS5 Bytestreams 0071: DONE (needs spec confirmation) XHTML-IM 0073: NEED @@ -42,9 +46,9 @@ Software Version 0093: NEED Roster Item Exchange -0095: NEED +0095: DONE Stream Initiation -0096: NEED +0096: DONE File Transfer Stream Initiation Profile 0100: NEED Gateway Interaction (Transports) diff -r c6ff3c1fb1b3 -r b250288fa948 src/protocols/jabber/Makefile.am --- a/src/protocols/jabber/Makefile.am Fri Oct 31 01:38:46 2003 +0000 +++ b/src/protocols/jabber/Makefile.am Fri Oct 31 02:43:58 2003 +0000 @@ -26,7 +26,9 @@ presence.c \ presence.h \ roster.c \ - roster.h + roster.h \ + si.c \ + si.h AM_CFLAGS = $(st) diff -r c6ff3c1fb1b3 -r b250288fa948 src/protocols/jabber/Makefile.mingw --- a/src/protocols/jabber/Makefile.mingw Fri Oct 31 01:38:46 2003 +0000 +++ b/src/protocols/jabber/Makefile.mingw Fri Oct 31 02:43:58 2003 +0000 @@ -80,6 +80,7 @@ parser.c \ presence.c \ roster.c \ + si.c \ win32/posix.uname.c diff -r c6ff3c1fb1b3 -r b250288fa948 src/protocols/jabber/auth.c --- a/src/protocols/jabber/auth.c Fri Oct 31 01:38:46 2003 +0000 +++ b/src/protocols/jabber/auth.c Fri Oct 31 02:43:58 2003 +0000 @@ -91,7 +91,7 @@ xmlnode_free(auth); } -static void auth_old_result_cb(JabberStream *js, xmlnode *packet) +static void auth_old_result_cb(JabberStream *js, xmlnode *packet, gpointer data) { const char *type = xmlnode_get_attrib(packet, "type"); @@ -122,7 +122,7 @@ jabber_stream_set_state(js, JABBER_STREAM_CONNECTED); } -static void auth_old_cb(JabberStream *js, xmlnode *packet) +static void auth_old_cb(JabberStream *js, xmlnode *packet, gpointer data) { JabberIq *iq; xmlnode *query, *x; @@ -163,7 +163,7 @@ xmlnode_insert_data(x, pw, -1); } - jabber_iq_set_callback(iq, auth_old_result_cb); + jabber_iq_set_callback(iq, auth_old_result_cb, NULL); jabber_iq_send(iq); } @@ -179,7 +179,7 @@ username = xmlnode_new_child(query, "username"); xmlnode_insert_data(username, js->user->node, -1); - jabber_iq_set_callback(iq, auth_old_cb); + jabber_iq_set_callback(iq, auth_old_cb, NULL); jabber_iq_send(iq); } @@ -225,8 +225,8 @@ y = g_strndup(result, 16); - a1 = g_strdup_printf("%s:%s:%s:%s@%s/%s", y, nonce, cnonce, jid->node, - jid->domain, jid->resource); + a1 = g_strdup_printf("%s:%s:%s:%s@%s", y, nonce, cnonce, jid->node, + jid->domain); md5_init(&ctx); md5_append(&ctx, a1, strlen(a1)); @@ -270,7 +270,14 @@ char *enc_out; GHashTable *parts; + if(!enc_in) { + gaim_connection_error(js->gc, _("Invalid response from server")); + return; + } + gaim_base64_decode(enc_in, &dec_in, NULL); + gaim_debug(GAIM_DEBUG_MISC, "jabber", "decoded challenge (%d): %s\n", + strlen(dec_in), dec_in); parts = parse_challenge(dec_in); @@ -331,8 +338,8 @@ g_string_append_printf(response, ",digest-uri=\"xmpp/%s\"", realm); g_string_append_printf(response, ",response=%s", auth_resp); g_string_append_printf(response, ",charset=utf-8"); - g_string_append_printf(response, ",authzid=\"%s@%s/%s\"", - js->user->node, js->user->domain, js->user->resource); + g_string_append_printf(response, ",authzid=\"%s@%s\"", + js->user->node, js->user->domain); g_free(auth_resp); g_free(cnonce); diff -r c6ff3c1fb1b3 -r b250288fa948 src/protocols/jabber/buddy.c --- a/src/protocols/jabber/buddy.c Fri Oct 31 01:38:46 2003 +0000 +++ b/src/protocols/jabber/buddy.c Fri Oct 31 02:43:58 2003 +0000 @@ -25,13 +25,14 @@ #include "notify.h" #include "request.h" #include "util.h" +#include "xmlnode.h" #include "buddy.h" #include "chat.h" #include "jabber.h" #include "iq.h" #include "presence.h" -#include "xmlnode.h" +#include "si.h" void jabber_buddy_free(JabberBuddy *jb) @@ -530,7 +531,7 @@ ******/ -static void jabber_vcard_parse(JabberStream *js, xmlnode *packet) +static void jabber_vcard_parse(JabberStream *js, xmlnode *packet, gpointer data) { GList *resources; const char *from = xmlnode_get_attrib(packet, "from"); @@ -789,7 +790,7 @@ vcard = xmlnode_new_child(iq->node, "vCard"); xmlnode_set_attrib(vcard, "xmlns", "vcard-temp"); - jabber_iq_set_callback(iq, jabber_vcard_parse); + jabber_iq_set_callback(iq, jabber_vcard_parse, NULL); jabber_iq_send(iq); } @@ -809,6 +810,31 @@ g_free(full_jid); } + + +static void jabber_buddy_ask_send_file(GaimConnection *gc, const char *name) +{ + JabberStream *js = gc->proto_data; + GaimXfer *xfer; + JabberSIXfer *jsx; + + xfer = gaim_xfer_new(gaim_connection_get_account(gc), GAIM_XFER_SEND, name); + + xfer->data = jsx = g_new0(JabberSIXfer, 1); + jsx->js = js; + + gaim_xfer_set_init_fnc(xfer, jabber_si_xfer_init); + gaim_xfer_set_start_fnc(xfer, jabber_si_xfer_start); + gaim_xfer_set_end_fnc(xfer, jabber_si_xfer_end); + gaim_xfer_set_cancel_send_fnc(xfer, jabber_si_xfer_cancel_send); + gaim_xfer_set_cancel_recv_fnc(xfer, jabber_si_xfer_cancel_recv); + gaim_xfer_set_ack_fnc(xfer, jabber_si_xfer_ack); + + js->file_transfers = g_list_append(js->file_transfers, xfer); + + gaim_xfer_request(xfer); +} + static void jabber_buddy_set_invisibility(JabberStream *js, const char *who, gboolean invisible) { @@ -869,6 +895,25 @@ struct proto_buddy_menu *pbm; JabberStream *js = gc->proto_data; JabberBuddy *jb = jabber_buddy_find(js, name, TRUE); + GList *resources; + gboolean has_file_xfer = FALSE; + + if(!jb) + return m; + + for(resources = jb->resources; resources; resources = resources->next) { + JabberBuddyResource *jbr = resources->data; + if(jbr->capabilities & JABBER_CAP_SI_FILE_XFER) + has_file_xfer = TRUE; + } + + if(has_file_xfer) { + pbm = g_new0(struct proto_buddy_menu, 1); + pbm->label = _("Send File"); + pbm->callback = jabber_buddy_ask_send_file; + pbm->gc = gc; + m = g_list_append(m, pbm); + } pbm = g_new0(struct proto_buddy_menu, 1); if(jb->invisible & JABBER_INVIS_BUDDY) { diff -r c6ff3c1fb1b3 -r b250288fa948 src/protocols/jabber/buddy.h --- a/src/protocols/jabber/buddy.h Fri Oct 31 01:38:46 2003 +0000 +++ b/src/protocols/jabber/buddy.h Fri Oct 31 02:43:58 2003 +0000 @@ -48,8 +48,11 @@ int state; char *status; enum { - JABBER_CAP_XHTML = 1 << 1, - JABBER_CAP_COMPOSING = 1 << 2 + JABBER_CAP_XHTML = 1 << 1, + JABBER_CAP_COMPOSING = 1 << 2, + JABBER_CAP_SI = 1 << 3, + JABBER_CAP_SI_FILE_XFER = 1 << 4, + JABBER_CAP_BYTESTREAMS = 1 << 5 } capabilities; } JabberBuddyResource; diff -r c6ff3c1fb1b3 -r b250288fa948 src/protocols/jabber/iq.c --- a/src/protocols/jabber/iq.c Fri Oct 31 01:38:46 2003 +0000 +++ b/src/protocols/jabber/iq.c Fri Oct 31 02:43:58 2003 +0000 @@ -22,9 +22,11 @@ #include "debug.h" #include "prefs.h" +#include "buddy.h" #include "iq.h" #include "oob.h" #include "roster.h" +#include "si.h" #ifdef _WIN32 #include "utsname.h" @@ -79,9 +81,16 @@ return iq; } -void jabber_iq_set_callback(JabberIq *iq, JabberCallback *callback) +typedef struct _JabberCallbackData { + JabberIqCallback *callback; + gpointer data; +} JabberCallbackData; + +void +jabber_iq_set_callback(JabberIq *iq, JabberIqCallback *callback, gpointer data) { iq->callback = callback; + iq->callback_data = data; } void jabber_iq_set_id(JabberIq *iq, const char *id) @@ -100,12 +109,17 @@ void jabber_iq_send(JabberIq *iq) { + JabberCallbackData *jcd; g_return_if_fail(iq != NULL); jabber_send(iq->js, iq->node); - if(iq->id && iq->callback) - g_hash_table_insert(iq->js->callbacks, g_strdup(iq->id), iq->callback); + if(iq->id && iq->callback) { + jcd = g_new0(JabberCallbackData, 1); + jcd->callback = iq->callback; + jcd->data = iq->callback_data; + g_hash_table_insert(iq->js->callbacks, g_strdup(iq->id), jcd); + } jabber_iq_free(iq); } @@ -205,35 +219,159 @@ jabber_iq_send(iq); } +#define SUPPORT_FEATURE(x) \ + feature = xmlnode_new_child(query, "feature"); \ + xmlnode_set_attrib(feature, "var", x); + + +void jabber_disco_info_parse(JabberStream *js, xmlnode *packet) { + const char *from = xmlnode_get_attrib(packet, "from"); + const char *type = xmlnode_get_attrib(packet, "type"); + + if(!from || !type) + return; + + if(!strcmp(type, "get")) { + xmlnode *query, *identity, *feature; + JabberIq *iq = jabber_iq_new_query(js, JABBER_IQ_RESULT, + "http://jabber.org/protocol/disco#info"); + + jabber_iq_set_id(iq, xmlnode_get_attrib(packet, "id")); + + xmlnode_set_attrib(iq->node, "to", from); + query = xmlnode_get_child(iq->node, "query"); + + identity = xmlnode_new_child(query, "identity"); + xmlnode_set_attrib(identity, "category", "client"); + xmlnode_set_attrib(identity, "type", "pc"); /* XXX: bot, console, + * handheld, pc, phone, + * web */ + + SUPPORT_FEATURE("jabber:iq:last") + SUPPORT_FEATURE("jabber:iq:oob") + 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") + 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") + + jabber_iq_send(iq); + }else if(!strcmp(type, "result")) { + xmlnode *query = xmlnode_get_child(packet, "query"); + xmlnode *child; + JabberID *jid; + JabberBuddy *jb; + JabberBuddyResource *jbr = NULL; + + if(!(jid = jabber_id_new(from))) + return; + + if(jid->resource && (jb = jabber_buddy_find(js, from, TRUE))) + jbr = jabber_buddy_find_resource(jb, jid->resource); + + if(!jbr) { + jabber_id_free(jid); + return; + } + + for(child = query->child; child; child = child->next) { + if(child->type != NODE_TYPE_TAG) + continue; + + if(!strcmp(child->name, "feature")) { + const char *var = xmlnode_get_attrib(child, "var"); + if(!var) + continue; + + if(!strcmp(var, "http://jabber.org/protocol/si")) + jbr->capabilities |= JABBER_CAP_SI; + else if(!strcmp(var, + "http://jabber.org/protocol/si/profile/file-transfer")) + jbr->capabilities |= JABBER_CAP_SI_FILE_XFER; + else if(!strcmp(var, "http://jabber.org/protocol/bytestreams")) + jbr->capabilities |= JABBER_CAP_BYTESTREAMS; + } + } + jabber_id_free(jid); + } +} + +void jabber_disco_items_parse(JabberStream *js, xmlnode *packet) { + const char *from = xmlnode_get_attrib(packet, "from"); + const char *type = xmlnode_get_attrib(packet, "type"); + + if(!strcmp(type, "get")) { + JabberIq *iq = jabber_iq_new_query(js, JABBER_IQ_RESULT, + "http://jabber.org/protocol/disco#items"); + + jabber_iq_set_id(iq, xmlnode_get_attrib(packet, "id")); + + xmlnode_set_attrib(iq->node, "to", from); + jabber_iq_send(iq); + } +} + void jabber_iq_parse(JabberStream *js, xmlnode *packet) { + JabberCallbackData *jcd; xmlnode *query; const char *xmlns; + const char *type, *id; query = xmlnode_get_child(packet, "query"); - if(!query) - return; + if(query) { - xmlns = xmlnode_get_attrib(query, "xmlns"); + xmlns = xmlnode_get_attrib(query, "xmlns"); - if(!xmlns) - return; + if(!xmlns) + return; - if(!strcmp(xmlns, "jabber:iq:roster")) { - jabber_roster_parse(js, packet); - } else if(!strcmp(xmlns, "jabber:iq:last")) { - jabber_iq_handle_last(js, packet); - } else if(!strcmp(xmlns, "jabber:iq:time")) { - jabber_iq_handle_time(js, packet); - } else if(!strcmp(xmlns, "jabber:iq:version")) { - jabber_iq_handle_version(js, packet); - } else if(!strcmp(xmlns, "jabber:iq:register")) { - jabber_register_parse(js, packet); - } else if(!strcmp(xmlns, "jabber:iq:oob")) { - jabber_oob_parse(js, packet); - } else { - gaim_debug(GAIM_DEBUG_WARNING, "jabber", "Unknown query: %s\n", xmlns); + if(!strcmp(xmlns, "jabber:iq:roster")) { + jabber_roster_parse(js, packet); + return; + } else if(!strcmp(xmlns, "jabber:iq:last")) { + jabber_iq_handle_last(js, packet); + return; + } else if(!strcmp(xmlns, "jabber:iq:time")) { + jabber_iq_handle_time(js, packet); + return; + } else if(!strcmp(xmlns, "jabber:iq:version")) { + jabber_iq_handle_version(js, packet); + return; + } else if(!strcmp(xmlns, "jabber:iq:register")) { + jabber_register_parse(js, packet); + return; + } else if(!strcmp(xmlns, "jabber:iq:oob")) { + jabber_oob_parse(js, packet); + return; + } else if(!strcmp(xmlns, "http://jabber.org/protocol/disco#info")) { + jabber_disco_info_parse(js, packet); + return; + } else if(!strcmp(xmlns, "http://jabber.org/protocol/disco#items")) { + jabber_disco_items_parse(js, packet); + return; + } + } else if(xmlnode_get_child(packet, "si")) { + jabber_si_parse(js, packet); + return; + } + + /* If we got here, no pre-defined handlers got it, lets see if a special + * callback got registered */ + + type = xmlnode_get_attrib(packet, "type"); + id = xmlnode_get_attrib(packet, "id"); + + if(type && (!strcmp(type, "result") || !strcmp(type, "error")) && id + && *id && (jcd = g_hash_table_lookup(js->callbacks, id))) { + jcd->callback(js, packet, jcd->data); + g_free(jcd); } } diff -r c6ff3c1fb1b3 -r b250288fa948 src/protocols/jabber/iq.h --- a/src/protocols/jabber/iq.h Fri Oct 31 01:38:46 2003 +0000 +++ b/src/protocols/jabber/iq.h Fri Oct 31 02:43:58 2003 +0000 @@ -34,12 +34,15 @@ JABBER_IQ_NONE } JabberIqType; +typedef void (JabberIqCallback)(JabberStream *js, xmlnode *packet, gpointer data); + struct _JabberIq { JabberIqType type; char *id; xmlnode *node; - JabberCallback *callback; + JabberIqCallback *callback; + gpointer callback_data; JabberStream *js; }; @@ -50,7 +53,7 @@ void jabber_iq_parse(JabberStream *js, xmlnode *packet); -void jabber_iq_set_callback(JabberIq *iq, JabberCallback *cb); +void jabber_iq_set_callback(JabberIq *iq, JabberIqCallback *cb, gpointer data); void jabber_iq_set_id(JabberIq *iq, const char *id); void jabber_iq_send(JabberIq *iq); diff -r c6ff3c1fb1b3 -r b250288fa948 src/protocols/jabber/jabber.c --- a/src/protocols/jabber/jabber.c Fri Oct 31 01:38:46 2003 +0000 +++ b/src/protocols/jabber/jabber.c Fri Oct 31 02:43:58 2003 +0000 @@ -63,8 +63,8 @@ open_stream = g_strdup_printf("", - /* "version='1.0'>" */ + "xmlns:stream='http://etherx.jabber.org/streams' " + "version='1.0'>", js->user->domain); jabber_send_raw(js, open_stream); @@ -72,7 +72,8 @@ g_free(open_stream); } -static void jabber_session_initialized_cb(JabberStream *js, xmlnode *packet) +static void +jabber_session_initialized_cb(JabberStream *js, xmlnode *packet, gpointer data) { const char *type = xmlnode_get_attrib(packet, "type"); if(type && !strcmp(type, "result")) { @@ -87,7 +88,7 @@ JabberIq *iq = jabber_iq_new(js, JABBER_IQ_SET); xmlnode *session; - jabber_iq_set_callback(iq, jabber_session_initialized_cb); + jabber_iq_set_callback(iq, jabber_session_initialized_cb, NULL); session = xmlnode_new_child(iq->node, "session"); xmlnode_set_attrib(session, "xmlns", "urn:ietf:params:xml:ns:xmpp-session"); @@ -95,6 +96,32 @@ jabber_iq_send(iq); } +static void jabber_bind_result_cb(JabberStream *js, xmlnode *packet, + gpointer data) +{ + /* XXX: check for errors, re-set our ow js->user JID */ + + jabber_session_init(js); +} + +static void jabber_stream_features_parse(JabberStream *js, xmlnode *packet) +{ + if(xmlnode_get_child(packet, "mechanisms")) { + jabber_auth_start(js, packet); + } else if(xmlnode_get_child(packet, "bind")) { + xmlnode *bind, *resource; + JabberIq *iq = jabber_iq_new(js, JABBER_IQ_SET); + bind = xmlnode_new_child(iq->node, "bind"); + xmlnode_set_attrib(bind, "xmlns", "urn:ietf:params:xml:ns:xmpp-bind"); + resource = xmlnode_new_child(bind, "resource"); + xmlnode_insert_data(resource, js->user->resource, -1); + + jabber_iq_set_callback(iq, jabber_bind_result_cb, NULL); + + jabber_iq_send(iq); + } +} + static void jabber_stream_handle_error(JabberStream *js, xmlnode *packet) { xmlnode *textnode; @@ -171,29 +198,14 @@ void jabber_process_packet(JabberStream *js, xmlnode *packet) { - const char *id = xmlnode_get_attrib(packet, "id"); - const char *type = xmlnode_get_attrib(packet, "type"); - JabberCallback *callback; - if(!strcmp(packet->name, "iq")) { - if(type && (!strcmp(type, "result") || !strcmp(type, "error")) && id - && *id && (callback = g_hash_table_lookup(js->callbacks, id))) - callback(js, packet); - else - jabber_iq_parse(js, packet); + jabber_iq_parse(js, packet); } else if(!strcmp(packet->name, "presence")) { jabber_presence_parse(js, packet); } else if(!strcmp(packet->name, "message")) { jabber_message_parse(js, packet); } else if(!strcmp(packet->name, "stream:features")) { - if(!js->registration && js->state == JABBER_STREAM_AUTHENTICATING) { - jabber_auth_start(js, packet); - } else if(js->state == JABBER_STREAM_REINITIALIZING) { - jabber_session_init(js); - } else { - gaim_debug(GAIM_DEBUG_WARNING, "jabber", - "Unexpected stream:features packet, ignoring\n", js->state); - } + jabber_stream_features_parse(js, packet); } else if(!strcmp(packet->name, "stream:error")) { jabber_stream_handle_error(js, packet); } else if(!strcmp(packet->name, "challenge")) { @@ -355,7 +367,7 @@ js = gc->proto_data = g_new0(JabberStream, 1); js->gc = gc; js->callbacks = g_hash_table_new_full(g_str_hash, g_str_equal, - g_free, NULL); + g_free, g_free); js->buddies = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, (GDestroyNotify)jabber_buddy_free); js->chats = g_hash_table_new_full(g_str_hash, g_str_equal, @@ -417,7 +429,7 @@ } static void -jabber_registration_result_cb(JabberStream *js, xmlnode *packet) +jabber_registration_result_cb(JabberStream *js, xmlnode *packet, gpointer data) { const char *type = xmlnode_get_attrib(packet, "type"); char *buf; @@ -512,7 +524,7 @@ gaim_account_set_username(js->gc->account, username); g_free(username); - jabber_iq_set_callback(iq, jabber_registration_result_cb); + jabber_iq_set_callback(iq, jabber_registration_result_cb, NULL); jabber_iq_send(iq); @@ -653,7 +665,7 @@ js->gc = gc; js->registration = TRUE; js->callbacks = g_hash_table_new_full(g_str_hash, g_str_equal, - g_free, NULL); + g_free, g_free); js->user = jabber_id_new(gaim_account_get_username(account)); js->next_id = g_random_int(); @@ -724,6 +736,15 @@ g_free(js); } +static void jabber_server_probe(JabberStream *js) +{ + JabberIq *iq = jabber_iq_new_query(js, JABBER_IQ_GET, + "http://jabber.org/protocol/disco#items"); + + xmlnode_set_attrib(iq->node, "to", js->user->domain); + jabber_iq_send(iq); +} + void jabber_stream_set_state(JabberStream *js, JabberStreamState state) { js->state = state; @@ -757,6 +778,7 @@ gaim_connection_set_state(js->gc, GAIM_CONNECTED); jabber_roster_request(js); jabber_presence_send(js->gc, js->gc->away_state, js->gc->away); + jabber_server_probe(js); serv_finish_login(js->gc); break; } @@ -898,7 +920,9 @@ return m; } -static void jabber_password_change_result_cb(JabberStream *js, xmlnode *packet) +static void +jabber_password_change_result_cb(JabberStream *js, xmlnode *packet, + gpointer data) { const char *type; @@ -954,7 +978,7 @@ y = xmlnode_new_child(query, "password"); xmlnode_insert_data(y, p1, -1); - jabber_iq_set_callback(iq, jabber_password_change_result_cb); + jabber_iq_set_callback(iq, jabber_password_change_result_cb, NULL); jabber_iq_send(iq); diff -r c6ff3c1fb1b3 -r b250288fa948 src/protocols/jabber/jabber.h --- a/src/protocols/jabber/jabber.h Fri Oct 31 01:38:46 2003 +0000 +++ b/src/protocols/jabber/jabber.h Fri Oct 31 02:43:58 2003 +0000 @@ -68,6 +68,7 @@ GHashTable *callbacks; int next_id; + GList *oob_file_transfers; GList *file_transfers; time_t idle; @@ -79,8 +80,6 @@ gboolean registration; } JabberStream; -typedef void (JabberCallback)(JabberStream *js, xmlnode *packet); - void jabber_process_packet(JabberStream *js, xmlnode *packet); void jabber_send(JabberStream *js, xmlnode *data); void jabber_send_raw(JabberStream *js, const char *data); diff -r c6ff3c1fb1b3 -r b250288fa948 src/protocols/jabber/jutil.h --- a/src/protocols/jabber/jutil.h Fri Oct 31 01:38:46 2003 +0000 +++ b/src/protocols/jabber/jutil.h Fri Oct 31 02:43:58 2003 +0000 @@ -19,8 +19,8 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ -#ifndef _GAIM_JABBER_JID_H_ -#define _GAIM_JABBER_JID_H_ +#ifndef _GAIM_JABBER_JUTIL_H_ +#define _GAIM_JABBER_JUTIL_H_ #include "account.h" @@ -46,4 +46,4 @@ gboolean jabber_nameprep_validate(const char *); gboolean jabber_resourceprep_validate(const char *); -#endif /* _GAIM_JABBER_JID_H_ */ +#endif /* _GAIM_JABBER_JUTIL_H_ */ diff -r c6ff3c1fb1b3 -r b250288fa948 src/protocols/jabber/oob.c --- a/src/protocols/jabber/oob.c Fri Oct 31 01:38:46 2003 +0000 +++ b/src/protocols/jabber/oob.c Fri Oct 31 02:43:58 2003 +0000 @@ -49,7 +49,8 @@ static void jabber_oob_xfer_free(GaimXfer *xfer) { JabberOOBXfer *jox = xfer->data; - jox->js->file_transfers = g_list_remove(jox->js->file_transfers, xfer); + jox->js->oob_file_transfers = g_list_remove(jox->js->oob_file_transfers, + xfer); g_string_free(jox->headers, TRUE); g_free(jox->address); @@ -177,7 +178,7 @@ gaim_xfer_set_read_fnc(xfer, jabber_oob_xfer_read); gaim_xfer_set_start_fnc(xfer, jabber_oob_xfer_start); - js->file_transfers = g_list_append(js->file_transfers, xfer); + js->oob_file_transfers = g_list_append(js->oob_file_transfers, xfer); gaim_xfer_request(xfer); } diff -r c6ff3c1fb1b3 -r b250288fa948 src/protocols/jabber/oob.h --- a/src/protocols/jabber/oob.h Fri Oct 31 01:38:46 2003 +0000 +++ b/src/protocols/jabber/oob.h Fri Oct 31 02:43:58 2003 +0000 @@ -24,4 +24,4 @@ void jabber_oob_parse(JabberStream *js, xmlnode *packet); -#endif /* _GAIM_JABBER_JID_H_ */ +#endif /* _GAIM_JABBER_OOB_H_ */ diff -r c6ff3c1fb1b3 -r b250288fa948 src/protocols/jabber/presence.c --- a/src/protocols/jabber/presence.c Fri Oct 31 01:38:46 2003 +0000 +++ b/src/protocols/jabber/presence.c Fri Oct 31 02:43:58 2003 +0000 @@ -145,7 +145,7 @@ JabberID *jid; JabberChat *chat; JabberBuddy *jb; - JabberBuddyResource *jbr; + JabberBuddyResource *jbr = FALSE; GaimBuddy *b; char *buddy_name; int state = 0; @@ -298,6 +298,7 @@ } g_free(room_jid); } else { + gboolean newly_online = FALSE; if(state != JABBER_STATE_ERROR && !(jb->subscription & JABBER_SUB_TO)) { gaim_debug(GAIM_DEBUG_INFO, "jabber", "got unexpected presence from %s, ignoring\n", from); @@ -314,13 +315,17 @@ } if(state == JABBER_STATE_ERROR || - (type && !strcasecmp(type, "unavailable"))) + (type && !strcasecmp(type, "unavailable"))) { jabber_buddy_remove_resource(jb, jid->resource); - else + } else { + if(!(jbr = jabber_buddy_find_resource(jb, jid->resource))) + newly_online = TRUE; jabber_buddy_track_resource(jb, jid->resource, priority, state, status); + } - jbr = jabber_buddy_find_resource(jb, jid->resource); + if(!jbr) + jbr = jabber_buddy_find_resource(jb, jid->resource); if(jbr) serv_got_update(js->gc, buddy_name, 1, 0, b->signon, b->idle, @@ -328,6 +333,13 @@ else serv_got_update(js->gc, buddy_name, 0, 0, 0, 0, 0); + if(newly_online) { + JabberIq *iq = jabber_iq_new_query(js, JABBER_IQ_GET, + "http://jabber.org/protocol/disco#info"); + xmlnode_set_attrib(iq->node, "to", from); + jabber_iq_send(iq); + } + g_free(buddy_name); } g_free(status); diff -r c6ff3c1fb1b3 -r b250288fa948 src/protocols/jabber/si.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/protocols/jabber/si.c Fri Oct 31 02:43:58 2003 +0000 @@ -0,0 +1,270 @@ +/* + * gaim - Jabber Protocol Plugin + * + * Copyright (C) 2003, Nathan Walp + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +#include "internal.h" +#include "debug.h" +#include "ft.h" +#include "notify.h" +#include "util.h" + +#include "buddy.h" +#include "jabber.h" +#include "iq.h" +#include "si.h" + +#include "si.h" + +static GaimXfer *jabber_si_xfer_find_by_id(JabberStream *js, const char *id) +{ + GList *xfers; + + if(!id) + return NULL; + + for(xfers = js->file_transfers; xfers; xfers = xfers->next) { + GaimXfer *xfer = xfers->data; + JabberSIXfer *jsx = xfer->data; + + if(!strcmp(jsx->id, id)) + return xfer; + } + + return NULL; +} + +static void +jabber_si_xfer_ibb_start(JabberStream *js, xmlnode *packet, gpointer data) { + GaimXfer *xfer = data; + JabberSIXfer *jsx = xfer->data; + + /* Make sure we didn't get an error back */ + + /* 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 */ +} + +void jabber_si_parse(JabberStream *js, xmlnode *packet) +{ + GaimXfer *xfer; + JabberSIXfer *jsx; + xmlnode *si, *feature, *x, *field, *value; + + si = xmlnode_get_child(packet, "si"); + + xfer = jabber_si_xfer_find_by_id(js, xmlnode_get_attrib(si, "id")); + + if(!xfer) + return; + + jsx = xfer->data; + + if(!(feature = xmlnode_get_child(si, "feature"))) + return; + + for(x = feature->child; x; x = x->next) { + const char *xmlns; + if(x->type != NODE_TYPE_TAG) + continue; + if(!(xmlns = xmlnode_get_attrib(x, "xmlns"))) + continue; + if(strcmp(xmlns, "jabber:x:data")) + continue; + for(field = x->child; field; field = field->next) { + const char *var; + if(field->type != NODE_TYPE_TAG) + continue; + if(!(var = xmlnode_get_attrib(field, "var"))) + continue; + if(!strcmp(var, "stream-method")) { + if((value = xmlnode_get_child(field, "value"))) { + char *val_data = xmlnode_get_data(value); + if(!val_data) + jsx->stream_method = STREAM_METHOD_UNKNOWN; + else if(!strcmp(val_data, + "http://jabber.org/protocol/bytestreams")) + jsx->stream_method = STREAM_METHOD_BYTESTREAMS; + else if(!strcmp(val_data, "http://jabber.org/protocol/ibb")) + jsx->stream_method = STREAM_METHOD_IBB; + else + jsx->stream_method = STREAM_METHOD_UNSUPPORTED; + g_free(val_data); + } + } + } + } + if(jsx->stream_method == STREAM_METHOD_UNKNOWN) { + /* XXX */ + } else if(jsx->stream_method == STREAM_METHOD_UNSUPPORTED) { + /* XXX */ + } else if(jsx->stream_method == STREAM_METHOD_BYTESTREAMS) { + /* XXX: open the port and stuff */ + char *buf; + xmlnode *query, *streamhost; + JabberIq *iq = jabber_iq_new_query(js, JABBER_IQ_SET, + "http://jabber.org/protocol/bytestreams"); + + buf = g_strdup_printf("%s/%s", xfer->who, jsx->resource); + xmlnode_set_attrib(iq->node, "to", buf); + g_free(buf); + + query = xmlnode_get_child(iq->node, "query"); + xmlnode_set_attrib(query, "sid", jsx->id); + streamhost = xmlnode_new_child(query, "streamhost"); + xmlnode_set_attrib(streamhost, "jid", + gaim_account_get_username(js->gc->account)); + xmlnode_set_attrib(streamhost, "host", xfer->local_ip); + buf = g_strdup_printf("%d", xfer->local_port); + xmlnode_set_attrib(streamhost, "port", buf); + g_free(buf); + jabber_iq_send(iq); + } else if(jsx->stream_method == STREAM_METHOD_IBB) { + char *buf; + xmlnode *open; + JabberIq *iq = jabber_iq_new(js, JABBER_IQ_SET); + buf = g_strdup_printf("%s/%s", xfer->who, jsx->resource); + xmlnode_set_attrib(iq->node, "to", buf); + g_free(buf); + + open = xmlnode_new_child(iq->node, "open"); + xmlnode_set_attrib(open, "xmlns", "http://jabber.org/protocol/ibb"); + xmlnode_set_attrib(open, "sid", jsx->id); + + jabber_iq_set_callback(iq, jabber_si_xfer_ibb_start, xfer); + + jabber_iq_send(iq); + + } +} + +static void jabber_si_xfer_send_request(GaimXfer *xfer) +{ + JabberSIXfer *jsx = xfer->data; + JabberIq *iq; + xmlnode *si, *file, *feature, *x, *field, *option, *value; + char buf[32]; + char *to; + + xfer->filename = g_path_get_basename(xfer->local_filename); + + iq = jabber_iq_new(jsx->js, JABBER_IQ_SET); + to = g_strdup_printf("%s/%s", xfer->who, jsx->resource); + xmlnode_set_attrib(iq->node, "to", to); + g_free(to); + si = xmlnode_new_child(iq->node, "si"); + xmlnode_set_attrib(si, "xmlns", "http://jabber.org/protocol/si"); + jsx->id = jabber_get_next_id(jsx->js); + xmlnode_set_attrib(si, "id", jsx->id); + xmlnode_set_attrib(si, "profile", + "http://jabber.org/protocol/si/profile/file-transfer"); + + file = xmlnode_new_child(si, "file"); + xmlnode_set_attrib(file, "xmlns", + "http://jabber.org/protocol/si/profile/file-transfer"); + xmlnode_set_attrib(file, "name", xfer->filename); + g_snprintf(buf, sizeof(buf), "%d", xfer->size); + xmlnode_set_attrib(file, "size", buf); + /* maybe later we'll do hash and date attribs */ + + 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"); + 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); + option = xmlnode_new_child(field, "option"); + value = xmlnode_new_child(option, "value"); + xmlnode_insert_data(value, "http://jabber.org/protocol/ibb", -1); + + jabber_iq_send(iq); +} + +void jabber_si_xfer_init(GaimXfer *xfer) +{ + JabberSIXfer *jsx = xfer->data; + if(gaim_xfer_get_type(xfer) == GAIM_XFER_SEND) { + JabberBuddy *jb; + JabberBuddyResource *jbr = NULL; + GList *resources; + GList *xfer_resources = NULL; + + jb = jabber_buddy_find(jsx->js, xfer->who, TRUE); + if(!jb) + return; + + for(resources = jb->resources; resources; resources = resources->next) { + jbr = resources->data; + if(jbr->capabilities & JABBER_CAP_SI_FILE_XFER) + xfer_resources = g_list_append(xfer_resources, jbr); + } + + if(g_list_length(xfer_resources) == 1) { + jbr = xfer_resources->data; + jsx->resource = g_strdup(jbr->name); + jabber_si_xfer_send_request(xfer); + } else if(g_list_length(xfer_resources) == 0) { + char *buf = g_strdup_printf(_("Could not send %s to %s, protocol not supported."), xfer->filename, xfer->who); + gaim_notify_error(jsx->js->gc, _("File Send Failed"), + _("File Send Failed"), buf); + g_free(buf); + } else { + /* XXX: ask which resource to send to! */ + } + g_list_free(xfer_resources); + } +} + +void jabber_si_xfer_start(GaimXfer *xfer) +{ + gaim_debug(GAIM_DEBUG_INFO, "jabber", "in jabber_si_xfer_start\n"); +} + +void jabber_si_xfer_end(GaimXfer *xfer) +{ + gaim_debug(GAIM_DEBUG_INFO, "jabber", "in jabber_si_xfer_end\n"); +} + +void jabber_si_xfer_cancel_send(GaimXfer *xfer) +{ + gaim_debug(GAIM_DEBUG_INFO, "jabber", "in jabber_si_xfer_cancel_send\n"); +} + + +void jabber_si_xfer_cancel_recv(GaimXfer *xfer) +{ + gaim_debug(GAIM_DEBUG_INFO, "jabber", "in jabber_si_xfer_cancel_recv\n"); +} + + +void jabber_si_xfer_ack(GaimXfer *xfer, const char *buffer, size_t size) +{ + gaim_debug(GAIM_DEBUG_INFO, "jabber", "in jabber_si_xfer_ack\n"); +} + diff -r c6ff3c1fb1b3 -r b250288fa948 src/protocols/jabber/si.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/protocols/jabber/si.h Fri Oct 31 02:43:58 2003 +0000 @@ -0,0 +1,53 @@ +/** + * @file jutil.h utility functions + * + * gaim + * + * Copyright (C) 2003 Nathan Walp + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef _GAIM_JABBER_SI_H_ +#define _GAIM_JABBER_SI_H_ + +#include "ft.h" + +#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_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_ */