changeset 31852:142429bcb4c8

propagate from branch 'im.pidgin.pidgin' (head bb53d1e9201138b79a1eef0d4a7cda1fc852c59f) to branch 'im.pidgin.pidgin.next.major' (head 85a38af15472bf17064bbe597f94f32ad167c4e6)
author Mark Doliner <mark@kingant.net>
date Sun, 07 Aug 2011 05:19:54 +0000
parents a3b1dcf433b5 (current diff) f33200f71cc3 (diff)
children ade9644db2fe
files ChangeLog ChangeLog.API configure.ac finch/finch.c gaim-uninstalled.pc.in gaim.pc.in libpurple/Makefile.am libpurple/account.c libpurple/dnssrv.h libpurple/gaim-compat.h libpurple/protocols/oscar/family_feedbag.c libpurple/protocols/oscar/oscar.c libpurple/purple-2-uninstalled.pc.in libpurple/purple-2.pc.in libpurple/purple-uninstalled.pc.in libpurple/purple.pc.in pidgin/gtkgaim-compat.h pidgin/gtkmain.c pidgin/gtkprefs.c pidgin/gtkrequest.c pidgin/pidgin-2-uninstalled.pc.in pidgin/pidgin-2.pc.in pidgin/pidgin-uninstalled.pc.in pidgin/pidgin.pc.in
diffstat 59 files changed, 1130 insertions(+), 475 deletions(-) [+]
line wrap: on
line diff
--- a/AUTHORS	Sun Aug 07 03:21:39 2011 +0000
+++ b/AUTHORS	Sun Aug 07 05:19:54 2011 +0000
@@ -12,55 +12,58 @@
 Paul 'darkrain42' Aurich - Developer
 John 'rekkanoryo' Bailey - Developer
 Ethan 'Paco-Paco' Blanton - Developer
-Thomas Butter - Developer
-Ka-Hing Cheung - Developer
 Sadrul Habib Chowdhury - Developer
 Mark 'KingAnt' Doliner - Developer
-Sean Egan - Developer
 Casey Harkins - Developer
 Ivan Komarov - Developer
 Gary 'grim' Kramlich - Developer
 Richard 'rlaager' Laager - Developer
+Marcus 'malu' Lundblad - Developer
 Sulabh 'sulabh_m' Mahajan - Developer
 Richard 'wabz' Nelson - Developer
-Christopher 'siege' O'Brien - Developer
-Bartosz Oler - Developer
 Etan 'deryni' Reisner - Developer
-Tim 'marv' Ringenbach - Developer
 Michael 'Maiku' Ruprecht - Developer, voice and video
 Elliott 'QuLogic' Sales de Andrade - Developer
 Luke 'LSchiere' Schierer - Support
-Megan 'Cae' Schneider - support/QA
 Evan Schoenberg - Developer
 Kevin 'SimGuy' Stange - Developer & Webmaster
 Will 'resiak' Thompson - Developer
 Stu 'nosnilmot' Tomlinson - Developer
-Nathan 'faceprint' Walp - Developer
+Jorge 'Masca' Villaseñor - Developer
 
 Crazy Patch Writers:
 -------------------
-Marcus 'malu' Lundblad
-Dennis 'EvilDennisR' Ristuccia
+Jakub 'haakon' Adam
+Krzysztof Klinikowski
 Peter 'Fmoo' Ruibal
 Gabriel 'Nix' Schulhof
-Jorge 'Masca' Villaseñor
+Tomasz Wasilczyk
 
 Retired Developers:
 ------------------
 Herman Bloggs - Win32 Port
+Thomas Butter - Developer
+Ka-Hing Cheung - Developer
 Jim Duchek <jim@linuxpimps.com> - maintainer
+Sean Egan - Developer
 Rob Flynn <gaim@robflynn.com> - maintainer
 Adam Fritzler - libfaim maintainer
 Christian 'ChipX86' Hammond - Developer & Webmaster
 Syd Logan - hacker and designated driver [lazy bum]
+Christopher 'siege' O'Brien - Developer
+Bartosz Oler - Developer
+Tim 'marv' Ringenbach - Developer
+Megan 'Cae' Schneider - support/QA
 Jim Seymour - XMPP developer
 Mark Spencer <markster@marko.net> - original author
+Nathan 'faceprint' Walp - Developer
 Eric Warmenhoven <eric@warmenhoven.org> - lead developer
 
 Retired Crazy Patch Writers:
 ---------------------------
 Felipe 'shx' Contreras
 Decklin Foster
+Dennis 'EvilDennisR' Ristuccia - Senior Contributor/QA
 Peter 'Bleeter' Lawler
 Robert 'Robot101' McQueen
 Benjamin Miller
--- a/COPYRIGHT	Sun Aug 07 03:21:39 2011 +0000
+++ b/COPYRIGHT	Sun Aug 07 05:19:54 2011 +0000
@@ -15,7 +15,7 @@
 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:
+Copyright (C) 1998-2011 by the following:
 
 Saleem Abdulrasool
 Jakub Adam
@@ -71,6 +71,7 @@
 Éric Boumaour
 Chris Boyle
 Stanislav Brabec
+Quentin Brandon
 Derrick J Brashear
 Mauro Sérgio Ferreira Brasil
 Luke Bratch
