propagate from branch 'im.pidgin.pidgin' (head 2c70cc3e4c687ded823027750c5230825eb56825) cpw.malu.ft_thumbnails

Thu, 13 Aug 2009 17:15:06 +0000

author
Marcus Lundblad <malu@pidgin.im>
date
Thu, 13 Aug 2009 17:15:06 +0000
branch
cpw.malu.ft_thumbnails
changeset 28262
c23f8beecd55
parent 27991
0fb38e624722 (current diff)
parent 28183
2c70cc3e4c68 (diff)
child 28263
d525655fd0f1

propagate from branch 'im.pidgin.pidgin' (head 2c70cc3e4c687ded823027750c5230825eb56825)
to branch 'im.pidgin.cpw.malu.ft_thumbnails' (head 0fb38e6247229d447693b04c74068211d5d21682)

libpurple/ft.c file | annotate | diff | comparison | revisions
libpurple/ft.h file | annotate | diff | comparison | revisions
libpurple/protocols/jabber/si.c file | annotate | diff | comparison | revisions
libpurple/protocols/yahoo/libymsg.c file | annotate | diff | comparison | revisions
--- a/COPYRIGHT	Tue Jul 28 20:51:56 2009 +0000
+++ b/COPYRIGHT	Thu Aug 13 17:15:06 2009 +0000
@@ -1,8 +1,21 @@
 Pidgin, Finch, and libpurple
-Copyright (C) 1998-2009 by the following:
+
+This file is intended to be a comprehensive list of contributors to
+this project.  If you have contributed to this project then you deserve
+to be on this list.  Contact us (see: AUTHORS) and we'll add you.
 
-If you have contributed to this project then you deserve to be on this
-list.  Contact us (see: AUTHORS) and we'll add you.
+Many open source projects list contributor names at the top of each
+source file containing their contribution.  However, we've found
+that it is difficult to keep this list accurate, especially when old
+code is removed or existing code is moved to a different file.  So
+instead we chose to list a generic message at the top of each source
+file that points here.
+
+If concerns are raised as to the copyright holder of a particular
+piece of code, then that code should be traced through our version
+control system to see from where it came and who has modified it.
+
+Copyright (C) 1998-2009 by the following:
 
 Saleem Abdulrasool
 Dave Ahlswede
--- a/ChangeLog	Tue Jul 28 20:51:56 2009 +0000
+++ b/ChangeLog	Thu Aug 13 17:15:06 2009 +0000
@@ -43,6 +43,12 @@
 	  against the GNU IDN library.
 	* Install scalable versions of the main Pidgin icon, the protocol icons,
 	  the dialog icons, and the Buddy List emblems.
+	* Build properly on Hurd.  (Marc Dequènes)
+	* Various memory leaks fixed as reported by Josh Mueller.
+	* Properly handle an IRC buddy appearing in multiple groups.
+	* Escape HTML entities in usernames when written with the HTML logger.
+	* Do not display MySpace status changes as incoming IMs.  (Mark Doliner and
+	  Justin Williams)
 
 	AIM and ICQ:
 	* Preliminary support for a new authentication scheme called
@@ -113,6 +119,8 @@
 	* When the GNU IDN library (libidn) is available, it is used for
 	  normalization of Jabber IDs.  When unavailable, internal routines are
 	  used (as in previous versions).
+	* Topics that contain '<' followed by a non-whitespace character can now
+	  be set properly.
 
 	Yahoo!/Yahoo! JAPAN:
 	* P2P file transfers.  (Sulabh Mahajan)
@@ -173,6 +181,12 @@
 	  conversation window is now linked to the file.
 	* Fix a crash when closing a conversation tab that has unread messages
 	  when the Message Notification plugin is loaded.
+	* Fix a crash when closing the New Mail dialog if an account with new
+	  mail was previously disconnected while the dialog was open.
+	* Fix incorrect unread message counts for the new mail notifications.
+	* Do not lose unread messages with a hidden conversation window when
+	  new IM conversations are hidden and "Close IMs immediately when the tab
+	  is closed" is unset.
 
 	Finch:
 	* The hardware cursor is updated correctly. This will be useful
--- a/ChangeLog.API	Tue Jul 28 20:51:56 2009 +0000
+++ b/ChangeLog.API	Thu Aug 13 17:15:06 2009 +0000
@@ -18,6 +18,9 @@
 		* Three Blist UI ops used to overload libpurple's built-in saving
 		  of the buddy list to blist.xml. If a UI implements these, it probably
 		  wants to add the buddies itself and not call purple_blist_load.
+		* Three File Transfer UI ops used to overload libpurple's use of fread
+		  and fwrite for saving a file locally. These allow a UI to stream a
+		  file through a socket without buffering the file on the local disk.
 		* Jabber plugin signals (see jabber-signals.dox)
 		* purple_account_remove_setting
 		* purple_buddy_destroy
@@ -65,6 +68,8 @@
 		* purple_strequal
 		* purple_utf8_strip_unprintables
 		* purple_util_fetch_url_request_len_with_account
+		* purple_xfer_prpl_ready
+		* purple_xfer_ui_ready
 		* xmlnode_from_file
 		* xmlnode_get_parent
 		* xmlnode_set_attrib_full
@@ -95,6 +100,10 @@
 		* status is set before emitting signals in purple_xfer_set_status.
 		* Creating multiple distinct chats with the same name (i.e. "MSN Chat")
 		  is deprecated and will be removed in libpurple 3.0.0.
+		* purple_xfer_start now accepts -1 as the fd parameter if the protocol
+		  plugin will administer the transfer itself. 0 is still accepted for
+		  backward compatibility since older versions of libpurple will not
+		  accept -1.
 
 		Deprecated:
 		* buddy-added and buddy-removed blist signals
--- a/ChangeLog.win32	Tue Jul 28 20:51:56 2009 +0000
+++ b/ChangeLog.win32	Thu Aug 13 17:15:06 2009 +0000
@@ -1,4 +1,5 @@
 version 2.6.0 (??/??/2009):
+	* Added XMPP URI support.
 
 version 2.5.8 (06/27/2009):
 	* No changes
--- a/Makefile.mingw	Tue Jul 28 20:51:56 2009 +0000
+++ b/Makefile.mingw	Thu Aug 13 17:15:06 2009 +0000
@@ -133,3 +133,5 @@
 	rm -f ./VERSION
 
 include $(PIDGIN_COMMON_TARGETS)
+
+.PHONY: $(PIDGIN_REVISION_H) $(PIDGIN_REVISION_RAW_TXT)
--- a/doc/Makefile.am	Tue Jul 28 20:51:56 2009 +0000
+++ b/doc/Makefile.am	Thu Aug 13 17:15:06 2009 +0000
@@ -32,6 +32,7 @@
 	gtkimhtml-signals.dox \
 	gtkrc-2.0 \
 	imgstore-signals.dox \
+	jabber-signals.dox \
 	log-signals.dox \
 	notify-signals.dox \
 	pidgin.1.in \
--- a/doc/funniest_home_convos.txt	Tue Jul 28 20:51:56 2009 +0000
+++ b/doc/funniest_home_convos.txt	Thu Aug 13 17:15:06 2009 +0000
@@ -572,3 +572,42 @@
 15:46 <khc> well, there was a Grand Smiley Theme Database
 15:47 <SimGuy> the GSTD sounds like a bad acronym
 15:47 <khc> I realized after typing that
+
+--
+
+(01:51:38 AM) user entered the room.
+(01:52:46 AM) user: .addKeyActionListener(new KeyActionListener() onKeyPress() {if (event.geyKeyPresss().equals(Key.UP_ARROW) { inputbox.text = history.pop() }}}}}}});
+(01:52:51 AM) user: THERE, FOR **** SAKE
+(01:52:53 AM) user: its 2009
+(01:53:06 AM) user: oh wait. ctrl up works
+(01:53:07 AM) user: lol
+(01:53:11 AM) user: yey me
+(01:53:16 AM) user left the room.
+(01:55:31 AM) darkrain42: Wow.
+(01:58:15 AM) QuLogic: I think he failed to realize we'd have to re-write pidgin in java to do that
+(01:59:44 AM) khc: history.pop() is clearly wrong too
+
+--
+
+Some time later:
+(02:41:55 AM) user entered the room.
+(02:42:24 AM) user: didn't I read some idiot post, about 2 years ago, before pidgin was renamed / forked, over one dev refusing to make minimize on close?
+(02:43:12 AM) QuLogic: I see you've learned to at least ask a question before jumping to random conclusions
+(02:44:01 AM) user: QuLogic: :-))))))))))))))))))))))
+(02:44:12 AM) user: hey, I submitted a code patch!
+(02:44:36 AM) user: now, anyway, what happened? why did I get the buddy list (empty) stealing focus, and why did it exit on close?
+(02:44:40 AM) QuLogic: it's not really a patch if it's in the wrong language
+(02:44:42 AM) user: I've had this argument before, in 2006
+(02:44:55 AM) user: QuLogic: simple, rewrite the rest ;-)
+(02:44:58 AM) khc: pidgin never steals focus
+(02:45:10 AM) khc: if it exit on close, it's because you didn't turn on the systray icon
+(02:47:17 AM) user: khc - and that isn't default... why? anyway. I recall something on the matter, and I think this was the project (pre-fork?) or is this the unforked, renamed? I forget.
+(02:47:42 AM) user: Whoever it was arguing about it (and font sizes I believe) was an idiot... not one of you I suppose, just making idle chit chat.
+(02:47:43 AM) user: thanks
+(02:47:48 AM) darkrain42: It is on by default. Some distros change that.
+(02:48:00 AM) darkrain42: And I don't even know what you're arguing about at this point.
+(02:48:11 AM) user: ... /leave - That command doesn't work on this protocol... /leave #pidgin ...That comm..... :-(((
+(02:48:18 AM) user: darkrain42: now arguing, just remembering something
+(02:48:27 AM) user left the room.
+(02:49:04 AM) darkrain42: Wow. (again)
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/jabber-signals.dox	Thu Aug 13 17:15:06 2009 +0000
@@ -0,0 +1,125 @@
+/** @page jabber-signals Jabber Signals
+
+ @signals
+  @signal jabber-receiving-iq
+  @signal jabber-receiving-message
+  @signal jabber-receiving-presence
+  @signal jabber-watched-iq
+  @signal jabber-register-namespace-watcher
+  @signal jabber-unregister-namespace-watcher
+  @signal jabber-sending-xmlnode
+ @endsignals
+
+ <hr>
+
+ @signaldef jabber-receiving-iq
+  @signalproto
+gboolean (*iq_received)(PurpleConnection *gc, const char *type, const char *id,
+                     const char *from, xmlnode *iq);
+  @endsignalproto
+  @signaldesc
+   Emitted when an XMPP IQ stanza is received. Allows a plugin to process IQ
+   stanzas.
+  @param gc        The connection on which the stanza is received
+  @param type      The IQ type ('get', 'set', 'result', or 'error')
+  @param id        The ID attribute from the stanza. MUST NOT be NULL.
+  @param from      The originator of the stanza. MAY BE NULL if the stanza
+                   originated from the user's server.
+  @param iq        The full stanza received.
+  @return TRUE if the plugin processed this stanza and *nobody else* should
+          process it. FALSE otherwise.
+ @endsignaldef
+
+ @signaldef jabber-receiving-message
+  @signalproto
+gboolean (*message_received)(PurpleConnection *gc, const char *type,
+                              const char *id, const char *from, const char *to,
+                              xmlnode *message);
+  @endsignalproto
+  @signaldesc
+   Emitted when an XMPP message stanza is received. Allows a plugin to
+   process message stanzas.
+  @param gc        The connection on which the stanza is received
+  @param type      The message type (see rfc3921 or rfc3921bis)
+  @param id        The ID attribute from the stanza. MAY BE NULL.
+  @param from      The originator of the stanza. MAY BE NULL if the stanza
+                   originated from the user's server.
+  @param to        The destination of the stanza. This is probably either the
+                   full JID of the receiver or the receiver's bare JID.
+  @param message   The full stanza received.
+  @return TRUE if the plugin processed this stanza and *nobody else* should
+          process it. FALSE otherwise.
+ @endsignaldef
+
+ @signaldef jabber-receiving-presence
+  @signalproto
+gboolean (*presence_received)(PurpleConnection *gc, const char *type,
+                               const char *from, xmlnode *presence);
+  @endsignalproto
+  @signaldesc
+   Emitted when an XMPP presence stanza is received. Allows a plugin to process
+   presence stanzas.
+  @param gc        The connection on which the stanza is received
+  @param type      The presence type (see rfc3921 or rfc3921bis). NULL indicates
+                   this is an "available" (i.e. online) presence.
+  @param from      The originator of the stanza. MAY BE NULL if the stanza
+                   originated from the user's server.
+  @param presence  The full stanza received.
+  @return TRUE if the plugin processed this stanza and *nobody else* should
+          process it. FALSE otherwise.
+ @endsignaldef
+
+ @signaldef jabber-watched-iq
+  @signalproto
+gboolean (*watched_iq)(PurpleConnection *pc, const char *type, const char *id,
+                       const char *from, xmlnode *child);
+  @endsignalproto
+  @signaldesc
+   Emitted when an IQ with a watched (child, namespace) pair is received.  See
+   jabber-register-namespace-watcher and jabber-unregister-namespace-watcher.
+  @param gc        The connection on which the stanza is received
+  @param type      The IQ type ('get', 'set', 'result', or 'error')
+  @param id        The ID attribute from the stanza. MUST NOT be NULL.
+  @param from      The originator of the stanza. MAY BE NULL if the stanza
+                   originated from the user's server.
+  @param child     The child node with namespace.
+  @return TRUE if the plugin processed this stanza and *nobody else* should
+          process it. FALSE otherwise.
+ @endsignaldef
+
+ @signaldef jabber-sending-xmlnode
+  @signalproto
+void (sending_xmlnode)(PurpleConnection *gc, xmlnode **stanza);
+  @endsignalproto
+  @signaldesc
+   Emit this signal (@c purple_signal_emit) to send a stanza. It is preferred
+   to use this instead of prpl_info->send_raw.
+   @param gc      The connectoin on which to send the stanza.
+   @param stanza  The stanza to send. If stanza is not NULL after being sent,
+                  the emitter should free it.
+ @endsignaldef
+
+ @signaldef jabber-register-namespace-watcher
+  @signalproto
+void (register_namespace_watcher)(const char *node, const char *namespace);
+  @endsignalproto
+  @signaldesc
+   Emit this signal to register your desire to have specific IQ stanzas to be
+   emitted via the jabber-watched-iq signal when received.
+  @param node      The IQ child name to longer watch.
+  @param namespace The IQ child namespace to longer watch.
+ @endsignaldef
+
+ @signaldef jabber-unregister-namespace-watcher
+  @signalproto
+void (unregister_namespace_watcher)(const char *node, const char *namespace);
+  @endsignalproto
+  @signaldesc
+   Emit this signal to unregister your desire to have specific IQ stanzas to be
+   emitted via the jabber-watched-iq signal when received.
+  @param node      The IQ child name to no longer watch.
+  @param namespace The IQ child namespace to no longer watch.
+ @endsignaldef
+
+*/
+// vim: syntax=c.doxygen tw=75 et
--- a/finch/gntaccount.c	Tue Jul 28 20:51:56 2009 +0000
+++ b/finch/gntaccount.c	Thu Aug 13 17:15:06 2009 +0000
@@ -489,6 +489,7 @@
 	GntWidget *combo, *button, *entry;
 	GList *list, *iter;
 	AccountEditDialog *dialog;
+	PurplePlugin *plugin;
 
 	if (account)
 	{
@@ -532,9 +533,10 @@
 				((PurplePlugin*)iter->data)->info->name);
 	}
 
-	if (account)
-		gnt_combo_box_set_selected(GNT_COMBO_BOX(combo),
-				purple_plugins_find_with_id(purple_account_get_protocol_id(account)));
+	plugin = purple_plugins_find_with_id(purple_account_get_protocol_id(account));
+
+	if (account && plugin)
+		gnt_combo_box_set_selected(GNT_COMBO_BOX(combo), plugin);
 	else
 		gnt_combo_box_set_selected(GNT_COMBO_BOX(combo), list->data);
 
--- a/finch/gntblist.c	Tue Jul 28 20:51:56 2009 +0000
+++ b/finch/gntblist.c	Thu Aug 13 17:15:06 2009 +0000
@@ -589,6 +589,16 @@
 		ggblist->manager = &default_manager;
 }
 
+static void destroy_list(PurpleBuddyList *list)
+{
+	if (ggblist == NULL)
+		return;
+
+	gnt_widget_destroy(ggblist->window);
+	g_free(ggblist);
+	ggblist = NULL;
+}
+
 static gboolean
 remove_new_empty_group(gpointer data)
 {
@@ -849,7 +859,7 @@
 	blist_show,
 	node_update,
 	node_remove,
-	NULL,
+	destroy_list,
 	NULL,
 	finch_request_add_buddy,
 	finch_request_add_chat,
@@ -3184,12 +3194,6 @@
 
 void finch_blist_uninit()
 {
-	if (ggblist == NULL)
-		return;
-
-	gnt_widget_destroy(ggblist->window);
-	g_free(ggblist);
-	ggblist = NULL;
 }
 
 gboolean finch_blist_get_position(int *x, int *y)
--- a/finch/gntmedia.c	Tue Jul 28 20:51:56 2009 +0000
+++ b/finch/gntmedia.c	Thu Aug 13 17:15:06 2009 +0000
@@ -417,11 +417,7 @@
 create_default_audio_src(PurpleMedia *media,
 		const gchar *session_id, const gchar *participant)
 {
-	GstElement *bin, *src, *volume;
-	GstPad *pad, *ghost;
-	double input_volume = purple_prefs_get_int(
-			"/finch/media/audio/volume/input")/10.0;
-
+	GstElement *src;
 	src = gst_element_factory_make("gconfaudiosrc", NULL);
 	if (src == NULL)
 		src = gst_element_factory_make("autoaudiosrc", NULL);
@@ -436,28 +432,15 @@
 				"element for the default audio source.\n");
 		return NULL;
 	}
-
-	bin = gst_bin_new("finchdefaultaudiosrc");
-	volume = gst_element_factory_make("volume", "purpleaudioinputvolume");
-	g_object_set(volume, "volume", input_volume, NULL);
-	gst_bin_add_many(GST_BIN(bin), src, volume, NULL);
-	gst_element_link(src, volume);
-	pad = gst_element_get_pad(volume, "src");
-	ghost = gst_ghost_pad_new("ghostsrc", pad);
-	gst_element_add_pad(bin, ghost);
-
-	return bin;
+	gst_element_set_name(src, "finchdefaultaudiosrc");
+	return src;
 }
 
 static GstElement *
 create_default_audio_sink(PurpleMedia *media,
 		const gchar *session_id, const gchar *participant)
 {
-	GstElement *bin, *sink, *volume, *queue;
-	GstPad *pad, *ghost;
-	double output_volume = purple_prefs_get_int(
-			"/finch/media/audio/volume/output")/10.0;
-
+	GstElement *sink;
 	sink = gst_element_factory_make("gconfaudiosink", NULL);
 	if (sink == NULL)
 		sink = gst_element_factory_make("autoaudiosink",NULL);
@@ -466,19 +449,7 @@
 				"element for the default audio sink.\n");
 		return NULL;
 	}
-
-	bin = gst_bin_new("finchdefaultaudiosink");
-	volume = gst_element_factory_make("volume", "purpleaudiooutputvolume");
-	g_object_set(volume, "volume", output_volume, NULL);
-	queue = gst_element_factory_make("queue", NULL);
-	gst_bin_add_many(GST_BIN(bin), sink, volume, queue, NULL);
-	gst_element_link(volume, sink);
-	gst_element_link(queue, volume);
-	pad = gst_element_get_pad(queue, "sink");
-	ghost = gst_ghost_pad_new("ghostsink", pad);
-	gst_element_add_pad(bin, ghost);
-
-	return bin;
+	return sink;
 }
 #endif  /* USE_VV */
 
@@ -516,12 +487,6 @@
 	purple_debug_info("gntmedia", "Registering media element types\n");
 	purple_media_manager_set_active_element(manager, default_audio_src);
 	purple_media_manager_set_active_element(manager, default_audio_sink);
-
-	purple_prefs_add_none("/finch/media");
-	purple_prefs_add_none("/finch/media/audio");
-	purple_prefs_add_none("/finch/media/audio/volume");
-	purple_prefs_add_int("/finch/media/audio/volume/input", 10);
-	purple_prefs_add_int("/finch/media/audio/volume/output", 10);
 #endif
 }
 
--- a/libpurple/certificate.c	Tue Jul 28 20:51:56 2009 +0000
+++ b/libpurple/certificate.c	Thu Aug 13 17:15:06 2009 +0000
@@ -386,7 +386,6 @@
 
 	scheme = crt->scheme;
 
-	/* TODO: Instead of failing, maybe use get_subject_name and strcmp? */
 	g_return_val_if_fail(scheme->check_subject_name, FALSE);
 
 	return (scheme->check_subject_name)(crt, name);
@@ -1318,20 +1317,113 @@
 	g_byte_array_free(cached_fpr, TRUE);
 }
 
