changeset 7395:b250288fa948

[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 <tailor@pidgin.im>
author Nathan Walp <nwalp@pidgin.im>
date Fri, 31 Oct 2003 02:43:58 +0000
parents c6ff3c1fb1b3
children f7a807db8e03
files src/protocols/jabber/JEPS src/protocols/jabber/Makefile.am src/protocols/jabber/Makefile.mingw src/protocols/jabber/auth.c src/protocols/jabber/buddy.c src/protocols/jabber/buddy.h src/protocols/jabber/iq.c src/protocols/jabber/iq.h src/protocols/jabber/jabber.c src/protocols/jabber/jabber.h src/protocols/jabber/jutil.h src/protocols/jabber/oob.c src/protocols/jabber/oob.h src/protocols/jabber/presence.c src/protocols/jabber/si.c src/protocols/jabber/si.h
diffstat 16 files changed, 642 insertions(+), 80 deletions(-) [+]
line wrap: on
line diff
--- 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)
--- 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)
 
--- 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
 
 
--- 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);
--- 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) {
--- 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;
 
--- 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);
 	}
 }
 
--- 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);
--- 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("<stream:stream to='%s' "
 				          "xmlns='jabber:client' "
-						  "xmlns:stream='http://etherx.jabber.org/streams'>",
-						  /* "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);
 
--- 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);
--- 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_ */
--- 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);
 }
--- 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_ */
--- 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);
--- /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 <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 "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 <close> like
+	 * we sent the <open> 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");
+}
+
--- /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 <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_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_ */