changeset 8312:ba12d8b12ab0

[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 <tailor@pidgin.im>
author Nathan Walp <nwalp@pidgin.im>
date Sat, 21 Feb 2004 23:59:49 +0000
parents 9e2b28acf1cd
children ab7859399509
files src/protocols/jabber/Makefile.am src/protocols/jabber/buddy.c src/protocols/jabber/buddy.h src/protocols/jabber/disco.c src/protocols/jabber/disco.h src/protocols/jabber/iq.c src/protocols/jabber/jabber.c src/protocols/jabber/jabber.h src/protocols/jabber/si.c src/protocols/jabber/si.h
diffstat 10 files changed, 606 insertions(+), 460 deletions(-) [+]
line wrap: on
line diff
--- 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 \
--- 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 */) {
--- 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);
--- /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 <faceprint@faceprint.com>
+ *
+ * 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);
+}
+
+
--- /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 <faceprint@faceprint.com>
+ *
+ * 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_ */
--- 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);
 
--- 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;
 	}
--- 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_ */
--- 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; i<len; i++) {
+		read(source, &method, 1);
+		if(method == 0x00) {
+			buf[0] = 0x05;
+			buf[1] = 0x00;
+			write(source, buf, 2);
+			gaim_xfer_start(xfer, source, NULL, 0);
+			return;
+		}
+	}
+
+	buf[0] = 0x05;
+	buf[1] = 0xFF;
+	write(source, buf, 2);
+	gaim_xfer_cancel_remote(xfer);
+}
+
+static void
+jabber_si_xfer_bytestreams_send_init(GaimXfer *xfer)
+{
+	JabberSIXfer *jsx = xfer->data;
+	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
--- 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_ */