+/*
+ * This is called from two points in x509_tls_cached_unknown_peer below
+ * once we've verified the signature chain is valid. Now we need to verify
+ * the subject name of the certificate.
+ */
+static void
+x509_tls_cached_check_subject_name(PurpleCertificateVerificationRequest *vrq,
+                                   gboolean had_ca_pool)
+{
+	PurpleCertificatePool *tls_peers;
+	PurpleCertificate *peer_crt;
+	GList *chain = vrq->cert_chain;
+
+	peer_crt = (PurpleCertificate *) chain->data;
+
+	/* Last, check that the hostname matches */
+	if ( ! purple_certificate_check_subject_name(peer_crt,
+						     vrq->subject_name) ) {
+		gchar *sn = purple_certificate_get_subject_name(peer_crt);
+
+		purple_debug_error("certificate/x509/tls_cached",
+				  "Name mismatch: Certificate given for %s "
+				  "has a name of %s\n",
+				  vrq->subject_name, sn);
+
+		if (had_ca_pool) {
+			/* Prompt the user to authenticate the certificate */
+			/* TODO: Provide the user with more guidance about why he is
+			   being prompted */
+			/* vrq will be completed by user_auth */
+			gchar *msg;
+			msg = g_strdup_printf(_("The certificate presented by \"%s\" "
+						"claims to be from \"%s\" instead.  "
+						"This could mean that you are not "
+						"connecting to the service you "
+						"believe you are."),
+					      vrq->subject_name, sn);
+
+			x509_tls_cached_user_auth(vrq, msg);
+			g_free(msg);
+		} else {
+			/* Had no CA pool, so couldn't verify the chain *and*
+			 * the subject name isn't valid.
+			 * I think this is bad enough to warrant a fatal error. It's
+			 * not likely anyway...
+			 */
+			purple_notify_error(NULL, /* TODO: Probably wrong. */
+						_("SSL Certificate Error"),
+						_("Invalid certificate chain"),
+						_("You have no database of root certificates, so "
+						"this certificate cannot be validated."));
+		}
+
+		g_free(sn);
+		return;
+	} /* if (name mismatch) */
+
+	if (!had_ca_pool) {
+		/* The subject name is correct, but we weren't able to verify the
+		 * chain because there was no pool of root CAs found. Prompt the user
+		 * to validate it.
+		 */
+
+		/* vrq will be completed by user_auth */
+		x509_tls_cached_user_auth(vrq,_("You have no database of root "
+						"certificates, so this "
+						"certificate cannot be "
+						"validated."));
+		return;
+	}
+
+	/* If we reach this point, the certificate is good. */
+	/* Look up the local cache and store it there for future use */
+	tls_peers = purple_certificate_find_pool(x509_tls_cached.scheme_name,
+						 "tls_peers");
+
+	if (tls_peers) {
+		if (!purple_certificate_pool_store(tls_peers,vrq->subject_name,
+						   peer_crt) ) {
+			purple_debug_error("certificate/x509/tls_cached",
+					   "FAILED to cache peer certificate\n");
+		}
+	} else {
+		purple_debug_error("certificate/x509/tls_cached",
+				   "Unable to locate tls_peers certificate "
+				   "cache.\n");
+	}
+
+	/* Whew! Done! */
+	purple_certificate_verify_complete(vrq, PURPLE_CERTIFICATE_VALID);
+
+}
+
 /* For when we've never communicated with this party before */
 /* TODO: Need ways to specify possibly multiple problems with a cert, or at
-   least  reprioritize them. For example, maybe the signature ought to be
-   checked BEFORE the hostname checking?
-   Stu thinks we should check the signature before the name, so we do now.
-   The above TODO still stands. */
+   least  reprioritize them.
+ */
 static void
 x509_tls_cached_unknown_peer(PurpleCertificateVerificationRequest *vrq)
 {
-	PurpleCertificatePool *ca, *tls_peers;
+	PurpleCertificatePool *ca;
 	PurpleCertificate *peer_crt;
+	PurpleCertificate *ca_crt, *end_crt;
 	PurpleCertificate *failing_crt;
 	GList *chain = vrq->cert_chain;
-	gboolean chain_validated = FALSE;
+	GByteArray *last_fpr, *ca_fpr;
+	gchar *ca_id;
 
 	peer_crt = (PurpleCertificate *) chain->data;
 
@@ -1361,10 +1453,10 @@
 	ca = purple_certificate_find_pool(x509_tls_cached.scheme_name, "ca");
 
 	/* Next, check that the certificate chain is valid */
-	if (purple_certificate_check_signature_chain_with_failing(chain,
-	                                                          &failing_crt))
-		chain_validated = TRUE;
-	else {
+	if (!purple_certificate_check_signature_chain_with_failing(chain,
+				&failing_crt))
+	{
+		gboolean chain_validated = FALSE;
 		/*
 		 * Check if the failing certificate is in the CA store. If it is, then
 		 * consider this fully validated. This works around issues with some
@@ -1399,7 +1491,9 @@
 		 * If we get here, either the cert matched the stuff right above
 		 * or it didn't, in which case we give up and complain to the user.
 		 */
-		if (!chain_validated) {
+		if (chain_validated) {
+			x509_tls_cached_check_subject_name(vrq, TRUE);
+		} else {
 			/* TODO: Tell the user where the chain broke? */
 			/* TODO: This error will hopelessly confuse any
 			   non-elite user. */
@@ -1421,156 +1515,100 @@
 			/* Okay, we're done here */
 			purple_certificate_verify_complete(vrq,
 							   PURPLE_CERTIFICATE_INVALID);
-			return;
 		}
+
+		return;
 	} /* if (signature chain not good) */
 
 	/* If, for whatever reason, there is no Certificate Authority pool
-	   loaded, we will simply present it to the user for checking. */
+	   loaded, we'll verify the subject name and then warn about thsi. */
 	if ( !ca ) {
 		purple_debug_error("certificate/x509/tls_cached",
 				   "No X.509 Certificate Authority pool "
 				   "could be found!\n");
 
+		x509_tls_cached_check_subject_name(vrq, FALSE);
+		return;
+	}
+
+	end_crt = g_list_last(chain)->data;
+
+	/* Attempt to look up the last certificate's issuer */
+	ca_id = purple_certificate_get_issuer_unique_id(end_crt);
+	purple_debug_info("certificate/x509/tls_cached",
+			  "Checking for a CA with DN=%s\n",
+			  ca_id);
+	ca_crt = purple_certificate_pool_retrieve(ca, ca_id);
+	if ( NULL == ca_crt ) {
+		purple_debug_warning("certificate/x509/tls_cached",
+				  "Certificate Authority with DN='%s' not "
+				  "found. I'll prompt the user, I guess.\n",
+				  ca_id);
+		g_free(ca_id);
 		/* vrq will be completed by user_auth */
-		x509_tls_cached_user_auth(vrq,_("You have no database of root "
-						"certificates, so this "
-						"certificate cannot be "
-						"validated."));
+		x509_tls_cached_user_auth(vrq,_("The root certificate this "
+						"one claims to be issued by "
+						"is unknown to Pidgin."));
 		return;
 	}
 
-	if (!chain_validated) {
-		GByteArray *last_fpr, *ca_fpr;
-		PurpleCertificate *ca_crt, *end_crt;
-		gchar *ca_id;
-
-		end_crt = g_list_last(chain)->data;
+	g_free(ca_id);
 
-		/* Attempt to look up the last certificate's issuer */
-		ca_id = purple_certificate_get_issuer_unique_id(end_crt);
-		purple_debug_info("certificate/x509/tls_cached",
-				  "Checking for a CA with DN=%s\n",
-				  ca_id);
-		ca_crt = purple_certificate_pool_retrieve(ca, ca_id);
-		if ( NULL == ca_crt ) {
-			purple_debug_warning("certificate/x509/tls_cached",
-					  "Certificate Authority with DN='%s' not "
-					  "found. I'll prompt the user, I guess.\n",
-					  ca_id);
-			g_free(ca_id);
-			/* vrq will be completed by user_auth */
-			x509_tls_cached_user_auth(vrq,_("The root certificate this "
-							"one claims to be issued by "
-							"is unknown to Pidgin."));
-			return;
-		}
-
-		g_free(ca_id);
+	/*
+	 * Check the fingerprints; if they match, then this certificate *is* one
+	 * of the designated "trusted roots", and we don't need to verify the
+	 * signature. This is good because some of the older roots are self-signed
+	 * with bad hash algorithms that we don't want to allow in any other
+	 * circumstances (one of Verisign's root CAs is self-signed with MD2).
+	 *
+	 * If the fingerprints don't match, we'll fall back to checking the
+	 * signature.
+	 *
+	 * GnuTLS doesn't seem to include the final root in the verification
+	 * list, so this check will never succeed.  NSS *does* include it in
+	 * the list, so here we are.
+	 */
+	last_fpr = purple_certificate_get_fingerprint_sha1(end_crt);
+	ca_fpr   = purple_certificate_get_fingerprint_sha1(ca_crt);
 
-		/*
-		 * Check the fingerprints; if they match, then this certificate *is* one
-		 * of the designated "trusted roots", and we don't need to verify the
-		 * signature. This is good because some of the older roots are self-signed
-		 * with bad hash algorithms that we don't want to allow in any other
-		 * circumstances (one of Verisign's root CAs is self-signed with MD2).
-		 *
-		 * If the fingerprints don't match, we'll fall back to checking the
-		 * signature.
-		 *
-		 * GnuTLS doesn't seem to include the final root in the verification
-		 * list, so this check will never succeed.  NSS *does* include it in
-		 * the list, so here we are.
-		 */
-		last_fpr = purple_certificate_get_fingerprint_sha1(end_crt);
-		ca_fpr   = purple_certificate_get_fingerprint_sha1(ca_crt);
+	if ( !byte_arrays_equal(last_fpr, ca_fpr) &&
+			!purple_certificate_signed_by(end_crt, ca_crt) )
+	{
+		/* TODO: If signed_by ever returns a reason, maybe mention
+		   that, too. */
+		/* TODO: Also mention the CA involved. While I could do this
+		   now, a full DN is a little much with which to assault the
+		   user's poor, leaky eyes. */
+		/* TODO: This error message makes my eyes cross, and I wrote it */
+		gchar * secondary =
+			g_strdup_printf(_("The certificate chain presented by "
+					  "%s does not have a valid digital "
+					  "signature from the Certificate "
+					  "Authority from which it claims to "
+					  "have a signature."),
+					vrq->subject_name);
 
-		if ( !byte_arrays_equal(last_fpr, ca_fpr) &&
-				!purple_certificate_signed_by(end_crt, ca_crt) )
-		{
-			/* TODO: If signed_by ever returns a reason, maybe mention
-			   that, too. */
-			/* TODO: Also mention the CA involved. While I could do this
-			   now, a full DN is a little much with which to assault the
-			   user's poor, leaky eyes. */
-			/* TODO: This error message makes my eyes cross, and I wrote it */
-			gchar * secondary =
-				g_strdup_printf(_("The certificate chain presented by "
-						  "%s does not have a valid digital "
-						  "signature from the Certificate "
-						  "Authority from which it claims to "
-						  "have a signature."),
-						vrq->subject_name);
+		purple_notify_error(NULL, /* TODO: Probably wrong */
+				    _("SSL Certificate Error"),
+				    _("Invalid certificate authority"
+				      " signature"),
+				    secondary);
+		g_free(secondary);
 
-			purple_notify_error(NULL, /* TODO: Probably wrong */
-					    _("SSL Certificate Error"),
-					    _("Invalid certificate authority"
-					      " signature"),
-					    secondary);
-			g_free(secondary);
+		/* Signal "bad cert" */
+		purple_certificate_verify_complete(vrq,
+						   PURPLE_CERTIFICATE_INVALID);
 
-			/* Signal "bad cert" */
-			purple_certificate_verify_complete(vrq,
-							   PURPLE_CERTIFICATE_INVALID);
-
-			purple_certificate_destroy(ca_crt);
-			g_byte_array_free(ca_fpr, TRUE);
-			g_byte_array_free(last_fpr, TRUE);
-			return;
-		} /* if (CA signature not good) */
-
+		purple_certificate_destroy(ca_crt);
 		g_byte_array_free(ca_fpr, TRUE);
 		g_byte_array_free(last_fpr, TRUE);
-	}
-
-	/* Last, check that the hostname matches */
-	if ( ! purple_certificate_check_subject_name(peer_crt,
-						     vrq->subject_name) ) {
-		gchar *sn = purple_certificate_get_subject_name(peer_crt);
-		gchar *msg;
-
-		purple_debug_error("certificate/x509/tls_cached",
-				  "Name mismatch: Certificate given for %s "
-				  "has a name of %s\n",
-				  vrq->subject_name, sn);
-
-		/* Prompt the user to authenticate the certificate */
-		/* TODO: Provide the user with more guidance about why he is
-		   being prompted */
-		/* vrq will be completed by user_auth */
-		msg = g_strdup_printf(_("The certificate presented by \"%s\" "
-					"claims to be from \"%s\" instead.  "
-					"This could mean that you are not "
-					"connecting to the service you "
-					"believe you are."),
-				      vrq->subject_name, sn);
+		return;
+	} /* if (CA signature not good) */
 
-		x509_tls_cached_user_auth(vrq,msg);
-
-		g_free(sn);
-		g_free(msg);
-		return;
-	} /* if (name mismatch) */
-
-	/* If we reach this point, the certificate is good. */
-	/* Look up the local cache and store it there for future use */
-	tls_peers = purple_certificate_find_pool(x509_tls_cached.scheme_name,
-						 "tls_peers");
+	g_byte_array_free(ca_fpr, TRUE);
+	g_byte_array_free(last_fpr, TRUE);
 
-	if (tls_peers) {
-		if (!purple_certificate_pool_store(tls_peers,vrq->subject_name,
-						   peer_crt) ) {
-			purple_debug_error("certificate/x509/tls_cached",
-					   "FAILED to cache peer certificate\n");
-		}
-	} else {
-		purple_debug_error("certificate/x509/tls_cached",
-				   "Unable to locate tls_peers certificate "
-				   "cache.\n");
-	}
-
-	/* Whew! Done! */
-	purple_certificate_verify_complete(vrq, PURPLE_CERTIFICATE_VALID);
+	x509_tls_cached_check_subject_name(vrq, TRUE);
 }
 
 static void
--- a/libpurple/cipher.c	Tue Jul 28 20:51:56 2009 +0000
+++ b/libpurple/cipher.c	Thu Aug 13 17:15:06 2009 +0000
@@ -2727,8 +2727,6 @@
 
 		cipher = PURPLE_CIPHER(l->data);
 		purple_ciphers_unregister_cipher(cipher);
-
-		ciphers = g_list_remove(ciphers, cipher);
 	}
 
 	g_list_free(ciphers);
--- a/libpurple/connection.c	Tue Jul 28 20:51:56 2009 +0000
+++ b/libpurple/connection.c	Thu Aug 13 17:15:06 2009 +0000
@@ -578,6 +578,9 @@
 
 	gc->wants_to_die = purple_connection_error_is_fatal (reason);
 
+	purple_debug_info("connection", "Connection error on %p (reason: %u description: %s)\n",
+	                  gc, reason, description);
+
 	ops = purple_connections_get_ui_ops();
 
 	if (ops != NULL)
--- a/libpurple/desktopitem.c	Tue Jul 28 20:51:56 2009 +0000
+++ b/libpurple/desktopitem.c	Thu Aug 13 17:15:06 2009 +0000
@@ -831,11 +831,10 @@
 static char *
 try_english_key (PurpleDesktopItem *item, const char *key)
 {
-	char *str;
+	char *str = NULL;
 	char *locales[] = { "en_US", "en_GB", "en_AU", "en", NULL };
 	int i;
 
-	str = NULL;
 	for (i = 0; locales[i] != NULL && str == NULL; i++) {
 		str = g_strdup (lookup_locale (item, key, locales[i]));
 	}
--- a/libpurple/ft.c	Tue Jul 28 20:51:56 2009 +0000
+++ b/libpurple/ft.c	Thu Aug 13 17:15:06 2009 +0000
@@ -40,8 +40,35 @@
 static PurpleXferUiOps *xfer_ui_ops = NULL;
 static GList *xfers;
 
+/*
+ * A hack to store more data since we can't extend the size of PurpleXfer
+ * easily.
+ */
+static GHashTable *xfers_data = NULL;
+
+typedef struct _PurpleXferPrivData {
+	/*
+	 * Used to moderate the file transfer when either the read/write ui_ops are
+	 * set or fd is not set. In those cases, the UI/prpl call the respective
+	 * function, which is somewhat akin to a fd watch being triggered.
+	 */
+	enum {
+		PURPLE_XFER_READY_NONE = 0x0,
+		PURPLE_XFER_READY_UI   = 0x1,
+		PURPLE_XFER_READY_PRPL = 0x2,
+	} ready;
+} PurpleXferPrivData;
+
 static int purple_xfer_choose_file(PurpleXfer *xfer);
 
+static void
+purple_xfer_priv_data_destroy(gpointer data)
+{
+	PurpleXferPrivData *priv = data;
+
+	g_free(priv);
+}
+
 GList *
 purple_xfers_get_all()
 {
@@ -53,6 +80,7 @@
 {
 	PurpleXfer *xfer;
 	PurpleXferUiOps *ui_ops;
+	PurpleXferPrivData *priv;
 
 	g_return_val_if_fail(type    != PURPLE_XFER_UNKNOWN, NULL);
 	g_return_val_if_fail(account != NULL,              NULL);
@@ -70,6 +98,11 @@
 	xfer->current_buffer_size = FT_INITIAL_BUFFER_SIZE;
 	xfer->fd = -1;
 
+	priv = g_new0(PurpleXferPrivData, 1);
+	priv->ready = PURPLE_XFER_READY_NONE;
+
+	g_hash_table_insert(xfers_data, xfer, priv);
+
 	ui_ops = purple_xfer_get_ui_ops(xfer);
 
 	if (ui_ops != NULL && ui_ops->new_xfer != NULL)
@@ -101,11 +134,13 @@
 	g_free(xfer->filename);
 	g_free(xfer->remote_ip);
 	g_free(xfer->local_filename);
+
+	g_hash_table_remove(xfers_data, xfer);
 	g_free(xfer->thumbnail_data);
 
 	PURPLE_DBUS_UNREGISTER_POINTER(xfer);
+	xfers = g_list_remove(xfers, xfer);
 	g_free(xfer);
-	xfers = g_list_remove(xfers, xfer);
 }
 
 void
@@ -133,6 +168,9 @@
 {
 	g_return_if_fail(xfer != NULL);
 
+	if (xfer->status == status)
+		return;
+
 	xfer->status = status;
 
 	if(xfer->type == PURPLE_XFER_SEND) {
@@ -487,13 +525,16 @@
 	if (type == PURPLE_XFER_SEND) {
 		/* Sending a file */
 		/* Check the filename. */
+		PurpleXferUiOps *ui_ops;
+		ui_ops = purple_xfer_get_ui_ops(xfer);
+
 #ifdef _WIN32
 		if (g_strrstr(filename, "../") || g_strrstr(filename, "..\\"))
 #else
 		if (g_strrstr(filename, "../"))
 #endif
 		{
-			char *utf8 = g_filename_to_utf8(filename, -1, NULL, NULL, NULL);
+			utf8 = g_filename_to_utf8(filename, -1, NULL, NULL, NULL);
 
 			msg = g_strdup_printf(_("%s is not a valid filename.\n"), utf8);
 			purple_xfer_error(type, account, xfer->who, msg);
@@ -504,15 +545,20 @@
 			return;
 		}
 
-		if (g_stat(filename, &st) == -1) {
-			purple_xfer_show_file_error(xfer, filename);
-			purple_xfer_unref(xfer);
-			return;
+		if (ui_ops == NULL || (ui_ops->ui_read == NULL && ui_ops->ui_write == NULL)) {
+			if (g_stat(filename, &st) == -1) {
+				purple_xfer_show_file_error(xfer, filename);
+				purple_xfer_unref(xfer);
+				return;
+			}
+
+			purple_xfer_set_local_filename(xfer, filename);
+			purple_xfer_set_size(xfer, st.st_size);
+		} else {
+			utf8 = g_strdup(filename);
+			purple_xfer_set_local_filename(xfer, filename);
 		}
 
-		purple_xfer_set_local_filename(xfer, filename);
-		purple_xfer_set_size(xfer, st.st_size);
-
 		base = g_path_get_basename(filename);
 		utf8 = g_filename_to_utf8(base, -1, NULL, NULL, NULL);
 		g_free(base);
@@ -521,7 +567,6 @@
 		msg = g_strdup_printf(_("Offering to send %s to %s"),
 				utf8, buddy ? purple_buddy_get_alias(buddy) : xfer->who);
 		g_free(utf8);
-
 		purple_xfer_conversation_write(xfer, msg, FALSE);
 		g_free(msg);
 	}
@@ -889,9 +934,6 @@
 
 	if (xfer->ops.read != NULL)	{
 		r = (xfer->ops.read)(buffer, xfer);
-		if ((purple_xfer_get_size(xfer) > 0) &&
-			((purple_xfer_get_bytes_sent(xfer)+r) >= purple_xfer_get_size(xfer)))
-			purple_xfer_set_completed(xfer, TRUE);
 	}
 	else {
 		*buffer = g_malloc0(s);
@@ -901,9 +943,6 @@
 			r = 0;
 		else if (r < 0)
 			r = -1;
-		else if ((purple_xfer_get_size(xfer) > 0) &&
-			((purple_xfer_get_bytes_sent(xfer)+r) >= purple_xfer_get_size(xfer)))
-			purple_xfer_set_completed(xfer, TRUE);
 		else if (r == 0)
 			r = -1;
 	}
@@ -944,29 +983,39 @@
 }
 
 static void
-transfer_cb(gpointer data, gint source, PurpleInputCondition condition)
+do_transfer(PurpleXfer *xfer)
 {
 	PurpleXferUiOps *ui_ops;
-	PurpleXfer *xfer = (PurpleXfer *)data;
 	guchar *buffer = NULL;
 	gssize r = 0;
 
-	if (condition & PURPLE_INPUT_READ) {
+	ui_ops = purple_xfer_get_ui_ops(xfer);
+
+	if (xfer->type == PURPLE_XFER_RECEIVE) {
 		r = purple_xfer_read(xfer, &buffer);
 		if (r > 0) {
-			const size_t wc = fwrite(buffer, 1, r, xfer->dest_fp);
+			size_t wc;
+			if (ui_ops && ui_ops->ui_write)
+				wc = ui_ops->ui_write(xfer, buffer, r);
+			else
+				wc = fwrite(buffer, 1, r, xfer->dest_fp);
+
 			if (wc != r) {
 				purple_debug_error("filetransfer", "Unable to write whole buffer.\n");
 				purple_xfer_cancel_local(xfer);
+				g_free(buffer);
 				return;
 			}
+
+			if ((purple_xfer_get_size(xfer) > 0) &&
+				((purple_xfer_get_bytes_sent(xfer)+r) >= purple_xfer_get_size(xfer)))
+				purple_xfer_set_completed(xfer, TRUE);
 		} else if(r < 0) {
 			purple_xfer_cancel_remote(xfer);
+			g_free(buffer);
 			return;
 		}
-	}
-
-	if (condition & PURPLE_INPUT_WRITE) {
+	} else if (xfer->type == PURPLE_XFER_SEND) {
 		size_t result;
 		size_t s = MIN(purple_xfer_get_bytes_remaining(xfer), xfer->current_buffer_size);
 
@@ -980,26 +1029,53 @@
 			return;
 		}
 
-		buffer = g_malloc0(s);
+		if (ui_ops && ui_ops->ui_read) {
+			gssize tmp = ui_ops->ui_read(xfer, &buffer, s);
+			if (tmp == 0) {
+				/*
+				 * UI isn't ready to send data. It will call
+				 * purple_xfer_ui_ready when ready, which sets back up this
+				 * watcher.
+				 */
+				if (xfer->watcher != 0) {
+					purple_timeout_remove(xfer->watcher);
+					xfer->watcher = 0;
+				}
 
-		result = fread(buffer, 1, s, xfer->dest_fp);
-		if (result != s) {
-			purple_debug_error("filetransfer", "Unable to read whole buffer.\n");
-			purple_xfer_cancel_remote(xfer);
-			g_free(buffer);
-			return;
+				return;
+			} else if (tmp < 0) {
+				purple_debug_error("filetransfer", "Unable to read whole buffer.\n");
+				purple_xfer_cancel_local(xfer);
+				return;
+			}
+
+			result = tmp;
+		} else {
+			buffer = g_malloc0(s);
+			result = fread(buffer, 1, s, xfer->dest_fp);
+			if (result != s) {
+				purple_debug_error("filetransfer", "Unable to read whole buffer.\n");
+				purple_xfer_cancel_local(xfer);
+				g_free(buffer);
+				return;
+			}
 		}
 
 		/* Write as much as we're allowed to. */
-		r = purple_xfer_write(xfer, buffer, s);
+		r = purple_xfer_write(xfer, buffer, result);
 
 		if (r == -1) {
 			purple_xfer_cancel_remote(xfer);
 			g_free(buffer);
 			return;
-		} else if (r < s) {
-			/* We have to seek back in the file now. */
-			fseek(xfer->dest_fp, r - s, SEEK_CUR);
+		} else if (r < result) {
+			if (ui_ops == NULL || (ui_ops->ui_read == NULL && ui_ops->ui_write == NULL)) {
+				/* We have to seek back in the file now. */
+				fseek(xfer->dest_fp, r - s, SEEK_CUR);
+			}
+			else {
+				ui_ops->data_not_sent(xfer, buffer + r, result - r);
+			}
 		} else {
 			/*
 			 * We managed to write the entire buffer.  This means our
@@ -1021,8 +1097,6 @@
 
 		g_free(buffer);
 
-		ui_ops = purple_xfer_get_ui_ops(xfer);
-
 		if (ui_ops != NULL && ui_ops->update_progress != NULL)
 			ui_ops->update_progress(xfer,
 				purple_xfer_get_progress(xfer));
@@ -1033,22 +1107,45 @@
 }
 
 static void
+transfer_cb(gpointer data, gint source, PurpleInputCondition condition)
+{
+	PurpleXfer *xfer = data;
+
+	if (xfer->dest_fp == NULL) {
+		/* The UI is moderating its side manually */
+		PurpleXferPrivData *priv = g_hash_table_lookup(xfers_data, xfer);
+		if (0 == (priv->ready & PURPLE_XFER_READY_UI)) {
+			priv->ready |= PURPLE_XFER_READY_PRPL;
+
+			purple_input_remove(xfer->watcher);
+			xfer->watcher = 0;
+			return;
+		}
+	}
+
+	do_transfer(xfer);
+}
+
+static void
 begin_transfer(PurpleXfer *xfer, PurpleInputCondition cond)
 {
 	PurpleXferType type = purple_xfer_get_type(xfer);
+	PurpleXferUiOps *ui_ops = purple_xfer_get_ui_ops(xfer);
 
-	xfer->dest_fp = g_fopen(purple_xfer_get_local_filename(xfer),
-						  type == PURPLE_XFER_RECEIVE ? "wb" : "rb");
+	if (ui_ops == NULL || (ui_ops->ui_read == NULL && ui_ops->ui_write == NULL)) {
+		xfer->dest_fp = g_fopen(purple_xfer_get_local_filename(xfer),
+		                        type == PURPLE_XFER_RECEIVE ? "wb" : "rb");
 
-	if (xfer->dest_fp == NULL) {
-		purple_xfer_show_file_error(xfer, purple_xfer_get_local_filename(xfer));
-		purple_xfer_cancel_local(xfer);
-		return;
+		if (xfer->dest_fp == NULL) {
+			purple_xfer_show_file_error(xfer, purple_xfer_get_local_filename(xfer));
+			purple_xfer_cancel_local(xfer);
+			return;
+		}
+
+		fseek(xfer->dest_fp, xfer->bytes_sent, SEEK_SET);
 	}
 
-	fseek(xfer->dest_fp, xfer->bytes_sent, SEEK_SET);
-
-	if (xfer->fd)
+	if (xfer->fd != -1)
 		xfer->watcher = purple_input_add(xfer->fd, cond, transfer_cb, xfer);
 
 	xfer->start_time = time(NULL);
@@ -1073,6 +1170,54 @@
 }
 
 void
+purple_xfer_ui_ready(PurpleXfer *xfer)
+{
+	PurpleInputCondition cond;
+	PurpleXferType type;
+	PurpleXferPrivData *priv;
+
+	g_return_if_fail(xfer != NULL);
+
+	priv = g_hash_table_lookup(xfers_data, xfer);
+	priv->ready |= PURPLE_XFER_READY_UI;
+
+	if (0 == (priv->ready & PURPLE_XFER_READY_PRPL))
+		return;
+
+	type = purple_xfer_get_type(xfer);
+	if (type == PURPLE_XFER_SEND)
+		cond = PURPLE_INPUT_WRITE;
+	else /* if (type == PURPLE_XFER_RECEIVE) */
+		cond = PURPLE_INPUT_READ;
+
+	if (xfer->watcher == 0 && xfer->fd != -1)
+		xfer->watcher = purple_input_add(xfer->fd, cond, transfer_cb, xfer);
+
+	priv->ready = PURPLE_XFER_READY_NONE;
+
+	do_transfer(xfer);
+}
+
+void
+purple_xfer_prpl_ready(PurpleXfer *xfer)
+{
+	PurpleXferPrivData *priv;
+
+	g_return_if_fail(xfer != NULL);
+
+	priv = g_hash_table_lookup(xfers_data, xfer);
+	priv->ready |= PURPLE_XFER_READY_PRPL;
+
+	/* I don't think fwrite/fread are ever *not* ready */
+	if (xfer->dest_fp == NULL && 0 == (priv->ready & PURPLE_XFER_READY_UI))
+		return;
+
+	priv->ready = PURPLE_XFER_READY_NONE;
+
+	do_transfer(xfer);
+}
+
+void
 purple_xfer_start(PurpleXfer *xfer, int fd, const char *ip,
 				unsigned int port)
 {
@@ -1086,6 +1231,13 @@
 
 	purple_xfer_set_status(xfer, PURPLE_XFER_STATUS_STARTED);
 
+	/*
+	 * FIXME 3.0.0 -- there's too much broken code depending on fd == 0
+	 * meaning "don't use a real fd"
+	 */
+	if (fd == 0)
+		fd = -1;
+
 	if (type == PURPLE_XFER_RECEIVE) {
 		cond = PURPLE_INPUT_READ;
 
@@ -1132,7 +1284,7 @@
 		xfer->watcher = 0;
 	}
 
-	if (xfer->fd != 0)
+	if (xfer->fd != -1)
 		close(xfer->fd);
 
 	if (xfer->dest_fp != NULL) {
@@ -1195,7 +1347,7 @@
 		xfer->watcher = 0;
 	}
 
-	if (xfer->fd != 0)
+	if (xfer->fd != -1)
 		close(xfer->fd);
 
 	if (xfer->dest_fp != NULL) {
@@ -1260,7 +1412,7 @@
 		xfer->watcher = 0;
 	}
 
-	if (xfer->fd != 0)
+	if (xfer->fd != -1)
 		close(xfer->fd);
 
 	if (xfer->dest_fp != NULL) {
@@ -1359,6 +1511,9 @@
 purple_xfers_init(void) {
 	void *handle = purple_xfers_get_handle();
 
+	xfers_data = g_hash_table_new_full(g_direct_hash, g_direct_equal,
+	                                   NULL, purple_xfer_priv_data_destroy);
+
 	/* register signals */
 	purple_signal_register(handle, "file-recv-accept",
 	                     purple_marshal_VOID__POINTER, NULL, 1,
@@ -1405,6 +1560,9 @@
 
 	purple_signals_disconnect_by_handle(handle);
 	purple_signals_unregister_by_instance(handle);
+
+	g_hash_table_destroy(xfers_data);
+	xfers_data = NULL;
 }
 
 void
--- a/libpurple/ft.h	Tue Jul 28 20:51:56 2009 +0000
+++ b/libpurple/ft.h	Thu Aug 13 17:15:06 2009 +0000
@@ -79,8 +79,6 @@
 	void (*add_thumbnail)(PurpleXfer *xfer);
 
 	void (*_purple_reserved1)(void);
-	void (*_purple_reserved2)(void);
-	void (*_purple_reserved3)(void);
 } PurpleXferUiOps;
 
 /**
@@ -132,7 +130,6 @@
 		gssize (*read)(guchar **buffer, PurpleXfer *xfer);
 		gssize (*write)(const guchar *buffer, size_t size, PurpleXfer *xfer);
 		void (*ack)(PurpleXfer *xfer, const guchar *buffer, size_t size);
-
 	} ops;
 
 	PurpleXferUiOps *ui_ops;            /**< UI-specific operations. */
@@ -551,6 +548,12 @@
  * file receive transfer. On send, @a fd must be specified, and
  * @a ip and @a port are ignored.
  *
+ * Prior to libpurple 2.6.0, passing '0' to @a fd was special-cased to
+ * allow the protocol plugin to facilitate the file transfer itself. As of
+ * 2.6.0, this is supported (for backward compatibility), but will be
+ * removed in libpurple 3.0.0. If a prpl detects that the running libpurple
+ * is running 2.6.0 or higher, it should use the invalid fd '-1'.
+ *
  * @param xfer The file transfer.
  * @param fd   The file descriptor for the socket.
  * @param ip   The IP address to connect to.
@@ -620,41 +623,6 @@
  */
 void purple_xfer_conversation_write(PurpleXfer *xfer, char *message, gboolean is_error);
 
-/**
- * Gets the thumbnail data for a transfer
- *
- * @param xfer The file transfer to get the thumbnail for
- * @return The thumbnail data, or NULL if there is no thumbnail
- */
-const void *purple_xfer_get_thumbnail_data(const PurpleXfer *xfer);
-
-/**
- * Gets the thumbnail size for a transfer
- *
- * @param xfer The file transfer to get the thumbnail size for
- * @return The size, in bytes of the file transfer's thumbnail
- */
-gsize purple_xfer_get_thumbnail_size(const PurpleXfer *xfer);
-
-
-/**
- * Sets the thumbnail data for a transfer
- *
- * @param xfer The file transfer to set the data for
- * @param thumbnail A pointer to the thumbnail data, this will be copied
- * @param size The size in bytes of the passed in thumbnail data
- */
-void purple_xfer_set_thumbnail(PurpleXfer *xfer, gconstpointer thumbnail,
-	gsize size);
-
-/**
- * Prepare a thumbnail for a transfer (if the UI supports it)
- * will be no-op in case the UI doesn't implement thumbnail creation
- *
- * @param xfer The file transfer to create a thumbnail for
- */
-void purple_xfer_prepare_thumbnail(PurpleXfer *xfer);
-
 /*@}*/
 
 /**************************************************************************/
--- a/libpurple/log.c	Tue Jul 28 20:51:56 2009 +0000
+++ b/libpurple/log.c	Thu Aug 13 17:15:06 2009 +0000
@@ -1375,6 +1375,7 @@
 	char *image_corrected_msg;
 	char *date;
 	char *header;
+	char *escaped_from;
 	PurplePlugin *plugin = purple_find_prpl(purple_account_get_protocol_id(log->account));
 	PurpleLogCommonLoggerData *data = log->logger_data;
 	gsize written = 0;
@@ -1413,6 +1414,8 @@
 	if(!data->file)
 		return 0;
 
+	escaped_from = g_markup_escape_text(from, -1);
+
 	image_corrected_msg = convert_image_tags(log, message);
 	purple_markup_html_to_xhtml(image_corrected_msg, &msg_fixed, NULL);
 
@@ -1434,34 +1437,35 @@
 			written += fprintf(data->file, "<font color=\"#FF0000\"><font size=\"2\">(%s)</font><b> %s</b></font><br/>\n", date, msg_fixed);
 		else if (type & PURPLE_MESSAGE_WHISPER)
 			written += fprintf(data->file, "<font color=\"#6C2585\"><font size=\"2\">(%s)</font><b> %s:</b></font> %s<br/>\n",
-					date, from, msg_fixed);
+					date, escaped_from, msg_fixed);
 		else if (type & PURPLE_MESSAGE_AUTO_RESP) {
 			if (type & PURPLE_MESSAGE_SEND)
-				written += fprintf(data->file, _("<font color=\"#16569E\"><font size=\"2\">(%s)</font> <b>%s &lt;AUTO-REPLY&gt;:</b></font> %s<br/>\n"), date, from, msg_fixed);
+				written += fprintf(data->file, _("<font color=\"#16569E\"><font size=\"2\">(%s)</font> <b>%s &lt;AUTO-REPLY&gt;:</b></font> %s<br/>\n"), date, escaped_from, msg_fixed);
 			else if (type & PURPLE_MESSAGE_RECV)
-				written += fprintf(data->file, _("<font color=\"#A82F2F\"><font size=\"2\">(%s)</font> <b>%s &lt;AUTO-REPLY&gt;:</b></font> %s<br/>\n"), date, from, msg_fixed);
+				written += fprintf(data->file, _("<font color=\"#A82F2F\"><font size=\"2\">(%s)</font> <b>%s &lt;AUTO-REPLY&gt;:</b></font> %s<br/>\n"), date, escaped_from, msg_fixed);
 		} else if (type & PURPLE_MESSAGE_RECV) {
 			if(purple_message_meify(msg_fixed, -1))
 				written += fprintf(data->file, "<font color=\"#062585\"><font size=\"2\">(%s)</font> <b>***%s</b></font> %s<br/>\n",
-						date, from, msg_fixed);
+						date, escaped_from, msg_fixed);
 			else
 				written += fprintf(data->file, "<font color=\"#A82F2F\"><font size=\"2\">(%s)</font> <b>%s:</b></font> %s<br/>\n",
-						date, from, msg_fixed);
+						date, escaped_from, msg_fixed);
 		} else if (type & PURPLE_MESSAGE_SEND) {
 			if(purple_message_meify(msg_fixed, -1))
 				written += fprintf(data->file, "<font color=\"#062585\"><font size=\"2\">(%s)</font> <b>***%s</b></font> %s<br/>\n",
-						date, from, msg_fixed);
+						date, escaped_from, msg_fixed);
 			else
 				written += fprintf(data->file, "<font color=\"#16569E\"><font size=\"2\">(%s)</font> <b>%s:</b></font> %s<br/>\n",
-						date, from, msg_fixed);
+						date, escaped_from, msg_fixed);
 		} else {
 			purple_debug_error("log", "Unhandled message type.\n");
 			written += fprintf(data->file, "<font size=\"2\">(%s)</font><b> %s:</b></font> %s<br/>\n",
-						date, from, msg_fixed);
+						date, escaped_from, msg_fixed);
 		}
 	}
 	g_free(date);
 	g_free(msg_fixed);
+	g_free(escaped_from);
 	fflush(data->file);
 
 	return written;
--- a/libpurple/marshallers.list	Tue Jul 28 20:51:56 2009 +0000
+++ b/libpurple/marshallers.list	Thu Aug 13 17:15:06 2009 +0000
@@ -1,5 +1,6 @@
 VOID:POINTER,POINTER,OBJECT
 BOOLEAN:OBJECT,POINTER,STRING
 VOID:STRING,STRING
+VOID:STRING,STRING,DOUBLE
 VOID:ENUM,STRING,STRING
 VOID:ENUM,STRING,STRING,BOOLEAN
--- a/libpurple/media.c	Tue Jul 28 20:51:56 2009 +0000
+++ b/libpurple/media.c	Thu Aug 13 17:15:06 2009 +0000
@@ -94,6 +94,8 @@
 	FsStream *stream;
 	GstElement *src;
 	GstElement *tee;
+	GstElement *volume;
+	GstElement *level;
 
 	GList *local_candidates;
 	GList *remote_candidates;
@@ -157,9 +159,9 @@
 
 enum {
 	S_ERROR,
-	ACCEPTED,
 	CANDIDATES_PREPARED,
 	CODECS_CHANGED,
+	LEVEL,
 	NEW_CANDIDATE,
 	STATE_CHANGED,
 	STREAM_INFO,
@@ -272,6 +274,10 @@
 					"PURPLE_MEDIA_INFO_MUTE", "mute" },
 			{ PURPLE_MEDIA_INFO_UNMUTE,
 					"PURPLE_MEDIA_INFO_UNMUTE", "unmute" },
+			{ PURPLE_MEDIA_INFO_PAUSE,
+					"PURPLE_MEDIA_INFO_PAUSE", "pause" },
+			{ PURPLE_MEDIA_INFO_UNPAUSE,
+					"PURPLE_MEDIA_INFO_UNPAUSE", "unpause" },
 			{ PURPLE_MEDIA_INFO_HOLD,
 					"PURPLE_MEDIA_INFO_HOLD", "hold" },
 			{ PURPLE_MEDIA_INFO_UNHOLD,
@@ -332,10 +338,6 @@
 					 G_SIGNAL_RUN_LAST, 0, NULL, NULL,
 					 g_cclosure_marshal_VOID__STRING,
 					 G_TYPE_NONE, 1, G_TYPE_STRING);
-	purple_media_signals[ACCEPTED] = g_signal_new("accepted", G_TYPE_FROM_CLASS(klass),
-					 G_SIGNAL_RUN_LAST, 0, NULL, NULL,
-					 purple_smarshal_VOID__STRING_STRING,
-					 G_TYPE_NONE, 2, G_TYPE_STRING, G_TYPE_STRING);
 	purple_media_signals[CANDIDATES_PREPARED] = g_signal_new("candidates-prepared", G_TYPE_FROM_CLASS(klass),
 					 G_SIGNAL_RUN_LAST, 0, NULL, NULL,
 					 purple_smarshal_VOID__STRING_STRING,
@@ -345,6 +347,11 @@
 					 G_SIGNAL_RUN_LAST, 0, NULL, NULL,
 					 g_cclosure_marshal_VOID__STRING,
 					 G_TYPE_NONE, 1, G_TYPE_STRING);
+	purple_media_signals[LEVEL] = g_signal_new("level", G_TYPE_FROM_CLASS(klass),
+					 G_SIGNAL_RUN_LAST, 0, NULL, NULL,
+					 purple_smarshal_VOID__STRING_STRING_DOUBLE,
+					 G_TYPE_NONE, 3, G_TYPE_STRING,
+					 G_TYPE_STRING, G_TYPE_DOUBLE);
 	purple_media_signals[NEW_CANDIDATE] = g_signal_new("new-candidate", G_TYPE_FROM_CLASS(klass),
 					 G_SIGNAL_RUN_LAST, 0, NULL, NULL,
 					 purple_smarshal_VOID__POINTER_POINTER_OBJECT,
@@ -1899,11 +1906,31 @@
 		gst_element_add_pad(session->media->priv->confbin, ghost);
 	}
 
+	gst_element_set_state(session->tee, GST_STATE_PLAYING);
 	gst_element_link(session->src, session->media->priv->confbin);
-	gst_element_set_state(session->tee, GST_STATE_PLAYING);
 
 	g_object_get(session->session, "sink-pad", &sinkpad, NULL);
-	srcpad = gst_element_get_request_pad(session->tee, "src%d");
+	if (session->type & PURPLE_MEDIA_SEND_AUDIO) {
+		gchar *name = g_strdup_printf("volume_%s", session->id);
+		GstElement *level;
+		GstElement *volume = gst_element_factory_make("volume", name);
+		double input_volume = purple_prefs_get_int(
+				"/purple/media/audio/volume/input")/10.0;
+		g_free(name);
+		name = g_strdup_printf("sendlevel_%s", session->id);
+		level = gst_element_factory_make("level", name);
+		g_free(name);
+		gst_bin_add(GST_BIN(session->media->priv->confbin), volume);
+		gst_bin_add(GST_BIN(session->media->priv->confbin), level);
+		gst_element_set_state(level, GST_STATE_PLAYING);
+		gst_element_set_state(volume, GST_STATE_PLAYING);
+		gst_element_link(volume, level);
+		gst_element_link(session->tee, volume);
+		srcpad = gst_element_get_static_pad(level, "src");
+		g_object_set(volume, "volume", input_volume, NULL);
+	} else {
+		srcpad = gst_element_get_request_pad(session->tee, "src%d");
+	}
 	purple_debug_info("media", "connecting pad: %s\n", 
 			  gst_pad_link(srcpad, sinkpad) == GST_PAD_LINK_OK
 			  ? "success" : "failure");
@@ -1960,6 +1987,55 @@
 {
 	switch(GST_MESSAGE_TYPE(msg)) {
 		case GST_MESSAGE_ELEMENT: {
+			if (g_signal_has_handler_pending(media,
+					purple_media_signals[LEVEL], 0, FALSE)
+					&& gst_structure_has_name(
+					gst_message_get_structure(msg), "level")) {
+				GstElement *src = GST_ELEMENT(GST_MESSAGE_SRC(msg));
+				gchar *name;
+				gchar *participant = NULL;
+				PurpleMediaSession *session = NULL;
+				gdouble rms_db;
+				gdouble percent;
+				const GValue *list;
+				const GValue *value;
+
+				if (!PURPLE_IS_MEDIA(media) ||
+						GST_ELEMENT_PARENT(src) !=
+						media->priv->confbin)
+					break;
+
+				name = gst_element_get_name(src);
+				if (!strncmp(name, "sendlevel_", 10)) {
+					session = purple_media_get_session(
+							media, name+10);
+				} else {
+					GList *iter = media->priv->streams;
+					for (; iter; iter = g_list_next(iter)) {
+						PurpleMediaStream *stream = iter->data;
+						if (stream->level == src) {
+							session = stream->session;
+							participant = stream->participant;
+							break;
+						}
+					}
+				}
+				g_free(name);
+				if (!session)
+					break;
+
+				list = gst_structure_get_value(
+						gst_message_get_structure(msg), "rms");
+				value = gst_value_list_get_value(list, 0);
+				rms_db = g_value_get_double(value);
+				percent = pow(10, rms_db / 20) * 5;
+				if(percent > 1.0)
+					percent = 1.0;
+
+				g_signal_emit(media, purple_media_signals[LEVEL],
+						0, session->id, participant, percent);
+				break;
+			}
 			if (!FS_IS_CONFERENCE(GST_MESSAGE_SRC(msg)) ||
 					!PURPLE_IS_MEDIA(media) ||
 					media->priv->conference !=
@@ -2155,9 +2231,6 @@
 					stream->session->type), NULL);
 			stream->accepted = TRUE;
 		}
-
-		g_signal_emit(media, purple_media_signals[ACCEPTED],
-				0, NULL, NULL);
 	} else if (local == TRUE && (type == PURPLE_MEDIA_INFO_MUTE ||
 			type == PURPLE_MEDIA_INFO_UNMUTE)) {
 		GList *sessions;
@@ -2180,12 +2253,30 @@
 				sessions, sessions)) {
 			PurpleMediaSession *session = sessions->data;
 			if (session->type & PURPLE_MEDIA_SEND_AUDIO) {
+				gchar *name = g_strdup_printf("volume_%s",
+						session->id);
 				GstElement *volume = gst_bin_get_by_name(
-						GST_BIN(session->src),
-						"purpleaudioinputvolume");
+						GST_BIN(session->media->
+						priv->confbin), name);
+				g_free(name);
 				g_object_set(volume, "mute", active, NULL);
 			}
 		}
+	} else if (local == TRUE && (type == PURPLE_MEDIA_INFO_PAUSE ||
+			type == PURPLE_MEDIA_INFO_UNPAUSE)) {
+		gboolean active = (type == PURPLE_MEDIA_INFO_PAUSE);
+		GList *streams = purple_media_get_streams(media,
+				session_id, participant);
+		for (; streams; streams = g_list_delete_link(streams, streams)) {
+			PurpleMediaStream *stream = streams->data;
+			if (stream->session->type & PURPLE_MEDIA_SEND_VIDEO) {
+				g_object_set(stream->stream, "direction",
+						purple_media_to_fs_stream_direction(
+						stream->session->type & ((active) ?
+						~PURPLE_MEDIA_SEND_VIDEO :
+						PURPLE_MEDIA_VIDEO)), NULL);
+			}
+		}
 	}
 
 	g_signal_emit(media, purple_media_signals[STREAM_INFO],
@@ -2346,11 +2437,21 @@
 		GstElement *sink = NULL;
 
 		if (codec->media_type == FS_MEDIA_TYPE_AUDIO) {
+			GstElement *queue = NULL;
+			double output_volume = purple_prefs_get_int(
+					"/purple/media/audio/volume/output")/10.0;
 			/*
 			 * Should this instead be:
 			 *  audioconvert ! audioresample ! liveadder !
 			 *   audioresample ! audioconvert ! realsink
 			 */
+			queue = gst_element_factory_make("queue", NULL);
+			stream->volume = gst_element_factory_make(
+					"volume", NULL);
+			g_object_set(stream->volume, "volume",
+					output_volume, NULL);
+			stream->level = gst_element_factory_make(
+					"level", NULL);
 			stream->src = gst_element_factory_make(
 					"liveadder", NULL);
 			sink = purple_media_manager_get_element(priv->manager,
@@ -2358,19 +2459,32 @@
 					stream->session->media,
 					stream->session->id,
 					stream->participant);
+			gst_bin_add(GST_BIN(priv->confbin), queue);
+			gst_bin_add(GST_BIN(priv->confbin), stream->volume);
+			gst_bin_add(GST_BIN(priv->confbin), stream->level);
+			gst_bin_add(GST_BIN(priv->confbin), sink);
+			gst_element_set_state(sink, GST_STATE_PLAYING);
+			gst_element_set_state(stream->level, GST_STATE_PLAYING);
+			gst_element_set_state(stream->volume, GST_STATE_PLAYING);
+			gst_element_set_state(queue, GST_STATE_PLAYING);
+			gst_element_link(stream->level, sink);
+			gst_element_link(stream->volume, stream->level);
+			gst_element_link(queue, stream->volume);
+			sink = queue;
 		} else if (codec->media_type == FS_MEDIA_TYPE_VIDEO) {
 			stream->src = gst_element_factory_make(
 					"fsfunnel", NULL);
 			sink = gst_element_factory_make(
 					"fakesink", NULL);
 			g_object_set(G_OBJECT(sink), "async", FALSE, NULL);
+			gst_bin_add(GST_BIN(priv->confbin), sink);
+			gst_element_set_state(sink, GST_STATE_PLAYING);
 		}
 		stream->tee = gst_element_factory_make("tee", NULL);
 		gst_bin_add_many(GST_BIN(priv->confbin),
-				stream->src, stream->tee, sink, NULL);
-		gst_element_sync_state_with_parent(sink);
-		gst_element_sync_state_with_parent(stream->tee);
-		gst_element_sync_state_with_parent(stream->src);
+				stream->src, stream->tee, NULL);
+		gst_element_set_state(stream->tee, GST_STATE_PLAYING);
+		gst_element_set_state(stream->src, GST_STATE_PLAYING);
 		gst_element_link_many(stream->src, stream->tee, sink, NULL);
 	}
 
