changeset 29973:01ed1337dddb

merge of '60c9671daa2b521f9de4c0980f8798876959570e' and '78026d85fc783da9d0cb93283729d22e5248c42b'
author Elliott Sales de Andrade <qulogic@pidgin.im>
date Wed, 10 Mar 2010 05:12:35 +0000
parents 9472a82eb553 (current diff) 9d1794da1d10 (diff)
children eb300dde8113
files libpurple/protocols/jabber/jabber.c libpurple/protocols/msnp9/Makefile.am libpurple/protocols/msnp9/Makefile.mingw libpurple/protocols/msnp9/cmdproc.c libpurple/protocols/msnp9/cmdproc.h libpurple/protocols/msnp9/command.c libpurple/protocols/msnp9/command.h libpurple/protocols/msnp9/dialog.c libpurple/protocols/msnp9/dialog.h libpurple/protocols/msnp9/directconn.c libpurple/protocols/msnp9/directconn.h libpurple/protocols/msnp9/error.c libpurple/protocols/msnp9/error.h libpurple/protocols/msnp9/group.c libpurple/protocols/msnp9/group.h libpurple/protocols/msnp9/history.c libpurple/protocols/msnp9/history.h libpurple/protocols/msnp9/httpconn.c libpurple/protocols/msnp9/httpconn.h libpurple/protocols/msnp9/msg.c libpurple/protocols/msnp9/msg.h libpurple/protocols/msnp9/msn-utils.c libpurple/protocols/msnp9/msn-utils.h libpurple/protocols/msnp9/msn.c libpurple/protocols/msnp9/msn.h libpurple/protocols/msnp9/nexus.c libpurple/protocols/msnp9/nexus.h libpurple/protocols/msnp9/notification.c libpurple/protocols/msnp9/notification.h libpurple/protocols/msnp9/object.c libpurple/protocols/msnp9/object.h libpurple/protocols/msnp9/page.c libpurple/protocols/msnp9/page.h libpurple/protocols/msnp9/servconn.c libpurple/protocols/msnp9/servconn.h libpurple/protocols/msnp9/session.c libpurple/protocols/msnp9/session.h libpurple/protocols/msnp9/slp.c libpurple/protocols/msnp9/slp.h libpurple/protocols/msnp9/slpcall.c libpurple/protocols/msnp9/slpcall.h libpurple/protocols/msnp9/slplink.c libpurple/protocols/msnp9/slplink.h libpurple/protocols/msnp9/slpmsg.c libpurple/protocols/msnp9/slpmsg.h libpurple/protocols/msnp9/slpsession.c libpurple/protocols/msnp9/slpsession.h libpurple/protocols/msnp9/state.c libpurple/protocols/msnp9/state.h libpurple/protocols/msnp9/switchboard.c libpurple/protocols/msnp9/switchboard.h libpurple/protocols/msnp9/sync.c libpurple/protocols/msnp9/sync.h libpurple/protocols/msnp9/table.c libpurple/protocols/msnp9/table.h libpurple/protocols/msnp9/transaction.c libpurple/protocols/msnp9/transaction.h libpurple/protocols/msnp9/user.c libpurple/protocols/msnp9/user.h libpurple/protocols/msnp9/userlist.c libpurple/protocols/msnp9/userlist.h
diffstat 11 files changed, 960 insertions(+), 720 deletions(-) [+]
line wrap: on
line diff
--- a/ChangeLog	Wed Mar 10 05:10:43 2010 +0000
+++ b/ChangeLog	Wed Mar 10 05:12:35 2010 +0000
@@ -32,6 +32,11 @@
 	* X-Status (Custom ICQ status icon) support (Andrew Ivanov, Tomáš Kebert,
 	  Yuriy Yevgrafov, and trac users bob007, salieff, and nops)
 
+	XMPP:
+	* Direct messages to a specific resource only upon receipt of a message
+	  with content (as opposed to a typing notification, etc).  (Thanks to
+	  rjoly for testing)
+
 version 2.6.6 (02/18/2010):
 	libpurple:
 	* Fix 'make check' on OS X. (David Fang)