--- a/ChangeLog	Sun Aug 07 03:21:39 2011 +0000
+++ b/ChangeLog	Sun Aug 07 05:19:54 2011 +0000
@@ -6,11 +6,67 @@
 	* Don't try to format ICQ usernames entered as email addresses.
 	  Gets rid of an "Unable to format username" error at login. (#13883)
 
-version 2.8.1 (MM/DD/YYYY):
+version 2.10.0 (MM/DD/YYYY):
+	Pidgin:
+	* Make the max size of incoming smileys a pref instead of hardcoding it.
+	  (Quentin Brandon) (#5231)
+	* Added a plugin information dialog to show information for plugins
+	  that aren't otherwise visible in the plugins dialog.
+	* Fix building with GTK+ earlier than 2.14.0 (GTK+ 2.10 is still the
+	  minimum supported) (#14261)
+
+	libpurple:
+	* Fix a potential crash in the Log Reader plugin when reading QIP logs.
+
+	Gadu-Gadu:
+	* Fixed searching for buddies in public directory. (Tomasz Wasilczyk)
+	  (#5242)
+	* Better status message handling. (Tomasz Wasilczyk) (#14314)
+	* Merged two buddy blocking methods. (Tomasz Wasilczyk) (#5303)
+	* Fix building of the bundled libgadu library with older versions
+	  of GnuTLS. (patch plucked from upstream) (#14365)
+
+	ICQ:
+	* Fix crash selecting Tools->Set Mood when you're online with an
+	  ICQ account that is configured as an AIM account. (#14437)
+
+	IRC:
+	* Fix the handling of formatting following mIRC ^O (#14436)
+
+	MSN:
+	* Fix seemingly random crashing. (#14307)
+
+	XMPP:
+	* Do not generate malformed XML ("</>") when setting an empty mood.
+	  (#14342)
+	* Fix the /join <room> behavior.  (Broken when adding support for
+	  <room>@<server>)  (#14205)
+
+	Yahoo!/Yahoo! JAPAN:
+	* Fix coming out of idle while in an unavailable state
+	* Fix logging into Yahoo! JAPAN.  (#14259)
+
+version 2.9.0 (06/23/2011):
+	Pidgin:
+	* Fix a potential remote denial-of-service bug related to displaying
+	  buddy icons.
+	* Significantly improved performance of larger IRC channels (regression
+	  introduced in 2.8.0).
+	* Fix Conversation->Add on AIM and MSN.
+	* Entries in the chat user list are sorted properly again.  This was
+	  inadvertenly broken in 2.8.0.
+
+	Finch:
+	* Fix logging in to ICQ.
+
 	libpurple:
 	* media: Actually use the specified TCP port from the TURN configuration to
 	  create a TCP relay candidate.
 
+	AIM and ICQ:
+	* Fix crashes on some non-mainstream OSes when attempting to
+	  printf("%s", NULL).  (Clemens Huebner) (#14297)
+
 	Plugins:
 	* The Evolution Integration plugin compiles again.
 
--- a/ChangeLog.API	Sun Aug 07 03:21:39 2011 +0000
+++ b/ChangeLog.API	Sun Aug 07 05:19:54 2011 +0000
@@ -11,6 +11,32 @@
 		Removed:
 		* purple_core_migrate
 
+version 2.10.0:
+	libpurple:
+		Added:
+		* purple_srv_txt_query_destroy (accidentally left out of 2.8.0)
+
+	Pidgin:
+		Added:
+		* pidgin_dialogs_plugins_info (should not be used by anything but Pidgin)
+
+version 2.9.0:
+	libpurple:
+		Added:
+		* Hash table to PurpleConvChat struct, used to make
+		  purple_conv_chat_cb_find O(1).
+		* ui_data pointer to PurpleConvChatBuddy struct.
+		* deleting-chat-buddy signal (conversation signals)
+		* pidgin_pixbuf_from_data
+		* pidgin_pixbuf_anim_from_data
+		* pidgin_pixbuf_new_from_file
+		* pidgin_pixbuf_new_from_file_at_size
+		* pidgin_pixbuf_new_from_file_at_scale
+
+		Deprecated:
+		* purple_conv_chat_set_users
+		* PurpleConvChat in_room list
+
 version 2.8.0 (06/07/2011):
 	libpurple:
 		Added:
--- a/configure.ac	Sun Aug 07 03:21:39 2011 +0000
+++ b/configure.ac	Sun Aug 07 05:19:54 2011 +0000
@@ -571,7 +571,6 @@
 			evo_deps="libebook-1.0 libedata-book-1.0"
 			PKG_CHECK_MODULES(EVOLUTION_ADDRESSBOOK, $evo_deps, [
 				enable_gevolution="yes"
-			], [
 			])
 		fi
 		if test "x$enable_gevolution" = "xyes"; then
--- a/finch/finch.c	Sun Aug 07 03:21:39 2011 +0000
+++ b/finch/finch.c	Sun Aug 07 05:19:54 2011 +0000
@@ -74,11 +74,28 @@
 		 * account "markdoliner."  Please don't use this key for other
 		 * applications.  You can either not specify a client key, in
 		 * which case the default "libpurple" key will be used, or you
-		 * can register for your own client key at
-		 * http://developer.aim.com/manageKeys.jsp
+		 * can try to register your own at the AIM or ICQ web sites
+		 * (although this functionality was removed at some point, it's
+		 * possible it has been re-added).  AOL's old key management
+		 * page is http://developer.aim.com/manageKeys.jsp
 		 */
 		g_hash_table_insert(ui_info, "prpl-aim-clientkey", "ma19sqWV9ymU6UYc");
-		g_hash_table_insert(ui_info, "prpl-icq-clientkey", "ma19sqWV9ymU6UYc");
+
+		/*
+		 * This is the client key for "Pidgin."  It is owned by the AIM
+		 * account "markdoliner."  Please don't use this key for other
+		 * applications.  You can either not specify a client key, in
+		 * which case the default "libpurple" key will be used, or you
+		 * can try to register your own at the AIM or ICQ web sites
+		 * (although this functionality was removed at some point, it's
+		 * possible it has been re-added).  AOL's old key management
+		 * page is http://developer.aim.com/manageKeys.jsp
+		 *
+		 * We used to have a Finch-specific devId/clientkey
+		 * (ma19sqWV9ymU6UYc), but it stopped working, so we switched
+		 * to this one.
+		 */
+		g_hash_table_insert(ui_info, "prpl-icq-clientkey", "ma1cSASNCKFtrdv9");
 
 		/*
 		 * This is the distid for Finch, given to us by AOL.  Please
--- a/libpurple/Makefile.am	Sun Aug 07 03:21:39 2011 +0000
+++ b/libpurple/Makefile.am	Sun Aug 07 05:19:54 2011 +0000
@@ -155,7 +155,6 @@
 	theme-manager.h \
 	upnp.h \
 	util.h \
-	valgrind.h \
 	value.h \
 	xmlnode.h \
 	whiteboard.h
--- a/libpurple/conversation.c	Sun Aug 07 03:21:39 2011 +0000
+++ b/libpurple/conversation.c	Sun Aug 07 05:19:54 2011 +0000
@@ -70,6 +70,23 @@
 	g_free(hc);
 }
 
+static guint _purple_conversation_user_hash(gconstpointer data)
+{
+	const gchar *name = data;
+	gchar *collated;
+	guint hash;
+
+	collated = g_utf8_collate_key(name, -1);
+	hash     = g_str_hash(collated);
+	g_free(collated);
+	return hash;
+}
+
+static gboolean _purple_conversation_user_equal(gconstpointer a, gconstpointer b)
+{
+	return !g_utf8_collate(a, b);
+}
+
 void
 purple_conversations_set_ui_ops(PurpleConversationUiOps *ops)
 {
@@ -393,6 +410,8 @@
 
 		conv->u.chat = g_new0(PurpleConvChat, 1);
 		conv->u.chat->conv = conv;
+		conv->u.chat->users = g_hash_table_new_full(_purple_conversation_user_hash,
+				_purple_conversation_user_equal, g_free, NULL);
 		PURPLE_DBUS_REGISTER_POINTER(conv->u.chat, PurpleConvChat);
 
 		chats = g_list_prepend(chats, conv);
@@ -547,6 +566,8 @@
 		conv->u.im = NULL;
 	}
 	else if (conv->type == PURPLE_CONV_TYPE_CHAT) {
+		g_hash_table_destroy(conv->u.chat->users);
+		conv->u.chat->users = NULL;
 
 		g_list_foreach(conv->u.chat->in_room, (GFunc)purple_conv_chat_cb_destroy, NULL);
 		g_list_free(conv->u.chat->in_room);
@@ -1677,9 +1698,9 @@
 
 		cbuddy = purple_conv_chat_cb_new(user, alias, flag);
 		cbuddy->buddy = purple_find_buddy(conv->account, user) != NULL;
-		/* This seems dumb. Why should we set users thousands of times? */
-		purple_conv_chat_set_users(chat,
-				g_list_prepend(chat->in_room, cbuddy));
+
+		chat->in_room = g_list_prepend(chat->in_room, cbuddy);
+		g_hash_table_replace(chat->users, g_strdup(cbuddy->name), cbuddy);
 
 		cbuddies = g_list_prepend(cbuddies, cbuddy);
 
@@ -1771,8 +1792,9 @@
 	flags = purple_conv_chat_user_get_flags(chat, old_user);
 	cb = purple_conv_chat_cb_new(new_user, new_alias, flags);
 	cb->buddy = purple_find_buddy(conv->account, new_user) != NULL;
-	purple_conv_chat_set_users(chat,
-		g_list_prepend(chat->in_room, cb));
+
+	chat->in_room = g_list_prepend(chat->in_room, cb);
+	g_hash_table_replace(chat->users, g_strdup(cb->name), cb);
 
 	if (ops != NULL && ops->chat_rename_user != NULL)
 		ops->chat_rename_user(conv, old_user, new_user, new_alias);
@@ -1780,8 +1802,8 @@
 	cb = purple_conv_chat_cb_find(chat, old_user);
 
 	if (cb) {
-		purple_conv_chat_set_users(chat,
-				g_list_remove(chat->in_room, cb));
+		chat->in_room = g_list_remove(chat->in_room, cb);
+		g_hash_table_remove(chat->users, cb->name);
 		purple_conv_chat_cb_destroy(cb);
 	}
 
@@ -1874,8 +1896,8 @@
 		cb = purple_conv_chat_cb_find(chat, user);
 
 		if (cb) {
-			purple_conv_chat_set_users(chat,
-					g_list_remove(chat->in_room, cb));
+			chat->in_room = g_list_remove(chat->in_room, cb);
+			g_hash_table_remove(chat->users, cb->name);
 			purple_conv_chat_cb_destroy(cb);
 		}
 
@@ -1955,8 +1977,10 @@
 		purple_conv_chat_cb_destroy(cb);
 	}
 
+	g_hash_table_remove_all(chat->users);
+
 	g_list_free(users);
-	purple_conv_chat_set_users(chat, NULL);
+	chat->in_room = NULL;
 }
 
 
@@ -2146,19 +2170,10 @@
 PurpleConvChatBuddy *
 purple_conv_chat_cb_find(PurpleConvChat *chat, const char *name)
 {
-	GList *l;
-	PurpleConvChatBuddy *cb = NULL;
-
 	g_return_val_if_fail(chat != NULL, NULL);
 	g_return_val_if_fail(name != NULL, NULL);
 
-	for (l = purple_conv_chat_get_users(chat); l; l = l->next) {
-		cb = l->data;
-		if (!g_utf8_collate(cb->name, name))
-			return cb;
-	}
-
-	return NULL;
+	return g_hash_table_lookup(chat->users, name);
 }
 
 void
@@ -2167,6 +2182,9 @@
 	if (cb == NULL)
 		return;
 
+	purple_signal_emit(purple_conversations_get_handle(),
+			"deleting-chat-buddy", cb);
+
 	g_free(cb->alias);
 	g_free(cb->alias_key);
 	g_free(cb->name);
@@ -2573,6 +2591,11 @@
 						 purple_value_new(PURPLE_TYPE_STRING),
 						 purple_value_new(PURPLE_TYPE_STRING));
 
+	purple_signal_register(handle, "deleting-chat-buddy",
+						 purple_marshal_VOID__POINTER, NULL, 1,
+						 purple_value_new(PURPLE_TYPE_SUBTYPE,
+										PURPLE_SUBTYPE_CHATBUDDY));
+
 	purple_signal_register(handle, "chat-inviting-user",
 						 purple_marshal_VOID__POINTER_POINTER_POINTER, NULL, 3,
 						 purple_value_new(PURPLE_TYPE_SUBTYPE,
--- a/libpurple/conversation.h	Sun Aug 07 03:21:39 2011 +0000
+++ b/libpurple/conversation.h	Sun Aug 07 05:19:54 2011 +0000
@@ -271,7 +271,9 @@
 {
 	PurpleConversation *conv;          /**< The parent conversation.      */
 
-	GList *in_room;                  /**< The users in the room.        */
+	GList *in_room;                  /**< The users in the room.
+	                                  *   @deprecated Will be removed in 3.0.0
+									  */
 	GList *ignored;                  /**< Ignored users.                */
 	char  *who;                      /**< The person who set the topic. */
 	char  *topic;                    /**< The topic.                    */
@@ -279,6 +281,9 @@
 	char *nick;                      /**< Your nick in this chat.       */
 
 	gboolean left;                   /**< We left the chat and kept the window open */
+	GHashTable *users;               /**< Hash table of the users in the room.
+	                                  *   @since 2.9.0
+	                                  */
 };
 
 /**
@@ -304,6 +309,7 @@
 	GHashTable *attributes;          /**< A hash table of attributes about the user, such as
                                     *   real name, user@host, etc.
                                     */
+	gpointer ui_data;                /** < The UI can put whatever it wants here. */
 };
 
 /**
@@ -1065,6 +1071,8 @@
  * @param users The list of users.
  *
  * @return The list passed.
+ *
+ * @deprecated This function will be removed in 3.0.0.  You shouldn't be using it anyway.
  */
 GList *purple_conv_chat_set_users(PurpleConvChat *chat, GList *users);
 
--- a/libpurple/dnsquery.c	Sun Aug 07 03:21:39 2011 +0000
+++ b/libpurple/dnsquery.c	Sun Aug 07 05:19:54 2011 +0000
@@ -154,8 +154,27 @@
 static gboolean
 resolve_ip(PurpleDnsQueryData *query_data)
 {
+#if defined(HAVE_GETADDRINFO) && defined(AI_NUMERICHOST)
+	struct addrinfo hints, *res;
+	char servname[20];
+
+	g_snprintf(servname, sizeof(servname), "%d", query_data->port);
+	memset(&hints, 0, sizeof(hints));
+	hints.ai_family = AF_UNSPEC;
+	hints.ai_flags |= AI_NUMERICHOST;
+
+	if (0 == getaddrinfo(query_data->hostname, servname, &hints, &res))
+	{
+		GSList *hosts = NULL;
+		hosts = g_slist_append(hosts, GINT_TO_POINTER(res->ai_addrlen));
+		hosts = g_slist_append(hosts, g_memdup(res->ai_addr, res->ai_addrlen));
+		purple_dnsquery_resolved(query_data, hosts);
+
+		freeaddrinfo(res);
+		return TRUE;
+	}
+#else /* defined(HAVE_GETADDRINFO) && defined(AI_NUMERICHOST) */
 	struct sockaddr_in sin;
-	/* TODO: Use inet_pton for IPv6 support */
 	if (inet_aton(query_data->hostname, &sin.sin_addr))
 	{
 		/*
@@ -171,6 +190,7 @@
 
 		return TRUE;
 	}
+#endif
 
 	return FALSE;
 }
--- a/libpurple/dnssrv.c	Sun Aug 07 03:21:39 2011 +0000
+++ b/libpurple/dnssrv.c	Sun Aug 07 05:19:54 2011 +0000
@@ -250,6 +250,52 @@
 	return list;
 }
 
+static PurpleSrvTxtQueryData *
+query_data_new(int type, gchar *query, gpointer extradata)
+{
+	PurpleSrvTxtQueryData *query_data = g_new0(PurpleSrvTxtQueryData, 1);
+	query_data->type = type;
+	query_data->extradata = extradata;
+	query_data->query = query;
+#ifndef _WIN32
+	query_data->fd_in = -1;
+	query_data->fd_out = -1;
+#endif
+	return query_data;
+}
+
+void
+purple_srv_txt_query_destroy(PurpleSrvTxtQueryData *query_data)
+{
+	PurpleSrvTxtQueryUiOps *ops = purple_srv_txt_query_get_ui_ops();
+
+	if (ops && ops->destroy)
+		ops->destroy(query_data);
+
+	if (query_data->handle > 0)
+		purple_input_remove(query_data->handle);
+#ifdef _WIN32
+	if (query_data->resolver != NULL)
+	{
+		/*
+		 * It's not really possible to kill a thread.  So instead we
+		 * just set the callback to NULL and let the DNS lookup
+		 * finish.
+		 */
+		query_data->cb.srv = NULL;
+		return;
+	}
+	g_free(query_data->error_message);
+#else
+	if (query_data->fd_out != -1)
+		close(query_data->fd_out);
+	if (query_data->fd_in != -1)
+		close(query_data->fd_in);
+#endif
+	g_free(query_data->query);
+	g_free(query_data);
+}
+
 #ifdef USE_IDN
 static gboolean
 dns_str_is_ascii(const char *name)
@@ -523,7 +569,7 @@
 	}
 
 	waitpid(query_data->pid, &status, 0);
-	purple_srv_cancel(query_data);
+	purple_srv_txt_query_destroy(query_data);
 }
 
 #else /* _WIN32 */
@@ -583,7 +629,7 @@
 	query_data->resolver = NULL;
 	query_data->handle = 0;
 
-	purple_srv_cancel(query_data);
+	purple_srv_txt_query_destroy(query_data);
 
 	return FALSE;
 }
@@ -730,13 +776,10 @@
 	purple_debug_info("dnssrv","querying SRV record for %s: %s\n", domain,
 			query);
 	g_free(hostname);
-	
-	query_data = g_new0(PurpleSrvTxtQueryData, 1);
-	query_data->type = PurpleDnsTypeSrv;
+
+	query_data = query_data_new(PurpleDnsTypeSrv, query, extradata);
 	query_data->cb.srv = cb;
-	query_data->extradata = extradata;
-	query_data->query = query;
-	
+
 	if (purple_srv_txt_query_ui_resolve(query_data))
 	{
 		return query_data;
@@ -746,6 +789,7 @@
 	if(pipe(in) || pipe(out)) {
 		purple_debug_error("dnssrv", "Could not create pipe\n");
 		g_free(query);
+		g_free(query_data);
 		cb(NULL, 0, extradata);
 		return NULL;
 	}
@@ -753,8 +797,9 @@
 	pid = fork();
 	if (pid == -1) {
 		purple_debug_error("dnssrv", "Could not create process!\n");
+		g_free(query);
+		g_free(query_data);
 		cb(NULL, 0, extradata);
-		g_free(query);
 		return NULL;
 	}
 
@@ -762,6 +807,7 @@
 	if (pid == 0)
 	{
 		g_free(query);
+		g_free(query_data);
 
 		close(out[0]);
 		close(in[1]);
@@ -784,8 +830,6 @@
 	query_data->fd_in = in[1];
 	query_data->handle = purple_input_add(out[0], PURPLE_INPUT_READ, resolved, query_data);
 
-	g_free(query);
-
 	return query_data;
 #else
 	if (!initialized) {
@@ -862,13 +906,10 @@
 	purple_debug_info("dnssrv","querying TXT record for %s: %s\n", domain,
 			query);
 	g_free(hostname);
-	
-	query_data = g_new0(PurpleSrvTxtQueryData, 1);
-	query_data->type = PurpleDnsTypeTxt;
+
+	query_data = query_data_new(PurpleDnsTypeTxt, query, extradata);
 	query_data->cb.txt = cb;
-	query_data->extradata = extradata;
-	query_data->query = query;
-	
+
 	if (purple_srv_txt_query_ui_resolve(query_data)) {
 		/* query intentionally not freed
 		 */
@@ -879,6 +920,7 @@
 	if(pipe(in) || pipe(out)) {
 		purple_debug_error("dnssrv", "Could not create pipe\n");
 		g_free(query);
+		g_free(query_data);
 		cb(NULL, extradata);
 		return NULL;
 	}
@@ -886,8 +928,9 @@
 	pid = fork();
 	if (pid == -1) {
 		purple_debug_error("dnssrv", "Could not create process!\n");
+		g_free(query);
+		g_free(query_data);
 		cb(NULL, extradata);
-		g_free(query);
 		return NULL;
 	}
 
@@ -895,6 +938,7 @@
 	if (pid == 0)
 	{
 		g_free(query);
+		g_free(query_data);
 
 		close(out[0]);
 		close(in[1]);
@@ -911,14 +955,12 @@
 
 	if (write(in[1], &internal_query, sizeof(internal_query)) < 0)
 		purple_debug_error("dnssrv", "Could not write to TXT resolver\n");
-	
+
 	query_data->pid = pid;
 	query_data->fd_out = out[0];
 	query_data->fd_in = in[1];
 	query_data->handle = purple_input_add(out[0], PURPLE_INPUT_READ, resolved, query_data);
 
-	g_free(query);
-
 	return query_data;
 #else
 	if (!initialized) {
@@ -949,39 +991,15 @@
 }
 
 void
-purple_srv_cancel(PurpleSrvTxtQueryData *query_data)
+purple_txt_cancel(PurpleSrvTxtQueryData *query_data)
 {
-	PurpleSrvTxtQueryUiOps *ops = purple_srv_txt_query_get_ui_ops();
-
-	if (ops && ops->destroy)
-		ops->destroy(query_data);
-	
-	if (query_data->handle > 0)
-		purple_input_remove(query_data->handle);
-#ifdef _WIN32
-	if (query_data->resolver != NULL)
-	{
-		/*
-		 * It's not really possible to kill a thread.  So instead we
-		 * just set the callback to NULL and let the DNS lookup
-		 * finish.
-		 */
-		query_data->cb.srv = NULL;
-		return;
-	}
-	g_free(query_data->query);
-	g_free(query_data->error_message);
-#else
-	close(query_data->fd_out);
-	close(query_data->fd_in);
-#endif
-	g_free(query_data);
+	purple_srv_txt_query_destroy(query_data);
 }
 
 void
-purple_txt_cancel(PurpleSrvTxtQueryData *query_data)
+purple_srv_cancel(PurpleSrvTxtQueryData *query_data)
 {
-	purple_srv_cancel(query_data);
+	purple_srv_txt_query_destroy(query_data);
 }
 
 const gchar *
@@ -1006,12 +1024,41 @@
 static void
 purple_srv_query_resolved(PurpleSrvTxtQueryData *query_data, GList *records)
 {
+	GList *l;
+	PurpleSrvResponse *records_array;
+	int i = 0, length;
+
 	g_return_if_fail(records != NULL);
-	
-	purple_debug_info("dnssrv", "SRV records resolved for %s, count: %d\n", query_data->query, g_list_length(records));
-	
-	if (query_data->cb.srv != NULL)
-		query_data->cb.srv(purple_srv_sort(records)->data, g_list_length(records), query_data->extradata);
+
+	if (query_data->cb.srv == NULL) {
+		purple_srv_txt_query_destroy(query_data);
+
+		while (records) {
+			g_free(records->data);
+			records = g_list_delete_link(records, records);
+		}
+		return;
+	}
+
+	records = purple_srv_sort(records);
+	length = g_list_length(records);
+
+	purple_debug_info("dnssrv", "SRV records resolved for %s, count: %d\n",
+	                            query_data->query, length);
+
+	records_array = g_new(PurpleSrvResponse, length);
+	for (l = records; l; l = l->next, i++) {
+		records_array[i] = *(PurpleSrvResponse *)l->data;
+	}
+
+	query_data->cb.srv(records_array, length, query_data->extradata);
+
+	purple_srv_txt_query_destroy(query_data);
+
+	while (records) {
+		g_free(records->data);
+		records = g_list_delete_link(records, records);
+	}
 }
 
 /*
@@ -1024,19 +1071,29 @@
 
 	purple_debug_info("dnssrv", "TXT entries resolved for %s, count: %d\n", query_data->query, g_list_length(entries));
 
+	/* the callback should g_free the entries.
+	 */
 	if (query_data->cb.txt != NULL)
 		query_data->cb.txt(entries, query_data->extradata);
+	else {
+		while (entries) {
+			g_free(entries->data);
+			entries = g_list_delete_link(entries, entries);
+		}
+	}
+
+	purple_srv_txt_query_destroy(query_data);
 }
 
 static void
 purple_srv_query_failed(PurpleSrvTxtQueryData *query_data, const gchar *error_message)
 {
 	purple_debug_error("dnssrv", "%s\n", error_message);
-	
+
 	if (query_data->cb.srv != NULL)
 		query_data->cb.srv(NULL, 0, query_data->extradata);
-		
-	purple_srv_cancel(query_data);
+
+	purple_srv_txt_query_destroy(query_data);
 }
 
 static gboolean
@@ -1069,7 +1126,7 @@
 purple_srv_txt_query_get_query(PurpleSrvTxtQueryData *query_data)
 {
 	g_return_val_if_fail(query_data != NULL, NULL);
-	
+
 	return query_data->query;
 }
 
@@ -1078,6 +1135,6 @@
 purple_srv_txt_query_get_type(PurpleSrvTxtQueryData *query_data)
 {
 	g_return_val_if_fail(query_data != NULL, 0);
-	
+
 	return query_data->type;
 }
--- a/libpurple/dnssrv.h	Sun Aug 07 03:21:39 2011 +0000
+++ b/libpurple/dnssrv.h	Sun Aug 07 05:19:54 2011 +0000
@@ -129,6 +129,8 @@
  * Cancel an SRV or DNS query.
  *
  * @param query_data The request to cancel.
+ *
+ * @deprecated Use purple_srv_txt_query_destroy instead
  */
 void purple_srv_cancel(PurpleSrvTxtQueryData *query_data);
 
@@ -166,6 +168,8 @@
  *
  * @param query_data The request to cancel.
  * @since 2.6.0
+ *
+ * @deprecated Use purple_srv_txt_query_destroy instead
  */
 void purple_txt_cancel(PurpleSrvTxtQueryData *query_data);
 
--- a/libpurple/plugins/log_reader.c	Sun Aug 07 03:21:39 2011 +0000
+++ b/libpurple/plugins/log_reader.c	Sun Aug 07 05:19:54 2011 +0000
@@ -1454,11 +1454,15 @@
 		const char *footer = NULL;
 		GString *temp = NULL;
 
-		if ((c = strstr(c, "\n")))
-		{
-			*c = '\0';
-			c++;
-		}
+		/* There's always a trailing '\n' at the end of the file (see above), so
+		 * just quit out if we don't find another, because we're at the end.
+		 */
+		c = strchr(c, '\n');
+		if (!c)
+			break;
+
+		*c = '\0';
+		c++;
 
 		/* Convert links.
 		 *
@@ -1482,14 +1486,14 @@
 				char *end_paren;
 				char *space;
 
-				if (!(end_paren = strstr(link, ")")))
+				if (!(end_paren = strchr(link, ')')))
 				{
 					/* Something is not as we expect.  Bail out. */
 					break;
 				}
 
 				if (!temp)
-					temp = g_string_sized_new(c ? (c - 1 - line) : strlen(line));
+					temp = g_string_sized_new(strlen(line));
 
 				g_string_append_len(temp, line, (tmp - line));
 
@@ -1504,7 +1508,7 @@
 
 				/* The \r is a bit of a hack to keep there from being a \r in
 				 * the link text, which may not matter. */
-				if ((space = strstr(end_paren, " ")) || (space = strstr(end_paren, "\r")))
+				if ((space = strchr(end_paren, ' ')) || (space = strchr(end_paren, '\r')))
 				{
 					g_string_append_len(temp, end_paren + 1, space - end_paren - 1);
 
@@ -1539,7 +1543,7 @@
 		if (*line == '[') {
 			const char *timestamp;
 
-			if ((timestamp = strstr(line, "]"))) {
+			if ((timestamp = strchr(line, ']'))) {
 				line++;
 				/* TODO: Parse the timestamp and convert it to Purple's format. */
 				g_string_append(formatted, "<font size=\"2\">(");
@@ -1658,7 +1662,7 @@
 					}
 				}
 			} else {
-				const char *line2 = strstr(line, ":");
+				const char *line2 = strchr(line, ':');
 				if (line2) {
 					const char *acct_name;
 					line2++;
@@ -1819,7 +1823,7 @@
 
 		gboolean add_new_log = FALSE;
 
-		if (*c) {
+		if (c && *c) {
 			if (purple_str_has_prefix(c, QIP_LOG_IN_MESSAGE) ||
 				purple_str_has_prefix(c, QIP_LOG_OUT_MESSAGE)) {
 
@@ -1828,11 +1832,11 @@
 				new_line = c;
 
 				/* find EOL */
-				c = strstr(c, "\n");
+				c = strchr(c, '\n');
 				c++;
 
 				/* Find the last '(' character. */
-				if ((tmp = strstr(c, "\n")) != NULL) {
+				if ((tmp = strchr(c, '\n')) != NULL) {
 					while (*tmp && *tmp != '(') --tmp;
 					c = tmp;
 				} else {
@@ -1902,10 +1906,10 @@
 			start_log = new_line;
 		}
 
-		if (*c) {
+		if (c && *c) {
 			/* find EOF */
-			c = strstr(c, "\n");
-			c++;
+			if ((c = strchr(c, '\n')))
+				c++;
 		}
 	}
 
@@ -1983,13 +1987,13 @@
 			is_in_message = purple_str_has_prefix(line, QIP_LOG_IN_MESSAGE_ESC);
 
 			/* find EOL */
-			c = strstr(c, "\n");
+			c = strchr(c, '\n');
 
 			/* XXX: Do we need buddy_name when we have buddy->alias? */
 			buddy_name = ++c;
 
 			/* Find the last '(' character. */
-			if ((tmp = strstr(c, "\n")) != NULL) {
+			if ((tmp = strchr(c, '\n')) != NULL) {
 				while (*tmp && *tmp != '(') --tmp;
 				c = tmp;
 			} else {
@@ -2042,12 +2046,12 @@
 					}
 
 					/* find EOF */
-					c = strstr(c, "\n");
+					c = strchr(c, '\n');
 					line = ++c;
 				}
 			}
 		} else {
-			if ((c = strstr(c, "\n")))
+			if ((c = strchr(c, '\n')))
 				*c = '\0';
 
 			if (line[0] != '\n' && line[0] != '\r') {
@@ -2186,7 +2190,7 @@
 				                  " length = (%d)\n",
 				                  sn, data->path, data->offset, data->length);
 			}
-			c = strstr(c, "\n");
+			c = strchr(c, '\n');
 			c++;
 		}
 
@@ -2342,7 +2346,7 @@
 		char *end;
 		char *old_tag;
 		char *tag;
-		end = strstr(start, "\n");
+		end = strchr(start, '\n');
 		if (!end)
 			break;
 		*end = '\0';
--- a/libpurple/prefs.c	Sun Aug 07 03:21:39 2011 +0000
+++ b/libpurple/prefs.c	Sun Aug 07 05:19:54 2011 +0000
@@ -277,6 +277,12 @@
 		}
 	}
 
+	if ((pref_type == PURPLE_PREF_BOOLEAN || pref_type == PURPLE_PREF_INT) &&
+			pref_value == NULL) {
+		/* Missing a value attribute */
+		return;
+	}
+
 	if(purple_strequal(element_name, "item")) {
 		struct purple_pref *pref;
 
--- a/libpurple/protocols/gg/gg.c	Sun Aug 07 03:21:39 2011 +0000
+++ b/libpurple/protocols/gg/gg.c	Sun Aug 07 05:19:54 2011 +0000
@@ -487,15 +487,16 @@
 	GGPSearchForm *form = user_data;
 	guint32 seq;
 
-	g_free(form->offset);
-	form->offset = g_strdup(form->last_uin);
+	form->page_number++;
 
 	ggp_search_remove(info->searches, form->seq);
-	purple_debug_info("gg", "ggp_callback_show_next(): Removed seq %u", form->seq);
+	purple_debug_info("gg", "ggp_callback_show_next(): Removed seq %u\n",
+		form->seq);
 
 	seq = ggp_search_start(gc, form);
 	ggp_search_add(info->searches, seq, form);
-	purple_debug_info("gg", "ggp_callback_show_next(): Added seq %u", seq);
+	purple_debug_info("gg", "ggp_callback_show_next(): Added seq %u\n",
+		seq);
 }
 
 static void ggp_callback_add_buddy(PurpleConnection *gc, GList *row, gpointer user_data)
@@ -526,21 +527,16 @@
 	form = ggp_search_form_new(GGP_SEARCH_TYPE_FULL);
 
 	form->user_data = info;
-	form->lastname  = charset_convert(
-				purple_request_fields_get_string(fields, "lastname"),
-				"UTF-8", "CP1250");
-	form->firstname = charset_convert(
-				purple_request_fields_get_string(fields, "firstname"),
-				"UTF-8", "CP1250");
-	form->nickname  = charset_convert(
-				purple_request_fields_get_string(fields, "nickname"),
-				"UTF-8", "CP1250");
-	form->city      = charset_convert(
-				purple_request_fields_get_string(fields, "city"),
-				"UTF-8", "CP1250");
-	form->birthyear = charset_convert(
-				purple_request_fields_get_string(fields, "year"),
-				"UTF-8", "CP1250");
+	form->lastname = g_strdup(
+		purple_request_fields_get_string(fields, "lastname"));
+	form->firstname = g_strdup(
+		purple_request_fields_get_string(fields, "firstname"));
+	form->nickname = g_strdup(
+		purple_request_fields_get_string(fields, "nickname"));
+	form->city = g_strdup(
+		purple_request_fields_get_string(fields, "city"));
+	form->birthyear = g_strdup(
+		purple_request_fields_get_string(fields, "year"));
 
 	switch (purple_request_fields_get_choice(fields, "gender")) {
 		case 1:
@@ -557,11 +553,10 @@
 	form->active = purple_request_fields_get_bool(fields, "active")
 				   ? g_strdup(GG_PUBDIR50_ACTIVE_TRUE) : NULL;
 
-	form->offset = g_strdup("0");
-
 	seq = ggp_search_start(gc, form);
 	ggp_search_add(info->searches, seq, form);
-	purple_debug_info("gg", "ggp_callback_find_buddies(): Added seq %u", seq);
+	purple_debug_info("gg", "ggp_callback_find_buddies(): Added seq %u\n",
+		seq);
 }
 
 static void ggp_find_buddies(PurplePluginAction *action)
@@ -882,30 +877,26 @@
 
 /* ----- BLOCK BUDDIES -------------------------------------------------- */
 
-static void ggp_bmenu_block(PurpleBlistNode *node, gpointer ignored)
+static void ggp_add_deny(PurpleConnection *gc, const char *who)
 {
-	PurpleConnection *gc;
-	PurpleBuddy *buddy;
-	GGPInfo *info;
-	uin_t uin;
-
-	buddy = (PurpleBuddy *)node;
-	gc = purple_account_get_connection(purple_buddy_get_account(buddy));
-	info = gc->proto_data;
-
-	uin = ggp_str_to_uin(purple_buddy_get_name(buddy));
-
-	if (purple_blist_node_get_bool(node, "blocked")) {
-		purple_blist_node_set_bool(node, "blocked", FALSE);
-		gg_remove_notify_ex(info->session, uin, GG_USER_BLOCKED);
-		gg_add_notify_ex(info->session, uin, GG_USER_NORMAL);
-		purple_debug_info("gg", "send: uin=%d; mode=NORMAL\n", uin);
-	} else {
-		purple_blist_node_set_bool(node, "blocked", TRUE);
-		gg_remove_notify_ex(info->session, uin, GG_USER_NORMAL);
-		gg_add_notify_ex(info->session, uin, GG_USER_BLOCKED);
-		purple_debug_info("gg", "send: uin=%d; mode=BLOCKED\n", uin);
-	}
+	GGPInfo *info = gc->proto_data;
+	uin_t uin = ggp_str_to_uin(who);
+	
+	purple_debug_info("gg", "ggp_add_deny: %u\n", uin);
+	
+	gg_remove_notify_ex(info->session, uin, GG_USER_NORMAL);
+	gg_add_notify_ex(info->session, uin, GG_USER_BLOCKED);
+}
+
+static void ggp_rem_deny(PurpleConnection *gc, const char *who)
+{
+	GGPInfo *info = gc->proto_data;
+	uin_t uin = ggp_str_to_uin(who);
+	
+	purple_debug_info("gg", "ggp_rem_deny: %u\n", uin);
+	
+	gg_remove_notify_ex(info->session, uin, GG_USER_BLOCKED);
+	gg_add_notify_ex(info->session, uin, GG_USER_NORMAL);
 }
 
 /* ---------------------------------------------------------------------- */
@@ -927,6 +918,9 @@
 	PurpleBuddy *buddy;
 	gpointer buddy_icon_data;
 
+	purple_debug_info("gg", "gg_fetch_avatar_cb: got avatar image for %s\n",
+		d->uin);
+
 	/* FIXME: This shouldn't be necessary */
 	if (!PURPLE_CONNECTION_IS_VALID(d->gc)) {
 		g_free(d->uin);
@@ -945,7 +939,8 @@
 
 	purple_buddy_icons_set_for_user(account, purple_buddy_get_name(buddy),
 			buddy_icon_data, len, d->avatar_url);
-	purple_debug_info("gg", "UIN: %s should have avatar now\n", d->uin);
+	purple_debug_info("gg", "gg_fetch_avatar_cb: UIN %s should have avatar "
+		"now\n", d->uin);
 
 out:
 	g_free(d->uin);
@@ -1027,6 +1022,8 @@
 				data->uin = g_strdup(uin);
 				data->avatar_url = g_strdup(bigavatar);
 
+				purple_debug_info("gg", "gg_get_avatar_url_cb: "
+					"requesting avatar for %s\n", uin);
 				url_data = purple_util_fetch_url_request_len_with_account(account,
 						bigavatar, TRUE, "Mozilla/4.0 (compatible; MSIE 5.0)",
 						FALSE, NULL, FALSE, -1, gg_fetch_avatar_cb, data);
@@ -1076,6 +1073,7 @@
 {
 	gchar *from;
 	const char *st;
+	char *status_msg = NULL;
 
 	ggp_update_buddy_avatar(gc, uin);
 
@@ -1113,14 +1111,24 @@
 			break;
 	}
 
-	purple_debug_info("gg", "st = %s\n", st);
-	//msg = charset_convert(descr, "CP1250", "UTF-8");
-	if (descr == NULL) {
+	if (descr != NULL) {
+		status_msg = g_strdup(descr);
+		g_strstrip(status_msg);
+		if (status_msg[0] == '\0') {
+			g_free(status_msg);
+			status_msg = NULL;
+		}
+	}
+
+	purple_debug_info("gg", "status of %u is %s [%s]\n", uin, st,
+		status_msg ? status_msg : "");
+	if (status_msg == NULL) {
 		purple_prpl_got_user_status(purple_connection_get_account(gc),
-		      from, st, NULL);
+			from, st, NULL);
 	} else {
 		purple_prpl_got_user_status(purple_connection_get_account(gc),
-		    from, st, "message", descr, NULL);
+			from, st, "message", status_msg, NULL);
+		g_free(status_msg);
 	}
 	g_free(from);
 }
@@ -1131,7 +1139,8 @@
 	GGPInfo *info = form->user_data;
 
 	ggp_search_remove(info->searches, form->seq);
-	purple_debug_info("gg", "ggp_sr_close_cb(): Removed seq %u", form->seq);
+	purple_debug_info("gg", "ggp_sr_close_cb(): Removed seq %u\n",
+		form->seq);
 	ggp_search_form_destroy(form);
 }
 
@@ -1246,6 +1255,8 @@
 
 	res_count = gg_pubdir50_count(req);
 	res_count = (res_count > PUBDIR_RESULTS_MAX) ? PUBDIR_RESULTS_MAX : res_count;
+	if (form->page_size == 0)
+		form->page_size = res_count;
 
 	results = purple_notify_searchresults_new();
 
@@ -1255,7 +1266,8 @@
 		purple_notify_error(gc, NULL,
 				  _("Unable to display the search results."),
 				  NULL);
-		ggp_sr_close_cb(form);
+		if (form->window == NULL)
+			ggp_sr_close_cb(form);
 		return;
 	}
 
@@ -1297,11 +1309,6 @@
 			(birth && strncmp(birth, "0", 1)) ? birth : g_strdup("-"));
 
 		purple_notify_searchresults_row_add(results, row);
-
-		if (i == res_count - 1) {
-			g_free(form->last_uin);
-			form->last_uin = ggp_search_get_result(req, i, GG_PUBDIR50_UIN);
-		}
 	}
 
 	purple_notify_searchresults_button_add(results, PURPLE_NOTIFY_BUTTON_CONTINUE,
@@ -1342,7 +1349,8 @@
 
 	seq = gg_pubdir50_seq(req);
 	form = ggp_search_get(info->searches, seq);
-	purple_debug_info("gg", "ggp_pubdir_reply_handler(): seq %u --> form %p", seq, form);
+	purple_debug_info("gg",
+		"ggp_pubdir_reply_handler(): seq %u --> form %p\n", seq, form);
 	/*
 	 * this can happen when user will request more results
 	 * and close the results window before they arrive.
@@ -1355,7 +1363,8 @@
 		purple_notify_error(gc, NULL,
 			_("No matching users found"),
 			_("There are no users matching your search criteria."));
-		ggp_sr_close_cb(form);
+		if (form->window == NULL)
+			ggp_sr_close_cb(form);
 		return;
 	}
 
@@ -1914,6 +1923,17 @@
 				PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
 				_("Connection failed"));
 			break;
+		case GG_EVENT_MSG:
+			if (ev->event.msg.sender == 0)
+				/* system messages are mostly ads */
+				purple_debug_info("gg", "System message:\n%s\n",
+					ev->event.msg.message);
+			else
+				purple_debug_warning("gg", "GG_EVENT_MSG: message from user %u "
+					"unexpected while connecting:\n%s\n",
+					ev->event.msg.sender,
+					ev->event.msg.message);
+			break;
 		default:
 			purple_debug_error("gg", "strange event: %d\n", ev->type);
 			break;
@@ -1938,23 +1958,18 @@
 	char *text;
 	char *tmp;
 
-	status = purple_presence_get_active_status(purple_buddy_get_presence(b));
-
+	status = purple_presence_get_active_status(
+		purple_buddy_get_presence(b));
 	msg = purple_status_get_attr_string(status, "message");
 
-	if (msg != NULL) {
-		tmp = purple_markup_strip_html(msg);
-		text = g_markup_escape_text(tmp, -1);
-		g_free(tmp);
-
-		return text;
-	} else {
-		tmp = purple_utf8_salvage(purple_status_get_name(status));
-		text = g_markup_escape_text(tmp, -1);
-		g_free(tmp);
-
-		return text;
-	}
+	if (msg == NULL)
+		return NULL;
+
+	tmp = purple_markup_strip_html(msg);
+	text = g_markup_escape_text(tmp, -1);
+	g_free(tmp);
+
+	return text;
 }
 
 static void ggp_tooltip_text(PurpleBuddy *b, PurpleNotifyUserInfo *user_info, gboolean full)
@@ -2061,20 +2076,6 @@
 		m = g_list_append(m, act);
 	}
 
-	/* Using a blist node boolean here is also wrong.
-	 * Once the Block and Unblock actions are added to the core,
-	 * this will have to go. -- rlaager */
-	if (purple_blist_node_get_bool(node, "blocked")) {
-		act = purple_menu_action_new(_("Unblock"),
-		                           PURPLE_CALLBACK(ggp_bmenu_block),
-		                           NULL, NULL);
-	} else {
-		act = purple_menu_action_new(_("Block"),
-		                           PURPLE_CALLBACK(ggp_bmenu_block),
-		                           NULL, NULL);
-	}
-	m = g_list_append(m, act);
-
 	return m;
 }
 
@@ -2371,8 +2372,6 @@
 
 	form->user_data = info;
 	form->uin = g_strdup(name);
-	form->offset = g_strdup("0");
-	form->last_uin = g_strdup("0");
 
 	seq = ggp_search_start(gc, form);
 	ggp_search_add(info->searches, seq, form);
@@ -2673,9 +2672,9 @@
 	ggp_remove_buddy,		/* remove_buddy */
 	NULL,				/* remove_buddies */
 	NULL,				/* add_permit */
-	NULL,				/* add_deny */
+	ggp_add_deny,			/* add_deny */
 	NULL,				/* rem_permit */
-	NULL,				/* rem_deny */
+	ggp_rem_deny,			/* rem_deny */
 	NULL,				/* set_permit_deny */
 	ggp_join_chat,			/* join_chat */
 	NULL,				/* reject_chat */
--- a/libpurple/protocols/gg/lib/libgadu.c	Sun Aug 07 03:21:39 2011 +0000
+++ b/libpurple/protocols/gg/lib/libgadu.c	Sun Aug 07 05:19:54 2011 +0000
@@ -893,8 +893,7 @@
 		gnutls_global_init();
 		gnutls_certificate_allocate_credentials(&tmp->xcred);
 		gnutls_init(&tmp->session, GNUTLS_CLIENT);
-		gnutls_priority_set_direct(tmp->session, "NORMAL:-VERS-TLS", NULL);
-//		gnutls_priority_set_direct(tmp->session, "NONE:+VERS-SSL3.0:+AES-128-CBC:+RSA:+SHA1:+COMP-NULL", NULL);
+		gnutls_set_default_priority(tmp->session);
 		gnutls_credentials_set(tmp->session, GNUTLS_CRD_CERTIFICATE, tmp->xcred);
 #elif defined(GG_CONFIG_HAVE_OPENSSL)
 		char buf[1024];
--- a/libpurple/protocols/gg/lib/libgadu.h	Sun Aug 07 03:21:39 2011 +0000
+++ b/libpurple/protocols/gg/lib/libgadu.h	Sun Aug 07 05:19:54 2011 +0000
@@ -1442,6 +1442,7 @@
 
 int gg_file_hash_sha1(int fd, uint8_t *result) GG_DEPRECATED;
 
+#undef printf
 #ifdef __GNUC__
 char *gg_saprintf(const char *format, ...) __attribute__ ((format (printf, 1, 2))) GG_DEPRECATED;
 #else
--- a/libpurple/protocols/gg/lib/resolver.c	Sun Aug 07 03:21:39 2011 +0000
+++ b/libpurple/protocols/gg/lib/resolver.c	Sun Aug 07 05:19:54 2011 +0000
@@ -249,6 +249,7 @@
 #endif /* GG_CONFIG_HAVE_GETHOSTBYNAME_R */
 }
 
+#if defined(GG_CONFIG_HAVE_PTHREAD) || !defined(_WIN32)
 /**
  * \internal Rozwiązuje nazwę i zapisuje wynik do podanego desktyptora.
  *
@@ -286,6 +287,7 @@
 
 	return res;
 }
+#endif
 
 /**
  * \internal Odpowiednik \c gethostbyname zapewniający współbieżność.
--- a/libpurple/protocols/gg/search.c	Sun Aug 07 03:21:39 2011 +0000
+++ b/libpurple/protocols/gg/search.c	Sun Aug 07 05:19:54 2011 +0000
@@ -38,6 +38,8 @@
 	form->window = NULL;
 	form->user_data = NULL;
 	form->seq = 0;
+	form->page_number = 0;
+	form->page_size = 0;
 
 	form->uin = NULL;
 	form->lastname = NULL;
@@ -47,8 +49,6 @@
 	form->birthyear = NULL;
 	form->gender = NULL;
 	form->active = NULL;
-	form->offset = NULL;
-	form->last_uin = NULL;
 
 	return form;
 }
@@ -62,6 +62,8 @@
 	form->window = NULL;
 	form->user_data = NULL;
 	form->seq = 0;
+	form->page_number = 0;
+	form->page_size = 0;
 
 	g_free(form->uin);
 	g_free(form->lastname);
@@ -71,8 +73,6 @@
 	g_free(form->birthyear);
 	g_free(form->gender);
 	g_free(form->active);
-	g_free(form->offset);
-	g_free(form->last_uin);
 	g_free(form);
 }
 /* }}} */
@@ -137,7 +137,7 @@
 {
 	GGPInfo *info = gc->proto_data;
 	gg_pubdir50_t req;
-	guint seq;
+	guint seq, offset;
 
 	purple_debug_info("gg", "It's time to perform a search...\n");
 
@@ -187,8 +187,10 @@
 		}
 	}
 
-	purple_debug_info("gg", "offset: %s\n", form->offset);
-	gg_pubdir50_add(req, GG_PUBDIR50_START, g_strdup(form->offset));
+	offset = form->page_size * form->page_number;
+	purple_debug_info("gg", "page number: %u, page size: %u, offset: %u\n",
+		form->page_number, form->page_size, offset);
+	gg_pubdir50_add(req, GG_PUBDIR50_START, g_strdup_printf("%u", offset));
 
 	if ((seq = gg_pubdir50(info->session, req)) == 0) {
 		purple_debug_warning("gg", "ggp_bmenu_show_details: Search failed.\n");
--- a/libpurple/protocols/gg/search.h	Sun Aug 07 03:21:39 2011 +0000
+++ b/libpurple/protocols/gg/search.h	Sun Aug 07 05:19:54 2011 +0000
@@ -46,12 +46,11 @@
 	char *birthyear;
 	char *gender;
 	char *active;
-	char *offset;
-
-	char *last_uin;
 
 	GGPSearchType search_type;
 	guint32 seq;
+	guint16 page_number;
+	guint16 page_size; /* how many contacts fits into one page of results */
 
 	void *user_data;
 	void *window;
--- a/libpurple/protocols/irc/irc.c	Sun Aug 07 03:21:39 2011 +0000
+++ b/libpurple/protocols/irc/irc.c	Sun Aug 07 05:19:54 2011 +0000
@@ -101,7 +101,11 @@
 static int irc_send_raw(PurpleConnection *gc, const char *buf, int len)
 {
 	struct irc_conn *irc = (struct irc_conn*)gc->proto_data;
-	return do_send(irc, buf, len);
+	if (len == -1) {
+		len = strlen(buf);
+	}
+	irc_send_len(irc, buf, len);
+	return len;
 }
 
 static void
@@ -144,16 +148,18 @@
 
 int irc_send(struct irc_conn *irc, const char *buf)
 {
-	int ret, buflen;
+    return irc_send_len(irc, buf, strlen(buf));
+}
+
+int irc_send_len(struct irc_conn *irc, const char *buf, int buflen)
+{
+	int ret;
  	char *tosend= g_strdup(buf);
 
 	purple_signal_emit(_irc_plugin, "irc-sending-text", purple_account_get_connection(irc->account), &tosend);
 	if (tosend == NULL)
 		return 0;
 
-	buflen = strlen(tosend);
-
-
 	/* If we're not buffering writes, try to send immediately */
 	if (!irc->writeh)
 		ret = do_send(irc, tosend, buflen);
@@ -245,10 +251,14 @@
 
 static void irc_who_channel(PurpleConversation *conv, struct irc_conn *irc)
 {
-	if (purple_conversation_get_account(conv) == irc->account && purple_conversation_get_type(conv) == PURPLE_CONV_TYPE_CHAT) {
+	if (purple_conversation_get_account(conv) == irc->account
+	    && purple_conversation_get_type(conv) == PURPLE_CONV_TYPE_CHAT
+	    && !purple_conv_chat_has_left(PURPLE_CONV_CHAT(conv))) {
 		char *buf = irc_format(irc, "vc", "WHO", purple_conversation_get_name(conv));
 		
-		purple_debug(PURPLE_DEBUG_INFO, "irc", "Performing periodic who on %s", purple_conversation_get_name(conv));
+		purple_debug(PURPLE_DEBUG_INFO, "irc",
+			     "Performing periodic who on %s\n",
+			     purple_conversation_get_name(conv));
 		irc_send(irc, buf);
 		g_free(buf);
 	}
--- a/libpurple/protocols/irc/irc.h	Sun Aug 07 03:21:39 2011 +0000
+++ b/libpurple/protocols/irc/irc.h	Sun Aug 07 05:19:54 2011 +0000
@@ -106,6 +106,7 @@
 typedef int (*IRCCmdCallback) (struct irc_conn *irc, const char *cmd, const char *target, const char **args);
 
 int irc_send(struct irc_conn *irc, const char *buf);
+int irc_send_len(struct irc_conn *irc, const char *buf, int len);
 gboolean irc_blist_timeout(struct irc_conn *irc);
 gboolean irc_who_channel_timeout(struct irc_conn *irc);
 void irc_buddy_query(struct irc_conn *irc);
--- a/libpurple/protocols/irc/msgs.c	Sun Aug 07 03:21:39 2011 +0000
+++ b/libpurple/protocols/irc/msgs.c	Sun Aug 07 05:19:54 2011 +0000
@@ -445,7 +445,7 @@
 		g_free(userhost);
 		g_free(realname);
 		
-		flags = purple_conv_chat_user_get_flags(chat, cb->name);
+		flags = cb->flags;
 
 		if (args[6][0] == 'G' && !(flags & PURPLE_CBFLAGS_AWAY)) {
 			purple_conv_chat_user_set_flags(chat, cb->name, flags | PURPLE_CBFLAGS_AWAY);
--- a/libpurple/protocols/irc/parse.c	Sun Aug 07 03:21:39 2011 +0000
+++ b/libpurple/protocols/irc/parse.c	Sun Aug 07 05:19:54 2011 +0000
@@ -459,6 +459,7 @@
 				decoded = g_string_append(decoded, "</U>");
 			if (font)
 				decoded = g_string_append(decoded, "</FONT>");
+			bold = italic = underline = font = FALSE;
 			break;
 		default:
 			purple_debug(PURPLE_DEBUG_ERROR, "irc", "Unexpected mIRC formatting character %d\n", *cur);
--- a/libpurple/protocols/jabber/jabber.c	Sun Aug 07 03:21:39 2011 +0000
+++ b/libpurple/protocols/jabber/jabber.c	Sun Aug 07 05:19:54 2011 +0000
@@ -579,7 +579,7 @@
 	 */
 
 	jabber_send_raw(js, buf, len);
-	return len;
+	return (len < 0 ? strlen(buf) : len);
 }
 
 void jabber_send_signal_cb(PurpleConnection *pc, xmlnode **packet,
@@ -3009,7 +3009,7 @@
 {
 	JabberChat *chat = jabber_chat_find_by_conv(conv);
 	GHashTable *components;
-	JabberID *jid;
+	JabberID *jid = NULL;
 	const char *room = NULL, *server = NULL, *handle = NULL;
 
 	if (!chat || !args || !args[0])
@@ -3017,7 +3017,8 @@
 
 	components = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, NULL);
 
-	jid = jabber_id_new(args[0]);
+	if (strchr(args[0], '@'))
+		jid = jabber_id_new(args[0]);
 	if (jid) {
 		room   = jid->node;
 		server = jid->domain;
@@ -3673,8 +3674,7 @@
 	                  PURPLE_CMD_FLAG_CHAT | PURPLE_CMD_FLAG_PRPL_ONLY |
 	                  PURPLE_CMD_FLAG_ALLOW_WRONG_ARGS, "prpl-jabber",
 	                  jabber_cmd_chat_join,
-	                  _("join: &lt;room&gt; [password]:  Join a chat on this server."),
-	                  /* _("join: &lt;room[@server]&gt; [password]:  Join a chat."), */
+	                  _("join: &lt;room[@server]&gt; [password]:  Join a chat."),
 	                  NULL);
 	commands = g_slist_prepend(commands, GUINT_TO_POINTER(id));
 
--- a/libpurple/protocols/jabber/usermood.c	Sun Aug 07 03:21:39 2011 +0000
+++ b/libpurple/protocols/jabber/usermood.c	Sun Aug 07 05:19:54 2011 +0000
@@ -177,12 +177,12 @@
 	xmlnode_set_attrib(publish,"node","http://jabber.org/protocol/mood");
 	moodnode = xmlnode_new_child(xmlnode_new_child(publish, "item"), "mood");
 	xmlnode_set_namespace(moodnode, "http://jabber.org/protocol/mood");
-	if (mood) {
+	if (mood && *mood) {
 		/* if mood is NULL, set an empty mood node, meaning: unset mood */
 	    xmlnode_new_child(moodnode, mood);
 	}
 
-	if (text && text[0] != '\0') {
+	if (text && *text) {
 		xmlnode *textnode = xmlnode_new_child(moodnode, "text");
 		xmlnode_insert_data(textnode, text, -1);
 	}
@@ -195,4 +195,4 @@
 PurpleMood *jabber_get_moods(PurpleAccount *account)
 {
 	return moods;
-}
\ No newline at end of file
+}
--- a/libpurple/protocols/msn/notification.c	Sun Aug 07 03:21:39 2011 +0000
+++ b/libpurple/protocols/msn/notification.c	Sun Aug 07 05:19:54 2011 +0000
@@ -198,6 +198,9 @@
 	{
 		/* RPS authentication */
 
+		if (session->nexus)
+			msn_nexus_destroy(session->nexus);
+
 		session->nexus = msn_nexus_new(session);
 
 		session->nexus->policy = g_strdup(cmd->params[3]);
--- a/libpurple/protocols/msn/slp.c	Sun Aug 07 03:21:39 2011 +0000
+++ b/libpurple/protocols/msn/slp.c	Sun Aug 07 05:19:54 2011 +0000
@@ -284,7 +284,6 @@
 	purple_xfer_unref(xfer);
 
 	slpmsg = msn_slpmsg_file_new(slpcall, purple_xfer_get_size(xfer));
-	msn_slpmsg_set_slplink(slpmsg, slpcall->slplink);
 
 	msn_slplink_send_slpmsg(slpcall->slplink, slpmsg);
 }
--- a/libpurple/protocols/msn/slpcall.c	Sun Aug 07 03:21:39 2011 +0000
+++ b/libpurple/protocols/msn/slpcall.c	Sun Aug 07 05:19:54 2011 +0000
@@ -490,12 +490,10 @@
 		if (img != NULL) {
 			/* DATA PREP */
 			slpmsg = msn_slpmsg_dataprep_new(slpcall);
-			msn_slpmsg_set_slplink(slpmsg, slplink);
 			msn_slplink_queue_slpmsg(slplink, slpmsg);
 
 			/* DATA */
 			slpmsg = msn_slpmsg_obj_new(slpcall, img);
-			msn_slpmsg_set_slplink(slpmsg, slplink);
 			msn_slplink_queue_slpmsg(slplink, slpmsg);
 			purple_imgstore_unref(img);
 
--- a/libpurple/protocols/oscar/family_feedbag.c	Sun Aug 07 03:21:39 2011 +0000
+++ b/libpurple/protocols/oscar/family_feedbag.c	Sun Aug 07 05:19:54 2011 +0000
@@ -127,7 +127,8 @@
 {
 	g_string_append_printf(str, 
 		"%s gid=0x%04hx, bid=0x%04hx, list_type=0x%04hx [%s], name=%s.\n",
-		prefix, item->gid, item->bid, item->type, aim_ssi_type_to_string(item->type), item->name);
+		prefix, item->gid, item->bid, item->type, aim_ssi_type_to_string(item->type),
+		item->name ? item->name : "(null)");
 }
 
 /**
--- a/libpurple/protocols/oscar/oscar.c	Sun Aug 07 03:21:39 2011 +0000
+++ b/libpurple/protocols/oscar/oscar.c	Sun Aug 07 05:19:54 2011 +0000
@@ -743,11 +743,15 @@
 	gc->flags |= PURPLE_CONNECTION_HTML;
 	if (g_str_equal(purple_account_get_protocol_id(account), "prpl-icq")) {
 		od->icq = TRUE;
-		gc->flags |= PURPLE_CONNECTION_SUPPORT_MOODS;
 	} else {
 		gc->flags |= PURPLE_CONNECTION_AUTO_RESP;
 	}
 
+	/* Set this flag based on the protocol_id rather than the username,
+	   because that is what's tied to the get_moods prpl callback. */
+	if (g_str_equal(purple_account_get_protocol_id(account), "prpl-icq"))
+		gc->flags |= PURPLE_CONNECTION_SUPPORT_MOODS;
+
 	od->default_port = purple_account_get_int(account, "port", OSCAR_DEFAULT_LOGIN_PORT);
 
 	encryption_type = purple_account_get_string(account, "encryption", OSCAR_DEFAULT_ENCRYPTION);
@@ -1465,10 +1469,10 @@
 		} else if (previous_status != NULL && purple_status_is_available(previous_status)) {
 			itmsurl = g_strdup(purple_status_get_attr_string(previous_status, "itmsurl"));
 		}
-		purple_debug_info("oscar", "Activating status '%s' for buddy %s, message = '%s', itmsurl = '%s'\n", status_id, info->bn, message, itmsurl);
+		purple_debug_info("oscar", "Activating status '%s' for buddy %s, message = '%s', itmsurl = '%s'\n", status_id, info->bn, message ? message : "(null)", itmsurl ? itmsurl : "(null)");
 		purple_prpl_got_user_status(account, info->bn, status_id, "message", message, "itmsurl", itmsurl, NULL);
 	} else {
-		purple_debug_info("oscar", "Activating status '%s' for buddy %s, message = '%s'\n", status_id, info->bn, message);
+		purple_debug_info("oscar", "Activating status '%s' for buddy %s, message = '%s'\n", status_id, info->bn, message ? message : "(null)");
 		purple_prpl_got_user_status(account, info->bn, status_id, "message", message, NULL);
 	}
 
--- a/libpurple/protocols/sametime/sametime.c	Sun Aug 07 03:21:39 2011 +0000
+++ b/libpurple/protocols/sametime/sametime.c	Sun Aug 07 05:19:54 2011 +0000
@@ -922,6 +922,11 @@
   alias = mwSametimeGroup_getAlias(stgroup);
   type = mwSametimeGroup_getType(stgroup);
 
+  if (!name) {
+    DEBUG_WARN("Can't ensure a null group\n");
+    return;
+  }
+
   DEBUG_INFO("attempting to ensure group %s, called %s\n",
 	     NSTR(name), NSTR(alias));
 
--- a/libpurple/protocols/simple/simple.c	Sun Aug 07 03:21:39 2011 +0000
+++ b/libpurple/protocols/simple/simple.c	Sun Aug 07 05:19:54 2011 +0000
@@ -1932,6 +1932,8 @@
 
 	gc->proto_data = sip = g_new0(struct simple_account_data, 1);
 	sip->gc = gc;
+	sip->fd = -1;
+	sip->listenfd = -1;
 	sip->account = account;
 	sip->registerexpire = 900;
 	sip->udp = purple_account_get_bool(account, "udp", FALSE);
--- a/libpurple/protocols/yahoo/libymsg.c	Sun Aug 07 03:21:39 2011 +0000
+++ b/libpurple/protocols/yahoo/libymsg.c	Sun Aug 07 05:19:54 2011 +0000
@@ -3567,11 +3567,10 @@
 	status_id = purple_status_get_id(status);
 	msg = purple_status_get_attr_string(status, "message");
 
-	if (!strcmp(status_id, YAHOO_STATUS_TYPE_AVAILABLE)) {
-		if ((msg != NULL) && (*msg != '\0'))
-			return YAHOO_STATUS_CUSTOM;
-		else
-			return YAHOO_STATUS_AVAILABLE;
+	if ((msg != NULL) && (*msg != '\0')) {
+		return YAHOO_STATUS_CUSTOM;
+	} else if (!strcmp(status_id, YAHOO_STATUS_TYPE_AVAILABLE)) {
+		return YAHOO_STATUS_AVAILABLE;
 	} else if (!strcmp(status_id, YAHOO_STATUS_TYPE_BRB)) {
 		return YAHOO_STATUS_BRB;
 	} else if (!strcmp(status_id, YAHOO_STATUS_TYPE_BUSY)) {
@@ -4849,6 +4848,11 @@
 
 	if (idle)
 		yahoo_packet_hash_str(pkt, 47, "2");
+	else if (yd->current_status == YAHOO_STATUS_CUSTOM &&
+			!purple_status_is_available(status))
+		/* We are still unavailable in this case.
+		 * Make sure Yahoo knows that */
+		yahoo_packet_hash_str(pkt, 47, "1");
 
 	yahoo_packet_send_and_free(pkt, yd);
 
@@ -4876,7 +4880,10 @@
 	type = purple_status_type_new(PURPLE_STATUS_AWAY, YAHOO_STATUS_TYPE_BRB, _("Be Right Back"), TRUE);
 	types = g_list_append(types, type);
 
-	type = purple_status_type_new(PURPLE_STATUS_UNAVAILABLE, YAHOO_STATUS_TYPE_BUSY, _("Busy"), TRUE);
+	type = purple_status_type_new_with_attrs(PURPLE_STATUS_UNAVAILABLE, YAHOO_STATUS_TYPE_BUSY,
+	                                       _("Busy"), TRUE, TRUE, FALSE,
+	                                       "message", _("Message"),
+	                                       purple_value_new(PURPLE_TYPE_STRING), NULL);
 	types = g_list_append(types, type);
 
 	type = purple_status_type_new(PURPLE_STATUS_AWAY, YAHOO_STATUS_TYPE_NOTATHOME, _("Not at Home"), TRUE);
--- a/libpurple/protocols/yahoo/libymsg.h	Sun Aug 07 03:21:39 2011 +0000
+++ b/libpurple/protocols/yahoo/libymsg.h	Sun Aug 07 05:19:54 2011 +0000
@@ -48,7 +48,7 @@
 #define YAHOO_ROOMLIST_LOCALE "us"
 
 /* Yahoo! JAPAN stuff */
-#define YAHOOJP_PAGER_HOST_REQ_URL "http://cs1.msg.vip.ogk.yahoo.co.jp/capacity"
+#define YAHOOJP_PAGER_HOST_REQ_URL "http://cs1.yahoo.co.jp/capacity"
 #define YAHOOJP_TOKEN_URL "https://login.yahoo.co.jp/config/pwtoken_get?src=ymsgr&ts=&login=%s&passwd=%s&chal=%s"
 #define YAHOOJP_LOGIN_URL "https://login.yahoo.co.jp/config/pwtoken_login?src=ymsgr&ts=&token=%s"
 #define YAHOOJP_PROFILE_URL "http://profiles.yahoo.co.jp/"
--- a/libpurple/tests/Makefile.am	Sun Aug 07 03:21:39 2011 +0000
+++ b/libpurple/tests/Makefile.am	Sun Aug 07 05:19:54 2011 +0000
@@ -17,6 +17,7 @@
 		test_oscar_util.c \
 		test_yahoo_util.c \
 		test_util.c \
+		test_xmlnode.c \
 		$(top_builddir)/libpurple/util.h
 
 check_libpurple_CFLAGS=\
--- a/libpurple/tests/check_libpurple.c	Sun Aug 07 03:21:39 2011 +0000
+++ b/libpurple/tests/check_libpurple.c	Sun Aug 07 05:19:54 2011 +0000
@@ -91,6 +91,7 @@
 	srunner_add_suite(sr, oscar_util_suite());
 	srunner_add_suite(sr, yahoo_util_suite());
 	srunner_add_suite(sr, util_suite());
+	srunner_add_suite(sr, xmlnode_suite());
 
 	/* make this a libpurple "ui" */
 	purple_check_init();
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/tests/test_xmlnode.c	Sun Aug 07 05:19:54 2011 +0000
@@ -0,0 +1,34 @@
+#include <string.h>
+
+#include "tests.h"
+#include "../xmlnode.h"
+
+/*
+ * If we really wanted to test the billion laughs attack we would
+ * need to have more than just 4 ha's.  But as long as this shorter
+ * document fails to parse, the longer one should also fail to parse.
+ */
+START_TEST(test_xmlnode_billion_laughs_attack)
+{
+	const char *malicious_xml_doc = "<!DOCTYPE root [ <!ENTITY ha \"Ha !\"><!ENTITY ha2 \"&ha; &ha;\"><!ENTITY ha3 \"&ha2; &ha2;\"> ]><root>&ha3;</root>";
+
+	/* Uncomment this line if you want to see the error message given by
+	   the parser for the above XML document */
+	/* purple_debug_set_enabled(TRUE); */
+
+	fail_if(xmlnode_from_str(malicious_xml_doc, -1),
+			"xmlnode_from_str() returned an XML tree, but we didn't want it to");
+}
+END_TEST
+
+Suite *
+xmlnode_suite(void)
+{
+	Suite *s = suite_create("Utility Functions");
+
+	TCase *tc = tcase_create("xmlnode");
+	tcase_add_test(tc, test_xmlnode_billion_laughs_attack);
+	suite_add_tcase(s, tc);
+
+	return s;
+}
--- a/libpurple/tests/tests.h	Sun Aug 07 03:21:39 2011 +0000
+++ b/libpurple/tests/tests.h	Sun Aug 07 05:19:54 2011 +0000
@@ -16,6 +16,7 @@
 Suite * oscar_util_suite(void);
 Suite * yahoo_util_suite(void);
 Suite * util_suite(void);
+Suite * xmlnode_suite(void);
 
 /* helper macros */
 #define assert_int_equal(expected, actual) { \
--- a/libpurple/upnp.c	Sun Aug 07 03:21:39 2011 +0000
+++ b/libpurple/upnp.c	Sun Aug 07 05:19:54 2011 +0000
@@ -535,7 +535,7 @@
 		dd->retry_count++;
 		purple_upnp_discover_send_broadcast(dd);
 	} else {
-		if (dd->fd)
+		if (dd->fd != -1)
 			close(dd->fd);
 
 		control_info.status = PURPLE_UPNP_STATUS_UNABLE_TO_DISCOVER;
@@ -662,7 +662,7 @@
 	}
 
 	/* Set up the sockets */
-	sock = socket(AF_INET, SOCK_DGRAM, 0);
+	dd->fd = sock = socket(AF_INET, SOCK_DGRAM, 0);
 	if(sock == -1) {
 		purple_debug_error("upnp",
 			"purple_upnp_discover(): Failed In sock creation\n");
@@ -672,8 +672,6 @@
 		return;
 	}
 
-	dd->fd = sock;
-
 	/* TODO: Non-blocking! */
 	if((hp = gethostbyname(HTTPMU_HOST_ADDRESS)) == NULL) {
 		purple_debug_error("upnp",
@@ -820,7 +818,7 @@
 static void
 looked_up_internal_ip_cb(gpointer data, gint source, const gchar *error_message)
 {
-	if (source) {
+	if (source != -1) {
 		strncpy(control_info.internalip,
 			purple_network_get_local_system_ip(source),
 			sizeof(control_info.internalip));
--- a/libpurple/value.h	Sun Aug 07 03:21:39 2011 +0000
+++ b/libpurple/value.h	Sun Aug 07 05:19:54 2011 +0000
@@ -79,7 +79,8 @@
 	PURPLE_SUBTYPE_XMLNODE,
 	PURPLE_SUBTYPE_USERINFO,
 	PURPLE_SUBTYPE_STORED_IMAGE,
-	PURPLE_SUBTYPE_CERTIFICATEPOOL
+	PURPLE_SUBTYPE_CERTIFICATEPOOL,
+	PURPLE_SUBTYPE_CHATBUDDY
 } PurpleSubType;
 
 /**
--- a/libpurple/xmlnode.c	Sun Aug 07 03:21:39 2011 +0000
+++ b/libpurple/xmlnode.c	Sun Aug 07 05:19:54 2011 +0000
@@ -62,7 +62,7 @@
 xmlnode*
 xmlnode_new(const char *name)
 {
-	g_return_val_if_fail(name != NULL, NULL);
+	g_return_val_if_fail(name != NULL && *name != '\0', NULL);
 
 	return new_node(name, XMLNODE_TYPE_TAG);
 }
@@ -73,7 +73,7 @@
 	xmlnode *node;
 
 	g_return_val_if_fail(parent != NULL, NULL);
-	g_return_val_if_fail(name != NULL, NULL);
+	g_return_val_if_fail(name != NULL && *name != '\0', NULL);
 
 	node = new_node(name, XMLNODE_TYPE_TAG);
 
--- a/pidgin/gtkaccount.c	Sun Aug 07 03:21:39 2011 +0000
+++ b/pidgin/gtkaccount.c	Sun Aug 07 05:19:54 2011 +0000
@@ -2135,7 +2135,7 @@
 	gtk_list_store_clear(dialog->model);
 
 	if ((path = purple_prefs_get_path(PIDGIN_PREFS_ROOT "/accounts/buddyicon")) != NULL) {
-		GdkPixbuf *pixbuf = gdk_pixbuf_new_from_file(path, NULL);
+		GdkPixbuf *pixbuf = pidgin_pixbuf_new_from_file(path);
 		if (pixbuf != NULL) {
 			global_buddyicon = gdk_pixbuf_scale_simple(pixbuf, 22, 22, GDK_INTERP_HYPER);
 			g_object_unref(G_OBJECT(pixbuf));
--- a/pidgin/gtkblist.c	Sun Aug 07 03:21:39 2011 +0000
+++ b/pidgin/gtkblist.c	Sun Aug 07 05:19:54 2011 +0000
@@ -2643,7 +2643,6 @@
                                               gboolean scaled, gboolean greyed)
 {
 	gsize len;
-	GdkPixbufLoader *loader;
 	PurpleBuddy *buddy = NULL;
 	PurpleGroup *group = NULL;
 	const guchar *data = NULL;
@@ -2710,21 +2709,20 @@
 			return NULL;
 	}
 
-	loader = gdk_pixbuf_loader_new();
-	gdk_pixbuf_loader_write(loader, data, len, NULL);
-	gdk_pixbuf_loader_close(loader, NULL);
-
-	purple_imgstore_unref(custom_img);
+	buf = pidgin_pixbuf_from_data(data, len);
 	purple_buddy_icon_unref(icon);
-
-	buf = gdk_pixbuf_loader_get_pixbuf(loader);
-	if (buf)
-		g_object_ref(G_OBJECT(buf));
-	g_object_unref(G_OBJECT(loader));
-
 	if (!buf) {
+		purple_debug_warning("gtkblist", "Couldn't load buddy icon "
+				"on account %s (%s)  buddyname=%s  "
+				"custom_img_data=%p\n",
+				account ? purple_account_get_username(account) : "(no account)",
+				account ? purple_account_get_protocol_id(account) : "(no account)",
+				buddy ? purple_buddy_get_name(buddy) : "(no buddy)",
+				custom_img ? purple_imgstore_get_data(custom_img) : NULL);
+		purple_imgstore_unref(custom_img);
 		return NULL;
 	}
+	purple_imgstore_unref(custom_img);
 
 	if (greyed) {
 		gboolean offline = FALSE, idle = FALSE;
@@ -3649,6 +3647,7 @@
 	{ N_("/Help/_Build Information"), NULL, pidgin_dialogs_buildinfo, 0, "<Item>", NULL },
 	{ N_("/Help/_Debug Window"), NULL, toggle_debug, 0, "<Item>", NULL },
 	{ N_("/Help/De_veloper Information"), NULL, pidgin_dialogs_developers, 0, "<Item>", NULL },
+	{ N_("/Help/_Plugin Information"), NULL, pidgin_dialogs_plugins_info, 0, "<Item>", NULL },
 	{ N_("/Help/_Translator Information"), NULL, pidgin_dialogs_translators, 0, "<Item>", NULL },
 	{ "/Help/sep2", NULL, NULL, 0, "<Separator>", NULL },
 	{ N_("/Help/_About"), NULL, pidgin_dialogs_about, 4,  "<StockItem>", GTK_STOCK_ABOUT },
@@ -3952,7 +3951,7 @@
 		g_object_ref(pb);
 		g_free(path);
 	} else {
-		pb = gdk_pixbuf_new_from_file(path, NULL);
+		pb = pidgin_pixbuf_new_from_file(path);
 		if (pb != NULL) {
 			/* We don't want to own a ref to the pixbuf, but we need to keep clean up. */
 			/* I'm not sure if it would be better to just keep our ref and not let the emblem ever be destroyed */
--- a/pidgin/gtkconv.c	Sun Aug 07 03:21:39 2011 +0000
+++ b/pidgin/gtkconv.c	Sun Aug 07 05:19:54 2011 +0000
@@ -3978,6 +3978,16 @@
 }
 
 static void
+deleting_chat_buddy_cb(PurpleConvChatBuddy *cb)
+{
+	if (cb->ui_data) {
+		GtkTreeRowReference *ref = cb->ui_data;
+		gtk_tree_row_reference_free(ref);
+		cb->ui_data = NULL;
+	}
+}
+
+static void
 add_chat_buddy_common(PurpleConversation *conv, PurpleConvChatBuddy *cb, const char *old_name)
 {
 	PidginConversation *gtkconv;
@@ -3985,18 +3995,20 @@
 	PurpleConvChat *chat;
 	PurpleConnection *gc;
 	PurplePluginProtocolInfo *prpl_info;
+	GtkTreeModel *tm;
 	GtkListStore *ls;
+	GtkTreePath *newpath;
 	const char *stock;
 	GtkTreeIter iter;
 	gboolean is_me = FALSE;
 	gboolean is_buddy;
 	gchar *tmp, *alias_key, *name, *alias;
-	int flags;
+	PurpleConvChatBuddyFlags flags;
 	GdkColor *color = NULL;
 
 	alias = cb->alias;
 	name  = cb->name;
-	flags = GPOINTER_TO_INT(cb->flags);
+	flags = cb->flags;
 
 	chat    = PURPLE_CONV_CHAT(conv);
 	gtkconv = PIDGIN_CONVERSATION(conv);
@@ -4006,7 +4018,8 @@
 	if (!gc || !(prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(gc->prpl)))
 		return;
 
-	ls = GTK_LIST_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(gtkchat->list)));
+	tm = gtk_tree_view_get_model(GTK_TREE_VIEW(gtkchat->list));
+	ls = GTK_LIST_STORE(tm);
 
 	stock = get_chat_buddy_status_icon(chat, name, flags);
 
@@ -4051,6 +4064,15 @@
 			CHAT_USERS_WEIGHT_COLUMN, is_buddy ? PANGO_WEIGHT_BOLD : PANGO_WEIGHT_NORMAL,
 			-1);
 
+	if (cb->ui_data) {
+		GtkTreeRowReference *ref = cb->ui_data;
+		gtk_tree_row_reference_free(ref);
+	}
+
+	newpath = gtk_tree_model_get_path(tm, &iter);
+	cb->ui_data = gtk_tree_row_reference_new(tm, newpath);
+	gtk_tree_path_free(newpath);
+
 	if (is_me && color)
 		gdk_color_free(color);
 	g_free(alias_key);
@@ -4346,6 +4368,12 @@
 	                   CHAT_USERS_WEIGHT_COLUMN, &buddy2,
 	                   -1);
 
+	/* Only sort by membership levels */
+	f1 &= PURPLE_CBFLAGS_VOICE | PURPLE_CBFLAGS_HALFOP | PURPLE_CBFLAGS_OP |
+			PURPLE_CBFLAGS_FOUNDER;
+	f2 &= PURPLE_CBFLAGS_VOICE | PURPLE_CBFLAGS_HALFOP | PURPLE_CBFLAGS_OP |
+			PURPLE_CBFLAGS_FOUNDER;
+
 	if (user1 == NULL || user2 == NULL) {
 		if (!(user1 == NULL && user2 == NULL))
 			ret = (user1 == NULL) ? -1: 1;
@@ -6102,6 +6130,28 @@
 	update_typing_message(gtkconv, NULL);
 }
 
+static gboolean get_iter_from_chatbuddy(PurpleConvChatBuddy *cb, GtkTreeIter *iter)
+{
+	GtkTreeRowReference *ref = cb->ui_data;
+	GtkTreePath *path;
+	GtkTreeModel *model;
+
+	if (!ref)
+		return FALSE;
+
+	if ((path = gtk_tree_row_reference_get_path(ref)) == NULL)
+		return FALSE;
+
+	model = gtk_tree_row_reference_get_model(ref);
+	if (!gtk_tree_model_get_iter(GTK_TREE_MODEL(model), iter, path)) {
+		gtk_tree_path_free(path);
+		return FALSE;
+	}
+
+	gtk_tree_path_free(path);
+	return TRUE;
+}
+
 static void
 pidgin_conv_chat_add_users(PurpleConversation *conv, GList *cbuddies, gboolean new_arrivals)
 {
@@ -6152,12 +6202,10 @@
 	PurpleConvChat *chat;
 	PidginConversation *gtkconv;
 	PidginChatPane *gtkchat;
-	PurpleConvChatBuddyFlags flags;
-	PurpleConvChatBuddy *cbuddy;
+	PurpleConvChatBuddy *old_cbuddy, *new_cbuddy;
 	GtkTreeIter iter;
 	GtkTreeModel *model;
 	GtkTextTag *tag;
-	int f = 1;
 
 	chat    = PURPLE_CONV_CHAT(conv);
 	gtkconv = PIDGIN_CONVERSATION(conv);
@@ -6168,20 +6216,13 @@
 	if (!gtk_tree_model_get_iter_first(GTK_TREE_MODEL(model), &iter))
 		return;
 
-	while (f != 0) {
-		char *val;
-
-		gtk_tree_model_get(GTK_TREE_MODEL(model), &iter, CHAT_USERS_NAME_COLUMN, &val, CHAT_USERS_FLAGS_COLUMN, &flags, -1);
-
-		if (!purple_utf8_strcasecmp(old_name, val)) {
-			gtk_list_store_remove(GTK_LIST_STORE(model), &iter);
-			g_free(val);
-			break;
-		}
-
-		f = gtk_tree_model_iter_next(GTK_TREE_MODEL(model), &iter);
-
-		g_free(val);
+	old_cbuddy = purple_conv_chat_cb_find(chat, old_name);
+	if (get_iter_from_chatbuddy(old_cbuddy, &iter)) {
+		GtkTreeRowReference *ref = old_cbuddy->ui_data;
+
+		gtk_list_store_remove(GTK_LIST_STORE(model), &iter);
+		gtk_tree_row_reference_free(ref);
+		old_cbuddy->ui_data = NULL;
 	}
 
 	if ((tag = get_buddy_tag(conv, old_name, 0, FALSE)))
@@ -6189,14 +6230,14 @@
 	if ((tag = get_buddy_tag(conv, old_name, PURPLE_MESSAGE_NICK, FALSE)))
 		g_object_set(G_OBJECT(tag), "style", PANGO_STYLE_ITALIC, NULL);
 
-	if (!purple_conv_chat_find_user(chat, old_name))
+	if (!old_cbuddy)
 		return;
 
 	g_return_if_fail(new_alias != NULL);
 
-	cbuddy = purple_conv_chat_cb_find(chat, new_name);
-
-	add_chat_buddy_common(conv, cbuddy, old_name);
+	new_cbuddy = purple_conv_chat_cb_find(chat, new_name);
+
+	add_chat_buddy_common(conv, new_cbuddy, old_name);
 }
 
 static void
@@ -6223,6 +6264,7 @@
 		model = gtk_tree_view_get_model(GTK_TREE_VIEW(gtkchat->list));
 
 		if (!gtk_tree_model_get_iter_first(GTK_TREE_MODEL(model), &iter))
+			/* XXX: Break? */
 			continue;
 
 		do {
@@ -6262,8 +6304,6 @@
 	PidginChatPane *gtkchat;
 	GtkTreeIter iter;
 	GtkTreeModel *model;
-	int f = 1;
-	char *alias = NULL;
 
 	chat    = PURPLE_CONV_CHAT(conv);
 	gtkconv = PIDGIN_CONVERSATION(conv);
@@ -6274,35 +6314,16 @@
 	if (!gtk_tree_model_get_iter_first(GTK_TREE_MODEL(model), &iter))
 		return;
 
-	while (f != 0) {
-		char *val;
-
-		gtk_tree_model_get(GTK_TREE_MODEL(model), &iter, CHAT_USERS_NAME_COLUMN, &val, -1);
-
-		if (!purple_utf8_strcasecmp(user, val)) {
-			gtk_tree_model_get(GTK_TREE_MODEL(model), &iter, CHAT_USERS_ALIAS_COLUMN, &alias, -1);
-			gtk_list_store_remove(GTK_LIST_STORE(model), &iter);
-			g_free(val);
-			break;
-		}
-
-		f = gtk_tree_model_iter_next(GTK_TREE_MODEL(model), &iter);
-
-		g_free(val);
-	}
-
-	if (!purple_conv_chat_find_user(chat, user))
-	{
-		g_free(alias);
-		return;
-	}
-
-	g_return_if_fail(alias != NULL);
-
 	cbuddy = purple_conv_chat_cb_find(chat, user);
+	if (get_iter_from_chatbuddy(cbuddy, &iter)) {
+		GtkTreeRowReference *ref = cbuddy->ui_data;
+		gtk_list_store_remove(GTK_LIST_STORE(model), &iter);
+		gtk_tree_row_reference_free(ref);
+		cbuddy->ui_data = NULL;
+	}
+
 	if (cbuddy)
 		add_chat_buddy_common(conv, cbuddy, NULL);
-	g_free(alias);
 }
 
 gboolean
@@ -6387,8 +6408,8 @@
 {
 	PidginConversation *gtkconv;
 	GtkIMHtmlSmiley *smiley;
-	GdkPixbufLoader *loader;
 	const char *sml;
+	GError *error = NULL;
 
 	sml = purple_account_get_protocol_name(conv->account);
 	gtkconv = PIDGIN_CONVERSATION(conv);
@@ -6401,11 +6422,24 @@
 	g_memmove((guchar *)smiley->data + smiley->datasize, data, size);
 	smiley->datasize += size;
 
-	loader = smiley->loader;
-	if (!loader)
+	if (!smiley->loader)
 		return;
 
-	gdk_pixbuf_loader_write(loader, data, size, NULL);
+	if (!gdk_pixbuf_loader_write(smiley->loader, data, size, &error) || error) {
+		purple_debug_warning("gtkconv", "gdk_pixbuf_loader_write() "
+				"failed with size=%zu: %s\n", size,
+				error ? error->message : "(no error message)");
+		if (error)
+			g_error_free(error);
+		/* We must stop using the GdkPixbufLoader because trying to load
+		   certain invalid GIFs with at least gdk-pixbuf 2.23.3 can return
+		   a GdkPixbuf that will cause some operations (like
+		   gdk_pixbuf_scale_simple()) to consume memory in an infinite loop.
+		   But we also don't want to set smiley->loader to NULL because our
+		   code might expect it to be set.  So create a new loader. */
+		g_object_unref(G_OBJECT(smiley->loader));
+		smiley->loader = gdk_pixbuf_loader_new();
+	}
 }
 
 static void
@@ -6413,8 +6447,8 @@
 {
 	PidginConversation *gtkconv;
 	GtkIMHtmlSmiley *smiley;
-	GdkPixbufLoader *loader;
 	const char *sml;
+	GError *error = NULL;
 
 	g_return_if_fail(conv  != NULL);
 	g_return_if_fail(smile != NULL);
@@ -6426,17 +6460,27 @@
 	if (!smiley)
 		return;
 
-	loader = smiley->loader;
-
-	if (!loader)
+	if (!smiley->loader)
 		return;
 
-
-
 	purple_debug_info("gtkconv", "About to close the smiley pixbuf\n");
 
-	gdk_pixbuf_loader_close(loader, NULL);
-
+	if (!gdk_pixbuf_loader_close(smiley->loader, &error) || error) {
+		purple_debug_warning("gtkconv", "gdk_pixbuf_loader_close() "
+				"failed: %s\n",
+				error ? error->message : "(no error message)");
+		if (error)
+			g_error_free(error);
+		/* We must stop using the GdkPixbufLoader because if we tried to
+		   load certain invalid GIFs with all current versions of GDK (as
+		   of 2011-06-15) then it's possible the loader will contain data
+		   that could cause some operations (like gdk_pixbuf_scale_simple())
+		   to consume memory in an infinite loop.  But we also don't want
+		   to set smiley->loader to NULL because our code might expect it
+		   to be set.  So create a new loader. */
+		g_object_unref(G_OBJECT(smiley->loader));
+		smiley->loader = gdk_pixbuf_loader_new();
+	}
 }
 
 static void
@@ -6589,7 +6633,7 @@
 
 		if (purple_conversation_get_type(conv) == PURPLE_CONV_TYPE_IM)
 		{
-			gtk_widget_set_sensitive(win->menu.add, (prpl_info->add_buddy != NULL));
+			gtk_widget_set_sensitive(win->menu.add, (prpl_info->add_buddy != NULL) || (prpl_info->add_buddy_with_invite != NULL));
 			gtk_widget_set_sensitive(win->menu.remove, (prpl_info->remove_buddy != NULL));
 			gtk_widget_set_sensitive(win->menu.send_file,
 									 (prpl_info->send_file != NULL && (!prpl_info->can_receive_file ||
@@ -6936,10 +6980,6 @@
 
 	PurpleBuddy *buddy;
 
-	GdkPixbufLoader *loader;
-	GdkPixbufAnimation *anim;
-	GError *err = NULL;
-
 	PurpleStoredImage *custom_img = NULL;
 	gconstpointer data = NULL;
 	size_t len;
@@ -7017,7 +7057,6 @@
 
 	if (data == NULL) {
 		icon = purple_conv_im_get_icon(PURPLE_CONV_IM(conv));
-
 		if (icon == NULL)
 		{
 			gtk_widget_set_size_request(gtkconv->u.im->icon_container,
@@ -7026,7 +7065,6 @@
 		}
 
 		data = purple_buddy_icon_get_data(icon, &len);
-
 		if (data == NULL)
 		{
 			gtk_widget_set_size_request(gtkconv->u.im->icon_container,
@@ -7035,25 +7073,13 @@
 		}
 	}
 
-	loader = gdk_pixbuf_loader_new();
-	gdk_pixbuf_loader_write(loader, data, len, NULL);
-	gdk_pixbuf_loader_close(loader, &err);
-
+	gtkconv->u.im->anim = pidgin_pixbuf_anim_from_data(data, len);
 	purple_imgstore_unref(custom_img);
 
-	anim = gdk_pixbuf_loader_get_animation(loader);
-	if (anim)
-		g_object_ref(G_OBJECT(anim));
-	g_object_unref(loader);
-
-	if (!anim)
+	if (!gtkconv->u.im->anim) {
+		purple_debug_error("gtkconv", "Couldn't load icon for conv %s\n",
+				purple_conversation_get_name(conv));
 		return;
-	gtkconv->u.im->anim = anim;
-
-	if (err) {
-		purple_debug(PURPLE_DEBUG_ERROR, "gtkconv",
-				   "Buddy icon error: %s\n", err->message);
-		g_error_free(err);
 	}
 
 	if (gdk_pixbuf_animation_is_static_image(gtkconv->u.im->anim)) {
@@ -7857,6 +7883,8 @@
 	purple_prefs_add_bool(PIDGIN_PREFS_ROOT "/conversations/send_underline", FALSE);
 	purple_prefs_add_bool(PIDGIN_PREFS_ROOT "/conversations/spellcheck", TRUE);
 	purple_prefs_add_bool(PIDGIN_PREFS_ROOT "/conversations/show_incoming_formatting", TRUE);
+	purple_prefs_add_bool(PIDGIN_PREFS_ROOT "/conversations/resize_custom_smileys", TRUE);
+	purple_prefs_add_int(PIDGIN_PREFS_ROOT "/conversations/custom_smileys_size", 96);
 	purple_prefs_add_int(PIDGIN_PREFS_ROOT "/conversations/minimum_entry_lines", 2);
 
 	purple_prefs_add_bool(PIDGIN_PREFS_ROOT "/conversations/show_timestamps", TRUE);
@@ -8083,6 +8111,9 @@
 	purple_signal_connect(purple_conversations_get_handle(), "cleared-message-history",
 	                      handle, G_CALLBACK(clear_conversation_scrollback_cb), NULL);
 
+	purple_signal_connect(purple_conversations_get_handle(), "deleting-chat-buddy",
+	                      handle, G_CALLBACK(deleting_chat_buddy_cb), NULL);
+
 	purple_conversations_set_ui_ops(&conversation_ui_ops);
 
 	hidden_convwin = pidgin_conv_window_new();
--- a/pidgin/gtkdialogs.c	Sun Aug 07 03:21:39 2011 +0000
+++ b/pidgin/gtkdialogs.c	Sun Aug 07 05:19:54 2011 +0000
@@ -31,6 +31,7 @@
 
 #include "debug.h"
 #include "notify.h"
+#include "plugin.h"
 #include "prpl.h"
 #include "request.h"
 #include "util.h"
@@ -433,7 +434,7 @@
 
 	/* Generate a logo with a version number */
 	filename = g_build_filename(DATADIR, "pixmaps", "pidgin", "logo.png", NULL);
-	pixbuf = gdk_pixbuf_new_from_file(filename, NULL);
+	pixbuf = pidgin_pixbuf_new_from_file(filename);
 	g_free(filename);
 
 #if 0  /* Don't versionize the logo when the logo has the version in it */
@@ -787,6 +788,52 @@
 	g_free(tmp);
 }
 
+void pidgin_dialogs_plugins_info(void)
+{
+	GString *str;
+	GList *l = NULL;
+	PurplePlugin *plugin = NULL;
+	char *title = g_strdup_printf(_("%s Plugin Information"), PIDGIN_NAME);
+	char *pname = NULL, *pauthor = NULL;
+	const char *pver, *pwebsite, *pid;
+	gboolean ploaded, punloadable;
+	static GtkWidget *plugins_info = NULL;
+
+	str = g_string_sized_new(4096);
+
+	g_string_append_printf(str, "<FONT SIZE=\"4\">%s</FONT><BR/>",
+			_("Plugin Information"));
+
+	for(l = purple_plugins_get_all(); l; l = l->next) {
+		plugin = (PurplePlugin *)l->data;
+
+		pname = g_markup_escape_text(purple_plugin_get_name(plugin), -1);
+		pauthor = g_markup_escape_text(purple_plugin_get_author(plugin), -1);
+		pver = purple_plugin_get_version(plugin);
+		pwebsite = purple_plugin_get_homepage(plugin);
+		pid = purple_plugin_get_id(plugin);
+		punloadable = purple_plugin_is_unloadable(plugin);
+		ploaded = purple_plugin_is_loaded(plugin);
+
+		g_string_append_printf(str,
+				"<FONT SIZE=\"3\"><B>%s</B></FONT><BR/><FONT SIZE=\"2\">"
+				"\t<B>Author:</B> %s<BR/>\t<B>Version:</B> %s<BR/>"
+				"\t<B>Website:</B> %s<BR/>\t<B>ID String:</B> %s<BR/>"
+				"\t<B>Loadable:</B> %s<BR/>\t<B>Loaded:</B> %s<BR/>"
+				"<BR/></FONT>", pname, pauthor ? pauthor : "(null)",
+				pver, pwebsite, pid,
+				punloadable ? "<FONT COLOR=\"#FF0000\"><B>No</B></FONT>" : "Yes",
+				ploaded ? "Yes" : "No");
+	}
+
+	plugins_info = pidgin_build_help_dialog(title, "plugins_info", str);
+	g_signal_connect(G_OBJECT(plugins_info), "destroy",
+			G_CALLBACK(gtk_widget_destroyed), &plugins_info);
+	g_free(title);
+	g_free(pname);
+	g_free(pauthor);
+}
+
 static void
 pidgin_dialogs_im_cb(gpointer data, PurpleRequestFields *fields)
 {
--- a/pidgin/gtkdialogs.h	Sun Aug 07 03:21:39 2011 +0000
+++ b/pidgin/gtkdialogs.h	Sun Aug 07 05:19:54 2011 +0000
@@ -36,6 +36,7 @@
 void pidgin_dialogs_buildinfo(void);
 void pidgin_dialogs_developers(void);
 void pidgin_dialogs_translators(void);
+void pidgin_dialogs_plugins_info(void);
 void pidgin_dialogs_im(void);
 void pidgin_dialogs_im_with_user(PurpleAccount *, const char *);
 void pidgin_dialogs_info(void);
--- a/pidgin/gtkft.c	Sun Aug 07 03:21:39 2011 +0000
+++ b/pidgin/gtkft.c	Sun Aug 07 05:19:54 2011 +0000
@@ -1149,8 +1149,8 @@
 
 	if (purple_xfer_get_size(xfer) <= PIDGIN_XFER_MAX_SIZE_IMAGE_THUMBNAIL) {
 		GdkPixbuf *thumbnail =
-			gdk_pixbuf_new_from_file_at_size(
-				purple_xfer_get_local_filename(xfer), 128, 128, NULL);
+			pidgin_pixbuf_new_from_file_at_size(
+					purple_xfer_get_local_filename(xfer), 128, 128);
 
 		if (thumbnail) {
 			gchar **formats_split = g_strsplit(formats, ",", 0);
--- a/pidgin/gtkimhtml.c	Sun Aug 07 03:21:39 2011 +0000
+++ b/pidgin/gtkimhtml.c	Sun Aug 07 05:19:54 2011 +0000
@@ -5057,16 +5057,8 @@
 
 		data = imhtml->funcs->image_get_data(image);
 		len = imhtml->funcs->image_get_size(image);
-
-		if (data && len) {
-			GdkPixbufLoader *loader = gdk_pixbuf_loader_new();
-			gdk_pixbuf_loader_write(loader, data, len, NULL);
-			gdk_pixbuf_loader_close(loader, NULL);
-			anim = gdk_pixbuf_loader_get_animation(loader);
-			if (anim)
-				g_object_ref(G_OBJECT(anim));
-			g_object_unref(G_OBJECT(loader));
-		}
+		if (data && len)
+			anim = pidgin_pixbuf_anim_from_data(data, len);
 
 	}
 
@@ -5699,18 +5691,19 @@
 static void
 gtk_custom_smiley_size_prepared(GdkPixbufLoader *loader, gint width, gint height, gpointer data)
 {
-#define CUSTOM_SMILEY_SIZE 96	/* XXX: Should this be a theme setting? */
-	if (width <= CUSTOM_SMILEY_SIZE && height <= CUSTOM_SMILEY_SIZE)
-		return;
-
-	if (width >= height) {
-		height = height * CUSTOM_SMILEY_SIZE / width;
-		width = CUSTOM_SMILEY_SIZE;
-	} else {
-		width = width * CUSTOM_SMILEY_SIZE / height;
-		height = CUSTOM_SMILEY_SIZE;
+	if (purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/conversations/resize_custom_smileys")) {
+		int custom_smileys_size = purple_prefs_get_int(PIDGIN_PREFS_ROOT "/conversations/custom_smileys_size");
+		if (width <= custom_smileys_size && height <= custom_smileys_size)
+			return;
+
+		if (width >= height) {
+			height = height * custom_smileys_size / width;
+			width = custom_smileys_size;
+		} else {
+			width = width * custom_smileys_size / height;
+			height = custom_smileys_size;
+		}
 	}
-
 	gdk_pixbuf_loader_set_size(loader, width, height);
 }
 
--- a/pidgin/gtkmain.c	Sun Aug 07 03:21:39 2011 +0000
+++ b/pidgin/gtkmain.c	Sun Aug 07 05:19:54 2011 +0000
@@ -270,7 +270,7 @@
 	/* use the nice PNG icon for all the windows */
 	for(i=0; i<G_N_ELEMENTS(icon_sizes); i++) {
 		icon_path = g_build_filename(DATADIR, "icons", "hicolor", icon_sizes[i].dir, "apps", icon_sizes[i].filename, NULL);
-		icon = gdk_pixbuf_new_from_file(icon_path, NULL);
+		icon = pidgin_pixbuf_new_from_file(icon_path);
 		g_free(icon_path);
 		if (icon) {
 			icons = g_list_append(icons,icon);
@@ -381,8 +381,10 @@
 		 * account "markdoliner."  Please don't use this key for other
 		 * applications.  You can either not specify a client key, in
 		 * which case the default "libpurple" key will be used, or you
-		 * can register for your own client key at
-		 * http://developer.aim.com/manageKeys.jsp
+		 * can try to register your own at the AIM or ICQ web sites
+		 * (although this functionality was removed at some point, it's
+		 * possible it has been re-added).  AOL's old key management
+		 * page is http://developer.aim.com/manageKeys.jsp
 		 */
 		g_hash_table_insert(ui_info, "prpl-aim-clientkey", "ma1cSASNCKFtrdv9");
 		g_hash_table_insert(ui_info, "prpl-icq-clientkey", "ma1cSASNCKFtrdv9");
--- a/pidgin/gtkmedia.c	Sun Aug 07 03:21:39 2011 +0000
+++ b/pidgin/gtkmedia.c	Sun Aug 07 05:19:54 2011 +0000
@@ -538,12 +538,20 @@
 	GdkWindow *window = NULL;
 
 	if (data->participant == NULL)
+#if GTK_CHECK_VERSION(2, 14, 0)
 		window = gtk_widget_get_window(priv->local_video);
+#else
+		window = (priv->local_video)->window;
+#endif
 	else {
 		GtkWidget *widget = pidgin_media_get_widget(data->gtkmedia,
 				data->session_id, data->participant);
 		if (widget)
+#if GTK_CHECK_VERSION(2, 14, 0)
 			window = gtk_widget_get_window(widget);
+#else
+			window = widget->window;
+#endif
 	}
 
 	if (window) {
--- a/pidgin/gtkprefs.c	Sun Aug 07 03:21:39 2011 +0000
+++ b/pidgin/gtkprefs.c	Sun Aug 07 05:19:54 2011 +0000
@@ -382,7 +382,7 @@
 		 * LEAK - Gentoo memprof thinks pixbuf is leaking here... but it
 		 * looks like it should be ok to me.  Anyone know what's up?  --Mark
 		 */
-		pixbuf = (theme->icon ? gdk_pixbuf_new_from_file(theme->icon, NULL) : NULL);
+		pixbuf = (theme->icon ? pidgin_pixbuf_new_from_file(theme->icon) : NULL);
 
 		gtk_list_store_set(prefs_smiley_themes, &iter,
 				   0, pixbuf,
@@ -452,7 +452,7 @@
 
 		image_full = purple_theme_get_image_full(theme);
 		if (image_full != NULL){
-			pixbuf = gdk_pixbuf_new_from_file_at_scale(image_full, PREFS_OPTIMAL_ICON_SIZE, PREFS_OPTIMAL_ICON_SIZE, TRUE, NULL);
+			pixbuf = pidgin_pixbuf_new_from_file_at_scale(image_full, PREFS_OPTIMAL_ICON_SIZE, PREFS_OPTIMAL_ICON_SIZE, TRUE);
 			g_free(image_full);
 		} else
 			pixbuf = NULL;
@@ -473,7 +473,7 @@
 
 		image_full = purple_theme_get_image_full(theme);
 		if (image_full != NULL){
-			pixbuf = gdk_pixbuf_new_from_file_at_scale(image_full, PREFS_OPTIMAL_ICON_SIZE, PREFS_OPTIMAL_ICON_SIZE, TRUE, NULL);
+			pixbuf = pidgin_pixbuf_new_from_file_at_scale(image_full, PREFS_OPTIMAL_ICON_SIZE, PREFS_OPTIMAL_ICON_SIZE, TRUE);
 			g_free(image_full);
 		} else
 			pixbuf = NULL;
@@ -529,7 +529,7 @@
 	purple_theme_manager_refresh();
 
 	tmp = g_build_filename(DATADIR, "icons", "hicolor", "32x32", "apps", "pidgin.png", NULL);
-	pixbuf = gdk_pixbuf_new_from_file_at_scale(tmp, PREFS_OPTIMAL_ICON_SIZE, PREFS_OPTIMAL_ICON_SIZE, TRUE, NULL);
+	pixbuf = pidgin_pixbuf_new_from_file_at_scale(tmp, PREFS_OPTIMAL_ICON_SIZE, PREFS_OPTIMAL_ICON_SIZE, TRUE);
 	g_free(tmp);
 
 	/* sound themes */
@@ -1448,6 +1448,9 @@
 	GtkWidget *iconpref2;
 	GtkWidget *imhtml;
 	GtkWidget *frame;
+	GtkWidget *hbox;
+	GtkWidget *checkbox;
+	GtkWidget *spin_button;
 
 	ret = gtk_vbox_new(FALSE, PIDGIN_HIG_CAT_SPACE);
 	gtk_container_set_border_width(GTK_CONTAINER(ret), PIDGIN_HIG_BORDER);
@@ -1480,6 +1483,24 @@
 #ifdef _WIN32
 	pidgin_prefs_checkbox(_("F_lash window when IMs are received"), PIDGIN_PREFS_ROOT "/win32/blink_im", vbox);
 #endif
+	hbox = gtk_hbox_new(FALSE, PIDGIN_HIG_BOX_SPACE);
+
+	checkbox = pidgin_prefs_checkbox(_("Resize incoming custom smileys"),
+			PIDGIN_PREFS_ROOT "/conversations/resize_custom_smileys", hbox);
+
+	spin_button = pidgin_prefs_labeled_spin_button(hbox,
+		_("Maximum size:"),
+		PIDGIN_PREFS_ROOT "/conversations/custom_smileys_size",
+		16, 512, NULL);
+
+	if (!purple_prefs_get_bool(
+				PIDGIN_PREFS_ROOT "/conversations/resize_custom_smileys"))
+		gtk_widget_set_sensitive(GTK_WIDGET(spin_button), FALSE);
+
+	g_signal_connect(G_OBJECT(checkbox), "clicked",
+					 G_CALLBACK(pidgin_toggle_sensitive), spin_button);
+
+	pidgin_add_widget_to_vbox(GTK_BOX(vbox), NULL, NULL, hbox, TRUE, NULL);
 
 	pidgin_prefs_labeled_spin_button(vbox,
 		_("Minimum input area height in lines:"),
--- a/pidgin/gtkrequest.c	Sun Aug 07 03:21:39 2011 +0000
+++ b/pidgin/gtkrequest.c	Sun Aug 07 05:19:54 2011 +0000
@@ -653,35 +653,30 @@
 
 	/* Dialog icon. */
 	if (icon_data) {
-		GdkPixbufLoader *loader = gdk_pixbuf_loader_new();
-		GdkPixbuf *pixbuf = NULL;
-		if (gdk_pixbuf_loader_write(loader, icon_data, icon_size, NULL)) {
-			pixbuf = gdk_pixbuf_loader_get_pixbuf(loader);
-			if (pixbuf) {
-				/* scale the image if it is too large */
-				int width = gdk_pixbuf_get_width(pixbuf);
-				int height = gdk_pixbuf_get_height(pixbuf);
-				if (width > 128 || height > 128) {
-					int scaled_width = width > height ? 128 : (128 * width) / height;
-					int scaled_height = height > width ? 128 : (128 * height) / width;
-					GdkPixbuf *scaled =
-							gdk_pixbuf_scale_simple(pixbuf, scaled_width, scaled_height,
-							    GDK_INTERP_BILINEAR);
+		GdkPixbuf *pixbuf = pidgin_pixbuf_from_data(icon_data, icon_size);
+		if (pixbuf) {
+			/* scale the image if it is too large */
+			int width = gdk_pixbuf_get_width(pixbuf);
+			int height = gdk_pixbuf_get_height(pixbuf);
+			if (width > 128 || height > 128) {
+				int scaled_width = width > height ? 128 : (128 * width) / height;
+				int scaled_height = height > width ? 128 : (128 * height) / width;
+				GdkPixbuf *scaled =
+						gdk_pixbuf_scale_simple(pixbuf, scaled_width, scaled_height,
+						    GDK_INTERP_BILINEAR);
 
-					purple_debug_info("pidgin",
-					    "dialog icon was too large, scale it down\n");
-					if (scaled) {
-						g_object_unref(pixbuf);
-						pixbuf = scaled;
-					}
+				purple_debug_info("pidgin",
+				    "dialog icon was too large, scaled it down\n");
+				if (scaled) {
+					g_object_unref(pixbuf);
+					pixbuf = scaled;
 				}
-				img = gtk_image_new_from_pixbuf(pixbuf);
 			}
+			img = gtk_image_new_from_pixbuf(pixbuf);
+			g_object_unref(pixbuf);
 		} else {
 			purple_debug_info("pidgin", "failed to parse dialog icon\n");
 		}
-		gdk_pixbuf_loader_close(loader, NULL);
-		g_object_unref(loader);
 	}
 
 	if (!img) {
@@ -1040,22 +1035,17 @@
 {
 	GtkWidget *widget;
 	GdkPixbuf *buf, *scale;
-	GdkPixbufLoader *loader;
 
-	loader = gdk_pixbuf_loader_new();
-	gdk_pixbuf_loader_write(loader,
-							(const guchar *)purple_request_field_image_get_buffer(field),
-							purple_request_field_image_get_size(field),
-							NULL);
-	gdk_pixbuf_loader_close(loader, NULL);
-	buf = gdk_pixbuf_loader_get_pixbuf(loader);
+	buf = pidgin_pixbuf_from_data(
+			(const guchar *)purple_request_field_image_get_buffer(field),
+			purple_request_field_image_get_size(field));
 
 	scale = gdk_pixbuf_scale_simple(buf,
 			purple_request_field_image_get_scale_x(field) * gdk_pixbuf_get_width(buf),
 			purple_request_field_image_get_scale_y(field) * gdk_pixbuf_get_height(buf),
 			GDK_INTERP_BILINEAR);
 	widget = gtk_image_new_from_pixbuf(scale);
-	g_object_unref(G_OBJECT(loader));
+	g_object_unref(G_OBJECT(buf));
 	g_object_unref(G_OBJECT(scale));
 
 #if GTK_CHECK_VERSION(2,12,0)
@@ -1164,7 +1154,7 @@
 			GdkPixbuf* pixbuf = NULL;
 
 			if (icon_path)
-				pixbuf = gdk_pixbuf_new_from_file(icon_path, NULL);
+				pixbuf = pidgin_pixbuf_new_from_file(icon_path);
 
 			gtk_list_store_set(store, &iter,
 						   0, purple_request_field_list_get_data(field, text),
--- a/pidgin/gtksmiley.c	Sun Aug 07 03:21:39 2011 +0000
+++ b/pidgin/gtksmiley.c	Sun Aug 07 05:19:54 2011 +0000
@@ -332,7 +332,7 @@
 
 	g_free(s->filename);
 	s->filename = g_strdup(filename);
-	pixbuf = gdk_pixbuf_new_from_file_at_scale(filename, 64, 64, FALSE, NULL);
+	pixbuf = pidgin_pixbuf_new_from_file_at_scale(filename, 64, 64, FALSE);
 	gtk_image_set_from_pixbuf(GTK_IMAGE(s->smiley_image), pixbuf);
 	if (pixbuf)
 		g_object_unref(G_OBJECT(pixbuf));
@@ -690,7 +690,6 @@
 	FILE *f;
 	gchar *path;
 	size_t wc;
-	GError *err = NULL;
 	PidginSmiley *ps;
 	GdkPixbuf *image;
 
@@ -709,13 +708,11 @@
 	}
 	fclose(f);
 
-	image = gdk_pixbuf_new_from_file(path, &err);
+	image = pidgin_pixbuf_new_from_file(path);
 	g_unlink(path);
 	g_free(path);
-	if (err) {
-		g_error_free(err);
+	if (!image)
 		return;
-	}
 
 	ps = pidgin_smiley_edit(dialog->window, NULL);
 	pidgin_smiley_editor_set_image(ps, image);
--- a/pidgin/gtkstatusbox.c	Sun Aug 07 03:21:39 2011 +0000
+++ b/pidgin/gtkstatusbox.c	Sun Aug 07 05:19:54 2011 +0000
@@ -2225,22 +2225,45 @@
 
 	if (status_box->buddy_icon_img != NULL)
 	{
-		GdkPixbuf *buf, *scale;
-		int scale_width, scale_height;
-		GdkPixbufLoader *loader = gdk_pixbuf_loader_new();
+		GdkPixbufLoader *loader;
+		GError *error = NULL;
+
+		loader = gdk_pixbuf_loader_new();
+
 		g_signal_connect(G_OBJECT(loader), "size-prepared", G_CALLBACK(pixbuf_size_prepared_cb), NULL);
-		gdk_pixbuf_loader_write(loader, purple_imgstore_get_data(status_box->buddy_icon_img),
-		                        purple_imgstore_get_size(status_box->buddy_icon_img), NULL);
-		gdk_pixbuf_loader_close(loader, NULL);
-		buf = gdk_pixbuf_loader_get_pixbuf(loader);
-		scale_width = gdk_pixbuf_get_width(buf);
-		scale_height = gdk_pixbuf_get_height(buf);
-		scale = gdk_pixbuf_new(GDK_COLORSPACE_RGB, TRUE, 8, scale_width, scale_height);
-		gdk_pixbuf_fill(scale, 0x00000000);
-		gdk_pixbuf_copy_area(buf, 0, 0, scale_width, scale_height, scale, 0, 0);
-		if (pidgin_gdk_pixbuf_is_opaque(scale))
-			pidgin_gdk_pixbuf_make_round(scale);
-		status_box->buddy_icon = scale;
+		if (!gdk_pixbuf_loader_write(loader,
+				purple_imgstore_get_data(status_box->buddy_icon_img),
+				purple_imgstore_get_size(status_box->buddy_icon_img),
+				&error) || error)
+		{
+			purple_debug_warning("gtkstatusbox", "gdk_pixbuf_loader_write() "
+					"failed with size=%zu: %s\n",
+					purple_imgstore_get_size(status_box->buddy_icon_img),
+					error ? error->message : "(no error message)");
+			if (error)
+				g_error_free(error);
+		} else if (!gdk_pixbuf_loader_close(loader, &error) || error) {
+			purple_debug_warning("gtkstatusbox", "gdk_pixbuf_loader_close() "
+					"failed for image of size %zu: %s\n",
+					purple_imgstore_get_size(status_box->buddy_icon_img),
+					error ? error->message : "(no error message)");
+			if (error)
+				g_error_free(error);
+		} else {
+			GdkPixbuf *buf, *scale;
+			int scale_width, scale_height;
+
+			buf = gdk_pixbuf_loader_get_pixbuf(loader);
+			scale_width = gdk_pixbuf_get_width(buf);
+			scale_height = gdk_pixbuf_get_height(buf);
+			scale = gdk_pixbuf_new(GDK_COLORSPACE_RGB, TRUE, 8, scale_width, scale_height);
+			gdk_pixbuf_fill(scale, 0x00000000);
+			gdk_pixbuf_copy_area(buf, 0, 0, scale_width, scale_height, scale, 0, 0);
+			if (pidgin_gdk_pixbuf_is_opaque(scale))
+				pidgin_gdk_pixbuf_make_round(scale);
+			status_box->buddy_icon = scale;
+		}
+
 		g_object_unref(loader);
 	}
 
--- a/pidgin/gtkutils.c	Sun Aug 07 03:21:39 2011 +0000
+++ b/pidgin/gtkutils.c	Sun Aug 07 05:19:54 2011 +0000
@@ -615,7 +615,7 @@
 				    tmp, NULL);
 	g_free(tmp);
 
-	pixbuf = gdk_pixbuf_new_from_file(filename, NULL);
+	pixbuf = pidgin_pixbuf_new_from_file(filename);
 	g_free(filename);
 
 	return pixbuf;
@@ -704,7 +704,7 @@
 			                                  "16", "google-talk.png", NULL);
 			GtkWidget *item;
 
-			pixbuf = gdk_pixbuf_new_from_file(filename, NULL);
+			pixbuf = pidgin_pixbuf_new_from_file(filename);
 			g_free(filename);
 
 			gtk_menu_shell_append(GTK_MENU_SHELL(aop_menu->menu),
@@ -723,7 +723,7 @@
 			                                  "16", "facebook.png", NULL);
 			GtkWidget *item;
 
-			pixbuf = gdk_pixbuf_new_from_file(filename, NULL);
+			pixbuf = pidgin_pixbuf_new_from_file(filename);
 			g_free(filename);
 
 			gtk_menu_shell_append(GTK_MENU_SHELL(aop_menu->menu),
@@ -1593,7 +1593,7 @@
 		}
 
 		/* Are we dealing with an image? */
-		pb = gdk_pixbuf_new_from_file(filename, NULL);
+		pb = pidgin_pixbuf_new_from_file(filename);
 		if (pb) {
 			_DndData *data = g_malloc(sizeof(_DndData));
 			gboolean ft = FALSE, im = FALSE;
@@ -2265,7 +2265,7 @@
 	filename = gtk_file_chooser_get_preview_filename(
 					GTK_FILE_CHOOSER(dialog->icon_filesel));
 
-	if (!filename || g_stat(filename, &st) || !(pixbuf = gdk_pixbuf_new_from_file(filename, NULL)))
+	if (!filename || g_stat(filename, &st) || !(pixbuf = pidgin_pixbuf_new_from_file(filename)))
 	{
 		gtk_image_set_from_pixbuf(GTK_IMAGE(dialog->icon_preview), NULL);
 		gtk_label_set_markup(GTK_LABEL(dialog->icon_text), "");
@@ -3086,17 +3086,134 @@
 #endif
 }
 
-GdkPixbuf * pidgin_pixbuf_from_imgstore(PurpleStoredImage *image)
+static GObject *pidgin_pixbuf_from_data_helper(const guchar *buf, gsize count, gboolean animated)
+{
+	GObject *pixbuf;
+	GdkPixbufLoader *loader;
+	GError *error = NULL;
+
+	loader = gdk_pixbuf_loader_new();
+
+	if (!gdk_pixbuf_loader_write(loader, buf, count, &error) || error) {
+		purple_debug_warning("gtkutils", "gdk_pixbuf_loader_write() "
+				"failed with size=%zu: %s\n", count,
+				error ? error->message : "(no error message)");
+		if (error)
+			g_error_free(error);
+		g_object_unref(G_OBJECT(loader));
+		return NULL;
+	}
+
+	if (!gdk_pixbuf_loader_close(loader, &error) || error) {
+		purple_debug_warning("gtkutils", "gdk_pixbuf_loader_close() "
+				"failed for image of size %zu: %s\n", count,
+				error ? error->message : "(no error message)");
+		if (error)
+			g_error_free(error);
+		g_object_unref(G_OBJECT(loader));
+		return NULL;
+	}
+
+	if (animated)
+		pixbuf = G_OBJECT(gdk_pixbuf_loader_get_animation(loader));
+	else
+		pixbuf = G_OBJECT(gdk_pixbuf_loader_get_pixbuf(loader));
+	if (!pixbuf) {
+		purple_debug_warning("gtkutils", "%s() returned NULL for image "
+				"of size %zu\n",
+				animated ? "gdk_pixbuf_loader_get_animation"
+					: "gdk_pixbuf_loader_get_pixbuf", count);
+		g_object_unref(G_OBJECT(loader));
+		return NULL;
+	}
+
+	g_object_ref(pixbuf);
+	g_object_unref(G_OBJECT(loader));
+
+	return pixbuf;
+}
+
+GdkPixbuf *pidgin_pixbuf_from_data(const guchar *buf, gsize count)
+{
+	return GDK_PIXBUF(pidgin_pixbuf_from_data_helper(buf, count, FALSE));
+}
+
+GdkPixbufAnimation *pidgin_pixbuf_anim_from_data(const guchar *buf, gsize count)
+{
+	return GDK_PIXBUF_ANIMATION(pidgin_pixbuf_from_data_helper(buf, count, TRUE));
+}
+
+GdkPixbuf *pidgin_pixbuf_from_imgstore(PurpleStoredImage *image)
+{
+	return pidgin_pixbuf_from_data(purple_imgstore_get_data(image),
+			purple_imgstore_get_size(image));
+}
+
+GdkPixbuf *pidgin_pixbuf_new_from_file(const gchar *filename)
 {
 	GdkPixbuf *pixbuf;
-	GdkPixbufLoader *loader = gdk_pixbuf_loader_new();
-	gdk_pixbuf_loader_write(loader, purple_imgstore_get_data(image),
-			purple_imgstore_get_size(image), NULL);
-	gdk_pixbuf_loader_close(loader, NULL);
-	pixbuf = gdk_pixbuf_loader_get_pixbuf(loader);
-	if (pixbuf)
-		g_object_ref(pixbuf);
-	g_object_unref(loader);
+	GError *error = NULL;
+
+	pixbuf = gdk_pixbuf_new_from_file(filename, &error);
+	if (!pixbuf || error) {
+		purple_debug_warning("gtkutils", "gdk_pixbuf_new_from_file() "
+				"returned %s for file %s: %s\n",
+				pixbuf ? "something" : "nothing",
+				filename,
+				error ? error->message : "(no error message)");
+		if (error)
+			g_error_free(error);
+		if (pixbuf)
+			g_object_unref(G_OBJECT(pixbuf));
+		return NULL;
+	}
+
+	return pixbuf;
+}
+
+GdkPixbuf *pidgin_pixbuf_new_from_file_at_size(const char *filename, int width, int height)
+{
+	GdkPixbuf *pixbuf;
+	GError *error = NULL;
+
+	pixbuf = gdk_pixbuf_new_from_file_at_size(filename,
+			width, height, &error);
+	if (!pixbuf || error) {
+		purple_debug_warning("gtkutils", "gdk_pixbuf_new_from_file_at_size() "
+				"returned %s for file %s: %s\n",
+				pixbuf ? "something" : "nothing",
+				filename,
+				error ? error->message : "(no error message)");
+		if (error)
+			g_error_free(error);
+		if (pixbuf)
+			g_object_unref(G_OBJECT(pixbuf));
+		return NULL;
+	}
+
+	return pixbuf;
+}
+
+GdkPixbuf *pidgin_pixbuf_new_from_file_at_scale(const char *filename, int width, int height, gboolean preserve_aspect_ratio)
+{
+	GdkPixbuf *pixbuf;
+	GError *error = NULL;
+
+	pixbuf = gdk_pixbuf_new_from_file_at_scale(filename,
+			width, height, preserve_aspect_ratio, &error);
+	if (!pixbuf || error) {
+		purple_debug_warning("gtkutils", "gdk_pixbuf_new_from_file_at_scale() "
+				"returned %s for file %s: %s\n",
+				pixbuf ? "something" : "nothing",
+				filename,
+				error ? error->message : "(no error message)");
+		if (error)
+			g_error_free(error);
+		if (pixbuf)
+			g_object_unref(G_OBJECT(pixbuf));
+		return NULL;
+	}
+
 	return pixbuf;
 }
 
--- a/pidgin/gtkutils.h	Sun Aug 07 03:21:39 2011 +0000
+++ b/pidgin/gtkutils.h	Sun Aug 07 05:19:54 2011 +0000
@@ -834,6 +834,32 @@
 GtkWidget *pidgin_add_widget_to_vbox(GtkBox *vbox, const char *widget_label, GtkSizeGroup *sg, GtkWidget *widget, gboolean expand, GtkWidget **p_label);
 
 /**
+ * Create a GdkPixbuf from a chunk of image data.
+ *
+ * @param buf The raw binary image data.
+ * @param count The length of buf in bytes.
+ *
+ * @return A GdkPixbuf created from the image data, or NULL if
+ *         there was an error parsing the data.
+ *
+ * @since 2.9.0
+ */
+GdkPixbuf *pidgin_pixbuf_from_data(const guchar *buf, gsize count);
+
+/**
+ * Create a GdkPixbufAnimation from a chunk of image data.
+ *
+ * @param buf The raw binary image data.
+ * @param count The length of buf in bytes.
+ *
+ * @return A GdkPixbufAnimation created from the image data, or NULL if
+ *         there was an error parsing the data.
+ *
+ * @since 2.9.0
+ */
+GdkPixbufAnimation *pidgin_pixbuf_anim_from_data(const guchar *buf, gsize count);
+
+/**
  * Create a GdkPixbuf from a PurpleStoredImage.
  *
  * @param  image   A PurpleStoredImage.
@@ -845,6 +871,86 @@
 GdkPixbuf *pidgin_pixbuf_from_imgstore(PurpleStoredImage *image);
 
 /**
+ * Helper function that calls gdk_pixbuf_new_from_file() and checks both
+ * the return code and the GError and returns NULL if either one failed.
+ *
+ * The gdk-pixbuf documentation implies that it is sufficient to check
+ * the return value of gdk_pixbuf_new_from_file() to determine
+ * whether the image was able to be loaded.  However, this is not the case
+ * with gdk-pixbuf 2.23.3 and probably many earlier versions.  In some
+ * cases a GdkPixbuf object is returned that will cause some operations
+ * (like gdk_pixbuf_scale_simple()) to rapidly consume memory in an
+ * infinite loop.
+ *
+ * This function shouldn't be necessary once Pidgin requires a version of
+ * gdk-pixbuf where the aforementioned bug is fixed.  However, it might be
+ * nice to keep this function around for the debug message that it logs.
+ *
+ * @param filename Name of file to load, in the GLib file name encoding
+ *
+ * @return The GdkPixbuf if successful.  Otherwise NULL is returned and
+ *         a warning is logged.
+ *
+ * @since 2.9.0
+ */
+GdkPixbuf *pidgin_pixbuf_new_from_file(const char *filename);
+
+/**
+ * Helper function that calls gdk_pixbuf_new_from_file_at_size() and checks
+ * both the return code and the GError and returns NULL if either one failed.
+ *
+ * The gdk-pixbuf documentation implies that it is sufficient to check
+ * the return value of gdk_pixbuf_new_from_file_at_size() to determine
+ * whether the image was able to be loaded.  However, this is not the case
+ * with gdk-pixbuf 2.23.3 and probably many earlier versions.  In some
+ * cases a GdkPixbuf object is returned that will cause some operations
+ * (like gdk_pixbuf_scale_simple()) to rapidly consume memory in an
+ * infinite loop.
+ *
+ * This function shouldn't be necessary once Pidgin requires a version of
+ * gdk-pixbuf where the aforementioned bug is fixed.  However, it might be
+ * nice to keep this function around for the debug message that it logs.
+ *
+ * @param filename Name of file to load, in the GLib file name encoding
+ * @param width The width the image should have or -1 to not constrain the width
+ * @param height The height the image should have or -1 to not constrain the height
+ *
+ * @return The GdkPixbuf if successful.  Otherwise NULL is returned and
+ *         a warning is logged.
+ *
+ * @since 2.9.0
+ */
+GdkPixbuf *pidgin_pixbuf_new_from_file_at_size(const char *filename, int width, int height);
+
+/**
+ * Helper function that calls gdk_pixbuf_new_from_file_at_scale() and checks
+ * both the return code and the GError and returns NULL if either one failed.
+ *
+ * The gdk-pixbuf documentation implies that it is sufficient to check
+ * the return value of gdk_pixbuf_new_from_file_at_scale() to determine
+ * whether the image was able to be loaded.  However, this is not the case
+ * with gdk-pixbuf 2.23.3 and probably many earlier versions.  In some
+ * cases a GdkPixbuf object is returned that will cause some operations
+ * (like gdk_pixbuf_scale_simple()) to rapidly consume memory in an
+ * infinite loop.
+ *
+ * This function shouldn't be necessary once Pidgin requires a version of
+ * gdk-pixbuf where the aforementioned bug is fixed.  However, it might be
+ * nice to keep this function around for the debug message that it logs.
+ *
+ * @param filename Name of file to load, in the GLib file name encoding
+ * @param width The width the image should have or -1 to not constrain the width
+ * @param height The height the image should have or -1 to not constrain the height
+ * @param preserve_aspect_ratio TRUE to preserve the image's aspect ratio
+ *
+ * @return The GdkPixbuf if successful.  Otherwise NULL is returned and
+ *         a warning is logged.
+ *
+ * @since 2.9.0
+ */
+GdkPixbuf *pidgin_pixbuf_new_from_file_at_scale(const char *filename, int width, int height, gboolean preserve_aspect_ratio);
+
+/**
  * Add scrollbars to a widget
  * @param widget      The child widget
  * @hscrollbar_policy Horizontal scrolling policy
--- a/po/ChangeLog	Sun Aug 07 03:21:39 2011 +0000
+++ b/po/ChangeLog	Sun Aug 07 05:19:54 2011 +0000
@@ -1,5 +1,11 @@
 Pidgin and Finch: The Pimpin' Penguin IM Clients That're Good for the Soul
 
+version 2.9.1
+	* German translation updated (Björn Voigt, Jochen Kemnade)
+
+version 2.9.0
+	* No changes
+
 version 2.8.0
 	* Albanian translation updated (Besnik Bleta)
 	* Bengali translation updated (Jamil Ahmed)
--- a/po/de.po	Sun Aug 07 03:21:39 2011 +0000
+++ b/po/de.po	Sun Aug 07 05:19:54 2011 +0000
@@ -11,8 +11,8 @@
 msgstr ""
 "Project-Id-Version: de\n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2011-05-12 09:37+0200\n"
-"PO-Revision-Date: 2011-05-12 09:37+0200\n"
+"POT-Creation-Date: 2011-07-31 12:34+0200\n"
+"PO-Revision-Date: 2011-07-31 12:33+0200\n"
 "Last-Translator: Björn Voigt <bjoern@cs.tu-berlin.de>\n"
 "Language-Team: German <de@li.org>\n"
 "Language: de\n"
@@ -4778,8 +4778,8 @@
 msgstr ""
 "invite &lt;Benutzer&gt; [Nachricht]:  Lade einen Benutzer in den Raum ein."
 
-msgid "join: &lt;room&gt; [password]:  Join a chat on this server."
-msgstr "join: &lt;Raum&gt; [Passwort]:  Betrete einen Chat auf diesem Server."
+msgid "join: &lt;room[@server]&gt; [password]:  Join a chat."
+msgstr "join: &lt;Raum[@Server]&gt; [Passwort]:  Betrete einen Chat."
 
 msgid "kick &lt;user&gt; [reason]:  Kick a user from the room."
 msgstr "kick &lt;Benutzer&gt; [Grund]:  Kickt einen Benutzer aus dem Raum."
@@ -6361,13 +6361,16 @@
 msgstr "Wo ich wohne"
 
 #, c-format
-msgid "You have %i suggested friends."
-msgstr "Sie haben %i vorgeschlagene(n) Freund(e)."
-
-#, c-format
-msgid "We found %i contacts that match your search."
-msgstr ""
-"Wir fanden %i Kontakt(e), die Ihrer Suchanfrage entspricht/entsprechen."
+msgid "You have %i suggested friend."
+msgid_plural "You have %i suggested friends."
+msgstr[0] "Sie haben %i vorgeschlagenen Freund."
+msgstr[1] "Sie haben %i vorgeschlagene Freunde."
+
+#, c-format
+msgid "We found %i contact that matches your search."
+msgid_plural "We found %i contacts that match your search."
+msgstr[0] "Wir fanden %i Kontakt, der Ihrer Suchanfrage entspricht."
+msgstr[1] "Wir fanden %i Kontakte, die Ihrer Suchanfrage entsprechen."
 
 #. we must have lost the connection, so terminate it so that we can reconnect
 msgid "We have lost the connection to MXit. Please reconnect."
@@ -11131,6 +11134,9 @@
 msgid "/Help/De_veloper Information"
 msgstr "/Hilfe/_Entwickler-Informationen"
 
+msgid "/Help/_Plugin Information"
+msgstr "/Hilfe/_Plugin-Informationen"
+
 msgid "/Help/_Translator Information"
 msgstr "/Hilfe/Über_setzer-Informationen"
 
@@ -12146,6 +12152,13 @@
 msgid "%s Translator Information"
 msgstr "%s-Übersetzer-Informationen"
 
+#, c-format
+msgid "%s Plugin Information"
+msgstr "%s Plugin-Informationen"
+
+msgid "Plugin Information"
+msgstr "Plugin-Informationen"
+
 msgid "_Name"
 msgstr "_Name"
 
@@ -13113,6 +13126,12 @@
 msgid "F_lash window when IMs are received"
 msgstr "Fenster b_linkt, wenn IM-Nachrichten empfangen werden"
 
+msgid "Resize incoming custom smileys"
+msgstr "Erhaltene benutzerdefinierte Smileys skalieren"
+
+msgid "Maximum size:"
+msgstr "Maximale Größe:"
+
 msgid "Minimum input area height in lines:"
 msgstr "Minimale Höhe des Eingabefeldes in Zeilen:"
 
@@ -13182,6 +13201,9 @@
 msgid "_UDP Port:"
 msgstr "_UDP-Port:"
 
+msgid "T_CP Port:"
+msgstr "T_CP-Port:"
+
 msgid "Use_rname:"
 msgstr "_Benutzername:"