@@ -2504,26 +2618,27 @@
 				0, PURPLE_MEDIA_STATE_NEW,
 				session->id, NULL);
 
-		session_type = purple_media_from_fs(media_type,
-				FS_DIRECTION_SEND);
-		src = purple_media_manager_get_element(
-				media->priv->manager, session_type,
-				media, session->id, who);
-		if (!GST_IS_ELEMENT(src)) {
-			purple_debug_error("media",
-					"Error creating src for session %s\n",
-					session->id);
-			purple_media_end(media, session->id, NULL);
-			return FALSE;
+		if (type_direction & FS_DIRECTION_SEND) {
+			session_type = purple_media_from_fs(media_type,
+					FS_DIRECTION_SEND);
+			src = purple_media_manager_get_element(
+					media->priv->manager, session_type,
+					media, session->id, who);
+			if (!GST_IS_ELEMENT(src)) {
+				purple_debug_error("media",
+						"Error creating src for session %s\n",
+						session->id);
+				purple_media_end(media, session->id, NULL);
+				return FALSE;
+			}
+
+			purple_media_set_src(media, session->id, src);
+			gst_element_set_state(session->src, GST_STATE_PLAYING);
+			purple_media_manager_create_output_window(
+					media->priv->manager,
+					session->media,
+					session->id, NULL);
 		}
-
-		purple_media_set_src(media, session->id, src);
-		gst_element_set_state(session->src, GST_STATE_PLAYING);
-
-		purple_media_manager_create_output_window(
-				media->priv->manager,
-				session->media,
-				session->id, NULL);
 	}
 
 	if (!(participant = purple_media_add_participant(media, who))) {
@@ -2889,14 +3004,23 @@
 
 		if (session == NULL)
 			return FALSE;
-
-		g_object_get(session->session, "codecs-ready", &ret, NULL);
+		if (session->type & (PURPLE_MEDIA_SEND_AUDIO |
+				PURPLE_MEDIA_SEND_VIDEO))
+			g_object_get(session->session,
+					"codecs-ready", &ret, NULL);
+		else
+			ret = TRUE;
 	} else {
 		GList *values = g_hash_table_get_values(media->priv->sessions);
 		for (; values; values = g_list_delete_link(values, values)) {
 			PurpleMediaSession *session = values->data;
-			g_object_get(session->session,
-					"codecs-ready", &ret, NULL);
+			if (session->type & (PURPLE_MEDIA_SEND_AUDIO |
+					PURPLE_MEDIA_SEND_VIDEO))
+				g_object_get(session->session,
+						"codecs-ready", &ret, NULL);
+			else
+				ret = TRUE;
+
 			if (ret == FALSE)
 				break;
 		}
@@ -2983,6 +3107,8 @@
 
 	g_return_if_fail(PURPLE_IS_MEDIA(media));
 
+	purple_prefs_set_int("/purple/media/audio/volume/input", level);
+
 	if (session_id == NULL)
 		sessions = g_hash_table_get_values(media->priv->sessions);
 	else
@@ -2993,10 +3119,13 @@
 		PurpleMediaSession *session = sessions->data;
 
 		if (session->type & PURPLE_MEDIA_SEND_AUDIO) {
+			gchar *name = g_strdup_printf("volume_%s",
+					session->id);
 			GstElement *volume = gst_bin_get_by_name(
-					GST_BIN(session->src),
-					"purpleaudioinputvolume");
-			g_object_set(volume, "volume", level, NULL);
+					GST_BIN(session->media->priv->confbin),
+					name);
+			g_free(name);
+			g_object_set(volume, "volume", level/10.0, NULL);
 		}
 	}
 #endif
@@ -3011,34 +3140,17 @@
 
 	g_return_if_fail(PURPLE_IS_MEDIA(media));
 
+	purple_prefs_set_int("/purple/media/audio/volume/output", level);
+
 	streams = purple_media_get_streams(media,
 			session_id, participant);
 
 	for (; streams; streams = g_list_delete_link(streams, streams)) {
 		PurpleMediaStream *stream = streams->data;
 
-		if (stream->session->type & PURPLE_MEDIA_RECV_AUDIO) {
-			GstElement *tee = stream->tee;
-			GstIterator *iter = gst_element_iterate_src_pads(tee);
-			GstPad *sinkpad;
-			while (gst_iterator_next(iter, (gpointer)&sinkpad)
-					 == GST_ITERATOR_OK) {
-				GstPad *peer = gst_pad_get_peer(sinkpad);
-				GstElement *volume;
-
-				if (peer == NULL) {
-					gst_object_unref(sinkpad);
-					continue;
-				}
-					
-				volume = gst_bin_get_by_name(GST_BIN(
-						GST_OBJECT_PARENT(peer)),
-						"purpleaudiooutputvolume");
-				g_object_set(volume, "volume", level, NULL);
-				gst_object_unref(peer);
-				gst_object_unref(sinkpad);
-			}
-			gst_iterator_free(iter);
+		if (stream->session->type & PURPLE_MEDIA_RECV_AUDIO
+				&& GST_IS_ELEMENT(stream->volume)) {
+			g_object_set(stream->volume, "volume", level/10.0, NULL);
 		}
 	}
 #endif
--- a/libpurple/media.h	Tue Jul 28 20:51:56 2009 +0000
+++ b/libpurple/media.h	Thu Aug 13 17:15:06 2009 +0000
@@ -103,6 +103,8 @@
 	PURPLE_MEDIA_INFO_REJECT,
 	PURPLE_MEDIA_INFO_MUTE,
 	PURPLE_MEDIA_INFO_UNMUTE,
+	PURPLE_MEDIA_INFO_PAUSE,
+	PURPLE_MEDIA_INFO_UNPAUSE,
 	PURPLE_MEDIA_INFO_HOLD,
 	PURPLE_MEDIA_INFO_UNHOLD,
 } PurpleMediaInfoType;
--- a/libpurple/mediamanager.c	Tue Jul 28 20:51:56 2009 +0000
+++ b/libpurple/mediamanager.c	Thu Aug 13 17:15:06 2009 +0000
@@ -39,6 +39,7 @@
 #ifdef USE_VV
 
 #include <gst/farsight/fs-conference-iface.h>
+#include <gst/farsight/fs-element-added-notifier.h>
 #include <gst/interfaces/xoverlay.h>
 
 /** @copydoc _PurpleMediaManagerPrivate */
@@ -156,6 +157,12 @@
 	media->priv = PURPLE_MEDIA_MANAGER_GET_PRIVATE(media);
 	media->priv->medias = NULL;
 	media->priv->next_output_window_id = 1;
+
+	purple_prefs_add_none("/purple/media");
+	purple_prefs_add_none("/purple/media/audio");
+	purple_prefs_add_none("/purple/media/audio/volume");
+	purple_prefs_add_int("/purple/media/audio/volume/input", 10);
+	purple_prefs_add_int("/purple/media/audio/volume/output", 10);
 }
 
 static void
@@ -229,6 +236,10 @@
 	g_return_val_if_fail(PURPLE_IS_MEDIA_MANAGER(manager), NULL);
 
 	if (manager->priv->pipeline == NULL) {
+		FsElementAddedNotifier *notifier;
+		gchar *filename;
+		GError *err = NULL;
+		GKeyFile *keyfile;
 		GstBus *bus;
 		manager->priv->pipeline = gst_pipeline_new(NULL);
 
@@ -241,6 +252,38 @@
 				gst_bus_sync_signal_handler, NULL);
 		gst_object_unref(bus);
 
+		filename = g_build_filename(purple_user_dir(),
+				"fs-element.conf", NULL);
+		keyfile = g_key_file_new();
+		if (!g_key_file_load_from_file(keyfile, filename,
+				G_KEY_FILE_NONE, &err)) {
+			if (err->code == 4)
+				purple_debug_info("mediamanager",
+						"Couldn't read "
+						"fs-element.conf: %s\n",
+						err->message);
+			else
+				purple_debug_error("mediamanager",
+						"Error reading "
+						"fs-element.conf: %s\n",
+						err->message);
+			g_error_free(err);
+		}
+		g_free(filename);
+
+		/* Hack to make alsasrc stop messing up audio timestamps */
+		if (!g_key_file_has_key(keyfile,
+				"alsasrc", "slave-method", NULL)) {
+			g_key_file_set_integer(keyfile,
+					"alsasrc", "slave-method", 2);
+		}
+
+		notifier = fs_element_added_notifier_new();
+		fs_element_added_notifier_add(notifier,
+				GST_BIN(manager->priv->pipeline));
+		fs_element_added_notifier_set_properties_from_keyfile(
+				notifier, keyfile);
+
 		gst_element_set_state(manager->priv->pipeline,
 				GST_STATE_PLAYING);
 	}
@@ -356,15 +399,19 @@
 	GstElement *parent = GST_ELEMENT_PARENT(pad);
 	GstIterator *iter;
 	GstPad *remaining_pad;
+	GstIteratorResult result;
 
 	gst_element_release_request_pad(GST_ELEMENT_PARENT(pad), pad);
-	iter = gst_element_iterate_pads(parent);
+	iter = gst_element_iterate_src_pads(parent);
 
-	if (gst_iterator_next(iter, (gpointer)&remaining_pad)
-			== GST_ITERATOR_DONE) {
+	result = gst_iterator_next(iter, (gpointer)&remaining_pad);
+
+	if (result == GST_ITERATOR_DONE) {
 		gst_element_set_locked_state(parent, TRUE);
 		gst_element_set_state(parent, GST_STATE_NULL);
 		gst_bin_remove(GST_BIN(GST_ELEMENT_PARENT(parent)), parent);
+	} else if (result == GST_ITERATOR_OK) {
+		gst_object_unref(remaining_pad);
 	}
 
 	gst_iterator_free(iter);
@@ -426,7 +473,6 @@
 			gst_element_link(tee, fakesink);
 
 			ret = bin;
-			gst_element_set_locked_state(ret, TRUE);
 			gst_object_ref(ret);
 			gst_bin_add(GST_BIN(purple_media_manager_get_pipeline(
 					manager)), ret);
@@ -667,7 +713,7 @@
 				(participant == ow->participant)) &&
 				!strcmp(session_id, ow->session_id)) {
 			GstBus *bus;
-			GstElement *queue;
+			GstElement *queue, *colorspace;
 			GstElement *tee = purple_media_get_tee(media,
 					session_id, participant);
 
@@ -676,6 +722,8 @@
 
 			queue = gst_element_factory_make(
 					"queue", NULL);
+			colorspace = gst_element_factory_make(
+					"ffmpegcolorspace", NULL);
 			ow->sink = purple_media_manager_get_element(
 					manager, PURPLE_MEDIA_RECV_VIDEO,
 					ow->media, ow->session_id,
@@ -696,7 +744,7 @@
 			}
 
 			gst_bin_add_many(GST_BIN(GST_ELEMENT_PARENT(tee)),
-					queue, ow->sink, NULL);
+					queue, colorspace, ow->sink, NULL);
 
 			bus = gst_pipeline_get_bus(GST_PIPELINE(
 					manager->priv->pipeline));
@@ -704,9 +752,11 @@
 					G_CALLBACK(window_id_cb), ow);
 			gst_object_unref(bus);
 
-			gst_element_sync_state_with_parent(ow->sink);
-			gst_element_link(queue, ow->sink);
-			gst_element_sync_state_with_parent(queue);
+			gst_element_set_state(ow->sink, GST_STATE_PLAYING);
+			gst_element_set_state(colorspace, GST_STATE_PLAYING);
+			gst_element_set_state(queue, GST_STATE_PLAYING);
+			gst_element_link(colorspace, ow->sink);
+			gst_element_link(queue, colorspace);
 			gst_element_link(tee, queue);
 		}
 	}
@@ -775,8 +825,14 @@
 		GstPad *pad = gst_element_get_static_pad(
 				output_window->sink, "sink");
 		GstPad *peer = gst_pad_get_peer(pad);
-		GstElement *queue = GST_ELEMENT_PARENT(peer);
+		GstElement *colorspace = GST_ELEMENT_PARENT(peer), *queue;
 		gst_object_unref(pad);
+		gst_object_unref(peer);
+		pad = gst_element_get_static_pad(colorspace, "sink");
+		peer = gst_pad_get_peer(pad);
+		queue = GST_ELEMENT_PARENT(peer);
+		gst_object_unref(pad);
+		gst_object_unref(peer);
 		pad = gst_element_get_static_pad(queue, "sink");
 		peer = gst_pad_get_peer(pad);
 		gst_object_unref(pad);
@@ -785,6 +841,9 @@
 		gst_element_set_locked_state(queue, TRUE);
 		gst_element_set_state(queue, GST_STATE_NULL);
 		gst_bin_remove(GST_BIN(GST_ELEMENT_PARENT(queue)), queue);
+		gst_element_set_locked_state(colorspace, TRUE);
+		gst_element_set_state(colorspace, GST_STATE_NULL);
+		gst_bin_remove(GST_BIN(GST_ELEMENT_PARENT(colorspace)), colorspace);
 		gst_element_set_locked_state(output_window->sink, TRUE);
 		gst_element_set_state(output_window->sink, GST_STATE_NULL);
 		gst_bin_remove(GST_BIN(GST_ELEMENT_PARENT(output_window->sink)),
--- a/libpurple/pounce.c	Tue Jul 28 20:51:56 2009 +0000
+++ b/libpurple/pounce.c	Thu Aug 13 17:15:06 2009 +0000
@@ -1157,4 +1157,7 @@
 	}
 
 	purple_signals_disconnect_by_handle(purple_pounces_get_handle());
+
+	g_hash_table_destroy(pounce_handlers);
+	pounce_handlers = NULL;
 }
--- a/libpurple/prefs.c	Tue Jul 28 20:51:56 2009 +0000
+++ b/libpurple/prefs.c	Thu Aug 13 17:15:06 2009 +0000
@@ -1450,6 +1450,8 @@
 		sync_prefs();
 	}
 
+	purple_prefs_disconnect_by_handle(purple_prefs_get_handle());
+
 	prefs_loaded = FALSE;
 	purple_prefs_destroy();
 	g_hash_table_destroy(prefs_hash);