--- a/libpurple/protocols/jabber/bosh.c	Wed Mar 10 05:10:43 2010 +0000
+++ b/libpurple/protocols/jabber/bosh.c	Wed Mar 10 05:12:35 2010 +0000
@@ -521,7 +521,7 @@
 	}
 
 	if (version) {
-		const char *dot = strstr(version, ".");
+		const char *dot = strchr(version, '.');
 		int major, minor = 0;
 
 		purple_debug_info("jabber", "BOSH connection manager version %s\n", version);
--- a/libpurple/protocols/jabber/buddy.c	Wed Mar 10 05:10:43 2010 +0000
+++ b/libpurple/protocols/jabber/buddy.c	Wed Mar 10 05:12:35 2010 +0000
@@ -1824,7 +1824,8 @@
 	if(!jb)
 		return m;
 
-	if (js->protocol_version == JABBER_PROTO_0_9 && jb != js->user_jb) {
+	if (js->protocol_version.major == 0 && js->protocol_version.minor == 9 &&
+			jb != js->user_jb) {
 		if(jb->invisible & JABBER_INVIS_BUDDY) {
 			act = purple_menu_action_new(_("Un-hide From"),
 			                           PURPLE_CALLBACK(jabber_buddy_make_visible),
--- a/libpurple/protocols/jabber/data.c	Wed Mar 10 05:10:43 2010 +0000
+++ b/libpurple/protocols/jabber/data.c	Wed Mar 10 05:12:35 2010 +0000
@@ -39,7 +39,7 @@
 	JabberStream *js)
 {
 	JabberData *data = g_new0(JabberData, 1);
-	gchar *checksum = purple_util_get_image_checksum(rawdata, size);
+	gchar *checksum = jabber_calculate_data_sha1sum(rawdata, size);
 	gchar cid[256];
 
 	g_snprintf(cid, sizeof(cid), "sha1+%s@bob.xmpp.org", checksum);
--- a/libpurple/protocols/jabber/jabber.c	Wed Mar 10 05:10:43 2010 +0000
+++ b/libpurple/protocols/jabber/jabber.c	Wed Mar 10 05:12:35 2010 +0000
@@ -74,7 +74,10 @@
 
 GList *jabber_features = NULL;
 GList *jabber_identities = NULL;
-static GSList *jabber_cmds = NULL;
+
+static GHashTable *jabber_cmds = NULL; /* PurplePlugin * => GSList of ids */
+
+static gint plugin_ref = 0;
 
 static void jabber_unregister_account_cb(JabberStream *js);
 static void try_srv_connect(JabberStream *js);
@@ -867,7 +870,8 @@
 	js->old_length = 0;
 	js->keepalive_timeout = 0;
 	/* Set the default protocol version to 1.0. Overridden in parser.c. */
-	js->protocol_version = JABBER_PROTO_1_0;
+	js->protocol_version.major = 1;
+	js->protocol_version.minor = 0;
 	js->sessions = NULL;
 	js->stun_ip = NULL;
 	js->stun_port = 0;
@@ -3330,6 +3334,8 @@
 		} else {
 			jabber_mood_set(js, args[0], args[1]);
 		}
+
+		return PURPLE_CMD_RET_OK;
 	} else {
 		/* account does not support PEP, can't set a mood */
 		purple_conversation_write(conv, NULL,
@@ -3337,44 +3343,43 @@
 		    PURPLE_MESSAGE_ERROR, time(NULL));
 		return PURPLE_CMD_RET_FAILED;
 	}
-
-	return PURPLE_CMD_STATUS_OK;
 }
 
-void jabber_register_commands(void)
+static void jabber_register_commands(PurplePlugin *plugin)
 {
+	GSList *commands = NULL;
 	PurpleCmdId id;
 	id = purple_cmd_register("config", "", PURPLE_CMD_P_PRPL,
 	                  PURPLE_CMD_FLAG_CHAT | PURPLE_CMD_FLAG_PRPL_ONLY,
 	                  "prpl-jabber", jabber_cmd_chat_config,
 	                  _("config:  Configure a chat room."), NULL);
-	jabber_cmds = g_slist_prepend(jabber_cmds, GUINT_TO_POINTER(id));
+	commands = g_slist_prepend(commands, GUINT_TO_POINTER(id));
 
 	id = purple_cmd_register("configure", "", PURPLE_CMD_P_PRPL,
 	                  PURPLE_CMD_FLAG_CHAT | PURPLE_CMD_FLAG_PRPL_ONLY,
 	                  "prpl-jabber", jabber_cmd_chat_config,
 	                  _("configure:  Configure a chat room."), NULL);
-	jabber_cmds = g_slist_prepend(jabber_cmds, GUINT_TO_POINTER(id));
+	commands = g_slist_prepend(commands, GUINT_TO_POINTER(id));
 
 	id = purple_cmd_register("nick", "s", PURPLE_CMD_P_PRPL,
 	                  PURPLE_CMD_FLAG_CHAT | PURPLE_CMD_FLAG_PRPL_ONLY,
 	                  "prpl-jabber", jabber_cmd_chat_nick,
 	                  _("nick &lt;new nickname&gt;:  Change your nickname."),
 	                  NULL);
-	jabber_cmds = g_slist_prepend(jabber_cmds, GUINT_TO_POINTER(id));
+	commands = g_slist_prepend(commands, GUINT_TO_POINTER(id));
 
 	id = purple_cmd_register("part", "s", PURPLE_CMD_P_PRPL,
 	                  PURPLE_CMD_FLAG_CHAT | PURPLE_CMD_FLAG_PRPL_ONLY |
 	                  PURPLE_CMD_FLAG_ALLOW_WRONG_ARGS, "prpl-jabber",
 	                  jabber_cmd_chat_part, _("part [message]:  Leave the room."),
 	                  NULL);
-	jabber_cmds = g_slist_prepend(jabber_cmds, GUINT_TO_POINTER(id));
+	commands = g_slist_prepend(commands, GUINT_TO_POINTER(id));
 
 	id = purple_cmd_register("register", "", PURPLE_CMD_P_PRPL,
 	                  PURPLE_CMD_FLAG_CHAT | PURPLE_CMD_FLAG_PRPL_ONLY,
 	                  "prpl-jabber", jabber_cmd_chat_register,
 	                  _("register:  Register with a chat room."), NULL);
-	jabber_cmds = g_slist_prepend(jabber_cmds, GUINT_TO_POINTER(id));
+	commands = g_slist_prepend(commands, GUINT_TO_POINTER(id));
 
 	/* XXX: there needs to be a core /topic cmd, methinks */
 	id = purple_cmd_register("topic", "s", PURPLE_CMD_P_PRPL,
@@ -3383,7 +3388,7 @@
 	                  jabber_cmd_chat_topic,
 	                  _("topic [new topic]:  View or change the topic."),
 	                  NULL);
-	jabber_cmds = g_slist_prepend(jabber_cmds, GUINT_TO_POINTER(id));
+	commands = g_slist_prepend(commands, GUINT_TO_POINTER(id));
 
 	id = purple_cmd_register("ban", "ws", PURPLE_CMD_P_PRPL,
 	                  PURPLE_CMD_FLAG_CHAT | PURPLE_CMD_FLAG_PRPL_ONLY |
@@ -3391,7 +3396,7 @@
 	                  jabber_cmd_chat_ban,
 	                  _("ban &lt;user&gt; [reason]:  Ban a user from the room."),
 	                  NULL);
-	jabber_cmds = g_slist_prepend(jabber_cmds, GUINT_TO_POINTER(id));
+	commands = g_slist_prepend(commands, GUINT_TO_POINTER(id));
 
 	id = purple_cmd_register("affiliate", "ws", PURPLE_CMD_P_PRPL,
 	                  PURPLE_CMD_FLAG_CHAT | PURPLE_CMD_FLAG_PRPL_ONLY |
@@ -3399,7 +3404,7 @@
 	                  jabber_cmd_chat_affiliate,
 	                  _("affiliate &lt;owner|admin|member|outcast|none&gt; [nick1] [nick2] ...: Get the users with an affiliation or set users' affiliation with the room."),
 	                  NULL);
-	jabber_cmds = g_slist_prepend(jabber_cmds, GUINT_TO_POINTER(id));
+	commands = g_slist_prepend(commands, GUINT_TO_POINTER(id));
 
 	id = purple_cmd_register("role", "ws", PURPLE_CMD_P_PRPL,
 	                  PURPLE_CMD_FLAG_CHAT | PURPLE_CMD_FLAG_PRPL_ONLY |
@@ -3407,7 +3412,7 @@
 	                  jabber_cmd_chat_role,
 	                  _("role &lt;moderator|participant|visitor|none&gt; [nick1] [nick2] ...: Get the users with a role or set users' role with the room."),
 	                  NULL);
-	jabber_cmds = g_slist_prepend(jabber_cmds, GUINT_TO_POINTER(id));
+	commands = g_slist_prepend(commands, GUINT_TO_POINTER(id));
 
 	id = purple_cmd_register("invite", "ws", PURPLE_CMD_P_PRPL,
 	                  PURPLE_CMD_FLAG_CHAT | PURPLE_CMD_FLAG_PRPL_ONLY |
@@ -3415,7 +3420,7 @@
 	                  jabber_cmd_chat_invite,
 	                  _("invite &lt;user&gt; [message]:  Invite a user to the room."),
 	                  NULL);
-	jabber_cmds = g_slist_prepend(jabber_cmds, GUINT_TO_POINTER(id));
+	commands = g_slist_prepend(commands, GUINT_TO_POINTER(id));
 
 	id = purple_cmd_register("join", "ws", PURPLE_CMD_P_PRPL,
 	                  PURPLE_CMD_FLAG_CHAT | PURPLE_CMD_FLAG_PRPL_ONLY |
@@ -3423,7 +3428,7 @@
 	                  jabber_cmd_chat_join,
 	                  _("join: &lt;room&gt; [password]:  Join a chat on this server."),
 	                  NULL);
-	jabber_cmds = g_slist_prepend(jabber_cmds, GUINT_TO_POINTER(id));
+	commands = g_slist_prepend(commands, GUINT_TO_POINTER(id));
 
 	id = purple_cmd_register("kick", "ws", PURPLE_CMD_P_PRPL,
 	                  PURPLE_CMD_FLAG_CHAT | PURPLE_CMD_FLAG_PRPL_ONLY |
@@ -3431,14 +3436,14 @@
 	                  jabber_cmd_chat_kick,
 	                  _("kick &lt;user&gt; [reason]:  Kick a user from the room."),
 	                  NULL);
-	jabber_cmds = g_slist_prepend(jabber_cmds, GUINT_TO_POINTER(id));
+	commands = g_slist_prepend(commands, GUINT_TO_POINTER(id));
 
 	id = purple_cmd_register("msg", "ws", PURPLE_CMD_P_PRPL,
 	                  PURPLE_CMD_FLAG_CHAT | PURPLE_CMD_FLAG_PRPL_ONLY,
 	                  "prpl-jabber", jabber_cmd_chat_msg,
 	                  _("msg &lt;user&gt; &lt;message&gt;:  Send a private message to another user."),
 	                  NULL);
-	jabber_cmds = g_slist_prepend(jabber_cmds, GUINT_TO_POINTER(id));
+	commands = g_slist_prepend(commands, GUINT_TO_POINTER(id));
 
 	id = purple_cmd_register("ping", "w", PURPLE_CMD_P_PRPL,
 					  PURPLE_CMD_FLAG_CHAT | PURPLE_CMD_FLAG_IM |
@@ -3446,31 +3451,39 @@
 					  "prpl-jabber", jabber_cmd_ping,
 					  _("ping &lt;jid&gt;:	Ping a user/component/server."),
 					  NULL);
-	jabber_cmds = g_slist_prepend(jabber_cmds, GUINT_TO_POINTER(id));
+	commands = g_slist_prepend(commands, GUINT_TO_POINTER(id));
 
 	id = purple_cmd_register("buzz", "w", PURPLE_CMD_P_PRPL,
 					  PURPLE_CMD_FLAG_IM | PURPLE_CMD_FLAG_PRPL_ONLY |
 					  PURPLE_CMD_FLAG_ALLOW_WRONG_ARGS,
 					  "prpl-jabber", jabber_cmd_buzz,
 					  _("buzz: Buzz a user to get their attention"), NULL);
-	jabber_cmds = g_slist_prepend(jabber_cmds, GUINT_TO_POINTER(id));
+	commands = g_slist_prepend(commands, GUINT_TO_POINTER(id));
 
 	id = purple_cmd_register("mood", "ws", PURPLE_CMD_P_PRPL,
 	    			  PURPLE_CMD_FLAG_CHAT | PURPLE_CMD_FLAG_IM |
 	    			  PURPLE_CMD_FLAG_PRPL_ONLY | PURPLE_CMD_FLAG_ALLOW_WRONG_ARGS,
 	    			  "prpl-jabber", jabber_cmd_mood,
 	    			  _("mood: Set current user mood"), NULL);
-	jabber_cmds = g_slist_prepend(jabber_cmds, GUINT_TO_POINTER(id));
+	commands = g_slist_prepend(commands, GUINT_TO_POINTER(id));
+
+	g_hash_table_insert(jabber_cmds, plugin, commands);
 }
 
-void jabber_unregister_commands(void)
+static void cmds_free_func(gpointer value)
 {
-	while (jabber_cmds != NULL) {
-		purple_cmd_unregister(GPOINTER_TO_UINT(jabber_cmds->data));
-		jabber_cmds = g_slist_delete_link(jabber_cmds, jabber_cmds);
+	GSList *commands = value;
+	while (commands) {
+		purple_cmd_unregister(GPOINTER_TO_UINT(commands->data));
+		commands = g_slist_delete_link(commands, commands);
 	}
 }
 
+static void jabber_unregister_commands(PurplePlugin *plugin)
+{
+	g_hash_table_remove(jabber_cmds, plugin);
+}
+
 /* IPC functions */
 
 /**
@@ -3519,14 +3532,46 @@
 	jabber_caps_broadcast_change();
 }
 
-void
-jabber_init_plugin(PurplePlugin *plugin)
+static void
+jabber_do_init(void)
 {
 	GHashTable *ui_info = purple_core_get_ui_info();
 	const gchar *ui_type;
 	const gchar *type = "pc"; /* default client type, if unknown or
 								unspecified */
 	const gchar *ui_name = NULL;
+#ifdef HAVE_CYRUS_SASL
+	/* We really really only want to do this once per process */
+	static gboolean sasl_initialized = FALSE;
+#ifdef _WIN32
+	UINT old_error_mode;
+	gchar *sasldir;
+#endif
+	int ret;
+#endif
+
+	/* XXX - If any other plugin wants SASL this won't be good ... */
+#ifdef HAVE_CYRUS_SASL
+	if (!sasl_initialized) {
+		sasl_initialized = TRUE;
+#ifdef _WIN32
+		sasldir = g_build_filename(wpurple_install_dir(), "sasl2", NULL);
+		sasl_set_path(SASL_PATH_TYPE_PLUGIN, sasldir);
+		g_free(sasldir);
+		/* Suppress error popups for failing to load sasl plugins */
+		old_error_mode = SetErrorMode(SEM_FAILCRITICALERRORS);
+#endif
+		if ((ret = sasl_client_init(NULL)) != SASL_OK) {
+			purple_debug_error("xmpp", "Error (%d) initializing SASL.\n", ret);
+		}
+#ifdef _WIN32
+		/* Restore the original error mode */
+		SetErrorMode(old_error_mode);
+#endif
+	}
+#endif
+
+	jabber_cmds = g_hash_table_new_full(g_direct_hash, g_direct_equal, NULL, cmds_free_func);
 
 	ui_type = ui_info ? g_hash_table_lookup(ui_info, "client_type") : NULL;
 	if (ui_type) {
@@ -3590,7 +3635,53 @@
 			G_CALLBACK(jabber_caps_broadcast_change), NULL);
 #endif
 
+	/* reverse order of unload_plugin */
+	jabber_iq_init();
+	jabber_presence_init();
+	jabber_caps_init();
+	/* PEP things should be init via jabber_pep_init, not here */
+	jabber_pep_init();
+	jabber_data_init();
+	jabber_bosh_init();
+
+	/* TODO: Implement adding and retrieving own features via IPC API */
+
+	jabber_ibb_init();
+	jabber_si_init();
+
 	jabber_auth_init();
+}
+
+static void
+jabber_do_uninit(void)
+{
+	/* reverse order of jabber_do_init */
+	jabber_bosh_uninit();
+	jabber_data_uninit();
+	jabber_si_uninit();
+	jabber_ibb_uninit();
+	/* PEP things should be uninit via jabber_pep_uninit, not here */
+	jabber_pep_uninit();
+	jabber_caps_uninit();
+	jabber_presence_uninit();
+	jabber_iq_uninit();
+
+	jabber_auth_uninit();
+	jabber_features_destroy();
+	jabber_identities_destroy();
+
+	g_hash_table_destroy(jabber_cmds);
+	jabber_cmds = NULL;
+}
+
+void jabber_plugin_init(PurplePlugin *plugin)
+{
+	++plugin_ref;
+
+	if (plugin_ref == 1)
+		jabber_do_init();
+
+	jabber_register_commands(plugin);
 
 	/* IPC functions */
 	purple_plugin_ipc_register(plugin, "contact_has_feature", PURPLE_CALLBACK(jabber_ipc_contact_has_feature),
@@ -3605,7 +3696,6 @@
 							 NULL, 1,
 							 purple_value_new(PURPLE_TYPE_STRING));
 
-	/* Modifying these? Look at libxmpp.c:load_plugin for the signal versions */
 	purple_plugin_ipc_register(plugin, "register_namespace_watcher",
 	                           PURPLE_CALLBACK(jabber_iq_signal_register),
 	                           purple_marshal_VOID__POINTER_POINTER,
@@ -3619,14 +3709,95 @@
 	                           NULL, 2,
 	                           purple_value_new(PURPLE_TYPE_STRING),  /* node */
 	                           purple_value_new(PURPLE_TYPE_STRING)); /* namespace */
+
+	purple_signal_register(plugin, "jabber-register-namespace-watcher",
+			purple_marshal_VOID__POINTER_POINTER,
+			NULL, 2,
+			purple_value_new(PURPLE_TYPE_STRING),  /* node */
+			purple_value_new(PURPLE_TYPE_STRING)); /* namespace */
+
+	purple_signal_register(plugin, "jabber-unregister-namespace-watcher",
+			purple_marshal_VOID__POINTER_POINTER,
+			NULL, 2,
+			purple_value_new(PURPLE_TYPE_STRING),  /* node */
+			purple_value_new(PURPLE_TYPE_STRING)); /* namespace */
+
+	purple_signal_connect(plugin, "jabber-register-namespace-watcher",
+			plugin, PURPLE_CALLBACK(jabber_iq_signal_register), NULL);
+	purple_signal_connect(plugin, "jabber-unregister-namespace-watcher",
+			plugin, PURPLE_CALLBACK(jabber_iq_signal_unregister), NULL);
+
+
+	purple_signal_register(plugin, "jabber-receiving-xmlnode",
+			purple_marshal_VOID__POINTER_POINTER, NULL, 2,
+			purple_value_new(PURPLE_TYPE_SUBTYPE, PURPLE_SUBTYPE_CONNECTION),
+			purple_value_new_outgoing(PURPLE_TYPE_SUBTYPE, PURPLE_SUBTYPE_XMLNODE));
+
+	purple_signal_register(plugin, "jabber-sending-xmlnode",
+			purple_marshal_VOID__POINTER_POINTER, NULL, 2,
+			purple_value_new(PURPLE_TYPE_SUBTYPE, PURPLE_SUBTYPE_CONNECTION),
+			purple_value_new_outgoing(PURPLE_TYPE_SUBTYPE, PURPLE_SUBTYPE_XMLNODE));
+
+	/*
+	 * Do not remove this or the plugin will fail. Completely. You have been
+	 * warned!
+	 */
+	purple_signal_connect_priority(plugin, "jabber-sending-xmlnode",
+			plugin, PURPLE_CALLBACK(jabber_send_signal_cb),
+			NULL, PURPLE_SIGNAL_PRIORITY_HIGHEST);
+
+	purple_signal_register(plugin, "jabber-sending-text",
+			     purple_marshal_VOID__POINTER_POINTER, NULL, 2,
+			     purple_value_new(PURPLE_TYPE_SUBTYPE, PURPLE_SUBTYPE_CONNECTION),
+			     purple_value_new_outgoing(PURPLE_TYPE_STRING));
+
+	purple_signal_register(plugin, "jabber-receiving-message",
+			purple_marshal_BOOLEAN__POINTER_POINTER_POINTER_POINTER_POINTER_POINTER,
+			purple_value_new(PURPLE_TYPE_BOOLEAN), 6,
+			purple_value_new(PURPLE_TYPE_SUBTYPE, PURPLE_SUBTYPE_CONNECTION),
+			purple_value_new(PURPLE_TYPE_STRING), /* type */
+			purple_value_new(PURPLE_TYPE_STRING), /* id */
+			purple_value_new(PURPLE_TYPE_STRING), /* from */
+			purple_value_new(PURPLE_TYPE_STRING), /* to */
+			purple_value_new(PURPLE_TYPE_SUBTYPE, PURPLE_SUBTYPE_XMLNODE));
+
+	purple_signal_register(plugin, "jabber-receiving-iq",
+			purple_marshal_BOOLEAN__POINTER_POINTER_POINTER_POINTER_POINTER,
+			purple_value_new(PURPLE_TYPE_BOOLEAN), 5,
+			purple_value_new(PURPLE_TYPE_SUBTYPE, PURPLE_SUBTYPE_CONNECTION),
+			purple_value_new(PURPLE_TYPE_STRING), /* type */
+			purple_value_new(PURPLE_TYPE_STRING), /* id */
+			purple_value_new(PURPLE_TYPE_STRING), /* from */
+			purple_value_new(PURPLE_TYPE_SUBTYPE, PURPLE_SUBTYPE_XMLNODE));
+
+	purple_signal_register(plugin, "jabber-watched-iq",
+			purple_marshal_BOOLEAN__POINTER_POINTER_POINTER_POINTER_POINTER,
+			purple_value_new(PURPLE_TYPE_BOOLEAN), 5,
+			purple_value_new(PURPLE_TYPE_SUBTYPE, PURPLE_SUBTYPE_CONNECTION),
+			purple_value_new(PURPLE_TYPE_STRING), /* type */
+			purple_value_new(PURPLE_TYPE_STRING), /* id */
+			purple_value_new(PURPLE_TYPE_STRING), /* from */
+			purple_value_new(PURPLE_TYPE_SUBTYPE, PURPLE_SUBTYPE_XMLNODE)); /* child */
+
+	purple_signal_register(plugin, "jabber-receiving-presence",
+			purple_marshal_BOOLEAN__POINTER_POINTER_POINTER_POINTER,
+			purple_value_new(PURPLE_TYPE_BOOLEAN), 4,
+			purple_value_new(PURPLE_TYPE_SUBTYPE, PURPLE_SUBTYPE_CONNECTION),
+			purple_value_new(PURPLE_TYPE_STRING), /* type */
+			purple_value_new(PURPLE_TYPE_STRING), /* from */
+			purple_value_new(PURPLE_TYPE_SUBTYPE, PURPLE_SUBTYPE_XMLNODE));
 }
 
-void
-jabber_uninit_plugin(PurplePlugin *plugin)
+void jabber_plugin_uninit(PurplePlugin *plugin)
 {
+	g_return_if_fail(plugin_ref > 0);
+
+	purple_signals_unregister_by_instance(plugin);
 	purple_plugin_ipc_unregister_all(plugin);
 
-	jabber_auth_uninit();
-	jabber_features_destroy();
-	jabber_identities_destroy();
+	jabber_unregister_commands(plugin);
+
+	--plugin_ref;
+	if (plugin_ref == 0)
+		jabber_do_uninit();
 }
--- a/libpurple/protocols/jabber/jabber.h	Wed Mar 10 05:10:43 2010 +0000
+++ b/libpurple/protocols/jabber/jabber.h	Wed Mar 10 05:12:35 2010 +0000
@@ -105,9 +105,9 @@
 	xmlParserCtxt *context;
 	xmlnode *current;
 
-	enum {
-		JABBER_PROTO_0_9,
-		JABBER_PROTO_1_0
+	struct {
+		guint8 major;
+		guint8 minor;
 	} protocol_version;
 
 	JabberSaslMech *auth_mech;
@@ -376,10 +376,7 @@
 PurpleMediaCaps jabber_get_media_caps(PurpleAccount *account, const char *who);
 gboolean jabber_can_receive_file(PurpleConnection *gc, const gchar *who);
 
-void jabber_register_commands(void);
-void jabber_unregister_commands(void);
-
-void jabber_init_plugin(PurplePlugin *plugin);
-void jabber_uninit_plugin(PurplePlugin *plugin);
+void jabber_plugin_init(PurplePlugin *plugin);
+void jabber_plugin_uninit(PurplePlugin *plugin);
 
 #endif /* PURPLE_JABBER_H_ */
--- a/libpurple/protocols/jabber/libxmpp.c	Wed Mar 10 05:10:43 2010 +0000
+++ b/libpurple/protocols/jabber/libxmpp.c	Wed Mar 10 05:12:35 2010 +0000
@@ -132,104 +132,14 @@
 
 static gboolean load_plugin(PurplePlugin *plugin)
 {
-	purple_signal_register(plugin, "jabber-receiving-xmlnode",
-			purple_marshal_VOID__POINTER_POINTER, NULL, 2,
-			purple_value_new(PURPLE_TYPE_SUBTYPE, PURPLE_SUBTYPE_CONNECTION),
-			purple_value_new_outgoing(PURPLE_TYPE_SUBTYPE, PURPLE_SUBTYPE_XMLNODE));
-
-	purple_signal_register(plugin, "jabber-sending-xmlnode",
-			purple_marshal_VOID__POINTER_POINTER, NULL, 2,
-			purple_value_new(PURPLE_TYPE_SUBTYPE, PURPLE_SUBTYPE_CONNECTION),
-			purple_value_new_outgoing(PURPLE_TYPE_SUBTYPE, PURPLE_SUBTYPE_XMLNODE));
-
-	/*
-	 * Do not remove this or the plugin will fail. Completely. You have been
-	 * warned!
-	 */
-	purple_signal_connect_priority(plugin, "jabber-sending-xmlnode",
-			plugin, PURPLE_CALLBACK(jabber_send_signal_cb),
-			NULL, PURPLE_SIGNAL_PRIORITY_HIGHEST);
-
-	purple_signal_register(plugin, "jabber-sending-text",
-			     purple_marshal_VOID__POINTER_POINTER, NULL, 2,
-			     purple_value_new(PURPLE_TYPE_SUBTYPE, PURPLE_SUBTYPE_CONNECTION),
-			     purple_value_new_outgoing(PURPLE_TYPE_STRING));
-
-	purple_signal_register(plugin, "jabber-receiving-message",
-			purple_marshal_BOOLEAN__POINTER_POINTER_POINTER_POINTER_POINTER_POINTER,
-			purple_value_new(PURPLE_TYPE_BOOLEAN), 6,
-			purple_value_new(PURPLE_TYPE_SUBTYPE, PURPLE_SUBTYPE_CONNECTION),
-			purple_value_new(PURPLE_TYPE_STRING), /* type */
-			purple_value_new(PURPLE_TYPE_STRING), /* id */
-			purple_value_new(PURPLE_TYPE_STRING), /* from */
-			purple_value_new(PURPLE_TYPE_STRING), /* to */
-			purple_value_new(PURPLE_TYPE_SUBTYPE, PURPLE_SUBTYPE_XMLNODE));
-
-	purple_signal_register(plugin, "jabber-receiving-iq",
-			purple_marshal_BOOLEAN__POINTER_POINTER_POINTER_POINTER_POINTER,
-			purple_value_new(PURPLE_TYPE_BOOLEAN), 5,
-			purple_value_new(PURPLE_TYPE_SUBTYPE, PURPLE_SUBTYPE_CONNECTION),
-			purple_value_new(PURPLE_TYPE_STRING), /* type */
-			purple_value_new(PURPLE_TYPE_STRING), /* id */
-			purple_value_new(PURPLE_TYPE_STRING), /* from */
-			purple_value_new(PURPLE_TYPE_SUBTYPE, PURPLE_SUBTYPE_XMLNODE));
-
-	purple_signal_register(plugin, "jabber-watched-iq",
-			purple_marshal_BOOLEAN__POINTER_POINTER_POINTER_POINTER_POINTER,
-			purple_value_new(PURPLE_TYPE_BOOLEAN), 5,
-			purple_value_new(PURPLE_TYPE_SUBTYPE, PURPLE_SUBTYPE_CONNECTION),
-			purple_value_new(PURPLE_TYPE_STRING), /* type */
-			purple_value_new(PURPLE_TYPE_STRING), /* id */
-			purple_value_new(PURPLE_TYPE_STRING), /* from */
-			purple_value_new(PURPLE_TYPE_SUBTYPE, PURPLE_SUBTYPE_XMLNODE)); /* child */
-
-	/* Modifying these? Look at jabber_init_plugin for the ipc versions */
-	purple_signal_register(plugin, "jabber-register-namespace-watcher",
-			purple_marshal_VOID__POINTER_POINTER,
-			NULL, 2,
-			purple_value_new(PURPLE_TYPE_STRING),  /* node */
-			purple_value_new(PURPLE_TYPE_STRING)); /* namespace */
-
-	purple_signal_register(plugin, "jabber-unregister-namespace-watcher",
-			purple_marshal_VOID__POINTER_POINTER,
-			NULL, 2,
-			purple_value_new(PURPLE_TYPE_STRING),  /* node */
-			purple_value_new(PURPLE_TYPE_STRING)); /* namespace */
-
-	purple_signal_connect(plugin, "jabber-register-namespace-watcher",
-			plugin, PURPLE_CALLBACK(jabber_iq_signal_register), NULL);
-	purple_signal_connect(plugin, "jabber-unregister-namespace-watcher",
-			plugin, PURPLE_CALLBACK(jabber_iq_signal_unregister), NULL);
-
-	purple_signal_register(plugin, "jabber-receiving-presence",
-			purple_marshal_BOOLEAN__POINTER_POINTER_POINTER_POINTER,
-			purple_value_new(PURPLE_TYPE_BOOLEAN), 4,
-			purple_value_new(PURPLE_TYPE_SUBTYPE, PURPLE_SUBTYPE_CONNECTION),
-			purple_value_new(PURPLE_TYPE_STRING), /* type */
-			purple_value_new(PURPLE_TYPE_STRING), /* from */
-			purple_value_new(PURPLE_TYPE_SUBTYPE, PURPLE_SUBTYPE_XMLNODE));
+	jabber_plugin_init(plugin);
 
 	return TRUE;
 }
 
 static gboolean unload_plugin(PurplePlugin *plugin)
 {
-	purple_signals_unregister_by_instance(plugin);
-
-	/* reverse order of init_plugin */
-	jabber_bosh_uninit();
-	jabber_data_uninit();
-	jabber_si_uninit();
-	jabber_ibb_uninit();
-	/* PEP things should be uninit via jabber_pep_uninit, not here */
-	jabber_pep_uninit();
-	jabber_caps_uninit();
-	jabber_iq_uninit();
-
-	jabber_unregister_commands();
-
-	/* Stay on target...stay on target... Almost there... */
-	jabber_uninit_plugin(plugin);
+	jabber_plugin_uninit(plugin);
 
 	return TRUE;
 }
@@ -339,13 +249,6 @@
 static void
 init_plugin(PurplePlugin *plugin)
 {
-#ifdef HAVE_CYRUS_SASL
-#ifdef _WIN32
-	UINT old_error_mode;
-	gchar *sasldir;
-#endif
-	int ret;
-#endif
 	PurpleAccountUserSplit *split;
 	PurpleAccountOption *option;
 
@@ -402,42 +305,9 @@
 		option);
 
 	my_protocol = plugin;
-	jabber_init_plugin(plugin);
 
 	purple_prefs_remove("/plugins/prpl/jabber");
 
-	/* XXX - If any other plugin wants SASL this won't be good ... */
-#ifdef HAVE_CYRUS_SASL
-#ifdef _WIN32
-	sasldir = g_build_filename(wpurple_install_dir(), "sasl2", NULL);
-	sasl_set_path(SASL_PATH_TYPE_PLUGIN, sasldir);
-	g_free(sasldir);
-	/* Suppress error popups for failing to load sasl plugins */
-	old_error_mode = SetErrorMode(SEM_FAILCRITICALERRORS);
-#endif
-	if ((ret = sasl_client_init(NULL)) != SASL_OK) {
-		purple_debug_error("xmpp", "Error (%d) initializing SASL.\n", ret);
-	}
-#ifdef _WIN32
-	/* Restore the original error mode */
-	SetErrorMode(old_error_mode);
-#endif
-#endif
-	jabber_register_commands();
-
-	/* reverse order of unload_plugin */
-	jabber_iq_init();
-	jabber_caps_init();
-	/* PEP things should be init via jabber_pep_init, not here */
-	jabber_pep_init();
-	jabber_data_init();
-	jabber_bosh_init();
-
-	/* TODO: Implement adding and retrieving own features via IPC API */
-
-	jabber_ibb_init();
-	jabber_si_init();
-
 	purple_signal_connect(purple_get_core(), "uri-handler", plugin,
 		PURPLE_CALLBACK(xmpp_uri_handler), NULL);
 }
--- a/libpurple/protocols/jabber/message.c	Wed Mar 10 05:10:43 2010 +0000
+++ b/libpurple/protocols/jabber/message.c	Wed Mar 10 05:12:35 2010 +0000
@@ -74,24 +74,6 @@
 	jb = jabber_buddy_find(jm->js, jm->from, TRUE);
 	jbr = jabber_buddy_find_resource(jb, jid->resource);
 
-	if (jid->resource) {
-		/*
-		 * We received a message from a specific resource, so we probably want a
-		 * reply to go to this specific resource (i.e. bind/lock the
-		 * conversation to this resource).
-		 *
-		 * This works because purple_conv_im_send gets the name from
-		 * purple_conversation_get_name()
-		 */
-		PurpleConversation *conv;
-
-		conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, jm->from, account);
-		if (conv && !g_str_equal(jm->from, purple_conversation_get_name(conv))) {
-			purple_debug_info("jabber", "Binding conversation to %s\n", jm->from);
-			purple_conversation_set_name(conv, jm->from);
-		}
-	}
-
 	if(!jm->xhtml && !jm->body) {
 		if (jbr) {
 			if (jm->chat_state != JM_STATE_NONE)
@@ -137,6 +119,28 @@
 			serv_got_typing_stopped(gc, jm->from);
 		}
 	} else {
+		if (jid->resource) {
+			/*
+			 * We received a message from a specific resource, so
+			 * we probably want a reply to go to this specific
+			 * resource (i.e. bind/lock the conversation to this
+			 * resource).
+			 *
+			 * This works because purple_conv_im_send gets the name
+			 * from purple_conversation_get_name()
+			 */
+			PurpleConversation *conv;
+
+			conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM,
+			                                             jm->from, account);
+			if (conv && !g_str_equal(jm->from,
+			                         purple_conversation_get_name(conv))) {
+				purple_debug_info("jabber", "Binding conversation to %s\n",
+				                  jm->from);
+				purple_conversation_set_name(conv, jm->from);
+			}
+		}
+
 		if(jbr) {
 			if (jm->chat_state != JM_STATE_NONE)
 				jbr->chat_states = JABBER_CHAT_STATES_SUPPORTED;
--- a/libpurple/protocols/jabber/parser.c	Wed Mar 10 05:10:43 2010 +0000
+++ b/libpurple/protocols/jabber/parser.c	Wed Mar 10 05:12:35 2010 +0000
@@ -44,14 +44,28 @@
 	if(!element_name) {
 		return;
 	} else if(!xmlStrcmp(element_name, (xmlChar*) "stream")) {
-		js->protocol_version = JABBER_PROTO_0_9;
+		js->protocol_version.major = 0;
+		js->protocol_version.minor = 9;
 		for(i=0; i < nb_attributes * 5; i += 5) {
 			int attrib_len = attributes[i+4] - attributes[i+3];
 			char *attrib = g_strndup((gchar *)attributes[i+3], attrib_len);
 
-			if(!xmlStrcmp(attributes[i], (xmlChar*) "version")
-					&& !strcmp(attrib, "1.0")) {
-				js->protocol_version = JABBER_PROTO_1_0;
+			if(!xmlStrcmp(attributes[i], (xmlChar*) "version")) {
+				const char *dot = strchr(attrib, '.');
+
+				js->protocol_version.major = atoi(attrib);
+				js->protocol_version.minor = dot ? atoi(dot + 1) : 0;
+
+				if (js->protocol_version.major > 1)
+					/* TODO: Send <unsupported-version/> error */
+					purple_connection_error_reason(js->gc,
+							PURPLE_CONNECTION_ERROR_AUTHENTICATION_IMPOSSIBLE,
+							_("XMPP Version Mismatch"));
+
+				if (js->protocol_version.major == 0 && js->protocol_version.minor != 9) {
+					purple_debug_warning("jabber", "Treating version %s as 0.9 for backward "
+					                     "compatibility\n", attrib);
+				}
 				g_free(attrib);
 			} else if(!xmlStrcmp(attributes[i], (xmlChar*) "id")) {
 				g_free(js->stream_id);
@@ -255,7 +269,8 @@
 		}
 	}
 
-	if (js->protocol_version == JABBER_PROTO_0_9 && !js->gc->disconnect_timeout &&
+	if (js->protocol_version.major == 0 && js->protocol_version.minor == 9 &&
+			!js->gc->disconnect_timeout &&
 			(js->state == JABBER_STREAM_INITIALIZING ||
 			 js->state == JABBER_STREAM_INITIALIZING_ENCRYPTION)) {
 		/*
--- a/libpurple/protocols/jabber/presence.c	Wed Mar 10 05:10:43 2010 +0000
+++ b/libpurple/protocols/jabber/presence.c	Wed Mar 10 05:12:35 2010 +0000
@@ -43,6 +43,37 @@
 #include "usermood.h"
 #include "usertune.h"
 
+static GHashTable *presence_handlers = NULL;
+
+static const struct {
+	const char *name;
+	JabberPresenceType type;
+} jabber_presence_types[] = {
+	{ "error", JABBER_PRESENCE_ERROR },
+	{ "probe", JABBER_PRESENCE_PROBE },
+	{ "unavailable", JABBER_PRESENCE_UNAVAILABLE },
+	{ "subscribe", JABBER_PRESENCE_SUBSCRIBE },
+	{ "subscribed", JABBER_PRESENCE_SUBSCRIBED },
+	{ "unsubscribe", JABBER_PRESENCE_UNSUBSCRIBE },
+	{ "unsubscribed", JABBER_PRESENCE_UNSUBSCRIBED }
+	/* { NULL, JABBER_PRESENCE_AVAILABLE } the default */
+};
+
+static JabberPresenceType
+str_to_presence_type(const char *type)
+{
+	int i;
+
+	if (type == NULL)
+		return JABBER_PRESENCE_AVAILABLE;
+
+	for (i = 0; i < G_N_ELEMENTS(jabber_presence_types); ++i)
+		if (g_str_equal(type, jabber_presence_types[i].name))
+			return jabber_presence_types[i].type;
+
+	purple_debug_warning("jabber", "Unknown presence type '%s'\n", type);
+	return JABBER_PRESENCE_AVAILABLE;
+}
 
 static void chats_send_presence_foreach(gpointer key, gpointer val,
 		gpointer user_data)
@@ -476,7 +507,6 @@
 	purple_prpl_got_media_caps(
 			purple_connection_get_account(userdata->js->gc),
 			userdata->from);
-
 	if (info == NULL)
 		goto out;
 
@@ -508,573 +538,498 @@
 	g_free(userdata);
 }
 
+gboolean
+handle_presence_chat(JabberStream *js, JabberPresence *presence, xmlnode *packet)
+{
+	static int i = 1;
+	PurpleConvChatBuddyFlags flags = PURPLE_CBFLAGS_NONE;
+	JabberChat *chat = presence->chat;
+
+	if (presence->state == JABBER_BUDDY_STATE_ERROR) {
+		char *title, *msg = jabber_parse_error(js, packet, NULL);
+
+		if (!chat->conv) {
+			title = g_strdup_printf(_("Error joining chat %s"), presence->from);
+			purple_serv_got_join_chat_failed(js->gc, chat->components);
+		} else {
+			title = g_strdup_printf(_("Error in chat %s"), presence->from);
+			if (g_hash_table_size(chat->members) == 0)
+				serv_got_chat_left(js->gc, chat->id);
+		}
+		purple_notify_error(js->gc, title, title, msg);
+		g_free(title);
+		g_free(msg);
+
+		if (g_hash_table_size(chat->members) == 0)
+			/* Only destroy the chat if the error happened while joining */
+			jabber_chat_destroy(chat);
+		return FALSE;
+	}
+
+	if (presence->type == JABBER_PRESENCE_AVAILABLE) {
+		const char *jid = NULL;
+		const char *affiliation = NULL;
+		const char *role = NULL;
+		gboolean is_our_resource = FALSE; /* Is the presence about us? */
+		JabberBuddyResource *jbr;
+
+		/*
+		 * XEP-0045 mandates the presence to include a resource (which is
+		 * treated as the chat nick). Some non-compliant servers allow
+		 * joining without a nick.
+		 */
+		if (!presence->jid_from->resource)
+			return FALSE;
+
+		if (presence->chat_info.item) {
+			jid = xmlnode_get_attrib(presence->chat_info.item, "jid");
+			affiliation = xmlnode_get_attrib(presence->chat_info.item, "affiliation");
+			role = xmlnode_get_attrib(presence->chat_info.item, "role");
+		}
+
+		if (g_slist_find(presence->chat_info.codes, GINT_TO_POINTER(110)))
+			is_our_resource = TRUE;
+
+		if (g_slist_find(presence->chat_info.codes, GINT_TO_POINTER(201))) {
+			chat->config_dialog_type = PURPLE_REQUEST_ACTION;
+			chat->config_dialog_handle =
+				purple_request_action(js->gc,
+						_("Create New Room"),
+						_("Create New Room"),
+						_("You are creating a new room.  Would"
+							" you like to configure it, or"
+							" accept the default settings?"),
+						/* Default Action */ 1,
+						purple_connection_get_account(js->gc), NULL, chat->conv,
+						chat, 2,
+						_("_Configure Room"), G_CALLBACK(jabber_chat_request_room_configure),
+						_("_Accept Defaults"), G_CALLBACK(jabber_chat_create_instant_room));
+		}
+
+		if (g_slist_find(presence->chat_info.codes, GINT_TO_POINTER(210))) {
+			/* server rewrote room-nick */
+			g_free(chat->handle);
+			chat->handle = g_strdup(presence->jid_from->resource);
+		}
+
+		if (purple_strequal(affiliation, "owner"))
+			flags |= PURPLE_CBFLAGS_FOUNDER;
+		if (role) {
+			if (g_str_equal(role, "moderator"))
+				flags |= PURPLE_CBFLAGS_OP;
+			else if (g_str_equal(role, "participant"))
+				flags |= PURPLE_CBFLAGS_VOICE;
+		}
+
+		if(!chat->conv) {
+			char *room_jid = g_strdup_printf("%s@%s", presence->jid_from->node, presence->jid_from->domain);
+			chat->id = i++;
+			chat->conv = serv_got_joined_chat(js->gc, chat->id, room_jid);
+			purple_conv_chat_set_nick(PURPLE_CONV_CHAT(chat->conv), chat->handle);
+
+			jabber_chat_disco_traffic(chat);
+			g_free(room_jid);
+		}
+
+		jbr = jabber_buddy_track_resource(presence->jb, presence->jid_from->resource, presence->priority, presence->state, presence->status);
+		jbr->commands_fetched = TRUE;
+
+		jabber_chat_track_handle(chat, presence->jid_from->resource, jid, affiliation, role);
+
+		if(!jabber_chat_find_buddy(chat->conv, presence->jid_from->resource))
+			purple_conv_chat_add_user(PURPLE_CONV_CHAT(chat->conv), presence->jid_from->resource,
+					jid, flags, !presence->delayed);
+		else
+			purple_conv_chat_user_set_flags(PURPLE_CONV_CHAT(chat->conv), presence->jid_from->resource,
+					flags);
+	} else if (presence->type == JABBER_PRESENCE_UNAVAILABLE) {
+		gboolean nick_change = FALSE;
+		gboolean kick = FALSE;
+		gboolean is_our_resource = FALSE; /* Is the presence about us? */
+
+		const char *jid = NULL;
+
+		/* If the chat nick is invalid, we haven't yet joined, or we've
+		 * already left (it was probably us leaving after we closed the
+		 * chat), we don't care.
+		 */
+		if (!presence->jid_from->resource || !chat->conv || chat->left) {
+			if (chat->left &&
+					presence->jid_from->resource && chat->handle && !strcmp(presence->jid_from->resource, chat->handle))
+				jabber_chat_destroy(chat);
+			return FALSE;
+		}
+
+		is_our_resource = (0 == g_utf8_collate(presence->jid_from->resource, chat->handle));
+
+		jabber_buddy_remove_resource(presence->jb, presence->jid_from->resource);
+
+		if (presence->chat_info.item)
+			jid = xmlnode_get_attrib(presence->chat_info.item, "jid");
+
+		if (chat->muc) {
+			if (g_slist_find(presence->chat_info.codes, GINT_TO_POINTER(110)))
+				is_our_resource = TRUE;
+			
+			if (g_slist_find(presence->chat_info.codes, GINT_TO_POINTER(301))) {
+				/* XXX: We got banned.  YAY! (No GIR, that's bad) */
+			}
+
+
+			if (g_slist_find(presence->chat_info.codes, GINT_TO_POINTER(303))) {
+				const char *nick = NULL;
+				if (presence->chat_info.item)
+					nick = xmlnode_get_attrib(presence->chat_info.item, "nick");
+
+				/* nick change */
+				if (nick) {
+					purple_debug_warning("jabber", "Chat presence indicating a nick change, but no new nickname!\n");
+				} else {
+					nick_change = TRUE;
+
+					if (g_str_equal(presence->jid_from->resource, chat->handle)) {
+						/* Changing our own nickname */
+						g_free(chat->handle);
+						chat->handle = g_strdup(nick);
+					}
+
+					purple_conv_chat_rename_user(PURPLE_CONV_CHAT(chat->conv),
+					                             presence->jid_from->resource,
+					                             nick);
+					jabber_chat_remove_handle(chat,
+					                          presence->jid_from->resource);
+				}
+			}
+
+			if (g_slist_find(presence->chat_info.codes, GINT_TO_POINTER(307))) {
+				/* Someone was kicked from the room */
+				const char *actor = NULL;
+				char *reason = NULL;
+				char *tmp;
+
+				kick = TRUE;
+
+				if (presence->chat_info.item) {
+					xmlnode *node;
+
+					node = xmlnode_get_child(presence->chat_info.item, "actor");
+					if (node)
+						actor = xmlnode_get_attrib(node, "jid");
+					node = xmlnode_get_child(presence->chat_info.item, "reason");
+					if (node)
+						reason = xmlnode_get_data(node);
+				}
+
+				if (reason == NULL)
+					reason = g_strdup(_("No reason"));
+
+				if (is_our_resource) {
+					if (actor)
+						tmp = g_strdup_printf(_("You have been kicked by %s: (%s)"),
+								actor, reason);
+					else
+						tmp = g_strdup_printf(_("You have been kicked: (%s)"),
+								reason);
+				} else {
+					if (actor)
+						tmp = g_strdup_printf(_("Kicked by %s (%s)"),
+								actor, reason);
+					else
+						tmp = g_strdup_printf(_("Kicked (%s)"),
+								reason);
+				}
+
+				g_free(presence->status);
+				presence->status = tmp;
+
+				g_free(reason);
+			}
+			
+			if (g_slist_find(presence->chat_info.codes, GINT_TO_POINTER(321))) {
+				/* XXX: removed due to an affiliation change */
+			}
+			
+			if (g_slist_find(presence->chat_info.codes, GINT_TO_POINTER(322))) {
+				/* XXX: removed because room is now members-only */
+			}
+			
+			if (g_slist_find(presence->chat_info.codes, GINT_TO_POINTER(332))) {
+				/* XXX: removed due to system shutdown */
+			}
+		}
+
+		/*
+		 * Possibly another connected resource of our JID (see XEP-0045
+		 * v1.24 section 7.1.10) being disconnected. Should be
+		 * distinguished by the item_jid.
+		 * Also possibly works around bits of an Openfire bug. See
+		 * #8319.
+		 */
+		if (is_our_resource && jid && !purple_strequal(presence->to, jid)) {
+			/* TODO: When the above is a loop, this needs to still act
+			 * sanely for all cases (this code is a little fragile). */
+			if (!kick && !nick_change)
+				/* Presumably, kicks and nick changes also affect us. */
+				is_our_resource = FALSE;
+		}
+
+		if(!nick_change) {
+			if (is_our_resource) {
+				if (kick)
+					purple_conv_chat_write(PURPLE_CONV_CHAT(chat->conv), presence->jid_from->resource,
+							presence->status, PURPLE_MESSAGE_SYSTEM, time(NULL));
+
+				serv_got_chat_left(js->gc, chat->id);
+				jabber_chat_destroy(chat);
+			} else {
+				purple_conv_chat_remove_user(PURPLE_CONV_CHAT(chat->conv), presence->jid_from->resource,
+						presence->status);
+				jabber_chat_remove_handle(chat, presence->jid_from->resource);
+			}
+		}
+	}
+
+	return TRUE;
+}
+
+gboolean
+handle_presence_contact(JabberStream *js, JabberPresence *presence)
+{
+	JabberBuddyResource *jbr;
+	PurpleAccount *account;
+	PurpleBuddy *b;
+	char *buddy_name;
+	PurpleConversation *conv;
+
+	buddy_name = jabber_id_get_bare_jid(presence->jid_from);
+
+	account = purple_connection_get_account(js->gc);
+	b = purple_find_buddy(account, buddy_name);
+
+	/*
+	 * Unbind/unlock from sending messages to a specific resource on
+	 * presence changes.  This is locked to a specific resource when
+	 * receiving a message (in message.c).
+	 */
+	conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM,
+			buddy_name, account);
+	if (conv) {
+		purple_debug_info("jabber", "Changed conversation binding from %s to %s\n",
+				purple_conversation_get_name(conv), buddy_name);
+		purple_conversation_set_name(conv, buddy_name);
+	}
+
+	if (b == NULL) {
+		if (presence->jb != js->user_jb) {
+			purple_debug_warning("jabber", "Got presence for unknown buddy %s on account %s (%p)\n",
+					buddy_name, purple_account_get_username(account), account);
+			return FALSE;
+		} else {
+			/* this is a different resource of our own account. Resume even when this account isn't on our blist */
+		}
+	}
+
+	if(b && presence->vcard_avatar_hash) {
+		const char *avatar_hash2 = purple_buddy_icons_get_checksum_for_user(b);
+		if(!avatar_hash2 || strcmp(presence->vcard_avatar_hash, avatar_hash2)) {
+			JabberIq *iq;
+			xmlnode *vcard;
+
+			/* XXX this is a crappy way of trying to prevent
+			 * someone from spamming us with presence packets
+			 * and causing us to DoS ourselves...what we really
+			 * need is a queue system that can throttle itself,
+			 * but i'm too tired to write that right now */
+			if(!g_slist_find(js->pending_avatar_requests, presence->jb)) {
+
+				js->pending_avatar_requests = g_slist_prepend(js->pending_avatar_requests, presence->jb);
+
+				iq = jabber_iq_new(js, JABBER_IQ_GET);
+				xmlnode_set_attrib(iq->node, "to", buddy_name);
+				vcard = xmlnode_new_child(iq->node, "vCard");
+				xmlnode_set_namespace(vcard, "vcard-temp");
+
+				jabber_iq_set_callback(iq, jabber_vcard_parse_avatar, NULL);
+				jabber_iq_send(iq);
+			}
+		}
+	}
+
+	if (presence->state == JABBER_BUDDY_STATE_ERROR ||
+			presence->type == JABBER_PRESENCE_UNAVAILABLE ||
+			presence->type == JABBER_PRESENCE_UNSUBSCRIBED) {
+		jabber_buddy_remove_resource(presence->jb, presence->jid_from->resource);
+	} else {
+		jbr = jabber_buddy_track_resource(presence->jb,
+				presence->jid_from->resource, presence->priority,
+				presence->state, presence->status);
+		jbr->idle = presence->idle ? time(NULL) - presence->idle : 0;
+	}
+
+	jbr = jabber_buddy_find_resource(presence->jb, NULL);
+	if (jbr) {
+		jabber_google_presence_incoming(js, buddy_name, jbr);
+		purple_prpl_got_user_status(account, buddy_name,
+				jabber_buddy_state_get_status_id(jbr->state),
+				"priority", jbr->priority,
+				"message", jbr->status,
+				NULL);
+		purple_prpl_got_user_idle(account, buddy_name,
+				jbr->idle, jbr->idle);
+		if (presence->nickname)
+			serv_got_alias(js->gc, buddy_name, presence->nickname);
+	} else {
+		purple_prpl_got_user_status(account, buddy_name,
+				jabber_buddy_state_get_status_id(JABBER_BUDDY_STATE_UNAVAILABLE),
+				presence->status ? "message" : NULL, presence->status,
+				NULL);
+	}
+	g_free(buddy_name);
+
+	return TRUE;
+}
+
 void jabber_presence_parse(JabberStream *js, xmlnode *packet)
 {
-	const char *from;
 	const char *type;
-	char *status = NULL;
-	int priority = 0;
-	JabberID *jid;
-	JabberChat *chat;
-	JabberBuddy *jb;
-	JabberBuddyResource *jbr = NULL, *found_jbr = NULL;
-	PurpleConvChatBuddyFlags flags = PURPLE_CBFLAGS_NONE;
-	gboolean delayed = FALSE;
-	const gchar *stamp = NULL; /* from <delayed/> element */
-	PurpleAccount *account;
-	PurpleBuddy *b = NULL;
-	char *buddy_name;
-	JabberBuddyState state = JABBER_BUDDY_STATE_UNKNOWN;
-	xmlnode *y;
-	char *avatar_hash = NULL;
-	xmlnode *caps = NULL;
-	int idle = 0;
-	gchar *nickname = NULL;
-	gboolean signal_return;
+	JabberBuddyResource *jbr = NULL;
+	gboolean signal_return, ret;
+	JabberPresence presence;
+	xmlnode *child;
 
-	from = xmlnode_get_attrib(packet, "from");
+	memset(&presence, 0, sizeof(presence));
+	/* defaults */
+	presence.state = JABBER_BUDDY_STATE_UNKNOWN;
+	presence.sent = time(NULL);
+	/* interesting values */
+	presence.from = xmlnode_get_attrib(packet, "from");
+	presence.to   = xmlnode_get_attrib(packet, "to");
 	type = xmlnode_get_attrib(packet, "type");
-
-	jb = jabber_buddy_find(js, from, TRUE);
-	g_return_if_fail(jb != NULL);
+	presence.type = str_to_presence_type(type);
 
-	signal_return = GPOINTER_TO_INT(purple_signal_emit_return_1(purple_connection_get_prpl(js->gc),
-			"jabber-receiving-presence", js->gc, type, from, packet));
-	if (signal_return)
-		return;
+	presence.jb = jabber_buddy_find(js, presence.from, TRUE);
+	g_return_if_fail(presence.jb != NULL);
 
-	account = purple_connection_get_account(js->gc);
-
-	jid = jabber_id_new(from);
-	if (jid == NULL) {
+	presence.jid_from = jabber_id_new(presence.from);
+	if (presence.jid_from == NULL) {
 		purple_debug_error("jabber", "Ignoring presence with malformed 'from' "
-		                   "JID: %s\n", from);
+		                   "JID: %s\n", presence.from);
 		return;
 	}
 
-	if(jb->error_msg) {
-		g_free(jb->error_msg);
-		jb->error_msg = NULL;
+	signal_return = GPOINTER_TO_INT(purple_signal_emit_return_1(purple_connection_get_prpl(js->gc),
+			"jabber-receiving-presence", js->gc, type, presence.from, packet));
+	if (signal_return) {
+		goto out;
+	}
+
+	presence.chat = jabber_chat_find(js, presence.jid_from->node,
+	                                 presence.jid_from->domain);
+	if(presence.jb->error_msg) {
+		g_free(presence.jb->error_msg);
+		presence.jb->error_msg = NULL;
 	}
 
-	if (type == NULL) {
-		xmlnode *show;
-		char *show_data = NULL;
-
-		state = JABBER_BUDDY_STATE_ONLINE;
-
-		show = xmlnode_get_child(packet, "show");
-		if (show) {
-			show_data = xmlnode_get_data(show);
-			if (show_data) {
-				state = jabber_buddy_show_get_state(show_data);
-				g_free(show_data);
-			} else
-				purple_debug_warning("jabber", "<show/> present on presence, "
-				                     "but no contents!\n");
-		}
-	} else if (g_str_equal(type, "error")) {
+	if (presence.type == JABBER_PRESENCE_AVAILABLE) {
+		presence.state = JABBER_BUDDY_STATE_ONLINE;
+	} else if (presence.type == JABBER_PRESENCE_ERROR) {
+		/* TODO: Is this handled properly?  Should it be treated as per-jbr? */
 		char *msg = jabber_parse_error(js, packet, NULL);
-
-		state = JABBER_BUDDY_STATE_ERROR;
-		jb->error_msg = msg ? msg : g_strdup(_("Unknown Error in presence"));
-	} else if (g_str_equal(type, "subscribe")) {
+		presence.state = JABBER_BUDDY_STATE_ERROR;
+		presence.jb->error_msg = msg ? msg : g_strdup(_("Unknown Error in presence"));
+	} else if (presence.type == JABBER_PRESENCE_SUBSCRIBE) {
+		/* TODO: Move to handle_subscribe() (so nick is extracted by the
+		 * PresenceHandler */
 		struct _jabber_add_permit *jap = g_new0(struct _jabber_add_permit, 1);
 		gboolean onlist = FALSE;
+		PurpleAccount *account;
 		PurpleBuddy *buddy;
-		JabberBuddy *jb = NULL;
 		xmlnode *nick;
 
-		buddy = purple_find_buddy(account, from);
+		account = purple_connection_get_account(js->gc);
+		buddy = purple_find_buddy(account, presence.from);
 		nick = xmlnode_get_child_with_namespace(packet, "nick", "http://jabber.org/protocol/nick");
 		if (nick)
-			nickname = xmlnode_get_data(nick);
+			presence.nickname = xmlnode_get_data(nick);
 
 		if (buddy) {
-			jb = jabber_buddy_find(js, from, TRUE);
-			if ((jb->subscription & (JABBER_SUB_TO | JABBER_SUB_PENDING)))
+			if ((presence.jb->subscription & (JABBER_SUB_TO | JABBER_SUB_PENDING)))
 				onlist = TRUE;
 		}
 
 		jap->gc = js->gc;
-		jap->who = g_strdup(from);
+		jap->who = g_strdup(presence.from);
 		jap->js = js;
 
-		purple_account_request_authorization(account, from, NULL, nickname,
+		purple_account_request_authorization(account, presence.from, NULL, presence.nickname,
 				NULL, onlist, authorize_add_cb, deny_add_cb, jap);
 
-		g_free(nickname);
-		jabber_id_free(jid);
-		return;
-	} else if (g_str_equal(type, "subscribed")) {
-		/* we've been allowed to see their presence, but we don't care */
-		jabber_id_free(jid);
-		return;
-	} else if (g_str_equal(type, "unsubscribe")) {
+		goto out;
+	} else if (presence.type == JABBER_PRESENCE_SUBSCRIBED) {
+		/* This case (someone has approved our subscribe request) is handled
+		 * by the roster push the server sends along with this.
+		 */
+		goto out;
+	} else if (presence.type == JABBER_PRESENCE_UNSUBSCRIBE) {
 		/* XXX I'm not sure this is the right way to handle this, it
 		 * might be better to add "unsubscribe" to the presence status
 		 * if lower down, but I'm not sure. */
 		/* they are unsubscribing from our presence, we don't care */
 		/* Well, maybe just a little, we might want/need to start
 		 * acknowledging this (and the others) at some point. */
-		jabber_id_free(jid);
-		return;
-	} else if (g_str_equal(type, "probe")) {
+		goto out;
+	} else if (presence.type == JABBER_PRESENCE_PROBE) {
 		purple_debug_warning("jabber", "Ignoring presence probe\n");
-		jabber_id_free(jid);
-		return;
-	} else if (g_str_equal(type, "unavailable")) {
-		state = JABBER_BUDDY_STATE_UNAVAILABLE;
-	} else if (g_str_equal(type, "unsubscribed")) {
-		state = JABBER_BUDDY_STATE_UNKNOWN;
+		goto out;
+	} else if (presence.type == JABBER_PRESENCE_UNAVAILABLE) {
+		presence.state = JABBER_BUDDY_STATE_UNAVAILABLE;
+	} else if (presence.type == JABBER_PRESENCE_UNSUBSCRIBED) {
+		presence.state = JABBER_BUDDY_STATE_UNKNOWN;
 	} else {
 		purple_debug_warning("jabber", "Ignoring presence with invalid type "
 		                     "'%s'\n", type);
-		jabber_id_free(jid);
-		return;
+		goto out;
 	}
 
-
-	for(y = packet->child; y; y = y->next) {
-		const char *xmlns;
-		if(y->type != XMLNODE_TYPE_TAG)
-			continue;
-		xmlns = xmlnode_get_namespace(y);
-
-		if(!strcmp(y->name, "status")) {
-			g_free(status);
-			status = xmlnode_get_data(y);
-		} else if(!strcmp(y->name, "priority")) {
-			char *p = xmlnode_get_data(y);
-			if(p) {
-				priority = atoi(p);
-				g_free(p);
-			}
-		} else if(xmlns == NULL) {
-			/* The rest of the cases used to check xmlns individually. */
+	for (child = packet->child; child; child = child->next) {
+		char *key;
+		JabberPresenceHandler *pih;
+		if (child->type != XMLNODE_TYPE_TAG)
 			continue;
-		} else if(!strcmp(y->name, "delay") && !strcmp(xmlns, NS_DELAYED_DELIVERY)) {
-			/* XXX: compare the time.  jabber:x:delay can happen on presence packets that aren't really and truly delayed */
-			delayed = TRUE;
-			stamp = xmlnode_get_attrib(y, "stamp");
-		} else if(!strcmp(y->name, "c") && !strcmp(xmlns, "http://jabber.org/protocol/caps")) {
-			caps = y; /* store for later, when creating buddy resource */
-		} else if (g_str_equal(y->name, "nick") && g_str_equal(xmlns, "http://jabber.org/protocol/nick")) {
-			nickname = xmlnode_get_data(y);
-		} else if(!strcmp(y->name, "x")) {
-			if(!strcmp(xmlns, NS_DELAYED_DELIVERY_LEGACY)) {
-				/* XXX: compare the time.  jabber:x:delay can happen on presence packets that aren't really and truly delayed */
-				delayed = TRUE;
-				stamp = xmlnode_get_attrib(y, "stamp");
-			} else if(!strcmp(xmlns, "http://jabber.org/protocol/muc#user")) {
-			} else if(!strcmp(xmlns, "vcard-temp:x:update")) {
-				xmlnode *photo = xmlnode_get_child(y, "photo");
-				if(photo) {
-					g_free(avatar_hash);
-					avatar_hash = xmlnode_get_data(photo);
-				}
-			}
-		} else if (!strcmp(y->name, "query") &&
-			!strcmp(xmlnode_get_namespace(y), NS_LAST_ACTIVITY)) {
-			/* resource has specified idle */
-			const gchar *seconds = xmlnode_get_attrib(y, "seconds");
-			if (seconds) {
-				/* we may need to take "delayed" into account here */
-				idle = atoi(seconds);
-			}
-		}
-	}
-
-	if (idle && delayed && stamp) {
-		/* if we have a delayed presence, we need to add the delay to the idle
-		 value */
-		time_t offset = time(NULL) - purple_str_to_time(stamp, TRUE, NULL, NULL,
-			NULL);
-		purple_debug_info("jabber", "got delay %s yielding %ld s offset\n",
-			stamp, offset);
-		idle += offset;
+	
+		key = g_strdup_printf("%s %s", child->name, xmlnode_get_namespace(child));
+		pih = g_hash_table_lookup(presence_handlers, key);
+		g_free(key);
+		if (pih)
+			pih(js, &presence, child);
 	}
 
-	/* DEALING WITH CHATS */
-	if(jid->node && (chat = jabber_chat_find(js, jid->node, jid->domain))) {
-		static int i = 1;
-
-		if(state == JABBER_BUDDY_STATE_ERROR) {
-			char *title, *msg = jabber_parse_error(js, packet, NULL);
-
-			if (!chat->conv) {
-				title = g_strdup_printf(_("Error joining chat %s"), from);
-				purple_serv_got_join_chat_failed(js->gc, chat->components);
-			} else {
-				title = g_strdup_printf(_("Error in chat %s"), from);
-				if (g_hash_table_size(chat->members) == 0)
-					serv_got_chat_left(js->gc, chat->id);
-			}
-			purple_notify_error(js->gc, title, title, msg);
-			g_free(title);
-			g_free(msg);
-
-			if (g_hash_table_size(chat->members) == 0)
-				/* Only destroy the chat if the error happened while joining */
-				jabber_chat_destroy(chat);
-			jabber_id_free(jid);
-			g_free(status);
-			g_free(avatar_hash);
-			g_free(nickname);
-			return;
-		}
-
-		if (type == NULL) {
-			xmlnode *x;
-			const char *real_jid = NULL;
-			const char *affiliation = NULL;
-			const char *role = NULL;
-			gboolean is_our_resource = FALSE; /* Is the presence about us? */
-
-			/*
-			 * XEP-0045 mandates the presence to include a resource (which is
-			 * treated as the chat nick). Some non-compliant servers allow
-			 * joining without a nick.
-			 */
-			if (!jid->resource) {
-				jabber_id_free(jid);
-				g_free(avatar_hash);
-				g_free(nickname);
-				g_free(status);
-				return;
-			}
-
-			x = xmlnode_get_child_with_namespace(packet, "x",
-					"http://jabber.org/protocol/muc#user");
-			if (x) {
-				xmlnode *status_node;
-				xmlnode *item_node;
-
-				for (status_node = xmlnode_get_child(x, "status"); status_node;
-						status_node = xmlnode_get_next_twin(status_node)) {
-					const char *code = xmlnode_get_attrib(status_node, "code");
-					if (!code)
-						continue;
-
-					if (g_str_equal(code, "110")) {
-						is_our_resource = TRUE;
-					} else if (g_str_equal(code, "201")) {
-						if ((chat = jabber_chat_find(js, jid->node, jid->domain))) {
-							chat->config_dialog_type = PURPLE_REQUEST_ACTION;
-							chat->config_dialog_handle =
-								purple_request_action(js->gc,
-										_("Create New Room"),
-										_("Create New Room"),
-										_("You are creating a new room.  Would"
-											" you like to configure it, or"
-											" accept the default settings?"),
-										/* Default Action */ 1,
-										account, NULL, chat->conv,
-										chat, 2,
-										_("_Configure Room"), G_CALLBACK(jabber_chat_request_room_configure),
-										_("_Accept Defaults"), G_CALLBACK(jabber_chat_create_instant_room));
-						}
-					} else if (g_str_equal(code, "210")) {
-						/* server rewrote room-nick */
-						if((chat = jabber_chat_find(js, jid->node, jid->domain))) {
-							g_free(chat->handle);
-							chat->handle = g_strdup(jid->resource);
-						}
-					}
-				}
-
-				item_node = xmlnode_get_child(x, "item");
-				if (item_node) {
-					real_jid    = xmlnode_get_attrib(item_node, "jid");
-					affiliation = xmlnode_get_attrib(item_node, "affiliation");
-					role        = xmlnode_get_attrib(item_node, "role");
-
-					if (purple_strequal(affiliation, "owner"))
-						flags |= PURPLE_CBFLAGS_FOUNDER;
-					if (role) {
-						if (g_str_equal(role, "moderator"))
-							flags |= PURPLE_CBFLAGS_OP;
-						else if (g_str_equal(role, "participant"))
-							flags |= PURPLE_CBFLAGS_VOICE;
-					}
-				}
-			}
-
-			if(!chat->conv) {
-				char *room_jid = g_strdup_printf("%s@%s", jid->node, jid->domain);
-				chat->id = i++;
-				chat->muc = (x != NULL);
-				chat->conv = serv_got_joined_chat(js->gc, chat->id, room_jid);
-				purple_conv_chat_set_nick(PURPLE_CONV_CHAT(chat->conv), chat->handle);
-
-				jabber_chat_disco_traffic(chat);
-				g_free(room_jid);
-			}
-
-			jbr = jabber_buddy_track_resource(jb, jid->resource, priority, state,
-					status);
-			jbr->commands_fetched = TRUE;
-
-			jabber_chat_track_handle(chat, jid->resource, real_jid, affiliation, role);
-
-			if(!jabber_chat_find_buddy(chat->conv, jid->resource))
-				purple_conv_chat_add_user(PURPLE_CONV_CHAT(chat->conv), jid->resource,
-						real_jid, flags, !delayed);
-			else
-				purple_conv_chat_user_set_flags(PURPLE_CONV_CHAT(chat->conv), jid->resource,
-						flags);
-		} else if (g_str_equal(type, "unavailable")) {
-			xmlnode *x;
-			gboolean nick_change = FALSE;
-			gboolean kick = FALSE;
-			gboolean is_our_resource = FALSE; /* Is the presence about us? */
-
-			/* If the chat nick is invalid, we haven't yet joined, or we've
-			 * already left (it was probably us leaving after we closed the
-			 * chat), we don't care.
-			 */
-			if (!jid->resource || !chat->conv || chat->left) {
-				if (chat->left &&
-						jid->resource && chat->handle && !strcmp(jid->resource, chat->handle))
-					jabber_chat_destroy(chat);
-				jabber_id_free(jid);
-				g_free(status);
-				g_free(avatar_hash);
-				g_free(nickname);
-				return;
-			}
-
-			is_our_resource = (0 == g_utf8_collate(jid->resource, chat->handle));
-
-			jabber_buddy_remove_resource(jb, jid->resource);
-
-			x = xmlnode_get_child_with_namespace(packet, "x",
-					"http://jabber.org/protocol/muc#user");
-			if (chat->muc && x) {
-				const char *nick;
-				const char *item_jid = NULL;
-				const char *to;
-				xmlnode *stat;
-				xmlnode *item;
-
-				item = xmlnode_get_child(x, "item");
-				if (item)
-					item_jid = xmlnode_get_attrib(item, "jid");
-
-				for (stat = xmlnode_get_child(x, "status"); stat;
-						stat = xmlnode_get_next_twin(stat)) {
-					const char *code = xmlnode_get_attrib(stat, "code");
-
-					if (!code)
-						continue;
-
-					if (g_str_equal(code, "110")) {
-						is_our_resource = TRUE;
-					} else if(!strcmp(code, "301")) {
-						/* XXX: we got banned */
-					} else if(!strcmp(code, "303") && item &&
-							(nick = xmlnode_get_attrib(item, "nick"))) {
-						nick_change = TRUE;
-						if(!strcmp(jid->resource, chat->handle)) {
-							g_free(chat->handle);
-							chat->handle = g_strdup(nick);
-						}
-
-						/* TODO: This should probably be moved out of the loop */
-						purple_conv_chat_rename_user(PURPLE_CONV_CHAT(chat->conv), jid->resource, nick);
-						jabber_chat_remove_handle(chat, jid->resource);
-						continue;
-					} else if(!strcmp(code, "307")) {
-						/* Someone was kicked from the room */
-						xmlnode *reason = NULL, *actor = NULL;
-						const char *actor_name = NULL;
-						char *reason_text = NULL;
-						char *tmp;
-
-						kick = TRUE;
-
-						if (item) {
-							reason = xmlnode_get_child(item, "reason");
-							actor = xmlnode_get_child(item, "actor");
-
-							if (reason != NULL)
-								reason_text = xmlnode_get_data(reason);
-							if (actor != NULL)
-								actor_name = xmlnode_get_attrib(actor, "jid");
-						}
-
-						if (reason_text == NULL)
-							reason_text = g_strdup(_("No reason"));
-
-						if (is_our_resource) {
-							if (actor_name != NULL)
-								tmp = g_strdup_printf(_("You have been kicked by %s: (%s)"),
-										actor_name, reason_text);
-							else
-								tmp = g_strdup_printf(_("You have been kicked: (%s)"),
-										reason_text);
-						} else {
-							if (actor_name != NULL)
-								tmp = g_strdup_printf(_("Kicked by %s (%s)"),
-										actor_name, reason_text);
-							else
-								tmp = g_strdup_printf(_("Kicked (%s)"),
-										reason_text);
-						}
-
-						g_free(reason_text);
-						g_free(status);
-						status = tmp;
-					} else if(!strcmp(code, "321")) {
-						/* XXX: removed due to an affiliation change */
-					} else if(!strcmp(code, "322")) {
-						/* XXX: removed because room is now members-only */
-					} else if(!strcmp(code, "332")) {
-						/* XXX: removed due to system shutdown */
-					}
-				}
-
-				/*
-				 * Possibly another connected resource of our JID (see XEP-0045
-				 * v1.24 section 7.1.10) being disconnected. Should be
-				 * distinguished by the item_jid.
-				 * Also possibly works around bits of an Openfire bug. See
-				 * #8319.
-				 */
-				to = xmlnode_get_attrib(packet, "to");
-				if (is_our_resource && item_jid && !purple_strequal(to, item_jid)) {
-					/* TODO: When the above is a loop, this needs to still act
-					 * sanely for all cases (this code is a little fragile). */
-					if (!kick && !nick_change)
-						/* Presumably, kicks and nick changes also affect us. */
-						is_our_resource = FALSE;
-				}
-			}
-			if(!nick_change) {
-				if (is_our_resource) {
-					if (kick)
-						purple_conv_chat_write(PURPLE_CONV_CHAT(chat->conv), jid->resource,
-								status, PURPLE_MESSAGE_SYSTEM, time(NULL));
-
-					serv_got_chat_left(js->gc, chat->id);
-					jabber_chat_destroy(chat);
-				} else {
-					purple_conv_chat_remove_user(PURPLE_CONV_CHAT(chat->conv), jid->resource,
-							status);
-					jabber_chat_remove_handle(chat, jid->resource);
-				}
-			}
-		} else {
-			/* A type that isn't available or unavailable */
-			purple_debug_error("jabber", "MUC presence with bad type: %s\n",
-			                   type);
-
-			jabber_id_free(jid);
-			g_free(avatar_hash);
-			g_free(status);
-			g_free(nickname);
-			g_return_if_reached();
-		}
-		/* End of DEALING WITH CHATS...about 5000 lines ago */
-	} else {
-		/* DEALING WITH CONTACT (i.e. not a chat) */
-		PurpleConversation *conv;
-
-		buddy_name = g_strdup_printf("%s%s%s", jid->node ? jid->node : "",
-									 jid->node ? "@" : "", jid->domain);
-
-		/*
-		 * Unbind/unlock from sending messages to a specific resource on
-		 * presence changes.  This is locked to a specific resource when
-		 * receiving a message (in message.c).
-		 */
-		conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM,
-				buddy_name, account);
-		if (conv) {
-			purple_debug_info("jabber", "Changed conversation binding from %s to %s\n",
-					purple_conversation_get_name(conv), buddy_name);
-			purple_conversation_set_name(conv, buddy_name);
-		}
-
-		if((b = purple_find_buddy(account, buddy_name)) == NULL) {
-			if (jb != js->user_jb) {
-				purple_debug_warning("jabber", "Got presence for unknown buddy %s on account %s (%p)\n",
-									 buddy_name, purple_account_get_username(account), account);
-				jabber_id_free(jid);
-				g_free(avatar_hash);
-				g_free(buddy_name);
-				g_free(nickname);
-				g_free(status);
-				return;
-			} else {
-				/* this is a different resource of our own account. Resume even when this account isn't on our blist */
-			}
-		}
-
-		if(b && avatar_hash) {
-			const char *avatar_hash2 = purple_buddy_icons_get_checksum_for_user(b);
-			if(!avatar_hash2 || strcmp(avatar_hash, avatar_hash2)) {
-				JabberIq *iq;
-				xmlnode *vcard;
-
-				/* XXX this is a crappy way of trying to prevent
-				 * someone from spamming us with presence packets
-				 * and causing us to DoS ourselves...what we really
-				 * need is a queue system that can throttle itself,
-				 * but i'm too tired to write that right now */
-				if(!g_slist_find(js->pending_avatar_requests, jb)) {
-
-					js->pending_avatar_requests = g_slist_prepend(js->pending_avatar_requests, jb);
-
-					iq = jabber_iq_new(js, JABBER_IQ_GET);
-					xmlnode_set_attrib(iq->node, "to", buddy_name);
-					vcard = xmlnode_new_child(iq->node, "vCard");
-					xmlnode_set_namespace(vcard, "vcard-temp");
-
-					jabber_iq_set_callback(iq, jabber_vcard_parse_avatar, NULL);
-					jabber_iq_send(iq);
-				}
-			}
-		}
-
-		if(state == JABBER_BUDDY_STATE_ERROR ||
-				(type && (g_str_equal(type, "unavailable") ||
-				          g_str_equal(type, "unsubscribed")))) {
-			jabber_buddy_remove_resource(jb, jid->resource);
-		} else {
-			jbr = jabber_buddy_track_resource(jb, jid->resource, priority,
-					state, status);
-			if (idle) {
-				jbr->idle = time(NULL) - idle;
-			} else {
-				jbr->idle = 0;
-			}
-		}
-
-		if((found_jbr = jabber_buddy_find_resource(jb, NULL))) {
-			jabber_google_presence_incoming(js, buddy_name, found_jbr);
-			purple_prpl_got_user_status(account, buddy_name, jabber_buddy_state_get_status_id(found_jbr->state), "priority", found_jbr->priority, "message", found_jbr->status, NULL);
-			purple_prpl_got_user_idle(account, buddy_name, found_jbr->idle, found_jbr->idle);
-			if (nickname)
-				serv_got_alias(js->gc, buddy_name, nickname);
-		} else {
-			purple_prpl_got_user_status(account, buddy_name, "offline", status ? "message" : NULL, status, NULL);
-		}
-		g_free(buddy_name);
+	if (presence.delayed && presence.idle) {
+		/* Delayed and idle, so update idle time */
+		presence.idle = presence.idle + (time(NULL) - presence.sent);
 	}
 
-	if (caps && !type) {
+	/* TODO: Handle tracking jb(r) here? */
+
+	if (presence.chat)
+		ret = handle_presence_chat(js, &presence, packet);
+	else
+		ret = handle_presence_contact(js, &presence);
+	if (!ret)
+		goto out;
+
+	if (presence.caps && presence.type == JABBER_PRESENCE_AVAILABLE) {
 		/* handle Entity Capabilities (XEP-0115) */
-		const char *node = xmlnode_get_attrib(caps, "node");
-		const char *ver  = xmlnode_get_attrib(caps, "ver");
-		const char *hash = xmlnode_get_attrib(caps, "hash");
-		const char *ext  = xmlnode_get_attrib(caps, "ext");
+		const char *node = xmlnode_get_attrib(presence.caps, "node");
+		const char *ver  = xmlnode_get_attrib(presence.caps, "ver");
+		const char *hash = xmlnode_get_attrib(presence.caps, "hash");
+		const char *ext  = xmlnode_get_attrib(presence.caps, "ext");
 
 		/* v1.3 uses: node, ver, and optionally ext.
 		 * v1.5 uses: node, ver, and hash. */
 		if (node && *node && ver && *ver) {
 			gchar **exts = ext && *ext ? g_strsplit(ext, " ", -1) : NULL;
-			jbr = jabber_buddy_find_resource(jb, jid->resource);
+			jbr = jabber_buddy_find_resource(presence.jb, presence.jid_from->resource);
 
 			/* Look it up if we don't already have all this information */
 			if (!jbr || !jbr->caps.info ||
@@ -1084,9 +1039,9 @@
 					!jabber_caps_exts_known(jbr->caps.info, (gchar **)exts)) {
 				JabberPresenceCapabilities *userdata = g_new0(JabberPresenceCapabilities, 1);
 				userdata->js = js;
-				userdata->jb = jb;
-				userdata->from = g_strdup(from);
-				jabber_caps_get_info(js, from, node, ver, hash, exts,
+				userdata->jb = presence.jb;
+				userdata->from = g_strdup(presence.from);
+				jabber_caps_get_info(js, presence.from, node, ver, hash, exts,
 				    (jabber_caps_get_info_cb)jabber_presence_set_capabilities,
 				    userdata);
 			} else {
@@ -1096,10 +1051,12 @@
 		}
 	}
 
-	g_free(nickname);
-	g_free(status);
-	jabber_id_free(jid);
-	g_free(avatar_hash);
+out:
+	g_free(presence.nickname);
+	g_free(presence.status);
+	jabber_id_free(presence.jid_from);
+	g_free(presence.nickname);
+	g_free(presence.vcard_avatar_hash);
 }
 
 void jabber_presence_subscription_set(JabberStream *js, const char *who, const char *type)
@@ -1142,3 +1099,170 @@
 			*priority = purple_status_get_attr_int(status, "priority");
 	}
 }
+
+/* Incoming presence handlers */
+static void
+parse_priority(JabberStream *js, JabberPresence *presence, xmlnode *priority)
+{
+	char *p = xmlnode_get_data(priority);
+
+	if (presence->priority != 0)
+		purple_debug_warning("jabber", "presence stanza received with multiple "
+		                     "priority children!?\n");
+
+	if (p) {
+		presence->priority = atoi(p);
+		g_free(p);
+	} else
+		purple_debug_warning("jabber", "Empty <priority/> in presence!\n");
+}
+
+static void
+parse_show(JabberStream *js, JabberPresence *presence, xmlnode *show)
+{
+	char *cdata;
+
+	if (presence->type != JABBER_PRESENCE_AVAILABLE) {
+		purple_debug_warning("jabber", "<show/> present on presence, but "
+		                               "type is not default ('available')\n");
+		return;
+	}
+
+	cdata = xmlnode_get_data(show);
+	if (cdata) {
+		presence->state = jabber_buddy_show_get_state(cdata);
+		g_free(cdata);
+	} else
+		purple_debug_warning("jabber", "<show/> present on presence, but "
+		                               "no contents!\n");
+}
+
+static void
+parse_status(JabberStream *js, JabberPresence *presence, xmlnode *status)
+{
+	/* TODO: Check/track language attribute? */
+
+	g_free(presence->status);
+	presence->status = xmlnode_get_data(status);
+}
+
+static void
+parse_delay(JabberStream *js, JabberPresence *presence, xmlnode *delay)
+{
+	/* XXX: compare the time.  Can happen on presence stanzas that aren't
+	 * actually delayed.
+	 */
+	const char *stamp = xmlnode_get_attrib(delay, "stamp");
+	presence->delayed = TRUE;
+	presence->sent = purple_str_to_time(stamp, TRUE, NULL, NULL, NULL);
+}
+
+static void
+parse_idle(JabberStream *js, JabberPresence *presence, xmlnode *query)
+{
+	const gchar *seconds = xmlnode_get_attrib(query, "seconds");
+	if (seconds) {
+		presence->idle = atoi(seconds);
+		if (presence->idle < 0) {
+			purple_debug_warning("jabber", "Received bogus idle time %s\n", seconds);
+			presence->idle = 0;
+		}
+	}
+}
+
+static void
+parse_caps(JabberStream *js, JabberPresence *presence, xmlnode *c)
+{
+	/* TODO: Move the rest of the caps handling in here, after changing the
+	 * the "do we have details about this (node, ver) and exts" to not
+	 * require the jbr to be present (since that happens later).
+	 */
+	presence->caps = c;
+}
+
+static void
+parse_nickname(JabberStream *js, JabberPresence *presence, xmlnode *nick)
+{
+	g_free(presence->nickname);
+	presence->nickname = xmlnode_get_data(nick);
+}
+
+static void
+parse_vcard_avatar(JabberStream *js, JabberPresence *presence, xmlnode *x)
+{
+	xmlnode *photo = xmlnode_get_child(x, "photo");
+	if (photo) {
+		g_free(presence->vcard_avatar_hash);
+		presence->vcard_avatar_hash = xmlnode_get_data(photo);
+	}
+}
+
+static void
+parse_muc_user(JabberStream *js, JabberPresence *presence, xmlnode *x)
+{
+	xmlnode *status;
+
+	if (presence->chat == NULL) {
+		purple_debug_warning("jabber", "Ignoring MUC gloop on non-MUC presence\n");
+		return;
+	}
+
+	if (presence->chat->conv == NULL)
+		presence->chat->muc = TRUE;
+
+	for (status = xmlnode_get_child(x, "status"); status;
+			status = xmlnode_get_next_twin(status)) {
+		const char *code = xmlnode_get_attrib(status, "code");
+		int val;
+		if (!code)
+			continue;
+
+		val = atoi(code);
+		if (val == 0 || val < 0) {
+			purple_debug_warning("jabber", "Ignoring bogus status code '%s'\n",
+			                               code);
+			continue;
+		}
+
+		presence->chat_info.codes = g_slist_prepend(presence->chat_info.codes, GINT_TO_POINTER(val));
+	}
+
+	presence->chat_info.item = xmlnode_get_child(x, "item");
+}
+
+void jabber_presence_register_handler(const char *node, const char *xmlns,
+                                      JabberPresenceHandler *handler)
+{
+	/*
+	 * This is valid because nodes nor namespaces cannot have spaces in them
+	 * (see http://www.w3.org/TR/2006/REC-xml-20060816/ and
+	 * http://www.w3.org/TR/REC-xml-names/)
+	 */
+	char *key = g_strdup_printf("%s %s", node, xmlns);
+	g_hash_table_replace(presence_handlers, key, handler);
+}
+
+void jabber_presence_init(void)
+{
+	presence_handlers = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
+
+	/* Core RFC things */
+	jabber_presence_register_handler("priority", "jabber:client", parse_priority);
+	jabber_presence_register_handler("show", "jabber:client", parse_show);
+	jabber_presence_register_handler("status", "jabber:client", parse_status);
+
+	/* XEPs */
+	jabber_presence_register_handler("c", "http://jabber.org/protocol/caps", parse_caps);
+	jabber_presence_register_handler("delay", NS_DELAYED_DELIVERY, parse_delay);
+	jabber_presence_register_handler("nick", "http://jabber.org/protocol/nick", parse_nickname);
+	jabber_presence_register_handler("query", NS_LAST_ACTIVITY, parse_idle);
+	jabber_presence_register_handler("x", NS_DELAYED_DELIVERY_LEGACY, parse_delay);
+	jabber_presence_register_handler("x", "http://jabber.org/protocol/muc#user", parse_muc_user);
+	jabber_presence_register_handler("x", "vcard-temp:x:update", parse_vcard_avatar);
+}
+
+void jabber_presence_uninit(void)
+{
+	g_hash_table_destroy(presence_handlers);
+	presence_handlers = NULL;
+}
--- a/libpurple/protocols/jabber/presence.h	Wed Mar 10 05:10:43 2010 +0000
+++ b/libpurple/protocols/jabber/presence.h	Wed Mar 10 05:12:35 2010 +0000
@@ -24,10 +24,63 @@
 #ifndef PURPLE_JABBER_PRESENCE_H_
 #define PURPLE_JABBER_PRESENCE_H_
 
+typedef enum {
+	JABBER_PRESENCE_ERROR = -2,
+	JABBER_PRESENCE_PROBE = -1,
+	JABBER_PRESENCE_AVAILABLE,
+	JABBER_PRESENCE_UNAVAILABLE,
+	JABBER_PRESENCE_SUBSCRIBE,
+	JABBER_PRESENCE_SUBSCRIBED,
+	JABBER_PRESENCE_UNSUBSCRIBE,
+	JABBER_PRESENCE_UNSUBSCRIBED
+} JabberPresenceType;
+
+typedef struct _JabberPresenceChatInfo JabberPresenceChatInfo;
+typedef struct _JabberPresence JabberPresence;
+
 #include "buddy.h"
+#include "chat.h"
 #include "jabber.h"
+#include "jutil.h"
 #include "xmlnode.h"
 
+struct _JabberPresenceChatInfo {
+	GSList *codes;
+	xmlnode *item;
+};
+
+struct _JabberPresence {
+	JabberPresenceType type;
+	JabberID *jid_from;
+	const char *from;
+	const char *to;
+	const char *id;
+
+	JabberBuddy *jb;
+	JabberChat *chat;
+	JabberPresenceChatInfo chat_info;
+	xmlnode *caps; /* TODO: Temporary, see presence.c:parse_caps */
+
+	JabberBuddyState state;
+	gchar *status;
+	int priority;
+
+	char *vcard_avatar_hash;
+	char *nickname;
+
+	gboolean delayed;
+	time_t sent;
+	int idle;
+};
+
+typedef void (JabberPresenceHandler)(JabberStream *js, JabberPresence *presence,
+                                     xmlnode *child);
+void jabber_presence_register_handler(const char *node, const char *xmlns,
+                                      JabberPresenceHandler *handler);
+
+void jabber_presence_init(void);
+void jabber_presence_uninit(void);
+
 void jabber_set_status(PurpleAccount *account, PurpleStatus *status);
 
 /**