# HG changeset patch # User Nathan Walp # Date 1077407989 0 # Node ID ba12d8b12ab07f1c44ad27b99165585456d935a4 # Parent 9e2b28acf1cd9cff2bed5490a881d94d9d5afafe [gaim-migrate @ 9036] I think this is preliminary jabber file sending support. I can't test it because the new network listening stuff is broken if you run both IPv4 and IPv6. If someone with a more "normal" setup can let me know if this works, I'd appreciate it. Note that it's not completely implemented yet, so sending via a proxy server doesn't work, cancelling transfers doesn't work, error handling isn't there, and it probably leaks memory. A sane person might even wonder why I'm committing this. Oh well. committer: Tailor Script diff -r 9e2b28acf1cd -r ba12d8b12ab0 src/protocols/jabber/Makefile.am --- a/src/protocols/jabber/Makefile.am Sat Feb 21 20:59:07 2004 +0000 +++ b/src/protocols/jabber/Makefile.am Sat Feb 21 23:59:49 2004 +0000 @@ -11,6 +11,8 @@ buddy.h \ chat.c \ chat.h \ + disco.c \ + disco.h \ iq.c \ iq.h \ jabber.c \ diff -r 9e2b28acf1cd -r ba12d8b12ab0 src/protocols/jabber/buddy.c --- a/src/protocols/jabber/buddy.c Sat Feb 21 20:59:07 2004 +0000 +++ b/src/protocols/jabber/buddy.c Sat Feb 21 23:59:49 2004 +0000 @@ -815,31 +815,6 @@ } -#if 0 -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); -} -#endif - static void jabber_buddy_set_invisibility(JabberStream *js, const char *who, gboolean invisible) { @@ -904,15 +879,12 @@ if(!jb) return m; - /* XXX: should check capability once we know we want to send pbm = g_new0(struct proto_buddy_menu, 1); pbm->label = _("Send File"); - pbm->callback = jabber_buddy_ask_send_file; + pbm->callback = jabber_si_xfer_ask_send; pbm->gc = gc; m = g_list_append(m, pbm); - */ - /* XXX: fix the NOT ME below */ if(js->protocol_version == JABBER_PROTO_0_9 /* && NOT ME */) { diff -r 9e2b28acf1cd -r ba12d8b12ab0 src/protocols/jabber/buddy.h --- a/src/protocols/jabber/buddy.h Sat Feb 21 20:59:07 2004 +0000 +++ b/src/protocols/jabber/buddy.h Sat Feb 21 23:59:49 2004 +0000 @@ -48,13 +48,7 @@ int priority; int state; char *status; - enum { - 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; + JabberCapabilities capabilities; } JabberBuddyResource; void jabber_buddy_free(JabberBuddy *jb); diff -r 9e2b28acf1cd -r ba12d8b12ab0 src/protocols/jabber/disco.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/protocols/jabber/disco.c Sat Feb 21 23:59:49 2004 +0000 @@ -0,0 +1,255 @@ +/* + * 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 "prefs.h" + +#include "buddy.h" +#include "iq.h" +#include "disco.h" + + +struct _jabber_disco_info_cb_data { + gpointer data; + JabberDiscoInfoCallback *callback; +}; + +#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") +#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); + } else if(!strcmp(type, "result")) { + xmlnode *query = xmlnode_get_child(packet, "query"); + xmlnode *child; + JabberID *jid; + JabberBuddy *jb; + JabberBuddyResource *jbr = NULL; + JabberCapabilities capabilities = JABBER_CAP_NONE; + struct _jabber_disco_info_cb_data *jdicd; + + if((jid = jabber_id_new(from))) { + if(jid->resource && (jb = jabber_buddy_find(js, from, TRUE))) + jbr = jabber_buddy_find_resource(jb, jid->resource); + jabber_id_free(jid); + } + + if(jbr) + capabilities = jbr->capabilities; + + for(child = query->child; child; child = child->next) { + if(child->type != XMLNODE_TYPE_TAG) + continue; + + if(!strcmp(child->name, "identity")) { + const char *category = xmlnode_get_attrib(child, "category"); + const char *type = xmlnode_get_attrib(child, "type"); + if(!category || !type) + continue; + + /* we found a groupchat or MUC server, add it to the list */ + /* XXX: actually check for protocol/muc or gc-1.0 support */ + if(!strcmp(category, "conference") && !strcmp(type, "text")) + js->chat_servers = g_list_append(js->chat_servers, g_strdup(from)); + + } else if(!strcmp(child->name, "feature")) { + const char *var = xmlnode_get_attrib(child, "var"); + if(!var) + continue; + + if(!strcmp(var, "http://jabber.org/protocol/si")) + capabilities |= JABBER_CAP_SI; + else if(!strcmp(var, "http://jabber.org/protocol/si/profile/file-transfer")) + capabilities |= JABBER_CAP_SI_FILE_XFER; + else if(!strcmp(var, "http://jabber.org/protocol/bytestreams")) + capabilities |= JABBER_CAP_BYTESTREAMS; + } + } + + capabilities |= JABBER_CAP_RETRIEVED; + + if(jbr) + jbr->capabilities = capabilities; + + if((jdicd = g_hash_table_lookup(js->disco_callbacks, from))) { + jdicd->callback(js, from, capabilities, jdicd->data); + g_hash_table_remove(js->disco_callbacks, from); + } + } else if(!strcmp(type, "error")) { + JabberID *jid; + JabberBuddy *jb; + JabberBuddyResource *jbr = NULL; + JabberCapabilities capabilities = JABBER_CAP_NONE; + struct _jabber_disco_info_cb_data *jdicd; + + if(!(jdicd = g_hash_table_lookup(js->disco_callbacks, from))) + return; + + if((jid = jabber_id_new(from))) { + if(jid->resource && (jb = jabber_buddy_find(js, from, TRUE))) + jbr = jabber_buddy_find_resource(jb, jid->resource); + jabber_id_free(jid); + } + + if(jbr) + capabilities = jbr->capabilities; + + jdicd->callback(js, from, capabilities, jdicd->data); + g_hash_table_remove(js->disco_callbacks, from); + } +} + +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); + } +} + +static void +jabber_disco_server_result_cb(JabberStream *js, xmlnode *packet, gpointer data) +{ + xmlnode *query, *child; + const char *from = xmlnode_get_attrib(packet, "from"); + const char *type = xmlnode_get_attrib(packet, "type"); + + if(!from || !type) + return; + + if(strcmp(from, js->user->domain)) + return; + + if(strcmp(type, "result")) + return; + + while(js->chat_servers) { + g_free(js->chat_servers->data); + js->chat_servers = g_list_delete_link(js->chat_servers, js->chat_servers); + } + + query = xmlnode_get_child(packet, "query"); + + for(child = xmlnode_get_child(query, "item"); child; + child = xmlnode_get_next_twin(child)) { + JabberIq *iq; + const char *jid; + + if(!(jid = xmlnode_get_attrib(child, "jid"))) + continue; + + iq = jabber_iq_new_query(js, JABBER_IQ_GET, "http://jabber.org/protocol/disco#info"); + xmlnode_set_attrib(iq->node, "to", jid); + jabber_iq_send(iq); + } +} + +void jabber_disco_items_server(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_set_callback(iq, jabber_disco_server_result_cb, NULL); + jabber_iq_send(iq); +} + +void jabber_disco_info_do(JabberStream *js, const char *who, JabberDiscoInfoCallback *callback, gpointer data) +{ + JabberID *jid; + JabberBuddy *jb; + JabberBuddyResource *jbr = NULL; + struct _jabber_disco_info_cb_data *jdicd; + JabberIq *iq; + + if((jid = jabber_id_new(who))) { + if(jid->resource && (jb = jabber_buddy_find(js, who, TRUE))) + jbr = jabber_buddy_find_resource(jb, jid->resource); + jabber_id_free(jid); + } + + if(jbr && jbr->capabilities & JABBER_CAP_RETRIEVED) { + callback(js, who, jbr->capabilities, data); + return; + } + + jdicd = g_new0(struct _jabber_disco_info_cb_data, 1); + jdicd->data = data; + jdicd->callback = callback; + + g_hash_table_insert(js->disco_callbacks, g_strdup(who), jdicd); + + iq = jabber_iq_new_query(js, JABBER_IQ_GET, "http://jabber.org/protocol/disco#info"); + xmlnode_set_attrib(iq->node, "to", who); + + jabber_iq_send(iq); +} + + diff -r 9e2b28acf1cd -r ba12d8b12ab0 src/protocols/jabber/disco.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/protocols/jabber/disco.h Sat Feb 21 23:59:49 2004 +0000 @@ -0,0 +1,38 @@ +/** + * @file iq.h JabberID handlers + * + * 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_DISCO_H_ +#define _GAIM_JABBER_DISCO_H_ + +#include "jabber.h" + +typedef void (JabberDiscoInfoCallback)(JabberStream *js, const char *who, + JabberCapabilities capabilities, gpointer data); + +void jabber_disco_info_parse(JabberStream *js, xmlnode *packet); +void jabber_disco_items_parse(JabberStream *js, xmlnode *packet); + +void jabber_disco_items_server(JabberStream *js); + +void jabber_disco_info_do(JabberStream *js, const char *who, + JabberDiscoInfoCallback *callback, gpointer data); + +#endif /* _GAIM_JABBER_DISCO_H_ */ diff -r 9e2b28acf1cd -r ba12d8b12ab0 src/protocols/jabber/iq.c --- a/src/protocols/jabber/iq.c Sat Feb 21 20:59:07 2004 +0000 +++ b/src/protocols/jabber/iq.c Sat Feb 21 23:59:49 2004 +0000 @@ -23,6 +23,7 @@ #include "prefs.h" #include "buddy.h" +#include "disco.h" #include "iq.h" #include "oob.h" #include "roster.h" @@ -118,7 +119,7 @@ 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); + g_hash_table_insert(iq->js->iq_callbacks, g_strdup(iq->id), jcd); } jabber_iq_free(iq); @@ -133,7 +134,7 @@ g_free(iq); } -static void jabber_iq_handle_last(JabberStream *js, xmlnode *packet) +static void jabber_iq_last_parse(JabberStream *js, xmlnode *packet) { JabberIq *iq; const char *type; @@ -161,7 +162,7 @@ } } -static void jabber_iq_handle_time(JabberStream *js, xmlnode *packet) +static void jabber_iq_time_parse(JabberStream *js, xmlnode *packet) { const char *type, *from, *id; JabberIq *iq; @@ -195,7 +196,7 @@ } } -static void jabber_iq_handle_version(JabberStream *js, xmlnode *packet) +static void jabber_iq_version_parse(JabberStream *js, xmlnode *packet) { JabberIq *iq; const char *type, *from, *id; @@ -234,162 +235,6 @@ } } -#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") -#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); - } 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); - jabber_id_free(jid); - - for(child = query->child; child; child = child->next) { - if(child->type != XMLNODE_TYPE_TAG) - continue; - - if(!strcmp(child->name, "identity")) { - const char *category = xmlnode_get_attrib(child, "category"); - const char *type = xmlnode_get_attrib(child, "type"); - if(!category || !type) - continue; - - /* we found a groupchat or MUC server, add it to the list */ - /* XXX: actually check for protocol/muc or gc-1.0 support */ - if(!strcmp(category, "conference") && !strcmp(type, "text")) - js->chat_servers = g_list_append(js->chat_servers, g_strdup(from)); - - } else if(!strcmp(child->name, "feature")) { - const char *var = xmlnode_get_attrib(child, "var"); - if(!var) - continue; - - if(jbr && !strcmp(var, "http://jabber.org/protocol/si")) - jbr->capabilities |= JABBER_CAP_SI; - else if(jbr && !strcmp(var, - "http://jabber.org/protocol/si/profile/file-transfer")) - jbr->capabilities |= JABBER_CAP_SI_FILE_XFER; - else if(jbr && !strcmp(var, "http://jabber.org/protocol/bytestreams")) - jbr->capabilities |= JABBER_CAP_BYTESTREAMS; - } - } - } -} - -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); - } -} - -static void -jabber_iq_disco_server_result_cb(JabberStream *js, xmlnode *packet, gpointer data) -{ - xmlnode *query, *child; - const char *from = xmlnode_get_attrib(packet, "from"); - const char *type = xmlnode_get_attrib(packet, "type"); - - if(!from || !type) - return; - - if(strcmp(from, js->user->domain)) - return; - - if(strcmp(type, "result")) - return; - - while(js->chat_servers) { - g_free(js->chat_servers->data); - js->chat_servers = g_list_delete_link(js->chat_servers, js->chat_servers); - } - - query = xmlnode_get_child(packet, "query"); - - for(child = xmlnode_get_child(query, "item"); child; - child = xmlnode_get_next_twin(child)) { - JabberIq *iq; - const char *jid; - - if(!(jid = xmlnode_get_attrib(child, "jid"))) - continue; - - iq = jabber_iq_new_query(js, JABBER_IQ_GET, "http://jabber.org/protocol/disco#info"); - xmlnode_set_attrib(iq->node, "to", jid); - jabber_iq_send(iq); - } -} - -void jabber_iq_disco_server(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_set_callback(iq, jabber_iq_disco_server_result_cb, NULL); - jabber_iq_send(iq); -} - - void jabber_iq_parse(JabberStream *js, xmlnode *packet) { JabberCallbackData *jcd; @@ -401,6 +246,19 @@ query = xmlnode_get_child(packet, "query"); type = xmlnode_get_attrib(packet, "type"); from = xmlnode_get_attrib(packet, "from"); + id = xmlnode_get_attrib(packet, "id"); + + /* First, lets see if a special callback got registered */ + + if(type && (!strcmp(type, "result") || !strcmp(type, "error"))) { + if(id && *id && (jcd = g_hash_table_lookup(js->iq_callbacks, id))) { + jcd->callback(js, packet, jcd->data); + g_hash_table_remove(js->iq_callbacks, id); + } + return; + } + + /* Apparently not, so lets see if we have a pre-defined handler */ if(type && query && (xmlns = xmlnode_get_attrib(query, "xmlns"))) { if(!strcmp(type, "set")) { @@ -416,13 +274,13 @@ } } else if(!strcmp(type, "get")) { if(!strcmp(xmlns, "jabber:iq:last")) { - jabber_iq_handle_last(js, packet); + jabber_iq_last_parse(js, packet); return; } else if(!strcmp(xmlns, "jabber:iq:time")) { - jabber_iq_handle_time(js, packet); + jabber_iq_time_parse(js, packet); return; } else if(!strcmp(xmlns, "jabber:iq:version")) { - jabber_iq_handle_version(js, packet); + jabber_iq_version_parse(js, packet); return; } else if(!strcmp(xmlns, "http://jabber.org/protocol/disco#info")) { jabber_disco_info_parse(js, packet); @@ -444,28 +302,14 @@ } } } else { - xmlnode *si; - if((si = xmlnode_get_child(packet, "si")) && (xmlns = xmlnode_get_attrib(si, "xmlns")) && - !strcmp(xmlns, "http://jabber.org/protocol/si")) { + if(xmlnode_get_child_with_namespace(packet, "si", "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 - * callback got registered */ - id = xmlnode_get_attrib(packet, "id"); - - if(type && (!strcmp(type, "result") || !strcmp(type, "error"))) { - if(id && *id && (jcd = g_hash_table_lookup(js->callbacks, id))) { - jcd->callback(js, packet, jcd->data); - g_hash_table_remove(js->callbacks, id); - } - return; - } - - /* Default error reply mandated by XMPP-CORE */ + /* If we get here, send the default error reply mandated by XMPP-CORE */ iq = jabber_iq_new(js, JABBER_IQ_ERROR); diff -r 9e2b28acf1cd -r ba12d8b12ab0 src/protocols/jabber/jabber.c --- a/src/protocols/jabber/jabber.c Sat Feb 21 20:59:07 2004 +0000 +++ b/src/protocols/jabber/jabber.c Sat Feb 21 23:59:49 2004 +0000 @@ -34,6 +34,7 @@ #include "auth.h" #include "buddy.h" #include "chat.h" +#include "disco.h" #include "iq.h" #include "jutil.h" #include "message.h" @@ -386,7 +387,9 @@ js = gc->proto_data = g_new0(JabberStream, 1); js->gc = gc; js->fd = -1; - js->callbacks = g_hash_table_new_full(g_str_hash, g_str_equal, + js->iq_callbacks = g_hash_table_new_full(g_str_hash, g_str_equal, + g_free, g_free); + js->disco_callbacks = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free); js->buddies = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, (GDestroyNotify)jabber_buddy_free); @@ -718,7 +721,9 @@ js = gc->proto_data = g_new0(JabberStream, 1); js->gc = gc; js->registration = TRUE; - js->callbacks = g_hash_table_new_full(g_str_hash, g_str_equal, + js->iq_callbacks = g_hash_table_new_full(g_str_hash, g_str_equal, + g_free, g_free); + js->disco_callbacks = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free); js->user = jabber_id_new(gaim_account_get_username(account)); js->next_id = g_random_int(); @@ -781,8 +786,10 @@ if(js->context) g_markup_parse_context_free(js->context); - if(js->callbacks) - g_hash_table_destroy(js->callbacks); + if(js->iq_callbacks) + g_hash_table_destroy(js->iq_callbacks); + if(js->disco_callbacks) + g_hash_table_destroy(js->disco_callbacks); if(js->buddies) g_hash_table_destroy(js->buddies); if(js->chats) @@ -832,7 +839,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_iq_disco_server(js); + jabber_disco_items_server(js); serv_finish_login(js->gc); break; } diff -r 9e2b28acf1cd -r ba12d8b12ab0 src/protocols/jabber/jabber.h --- a/src/protocols/jabber/jabber.h Sat Feb 21 20:59:07 2004 +0000 +++ b/src/protocols/jabber/jabber.h Sat Feb 21 23:59:49 2004 +0000 @@ -31,6 +31,17 @@ #include "xmlnode.h" typedef enum { + JABBER_CAP_NONE = 0, + JABBER_CAP_XHTML = 1 << 0, + JABBER_CAP_COMPOSING = 1 << 1, + JABBER_CAP_SI = 1 << 2, + JABBER_CAP_SI_FILE_XFER = 1 << 3, + JABBER_CAP_BYTESTREAMS = 1 << 4, + JABBER_CAP_IBB = 1 << 5, + JABBER_CAP_RETRIEVED = 1 << 31 +} JabberCapabilities; + +typedef enum { JABBER_STREAM_OFFLINE, JABBER_STREAM_CONNECTING, JABBER_STREAM_INITIALIZING, @@ -69,9 +80,11 @@ GList *chat_servers; GaimRoomlist *roomlist; - GHashTable *callbacks; + GHashTable *iq_callbacks; + GHashTable *disco_callbacks; int next_id; + GList *oob_file_transfers; GList *file_transfers; @@ -95,6 +108,4 @@ char *jabber_get_next_id(JabberStream *js); -void jabber_iq_disco_server(JabberStream *js); - #endif /* _GAIM_JABBER_H_ */ diff -r 9e2b28acf1cd -r ba12d8b12ab0 src/protocols/jabber/si.c --- a/src/protocols/jabber/si.c Sat Feb 21 20:59:07 2004 +0000 +++ b/src/protocols/jabber/si.c Sat Feb 21 23:59:49 2004 +0000 @@ -27,6 +27,7 @@ #include "util.h" #include "buddy.h" +#include "disco.h" #include "jabber.h" #include "iq.h" #include "si.h" @@ -202,56 +203,281 @@ jabber_si_bytestreams_attempt_connect(xfer); } +static void +jabber_si_xfer_bytestreams_send_connected_cb(gpointer data, gint source, + GaimInputCondition cond) +{ + GaimXfer *xfer = data; + char ver, len, method; + int i; + char buf[2]; + + xfer->fd = source; + + read(source, &ver, 1); + + if(ver != 0x05) { + close(source); + gaim_xfer_cancel_remote(xfer); + return; + } + + read(source, &len, 1); + for(i=0; idata; + JabberIq *iq; + xmlnode *query, *streamhost; + char *jid, *port; + int fd; + + iq = jabber_iq_new_query(jsx->js, JABBER_IQ_SET, + "http://jabber.org/protocol/bytestreams"); + xmlnode_set_attrib(iq->node, "to", xfer->who); + query = xmlnode_get_child(iq->node, "query"); + + 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); + g_free(jid); + + if((fd = gaim_network_listen_range(0, 0)) < 0) { + /* XXX: couldn't open a port, we're fscked */ + return; + } + + xmlnode_set_attrib(streamhost, "host", gaim_network_get_ip_for_account(jsx->js->gc->account, fd)); + xfer->local_port = gaim_network_get_port_from_fd(fd); + port = g_strdup_printf("%d", xfer->local_port); + xmlnode_set_attrib(streamhost, "port", port); + g_free(port); + + xfer->watcher = gaim_input_add(fd, GAIM_INPUT_READ, + jabber_si_xfer_bytestreams_send_connected_cb, xfer); + + /* XXX: insert proxies here */ + + /* XXX: callback to find out which streamhost they used, or see if they + * screwed it up */ + jabber_iq_send(iq); +} + +static void jabber_si_xfer_send_method_cb(JabberStream *js, xmlnode *packet, + gpointer data) +{ + GaimXfer *xfer = data; + xmlnode *si, *feature, *x, *field, *value; + + if(!(si = xmlnode_get_child_with_namespace(packet, "si", "http://jabber.org/protocol/si"))) { + gaim_xfer_cancel_remote(xfer); + return; + } + + if(!(feature = xmlnode_get_child_with_namespace(si, "feature", "http://jabber.org/protocol/feature-neg"))) { + gaim_xfer_cancel_remote(xfer); + return; + } + + if(!(x = xmlnode_get_child_with_namespace(feature, "x", "jabber:x:data"))) { + gaim_xfer_cancel_remote(xfer); + return; + } + + 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")) { + if((value = xmlnode_get_child(field, "value"))) { + char *val = xmlnode_get_data(value); + if(val && !strcmp(val, "http://jabber.org/protocol/bytestreams")) { + jabber_si_xfer_bytestreams_send_init(xfer); + g_free(val); + return; + } + g_free(val); + } + } + } + gaim_xfer_cancel_remote(xfer); +} + +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]; + + xfer->filename = g_path_get_basename(xfer->local_filename); + + iq = jabber_iq_new(jsx->js, JABBER_IQ_SET); + xmlnode_set_attrib(iq->node, "to", xfer->who); + si = xmlnode_new_child(iq->node, "si"); + xmlnode_set_attrib(si, "xmlns", "http://jabber.org/protocol/si"); + jsx->stream_id = jabber_get_next_id(jsx->js); + xmlnode_set_attrib(si, "id", jsx->stream_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_set_callback(iq, jabber_si_xfer_send_method_cb, xfer); + + jabber_iq_send(iq); +} + +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"); +} + + +static void jabber_si_xfer_send_disco_cb(JabberStream *js, const char *who, + JabberCapabilities capabilities, gpointer data) +{ + GaimXfer *xfer = data; + + if(capabilities & JABBER_CAP_SI_FILE_XFER) { + jabber_si_xfer_send_request(xfer); + } else { + char *msg = g_strdup_printf(_("Unable to send file to %s, user does not support file transfers"), who); + gaim_notify_error(js->gc, _("File Send Failed"), + _("File Send Failed"), msg); + g_free(msg); + } +} + static void jabber_si_xfer_init(GaimXfer *xfer) { JabberSIXfer *jsx = xfer->data; JabberIq *iq; - xmlnode *si, *feature, *x, *field, *value; + if(gaim_xfer_get_type(xfer) == GAIM_XFER_SEND) { + JabberBuddy *jb; + JabberBuddyResource *jbr = NULL; + + jb = jabber_buddy_find(jsx->js, xfer->who, TRUE); + /* XXX */ + if(!jb) + return; - 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"); + /* XXX: for now, send to the first resource available */ + if(g_list_length(jb->resources) >= 1) { + char *who; + jbr = jb->resources->data; + who = g_strdup_printf("%s/%s", xfer->who, jbr->name); + g_free(xfer->who); + xfer->who = who; + jabber_disco_info_do(jsx->js, who, + jabber_si_xfer_send_disco_cb, xfer); + } else { + return; /* XXX: ick */ + } + } else { + xmlnode *si, *feature, *x, *field, *value; - x = xmlnode_new_child(feature, "x"); - xmlnode_set_attrib(x, "xmlns", "jabber:x:data"); - xmlnode_set_attrib(x, "type", "form"); + 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"); - field = xmlnode_new_child(x, "field"); - xmlnode_set_attrib(field, "var", "stream-method"); + x = xmlnode_new_child(feature, "x"); + xmlnode_set_attrib(x, "xmlns", "jabber:x:data"); + xmlnode_set_attrib(x, "type", "form"); - 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) + 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); + jabber_iq_send(iq); + } } -static ssize_t jabber_si_xfer_read(char **buffer, GaimXfer *xfer) { - char buf; +void jabber_si_xfer_ask_send(GaimConnection *gc, const char *name) +{ + JabberStream *js = gc->proto_data; + GaimXfer *xfer; + JabberSIXfer *jsx; + + if(!gaim_find_buddy(gc->account, name) || !jabber_buddy_find(js, name, FALSE)) + return; + + xfer = gaim_xfer_new(gaim_connection_get_account(gc), GAIM_XFER_SEND, name); - 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); - } + xfer->data = jsx = g_new0(JabberSIXfer, 1); + jsx->js = js; - return 0; + gaim_xfer_set_init_fnc(xfer, jabber_si_xfer_init); + gaim_xfer_set_cancel_send_fnc(xfer, jabber_si_xfer_cancel_send); + + js->file_transfers = g_list_append(js->file_transfers, xfer); + + gaim_xfer_request(xfer); } - void jabber_si_parse(JabberStream *js, xmlnode *packet) { JabberSIXfer *jsx; @@ -327,214 +553,10 @@ 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; - JabberSIXfer *jsx; - xmlnode *si, *feature, *x, *field, *value; - GaimAccount *account = gaim_connection_get_account(js->gc); - 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 = xmlnode_get_child(feature, "x"); x; x = xmlnode_get_next_twin(x)) { - const char *xmlns; - if(!(xmlns = xmlnode_get_attrib(x, "xmlns"))) - continue; - if(strcmp(xmlns, "jabber:x:data")) - continue; - for(field = xmlnode_get_child(x, "field"); field; - field = xmlnode_get_next_twin(field)) { - const char *var; - 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", gaim_network_get_ip_for_account(account, js->fd)); - 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"); -} - -#endif diff -r 9e2b28acf1cd -r ba12d8b12ab0 src/protocols/jabber/si.h --- a/src/protocols/jabber/si.h Sat Feb 21 20:59:07 2004 +0000 +++ b/src/protocols/jabber/si.h Sat Feb 21 23:59:49 2004 +0000 @@ -28,5 +28,6 @@ void jabber_bytestreams_parse(JabberStream *js, xmlnode *packet); void jabber_si_parse(JabberStream *js, xmlnode *packet); +void jabber_si_xfer_ask_send(GaimConnection *gc, const char *name); #endif /* _GAIM_JABBER_SI_H_ */