--- a/libpurple/protocols/bonjour/mdns_avahi.c	Tue Jul 28 20:51:56 2009 +0000
+++ b/libpurple/protocols/bonjour/mdns_avahi.c	Thu Aug 13 17:15:06 2009 +0000
@@ -200,8 +200,8 @@
 			}
 
 			if (!bonjour_buddy_check(bb)) {
+				b_impl->resolvers = g_slist_remove(b_impl->resolvers, rd);
 				_cleanup_resolver_data(rd);
-				b_impl->resolvers = g_slist_remove(b_impl->resolvers, rd);
 				/* If this was the last resolver, remove the buddy */
 				if (b_impl->resolvers == NULL) {
 					if (pb != NULL)
--- a/libpurple/protocols/gg/lib/libgadu.c	Tue Jul 28 20:51:56 2009 +0000
+++ b/libpurple/protocols/gg/lib/libgadu.c	Thu Aug 13 17:15:06 2009 +0000
@@ -790,6 +790,7 @@
 		gg_debug(GG_DEBUG_MISC, "// gg_recv_packet() body recv(%d,%p,%d) = %d\n", sess->fd, buf + sizeof(h) + offset, size, ret);
 		if (!ret) {
 			gg_debug(GG_DEBUG_MISC, "// gg_recv_packet() body recv() failed: connection broken\n");
+			free(buf);
 			errno = ECONNRESET;
 			return NULL;
 		}
--- a/libpurple/protocols/irc/irc.c	Tue Jul 28 20:51:56 2009 +0000
+++ b/libpurple/protocols/irc/irc.c	Thu Aug 13 17:15:06 2009 +0000
@@ -569,9 +569,20 @@
 static void irc_add_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group)
 {
 	struct irc_conn *irc = (struct irc_conn *)gc->proto_data;
-	struct irc_buddy *ib = g_new0(struct irc_buddy, 1);
-	ib->name = g_strdup(purple_buddy_get_name(buddy));
-	g_hash_table_replace(irc->buddies, ib->name, ib);
+	struct irc_buddy *ib;
+	const char *bname = purple_buddy_get_name(buddy);
+
+	ib = g_hash_table_lookup(irc->buddies, bname);
+	if (ib != NULL) {
+		ib->ref++;
+		purple_prpl_got_user_status(irc->account, bname,
+				ib->online ? "available" : "offline", NULL);
+	} else {
+		ib = g_new0(struct irc_buddy, 1);
+		ib->name = g_strdup(bname);
+		ib->ref = 1;
+		g_hash_table_replace(irc->buddies, ib->name, ib);
+	}
 
 	/* if the timer isn't set, this is during signon, so we don't want to flood
 	 * ourself off with ISON's, so we don't, but after that we want to know when
@@ -583,7 +594,12 @@
 static void irc_remove_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group)
 {
 	struct irc_conn *irc = (struct irc_conn *)gc->proto_data;
-	g_hash_table_remove(irc->buddies, purple_buddy_get_name(buddy));
+	struct irc_buddy *ib;
+
+	ib = g_hash_table_lookup(irc->buddies, purple_buddy_get_name(buddy));
+	if (ib && --ib->ref == 0) {
+		g_hash_table_remove(irc->buddies, purple_buddy_get_name(buddy));
+	}
 }
 
 static void read_input(struct irc_conn *irc, int len)
--- a/libpurple/protocols/irc/irc.h	Tue Jul 28 20:51:56 2009 +0000
+++ b/libpurple/protocols/irc/irc.h	Thu Aug 13 17:15:06 2009 +0000
@@ -97,6 +97,7 @@
 	char *name;
 	gboolean online;
 	gboolean flag;
+	int ref;
 };
 
 typedef int (*IRCCmdCallback) (struct irc_conn *irc, const char *cmd, const char *target, const char **args);
--- a/libpurple/protocols/irc/msgs.c	Tue Jul 28 20:51:56 2009 +0000
+++ b/libpurple/protocols/irc/msgs.c	Thu Aug 13 17:15:06 2009 +0000
@@ -103,7 +103,8 @@
 		PurpleBuddy *b = buddies->data;
 		struct irc_buddy *ib = g_new0(struct irc_buddy, 1);
 		ib->name = g_strdup(purple_buddy_get_name(b));
-		g_hash_table_insert(irc->buddies, ib->name, ib);
+		ib->ref = 1;
+		g_hash_table_replace(irc->buddies, ib->name, ib);
 	}
 
 	irc_blist_timeout(irc);
--- a/libpurple/protocols/jabber/bosh.c	Tue Jul 28 20:51:56 2009 +0000
+++ b/libpurple/protocols/jabber/bosh.c	Thu Aug 13 17:15:06 2009 +0000
@@ -29,8 +29,12 @@
 
 #include "bosh.h"
 
-#define MAX_HTTP_CONNECTIONS      2
+/* The number of HTTP connections to use. This MUST be at least 2. */
+#define NUM_HTTP_CONNECTIONS      2
+/* How many failed connection attempts before it becomes a fatal error */
 #define MAX_FAILED_CONNECTIONS    3
+/* How long in seconds to queue up outgoing messages */
+#define BUFFER_SEND_IN_SECS       1
 
 typedef struct _PurpleHTTPConnection PurpleHTTPConnection;
 
@@ -40,55 +44,63 @@
 static char *bosh_useragent = NULL;
 
 typedef enum {
+	PACKET_NORMAL,
 	PACKET_TERMINATE,
-	PACKET_STREAM_RESTART,
-	PACKET_NORMAL,
+	PACKET_FLUSH,
 } PurpleBOSHPacketType;
 
 struct _PurpleBOSHConnection {
 	JabberStream *js;
+	PurpleHTTPConnection *connections[NUM_HTTP_CONNECTIONS];
+
+	PurpleCircBuffer *pending;
+	PurpleBOSHConnectionConnectFunction connect_cb;
+	PurpleBOSHConnectionReceiveFunction receive_cb;
+
+	/* Must be big enough to hold 2^53 - 1 */
+	char *sid;
+	guint64 rid;
+
+	/* decoded URL */
+	char *host;
+	char *path;
+	guint16 port;
+
 	gboolean pipelining;
-	PurpleHTTPConnection *connections[MAX_HTTP_CONNECTIONS];
-	unsigned short failed_connections;
+	gboolean ssl;
+	gboolean needs_restart;
 
 	enum {
 		BOSH_CONN_OFFLINE,
 		BOSH_CONN_BOOTING,
 		BOSH_CONN_ONLINE
 	} state;
-	gboolean ssl;
-	gboolean needs_restart;
+	guint8 failed_connections;
 
-	/* decoded URL */
-	char *host;
-	int port;
-	char *path;
-
-	/* Must be big enough to hold 2^53 - 1 */
-	guint64 rid;
-	char *sid;
-
-	unsigned int inactivity_timer;
 	int max_inactivity;
 	int wait;
 
-	PurpleCircBuffer *pending;
 	int max_requests;
 	int requests;
 
-	PurpleBOSHConnectionConnectFunction connect_cb;
-	PurpleBOSHConnectionReceiveFunction receive_cb;
+	guint inactivity_timer;
+	guint send_timer;
 };
 
 struct _PurpleHTTPConnection {
 	PurpleBOSHConnection *bosh;
 	PurpleSslConnection *psc;
+
+	PurpleCircBuffer *write_buf;
+	GString *read_buf;
+
+	gsize handled_len;
+	gsize body_len;
+
 	int fd;
 	guint readh;
 	guint writeh;
 
-	PurpleCircBuffer *write_buffer;
-
 	enum {
 		HTTP_CONN_OFFLINE,
 		HTTP_CONN_CONNECTING,
@@ -96,16 +108,14 @@
 	} state;
 	int requests; /* number of outstanding HTTP requests */
 
-	GString *buf;
 	gboolean headers_done;
-	gsize handled_len;
-	gsize body_len;
 
 };
 
 static void http_connection_connect(PurpleHTTPConnection *conn);
 static void http_connection_send_request(PurpleHTTPConnection *conn,
                                          const GString *req);
+static gboolean send_timer_cb(gpointer data);
 
 void jabber_bosh_init(void)
 {
@@ -140,7 +150,7 @@
 	conn->fd = -1;
 	conn->state = HTTP_CONN_OFFLINE;
 
-	conn->write_buffer = purple_circ_buffer_new(0 /* default grow size */);
+	conn->write_buf = purple_circ_buffer_new(0 /* default grow size */);
 
 	return conn;
 }
@@ -148,11 +158,11 @@
 static void
 jabber_bosh_http_connection_destroy(PurpleHTTPConnection *conn)
 {
-	if (conn->buf)
-		g_string_free(conn->buf, TRUE);
+	if (conn->read_buf)
+		g_string_free(conn->read_buf, TRUE);
 
-	if (conn->write_buffer)
-		purple_circ_buffer_destroy(conn->write_buffer);
+	if (conn->write_buf)
+		purple_circ_buffer_destroy(conn->write_buf);
 	if (conn->readh)
 		purple_input_remove(conn->readh);
 	if (conn->writeh)
@@ -227,12 +237,14 @@
 	g_free(conn->host);
 	g_free(conn->path);
 
+	if (conn->send_timer)
+		purple_timeout_remove(conn->send_timer);
 	if (conn->inactivity_timer)
 		purple_timeout_remove(conn->inactivity_timer);
 
 	purple_circ_buffer_destroy(conn->pending);
 
-	for (i = 0; i < MAX_HTTP_CONNECTIONS; ++i) {
+	for (i = 0; i < NUM_HTTP_CONNECTIONS; ++i) {
 		if (conn->connections[i])
 			jabber_bosh_http_connection_destroy(conn->connections[i]);
 	}
@@ -250,6 +262,19 @@
 {
 	int i;
 
+	if (purple_debug_is_verbose()) {
+		for (i = 0; i < NUM_HTTP_CONNECTIONS; ++i) {
+			PurpleHTTPConnection *httpconn = conn->connections[i];
+			if (httpconn == NULL)
+				purple_debug_misc("jabber", "BOSH %p->connections[%d] = (nil)\n",
+				                  conn, i);
+			else
+				purple_debug_misc("jabber", "BOSH %p->connections[%d] = %p, state = %d"
+				                  ", requests = %d\n", conn, i, httpconn,
+				                  httpconn->state, httpconn->requests);
+		}
+	}
+
 	/* Easy solution: Does everyone involved support pipelining? Hooray! Just use
 	 * one TCP connection! */
 	if (conn->pipelining)
@@ -257,7 +282,7 @@
 				conn->connections[0] : NULL;
 
 	/* First loop, look for a connection that's ready */
-	for (i = 0; i < MAX_HTTP_CONNECTIONS; ++i) {
+	for (i = 0; i < NUM_HTTP_CONNECTIONS; ++i) {
 		if (conn->connections[i] &&
 				conn->connections[i]->state == HTTP_CONN_CONNECTED &&
 				conn->connections[i]->requests == 0)
@@ -265,14 +290,14 @@
 	}
 
 	/* Second loop, is something currently connecting? If so, just queue up. */
-	for (i = 0; i < MAX_HTTP_CONNECTIONS; ++i) {
+	for (i = 0; i < NUM_HTTP_CONNECTIONS; ++i) {
 		if (conn->connections[i] &&
 				conn->connections[i]->state == HTTP_CONN_CONNECTING)
 			return NULL;
 	}
 
 	/* Third loop, look for one that's NULL and create a new connection */
-	for (i = 0; i < MAX_HTTP_CONNECTIONS; ++i) {
+	for (i = 0; i < NUM_HTTP_CONNECTIONS; ++i) {
 		if (!conn->connections[i]) {
 			purple_debug_info("jabber", "bosh: Creating and connecting new httpconn\n");
 			conn->connections[i] = jabber_bosh_http_connection_init(conn);
@@ -282,6 +307,8 @@
 		}
 	}
 
+	purple_debug_warning("jabber", "Could not find a HTTP connection!\n");
+
 	/* None available. */
 	return NULL;
 }
@@ -293,9 +320,29 @@
 	PurpleHTTPConnection *chosen;
 	GString *packet = NULL;
 
+	if (type != PACKET_FLUSH && type != PACKET_TERMINATE) {
+		/*
+		 * Unless this is a flush (or session terminate, which needs to be
+		 * sent immediately), queue up the data and start a timer to flush
+		 * the buffer.
+		 */
+		if (data) {
+			int len = data ? strlen(data) : 0;
+			purple_circ_buffer_append(conn->pending, data, len);
+		}
+
+		if (purple_debug_is_verbose())
+			purple_debug_misc("jabber", "bosh: %p has %" G_GSIZE_FORMAT " bytes in "
+			                  "the buffer.\n", conn, conn->pending->bufused);
+		if (conn->send_timer == 0)
+			conn->send_timer = purple_timeout_add_seconds(BUFFER_SEND_IN_SECS,
+					send_timer_cb, conn);
+		return;
+	}
+
 	chosen = find_available_http_connection(conn);
 
-	if (type != PACKET_NORMAL && !chosen) {
+	if (!chosen) {
 		/*
 		 * For non-ordinary traffic, we can't 'buffer' it, so use the
 		 * first connection.
@@ -303,25 +350,16 @@
 		chosen = conn->connections[0];
 
 		if (chosen->state != HTTP_CONN_CONNECTED) {
-			purple_debug_info("jabber", "Unable to find a ready BOSH "
+			purple_debug_warning("jabber", "Unable to find a ready BOSH "
 					"connection. Ignoring send of type 0x%02x.\n", type);
 			return;
 		}
 	}
 
-	if (type == PACKET_NORMAL && (!chosen ||
-	        (conn->max_requests > 0 && conn->requests == conn->max_requests))) {
-		/*
-		 * For normal data, send up to max_requests requests at a time or there is no
-		 * connection ready (likely, we're currently opening a second connection and
-		 * will send these packets when connected).
-		 */
-		if (data) {
-			int len = data ? strlen(data) : 0;
-			purple_circ_buffer_append(conn->pending, data, len);
-		}
-
-		return;
+	/* We're flushing the send buffer, so remove the send timer */
+	if (conn->send_timer != 0) {
+		purple_timeout_remove(conn->send_timer);
+		conn->send_timer = 0;
 	}
 
 	packet = g_string_new(NULL);
@@ -337,7 +375,7 @@
 	                conn->sid,
 	                conn->js->user->domain);
 
-	if (type == PACKET_STREAM_RESTART) {
+	if (conn->needs_restart) {
 		packet = g_string_append(packet, " xmpp:restart='true'/>");
 		/* TODO: Do we need to wait for a response? */
 		conn->needs_restart = FALSE;
@@ -369,7 +407,7 @@
 static void jabber_bosh_connection_stream_restart(PurpleBOSHConnection *conn)
 {
 	conn->needs_restart = TRUE;
-	jabber_bosh_connection_send(conn, PACKET_STREAM_RESTART, NULL);
+	jabber_bosh_connection_send(conn, PACKET_NORMAL, NULL);
 }
 
 static gboolean jabber_bosh_connection_error_check(PurpleBOSHConnection *conn, xmlnode *node) {
@@ -388,12 +426,46 @@
 }
 
 static gboolean
+send_timer_cb(gpointer data)
+{
+	PurpleBOSHConnection *bosh;
+
+	bosh = data;
+	bosh->send_timer = 0;
+
+	jabber_bosh_connection_send(bosh, PACKET_FLUSH, NULL);
+
+	return FALSE;
+}
+
+static gboolean
 bosh_inactivity_cb(gpointer data)
 {
 	PurpleBOSHConnection *bosh = data;
+	bosh->inactivity_timer = 0;
 
-	jabber_bosh_connection_send(bosh, PACKET_NORMAL, NULL);
-	return TRUE;
+	if (bosh->send_timer != 0)
+		purple_timeout_remove(bosh->send_timer);
+
+	/* clears bosh->send_timer */
+	send_timer_cb(bosh);
+
+	return FALSE;
+}
+
+static void
+restart_inactivity_timer(PurpleBOSHConnection *conn)
+{
+	if (conn->inactivity_timer != 0) {
+		purple_timeout_remove(conn->inactivity_timer);
+		conn->inactivity_timer = 0;
+	}
+
+	if (conn->max_inactivity != 0) {
+		conn->inactivity_timer =
+			purple_timeout_add_seconds(conn->max_inactivity - 5 /* rounding */,
+			                           bosh_inactivity_cb, conn);
+	}
 }
 
 static void jabber_bosh_connection_received(PurpleBOSHConnection *conn, xmlnode *node) {
@@ -490,18 +562,18 @@
 
 	if (inactivity) {
 		conn->max_inactivity = atoi(inactivity);
-		if (conn->max_inactivity <= 2) {
+		if (conn->max_inactivity <= 5) {
 			purple_debug_warning("jabber", "Ignoring bogusly small inactivity: %s\n",
 			                     inactivity);
 			conn->max_inactivity = 0;
 		} else {
 			/* TODO: Integrate this with jabber.c keepalive checks... */
+			/* TODO: Can this check fail? It shouldn't */
 			if (conn->inactivity_timer == 0) {
-				purple_debug_misc("jabber", "Starting BOSH inactivity timer for %d secs (compensating for rounding)\n",
+				purple_debug_misc("jabber", "Starting BOSH inactivity timer "
+						"for %d secs (compensating for rounding)\n",
 						conn->max_inactivity - 5);
-				conn->inactivity_timer = purple_timeout_add_seconds(
-						conn->max_inactivity - 5 /* rounding */,
-						bosh_inactivity_cb, conn);
+				restart_inactivity_timer(conn);
 			}
 		}
 	}
@@ -512,7 +584,6 @@
 	/* FIXME: Depending on receiving features might break with some hosts */
 	packet = xmlnode_get_child(node, "features");
 	conn->state = BOSH_CONN_ONLINE;
-	conn->js->use_bosh = TRUE;
 	conn->receive_cb = auth_response_cb;
 	jabber_stream_features_parse(conn->js, packet);
 }
@@ -578,10 +649,14 @@
 {
 	/* Indicate we're ready and reset some variables */
 	conn->state = HTTP_CONN_CONNECTED;
+	if (conn->requests != 0)
+		purple_debug_error("jabber", "bosh: httpconn %p has %d requests, != 0\n",
+		                   conn, conn->requests);
+
 	conn->requests = 0;
-	if (conn->buf) {
-		g_string_free(conn->buf, TRUE);
-		conn->buf = NULL;
+	if (conn->read_buf) {
+		g_string_free(conn->read_buf, TRUE);
+		conn->read_buf = NULL;
 	}
 	conn->headers_done = FALSE;
 	conn->handled_len = conn->body_len = 0;
@@ -592,22 +667,12 @@
 		purple_debug_info("jabber", "BOSH session already exists. Trying to reuse it.\n");
 		if (conn->bosh->requests == 0 || conn->bosh->pending->bufused > 0) {
 			/* Send the pending data */
-			jabber_bosh_connection_send(conn->bosh, PACKET_NORMAL, NULL);
+			jabber_bosh_connection_send(conn->bosh, PACKET_FLUSH, NULL);
 		}
-#if 0
-		conn->bosh->receive_cb = jabber_bosh_connection_received;
-		if (conn->bosh->connect_cb)
-			conn->bosh->connect_cb(conn->bosh);
-#endif
 	} else
 		jabber_bosh_connection_boot(conn->bosh);
 }
 
-void jabber_bosh_connection_refresh(PurpleBOSHConnection *conn)
-{
-	jabber_bosh_connection_send(conn, PACKET_NORMAL, NULL);
-}
-
 static void http_connection_disconnected(PurpleHTTPConnection *conn)
 {
 	/*
@@ -633,9 +698,21 @@
 		conn->writeh = 0;
 	}
 
-	if (conn->bosh->pipelining)
+	if (conn->requests > 0 && conn->read_buf->len == 0) {
+		purple_debug_error("jabber", "bosh: Adjusting BOSHconn requests (%d) to %d\n",
+		                   conn->bosh->requests, conn->bosh->requests - conn->requests);
+		conn->bosh->requests -= conn->requests;
+		conn->requests = 0;
+	}
+
+	if (conn->bosh->pipelining) {
 		/* Hmmmm, fall back to multiple connections */
 		conn->bosh->pipelining = FALSE;
+		if (conn->bosh->connections[1] == NULL) {
+			conn->bosh->connections[1] = jabber_bosh_http_connection_init(conn->bosh);
+			http_connection_connect(conn->bosh->connections[1]);
+		}
+	}
 
 	if (++conn->bosh->failed_connections == MAX_FAILED_CONNECTIONS) {
 		purple_connection_error_reason(conn->bosh->js->gc,
@@ -661,7 +738,7 @@
 {
 	const char *cursor;
 
-	cursor = conn->buf->str + conn->handled_len;
+	cursor = conn->read_buf->str + conn->handled_len;
 
 	if (!conn->headers_done) {
 		const char *content_length = purple_strcasestr(cursor, "\r\nContent-Length");
@@ -690,26 +767,26 @@
 
 		if (end_of_headers) {
 			conn->headers_done = TRUE;
-			conn->handled_len = end_of_headers - conn->buf->str + 4;
+			conn->handled_len = end_of_headers - conn->read_buf->str + 4;
 			cursor = end_of_headers + 4;
 		} else {
-			conn->handled_len = conn->buf->len;
+			conn->handled_len = conn->read_buf->len;
 			return;
 		}
 	}
 
 	/* Have we handled everything in the buffer? */
-	if (conn->handled_len >= conn->buf->len)
+	if (conn->handled_len >= conn->read_buf->len)
 		return;
 
 	/* Have we read all that the Content-Length promised us? */
-	if (conn->buf->len - conn->handled_len < conn->body_len)
+	if (conn->read_buf->len - conn->handled_len < conn->body_len)
 		return;
 
 	--conn->requests;
 	--conn->bosh->requests;
 
-	http_received_cb(conn->buf->str + conn->handled_len, conn->body_len,
+	http_received_cb(conn->read_buf->str + conn->handled_len, conn->body_len,
 	                 conn->bosh);
 
 	if (conn->bosh->state == BOSH_CONN_ONLINE &&
@@ -718,8 +795,8 @@
 		jabber_bosh_connection_send(conn->bosh, PACKET_NORMAL, NULL);
 	}
 
-	g_string_free(conn->buf, TRUE);
-	conn->buf = NULL;
+	g_string_free(conn->read_buf, TRUE);
+	conn->read_buf = NULL;
 	conn->headers_done = FALSE;
 	conn->handled_len = conn->body_len = 0;
 }
@@ -734,8 +811,8 @@
 	char buffer[1025];
 	int cnt, count = 0;
 
-	if (!conn->buf)
-		conn->buf = g_string_new(NULL);
+	if (!conn->read_buf)
+		conn->read_buf = g_string_new(NULL);
 
 	do {
 		if (conn->psc)
@@ -745,7 +822,7 @@
 
 		if (cnt > 0) {
 			count += cnt;
-			g_string_append_len(conn->buf, buffer, cnt);
+			g_string_append_len(conn->read_buf, buffer, cnt);
 		}
 	} while (cnt > 0);
 
@@ -765,7 +842,7 @@
 		/* Process what we do have */
 	}
 
-	if (conn->buf->len > 0)
+	if (conn->read_buf->len > 0)
 		jabber_bosh_http_connection_process(conn);
 }
 
@@ -879,7 +956,7 @@
 {
 	PurpleHTTPConnection *conn = data;
 	int ret;
-	int writelen = purple_circ_buffer_get_max_read(conn->write_buffer);
+	int writelen = purple_circ_buffer_get_max_read(conn->write_buf);
 
 	if (writelen == 0) {
 		purple_input_remove(conn->writeh);
@@ -887,7 +964,7 @@
 		return;
 	}
 
-	ret = http_connection_do_send(conn, conn->write_buffer->outptr, writelen);
+	ret = http_connection_do_send(conn, conn->write_buf->outptr, writelen);
 
 	if (ret < 0 && errno == EAGAIN)
 		return;
@@ -906,7 +983,7 @@
 		return;
 	}
 
-	purple_circ_buffer_mark_read(conn->write_buffer, ret);
+	purple_circ_buffer_mark_read(conn->write_buf, ret);
 }
 
 static void
@@ -916,6 +993,9 @@
 	int ret;
 	size_t len;
 
+	/* Sending something to the server, restart the inactivity timer */
+	restart_inactivity_timer(conn->bosh);
+
 	data = g_strdup_printf("POST %s HTTP/1.1\r\n"
 	                       "Host: %s\r\n"
 	                       "User-Agent: %s\r\n"
@@ -930,6 +1010,10 @@
 	++conn->requests;
 	++conn->bosh->requests;
 
+	if (purple_debug_is_unsafe() && purple_debug_is_verbose())
+		/* Will contain passwords for SASL PLAIN and is verbose */
+		purple_debug_misc("jabber", "BOSH: Sending %s\n", data);
+
 	if (conn->writeh == 0)
 		ret = http_connection_do_send(conn, data, len);
 	else {
@@ -956,7 +1040,7 @@
 		if (conn->writeh == 0)
 			conn->writeh = purple_input_add(conn->psc ? conn->psc->fd : conn->fd,
 					PURPLE_INPUT_WRITE, http_connection_send_cb, conn);
-		purple_circ_buffer_append(conn->write_buffer, data + ret, len - ret);
+		purple_circ_buffer_append(conn->write_buf, data + ret, len - ret);
 	}
 }
 
--- a/libpurple/protocols/jabber/bosh.h	Tue Jul 28 20:51:56 2009 +0000
+++ b/libpurple/protocols/jabber/bosh.h	Thu Aug 13 17:15:06 2009 +0000
@@ -37,5 +37,4 @@
 void jabber_bosh_connection_connect(PurpleBOSHConnection *conn);
 void jabber_bosh_connection_close(PurpleBOSHConnection *conn);
 void jabber_bosh_connection_send_raw(PurpleBOSHConnection *conn, const char *data);
-void jabber_bosh_connection_refresh(PurpleBOSHConnection *conn);
 #endif /* PURPLE_JABBER_BOSH_H_ */
--- a/libpurple/protocols/jabber/buddy.c	Tue Jul 28 20:51:56 2009 +0000
+++ b/libpurple/protocols/jabber/buddy.c	Thu Aug 13 17:15:06 2009 +0000
@@ -463,6 +463,11 @@
 	if(!js->vcard_fetched)
 		return;
 
+	if (js->vcard_timer) {
+		purple_timeout_remove(js->vcard_timer);
+		js->vcard_timer = 0;
+	}
+
 	g_free(js->avatar_hash);
 	js->avatar_hash = NULL;
 
@@ -508,6 +513,7 @@
 
 		xmlnode_insert_data(binval, enc, -1);
 		g_free(enc);
+		purple_imgstore_unref(img);
 	} else if (vc_node) {
 		xmlnode *photo;
 		/* TODO: Remove all PHOTO children? (see above note) */
@@ -854,12 +860,26 @@
 	}
 }
 
+static gboolean
+set_own_vcard_cb(gpointer data)
+{
+	JabberStream *js = data;
+	PurpleAccount *account = purple_connection_get_account(js->gc);
+
+	js->vcard_timer = 0;
+
+	jabber_set_info(js->gc, purple_account_get_user_info(account));
+
+	return FALSE;
+}
+
 static void jabber_vcard_save_mine(JabberStream *js, const char *from,
                                    JabberIqType type, const char *id,
                                    xmlnode *packet, gpointer data)
 {
 	xmlnode *vcard, *photo, *binval;
 	char *txt, *vcard_hash = NULL;
+	PurpleAccount *account;
 
 	if (type == JABBER_IQ_ERROR) {
 		xmlnode *error;
@@ -870,12 +890,13 @@
 			return;
 	}
 
+	account = purple_connection_get_account(js->gc);
+
 	if((vcard = xmlnode_get_child(packet, "vCard")) ||
 			(vcard = xmlnode_get_child_with_namespace(packet, "query", "vcard-temp")))
 	{
 		txt = xmlnode_to_str(vcard, NULL);
-		purple_account_set_user_info(purple_connection_get_account(js->gc), txt);
-
+		purple_account_set_user_info(account, txt);
 		g_free(txt);
 	} else {
 		/* if we have no vCard, then lets not overwrite what we might have locally */
@@ -898,8 +919,17 @@
 
 	/* Republish our vcard if the photo is different than the server's */
 	if (!purple_strequal(vcard_hash, js->initial_avatar_hash)) {
-		PurpleAccount *account = purple_connection_get_account(js->gc);
-		jabber_set_info(js->gc, purple_account_get_user_info(account));
+		/*
+		 * Google Talk has developed the behavior that it will not accept
+		 * a vcard set in the first 10 seconds (or so) of the connection;
+		 * it returns an error (namespaces trimmed):
+		 * <error code="500" type="wait"><internal-server-error/></error>.
+		 */
+		if (js->googletalk)
+			js->vcard_timer = purple_timeout_add_seconds(10, set_own_vcard_cb,
+			                                             js);
+		else
+			jabber_set_info(js->gc, purple_account_get_user_info(account));
 	} else if (js->initial_avatar_hash) {
 		/* Our photo is in the vcard, so advertise vcard-temp updates */
 		js->avatar_hash = g_strdup(js->initial_avatar_hash);
@@ -1018,7 +1048,12 @@
 
 					if(!strcmp(child2->name, "POBOX")) {
 						purple_notify_user_info_add_pair(user_info, _("P.O. Box"), text2);
-					} else if(!strcmp(child2->name, "EXTADR")) {
+					} else if (g_str_equal(child2->name, "EXTADD") || g_str_equal(child2->name, "EXTADR")) {
+						/*
+						 * EXTADD is correct, EXTADR is generated by other
+						 * clients. The next time someone reads this, remove
+						 * EXTADR.
+						 */
 						purple_notify_user_info_add_pair(user_info, _("Extended Address"), text2);
 					} else if(!strcmp(child2->name, "STREET")) {
 						purple_notify_user_info_add_pair(user_info, _("Street Address"), text2);
@@ -1159,6 +1194,22 @@
 	g_free(jbri);
 }
 
+static guint jbir_hash(gconstpointer v)
+{
+	if (v)
+		return g_str_hash(v);
+	else
+		return 0;
+}
+
+static gboolean jbir_equal(gconstpointer v1, gconstpointer v2)
+{
+	const gchar *resource_1 = v1;
+	const gchar *resource_2 = v2;
+
+	return purple_strequal(resource_1, resource_2);
+}
+
 static void jabber_version_parse(JabberStream *js, const char *from,
                                  JabberIqType type, const char *id,
                                  xmlnode *packet, gpointer data)
@@ -1430,9 +1481,7 @@
 	char *full_jid = NULL;
 	const char *to;
 
-	g_return_if_fail(jbr->name != NULL);
-
-	if (is_bare_jid) {
+	if (is_bare_jid && jbr->name) {
 		full_jid = g_strdup_printf("%s/%s", jid, jbr->name);
 		to = full_jid;
 	} else
@@ -1501,7 +1550,7 @@
 	jbi->jid = g_strdup(jid);
 	jbi->js = js;
 	jbi->jb = jb;
-	jbi->resources = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, jabber_buddy_info_resource_free);
+	jbi->resources = g_hash_table_new_full(jbir_hash, jbir_equal, g_free, jabber_buddy_info_resource_free);
 	jbi->user_info = purple_notify_user_info_new();
 
 	iq = jabber_iq_new(js, JABBER_IQ_GET);
@@ -1731,9 +1780,7 @@
 	if(!jb)
 		return m;
 
-	/* XXX: fix the NOT ME below */
-
-	if(js->protocol_version == JABBER_PROTO_0_9 /* && NOT ME */) {
+	if (js->protocol_version == JABBER_PROTO_0_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),
@@ -1746,7 +1793,7 @@
 		m = g_list_append(m, act);
 	}
 
-	if(jb->subscription & JABBER_SUB_FROM /* && NOT ME */) {
+	if(jb->subscription & JABBER_SUB_FROM && jb != js->user_jb) {
 		act = purple_menu_action_new(_("Cancel Presence Notification"),
 		                           PURPLE_CALLBACK(jabber_buddy_cancel_presence_notification),
 		                           NULL, NULL);
@@ -1759,7 +1806,7 @@
 		                           NULL, NULL);
 		m = g_list_append(m, act);
 
-	} else /* if(NOT ME) */{
+	} else if (jb != js->user_jb) {
 
 		/* shouldn't this just happen automatically when the buddy is
 		   removed? */
--- a/libpurple/protocols/jabber/buddy.h	Tue Jul 28 20:51:56 2009 +0000
+++ b/libpurple/protocols/jabber/buddy.h	Thu Aug 13 17:15:06 2009 +0000
@@ -33,10 +33,12 @@
 	JABBER_BUDDY_STATE_DND
 } JabberBuddyState;
 
+typedef struct _JabberBuddy JabberBuddy;
+
 #include "jabber.h"
 #include "caps.h"
 
-typedef struct _JabberBuddy {
+struct _JabberBuddy {
 	GList *resources;
 	char *error_msg;
 	enum {
@@ -52,7 +54,7 @@
 		JABBER_SUB_BOTH    = (JABBER_SUB_TO | JABBER_SUB_FROM),
 		JABBER_SUB_REMOVE  = 1 << 4
 	} subscription;
-} JabberBuddy;
+};
 
 typedef struct _JabberAdHocCommands {
 	char *jid;
--- a/libpurple/protocols/jabber/chat.c	Tue Jul 28 20:51:56 2009 +0000
+++ b/libpurple/protocols/jabber/chat.c	Thu Aug 13 17:15:06 2009 +0000
@@ -605,7 +605,7 @@
 	jm->to = g_strdup_printf("%s@%s", chat->room, chat->server);
 
 	if (topic && *topic)
-		jm->subject = purple_markup_strip_html(topic);
+		jm->subject = g_strdup(topic);
 	else
 		jm->subject = g_strdup("");
 
@@ -750,6 +750,7 @@
 
 	if(!server || !*server) {
 		purple_notify_error(js->gc, _("Invalid Server"), _("Invalid Server"), NULL);
+		purple_roomlist_set_in_progress(js->roomlist, FALSE);
 		return;
 	}
 
--- a/libpurple/protocols/jabber/disco.c	Tue Jul 28 20:51:56 2009 +0000
+++ b/libpurple/protocols/jabber/disco.c	Thu Aug 13 17:15:06 2009 +0000
@@ -85,11 +85,11 @@
 
 	/* TODO: When we support zeroconf proxies, fix this to handle them */
 	if (!(sh->jid && sh->host && sh->port > 0)) {
+		js->bs_proxies = g_list_remove(js->bs_proxies, sh);
 		g_free(sh->jid);
 		g_free(sh->host);
 		g_free(sh->zeroconf);
 		g_free(sh);
-		js->bs_proxies = g_list_remove(js->bs_proxies, sh);
 	}
 }
 
@@ -98,10 +98,6 @@
                              JabberIqType type, const char *id,
                              xmlnode *in_query)
 {
-
-	if(!from)
-		return;
-
 	if(type == JABBER_IQ_GET) {
 		xmlnode *query, *identity, *feature;
 		JabberIq *iq;
@@ -116,7 +112,8 @@
 
 		jabber_iq_set_id(iq, id);
 
-		xmlnode_set_attrib(iq->node, "to", from);
+		if (from)
+			xmlnode_set_attrib(iq->node, "to", from);
 		query = xmlnode_get_child(iq->node, "query");
 
 		if(node)
@@ -166,6 +163,18 @@
 			 */
 			xmlnode *feature = xmlnode_new_child(query, "feature");
 			xmlnode_set_attrib(feature, "var", "http://www.google.com/xmpp/protocol/video/v1");
+		} else if (g_str_equal(node, CAPS0115_NODE "#" "camera-v1")) {
+			/*
+			 * HUGE HACK! We advertise this ext (see jabber_presence_create_js
+			 * where we add <c/> to the <presence/>) for the Google Talk
+			 * clients that don't actually check disco#info features.
+			 *
+			 * This specific feature is redundant but is what
+			 * node='http://mail.google.com/xmpp/client/caps', ver='1.1'
+			 * advertises as 'camera-v1'.
+			 */
+			xmlnode *feature = xmlnode_new_child(query, "feature");
+			xmlnode_set_attrib(feature, "var", "http://www.google.com/xmpp/protocol/camera/v1");
 #endif
 		} else {
 			xmlnode *error, *inf;
@@ -196,7 +205,8 @@
 		xmlnode_set_namespace(bad_request, "urn:ietf:params:xml:ns:xmpp-stanzas");
 
 		jabber_iq_set_id(iq, id);
-		xmlnode_set_attrib(iq->node, "to", from);
+		if (from)
+			xmlnode_set_attrib(iq->node, "to", from);
 
 		jabber_iq_send(iq);
 	}
--- a/libpurple/protocols/jabber/google.c	Tue Jul 28 20:51:56 2009 +0000
+++ b/libpurple/protocols/jabber/google.c	Thu Aug 13 17:15:06 2009 +0000
@@ -430,7 +430,7 @@
 	return (session->media != NULL) ? TRUE : FALSE;
 }
 
-static void
+static gboolean
 google_session_handle_initiate(JabberStream *js, GoogleSession *session, xmlnode *sess, const char *iq_id)
 {
 	JabberIq *result;
@@ -443,7 +443,7 @@
 
 	if (session->state != UNINIT) {
 		purple_debug_error("jabber", "Received initiate for active session.\n");
-		return;
+		return FALSE;
 	}
 
 	desc_element = xmlnode_get_child(sess, "description");
@@ -456,7 +456,7 @@
 	else {
 		purple_debug_error("jabber", "Received initiate with "
 				"invalid namespace %s.\n", xmlns);
-		return;
+		return FALSE;
 	}
 
 	session->media = purple_media_manager_create_media(
@@ -480,7 +480,7 @@
 				PURPLE_MEDIA_INFO_HANGUP, NULL, NULL, TRUE);
 		google_session_send_terminate(session);
 		g_free(params);
-		return;
+		return FALSE;
 	}
 
 	g_free(params);
@@ -551,6 +551,8 @@
 	jabber_iq_set_id(result, iq_id);
 	xmlnode_set_attrib(result->node, "to", session->remote_jid);
 	jabber_iq_send(result);
+
+	return TRUE;
 }
 
 static void
@@ -776,7 +778,8 @@
 	session->js = js;
 	session->remote_jid = g_strdup(session->id.initiator);
 
-	google_session_parse_iq(js, session, session_node, iq_id);
+	if (!google_session_handle_initiate(js, session, session_node, iq_id))
+		google_session_destroy(session);
 }
 #endif /* USE_VV */
 
--- a/libpurple/protocols/jabber/iq.c	Tue Jul 28 20:51:56 2009 +0000
+++ b/libpurple/protocols/jabber/iq.c	Thu Aug 13 17:15:06 2009 +0000
@@ -282,11 +282,6 @@
 	id = xmlnode_get_attrib(packet, "id");
 	iq_type = xmlnode_get_attrib(packet, "type");
 
-	signal_return = GPOINTER_TO_INT(purple_signal_emit_return_1(jabber_plugin,
-			"jabber-receiving-iq", js->gc, iq_type, id, from, packet));
-	if (signal_return)
-		return;
-
 	/*
 	 * child will be either the first tag child or NULL if there is no child.
 	 * Historically, we used just the 'query' subchild, but newer XEPs use
@@ -345,6 +340,11 @@
 		return;
 	}
 
+	signal_return = GPOINTER_TO_INT(purple_signal_emit_return_1(jabber_plugin,
+			"jabber-receiving-iq", js->gc, iq_type, id, from, packet));
+	if (signal_return)
+		return;
+
 	/* First, lets see if a special callback got registered */
 	if(type == JABBER_IQ_RESULT || type == JABBER_IQ_ERROR) {
 		if((jcd = g_hash_table_lookup(js->iq_callbacks, id))) {
--- a/libpurple/protocols/jabber/jabber.c	Tue Jul 28 20:51:56 2009 +0000
+++ b/libpurple/protocols/jabber/jabber.c	Thu Aug 13 17:15:06 2009 +0000
@@ -132,15 +132,19 @@
 		xmlnode *jid;
 		char *full_jid;
 		if((jid = xmlnode_get_child(bind, "jid")) && (full_jid = xmlnode_get_data(jid))) {
-			JabberBuddy *my_jb = NULL;
 			jabber_id_free(js->user);
-			if(!(js->user = jabber_id_new(full_jid))) {
+
+			js->user = jabber_id_new(full_jid);
+			if (js->user == NULL) {
 				purple_connection_error_reason(js->gc,
 					PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
 					_("Invalid response from server"));
+				g_free(full_jid);
+				return;
 			}
-			if((my_jb = jabber_buddy_find(js, full_jid, TRUE)))
-				my_jb->subscription |= JABBER_SUB_BOTH;
+
+			js->user_jb = jabber_buddy_find(js, full_jid, TRUE);
+			js->user_jb->subscription |= JABBER_SUB_BOTH;
 
 			purple_connection_set_display_name(js->gc, full_jid);
 
@@ -441,7 +445,7 @@
 	if (len == -1)
 		len = strlen(data);
 
-	if (js->use_bosh)
+	if (js->bosh)
 		jabber_bosh_connection_send_raw(js->bosh, data);
 	else
 		do_jabber_send_raw(js, data, len);
@@ -465,7 +469,7 @@
 		return;
 
 	js = purple_connection_get_protocol_data(pc);
-	if (js->use_bosh)
+	if (js->bosh)
 		if (g_str_equal((*packet)->name, "message") ||
 				g_str_equal((*packet)->name, "iq") ||
 				g_str_equal((*packet)->name, "presence"))
@@ -632,7 +636,6 @@
 		if (!strcmp(token[0], "_xmpp-client-xbosh")) {
 			purple_debug_info("jabber","Found alternative connection method using %s at %s.\n", token[0], token[1]);
 			js->bosh = jabber_bosh_connection_init(js, token[1]);
-			js->use_bosh = TRUE;
 			g_strfreev(token);
 			break;
 		}
@@ -776,7 +779,6 @@
 {
 	PurpleConnection *gc = purple_account_get_connection(account);
 	JabberStream *js;
-	JabberBuddy *my_jb;
 	PurplePresence *presence;
 	gchar *user;
 	gchar *slash;
@@ -811,9 +813,9 @@
 	js->buddies = g_hash_table_new_full(g_str_hash, g_str_equal,
 			g_free, (GDestroyNotify)jabber_buddy_free);
 
-	my_jb = jabber_buddy_find(js, user, TRUE);
+	js->user_jb = jabber_buddy_find(js, user, TRUE);
 	g_free(user);
-	if (!my_jb) {
+	if (!js->user_jb) {
 		/* This basically *can't* fail, but for good measure... */
 		purple_connection_error_reason(gc,
 			PURPLE_CONNECTION_ERROR_INVALID_SETTINGS,
@@ -822,7 +824,7 @@
 		g_return_val_if_reached(NULL);
 	}
 
-	my_jb->subscription |= JABBER_SUB_BOTH;
+	js->user_jb->subscription |= JABBER_SUB_BOTH;
 
 	js->iq_callbacks = g_hash_table_new_full(g_str_hash, g_str_equal,
 			g_free, g_free);
@@ -865,7 +867,6 @@
 	 * attached to that choice, though.
 	 */
 	if (*bosh_url) {
-		js->use_bosh = TRUE;
 		js->bosh = jabber_bosh_connection_init(js, bosh_url);
 		if (js->bosh)
 			jabber_bosh_connection_connect(js->bosh);
@@ -1445,7 +1446,7 @@
 	 * on some SSL backends.
 	 */
 	if (!gc->disconnect_timeout) {
-		if (js->use_bosh)
+		if (js->bosh)
 			jabber_bosh_connection_close(js->bosh);
 		else if ((js->gsc && js->gsc->fd > 0) || js->fd > 0)
 			jabber_send_raw(js, "</stream:stream>", -1);
@@ -1542,6 +1543,9 @@
 	g_free(js->old_track);
 	g_free(js->expected_rspauth);
 
+	if (js->vcard_timer != 0)
+		purple_timeout_remove(js->vcard_timer);
+
 	if (js->keepalive_timeout != 0)
 		purple_timeout_remove(js->keepalive_timeout);
 
@@ -3467,6 +3471,8 @@
 #ifdef USE_VV
 	jabber_add_feature("http://www.google.com/xmpp/protocol/session", jabber_audio_enabled);
 	jabber_add_feature("http://www.google.com/xmpp/protocol/voice/v1", jabber_audio_enabled);
+	jabber_add_feature("http://www.google.com/xmpp/protocol/video/v1", jabber_video_enabled);
+	jabber_add_feature("http://www.google.com/xmpp/protocol/camera/v1", jabber_video_enabled);
 	jabber_add_feature(JINGLE_APP_RTP_SUPPORT_AUDIO, jabber_audio_enabled);
 	jabber_add_feature(JINGLE_APP_RTP_SUPPORT_VIDEO, jabber_video_enabled);
 	jabber_add_feature(JINGLE_TRANSPORT_ICEUDP, 0);
--- a/libpurple/protocols/jabber/jabber.h	Tue Jul 28 20:51:56 2009 +0000
+++ b/libpurple/protocols/jabber/jabber.h	Thu Aug 13 17:15:06 2009 +0000
@@ -164,6 +164,8 @@
 	time_t old_idle;
 
 	JabberID *user;
+	JabberBuddy *user_jb;
+
 	PurpleConnection *gc;
 	PurpleSslConnection *gsc;
 
@@ -214,6 +216,8 @@
 	void *unregistration_user_data;
 
 	gboolean vcard_fetched;
+	/* Timer at login to push updated avatar */
+	guint vcard_timer;
 
 	/* Entity Capabilities hash */
 	char *caps_hash;
@@ -251,7 +255,6 @@
 	guint max_srv_rec_idx;
 
 	/* BOSH stuff */
-	gboolean use_bosh;
 	PurpleBOSHConnection *bosh;
 
 	/**
--- a/libpurple/protocols/jabber/jingle/rtp.c	Tue Jul 28 20:51:56 2009 +0000
+++ b/libpurple/protocols/jabber/jingle/rtp.c	Thu Aug 13 17:15:06 2009 +0000
@@ -355,14 +355,6 @@
 static void jingle_rtp_ready(JingleSession *session);
 
 static void
-jingle_rtp_accepted_cb(PurpleMedia *media, gchar *sid, gchar *name,
-		JingleSession *session)
-{
-	purple_debug_info("jingle-rtp", "jingle_rtp_accepted_cb\n");
-	jingle_rtp_ready(session);
-}
-
-static void
 jingle_rtp_candidates_prepared_cb(PurpleMedia *media,
 		gchar *sid, gchar *name, JingleSession *session)
 {
@@ -480,6 +472,9 @@
 		jabber_iq_send(jingle_session_terminate_packet(
 				session, "decline"));
 		g_object_unref(session);
+	} else if (type == PURPLE_MEDIA_INFO_ACCEPT &&
+			jingle_session_is_initiator(session) == FALSE) {
+		jingle_rtp_ready(session);
 	}
 }
 
@@ -504,8 +499,6 @@
 		}
 
 		g_signal_handlers_disconnect_by_func(G_OBJECT(media),
-				G_CALLBACK(jingle_rtp_accepted_cb), session);
-		g_signal_handlers_disconnect_by_func(G_OBJECT(media),
 				G_CALLBACK(jingle_rtp_candidates_prepared_cb),
 				session);
 		g_signal_handlers_disconnect_by_func(G_OBJECT(media),
@@ -539,9 +532,6 @@
 	purple_media_set_prpl_data(media, session);
 
 	/* connect callbacks */
-	if (jingle_session_is_initiator(session) == FALSE)
-		g_signal_connect(G_OBJECT(media), "accepted",
-				G_CALLBACK(jingle_rtp_accepted_cb), session);
 	g_signal_connect(G_OBJECT(media), "candidates-prepared",
 				 G_CALLBACK(jingle_rtp_candidates_prepared_cb), session);
 	g_signal_connect(G_OBJECT(media), "codecs-changed",
@@ -599,8 +589,8 @@
 	if (!strcmp(senders, "both"))
 		type = is_audio == TRUE ? PURPLE_MEDIA_AUDIO
 				: PURPLE_MEDIA_VIDEO;
-	else if (!strcmp(senders, "initiator")
-			&& jingle_session_is_initiator(session))
+	else if ((strcmp(senders, "initiator") == 0) ==
+			jingle_session_is_initiator(session))
 		type = is_audio == TRUE ? PURPLE_MEDIA_SEND_AUDIO
 				: PURPLE_MEDIA_SEND_VIDEO;
 	else
--- a/libpurple/protocols/jabber/libxmpp.c	Tue Jul 28 20:51:56 2009 +0000
+++ b/libpurple/protocols/jabber/libxmpp.c	Thu Aug 13 17:15:06 2009 +0000
@@ -28,6 +28,7 @@
 #include "internal.h"
 
 #include "accountopt.h"
+#include "core.h"
 #include "debug.h"
 #include "version.h"
 
@@ -47,6 +48,8 @@
 #include "data.h"
 #include "ibb.h"
 
+static PurplePlugin *my_protocol = NULL;
+
 static PurplePluginProtocolInfo prpl_info =
 {
 	OPT_PROTO_CHAT_TOPIC | OPT_PROTO_UNIQUE_CHATNAME | OPT_PROTO_MAIL_CHECK |
@@ -266,6 +269,70 @@
 	NULL
 };
 
+static PurpleAccount *find_acct(const char *prpl, const char *acct_id)
+{
+	PurpleAccount *acct = NULL;
+
+	/* If we have a specific acct, use it */
+	if (acct_id) {
+		acct = purple_accounts_find(acct_id, prpl);
+		if (acct && !purple_account_is_connected(acct))
+			acct = NULL;
+	} else { /* Otherwise find an active account for the protocol */
+		GList *l = purple_accounts_get_all();
+		while (l) {
+			if (!strcmp(prpl, purple_account_get_protocol_id(l->data))
+					&& purple_account_is_connected(l->data)) {
+				acct = l->data;
+				break;
+			}
+			l = l->next;
+		}
+	}
+
+	return acct;
+}
+
+static gboolean xmpp_uri_handler(const char *proto, const char *user, GHashTable *params)
+{
+	char *acct_id = g_hash_table_lookup(params, "account");
+	PurpleAccount *acct;
+
+	if (g_ascii_strcasecmp(proto, "xmpp"))
+		return FALSE;
+
+	acct = find_acct(purple_plugin_get_id(my_protocol), acct_id);
+
+	if (!acct)
+		return FALSE;
+
+	/* xmpp:romeo@montague.net?message;subject=Test%20Message;body=Here%27s%20a%20test%20message */
+	if (g_hash_table_lookup_extended(params, "message", NULL, NULL)) {
+		char *body = g_hash_table_lookup(params, "body");
+		if (user && *user) {
+			PurpleConversation *conv =
+					purple_conversation_new(PURPLE_CONV_TYPE_IM, acct, user);
+			purple_conversation_present(conv);
+			if (body && *body)
+				purple_conv_send_confirm(conv, body);
+		}
+	} else if (g_hash_table_lookup_extended(params, "roster", NULL, NULL)) {
+		char *name = g_hash_table_lookup(params, "name");
+		if (user && *user)
+			purple_blist_request_add_buddy(acct, user, NULL, name);
+	} else if (g_hash_table_lookup_extended(params, "join", NULL, NULL)) {
+		PurpleConnection *gc = purple_account_get_connection(acct);
+		if (user && *user) {
+			GHashTable *params = jabber_chat_info_defaults(gc, user);
+			jabber_chat_join(gc, params);
+		}
+		return TRUE;
+	}
+
+	return FALSE;
+}
+
+
 static void
 init_plugin(PurplePlugin *plugin)
 {
@@ -331,6 +398,7 @@
 	prpl_info.protocol_options = g_list_append(prpl_info.protocol_options,
 		option);
 
+	my_protocol = plugin;
 	jabber_init_plugin(plugin);
 
 	purple_prefs_remove("/plugins/prpl/jabber");
@@ -366,6 +434,9 @@
 
 	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/presence.c	Tue Jul 28 20:51:56 2009 +0000
+++ b/libpurple/protocols/jabber/presence.c	Thu Aug 13 17:15:06 2009 +0000
@@ -324,11 +324,11 @@
 	video_enabled = jabber_video_enabled(js, NULL /* unused */);
 
 	if (audio_enabled && video_enabled)
-		xmlnode_set_attrib(c, "ext", "voice-v1 video-v1");
+		xmlnode_set_attrib(c, "ext", "voice-v1 camera-v1 video-v1");
 	else if (audio_enabled)
 		xmlnode_set_attrib(c, "ext", "voice-v1");
 	else if (video_enabled)
-		xmlnode_set_attrib(c, "ext", "video-v1");
+		xmlnode_set_attrib(c, "ext", "camera-v1 video-v1");
 #endif
 
 	return presence;
@@ -698,6 +698,7 @@
 			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
@@ -718,10 +719,15 @@
 				xmlnode *status_node;
 				xmlnode *item_node;
 
-				status_node = xmlnode_get_child(x, "status");
-				if (status_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 (purple_strequal(code, "201")) {
+					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 =
@@ -737,7 +743,7 @@
 										_("_Configure Room"), G_CALLBACK(jabber_chat_request_room_configure),
 										_("_Accept Defaults"), G_CALLBACK(jabber_chat_create_instant_room));
 						}
-					} else if (purple_strequal(code, "210")) {
+					} 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);
@@ -814,7 +820,6 @@
 					"http://jabber.org/protocol/muc#user");
 			if (chat->muc && x) {
 				const char *nick;
-				const char *code = NULL;
 				const char *item_jid = NULL;
 				const char *to;
 				xmlnode *stat;
@@ -824,13 +829,16 @@
 				if (item)
 					item_jid = xmlnode_get_attrib(item, "jid");
 
-				stat = xmlnode_get_child(x, "status");
+				for (stat = xmlnode_get_child(x, "status"); stat;
+						stat = xmlnode_get_next_twin(stat)) {
+					const char *code = xmlnode_get_attrib(stat, "code");
 
-				if (stat)
-					code = xmlnode_get_attrib(stat, "code");
+					if (!code)
+						continue;
 
-				if (code) {
-					if(!strcmp(code, "301")) {
+					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"))) {
@@ -839,10 +847,11 @@
 							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);
-						/* TODO: Enable this when this is in a for-loop...
-						break; */
+						continue;
 					} else if(!strcmp(code, "307")) {
 						/* Someone was kicked from the room */
 						xmlnode *reason = NULL, *actor = NULL;
--- a/libpurple/protocols/jabber/roster.c	Tue Jul 28 20:51:56 2009 +0000
+++ b/libpurple/protocols/jabber/roster.c	Thu Aug 13 17:15:06 2009 +0000
@@ -66,7 +66,7 @@
 }
 
 static void add_purple_buddy_to_groups(JabberStream *js, const char *jid,
-		const char *alias, GSList *groups, const char *own_jid)
+		const char *alias, GSList *groups)
 {
 	GSList *buddies, *l;
 	GSList *pool = NULL;
@@ -162,12 +162,6 @@
 		purple_blist_add_buddy(b, NULL, g, NULL);
 		purple_blist_alias_buddy(b, alias);
 
-		/* If we just learned about ourself, then fake our status,
-		 * because we won't be receiving a normal presence message
-		 * about ourself. */
-		if(!strcmp(purple_buddy_get_name(b), own_jid))
-			jabber_presence_fake_to_self(js, NULL);
-
 		g_free(groups->data);
 		groups = g_slist_delete_link(groups, groups);
 	}
@@ -187,7 +181,6 @@
                          JabberIqType type, const char *id, xmlnode *query)
 {
 	xmlnode *item, *group;
-	gchar *own_jid;
 
 	if (!jabber_is_own_account(js, from)) {
 		purple_debug_warning("jabber", "Received bogon roster push from %s\n",
@@ -197,8 +190,6 @@
 
 	js->currently_parsing_roster_push = TRUE;
 
-	own_jid = g_strdup_printf("%s@%s", js->user->node, js->user->domain);
-
 	for(item = xmlnode_get_child(query, "item"); item; item = xmlnode_get_next_twin(item))
 	{
 		const char *jid, *name, *subscription, *ask;
@@ -216,11 +207,7 @@
 			continue;
 
 		if(subscription) {
-			gboolean me = FALSE;
-
-			me = g_str_equal(own_jid, jabber_normalize(js->gc->account, jid));
-
-			if(me)
+			if (jb == js->user_jb)
 				jb->subscription = JABBER_SUB_BOTH;
 			else if(!strcmp(subscription, "none"))
 				jb->subscription = JABBER_SUB_NONE;
@@ -260,11 +247,12 @@
 				groups = g_slist_prepend(groups, group_name);
 			}
 
-			add_purple_buddy_to_groups(js, jid, name, groups, own_jid);
+			add_purple_buddy_to_groups(js, jid, name, groups);
+			if (jb == js->user_jb)
+				jabber_presence_fake_to_self(js, NULL);
 		}
 	}
 
-	g_free(own_jid);
 	js->currently_parsing_roster_push = FALSE;
 
 	/* if we're just now parsing the roster for the first time,
@@ -348,7 +336,6 @@
 	char *who;
 	JabberBuddy *jb;
 	JabberBuddyResource *jbr;
-	char *own_jid;
 	const char *name;
 
 	/* If we haven't received the roster yet, ignore any adds */
@@ -366,8 +353,7 @@
 
 	jabber_roster_update(js, who, NULL);
 
-	own_jid = g_strdup_printf("%s@%s", js->user->node, js->user->domain);
-	if (g_str_equal(who, own_jid)) {
+	if (jb == js->user_jb) {
 		jabber_presence_fake_to_self(js, NULL);
 	} else if(!jb || !(jb->subscription & JABBER_SUB_TO)) {
 		jabber_presence_subscription_set(js, who, "subscribe");
@@ -377,7 +363,6 @@
 				"priority", jbr->priority, jbr->status ? "message" : NULL, jbr->status, NULL);
 	}
 
-	g_free(own_jid);
 	g_free(who);
 }
 
--- a/libpurple/protocols/jabber/si.c	Tue Jul 28 20:51:56 2009 +0000
+++ b/libpurple/protocols/jabber/si.c	Thu Aug 13 17:15:06 2009 +0000
@@ -68,7 +68,7 @@
 
 	JabberIBBSession *ibb_session;
 	guint ibb_timeout_handle;
-	FILE *fp;
+	PurpleCircBuffer *ibb_buffer;
 } JabberSIXfer;
 
 /* some forward declarations */
@@ -1013,18 +1013,8 @@
 	if (size <= purple_xfer_get_bytes_remaining(xfer)) {
 		purple_debug_info("jabber", "about to write %" G_GSIZE_FORMAT " bytes from IBB stream\n",
 			size);
-		if(!fwrite(data, size, 1, jsx->fp)) {
-			purple_debug_error("jabber", "error writing to file\n");
-			purple_xfer_cancel_remote(xfer);
-			return;
-		}
-		purple_xfer_set_bytes_sent(xfer, purple_xfer_get_bytes_sent(xfer) + size);
-		purple_xfer_update_progress(xfer);
-
-		if (purple_xfer_get_bytes_remaining(xfer) == 0) {
-			purple_xfer_set_completed(xfer, TRUE);
-			purple_xfer_end(xfer);
-		}
+		purple_circ_buffer_append(jsx->ibb_buffer, data, size);
+		purple_xfer_prpl_ready(xfer);
 	} else {
 		/* trying to write past size of file transfers negotiated size,
 		  reject transfer to protect against malicious behaviour */
@@ -1035,6 +1025,25 @@
 
 }
 
+static gssize
+jabber_si_xfer_ibb_read(guchar **out_buffer, PurpleXfer *xfer)
+{
+	JabberSIXfer *jsx = xfer->data;
+	guchar *buffer;
+	gsize size;
+	gsize tmp;
+
+	size = jsx->ibb_buffer->bufused;
+	*out_buffer = buffer = g_malloc(size);
+	while ((tmp = purple_circ_buffer_get_max_read(jsx->ibb_buffer))) {
+		memcpy(buffer, jsx->ibb_buffer->outptr, tmp);
+		buffer += tmp;
+		purple_circ_buffer_mark_read(jsx->ibb_buffer, tmp);
+	}
+
+	return size;
+}
+
 static gboolean
 jabber_si_xfer_ibb_open_cb(JabberStream *js, const char *who, const char *id,
                            xmlnode *open)
@@ -1045,21 +1054,10 @@
 		JabberSIXfer *jsx = (JabberSIXfer *) xfer->data;
 		JabberIBBSession *sess =
 			jabber_ibb_session_create_from_xmlnode(js, who, id, open, xfer);
-		const char *filename;
 
 		jabber_si_bytestreams_ibb_timeout_remove(jsx);
 
 		if (sess) {
-			/* open the file to write to */
-			filename = purple_xfer_get_local_filename(xfer);
-			jsx->fp = g_fopen(filename, "wb");
-			if (jsx->fp == NULL) {
-				purple_debug_error("jabber", "failed to open file %s for writing: %s\n",
-					filename, g_strerror(errno));
-				purple_xfer_cancel_remote(xfer);
-				return FALSE;
-			}
-
 			/* setup callbacks here...*/
 			jabber_ibb_session_set_data_received_callback(sess,
 				jabber_si_xfer_ibb_recv_data_cb);
@@ -1069,9 +1067,14 @@
 				jabber_si_xfer_ibb_error_cb);
 
 			jsx->ibb_session = sess;
+			jsx->ibb_buffer =
+				purple_circ_buffer_new(jabber_ibb_session_get_block_size(sess));
+
+			/* set up read function */
+			purple_xfer_set_read_fnc(xfer, jabber_si_xfer_ibb_read);
 
 			/* start the transfer */
-			purple_xfer_start(xfer, 0, NULL, 0);
+			purple_xfer_start(xfer, -1, NULL, 0);
 			return TRUE;
 		} else {
 			/* failed to create IBB session */
@@ -1087,32 +1090,17 @@
 	}
 }
 
-static void
-jabber_si_xfer_ibb_send_data(JabberIBBSession *sess)
+static gssize
+jabber_si_xfer_ibb_write(const guchar *buffer, size_t len, PurpleXfer *xfer)
 {
-	PurpleXfer *xfer = (PurpleXfer *) jabber_ibb_session_get_user_data(sess);
 	JabberSIXfer *jsx = (JabberSIXfer *) xfer->data;
-	gsize remaining = purple_xfer_get_bytes_remaining(xfer);
-	gsize packet_size = remaining < jabber_ibb_session_get_block_size(sess) ?
-		remaining : jabber_ibb_session_get_block_size(sess);
-	gpointer data = g_malloc(packet_size);
-	int res;
+	JabberIBBSession *sess = jsx->ibb_session;
+	gsize packet_size = len < jabber_ibb_session_get_block_size(sess) ?
+		len : jabber_ibb_session_get_block_size(sess);
 
-	purple_debug_info("jabber", "IBB: about to read %" G_GSIZE_FORMAT " bytes from file %p\n",
-		packet_size, jsx->fp);
-	res = fread(data, packet_size, 1, jsx->fp);
+	jabber_ibb_session_send_data(sess, buffer, packet_size);
 
-	if (res == 1) {
-		jabber_ibb_session_send_data(sess, data, packet_size);
-		purple_xfer_set_bytes_sent(xfer,
-			purple_xfer_get_bytes_sent(xfer) + packet_size);
-		purple_xfer_update_progress(xfer);
-	} else {
-		purple_debug_error("jabber",
-			"jabber_si_xfer_ibb_send_data: error reading from file\n");
-		purple_xfer_cancel_local(xfer);
-	}
-	g_free(data);
+	return packet_size;
 }
 
 static void
@@ -1128,7 +1116,7 @@
 		purple_xfer_end(xfer);
 	} else {
 		/* send more... */
-		jabber_si_xfer_ibb_send_data(sess);
+		purple_xfer_prpl_ready(xfer);
 	}
 }
 
@@ -1136,28 +1124,13 @@
 jabber_si_xfer_ibb_opened_cb(JabberIBBSession *sess)
 {
 	PurpleXfer *xfer = (PurpleXfer *) jabber_ibb_session_get_user_data(sess);
-	JabberSIXfer *jsx = (JabberSIXfer *) xfer->data;
 	JabberStream *js = jabber_ibb_session_get_js(sess);
 	PurpleConnection *gc = js->gc;
 	PurpleAccount *account = purple_connection_get_account(gc);
 
 	if (jabber_ibb_session_get_state(sess) == JABBER_IBB_SESSION_OPENED) {
-		const char *filename = purple_xfer_get_local_filename(xfer);
-		jsx->fp = g_fopen(filename, "rb");
-		if (jsx->fp == NULL) {
-			purple_debug_error("jabber", "Failed to open file %s for reading: %s\n",
-				filename, g_strerror(errno));
-			purple_xfer_error(purple_xfer_get_type(xfer), account,
-				jabber_ibb_session_get_who(sess),
-				_("Failed to open the file"));
-			purple_xfer_cancel_local(xfer);
-			return;
-		}
-
-		purple_xfer_start(xfer, 0, NULL, 0);
-		purple_xfer_set_bytes_sent(xfer, 0);
-		purple_xfer_update_progress(xfer);
-		jabber_si_xfer_ibb_send_data(sess);
+		purple_xfer_start(xfer, -1, NULL, 0);
+		purple_xfer_prpl_ready(xfer);
 	} else {
 		/* error */
 		purple_xfer_error(purple_xfer_get_type(xfer), account,
@@ -1172,8 +1145,6 @@
 {
 	JabberSIXfer *jsx = (JabberSIXfer *) xfer->data;
 
-	purple_xfer_ref(xfer);
-
 	jsx->ibb_session = jabber_ibb_session_create(js, jsx->stream_id,
 		purple_xfer_get_remote_user(xfer), xfer);
 
@@ -1188,6 +1159,11 @@
 		jabber_ibb_session_set_error_callback(jsx->ibb_session,
 			jabber_si_xfer_ibb_error_cb);
 
+		purple_xfer_set_write_fnc(xfer, jabber_si_xfer_ibb_write);
+
+		jsx->ibb_buffer =
+			purple_circ_buffer_new(jabber_ibb_session_get_block_size(jsx->ibb_session));
+
 		/* open the IBB session */
 		jabber_ibb_session_open(jsx->ibb_session);
 
@@ -1359,20 +1335,18 @@
 			jabber_ibb_session_destroy(jsx->ibb_session);
 		}
 
-		if (jsx->fp) {
-			purple_debug_info("jabber",
-				"jabber_si_xfer_free: closing file for IBB transfer\n");
-			fclose(jsx->fp);
+		if (jsx->ibb_buffer) {
+			purple_circ_buffer_destroy(jsx->ibb_buffer);
 		}
 
+		purple_debug_info("jabber", "jabber_si_xfer_free(): freeing jsx %p\n", jsx);
+
 		g_free(jsx->stream_id);
 		g_free(jsx->iq_id);
 		/* XXX: free other stuff */
 		g_free(jsx->rxqueue);
 		g_free(jsx);
 		xfer->data = NULL;
-
-		purple_debug_info("jabber", "jabber_si_xfer_free(): freeing jsx %p\n", jsx);
 	}
 }
 
@@ -1648,7 +1622,6 @@
 		jsx->local_streamhost_fd = -1;
 
 		jsx->ibb_session = NULL;
-		jsx->fp = NULL;
 
 		purple_xfer_set_init_fnc(xfer, jabber_si_xfer_init);
 		purple_xfer_set_cancel_send_fnc(xfer, jabber_si_xfer_cancel_send);
--- a/libpurple/protocols/jabber/useravatar.c	Tue Jul 28 20:51:56 2009 +0000
+++ b/libpurple/protocols/jabber/useravatar.c	Thu Aug 13 17:15:06 2009 +0000
@@ -219,16 +219,18 @@
 	const char *server_hash = NULL;
 
 	if (items && (item = xmlnode_get_child(items, "item")) &&
-	     (metadata = xmlnode_get_child(item, "metadata")) &&
-	     (info = xmlnode_get_child(metadata, "info"))) {
+			(metadata = xmlnode_get_child(item, "metadata")) &&
+			(info = xmlnode_get_child(metadata, "info"))) {
 		server_hash = xmlnode_get_attrib(info, "id");
 	}
 
-	if (items && !metadata)
-		return;
-
-	/* Publish ours if it's different than the server's */
-	if (!purple_strequal(server_hash, js->initial_avatar_hash)) {
+	/*
+	 * If we have an avatar and the server returned an error/malformed data,
+	 * push our avatar. If the server avatar doesn't match the local one, push
+	 * our avatar.
+	 */
+	if (((!items || !metadata) && js->initial_avatar_hash) ||
+			!purple_strequal(server_hash, js->initial_avatar_hash)) {
 		PurpleStoredImage *img = purple_buddy_icons_find_account_icon(account);
 		jabber_avatar_set(js, img);
 		purple_imgstore_unref(img);
--- a/libpurple/protocols/msn/cmdproc.c	Tue Jul 28 20:51:56 2009 +0000
+++ b/libpurple/protocols/msn/cmdproc.c	Thu Aug 13 17:15:06 2009 +0000
@@ -333,60 +333,38 @@
 			trans->timer = 0;
 		}
 
-	if (g_ascii_isdigit(cmd->command[0]))
+	if (g_ascii_isdigit(cmd->command[0]) && trans != NULL)
 	{
-		if (trans != NULL)
-		{
-			MsnErrorCb error_cb = NULL;
-			int error;
+		MsnErrorCb error_cb;
+		int error;
 
-			error = atoi(cmd->command);
-
-			if (trans->error_cb != NULL)
-				error_cb = trans->error_cb;
-
-			if (error_cb == NULL && cmdproc->cbs_table->errors != NULL)
-				error_cb = g_hash_table_lookup(cmdproc->cbs_table->errors, trans->command);
+		error = atoi(cmd->command);
 
-			if (error_cb != NULL)
-			{
-				error_cb(cmdproc, trans, error);
-			}
-			else
-			{
-#if 1
-				msn_error_handle(cmdproc->session, error);
-#else
-				purple_debug_warning("msn", "Unhandled error '%s'\n",
-								   cmd->command);
-#endif
-			}
+		error_cb = trans->error_cb;
+		if (error_cb == NULL)
+			error_cb = g_hash_table_lookup(cmdproc->cbs_table->errors, trans->command);
 
-			return;
-		}
+		if (error_cb != NULL)
+			error_cb(cmdproc, trans, error);
+		else
+			msn_error_handle(cmdproc->session, error);
+
+		return;
 	}
 
-	if (cmdproc->cbs_table->async != NULL)
-		cb = g_hash_table_lookup(cmdproc->cbs_table->async, cmd->command);
+	cb = g_hash_table_lookup(cmdproc->cbs_table->async, cmd->command);
 
-	if (cb == NULL && trans != NULL)
-	{
-		if (trans->callbacks != NULL)
-			cb = g_hash_table_lookup(trans->callbacks, cmd->command);
-	}
+	if (cb == NULL && trans != NULL && trans->callbacks != NULL)
+		cb = g_hash_table_lookup(trans->callbacks, cmd->command);
 
-	if (cb == NULL && cmdproc->cbs_table->fallback != NULL)
+	if (cb == NULL)
 		cb = g_hash_table_lookup(cmdproc->cbs_table->fallback, cmd->command);
 
 	if (cb != NULL)
-	{
 		cb(cmdproc, cmd);
-	}
 	else
-	{
 		purple_debug_warning("msn", "Unhandled command '%s'\n",
 						   cmd->command);
-	}
 
 	if (trans != NULL && trans->pendent_cmd != NULL)
 		msn_transaction_unqueue_cmd(trans, cmdproc);
--- a/libpurple/protocols/msn/contact.c	Tue Jul 28 20:51:56 2009 +0000
+++ b/libpurple/protocols/msn/contact.c	Thu Aug 13 17:15:06 2009 +0000
@@ -351,14 +351,24 @@
 msn_parse_each_member(MsnSession *session, xmlnode *member, const char *node,
 	MsnListId list)
 {
-	char *passport = xmlnode_get_data(xmlnode_get_child(member, node));
-	char *type = xmlnode_get_data(xmlnode_get_child(member, "Type"));
-	char *member_id = xmlnode_get_data(xmlnode_get_child(member, "MembershipId"));
-	MsnUser *user = msn_userlist_find_add_user(session->userlist, passport, NULL);
+	char *passport;
+	char *type;
+	char *member_id;
+	MsnUser *user;
 	xmlnode *annotation;
 	guint nid = MSN_NETWORK_UNKNOWN;
 	char *invite = NULL;
 
+	passport = xmlnode_get_data(xmlnode_get_child(member, node));
+	if (!purple_email_is_valid(passport)) {
+		g_free(passport);
+		return;
+	}
+
+	type = xmlnode_get_data(xmlnode_get_child(member, "Type"));
+	member_id = xmlnode_get_data(xmlnode_get_child(member, "MembershipId"));
+	user = msn_userlist_find_add_user(session->userlist, passport, NULL);
+
 	for (annotation = xmlnode_get_child(member, "Annotations/Annotation");
 	     annotation;
 	     annotation = xmlnode_get_next_twin(annotation)) {
@@ -746,6 +756,9 @@
 		if (passport == NULL)
 			continue;
 
+		if (!purple_email_is_valid(passport))
+			continue;
+
 		if ((displayName = xmlnode_get_child(contactInfo, "displayName")))
 			Name = xmlnode_get_data(displayName);
 		else
--- a/libpurple/protocols/msn/directconn.c	Tue Jul 28 20:51:56 2009 +0000
+++ b/libpurple/protocols/msn/directconn.c	Thu Aug 13 17:15:06 2009 +0000
@@ -247,14 +247,6 @@
 }
 
 static void
-msn_directconn_process_msg(MsnDirectConn *directconn, MsnMessage *msg)
-{
-	purple_debug_info("msn", "directconn: process_msg\n");
-
-	msn_slplink_process_msg(directconn->slplink, msg);
-}
-
-static void
 read_cb(gpointer data, gint source, PurpleInputCondition cond)
 {
 	MsnDirectConn* directconn;
@@ -267,6 +259,19 @@
 	directconn = data;
 
 	/* Let's read the length of the data. */
+#error This code is broken.  See the note below.
+	/*
+	 * TODO: This has problems!  First of all, sizeof(body_len) will be
+	 *       different on 32bit systems and on 64bit systems (4 bytes
+	 *       vs. 8 bytes).
+	 *       Secondly, we're reading from a TCP stream.  There is no
+	 *       guarantee that we have received the number of bytes we're
+	 *       trying to read.  We need to read into a buffer.  If read
+	 *       returns <0 then we need to check errno.  If errno is EAGAIN
+	 *       then don't destroy anything, just exit and wait for more
+	 *       data.  See every other function in libpurple that does this
+	 *       correctly for an example.
+	 */
 	len = read(directconn->fd, &body_len, sizeof(body_len));
 
 	if (len <= 0)
@@ -337,7 +342,8 @@
 		msg = msn_message_new_msnslp();
 		msn_message_parse_slp_body(msg, body, body_len);
 
-		msn_directconn_process_msg(directconn, msg);
+		purple_debug_info("msn", "directconn: process_msg\n");
+		msn_slplink_process_msg(directconn->slplink, msg);
 	}
 	else
 	{
@@ -351,6 +357,8 @@
 
 		msn_directconn_destroy(directconn);
 	}
+
+	g_free(body);
 }
 
 static void
--- a/libpurple/protocols/msn/msg.c	Tue Jul 28 20:51:56 2009 +0000
+++ b/libpurple/protocols/msn/msg.c	Thu Aug 13 17:15:06 2009 +0000
@@ -1102,6 +1102,7 @@
 {
 	GHashTable *body;
 	const gchar *guid;
+	gboolean accepted = FALSE;
 
 	g_return_if_fail(cmdproc != NULL);
 	g_return_if_fail(msg != NULL);
@@ -1129,6 +1130,9 @@
 		} else
 			purple_debug_warning("msn", "Invite msg missing "
 					"Application-GUID.\n");
+
+		accepted = TRUE;
+
 	} else if (!strcmp(guid, "{02D3C01F-BF30-4825-A83A-DE7AF41648AA}")) {
 		purple_debug_info("msn", "Computer call\n");
 
@@ -1154,9 +1158,35 @@
 				g_free(buf);
 			}
 		}
-	} else
-		purple_debug_warning("msn",
-				"Unhandled invite msg with GUID %s.\n", guid);
+	} else {
+		const gchar *application = g_hash_table_lookup(body, "Application-Name");
+		purple_debug_warning("msn", "Unhandled invite msg with GUID %s: %s.\n",
+		                     guid, application ? application : "(null)");
+	}
+
+	if (!accepted) {
+		const gchar *cookie = g_hash_table_lookup(body, "Invitation-Cookie");
+		if (cookie) {
+			MsnSwitchBoard *swboard = cmdproc->data;
+			char *text;
+			MsnMessage *cancel;
+
+			cancel = msn_message_new(MSN_MSG_TEXT);
+			msn_message_set_content_type(cancel, "text/x-msmsgsinvite");
+			msn_message_set_charset(cancel, "UTF-8");
+			msn_message_set_flag(cancel, 'U');
+
+			text = g_strdup_printf("Invitation-Command: CANCEL\r\n"
+			                       "Invitation-Cookie: %s\r\n"
+			                       "Cancel-Code: REJECT_NOT_INSTALLED\r\n",
+			                       cookie);
+			msn_message_set_bin_data(cancel, text, strlen(text));
+			g_free(text);
+
+			msn_switchboard_send_msg(swboard, cancel, TRUE);
+			msn_message_destroy(cancel);
+		}
+	}
 
 	g_hash_table_destroy(body);
 }
--- a/libpurple/protocols/msn/session.h	Tue Jul 28 20:51:56 2009 +0000
+++ b/libpurple/protocols/msn/session.h	Thu Aug 13 17:15:06 2009 +0000
@@ -35,7 +35,6 @@
 #include "switchboard.h"
 #include "group.h"
 
-#include "cmdproc.h"
 #include "nexus.h"
 #include "httpconn.h"
 #include "oim.h"
--- a/libpurple/protocols/msn/slp.c	Tue Jul 28 20:51:56 2009 +0000
+++ b/libpurple/protocols/msn/slp.c	Thu Aug 13 17:15:06 2009 +0000
@@ -244,6 +244,8 @@
 got_sessionreq(MsnSlpCall *slpcall, const char *branch,
 			   const char *euf_guid, const char *context)
 {
+	gboolean accepted = FALSE;
+
 	if (!strcmp(euf_guid, MSN_OBJ_GUID))
 	{
 		/* Emoticon or UserDisplay */
@@ -314,7 +316,10 @@
 		msn_slpmsg_set_image(slpmsg, img);
 		msn_slplink_queue_slpmsg(slplink, slpmsg);
 		purple_imgstore_unref(img);
+
+		accepted = TRUE;
 	}
+
 	else if (!strcmp(euf_guid, MSN_FT_GUID))
 	{
 		/* File Transfer */
@@ -324,7 +329,6 @@
 		gsize bin_len;
 		guint32 file_size;
 		char *file_name;
-		gunichar2 *uni_name;
 
 		account = slpcall->slplink->session->account;
 
@@ -342,14 +346,8 @@
 			bin = (char *)purple_base64_decode(context, &bin_len);
 			file_size = GUINT32_FROM_LE(*(gsize *)(bin + 8));
 
-			uni_name = (gunichar2 *)(bin + 20);
-			while(*uni_name != 0 && ((char *)uni_name - (bin + 20)) < MAX_FILE_NAME_LEN) {
-				*uni_name = GUINT16_FROM_LE(*uni_name);
-				uni_name++;
-			}
-
-			file_name = g_utf16_to_utf8((const gunichar2 *)(bin + 20), -1,
-										NULL, NULL, NULL);
+			file_name = g_convert(bin + 20, MAX_FILE_NAME_LEN, "UTF-8", "UTF-16LE",
+			                      NULL, NULL, NULL);
 
 			g_free(bin);
 
@@ -367,6 +365,9 @@
 
 			purple_xfer_request(xfer);
 		}
+
+		accepted = TRUE;
+
 	} else if (!strcmp(euf_guid, MSN_CAM_REQUEST_GUID)) {
 		purple_debug_info("msn", "Cam request.\n");
 		if (slpcall && slpcall->slplink &&
@@ -389,6 +390,7 @@
 				g_free(buf);
 			}
 		}
+
 	} else if (!strcmp(euf_guid, MSN_CAM_GUID)) {
 		purple_debug_info("msn", "Cam invite.\n");
 		if (slpcall && slpcall->slplink &&
@@ -411,8 +413,16 @@
 				g_free(buf);
 			}
 		}
+
 	} else
 		purple_debug_warning("msn", "SLP SessionReq with unknown EUF-GUID: %s\n", euf_guid);
+
+	if (!accepted) {
+		char *content = g_strdup_printf("SessionID: %lu\r\n\r\n",
+		                                slpcall->session_id);
+		send_decline(slpcall, branch, "application/x-msnmsgr-sessionreqbody", content);
+		g_free(content);
+	}
 }
 
 void
--- a/libpurple/protocols/msn/slpcall.c	Tue Jul 28 20:51:56 2009 +0000
+++ b/libpurple/protocols/msn/slpcall.c	Thu Aug 13 17:15:06 2009 +0000
@@ -206,20 +206,23 @@
 		{
 			/* This is for handwritten messages (Ink) */
 			GError *error;
-			glong items_read, items_written;
+			gsize bytes_read, bytes_written;
 
-			body_str = g_utf16_to_utf8((gunichar2 *)body, body_len / 2,
-			                           &items_read, &items_written, &error);
-			body_len -= items_read * 2 + 2;
-			body += items_read * 2 + 2;
+			body_str = g_convert((const gchar *)body, body_len / 2,
+			                     "UTF-8", "UTF-16LE",
+			                     &bytes_read, &bytes_written, &error);
+			body_len -= bytes_read + 2;
+			body += bytes_read + 2;
 			if (body_str == NULL
 			 || body_len <= 0
 			 || strstr(body_str, "image/gif") == NULL)
 			{
-				if (error != NULL)
+				if (error != NULL) {
 					purple_debug_error("msn",
 					                   "Unable to convert Ink header from UTF-16 to UTF-8: %s\n",
 					                   error->message);
+					g_error_free(error);
+				}
 				else
 					purple_debug_error("msn",
 					                   "Received Ink in unknown format\n");
@@ -228,13 +231,20 @@
 			}
 			g_free(body_str);
 
-			body_str = g_utf16_to_utf8((gunichar2 *)body, body_len / 2,
-			                           &items_read, &items_written, &error);
+			body_str = g_convert((const gchar *)body, body_len / 2,
+			                     "UTF-8", "UTF16-LE",
+			                     &bytes_read, &bytes_written, &error);
 			if (!body_str)
 			{
-				purple_debug_error("msn",
-				                   "Unable to convert Ink body from UTF-16 to UTF-8: %s\n",
-				                   error->message);
+				if (error != NULL) {
+					purple_debug_error("msn",
+					                   "Unable to convert Ink body from UTF-16 to UTF-8: %s\n",
+					                   error->message);
+					g_error_free(error);
+				}
+				else
+					purple_debug_error("msn",
+					                   "Received Ink in unknown format\n");
 				return NULL;
 			}
 
--- a/libpurple/protocols/msn/slplink.c	Tue Jul 28 20:51:56 2009 +0000
+++ b/libpurple/protocols/msn/slplink.c	Thu Aug 13 17:15:06 2009 +0000
@@ -456,7 +456,7 @@
 	slpmsg->info = "SLP FILE";
 
 	xfer = (PurpleXfer *)slpcall->xfer;
-	purple_xfer_start(slpcall->xfer, 0, NULL, 0);
+	purple_xfer_start(slpcall->xfer, -1, NULL, 0);
 	slpmsg->fp = xfer->dest_fp;
 	if (g_stat(purple_xfer_get_local_filename(xfer), &st) == 0)
 		slpmsg->size = st.st_size;
@@ -502,7 +502,6 @@
 		g_return_if_reached();
 	}
 
-	slpmsg = NULL;
 	data = msn_message_get_bin_data(msg, &len);
 
 	/*
@@ -537,11 +536,12 @@
 					if (xfer != NULL)
 					{
 						purple_xfer_ref(xfer);
-						purple_xfer_start(xfer,	0, NULL, 0);
+						purple_xfer_start(xfer,	-1, NULL, 0);
 
 						if (xfer->data == NULL) {
 							purple_xfer_unref(xfer);
-							return;
+							msn_slpmsg_destroy(slpmsg);
+							g_return_if_reached();
 						} else {
 							purple_xfer_unref(xfer);
 							slpmsg->fp = xfer->dest_fp;
@@ -565,13 +565,12 @@
 	else
 	{
 		slpmsg = msn_slplink_message_find(slplink, msg->msnslp_header.session_id, msg->msnslp_header.id);
-	}
-
-	if (slpmsg == NULL)
-	{
-		/* Probably the transfer was canceled */
-		purple_debug_error("msn", "Couldn't find slpmsg\n");
-		return;
+		if (slpmsg == NULL)
+		{
+			/* Probably the transfer was canceled */
+			purple_debug_error("msn", "Couldn't find slpmsg\n");
+			return;
+		}
 	}
 
 	if (slpmsg->fp)
--- a/libpurple/protocols/msn/slpmsg.h	Tue Jul 28 20:51:56 2009 +0000
+++ b/libpurple/protocols/msn/slpmsg.h	Thu Aug 13 17:15:06 2009 +0000
@@ -50,10 +50,8 @@
 	long ack_id;
 	long ack_sub_id;
 	long long ack_size;
-	long app_id;
 
 	gboolean sip; /**< A flag that states if this is a SIP slp message. */
-	int ref_count; /**< The reference count. */
 	long flags;
 
 	FILE *fp;
--- a/libpurple/protocols/msn/soap.c	Tue Jul 28 20:51:56 2009 +0000
+++ b/libpurple/protocols/msn/soap.c	Thu Aug 13 17:15:06 2009 +0000
@@ -385,7 +385,7 @@
 					msn_soap_connection_handle_next(conn);
 					handled = TRUE;
 					break;
-				} else if (conn->response_code == 503) {
+				} else if (conn->response_code == 503 && conn->session->login_step < MSN_LOGIN_STEP_END) {
 					msn_soap_connection_sanitize(conn, TRUE);
 					msn_session_set_error(conn->session, MSN_ERROR_SERV_UNAVAILABLE, NULL);
 					return;
--- a/libpurple/protocols/msn/sync.h	Tue Jul 28 20:51:56 2009 +0000
+++ b/libpurple/protocols/msn/sync.h	Thu Aug 13 17:15:06 2009 +0000
@@ -34,6 +34,11 @@
 {
 	MsnSession *session;
 	MsnTable *cbs_table;
+
+	/*
+	 * TODO: What is the intended purpose of old_cbs_table?  Nothing
+	 *       sets it and it is only read in two places.
+	 */
 	MsnTable *old_cbs_table;
 
 	int num_users;
--- a/libpurple/protocols/msn/userlist.h	Tue Jul 28 20:51:56 2009 +0000
+++ b/libpurple/protocols/msn/userlist.h	Thu Aug 13 17:15:06 2009 +0000
@@ -26,7 +26,6 @@
 
 typedef struct _MsnUserList MsnUserList;
 
-#include "cmdproc.h"
 #include "user.h"
 #include "group.h"
 
--- a/libpurple/protocols/msnp9/directconn.c	Tue Jul 28 20:51:56 2009 +0000
+++ b/libpurple/protocols/msnp9/directconn.c	Thu Aug 13 17:15:06 2009 +0000
@@ -355,6 +355,8 @@
 
 		msn_directconn_destroy(directconn);
 	}
+
+	g_free(body);
 }
 
 static void
--- a/libpurple/protocols/msnp9/slplink.c	Tue Jul 28 20:51:56 2009 +0000
+++ b/libpurple/protocols/msnp9/slplink.c	Thu Aug 13 17:15:06 2009 +0000
@@ -496,7 +496,7 @@
 	slpmsg->info = "SLP FILE";
 #endif
 	xfer = (PurpleXfer *)slpcall->xfer;
-	purple_xfer_start(slpcall->xfer, 0, NULL, 0);
+	purple_xfer_start(slpcall->xfer, -1, NULL, 0);
 	slpmsg->fp = xfer->dest_fp;
 	if (g_stat(purple_xfer_get_local_filename(xfer), &st) == 0)
 		slpmsg->size = st.st_size;
@@ -561,7 +561,7 @@
 					if (xfer != NULL)
 					{
 						purple_xfer_ref(xfer);
-						purple_xfer_start(xfer,	0, NULL, 0);
+						purple_xfer_start(xfer,	-1, NULL, 0);
 
 						if (xfer->data == NULL) {
 							purple_xfer_unref(xfer);
--- a/libpurple/protocols/myspace/myspace.c	Tue Jul 28 20:51:56 2009 +0000
+++ b/libpurple/protocols/myspace/myspace.c	Thu Aug 13 17:15:06 2009 +0000
@@ -1341,6 +1341,27 @@
 	return ret;
 }
 #endif
+/**
+ * Process incoming status mood messages.
+ *
+ * @param session
+ * @param msg Status mood update message. Caller frees.
+ *
+ * @return TRUE if successful.
+ */
+static gboolean
+msim_incoming_status_mood(MsimSession *session, MsimMessage *msg) {
+	/* TODO: I dont know too much about this yet,
+	 * so until I see how the official client handles
+	 * this and decide if libpurple should as well,
+	 * well just say we used it
+	 */
+	gchar *ss;
+	ss = msim_msg_get_string(msg, "msg");
+	purple_debug_info("msim", "Incoming Status Message: %s", ss ? ss : "(NULL)");
+	g_free(ss);
+	return TRUE;
+}
 
 /**
  * Process incoming status messages.
@@ -1692,11 +1713,19 @@
 			return msim_incoming_media(session, msg);
 		case MSIM_BM_UNOFFICIAL_CLIENT:
 			return msim_incoming_unofficial_client(session, msg);
+		case MSIM_BM_STATUS_MOOD:
+			return msim_incoming_status_mood(session, msg);
 		default:
-			/* Not really an IM, but show it for informational
-			 * purposes during development. */
-			/* TODO: This is probably wrong */
-			return msim_incoming_action_or_im(session, msg);
+			/*
+			 * Unknown message type!  We used to call
+			 *   msim_incoming_action_or_im(session, msg);
+			 * for these, but that doesn't help anything, and it means
+			 * we'll show broken gibberish if MySpace starts sending us
+			 * other message types.
+			 */
+			purple_debug_warning("myspace", "Received unknown imcoming "
+					"message, bm=%u\n", bm);
+			return TRUE;
 	}
 }
 
--- a/libpurple/protocols/myspace/myspace.h	Tue Jul 28 20:51:56 2009 +0000
+++ b/libpurple/protocols/myspace/myspace.h	Thu Aug 13 17:15:06 2009 +0000
@@ -132,6 +132,7 @@
 #define MSIM_BM_ACTION_OR_IM_INSTANT    121
 #define MSIM_BM_MEDIA                   122
 #define MSIM_BM_PROFILE                 124
+#define MSIM_BM_STATUS_MOOD             126
 #define MSIM_BM_UNOFFICIAL_CLIENT       200
 
 /* Authentication algorithm for login2 */
--- a/libpurple/protocols/oscar/family_icq.c	Tue Jul 28 20:51:56 2009 +0000
+++ b/libpurple/protocols/oscar/family_icq.c	Thu Aug 13 17:15:06 2009 +0000
@@ -246,7 +246,7 @@
 	if (!od || !(conn = flap_connection_findbygroup(od, SNAC_FAMILY_ICQ)))
 		return -EINVAL;
 
-	purple_debug_info("oscar", "Requesting ICQ alias for %s", uin);
+	purple_debug_info("oscar", "Requesting ICQ alias for %s\n", uin);
 
 	bslen = 2 + 4 + 2 + 2 + 2 + 4;
 
@@ -735,14 +735,6 @@
 
 				info = g_new0(struct aim_icq_info, 1);
 
-				if (info == NULL)
-				{
-					g_free(uin);
-					g_free(status_note_title);
-
-					break;
-				}
-
 				bslen = 13 + strlen(uin) + 30 + 6 + 4 + 55 + 85 + 4;
 				byte_stream_new(&bs, 4 + bslen);
 
--- a/libpurple/protocols/oscar/family_locate.c	Tue Jul 28 20:51:56 2009 +0000
+++ b/libpurple/protocols/oscar/family_locate.c	Thu Aug 13 17:15:06 2009 +0000
@@ -615,13 +615,15 @@
 			 * User flags
 			 *
 			 * Specified as any of the following ORed together:
-			 *      0x0001  Trial (user less than 60days)
+			 *      0x0001  Unconfirmed account
 			 *      0x0002  Unknown bit 2
 			 *      0x0004  AOL Main Service user
 			 *      0x0008  Unknown bit 4
 			 *      0x0010  Free (AIM) user
 			 *      0x0020  Away
-			 *      0x0400  ActiveBuddy
+			 *      0x0040  ICQ user (AIM bit also set)
+			 *      0x0080  Mobile device
+			 *      0x0400  Bot (like ActiveBuddy)
 			 */
 			outinfo->flags = byte_stream_get16(bs);
 			outinfo->present |= AIM_USERINFO_PRESENT_FLAGS;
--- a/libpurple/protocols/yahoo/libymsg.c	Tue Jul 28 20:51:56 2009 +0000
+++ b/libpurple/protocols/yahoo/libymsg.c	Thu Aug 13 17:15:06 2009 +0000
@@ -478,7 +478,7 @@
 	PurpleAccount *account = purple_connection_get_account(gc);
 	YahooData *yd = gc->proto_data;
 	GHashTable *ht;
-	char *norm_bud = NULL;
+	char *norm_bud;
 	char *temp = NULL;
 	YahooFriend *f = NULL; /* It's your friends. They're going to want you to share your StarBursts. */
 	                       /* But what if you had no friends? */
@@ -487,7 +487,6 @@
 	int protocol = 0;
 	int stealth = 0;
 
-
 	ht = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, (GDestroyNotify) g_slist_free);
 
 	while (l) {
@@ -546,9 +545,11 @@
 					purple_privacy_deny_add(account, norm_bud, 1);
 				}
 
+				g_free(norm_bud);
+
 				protocol = 0;
 				stealth = 0;
-				norm_bud = NULL;
+				g_free(temp);
 				temp = NULL;
 			}
 			break;
@@ -559,6 +560,7 @@
 			yd->current_list15_grp = yahoo_string_decode(gc, pair->value, FALSE);
 			break;
 		case 7: /* buddy's s/n */
+			g_free(temp);
 			temp = g_strdup(purple_normalize(account, pair->value));
 			break;
 		case 241: /* another protocol user */
@@ -594,7 +596,6 @@
 	yahoo_set_status(account, purple_account_get_active_status(account));
 
 	g_hash_table_destroy(ht);
-	g_free(norm_bud);
 	g_free(temp);
 }
 
@@ -976,7 +977,7 @@
 		return;
 	}
 
-	/** TODO: It seems that this check should be per IM, not global */
+	/* TODO: It seems that this check should be per IM, not global */
 	/* Check for the Doodle IMV */
 	if (im != NULL && imv!= NULL && im->from != NULL)
 	{
@@ -1830,6 +1831,7 @@
 			g_free(error_reason);
 			g_free(auth_data->seed);
 			g_free(auth_data);
+			g_free(token);
 		}
 		else {
 			/* OK to login, correct information provided */
@@ -2082,6 +2084,12 @@
 		msg = g_strdup(_("Your account is locked, please log in to the Yahoo! website."));
 		reason = PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED;
 		break;
+	case 52:
+		/* See #9660. As much as we know, reconnecting shouldn't hurt */
+		purple_debug_info("yahoo", "Got error 52, Set to autoreconnect\n");
+		msg = g_strdup_printf(_("Unknown error"));
+		reason = PURPLE_CONNECTION_ERROR_NETWORK_ERROR;
+		break;
 	case 1013:
 		msg = g_strdup(_("Invalid username"));
 		reason = PURPLE_CONNECTION_ERROR_INVALID_USERNAME;
@@ -2578,8 +2586,7 @@
 	PurpleAccount *account;
 	YahooData *yd;
 
-	if(!(p2p_data = data))
-		return ;
+	p2p_data = data;
 	yd = p2p_data->gc->proto_data;
 
 	if(error_message != NULL) {
@@ -3767,7 +3774,7 @@
 	if (presence != NULL)
 		purple_notify_user_info_add_pair(user_info, _("Presence"), presence);
 
-	if (full) {
+	if (f && full) {
 		YahooPersonalDetails *ypd = &f->ypd;
 		int i;
 		struct {
--- a/libpurple/protocols/yahoo/util.c	Tue Jul 28 20:51:56 2009 +0000
+++ b/libpurple/protocols/yahoo/util.c	Thu Aug 13 17:15:06 2009 +0000
@@ -172,7 +172,6 @@
 char *yahoo_convert_to_numeric(const char *str)
 {
 	GString *gstr = NULL;
-	char *retstr;
 	const unsigned char *p;
 
 	gstr = g_string_sized_new(strlen(str) * 6 + 1);
@@ -181,121 +180,172 @@
 		g_string_append_printf(gstr, "&#%u;", *p);
 	}
 
-	retstr = gstr->str;
-
-	g_string_free(gstr, FALSE);
-
-	return retstr;
+	return g_string_free(gstr, FALSE);
 }
 
 /*
+ * The values in this hash table should probably be lowercase, since that's
+ * what xhtml expects.  Also because yahoo_codes_to_html() does
+ * case-sensitive comparisons.
+ *
  * I found these on some website but i don't know that they actually
  * work (or are supposed to work). I didn't implement them yet.
  *
-     * [0;30m ---black
-     * [1;37m ---white
-     * [0;37m ---tan
-     * [0;38m ---light black
-     * [1;39m ---dark blue
-     * [0;32m ---green
-     * [0;33m ---yellow
-     * [0;35m ---pink
-     * [1;35m ---purple
-     * [1;30m ---light blue
-     * [0;31m ---red
-     * [0;34m ---blue
-     * [0;36m ---aqua
-         * (shift+comma)lyellow(shift+period) ---light yellow
-     * (shift+comma)lgreen(shift+period) ---light green
-[2;30m <--white out
-*/
+ * [0;30m ---black
+ * [1;37m ---white
+ * [0;37m ---tan
+ * [0;38m ---light black
+ * [1;39m ---dark blue
+ * [0;32m ---green
+ * [0;33m ---yellow
+ * [0;35m ---pink
+ * [1;35m ---purple
+ * [1;30m ---light blue
+ * [0;31m ---red
+ * [0;34m ---blue
+ * [0;36m ---aqua
+ * (shift+comma)lyellow(shift+period) ---light yellow
+ * (shift+comma)lgreen(shift+period) ---light green
+ * [2;30m <--white out
+ */
 
-static GHashTable *ht = NULL;
+static GHashTable *esc_codes_ht = NULL;
+static GHashTable *tags_ht = NULL;
 
 void yahoo_init_colorht()
 {
-	if (ht != NULL)
+	if (esc_codes_ht != NULL)
 		/* Hash table has already been initialized */
 		return;
 
-	ht = g_hash_table_new(g_str_hash, g_str_equal);
+	/* Key is the escape code string.  Value is the HTML that should be
+	 * inserted in place of the escape code. */
+	esc_codes_ht = g_hash_table_new(g_str_hash, g_str_equal);
+
+	/* Key is the name of the HTML tag, for example "font" or "/font"
+	 * value is the HTML that should be inserted in place of the old tag */
+	tags_ht = g_hash_table_new(g_str_hash, g_str_equal);
+
 	/* the numbers in comments are what gyach uses, but i think they're incorrect */
-	g_hash_table_insert(ht, "30", "<FONT COLOR=\"#000000\">"); /* black */
-	g_hash_table_insert(ht, "31", "<FONT COLOR=\"#0000FF\">"); /* blue */
-	g_hash_table_insert(ht, "32", "<FONT COLOR=\"#008080\">"); /* cyan */      /* 00b2b2 */
-	g_hash_table_insert(ht, "33", "<FONT COLOR=\"#808080\">"); /* gray */      /* 808080 */
-	g_hash_table_insert(ht, "34", "<FONT COLOR=\"#008000\">"); /* green */     /* 00c200 */
-	g_hash_table_insert(ht, "35", "<FONT COLOR=\"#FF0080\">"); /* pink */      /* ffafaf */
-	g_hash_table_insert(ht, "36", "<FONT COLOR=\"#800080\">"); /* purple */    /* b200b2 */
-	g_hash_table_insert(ht, "37", "<FONT COLOR=\"#FF8000\">"); /* orange */    /* ffff00 */
-	g_hash_table_insert(ht, "38", "<FONT COLOR=\"#FF0000\">"); /* red */
-	g_hash_table_insert(ht, "39", "<FONT COLOR=\"#808000\">"); /* olive */     /* 546b50 */
+#ifdef USE_CSS_FORMATTING
+	g_hash_table_insert(esc_codes_ht, "30", "<span style=\"color: #000000\">"); /* black */
+	g_hash_table_insert(esc_codes_ht, "31", "<span style=\"color: #0000FF\">"); /* blue */
+	g_hash_table_insert(esc_codes_ht, "32", "<span style=\"color: #008080\">"); /* cyan */      /* 00b2b2 */
+	g_hash_table_insert(esc_codes_ht, "33", "<span style=\"color: #808080\">"); /* gray */      /* 808080 */
+	g_hash_table_insert(esc_codes_ht, "34", "<span style=\"color: #008000\">"); /* green */     /* 00c200 */
+	g_hash_table_insert(esc_codes_ht, "35", "<span style=\"color: #FF0080\">"); /* pink */      /* ffafaf */
+	g_hash_table_insert(esc_codes_ht, "36", "<span style=\"color: #800080\">"); /* purple */    /* b200b2 */
+	g_hash_table_insert(esc_codes_ht, "37", "<span style=\"color: #FF8000\">"); /* orange */    /* ffff00 */
+	g_hash_table_insert(esc_codes_ht, "38", "<span style=\"color: #FF0000\">"); /* red */
+	g_hash_table_insert(esc_codes_ht, "39", "<span style=\"color: #808000\">"); /* olive */     /* 546b50 */
+#else
+	g_hash_table_insert(esc_codes_ht, "30", "<font color=\"#000000\">"); /* black */
+	g_hash_table_insert(esc_codes_ht, "31", "<font color=\"#0000FF\">"); /* blue */
+	g_hash_table_insert(esc_codes_ht, "32", "<font color=\"#008080\">"); /* cyan */      /* 00b2b2 */
+	g_hash_table_insert(esc_codes_ht, "33", "<font color=\"#808080\">"); /* gray */      /* 808080 */
+	g_hash_table_insert(esc_codes_ht, "34", "<font color=\"#008000\">"); /* green */     /* 00c200 */
+	g_hash_table_insert(esc_codes_ht, "35", "<font color=\"#FF0080\">"); /* pink */      /* ffafaf */
+	g_hash_table_insert(esc_codes_ht, "36", "<font color=\"#800080\">"); /* purple */    /* b200b2 */
+	g_hash_table_insert(esc_codes_ht, "37", "<font color=\"#FF8000\">"); /* orange */    /* ffff00 */
+	g_hash_table_insert(esc_codes_ht, "38", "<font color=\"#FF0000\">"); /* red */
+	g_hash_table_insert(esc_codes_ht, "39", "<font color=\"#808000\">"); /* olive */     /* 546b50 */
+#endif /* !USE_CSS_FORMATTING */
 
-	g_hash_table_insert(ht,  "1",  "<B>");
-	g_hash_table_insert(ht, "x1", "</B>");
-	g_hash_table_insert(ht,  "2",  "<I>");
-	g_hash_table_insert(ht, "x2", "</I>");
-	g_hash_table_insert(ht,  "4",  "<U>");
-	g_hash_table_insert(ht, "x4", "</U>");
+	g_hash_table_insert(esc_codes_ht,  "1",  "<b>");
+	g_hash_table_insert(esc_codes_ht, "x1", "</b>");
+	g_hash_table_insert(esc_codes_ht,  "2",  "<i>");
+	g_hash_table_insert(esc_codes_ht, "x2", "</i>");
+	g_hash_table_insert(esc_codes_ht,  "4",  "<u>");
+	g_hash_table_insert(esc_codes_ht, "x4", "</u>");
 
 	/* these just tell us the text they surround is supposed
 	 * to be a link. purple figures that out on its own so we
 	 * just ignore it.
 	 */
-	g_hash_table_insert(ht, "l", ""); /* link start */
-	g_hash_table_insert(ht, "xl", ""); /* link end */
+	g_hash_table_insert(esc_codes_ht, "l", ""); /* link start */
+	g_hash_table_insert(esc_codes_ht, "xl", ""); /* link end */
 
-	g_hash_table_insert(ht, "<black>",  "<FONT COLOR=\"#000000\">");
-	g_hash_table_insert(ht, "<blue>",   "<FONT COLOR=\"#0000FF\">");
-	g_hash_table_insert(ht, "<cyan>",   "<FONT COLOR=\"#008284\">");
-	g_hash_table_insert(ht, "<gray>",   "<FONT COLOR=\"#848284\">");
-	g_hash_table_insert(ht, "<green>",  "<FONT COLOR=\"#008200\">");
-	g_hash_table_insert(ht, "<pink>",   "<FONT COLOR=\"#FF0084\">");
-	g_hash_table_insert(ht, "<purple>", "<FONT COLOR=\"#840084\">");
-	g_hash_table_insert(ht, "<orange>", "<FONT COLOR=\"#FF8000\">");
-	g_hash_table_insert(ht, "<red>",    "<FONT COLOR=\"#FF0000\">");
-	g_hash_table_insert(ht, "<yellow>", "<FONT COLOR=\"#848200\">");
+#ifdef USE_CSS_FORMATTING
+	g_hash_table_insert(tags_ht, "black",  "<span style=\"color: #000000\">");
+	g_hash_table_insert(tags_ht, "blue",   "<span style=\"color: #0000FF\">");
+	g_hash_table_insert(tags_ht, "cyan",   "<span style=\"color: #008284\">");
+	g_hash_table_insert(tags_ht, "gray",   "<span style=\"color: #848284\">");
+	g_hash_table_insert(tags_ht, "green",  "<span style=\"color: #008200\">");
+	g_hash_table_insert(tags_ht, "pink",   "<span style=\"color: #FF0084\">");
+	g_hash_table_insert(tags_ht, "purple", "<span style=\"color: #840084\">");
+	g_hash_table_insert(tags_ht, "orange", "<span style=\"color: #FF8000\">");
+	g_hash_table_insert(tags_ht, "red",    "<span style=\"color: #FF0000\">");
+	g_hash_table_insert(tags_ht, "yellow", "<span style=\"color: #848200\">");
 
-	g_hash_table_insert(ht, "</black>",  "</FONT>");
-	g_hash_table_insert(ht, "</blue>",   "</FONT>");
-	g_hash_table_insert(ht, "</cyan>",   "</FONT>");
-	g_hash_table_insert(ht, "</gray>",   "</FONT>");
-	g_hash_table_insert(ht, "</green>",  "</FONT>");
-	g_hash_table_insert(ht, "</pink>",   "</FONT>");
-	g_hash_table_insert(ht, "</purple>", "</FONT>");
-	g_hash_table_insert(ht, "</orange>", "</FONT>");
-	g_hash_table_insert(ht, "</red>",    "</FONT>");
-	g_hash_table_insert(ht, "</yellow>", "</FONT>");
+	g_hash_table_insert(tags_ht, "/black",  "</span>");
+	g_hash_table_insert(tags_ht, "/blue",   "</span>");
+	g_hash_table_insert(tags_ht, "/cyan",   "</span>");
+	g_hash_table_insert(tags_ht, "/gray",   "</span>");
+	g_hash_table_insert(tags_ht, "/green",  "</span>");
+	g_hash_table_insert(tags_ht, "/pink",   "</span>");
+	g_hash_table_insert(tags_ht, "/purple", "</span>");
+	g_hash_table_insert(tags_ht, "/orange", "</span>");
+	g_hash_table_insert(tags_ht, "/red",    "</span>");
+	g_hash_table_insert(tags_ht, "/yellow", "</span>");
+#else
+	g_hash_table_insert(tags_ht, "black",  "<font color=\"#000000\">");
+	g_hash_table_insert(tags_ht, "blue",   "<font color=\"#0000FF\">");
+	g_hash_table_insert(tags_ht, "cyan",   "<font color=\"#008284\">");
+	g_hash_table_insert(tags_ht, "gray",   "<font color=\"#848284\">");
+	g_hash_table_insert(tags_ht, "green",  "<font color=\"#008200\">");
+	g_hash_table_insert(tags_ht, "pink",   "<font color=\"#FF0084\">");
+	g_hash_table_insert(tags_ht, "purple", "<font color=\"#840084\">");
+	g_hash_table_insert(tags_ht, "orange", "<font color=\"#FF8000\">");
+	g_hash_table_insert(tags_ht, "red",    "<font color=\"#FF0000\">");
+	g_hash_table_insert(tags_ht, "yellow", "<font color=\"#848200\">");
 
-	/* remove these once we have proper support for <FADE> and <ALT> */
-	g_hash_table_insert(ht, "</fade>", "");
-	g_hash_table_insert(ht, "</alt>", "");
+	g_hash_table_insert(tags_ht, "/black",  "</font>");
+	g_hash_table_insert(tags_ht, "/blue",   "</font>");
+	g_hash_table_insert(tags_ht, "/cyan",   "</font>");
+	g_hash_table_insert(tags_ht, "/gray",   "</font>");
+	g_hash_table_insert(tags_ht, "/green",  "</font>");
+	g_hash_table_insert(tags_ht, "/pink",   "</font>");
+	g_hash_table_insert(tags_ht, "/purple", "</font>");
+	g_hash_table_insert(tags_ht, "/orange", "</font>");
+	g_hash_table_insert(tags_ht, "/red",    "</font>");
+	g_hash_table_insert(tags_ht, "/yellow", "</font>");
+#endif /* !USE_CSS_FORMATTING */
 
-	/* these are the normal html yahoo sends (besides <font>).
-	 * anything else will get turned into &lt;tag&gt;, so if I forgot
-	 * about something, please add it. Why Yahoo! has to send unescaped
-	 * <'s and >'s that aren't supposed to be html is beyond me.
-	 */
-	g_hash_table_insert(ht, "<b>", "<b>");
-	g_hash_table_insert(ht, "<i>", "<i>");
-	g_hash_table_insert(ht, "<u>", "<u>");
+	/* We don't support these tags, so discard them */
+	g_hash_table_insert(tags_ht, "alt", "");
+	g_hash_table_insert(tags_ht, "fade", "");
+	g_hash_table_insert(tags_ht, "snd", "");
+	g_hash_table_insert(tags_ht, "/alt", "");
+	g_hash_table_insert(tags_ht, "/fade", "");
 
-	g_hash_table_insert(ht, "</b>", "</b>");
-	g_hash_table_insert(ht, "</i>", "</i>");
-	g_hash_table_insert(ht, "</u>", "</u>");
-	g_hash_table_insert(ht, "</font>", "</font>");
+	/* Official clients don't seem to send b, i or u tags.  They use
+	 * the escape codes listed above.  Official clients definitely send
+	 * font tags, though.  I wonder if we can remove the opening and
+	 * closing b, i and u tags from here? */
+	g_hash_table_insert(tags_ht, "b", "<b>");
+	g_hash_table_insert(tags_ht, "i", "<i>");
+	g_hash_table_insert(tags_ht, "u", "<u>");
+	g_hash_table_insert(tags_ht, "font", "<font>");
+
+	g_hash_table_insert(tags_ht, "/b", "</b>");
+	g_hash_table_insert(tags_ht, "/i", "</i>");
+	g_hash_table_insert(tags_ht, "/u", "</u>");
+	g_hash_table_insert(tags_ht, "/font", "</font>");
 }
 
 void yahoo_dest_colorht()
 {
-	if (ht == NULL)
+	if (esc_codes_ht == NULL)
 		/* Hash table has already been destroyed */
 		return;
 
-	g_hash_table_destroy(ht);
-	ht = NULL;
+	g_hash_table_destroy(esc_codes_ht);
+	esc_codes_ht = NULL;
+	g_hash_table_destroy(tags_ht);
+	tags_ht = NULL;
 }
 
+#ifndef USE_CSS_FORMATTING
 static int point_to_html(int x)
 {
 	if (x < 9)
@@ -312,128 +362,303 @@
 		return 6;
 	return 7;
 }
+#endif /* !USE_CSS_FORMATTING */
 
-/* The Yahoo size tag is actually an absz tag; convert it to an HTML size, and include both tags */
-static void _font_tags_fix_size(GString *tag, GString *dest)
+static void append_attrs_datalist_foreach_cb(GQuark key_id, gpointer data, gpointer user_data)
 {
-	char *x, *end;
-	int size;
+	const char *key;
+	const char *value;
+	xmlnode *cur;
+
+	key = g_quark_to_string(key_id);
+	value = data;
+	cur = user_data;
+
+	xmlnode_set_attrib(cur, key, value);
+}
 
-	if (((x = strstr(tag->str, "size"))) && ((x = strchr(x, '=')))) {
-		while (*x && !g_ascii_isdigit(*x))
-			x++;
-		if (*x) {
-			int htmlsize;
+/**
+ * @param cur A pointer to the position in the XML tree that we're
+ *        currently building.  This will be modified when opening a tag
+ *        or closing an existing tag.
+ */
+static void yahoo_codes_to_html_add_tag(xmlnode **cur, const char *tag, gboolean is_closing_tag, const gchar *tag_name, gboolean is_font_tag)
+{
+	if (is_closing_tag) {
+		xmlnode *tmp;
+		GSList *dangling_tags = NULL;
 
-			size = strtol(x, &end, 10);
-			htmlsize = point_to_html(size);
-			g_string_append_len(dest, tag->str, x - tag->str);
-			g_string_append_printf(dest, "%d", htmlsize);
-			g_string_append_printf(dest, "\" absz=\"%d", size);
-			g_string_append(dest, end);
-		} else {
-			g_string_append(dest, tag->str);
+		/* Move up the DOM until we find the opening tag */
+		for (tmp = *cur; tmp != NULL; tmp = xmlnode_get_parent(tmp)) {
+			/* Add one to tag_name when doing this comparison because it starts with a / */
+			if (g_str_equal(tmp->name, tag_name + 1))
+				/* Found */
+				break;
+			dangling_tags = g_slist_prepend(dangling_tags, tmp);
+		}
+		if (tmp == NULL) {
+			/* This is a closing tag with no opening tag.  Useless. */
+			purple_debug_error("yahoo", "Ignoring unmatched tag %s", tag);
+			g_slist_free(dangling_tags);
 			return;
 		}
+
+		/* Move our current position up, now that we've closed a tag */
+		*cur = xmlnode_get_parent(tmp);
+
+		/* Re-open any tags that were nested below the tag we just closed */
+		while (dangling_tags != NULL) {
+			tmp = dangling_tags->data;
+			dangling_tags = g_slist_delete_link(dangling_tags, dangling_tags);
+
+			/* Create a copy of this tag+attributes (but not child tags or
+			 * data) at our new location */
+			*cur = xmlnode_new_child(*cur, tmp->name);
+			for (tmp = tmp->child; tmp != NULL; tmp = tmp->next)
+				if (tmp->type == XMLNODE_TYPE_ATTRIB)
+					xmlnode_set_attrib_full(*cur, tmp->name,
+							tmp->xmlns, tmp->prefix, tmp->data);
+		}
 	} else {
-		g_string_append(dest, tag->str);
-		return;
+		const char *start;
+		const char *end;
+		GData *attributes;
+		char *fontsize = NULL;
+
+		purple_markup_find_tag(tag_name, tag, &start, &end, &attributes);
+		*cur = xmlnode_new_child(*cur, tag_name);
+
+		if (is_font_tag) {
+			/* Special case for the font size attribute */
+			fontsize = g_strdup(g_datalist_get_data(&attributes, "size"));
+			if (fontsize != NULL)
+				g_datalist_remove_data(&attributes, "size");
+		}
+
+		/* Add all font tag attributes */
+		g_datalist_foreach(&attributes, append_attrs_datalist_foreach_cb, *cur);
+		g_datalist_clear(&attributes);
+
+		if (fontsize != NULL) {
+#ifdef USE_CSS_FORMATTING
+			/*
+			 * The Yahoo font size value is given in pt, even though the HTML
+			 * standard for <font size="x"> treats the size as a number on a
+			 * scale between 1 and 7.  So we insert the font size as a CSS
+			 * style on a span tag.
+			 */
+			gchar *tmp = g_strdup_printf("font-size: %spt", fontsize);
+			*cur = xmlnode_new_child(*cur, "span");
+			xmlnode_set_attrib(*cur, "style", tmp);
+			g_free(tmp);
+#else
+			/*
+			 * The Yahoo font size value is given in pt, even though the HTML
+			 * standard for <font size="x"> treats the size as a number on a
+			 * scale between 1 and 7.  So we convert it to an appropriate
+			 * value.  This loses precision, which is why CSS formatting is
+			 * preferred.  The "absz" attribute remains here for backward
+			 * compatibility with UIs that might use it, but it is totally
+			 * not standard at all.
+			 */
+			int size, htmlsize;
+			gchar tmp[11];
+			size = strtol(fontsize, NULL, 10);
+			htmlsize = point_to_html(size);
+			sprintf(tmp, "%u", htmlsize);
+			xmlnode_set_attrib(*cur, "size", tmp);
+			xmlnode_set_attrib(*cur, "absz", fontsize);
+#endif /* !USE_CSS_FORMATTING */
+			g_free(fontsize);
+		}
 	}
 }
 
+/**
+ * Similar to purple_markup_get_tag_name(), but works with closing tags.
+ *
+ * @return The lowercase name of the tag.  If this is a closing tag then
+ *         this value starts with a forward slash.  The caller must free
+ *         this string with g_free.
+ */
+static gchar *yahoo_markup_get_tag_name(const char *tag, gboolean *is_closing_tag)
+{
+	size_t len;
+
+	*is_closing_tag = (tag[1] == '/');
+	if (*is_closing_tag)
+		len = strcspn(tag + 1, "> ");
+	else
+		len = strcspn(tag + 1, "> /");
+
+	return g_utf8_strdown(tag + 1, len);
+}
+
+/*
+ * Yahoo! messages generally aren't well-formed.  Their markup is
+ * more of a flow from start to finish rather than a hierarchy from
+ * outer to inner.  They tend to open tags and close them only when
+ * necessary.
+ *
+ * Example: <font size="8">size 8 <font size="16">size 16 <font size="8">size 8 again
+ *
+ * But we want to send well-formed HTML to the core, so we step through
+ * the input string and build an xmlnode tree containing sanitized HTML.
+ */
 char *yahoo_codes_to_html(const char *x)
 {
-	GString *s, *tmp;
-	int i, j, xs, nomoreendtags = 0; /* s/endtags/closinganglebrackets */
-	char *match, *ret;
+	size_t x_len;
+	xmlnode *html, *cur;
+	GString *cdata = g_string_new(NULL);
+	int i, j;
+	gboolean no_more_gt_brackets = FALSE;
+	const char *match;
+	gchar *xmlstr1, *xmlstr2;
 
-	s = g_string_sized_new(strlen(x));
+	x_len = strlen(x);
+	html = xmlnode_new("html");
 
-	for (i = 0, xs = strlen(x); i < xs; i++) {
+	cur = html;
+	for (i = 0; i < x_len; i++) {
 		if ((x[i] == 0x1b) && (x[i+1] == '[')) {
+			/* This escape sequence signifies the beginning of some
+			 * text formatting code */
 			j = i + 1;
 
-			while (j++ < xs) {
+			while (j++ < x_len) {
+				gchar *code;
+
 				if (x[j] != 'm')
+					/* Keep looking for the end of this sequence */
 					continue;
-				else {
-					tmp = g_string_new_len(x + i + 2, j - i - 2);
-					if (tmp->str[0] == '#')
-						g_string_append_printf(s, "<FONT COLOR=\"%s\">", tmp->str);
-					else if ((match = (char *) g_hash_table_lookup(ht, tmp->str)))
-						g_string_append(s, match);
-					else {
-						purple_debug_error("yahoo",
-							"Unknown ansi code 'ESC[%sm'.\n", tmp->str);
-						g_string_free(tmp, TRUE);
-						break;
-					}
+
+				/* We've reached the end of the formatting sequence, yay */
+
+				/* Append any character data that belongs in the current node */
+				if (cdata->len > 0) {
+					xmlnode_insert_data(cur, cdata->str, cdata->len);
+					g_string_truncate(cdata, 0);
+				}
 
-					i = j;
-					g_string_free(tmp, TRUE);
-					break;
+				code = g_strndup(x + i + 2, j - i - 2);
+				if (code[0] == '#') {
+#ifdef USE_CSS_FORMATTING
+					gchar *tmp = g_strdup_printf("color: %s", code);
+					cur = xmlnode_new_child(cur, "span");
+					xmlnode_set_attrib(cur, "style", tmp);
+					g_free(tmp);
+#else
+					cur = xmlnode_new_child(cur, "font");
+					xmlnode_set_attrib(cur, "color", code);
+#endif /* !USE_CSS_FORMATTING */
+
+				} else if ((match = g_hash_table_lookup(esc_codes_ht, code))) {
+					gboolean is_closing_tag;
+					gchar *tag_name;
+
+					tag_name = yahoo_markup_get_tag_name(match, &is_closing_tag);
+					yahoo_codes_to_html_add_tag(&cur, match, is_closing_tag, tag_name, FALSE);
+					g_free(tag_name);
+
+				} else {
+					purple_debug_error("yahoo",
+						"Ignoring unknown ansi code 'ESC[%sm'.\n", code);
 				}
+
+				g_free(code);
+				i = j;
+				break;
 			}
 
-		} else if (!nomoreendtags && (x[i] == '<')) {
+		} else if (x[i] == '<' && !no_more_gt_brackets) {
+			/* The start of an HTML tag */
 			j = i;
 
-			while (j++ < xs) {
-				if (x[j] != '>')
-					if (j == xs) {
-						g_string_append(s, "&lt;");
-						nomoreendtags = 1;
-					}
-					else
-						continue;
-				else {
-					tmp = g_string_new_len(x + i, j - i + 1);
-					g_string_ascii_down(tmp);
+			while (j++ < x_len) {
+				gchar *tag;
+				gboolean is_closing_tag;
+				gchar *tag_name;
 
-					if ((match = (char *) g_hash_table_lookup(ht, tmp->str)))
-						g_string_append(s, match);
-					else if (!strncmp(tmp->str, "<fade ", 6) ||
-						!strncmp(tmp->str, "<alt ", 5) ||
-						!strncmp(tmp->str, "<snd ", 5)) {
-
-						/* remove this if gtkimhtml ever supports any of these */
-						i = j;
-						g_string_free(tmp, TRUE);
-						break;
+				if (x[j] != '>') {
+					if (x[j] == '"') {
+						/* We're inside a quoted attribute value. Skip to the end */
+						j++;
+						while (j != x_len && x[j] != '"')
+							j++;
+					} else if (x[j] == '\'') {
+						/* We're inside a quoted attribute value. Skip to the end */
+						j++;
+						while (j != x_len && x[j] != '\'')
+							j++;
+					}
+					if (j != x_len)
+						/* Keep looking for the end of this tag */
+						continue;
 
-					} else if (!strncmp(tmp->str, "<font ", 6)) {
-						_font_tags_fix_size(tmp, s);
-					} else {
-						g_string_append(s, "&lt;");
-						g_string_free(tmp, TRUE);
-						break;
-					}
-
-					i = j;
-					g_string_free(tmp, TRUE);
+					/* This < has no corresponding > */
+					g_string_append_c(cdata, x[i]);
+					no_more_gt_brackets = TRUE;
 					break;
 				}
 
+				tag = g_strndup(x + i, j - i + 1);
+				tag_name = yahoo_markup_get_tag_name(tag, &is_closing_tag);
+
+				match = g_hash_table_lookup(tags_ht, tag_name);
+				if (match == NULL) {
+					/* Unknown tag.  The user probably typed a less-than sign */
+					g_string_append_c(cdata, x[i]);
+					no_more_gt_brackets = TRUE;
+					g_free(tag);
+					g_free(tag_name);
+					break;
+				}
+
+				/* Some tags are in the hash table only because we
+				 * want to ignore them */
+				if (match[0] != '\0') {
+					/* Append any character data that belongs in the current node */
+					if (cdata->len > 0) {
+						xmlnode_insert_data(cur, cdata->str, cdata->len);
+						g_string_truncate(cdata, 0);
+					}
+					if (g_str_equal(tag_name, "font"))
+						/* Font tags are a special case.  We don't
+						 * necessarily want to replace the whole thing--
+						 * we just want to fix the size attribute. */
+						yahoo_codes_to_html_add_tag(&cur, tag, is_closing_tag, tag_name, TRUE);
+					else
+						yahoo_codes_to_html_add_tag(&cur, match, is_closing_tag, tag_name, FALSE);
+				}
+
+				i = j;
+				g_free(tag);
+				g_free(tag_name);
+				break;
 			}
 
 		} else {
-			if (x[i] == '<')
-				g_string_append(s, "&lt;");
-			else if (x[i] == '>')
-				g_string_append(s, "&gt;");
-			else if (x[i] == '&')
-				g_string_append(s, "&amp;");
-			else if (x[i] == '"')
-				g_string_append(s, "&quot;");
-			else
-				g_string_append_c(s, x[i]);
+			g_string_append_c(cdata, x[i]);
 		}
 	}
 
-	ret = s->str;
-	g_string_free(s, FALSE);
-	purple_debug_misc("yahoo", "yahoo_codes_to_html:  Returning string: '%s'.\n", ret);
-	return ret;
+	/* Append any remaining character data */
+	if (cdata->len > 0)
+		xmlnode_insert_data(cur, cdata->str, cdata->len);
+	g_string_free(cdata, TRUE);
+
+	/* Serialize our HTML */
+	xmlstr1 = xmlnode_to_str(html, NULL);
+	xmlnode_free(html);
+
+	/* Strip off the outter HTML node */
+	/* This probably isn't necessary, especially if we made the outter HTML
+	 * node an empty span.  But the HTML is simpler this way. */
+	xmlstr2 = g_strndup(xmlstr1 + 6, strlen(xmlstr1) - 13);
+	g_free(xmlstr1);
+
+	purple_debug_misc("yahoo", "yahoo_codes_to_html:  Returning string: '%s'.\n", xmlstr2);
+	return xmlstr2;
 }
 
 /* borrowed from gtkimhtml */
@@ -441,8 +666,16 @@
 #define POINT_SIZE(x) (_point_sizes [MIN ((x > 0 ? x : 1), MAX_FONT_SIZE) - 1])
 static const gint _point_sizes [] = { 8, 10, 12, 14, 20, 30, 40 };
 
-enum fatype { size, color, face, junk };
-typedef struct {
+enum fatype
+{
+	FATYPE_SIZE,
+	FATYPE_COLOR,
+	FATYPE_FACE,
+	FATYPE_JUNK
+};
+
+typedef struct
+{
 	enum fatype type;
 	union {
 		int size;
@@ -454,28 +687,26 @@
 
 static void fontattr_free(fontattr *f)
 {
-	if (f->type == color)
+	if (f->type == FATYPE_COLOR)
 		g_free(f->u.color);
-	else if (f->type == face)
+	else if (f->type == FATYPE_FACE)
 		g_free(f->u.face);
 	g_free(f);
 }
 
-static void yahoo_htc_queue_cleanup(GQueue *q)
+static void yahoo_htc_list_cleanup(GSList *l)
 {
-	char *tmp;
-
-	while ((tmp = g_queue_pop_tail(q)))
-		g_free(tmp);
-	g_queue_free(q);
+	while (l != NULL) {
+		g_free(l->data);
+		l = g_slist_delete_link(l, l);
+	}
 }
 
 static void _parse_font_tag(const char *src, GString *dest, int *i, int *j,
-				int len, GQueue *colors, GQueue *tags, GQueue *ftattr)
+				int len, GSList **colors, GSList **tags, GQueue *ftattr)
 {
-
 	int m, n, vstart;
-	gboolean quote = 0, done = 0;
+	gboolean quote = FALSE, done = FALSE;
 
 	m = *j;
 
@@ -500,7 +731,7 @@
 
 				if (src[n] == '"') {
 					if (!quote) {
-						quote = 1;
+						quote = TRUE;
 						vstart = n;
 						continue;
 					} else {
@@ -509,14 +740,14 @@
 				}
 
 				if (!quote && ((src[n] == ' ') || (src[n] == '>')))
-					done = 1;
+					done = TRUE;
 
 				if (done) {
 					if (!g_ascii_strncasecmp(&src[*j+1], "FACE", m - *j - 1)) {
 						fontattr *f;
 
 						f = g_new(fontattr, 1);
-						f->type = face;
+						f->type = FATYPE_FACE;
 						f->u.face = g_strndup(&src[vstart+1], n-vstart-1);
 						if (!ftattr)
 							ftattr = g_queue_new();
@@ -527,7 +758,7 @@
 						fontattr *f;
 
 						f = g_new(fontattr, 1);
-						f->type = size;
+						f->type = FATYPE_SIZE;
 						f->u.size = POINT_SIZE(strtol(&src[vstart+1], NULL, 10));
 						if (!ftattr)
 							ftattr = g_queue_new();
@@ -538,7 +769,7 @@
 						fontattr *f;
 
 						f = g_new(fontattr, 1);
-						f->type = color;
+						f->type = FATYPE_COLOR;
 						f->u.color = g_strndup(&src[vstart+1], n-vstart-1);
 						if (!ftattr)
 							ftattr = g_queue_new();
@@ -549,7 +780,7 @@
 						fontattr *f;
 
 						f = g_new(fontattr, 1);
-						f->type = junk;
+						f->type = FATYPE_JUNK;
 						f->u.junk = g_strndup(&src[*j+1], n-*j);
 						if (!ftattr)
 							ftattr = g_queue_new();
@@ -566,56 +797,52 @@
 			*j = m;
 
 		if (src[m] == '>') {
-			gboolean needendtag = 0;
+			gboolean needendtag = FALSE;
 			fontattr *f;
 			GString *tmp = g_string_new(NULL);
-			char *colorstr;
 
 			if (!g_queue_is_empty(ftattr)) {
 				while ((f = g_queue_pop_tail(ftattr))) {
 					switch (f->type) {
-					case size:
+					case FATYPE_SIZE:
 						if (!needendtag) {
-							needendtag = 1;
+							needendtag = TRUE;
 							g_string_append(dest, "<font ");
 						}
 
 						g_string_append_printf(dest, "size=\"%d\" ", f->u.size);
-						fontattr_free(f);
 						break;
-					case face:
+					case FATYPE_FACE:
 						if (!needendtag) {
-							needendtag = 1;
+							needendtag = TRUE;
 							g_string_append(dest, "<font ");
 						}
 
 						g_string_append_printf(dest, "face=\"%s\" ", f->u.face);
-						fontattr_free(f);
 						break;
-					case junk:
+					case FATYPE_JUNK:
 						if (!needendtag) {
-							needendtag = 1;
+							needendtag = TRUE;
 							g_string_append(dest, "<font ");
 						}
 
 						g_string_append(dest, f->u.junk);
-						fontattr_free(f);
 						break;
 
-					case color:
+					case FATYPE_COLOR:
 						if (needendtag) {
 							g_string_append(tmp, "</font>");
 							dest->str[dest->len-1] = '>';
-							needendtag = 0;
+							needendtag = TRUE;
 						}
 
-						colorstr = g_queue_peek_tail(colors);
-						g_string_append(tmp, colorstr ? colorstr : "\033[#000000m");
+						g_string_append(tmp, *colors ? (*colors)->data : "\033[#000000m");
 						g_string_append_printf(dest, "\033[%sm", f->u.color);
-						g_queue_push_tail(colors, g_strdup_printf("\033[%sm", f->u.color));
-						fontattr_free(f);
+						*colors = g_slist_prepend(*colors,
+								g_strdup_printf("\033[%sm", f->u.color));
 						break;
 					}
+					fontattr_free(f);
 				}
 
 				g_queue_free(ftattr);
@@ -623,10 +850,10 @@
 
 				if (needendtag) {
 					dest->str[dest->len-1] = '>';
-					g_queue_push_tail(tags, g_strdup("</font>"));
+					*tags = g_slist_prepend(*tags, g_strdup("</font>"));
 					g_string_free(tmp, TRUE);
 				} else {
-					g_queue_push_tail(tags, tmp->str);
+					*tags = g_slist_prepend(*tags, tmp->str);
 					g_string_free(tmp, FALSE);
 				}
 			}
@@ -635,31 +862,31 @@
 			break;
 		}
 	}
-
 }
 
 char *yahoo_html_to_codes(const char *src)
 {
-	int i, j, len;
+	GSList *colors = NULL;
+	GSList *tags = NULL;
+	size_t src_len;
+	int i, j;
 	GString *dest;
-	char *ret, *esc;
-	GQueue *colors, *tags;
+	char *esc;
 	GQueue *ftattr = NULL;
 	gboolean no_more_specials = FALSE;
 
-	colors = g_queue_new();
-	tags = g_queue_new();
-	dest = g_string_sized_new(strlen(src));
+	src_len = strlen(src);
+	dest = g_string_sized_new(src_len);
 
-	for (i = 0, len = strlen(src); i < len; i++) {
+	for (i = 0; i < src_len; i++) {
 
-		if (!no_more_specials && src[i] == '<') {
+		if (src[i] == '<' && !no_more_specials) {
 			j = i;
 
 			while (1) {
 				j++;
 
-				if (j >= len) { /* no '>' */
+				if (j >= src_len) { /* no '>' */
 					g_string_append_c(dest, src[i]);
 					no_more_specials = TRUE;
 					break;
@@ -688,7 +915,7 @@
 						char *t = strchr(&src[j], '>');
 						if (!t) {
 							g_string_append(dest, &src[i]);
-							i = len;
+							i = src_len;
 							break;
 						} else {
 							i = t - src;
@@ -697,17 +924,19 @@
 					} else if (!g_ascii_strncasecmp(&src[i+1], "A HREF=\"", j - i - 1)) {
 						j += 7;
 						g_string_append(dest, "\033[lm");
+						if (purple_str_has_prefix(src + j, "mailto:"))
+							j += sizeof("mailto:") - 1;
 						while (1) {
 							g_string_append_c(dest, src[j]);
-							if (++j >= len) {
-								i = len;
+							if (++j >= src_len) {
+								i = src_len;
 								break;
 							}
 							if (src[j] == '"') {
 								g_string_append(dest, "\033[xlm");
 								while (1) {
-									if (++j >= len) {
-										i = len;
+									if (++j >= src_len) {
+										i = src_len;
 										break;
 									}
 									if (!g_ascii_strncasecmp(&src[j], "</A>", 4)) {
@@ -721,9 +950,9 @@
 						}
 					} else if (!g_ascii_strncasecmp(&src[i+1], "SPAN", j - i - 1)) { /* drop span tags */
 						while (1) {
-							if (++j >= len) {
+							if (++j >= src_len) {
 								g_string_append(dest, &src[i]);
-								i = len;
+								i = src_len;
 								break;
 							}
 							if (src[j] == '>') {
@@ -733,9 +962,9 @@
 						}
 					} else if (g_ascii_strncasecmp(&src[i+1], "FONT", j - i - 1)) { /* not interested! */
 						while (1) {
-							if (++j >= len) {
+							if (++j >= src_len) {
 								g_string_append(dest, &src[i]);
-								i = len;
+								i = src_len;
 								break;
 							}
 							if (src[j] == '>') {
@@ -745,7 +974,7 @@
 							}
 						}
 					} else { /* yay we have a font tag */
-						_parse_font_tag(src, dest, &i, &j, len, colors, tags, ftattr);
+						_parse_font_tag(src, dest, &i, &j, src_len, &colors, &tags, ftattr);
 					}
 
 					break;
@@ -779,16 +1008,18 @@
 							/* mmm, </body> tags. *BURP* */
 						} else if (!g_ascii_strncasecmp(&src[i+1], "/SPAN", sublen)) {
 							/* </span> tags. dangerously close to </spam> */
-						} else if (!g_ascii_strncasecmp(&src[i+1], "/FONT", sublen) && g_queue_peek_tail(tags)) {
-							char *etag, *cl;
+						} else if (!g_ascii_strncasecmp(&src[i+1], "/FONT", sublen) && tags != NULL) {
+							char *etag;
 
-							etag = g_queue_pop_tail(tags);
+							etag = tags->data;
+							tags = g_slist_delete_link(tags, tags);
 							if (etag) {
 								g_string_append(dest, etag);
 								if (!strcmp(etag, "</font>")) {
-									cl = g_queue_pop_tail(colors);
-									if (cl)
-										g_free(cl);
+									if (colors != NULL) {
+										g_free(colors->data);
+										colors = g_slist_delete_link(colors, colors);
+									}
 								}
 								g_free(etag);
 							}
@@ -806,36 +1037,26 @@
 			}
 
 		} else {
-			if (((len - i) >= 4) && !strncmp(&src[i], "&lt;", 4)) {
-				g_string_append_c(dest, '<');
-				i += 3;
-			} else if (((len - i) >= 4) && !strncmp(&src[i], "&gt;", 4)) {
-				g_string_append_c(dest, '>');
-				i += 3;
-			} else if (((len - i) >= 5) && !strncmp(&src[i], "&amp;", 5)) {
-				g_string_append_c(dest, '&');
-				i += 4;
-			} else if (((len - i) >= 6) && !strncmp(&src[i], "&quot;", 6)) {
-				g_string_append_c(dest, '"');
-				i += 5;
-			} else if (((len - i) >= 6) && !strncmp(&src[i], "&apos;", 6)) {
-				g_string_append_c(dest, '\'');
-				i += 5;
-			} else {
+			const char *entity;
+			int length;
+
+			entity = purple_markup_unescape_entity(src + i, &length);
+			if (entity != NULL) {
+				/* src[i] is the start of an HTML entity */
+				g_string_append(dest, entity);
+				i += length - 1;
+			} else
+				/* src[i] is a normal character */
 				g_string_append_c(dest, src[i]);
-			}
 		}
 	}
 
-	ret = dest->str;
-	g_string_free(dest, FALSE);
-
-	esc = g_strescape(ret, NULL);
+	esc = g_strescape(dest->str, NULL);
 	purple_debug_misc("yahoo", "yahoo_html_to_codes:  Returning string: '%s'.\n", esc);
 	g_free(esc);
 
-	yahoo_htc_queue_cleanup(colors);
-	yahoo_htc_queue_cleanup(tags);
+	yahoo_htc_list_cleanup(colors);
+	yahoo_htc_list_cleanup(tags);
 
-	return ret;
+	return g_string_free(dest, FALSE);
 }
--- a/libpurple/protocols/yahoo/yahoo_filexfer.c	Tue Jul 28 20:51:56 2009 +0000
+++ b/libpurple/protocols/yahoo/yahoo_filexfer.c	Thu Aug 13 17:15:06 2009 +0000
@@ -884,46 +884,48 @@
 
 	/* Build the file transfer handle. */
 	xfer = purple_xfer_new(gc->account, PURPLE_XFER_RECEIVE, from);
-	if (xfer)
-	{
-		xfer->data = xfer_data;
+	if (xfer == NULL) {
+		g_free(xfer_data);
+		g_return_if_reached();
+	}
+
+	xfer->data = xfer_data;
 
-		/* Set the info about the incoming file. */
-		if (filename) {
-			char *utf8_filename = yahoo_string_decode(gc, filename, TRUE);
+	/* Set the info about the incoming file. */
+	if (filename) {
+		char *utf8_filename = yahoo_string_decode(gc, filename, TRUE);
+		purple_xfer_set_filename(xfer, utf8_filename);
+		g_free(utf8_filename);
+	} else {
+		gchar *start, *end;
+		start = g_strrstr(xfer_data->path, "/");
+		if (start)
+			start++;
+		end = g_strrstr(xfer_data->path, "?");
+		if (start && *start && end) {
+			char *utf8_filename;
+			filename = g_strndup(start, end - start);
+			utf8_filename = yahoo_string_decode(gc, filename, TRUE);
+			g_free(filename);
 			purple_xfer_set_filename(xfer, utf8_filename);
 			g_free(utf8_filename);
-		} else {
-			gchar *start, *end;
-			start = g_strrstr(xfer_data->path, "/");
-			if (start)
-				start++;
-			end = g_strrstr(xfer_data->path, "?");
-			if (start && *start && end) {
-				char *utf8_filename;
-				filename = g_strndup(start, end - start);
-				utf8_filename = yahoo_string_decode(gc, filename, TRUE);
-				g_free(filename);
-				purple_xfer_set_filename(xfer, utf8_filename);
-				g_free(utf8_filename);
-				filename = NULL;
-			}
+			filename = NULL;
 		}
+	}
 
-		purple_xfer_set_size(xfer, filesize);
+	purple_xfer_set_size(xfer, filesize);
 
-		/* Setup our I/O op functions */
-		purple_xfer_set_init_fnc(xfer,        yahoo_xfer_init);
-		purple_xfer_set_start_fnc(xfer,       yahoo_xfer_start);
-		purple_xfer_set_end_fnc(xfer,         yahoo_xfer_end);
-		purple_xfer_set_cancel_send_fnc(xfer, yahoo_xfer_cancel_send);
-		purple_xfer_set_cancel_recv_fnc(xfer, yahoo_xfer_cancel_recv);
-		purple_xfer_set_read_fnc(xfer,        yahoo_xfer_read);
-		purple_xfer_set_write_fnc(xfer,       yahoo_xfer_write);
+	/* Setup our I/O op functions */
+	purple_xfer_set_init_fnc(xfer,        yahoo_xfer_init);
+	purple_xfer_set_start_fnc(xfer,       yahoo_xfer_start);
+	purple_xfer_set_end_fnc(xfer,         yahoo_xfer_end);
+	purple_xfer_set_cancel_send_fnc(xfer, yahoo_xfer_cancel_send);
+	purple_xfer_set_cancel_recv_fnc(xfer, yahoo_xfer_cancel_recv);
+	purple_xfer_set_read_fnc(xfer,        yahoo_xfer_read);
+	purple_xfer_set_write_fnc(xfer,       yahoo_xfer_write);
 
-		/* Now perform the request */
-		purple_xfer_request(xfer);
-	}
+	/* Now perform the request */
+	purple_xfer_request(xfer);
 }
 
 PurpleXfer *yahoo_new_xfer(PurpleConnection *gc, const char *who)
@@ -938,19 +940,22 @@
 
 	/* Build the file transfer handle. */
 	xfer = purple_xfer_new(gc->account, PURPLE_XFER_SEND, who);
-	if (xfer)
+	if (xfer == NULL)
 	{
-		xfer->data = xfer_data;
+		g_free(xfer_data);
+		g_return_val_if_reached(NULL);
+	}
+
+	xfer->data = xfer_data;
 
-		/* Setup our I/O op functions */
-		purple_xfer_set_init_fnc(xfer,        yahoo_xfer_init);
-		purple_xfer_set_start_fnc(xfer,       yahoo_xfer_start);
-		purple_xfer_set_end_fnc(xfer,         yahoo_xfer_end);
-		purple_xfer_set_cancel_send_fnc(xfer, yahoo_xfer_cancel_send);
-		purple_xfer_set_cancel_recv_fnc(xfer, yahoo_xfer_cancel_recv);
-		purple_xfer_set_read_fnc(xfer,        yahoo_xfer_read);
-		purple_xfer_set_write_fnc(xfer,       yahoo_xfer_write);
-	}
+	/* Setup our I/O op functions */
+	purple_xfer_set_init_fnc(xfer,        yahoo_xfer_init);
+	purple_xfer_set_start_fnc(xfer,       yahoo_xfer_start);
+	purple_xfer_set_end_fnc(xfer,         yahoo_xfer_end);
+	purple_xfer_set_cancel_send_fnc(xfer, yahoo_xfer_cancel_send);
+	purple_xfer_set_cancel_recv_fnc(xfer, yahoo_xfer_cancel_recv);
+	purple_xfer_set_read_fnc(xfer,        yahoo_xfer_read);
+	purple_xfer_set_write_fnc(xfer,       yahoo_xfer_write);
 
 	return xfer;
 }
@@ -1588,6 +1593,7 @@
 	char *service = NULL;
 	char *filename = NULL;
 	char *xfer_peer_idstring = NULL;
+	char *utf8_filename;
 	unsigned long filesize = 0L;
 	GSList *l;
 	GSList *filename_list = NULL;
@@ -1714,42 +1720,44 @@
 
 	/* Build the file transfer handle. */
 	xfer = purple_xfer_new(gc->account, PURPLE_XFER_RECEIVE, from);
+	if (xfer == NULL)
+	{
+		g_free(xfer_data);
+		g_return_if_reached();
+	}
+
 	xfer->message = NULL;
 
-	if (xfer)
-	{
-		/* Set the info about the incoming file. */
-		char *utf8_filename = yahoo_string_decode(gc, filename, TRUE);
-		purple_xfer_set_filename(xfer, utf8_filename);
-		g_free(utf8_filename);
-		purple_xfer_set_size(xfer, filesize);
+	/* Set the info about the incoming file. */
+	utf8_filename = yahoo_string_decode(gc, filename, TRUE);
+	purple_xfer_set_filename(xfer, utf8_filename);
+	g_free(utf8_filename);
+	purple_xfer_set_size(xfer, filesize);
 
-		xfer->data = xfer_data;
-
+	xfer->data = xfer_data;
 
-		/* Setup our I/O op functions */
-		purple_xfer_set_init_fnc(xfer,        yahoo_xfer_init_15);
-		purple_xfer_set_start_fnc(xfer,       yahoo_xfer_start);
-		purple_xfer_set_end_fnc(xfer,         yahoo_xfer_end);
-		purple_xfer_set_cancel_send_fnc(xfer, yahoo_xfer_cancel_send);
-		purple_xfer_set_cancel_recv_fnc(xfer, yahoo_xfer_cancel_recv);
-		purple_xfer_set_read_fnc(xfer,        yahoo_xfer_read);
-		purple_xfer_set_write_fnc(xfer,       yahoo_xfer_write);
-		purple_xfer_set_request_denied_fnc(xfer,yahoo_xfer_cancel_recv);
+	/* Setup our I/O op functions */
+	purple_xfer_set_init_fnc(xfer,        yahoo_xfer_init_15);
+	purple_xfer_set_start_fnc(xfer,       yahoo_xfer_start);
+	purple_xfer_set_end_fnc(xfer,         yahoo_xfer_end);
+	purple_xfer_set_cancel_send_fnc(xfer, yahoo_xfer_cancel_send);
+	purple_xfer_set_cancel_recv_fnc(xfer, yahoo_xfer_cancel_recv);
+	purple_xfer_set_read_fnc(xfer,        yahoo_xfer_read);
+	purple_xfer_set_write_fnc(xfer,       yahoo_xfer_write);
+	purple_xfer_set_request_denied_fnc(xfer,yahoo_xfer_cancel_recv);
 
-		g_hash_table_insert(yd->xfer_peer_idstring_map,
-							xfer_data->xfer_peer_idstring,
-							xfer);
+	g_hash_table_insert(yd->xfer_peer_idstring_map,
+						xfer_data->xfer_peer_idstring,
+						xfer);
 
-		if(nooffiles > 1) {
-			gchar* message;
-			message = g_strdup_printf(_("%s is trying to send you a group of %d files.\n"), xfer->who, nooffiles);
-			purple_xfer_conversation_write(xfer, message, FALSE);
-			g_free(message);
-		}
-		/* Now perform the request */
-		purple_xfer_request(xfer);
+	if(nooffiles > 1) {
+		gchar* message;
+		message = g_strdup_printf(_("%s is trying to send you a group of %d files.\n"), xfer->who, nooffiles);
+		purple_xfer_conversation_write(xfer, message, FALSE);
+		g_free(message);
 	}
+	/* Now perform the request */
+	purple_xfer_request(xfer);
 }
 
 void yahoo_process_filetrans_info_15(PurpleConnection *gc, struct yahoo_packet *pkt)
--- a/libpurple/protocols/yahoo/yahoo_packet.c	Tue Jul 28 20:51:56 2009 +0000
+++ b/libpurple/protocols/yahoo/yahoo_packet.c	Thu Aug 13 17:15:06 2009 +0000
@@ -397,7 +397,7 @@
 		struct yahoo_pair *pair = pkt->hash->data;
 		g_free(pair->value);
 		g_free(pair);
-		pkt->hash = g_slist_remove(pkt->hash, pair);
+		pkt->hash = g_slist_delete_link(pkt->hash, pkt->hash);
 	}
 	g_free(pkt);
 }
--- a/libpurple/protocols/zephyr/ZVariables.c	Tue Jul 28 20:51:56 2009 +0000
+++ b/libpurple/protocols/zephyr/ZVariables.c	Thu Aug 13 17:15:06 2009 +0000
@@ -18,27 +18,32 @@
 #include <pwd.h>
 #endif
 
-static int get_localvarfile __P((char *bfr));
+static char *get_localvarfile __P((void));
 static char *get_varval __P((char *fn, char *val));
 static int varline __P((char *bfr, char *var));
 
 char *ZGetVariable(var)
     char *var;
 {
-    char varfile[128], *ret;
+	char *varfile, *ret;
+
+	if ((varfile = get_localvarfile()) == NULL)
+		return ((char *)0);
 
-    if (get_localvarfile(varfile))
-	return ((char *)0);
-
-    if ((ret = get_varval(varfile, var)) != ZERR_NONE)
-	return (ret);
+	if ((ret = get_varval(varfile, var)) != ZERR_NONE) {
+		g_free(varfile);
+		return ret;
+	}
 
 #ifdef WIN32
-    sprintf(varfile, "C:\\zephyr\\zephyr.var");
+	varfile = g_strdup("C:\\zephyr\\zephyr.var");
 #else
-    sprintf(varfile, "%s/zephyr.vars", CONFDIR);
+	varfile = g_strdup_printf("%s/zephyr.vars", CONFDIR);
 #endif
-    return (get_varval(varfile, var));
+	ret = get_varval(varfile, var);
+	g_free(varfile);
+
+	return ret;
 }
 
 Code_t ZSetVariable(var, value)
@@ -47,18 +52,20 @@
 {
     int written;
     FILE *fpin, *fpout;
-    char varfile[128], varfilebackup[128], varbfr[512];
+    char *varfile, *varfilebackup, varbfr[512];
 
     written = 0;
 	
-    if (get_localvarfile(varfile))
+    if ((varfile = get_localvarfile()) == NULL)
 	return (ZERR_INTERNAL);
 
-    (void) strcpy(varfilebackup, varfile);
-    (void) strcat(varfilebackup, ".backup");
+    varfilebackup = g_strconcat(varfile, ".backup", NULL);
 	
-    if (!(fpout = fopen(varfilebackup, "w")))
+    if (!(fpout = fopen(varfilebackup, "w"))) {
+	g_free(varfile);
+	g_free(varfilebackup);
 	return (errno);
+    }
     if ((fpin = fopen(varfile, "r")) != NULL) {
 	while (fgets(varbfr, sizeof varbfr, fpin) != (char *) 0) {
 	    if (varbfr[strlen(varbfr)-1] < ' ')
@@ -74,10 +81,18 @@
     } 
     if (!written)
 	fprintf(fpout, "%s = %s\n", var, value);
-    if (fclose(fpout) == EOF)
-	    return(EIO);		/* can't rely on errno */
-    if (rename(varfilebackup, varfile))
+    if (fclose(fpout) == EOF) {
+    	g_free(varfilebackup);
+    	g_free(varfile);
+	return(EIO);		/* can't rely on errno */
+    }
+    if (rename(varfilebackup, varfile)) {
+	g_free(varfilebackup);
+	g_free(varfile);
 	return (errno);
+    }
+    g_free(varfilebackup);
+    g_free(varfile);
     return (ZERR_NONE);
 }	
 
@@ -85,16 +100,18 @@
     char *var;
 {
     FILE *fpin, *fpout;
-    char varfile[128], varfilebackup[128], varbfr[512];
+    char *varfile, *varfilebackup, varbfr[512];
 
-    if (get_localvarfile(varfile))
+    if ((varfile = get_localvarfile()) == NULL)
 	return (ZERR_INTERNAL);
 
-    (void) strcpy(varfilebackup, varfile);
-    (void) strcat(varfilebackup, ".backup");
+    varfilebackup = g_strconcat(varfile, ".backup", NULL);
 	
-    if (!(fpout = fopen(varfilebackup, "w")))
+    if (!(fpout = fopen(varfilebackup, "w"))) {
+	g_free(varfile);
+	g_free(varfilebackup);
 	return (errno);
+    }
     if ((fpin = fopen(varfile, "r")) != NULL) {
 	while (fgets(varbfr, sizeof varbfr, fpin) != (char *) 0) {
 	    if (varbfr[strlen(varbfr)-1] < ' ')
@@ -104,42 +121,45 @@
 	}
 	(void) fclose(fpin);		/* don't care about read close errs */
     } 
-    if (fclose(fpout) == EOF)
-	    return(EIO);		/* errno isn't reliable */
-    if (rename(varfilebackup, varfile))
+    if (fclose(fpout) == EOF) {
+	g_free(varfilebackup);
+	g_free(varfile);
+	return(EIO);		/* errno isn't reliable */
+    }
+    if (rename(varfilebackup, varfile)) {
+	g_free(varfilebackup);
+	g_free(varfile);
 	return (errno);
+    }
+    g_free(varfilebackup);
+    g_free(varfile);
     return (ZERR_NONE);
 }	
 
-static int get_localvarfile(bfr)
-    char *bfr;
+static char *get_localvarfile(void)
 {
-    const char *envptr;
+    const char *base;
 #ifndef WIN32
     struct passwd *pwd;
-    envptr = purple_home_dir();
+    base = purple_home_dir();
 #else
-    envptr = getenv("HOME");
-    if (!envptr)
-        envptr = getenv("HOMEPATH");
-    if (!envptr) 
-        envptr = "C:\\";
+    base = getenv("HOME");
+    if (!base)
+        base = getenv("HOMEPATH");
+    if (!base) 
+        base = "C:\\";
 #endif
-    if (envptr)
-	(void) strcpy(bfr, envptr);
-    else {
+    if (!base) {
 #ifndef WIN32
 	if (!(pwd = getpwuid((int) getuid()))) {
 	    fprintf(stderr, "Zephyr internal failure: Can't find your entry in /etc/passwd\n");
-	    return (1);
+	    return NULL;
 	}
-	(void) strcpy(bfr, pwd->pw_dir);
+	base = pwd->pw_dir;
 #endif
     }
 
-    (void) strcat(bfr, "/");
-    (void) strcat(bfr, ".zephyr.vars");
-    return (0);
+    return g_strconcat(base, "/.zephyr.vars", NULL);
 } 
 	
 static char *get_varval(fn, var)
--- a/libpurple/protocols/zephyr/internal.h	Tue Jul 28 20:51:56 2009 +0000
+++ b/libpurple/protocols/zephyr/internal.h	Thu Aug 13 17:15:06 2009 +0000
@@ -23,6 +23,12 @@
 
 #define ETIMEDOUT WSAETIMEDOUT
 #define EADDRINUSE WSAEADDRINUSE
+#else /* !WIN32 */
+
+#ifndef MAXHOSTNAMELEN
+#define MAXHOSTNAMELEN 4096
+#endif
+
 #endif
 
 #ifdef ZEPHYR_USES_HESIOD
--- a/libpurple/proxy.c	Tue Jul 28 20:51:56 2009 +0000
+++ b/libpurple/proxy.c	Thu Aug 13 17:15:06 2009 +0000
@@ -2531,4 +2531,9 @@
 		purple_proxy_connect_data_disconnect(handles->data, NULL);
 		purple_proxy_connect_data_destroy(handles->data);
 	}
+
+	purple_prefs_disconnect_by_handle(purple_proxy_get_handle());
+
+	purple_proxy_info_destroy(global_proxy_info);
+	global_proxy_info = NULL;
 }
--- a/libpurple/purple.h.in	Tue Jul 28 20:51:56 2009 +0000
+++ b/libpurple/purple.h.in	Thu Aug 13 17:15:06 2009 +0000
@@ -65,11 +65,13 @@
 #include <idle.h>
 #include <imgstore.h>
 #include <log.h>
+#include <media.h>
+#include <mediamanager.h>
 #include <mime.h>
 #include <nat-pmp.h>
 #include <network.h>
+#include <notify.h>
 #include <ntlm.h>
-#include <notify.h>
 #include <plugin.h>
 #include <pluginpref.h>
 #include <pounce.h>
@@ -82,16 +84,22 @@
 #include <savedstatuses.h>
 #include <server.h>
 #include <signals.h>
+#include <smiley.h>
+#include <sound.h>
+#include <sound-theme.h>
+#include <sound-theme-loader.h>
+#include <sslconn.h>
 #include <status.h>
 #include <stringref.h>
 #include <stun.h>
-#include <sound.h>
-#include <sslconn.h>
+#include <theme.h>
+#include <theme-loader.h>
+#include <theme-manager.h>
 #include <upnp.h>
 #include <util.h>
 #include <value.h>
 #include <version.h>
+#include <whiteboard.h>
 #include <xmlnode.h>
-#include <whiteboard.h>
 
 #endif
--- a/libpurple/savedstatuses.c	Tue Jul 28 20:51:56 2009 +0000
+++ b/libpurple/savedstatuses.c	Thu Aug 13 17:15:06 2009 +0000
@@ -1235,6 +1235,8 @@
 void
 purple_savedstatuses_uninit(void)
 {
+	gpointer handle = purple_savedstatuses_get_handle();
+
 	remove_old_transient_statuses();
 
 	if (save_timer != 0)
@@ -1253,6 +1255,7 @@
 	g_hash_table_destroy(creation_times);
 	creation_times = NULL;
 
-	purple_signals_unregister_by_instance(purple_savedstatuses_get_handle());
+	purple_signals_unregister_by_instance(handle);
+	purple_signals_disconnect_by_handle(handle);
 }
 
--- a/libpurple/signals.c	Tue Jul 28 20:51:56 2009 +0000
+++ b/libpurple/signals.c	Thu Aug 13 17:15:06 2009 +0000
@@ -363,8 +363,8 @@
 		{
 			g_free(handler_data);
 
-			signal_data->handlers = g_list_remove(signal_data->handlers,
-												  handler_data);
+			signal_data->handlers = g_list_delete_link(signal_data->handlers,
+												       l);
 			signal_data->handler_count--;
 
 			found = TRUE;
@@ -398,8 +398,8 @@
 			g_free(handler_data);
 
 			signal_data->handler_count--;
-			signal_data->handlers = g_list_remove(signal_data->handlers,
-												  handler_data);
+			signal_data->handlers = g_list_delete_link(signal_data->handlers,
+			                                           l);
 		}
 	}
 }
--- a/libpurple/status.c	Tue Jul 28 20:51:56 2009 +0000
+++ b/libpurple/status.c	Thu Aug 13 17:15:06 2009 +0000
@@ -1660,7 +1660,7 @@
 void
 purple_status_init(void)
 {
-	void *handle = purple_status_get_handle;
+	void *handle = purple_status_get_handle();
 
 	purple_prefs_add_none("/purple/status");
 	purple_prefs_add_none("/purple/status/scores");
@@ -1714,4 +1714,5 @@
 void
 purple_status_uninit(void)
 {
+	purple_prefs_disconnect_by_handle(purple_prefs_get_handle());
 }
--- a/libpurple/tests/Makefile.am	Tue Jul 28 20:51:56 2009 +0000
+++ b/libpurple/tests/Makefile.am	Thu Aug 13 17:15:06 2009 +0000
@@ -11,6 +11,7 @@
 	    tests.h \
 		test_cipher.c \
 		test_jabber_jutil.c \
+		test_yahoo_util.c \
 		test_util.c \
 		$(top_builddir)/libpurple/util.h
 
@@ -27,6 +28,7 @@
         @CHECK_LIBS@ \
 		$(GLIB_LIBS) \
 		$(top_builddir)/libpurple/protocols/jabber/libjabber.la \
+		$(top_builddir)/libpurple/protocols/yahoo/libymsg.la \
 		$(top_builddir)/libpurple/libpurple.la
 
 endif
--- a/libpurple/tests/check_libpurple.c	Tue Jul 28 20:51:56 2009 +0000
+++ b/libpurple/tests/check_libpurple.c	Thu Aug 13 17:15:06 2009 +0000
@@ -76,6 +76,7 @@
 
 	srunner_add_suite(sr, cipher_suite());
 	srunner_add_suite(sr, jabber_jutil_suite());
+	srunner_add_suite(sr, yahoo_util_suite());
 	srunner_add_suite(sr, util_suite());
 
 	/* make this a libpurple "ui" */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/tests/test_yahoo_util.c	Thu Aug 13 17:15:06 2009 +0000
@@ -0,0 +1,110 @@
+#include <string.h>
+
+#include "tests.h"
+#include "../protocols/yahoo/libymsg.h"
+
+static void setup_codes_to_html(void)
+{
+	yahoo_init_colorht();
+}
+
+static void teardown_codes_to_html(void)
+{
+	yahoo_dest_colorht();
+}
+
+START_TEST(test_codes_to_html)
+{
+	assert_string_equal_free("plain",
+			yahoo_codes_to_html("plain"));
+	assert_string_equal_free("unknown  ansi code",
+			yahoo_codes_to_html("unknown \x1B[12345m ansi code"));
+	assert_string_equal_free("plain &lt;peanut&gt;",
+			yahoo_codes_to_html("plain <peanut>"));
+	assert_string_equal_free("plain &lt;peanut",
+			yahoo_codes_to_html("plain <peanut"));
+	assert_string_equal_free("plain&gt; peanut",
+			yahoo_codes_to_html("plain> peanut"));
+
+	/* bold/italic/underline */
+	assert_string_equal_free("<b>bold</b>",
+			yahoo_codes_to_html("\x1B[1mbold"));
+	assert_string_equal_free("<i>italic</i>",
+			yahoo_codes_to_html("\x1B[2mitalic"));
+	assert_string_equal_free("<u>underline</u>",
+			yahoo_codes_to_html("\x1B[4munderline"));
+	assert_string_equal_free("no markup",
+			yahoo_codes_to_html("no\x1B[x4m markup"));
+	assert_string_equal_free("<b>bold</b> <i>italic</i> <u>underline</u>",
+			yahoo_codes_to_html("\x1B[1mbold\x1B[x1m \x1B[2mitalic\x1B[x2m \x1B[4munderline"));
+	assert_string_equal_free("<b>bold <i>bolditalic</i></b><i> italic</i>",
+			yahoo_codes_to_html("\x1B[1mbold \x1B[2mbolditalic\x1B[x1m italic"));
+	assert_string_equal_free("<b>bold <i>bolditalic</i></b><i> <u>italicunderline</u></i>",
+			yahoo_codes_to_html("\x1B[1mbold \x1B[2mbolditalic\x1B[x1m \x1B[4mitalicunderline"));
+	assert_string_equal_free("<b>bold <i>bolditalic <u>bolditalicunderline</u></i><u> boldunderline</u></b>",
+			yahoo_codes_to_html("\x1B[1mbold \x1B[2mbolditalic \x1B[4mbolditalicunderline\x1B[x2m boldunderline"));
+	assert_string_equal_free("<b>bold <i>bolditalic <u>bolditalicunderline</u></i></b><i><u> italicunderline</u></i>",
+			yahoo_codes_to_html("\x1B[1mbold \x1B[2mbolditalic \x1B[4mbolditalicunderline\x1B[x1m italicunderline"));
+
+#ifdef USE_CSS_FORMATTING
+	/* font color */
+	assert_string_equal_free("<span style='color: #0000FF'>blue</span>",
+			yahoo_codes_to_html("\x1B[31mblue"));
+	assert_string_equal_free("<span style='color: #70ea15'>custom color</span>",
+			yahoo_codes_to_html("\x1B[#70ea15mcustom color"));
+
+	/* font face */
+	assert_string_equal_free("<font face='Georgia'>test</font>",
+			yahoo_codes_to_html("<font face='Georgia'>test</font>"));
+
+	/* font size */
+	assert_string_equal_free("<font><span style='font-size: 15pt'>test</span></font>",
+			yahoo_codes_to_html("<font size='15'>test"));
+	assert_string_equal_free("<font><span style='font-size: 32pt'>size 32</span></font>",
+			yahoo_codes_to_html("<font size='32'>size 32"));
+
+	/* combinations */
+	assert_string_equal_free("<font face='Georgia'><span style='font-size: 32pt'>test</span></font>",
+			yahoo_codes_to_html("<font face='Georgia' size='32'>test"));
+	assert_string_equal_free("<span style='color: #FF0080'><font><span style='font-size: 15pt'>test</span></font></span>",
+			yahoo_codes_to_html("\x1B[35m<font size='15'>test"));
+#else
+	/* font color */
+	assert_string_equal_free("<font color='#0000FF'>blue</font>",
+			yahoo_codes_to_html("\x1B[31mblue"));
+	assert_string_equal_free("<font color='#70ea15'>custom color</font>",
+			yahoo_codes_to_html("\x1B[#70ea15mcustom color"));
+	assert_string_equal_free("test",
+			yahoo_codes_to_html("<ALT #ff0000,#00ff00,#0000ff>test</ALT>"));
+
+	/* font face */
+	assert_string_equal_free("<font face='Georgia'>test</font>",
+			yahoo_codes_to_html("<font face='Georgia'>test"));
+
+	/* font size */
+	assert_string_equal_free("<font size='4' absz='15'>test</font>",
+			yahoo_codes_to_html("<font size='15'>test"));
+	assert_string_equal_free("<font size='6' absz='32'>size 32</font>",
+			yahoo_codes_to_html("<font size='32'>size 32"));
+
+	/* combinations */
+	assert_string_equal_free("<font face='Georgia' size='6' absz='32'>test</font>",
+			yahoo_codes_to_html("<font face='Georgia' size='32'>test"));
+	assert_string_equal_free("<font color='#FF0080'><font size='4' absz='15'>test</font></font>",
+			yahoo_codes_to_html("\x1B[35m<font size='15'>test"));
+#endif /* !USE_CSS_FORMATTING */
+}
+END_TEST
+
+Suite *
+yahoo_util_suite(void)
+{
+	Suite *s = suite_create("Yahoo Utility Functions");
+
+	TCase *tc = tcase_create("Convert to Numeric");
+	tcase_add_unchecked_fixture(tc, setup_codes_to_html, teardown_codes_to_html);
+	tcase_add_test(tc, test_codes_to_html);
+	suite_add_tcase(s, tc);
+
+	return s;
+}
--- a/libpurple/tests/tests.h	Tue Jul 28 20:51:56 2009 +0000
+++ b/libpurple/tests/tests.h	Thu Aug 13 17:15:06 2009 +0000
@@ -10,6 +10,7 @@
 Suite * master_suite(void);
 Suite * cipher_suite(void);
 Suite * jabber_jutil_suite(void);
+Suite * yahoo_util_suite(void);
 Suite * util_suite(void);
 
 /* helper macros */
--- a/libpurple/util.c	Tue Jul 28 20:51:56 2009 +0000
+++ b/libpurple/util.c	Thu Aug 13 17:15:06 2009 +0000
@@ -3519,6 +3519,7 @@
 void purple_got_protocol_handler_uri(const char *uri)
 {
 	char proto[11];
+	char delimiter;
 	const char *tmp, *param_string;
 	char *cmd;
 	GHashTable *params = NULL;
@@ -3534,7 +3535,13 @@
 	proto[len] = '\0';
 
 	tmp++;
-	purple_debug_info("util", "Processing message '%s' for protocol '%s'.\n", tmp, proto);
+
+	if (g_str_equal(proto, "xmpp"))
+		delimiter = ';';
+	else
+		delimiter = '&';
+
+	purple_debug_info("util", "Processing message '%s' for protocol '%s' using delimiter '%c'.\n", tmp, proto, delimiter);
 
 	if ((param_string = strchr(tmp, '?'))) {
 		const char *keyend = NULL, *pairstart;
@@ -3547,7 +3554,7 @@
 		pairstart = tmp = param_string;
 
 		while (*tmp || *pairstart) {
-			if (*tmp == '&' || !(*tmp)) {
+			if (*tmp == delimiter || !(*tmp)) {
 				/* If there is no explicit value */
 				if (keyend == NULL)
 					keyend = tmp;
--- a/libpurple/xmlnode.h	Tue Jul 28 20:51:56 2009 +0000
+++ b/libpurple/xmlnode.h	Thu Aug 13 17:15:06 2009 +0000
@@ -53,10 +53,10 @@
 	XMLNodeType type;		/**< The type of the node. */
 	char *data;			/**< The data for the node. */
 	size_t data_sz;			/**< The size of the data. */
-	struct _xmlnode *parent;	/**< The parent node or @c NULL.*/
-	struct _xmlnode *child;		/**< The child node or @c NULL.*/
-	struct _xmlnode *lastchild;	/**< The last child node or @c NULL.*/
-	struct _xmlnode *next;		/**< The next node or @c NULL. */
+	xmlnode *parent;            /**< The parent node or @c NULL.*/
+	xmlnode *child;             /**< The child node or @c NULL.*/
+	xmlnode *lastchild;         /**< The last child node or @c NULL.*/
+	xmlnode *next;              /**< The next node or @c NULL. */
 	char *prefix;               /**< The namespace prefix if any. */
 	GHashTable *namespace_map;  /**< The namespace map. */
 };
--- a/pidgin.spec.in	Tue Jul 28 20:51:56 2009 +0000
+++ b/pidgin.spec.in	Thu Aug 13 17:15:06 2009 +0000
@@ -251,6 +251,7 @@
 rm -f $RPM_BUILD_ROOT%{_libdir}/purple-2/*.la
 rm -f $RPM_BUILD_ROOT%{_libdir}/purple-2/liboscar.so
 rm -f $RPM_BUILD_ROOT%{_libdir}/purple-2/libjabber.so
+rm -f $RPM_BUILD_ROOT%{_libdir}/purple-2/libymsg.so
 rm -f $RPM_BUILD_ROOT%{_libdir}/*.la
 rm -f $RPM_BUILD_ROOT%{perl_archlib}/perllocal.pod
 find $RPM_BUILD_ROOT -type f -name '*.a' -exec rm -f {} ';'
--- a/pidgin/gtkblist.c	Tue Jul 28 20:51:56 2009 +0000
+++ b/pidgin/gtkblist.c	Thu Aug 13 17:15:06 2009 +0000
@@ -3560,7 +3560,7 @@
 			}
 
 			g_free(pce);
-			cur = g_list_remove(cur, pce);
+			cur = g_list_delete_link(cur, cur);
 		}
 	}
 	else if (PURPLE_BLIST_NODE_IS_CONTACT(node) || PURPLE_BLIST_NODE_IS_BUDDY(node))
--- a/pidgin/gtkconv.c	Tue Jul 28 20:51:56 2009 +0000
+++ b/pidgin/gtkconv.c	Thu Aug 13 17:15:06 2009 +0000
@@ -3326,6 +3326,62 @@
 }
 
 static void
+regenerate_media_items(PidginWindow *win)
+{
+#ifdef USE_VV
+	PurpleAccount *account;
+	PurpleConversation *conv;
+
+	conv = pidgin_conv_window_get_active_conversation(win);
+
+	if (conv == NULL) {
+		purple_debug_error("gtkconv", "couldn't get active conversation"
+				" when regenerating media items\n");
+		return;
+	}
+
+	account = purple_conversation_get_account(conv);
+
+	if (account == NULL) {
+		purple_debug_error("gtkconv", "couldn't get account when"
+				" regenerating media items\n");
+		return;
+	}
+
+	/*
+	 * Check if account support voice and/or calls, and
+	 * if the current buddy	supports it.
+	 */
+	if (account != NULL && purple_conversation_get_type(conv)
+			== PURPLE_CONV_TYPE_IM) {
+		PurpleMediaCaps caps =
+				purple_prpl_get_media_caps(account,
+				purple_conversation_get_name(conv));
+
+		gtk_widget_set_sensitive(win->audio_call,
+				caps & PURPLE_MEDIA_CAPS_AUDIO
+				? TRUE : FALSE);
+		gtk_widget_set_sensitive(win->video_call,
+				caps & PURPLE_MEDIA_CAPS_VIDEO
+				? TRUE : FALSE);
+		gtk_widget_set_sensitive(win->audio_video_call, 
+				caps & PURPLE_MEDIA_CAPS_AUDIO_VIDEO
+				? TRUE : FALSE);
+	} else if (purple_conversation_get_type(conv)
+			== PURPLE_CONV_TYPE_CHAT) {
+		/* for now, don't care about chats... */
+		gtk_widget_set_sensitive(win->audio_call, FALSE);
+		gtk_widget_set_sensitive(win->video_call, FALSE);
+		gtk_widget_set_sensitive(win->audio_video_call, FALSE);
+	} else {
+		gtk_widget_set_sensitive(win->audio_call, FALSE);
+		gtk_widget_set_sensitive(win->video_call, FALSE);
+		gtk_widget_set_sensitive(win->audio_video_call, FALSE);
+	}							
+#endif
+}
+
+static void
 regenerate_options_items(PidginWindow *win)
 {
 	GtkWidget *menu;
@@ -3410,6 +3466,7 @@
 static void menubar_activated(GtkWidget *item, gpointer data)
 {
 	PidginWindow *win = data;
+	regenerate_media_items(win);
 	regenerate_options_items(win);
 	regenerate_plugins_items(win);
 
@@ -4308,7 +4365,11 @@
 		return;
 	}
 
-	gtk_entry_set_text(GTK_ENTRY(gtkchat->topic_text), current_topic);
+	if (current_topic)
+		gtk_entry_set_text(GTK_ENTRY(gtkchat->topic_text), current_topic);
+	else
+		gtk_entry_set_text(GTK_ENTRY(gtkchat->topic_text), "");
+
 	prpl_info->set_chat_topic(gc, purple_conv_chat_get_id(PURPLE_CONV_CHAT(conv)),
 			new_topic);
 
@@ -5376,6 +5437,7 @@
 {
 	PurpleConversationUiOps *ui_ops = pidgin_conversations_get_conv_ui_ops();
 	gboolean hide = FALSE;
+	guint timer;
 
 	/* create hidden conv if hide_new pref is always */
 	if (strcmp(purple_prefs_get_string(PIDGIN_PREFS_ROOT "/conversations/im/hide_new"), "always") == 0)
@@ -5399,6 +5461,13 @@
 		purple_conversation_new(PURPLE_CONV_TYPE_IM, account, sender);
 		ui_ops->create_conversation = pidgin_conv_new;
 	}
+
+	/* Somebody wants to keep this conversation around, so don't time it out */
+	timer = GPOINTER_TO_INT(purple_conversation_get_data(conv, "close-timer"));
+	if (timer) {
+		purple_timeout_remove(timer);
+		purple_conversation_set_data(conv, "close-timer", GINT_TO_POINTER(0));
+	}
 }
 
 static void
@@ -6484,36 +6553,6 @@
 			buttons |= GTK_IMHTML_CUSTOM_SMILEY;
 		else
 			buttons &= ~GTK_IMHTML_CUSTOM_SMILEY;
-
-#ifdef USE_VV
-		/* check if account support voice calls, and if the current buddy
-			supports it */
-		if (account != NULL && purple_conversation_get_type(conv)
-					== PURPLE_CONV_TYPE_IM) {
-			PurpleMediaCaps caps =
-					purple_prpl_get_media_caps(account,
-					purple_conversation_get_name(conv));
-
-			gtk_widget_set_sensitive(win->audio_call,
-					caps & PURPLE_MEDIA_CAPS_AUDIO
-					? TRUE : FALSE);
-			gtk_widget_set_sensitive(win->video_call,
-					caps & PURPLE_MEDIA_CAPS_VIDEO
-					? TRUE : FALSE);
-			gtk_widget_set_sensitive(win->audio_video_call, 
-					caps & PURPLE_MEDIA_CAPS_AUDIO_VIDEO
-					? TRUE : FALSE);
-		} else if (purple_conversation_get_type(conv) == PURPLE_CONV_TYPE_CHAT) {
-			/* for now, don't care about chats... */
-			gtk_widget_set_sensitive(win->audio_call, FALSE);
-			gtk_widget_set_sensitive(win->video_call, FALSE);
-			gtk_widget_set_sensitive(win->audio_video_call, FALSE);
-		} else {
-			gtk_widget_set_sensitive(win->audio_call, FALSE);
-			gtk_widget_set_sensitive(win->video_call, FALSE);
-			gtk_widget_set_sensitive(win->audio_video_call, FALSE);
-		}							
-#endif
 		
 		gtk_imhtml_set_format_functions(GTK_IMHTML(gtkconv->entry), buttons);
 		if (account != NULL)
--- a/pidgin/gtkdialogs.c	Tue Jul 28 20:51:56 2009 +0000
+++ b/pidgin/gtkdialogs.c	Thu Aug 13 17:15:06 2009 +0000
@@ -170,8 +170,9 @@
 	{N_("Esperanto"),           "eo", "Stéphane Fillod", "fillods@users.sourceforge.net"},
 	{N_("Spanish"),             "es", "Javier Fernández-Sanguino Peña", "jfs@debian.org"},
 	{N_("Estonian"),            "et", "Ivar Smolin", "okul@linux.ee"},
+	{N_("Euskera(Basque)"),     "eu", "Mikel Pascual Aldabaldetreku", "mikel.paskual@gmail.com"},
+	{N_("Euskera(Basque)"),     "eu", "Iñaki Larrañaga Murgoitio", "dooteo@zundan.com"},
 	{N_("Euskera(Basque)"),     "eu", "Hizkuntza Politikarako Sailburuordetza", "hizkpol@ej-gv.es"},
-	{N_("Euskera(Basque)"),     "eu", "Iñaki Larrañaga Murgoitio", "dooteo@zundan.com"},
 	{N_("Persian"),             "fa", "Elnaz Sarbar", "elnaz@farsiweb.info"},
 	{N_("Persian"),             "fa", "Meelad Zakaria", "meelad@farsiweb.info"},
 	{N_("Persian"),             "fa", "Roozbeh Pournader ", "roozbeh@farsiweb.info"},
@@ -213,6 +214,7 @@
 	{N_("Portuguese-Brazil"),   "pt_BR", "Rodrigo Luiz Marques Flores", "rodrigomarquesflores@gmail.com"},
 	{N_("Pashto"),              "ps", "Kashif Masood", "masudmails@yahoo.com"},
 	{N_("Romanian"),            "ro", "Mişu Moldovan", "dumol@gnome.ro"},
+	{N_("Romanian"),            "ro", "Andrei Popescu", "andreimpopescu@gmail.com"},
 	{N_("Russian"),             "ru", "Антон Самохвалов", "samant.ua@mail.ru"},
 	{N_("Slovak"),              "sk", "Jozef Káčer", "quickparser@gmail.com"},
 	{N_("Slovak"),              "sk", "loptosko", "loptosko@gmail.com"},
--- a/pidgin/gtkimhtml.c	Tue Jul 28 20:51:56 2009 +0000
+++ b/pidgin/gtkimhtml.c	Thu Aug 13 17:15:06 2009 +0000
@@ -2953,7 +2953,7 @@
 							font->size = oldfont->size;
 						else
 							font->size = 3;
-						if ((imhtml->format_functions & (GTK_IMHTML_GROW|GTK_IMHTML_SHRINK)))
+						if ((imhtml->format_functions & (GTK_IMHTML_GROW|GTK_IMHTML_SHRINK)) && (font->size != 3 || (oldfont && oldfont->size == 3)))
 							gtk_imhtml_font_set_size(imhtml, font->size);
 						g_free(size);
 						fonts = g_slist_prepend (fonts, font);
@@ -5838,9 +5838,9 @@
 		if (activate) {
 			return FALSE;
 		}
+		klass->protocols = g_list_remove(klass->protocols, proto);
 		g_free(proto->name);
 		g_free(proto);
-		klass->protocols = g_list_remove(klass->protocols, proto);
 		return TRUE;
 	} else if (!activate) {
 		return FALSE;
--- a/pidgin/gtkmedia.c	Tue Jul 28 20:51:56 2009 +0000
+++ b/pidgin/gtkmedia.c	Thu Aug 13 17:15:06 2009 +0000
@@ -82,14 +82,14 @@
 {
 	PurpleMedia *media;
 	gchar *screenname;
-	GstElement *send_level;
-	GstElement *recv_level;
+	gulong level_handler_id;
 
 	GtkItemFactory *item_factory;
 	GtkWidget *menubar;
 	GtkWidget *statusbar;
 
 	GtkWidget *mute;
+	GtkWidget *pause;
 
 	GtkWidget *send_progress;
 	GtkWidget *recv_progress;
@@ -99,6 +99,7 @@
 	GtkWidget *display;
 	GtkWidget *send_widget;
 	GtkWidget *recv_widget;
+	GtkWidget *button_widget;
 	GtkWidget *local_video;
 	GtkWidget *remote_video;
 
@@ -129,9 +130,7 @@
 enum {
 	PROP_0,
 	PROP_MEDIA,
-	PROP_SCREENNAME,
-	PROP_SEND_LEVEL,
-	PROP_RECV_LEVEL
+	PROP_SCREENNAME
 };
 
 static GType
@@ -182,18 +181,6 @@
 			"The screenname of the user this session is with.",
 			NULL,
 			G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE));
-	g_object_class_install_property(gobject_class, PROP_SEND_LEVEL,
-			g_param_spec_object("send-level",
-			"Send level",
-			"The GstElement of this media's send 'level'",
-			GST_TYPE_ELEMENT,
-			G_PARAM_READWRITE));
-	g_object_class_install_property(gobject_class, PROP_RECV_LEVEL,
-			g_param_spec_object("recv-level",
-			"Receive level",
-			"The GstElement of this media's recv 'level'",
-			GST_TYPE_ELEMENT,
-			G_PARAM_READWRITE));
 
 	g_type_class_add_private(klass, sizeof(PidginMediaPrivate));
 }
@@ -207,6 +194,15 @@
 			NULL, NULL, TRUE);
 }
 
+static void
+pidgin_media_pause_toggled(GtkToggleButton *toggle, PidginMedia *media)
+{
+	purple_media_stream_info(media->priv->media,
+			gtk_toggle_button_get_active(toggle) ?
+			PURPLE_MEDIA_INFO_PAUSE : PURPLE_MEDIA_INFO_UNPAUSE,
+			NULL, NULL, TRUE);
+}
+
 static gboolean
 pidgin_media_delete_event_cb(GtkWidget *widget,
 		GdkEvent *event, PidginMedia *media)
@@ -338,45 +334,16 @@
 			G_CALLBACK(pidgin_media_delete_event_cb), media);
 }
 
-static gboolean
-level_message_cb(GstBus *bus, GstMessage *message, PidginMedia *gtkmedia)
+static void
+level_message_cb(PurpleMedia *media, gchar *session_id, gchar *participant,
+		double level, PidginMedia *gtkmedia)
 {
-	gdouble rms_db;
-	gdouble percent;
-	const GValue *list;
-	const GValue *value;
-
-	GstElement *src = GST_ELEMENT(GST_MESSAGE_SRC(message));
 	GtkWidget *progress;
-
-	if (message->type != GST_MESSAGE_ELEMENT)
-		return TRUE;
-
-	if (!gst_structure_has_name(
-			gst_message_get_structure(message), "level"))
-		return TRUE;
-
-	if (src == gtkmedia->priv->send_level)
+	if (participant == NULL)
 		progress = gtkmedia->priv->send_progress;
-	else if (src == gtkmedia->priv->recv_level)
+	else
 		progress = gtkmedia->priv->recv_progress;
-	else
-		return TRUE;
-
-	list = gst_structure_get_value(
-			gst_message_get_structure(message), "rms");
-
-	/* Only bother with the first channel. */
-	value = gst_value_list_get_value(list, 0);
-	rms_db = g_value_get_double(value);
-
-	percent = pow(10, rms_db / 20) * 5;
-
-	if(percent > 1.0)
-		percent = 1.0;
-
-	gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(progress), percent);
-	return TRUE;
+	gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(progress), level);
 }
 
 
@@ -412,16 +379,6 @@
 		gtkmedia->priv->item_factory = NULL;
 	}
 
-	if (gtkmedia->priv->send_level) {
-		gst_object_unref(gtkmedia->priv->send_level);
-		gtkmedia->priv->send_level = NULL;
-	}
-
-	if (gtkmedia->priv->recv_level) {
-		gst_object_unref(gtkmedia->priv->recv_level);
-		gtkmedia->priv->recv_level = NULL;
-	}
-
 	G_OBJECT_CLASS(parent_class)->dispose(media);
 }
 
@@ -501,17 +458,6 @@
 }
 
 static void
-pidgin_media_accepted_cb(PurpleMedia *media, const gchar *session_id,
-		const gchar *participant, PidginMedia *gtkmedia)
-{
-	pidgin_media_set_state(gtkmedia, PIDGIN_MEDIA_ACCEPTED);
-	pidgin_media_emit_message(gtkmedia, _("Call in progress."));
-	gtk_statusbar_push(GTK_STATUSBAR(gtkmedia->priv->statusbar),
-			0, _("Call in progress."));
-	gtk_widget_show(GTK_WIDGET(gtkmedia));
-}
-
-static void
 pidgin_media_accept_cb(PurpleMedia *media, int index)
 {
 	purple_media_stream_info(media, PURPLE_MEDIA_INFO_ACCEPT,
@@ -554,7 +500,7 @@
 
 	gtkmedia->priv->request_type = PURPLE_MEDIA_NONE;
 
-	purple_request_accept_cancel(gtkmedia, "Media invitation",
+	purple_request_accept_cancel(gtkmedia, _("Incoming Call"),
 			message, NULL, PURPLE_DEFAULT_ACTION_NONE,
 			(void*)account, gtkmedia->priv->screenname, NULL,
 			gtkmedia->priv->media,
@@ -576,8 +522,7 @@
 {
 	double val = (double)gtk_range_get_value(GTK_RANGE(range));
 #endif
-	purple_prefs_set_int("/pidgin/media/audio/volume/input", val);
-	purple_media_set_input_volume(media, NULL, val / 10.0);
+	purple_media_set_input_volume(media, NULL, val);
 }
 
 static void
@@ -591,8 +536,7 @@
 {
 	double val = (double)gtk_range_get_value(GTK_RANGE(range));
 #endif
-	purple_prefs_set_int("/pidgin/media/audio/volume/output", val);
-	purple_media_set_output_volume(media, NULL, NULL, val / 10.0);
+	purple_media_set_output_volume(media, NULL, NULL, val);