propagate from branch 'im.pidgin.sadrul.currentmedia' (head aed170143d4317466eb52019a328c54e01784e61)

Fri, 12 Oct 2007 02:06:38 +0000

author
Sadrul Habib Chowdhury <sadrul@pidgin.im>
date
Fri, 12 Oct 2007 02:06:38 +0000
changeset 21203
182f920f98a0
parent 21202
aed170143d43 (current diff)
parent 20827
1c90c7de8688 (diff)
child 21204
93f18311e16a

propagate from branch 'im.pidgin.sadrul.currentmedia' (head aed170143d4317466eb52019a328c54e01784e61)
to branch 'next.minor' (head 1c90c7de868806272bde7e95f1792ee28bb531c2)

libpurple/protocols/jabber/google.c file | annotate | diff | comparison | revisions
libpurple/protocols/jabber/jabber.c file | annotate | diff | comparison | revisions
libpurple/protocols/msn/msn.c file | annotate | diff | comparison | revisions
libpurple/protocols/msn/notification.c file | annotate | diff | comparison | revisions
pidgin/gtkblist.c file | annotate | diff | comparison | revisions
po/sr@Latn.po file | annotate | diff | comparison | revisions
--- a/COPYRIGHT	Wed Sep 26 06:56:02 2007 +0000
+++ b/COPYRIGHT	Fri Oct 12 02:06:38 2007 +0000
@@ -267,6 +267,7 @@
 Ruediger Oertel
 Gudmundur Bjarni Olafsson
 Bartosz Oler
+Stefan Ott
 Shawn Outman
 Nathan Owens (pianocomp81)
 John Oyler
--- a/ChangeLog	Wed Sep 26 06:56:02 2007 +0000
+++ b/ChangeLog	Fri Oct 12 02:06:38 2007 +0000
@@ -1,15 +1,13 @@
 Pidgin and Finch: The Pimpin' Penguin IM Clients That're Good for the Soul
 
-Version 2.2.1:
-	http://developer.pidgin.im/query?status=closed&milestone=2.2.1
-	
+version 2.2.2:
+	http://developer.pidgin.im/query?status=closed&milestone=2.2.2
+		NOTE: Due to 2.2.1 being a security fix release, some bugs
+		      marked fixed in 2.2.1 may not have been fixed until
+		      this release (2.2.2).
+
 	libpurple:
-	* A few build issues on Solaris were fixed.
-	* Cancelling the password prompt for an account will no longer leave
-	  it in an ambiguous state.  (It will be disabled.)
-	* Fixed an erroneous size display for MSN file transfers. (galt)
 	* Real usernames are now shown in the system log.
-	* Gmail notifications are better tracked
 
 	Pidgin:
 	* If you alias a buddy to an alias that is already present within
@@ -30,17 +28,36 @@
 	  resumes, the history will be retained.  A preference has been added
 	  to toggle this behavior.
 	* The "Smiley" menu has been moved to the top-level of the toolbar.
-	* Fixed keyboard tab reordering to move tabs one step instead of two.
 	* Pidgin's display is now saved with the command line for session
 	  restoration.  (David Mohr)
+	* ICQ Birthday notifications are shown as buddy list emblems.
+
+version 2.2.1 (09/29/2007):
+	http://developer.pidgin.im/query?status=closed&milestone=2.2.1
+		NOTE: Due to the backporting that happened for the actual
+		      release, it is possible bugs marked as fixed in 2.2.1
+		      will not be fixed until 2.2.2.
+
+	libpurple:
+	* A few build issues on Solaris were fixed.
+	* Cancelling the password prompt for an account will no longer leave
+	  it in an ambiguous state.  (It will be disabled.)
+	* Fixed an erroneous size display for MSN file transfers. (galt)
+	* Fixed multiple memory leaks, particularly in XMPP and MySpace
+	  protocols
+	* Fixed remembering proxy preferences and status scores
+	* Gmail notifications are better tracked
+
+	Pidgin:
+	* Fixed keyboard tab reordering to move tabs one step instead of two.
 	* You should no longer lose proxy settings when Pidgin is restarted.
-	* ICQ Birthday notifications are shown as buddy list emblems
+	* Fixed detection of X11 when compiling
 
 	Finch:
 	* Pressing 'Insert' in the buddylist will bring up the 'Add Buddy'
 	  dialog.
 
-Version 2.2.0 (09/13/2007):
+version 2.2.0 (09/13/2007):
 	http://developer.pidgin.im/query?status=closed&milestone=2.2.0
 
 	Libpurple:
--- a/NEWS	Wed Sep 26 06:56:02 2007 +0000
+++ b/NEWS	Fri Oct 12 02:06:38 2007 +0000
@@ -1,5 +1,24 @@
 Pidgin and Finch: The Pimpin' Penguin IM Clients That're Good for the Soul
 
+2.2.1 (9/28/2007):
+	Richard: We have some new code in the pipeline, but it's not quite
+	ready for a general release.  Instead, this is basically a bug fix
+	release.
+
+	Luke: Unfortunately the necessity of this bug fix release means
+	some of the tickets that have been closed as part of the 2.2.1
+	milestone are not actually fixed yet.  We have grabbed as many
+	of the changes as we could while avoiding those that are as
+	yet unstable though, and this should still be a marked
+	improvement over 2.2.0.  We have spent a lot of time since the
+	last release looking at the tickets that have been submitted
+	and many of them have been closed.
+
+	Stu: I haven't NEWS'd in a while. I haven't actually done much for
+	too long also, maybe I'll find some time soon. This release is
+	basically what 2.2.0 should have been - it actually compiles this
+	time.
+
 2.2.0 (9/13/2007):
 	Sean: 2.2.0 contains the results of several major Google Summer
 	of Code branches bringing some new, extraordinary features. We
--- a/configure.ac	Wed Sep 26 06:56:02 2007 +0000
+++ b/configure.ac	Fri Oct 12 02:06:38 2007 +0000
@@ -46,8 +46,8 @@
 m4_define([purple_lt_current], [2])
 m4_define([purple_major_version], [2])
 m4_define([purple_minor_version], [2])
-m4_define([purple_micro_version], [1])
-m4_define([purple_version_suffix], [devel])
+m4_define([purple_micro_version], [2])
+m4_define([purple_version_suffix], [])
 m4_define([purple_version],
           [purple_major_version.purple_minor_version.purple_micro_version])
 m4_define([purple_display_version], purple_version[]m4_ifdef([purple_version_suffix],[purple_version_suffix]))
@@ -55,8 +55,8 @@
 m4_define([gnt_lt_current], [2])
 m4_define([gnt_major_version], [2])
 m4_define([gnt_minor_version], [2])
-m4_define([gnt_micro_version], [1])
-m4_define([gnt_version_suffix], [devel])
+m4_define([gnt_micro_version], [2])
+m4_define([gnt_version_suffix], [])
 m4_define([gnt_version],
           [gnt_major_version.gnt_minor_version.gnt_micro_version])
 m4_define([gnt_display_version], gnt_version[]m4_ifdef([gnt_version_suffix],[gnt_version_suffix]))
@@ -136,7 +136,7 @@
 	;;
 esac
 
-ALL_LINGUAS="af am ar az be@latin bg bn bs ca ca@valencia cs da de dz el en_AU en_CA en_GB eo es et eu fa fi fr gl gu he hi hu id it ja ka kn ko ku lt mk my_MM nb ne nl nn pa pl pt_BR pt ps ro ru sk sl sq sr sr@Latn sv ta te th tr uk vi xh zh_CN zh_HK zh_TW"
+ALL_LINGUAS="af am ar az be@latin bg bn bs ca ca@valencia cs da de dz el en_AU en_CA en_GB eo es et eu fa fi fr gl gu he hi hu id it ja ka kn ko ku lo lt mk my_MM nb ne nl nn pa pl pt_BR pt ps ro ru sk sl sq sr sr@latin sv ta te th tr uk vi xh zh_CN zh_HK zh_TW"
 AM_GLIB_GNU_GETTEXT
 
 dnl If we don't have msgfmt, then po/ is going to fail -- ensure that
@@ -1460,7 +1460,7 @@
 	[enable_nss="$enableval"],
 	[enable_nss="yes"])
 
-msg_ssl="None (MSN and Google Talk will not work without SSL!)"
+msg_ssl="None. MSN, Novell Groupwise and Google Talk will not work without GnuTLS or NSS. OpenSSL is NOT usable!"
 
 dnl #
 dnl # Check for GnuTLS if it's specified.
--- a/doc/account-signals.dox	Wed Sep 26 06:56:02 2007 +0000
+++ b/doc/account-signals.dox	Fri Oct 12 02:06:38 2007 +0000
@@ -100,5 +100,38 @@
   @param old     The alias before change.
  @endsignaldef
 
+ @signaldef account-authorization-requested
+  @signalproto
+void (*account_authorization_requested)(PurpleAccount *account, const char *user);
+  @endsignalproto
+  @signaldesc
+   Emitted when a user requests authorization.
+  @param account The account.
+  @param user    The name of the user requesting authorization.
+  @return Less than zero to deny the request without prompting, greater
+          than zero if the request should be granted. If zero is returned,
+          then the user will be prompted with the request.
+ @endsignaldef
+
+ @signaldef account-authorization-denied
+  @signalproto
+void (*account_authorization_denied)(PurpleAccount *account, const char *user);
+  @endsignalproto
+  @signaldesc
+   Emitted when the authorization request for a buddy is denied.
+  @param account The account.
+  @param user    The name of the user requesting authorization.
+ @endsignaldef
+
+ @signaldef account-authorization-granted
+  @signalproto
+void (*account_authorization_granted)(PurpleAccount *account, const char *user);
+  @endsignalproto
+  @signaldesc
+   Emitted when the authorization request for a buddy is granted.
+  @param account The account.
+  @param user    The name of the user requesting authorization.
+ @endsignaldef
+
  */
 // vim: syntax=c tw=75 et
--- a/finch/gntblist.c	Wed Sep 26 06:56:02 2007 +0000
+++ b/finch/gntblist.c	Fri Oct 12 02:06:38 2007 +0000
@@ -2202,12 +2202,12 @@
 	group = purple_request_field_group_new(NULL);
 	purple_request_fields_add_group(fields, group);
 
-	field = purple_request_field_string_new("screenname", _("_Name"), NULL, FALSE);
+	field = purple_request_field_string_new("screenname", _("Name"), NULL, FALSE);
 	purple_request_field_set_type_hint(field, "screenname");
 	purple_request_field_set_required(field, TRUE);
 	purple_request_field_group_add_field(group, field);
 
-	field = purple_request_field_account_new("account", _("_Account"), NULL);
+	field = purple_request_field_account_new("account", _("Account"), NULL);
 	purple_request_field_set_type_hint(field, "account");
 	purple_request_field_set_visible(field,
 		(purple_connections_get_all() != NULL &&
@@ -2227,6 +2227,69 @@
 }
 
 static void
+join_chat_select_cb(gpointer data, PurpleRequestFields *fields)
+{
+	PurpleAccount *account;
+	const char *name;
+	PurpleConnection *gc;
+	PurpleChat *chat;
+	GHashTable *hash = NULL;
+
+	account = purple_request_fields_get_account(fields, "account");
+	name = purple_request_fields_get_string(fields,  "chat");
+
+	if (!purple_account_is_connected(account))
+		return;
+
+	gc = purple_account_get_connection(account);	
+	purple_conversation_new(PURPLE_CONV_TYPE_CHAT, account, name);
+	chat = purple_blist_find_chat(account, name);
+	if (chat == NULL) {
+		if (PURPLE_PLUGIN_PROTOCOL_INFO(gc->prpl)->chat_info_defaults != NULL)
+			hash = PURPLE_PLUGIN_PROTOCOL_INFO(gc->prpl)->chat_info_defaults(gc, name);
+	} else {
+		hash = chat->components;
+	}
+	serv_join_chat(gc, hash);
+	if (chat == NULL && hash != NULL)
+		g_hash_table_destroy(hash);
+}
+
+static void
+join_chat_select(GntMenuItem *item, gpointer n)
+{
+	PurpleRequestFields *fields;
+	PurpleRequestFieldGroup *group;
+	PurpleRequestField *field;
+
+	fields = purple_request_fields_new();
+
+	group = purple_request_field_group_new(NULL);
+	purple_request_fields_add_group(fields, group);
+
+	field = purple_request_field_string_new("chat", _("Channel"), NULL, FALSE);
+	purple_request_field_set_required(field, TRUE);
+	purple_request_field_group_add_field(group, field);
+
+	field = purple_request_field_account_new("account", _("Account"), NULL);
+	purple_request_field_set_type_hint(field, "account");
+	purple_request_field_set_visible(field,
+		(purple_connections_get_all() != NULL &&
+		 purple_connections_get_all()->next != NULL));
+	purple_request_field_set_required(field, TRUE);
+	purple_request_field_group_add_field(group, field);
+
+	purple_request_fields(purple_get_blist(), _("Join a Chat"),
+						NULL,
+						_("Please enter the name of the chat you want to join."),
+						fields,
+						_("Join"), G_CALLBACK(join_chat_select_cb),
+						_("Cancel"), NULL,
+						NULL, NULL, NULL,
+						NULL);
+}
+
+static void
 menu_add_buddy_cb(GntMenuItem *item, gpointer null)
 {
 	purple_blist_request_add_buddy(NULL, NULL, NULL, NULL);
@@ -2265,21 +2328,29 @@
 	gnt_menuitem_set_submenu(item, GNT_MENU(sub));
 
 	item = gnt_menuitem_new(_("Send IM..."));
+	gnt_menuitem_set_id(GNT_MENU_ITEM(item), "send-im");
 	gnt_menu_add_item(GNT_MENU(sub), item);
 	gnt_menuitem_set_callback(GNT_MENU_ITEM(item), send_im_select, NULL);
 
+	item = gnt_menuitem_new(_("Join Chat..."));
+	gnt_menuitem_set_id(GNT_MENU_ITEM(item), "join-chat");
+	gnt_menu_add_item(GNT_MENU(sub), item);
+	gnt_menuitem_set_callback(GNT_MENU_ITEM(item), join_chat_select, NULL);
+
 	item = gnt_menuitem_new(_("Show"));
 	gnt_menu_add_item(GNT_MENU(sub), item);
 	subsub = gnt_menu_new(GNT_MENU_POPUP);
 	gnt_menuitem_set_submenu(item, GNT_MENU(subsub));
 
 	item = gnt_menuitem_check_new(_("Empty groups"));
+	gnt_menuitem_set_id(GNT_MENU_ITEM(item), "show-empty-groups");
 	gnt_menuitem_check_set_checked(GNT_MENU_ITEM_CHECK(item),
 				purple_prefs_get_bool(PREF_ROOT "/emptygroups"));
 	gnt_menu_add_item(GNT_MENU(subsub), item);
 	gnt_menuitem_set_callback(GNT_MENU_ITEM(item), toggle_pref_cb, PREF_ROOT "/emptygroups");
 	
 	item = gnt_menuitem_check_new(_("Offline buddies"));
+	gnt_menuitem_set_id(GNT_MENU_ITEM(item), "show-offline-buddies");
 	gnt_menuitem_check_set_checked(GNT_MENU_ITEM_CHECK(item),
 				purple_prefs_get_bool(PREF_ROOT "/showoffline"));
 	gnt_menu_add_item(GNT_MENU(subsub), item);
@@ -2291,14 +2362,17 @@
 	gnt_menuitem_set_submenu(item, GNT_MENU(subsub));
 
 	item = gnt_menuitem_new(_("By Status"));
+	gnt_menuitem_set_id(GNT_MENU_ITEM(item), "sort-status");
 	gnt_menu_add_item(GNT_MENU(subsub), item);
 	gnt_menuitem_set_callback(GNT_MENU_ITEM(item), sort_blist_change_cb, "status");
 
 	item = gnt_menuitem_new(_("Alphabetically"));
+	gnt_menuitem_set_id(GNT_MENU_ITEM(item), "sort-alpha");
 	gnt_menu_add_item(GNT_MENU(subsub), item);
 	gnt_menuitem_set_callback(GNT_MENU_ITEM(item), sort_blist_change_cb, "text");
 
 	item = gnt_menuitem_new(_("By Log Size"));
+	gnt_menuitem_set_id(GNT_MENU_ITEM(item), "sort-log");
 	gnt_menu_add_item(GNT_MENU(subsub), item);
 	gnt_menuitem_set_callback(GNT_MENU_ITEM(item), sort_blist_change_cb, "log");
 
@@ -2309,14 +2383,17 @@
 	gnt_menuitem_set_submenu(item, GNT_MENU(subsub));
 
 	item = gnt_menuitem_new("Buddy");
+	gnt_menuitem_set_id(GNT_MENU_ITEM(item), "add-buddy");
 	gnt_menu_add_item(GNT_MENU(subsub), item);
 	gnt_menuitem_set_callback(item, menu_add_buddy_cb, NULL);
 
 	item = gnt_menuitem_new("Chat");
+	gnt_menuitem_set_id(GNT_MENU_ITEM(item), "add-chat");
 	gnt_menu_add_item(GNT_MENU(subsub), item);
 	gnt_menuitem_set_callback(item, menu_add_chat_cb, NULL);
 
 	item = gnt_menuitem_new("Group");
+	gnt_menuitem_set_id(GNT_MENU_ITEM(item), "add-group");
 	gnt_menu_add_item(GNT_MENU(subsub), item);
 	gnt_menuitem_set_callback(item, menu_add_group_cb, NULL);
 
--- a/finch/gntconv.c	Wed Sep 26 06:56:02 2007 +0000
+++ b/finch/gntconv.c	Fri Oct 12 02:06:38 2007 +0000
@@ -959,7 +959,7 @@
 {
 	FinchConv *fc = FINCH_CONV(conv);
 	if (fc && fc->window)
-		return gnt_window_present(fc->window);
+		gnt_window_present(fc->window);
 }
 
 static gboolean
--- a/finch/gntdebug.c	Wed Sep 26 06:56:02 2007 +0000
+++ b/finch/gntdebug.c	Fri Oct 12 02:06:38 2007 +0000
@@ -28,12 +28,14 @@
 #include <gntbutton.h>
 #include <gntcheckbox.h>
 #include <gntentry.h>
+#include <gntfilesel.h>
 #include <gntlabel.h>
 #include <gntline.h>
 #include <gnttextview.h>
 
 #include "gntdebug.h"
 #include "finch.h"
+#include "notify.h"
 #include "util.h"
 
 #include <stdio.h>
@@ -220,9 +222,43 @@
 					(GDestroyNotify)g_source_remove);
 }
 
+static void
+file_save(GntFileSel *fs, const char *path, const char *file, GntTextView *tv)
+{
+	FILE *fp;
+
+	if ((fp = g_fopen(path, "w+")) == NULL) {
+		purple_notify_error(NULL, NULL, _("Unable to open file."), NULL);
+		return;
+	}
+
+	fprintf(fp, "Finch Debug Log : %s\n", purple_date_format_full(NULL));
+	fprintf(fp, tv->string->str);
+	fclose(fp);
+	gnt_widget_destroy(GNT_WIDGET(fs));
+}
+
+static void
+file_cancel(GntWidget *w, GntFileSel *fs)
+{
+	gnt_widget_destroy(GNT_WIDGET(fs));
+}
+
+static void
+save_debug_win(GntWidget *w, GntTextView *tv)
+{
+	GntWidget *window = gnt_file_sel_new();
+	GntFileSel *sel = GNT_FILE_SEL(window);
+	gnt_file_sel_set_current_location(sel, purple_home_dir());
+	gnt_file_sel_set_suggested_filename(sel, "debug.txt");
+	g_signal_connect(G_OBJECT(sel), "file_selected", G_CALLBACK(file_save), tv);
+	g_signal_connect(G_OBJECT(sel->cancel), "activate", G_CALLBACK(file_cancel), sel);
+	gnt_widget_show(window);
+}
+
 void finch_debug_window_show()
 {
-	GntWidget *wid, *box;
+	GntWidget *wid, *box, *label;
 
 	debug.paused = FALSE;
 	if (debug.window) {
@@ -258,8 +294,15 @@
 	GNT_WIDGET_SET_FLAGS(wid, GNT_WIDGET_GROW_Y);
 	gnt_box_add_widget(GNT_BOX(box), wid);
 
+	wid = gnt_button_new(_("Save"));
+	g_signal_connect(G_OBJECT(wid), "activate", G_CALLBACK(save_debug_win), debug.tview);
+	GNT_WIDGET_SET_FLAGS(wid, GNT_WIDGET_GROW_Y);
+	gnt_box_add_widget(GNT_BOX(box), wid);
+
 	debug.search = gnt_entry_new(purple_prefs_get_string(PREF_ROOT "/filter"));
-	gnt_box_add_widget(GNT_BOX(box), gnt_label_new(_("Filter: ")));
+	label = gnt_label_new(_("Filter:"));
+	GNT_WIDGET_UNSET_FLAGS(label, GNT_WIDGET_GROW_X);
+	gnt_box_add_widget(GNT_BOX(box), label);
 	gnt_box_add_widget(GNT_BOX(box), debug.search);
 	g_signal_connect(G_OBJECT(debug.search), "text_changed", G_CALLBACK(update_filter_string), NULL);
 
--- a/finch/gntplugin.c	Wed Sep 26 06:56:02 2007 +0000
+++ b/finch/gntplugin.c	Fri Oct 12 02:06:38 2007 +0000
@@ -32,6 +32,7 @@
 
 #include "finch.h"
 
+#include "debug.h"
 #include "notify.h"
 #include "request.h"
 
@@ -127,8 +128,8 @@
 	/* XXX: Use formatting and stuff */
 	gnt_text_view_clear(GNT_TEXT_VIEW(plugins.aboot));
 	text = g_strdup_printf(_("Name: %s\nVersion: %s\nDescription: %s\nAuthor: %s\nWebsite: %s\nFilename: %s\n"),
-			SAFE(plugin->info->name), SAFE(plugin->info->version), SAFE(plugin->info->description),
-			SAFE(plugin->info->author), SAFE(plugin->info->homepage), SAFE(plugin->path));
+			SAFE(_(plugin->info->name)), SAFE(_(plugin->info->version)), SAFE(_(plugin->info->description)),
+			SAFE(_(plugin->info->author)), SAFE(_(plugin->info->homepage)), SAFE(plugin->path));
 	gnt_text_view_append_text_with_flags(GNT_TEXT_VIEW(plugins.aboot),
 			text, GNT_TEXT_FLAG_NORMAL);
 	gnt_text_view_scroll(GNT_TEXT_VIEW(plugins.aboot), 0);
@@ -237,6 +238,93 @@
 	}
 }
 
+static void
+install_selected_file_cb(gpointer handle, const char *filename)
+{
+	/* Try to init the selected file.
+	 * If it succeeds, try to make a copy of the file in $USERDIR/plugins/.
+	 * If the copy succeeds, unload and destroy the plugin in the original
+	 *  location and init+load the new one.
+	 * Select the plugin in the plugin list.
+	 */
+	char *path;
+	PurplePlugin *plugin;
+
+	g_return_if_fail(plugins.window);
+	
+	plugin = purple_plugin_probe(filename);
+	if (!plugin) {
+		purple_notify_error(handle, _("Error loading plugin"),
+				_("The selected file is not a valid plugin."),
+				_("Please open the debug window and try again to see the exact error message."));
+		return;
+	}
+	if (g_list_find(gnt_tree_get_rows(GNT_TREE(plugins.tree)), plugin)) {
+		purple_plugin_load(plugin);
+		gnt_tree_set_choice(GNT_TREE(plugins.tree), plugin, purple_plugin_is_loaded(plugin));
+		gnt_tree_set_selected(GNT_TREE(plugins.tree), plugin);
+		return;
+	}
+
+	path = g_build_filename(purple_user_dir(), "plugins", NULL);
+	if (purple_build_dir(path, S_IRUSR | S_IWUSR | S_IXUSR) == 0) {
+		char *content = NULL;
+		gsize length = 0;
+
+		if (g_file_get_contents(filename, &content, &length, NULL)) {
+			char *file = g_path_get_basename(filename);
+			g_free(path);
+			path = g_build_filename(purple_user_dir(), "plugins", file, NULL);
+			if (purple_util_write_data_to_file_absolute(path, content, length)) {
+				purple_plugin_destroy(plugin);
+				plugin = purple_plugin_probe(path);
+				if (!plugin) {
+					purple_debug_warning("gntplugin", "This is really strange. %s can be loaded, but %s can't!\n",
+							filename, path);
+					g_unlink(path);
+					plugin = purple_plugin_probe(filename);
+				}
+			} else {
+			}
+		}
+		g_free(content);
+	}
+	g_free(path);
+
+	purple_plugin_load(plugin);
+
+	if (plugin->info->type == PURPLE_PLUGIN_LOADER) {
+		GList *cur;
+		for (cur = PURPLE_PLUGIN_LOADER_INFO(plugin)->exts; cur != NULL;
+				cur = cur->next)
+			purple_plugins_probe(cur->data);
+		return;
+	}
+
+	if (plugin->info->type != PURPLE_PLUGIN_STANDARD ||
+			(plugin->info->flags & PURPLE_PLUGIN_FLAG_INVISIBLE) ||
+			plugin->error)
+		return;
+
+	gnt_tree_add_choice(GNT_TREE(plugins.tree), plugin,
+			gnt_tree_create_row(GNT_TREE(plugins.tree), plugin->info->name), NULL, NULL);
+	gnt_tree_set_choice(GNT_TREE(plugins.tree), plugin, purple_plugin_is_loaded(plugin));
+	gnt_tree_set_row_flags(GNT_TREE(plugins.tree), plugin, GNT_TEXT_FLAG_BOLD);
+	gnt_tree_set_selected(GNT_TREE(plugins.tree), plugin);
+}
+
+static void
+install_plugin_cb(GntWidget *w, gpointer null)
+{
+	static int handle;
+
+	purple_request_close_with_handle(&handle);
+	purple_request_file(&handle, _("Select plugin to install"), NULL,
+			FALSE, G_CALLBACK(install_selected_file_cb), NULL,
+			NULL, NULL, NULL, &handle);
+	g_signal_connect_swapped(G_OBJECT(w), "destroy", G_CALLBACK(purple_request_close_with_handle), &handle);
+}
+
 void finch_plugins_show_all()
 {
 	GntWidget *window, *tree, *box, *aboot, *button;
@@ -272,6 +360,7 @@
 	gnt_box_add_widget(GNT_BOX(box), gnt_vline_new());
 
 	plugins.aboot = aboot = gnt_text_view_new();
+	gnt_text_view_set_flag(GNT_TEXT_VIEW(aboot), GNT_TEXT_VIEW_TOP_ALIGN);
 	gnt_widget_set_size(aboot, 40, 20);
 	gnt_box_add_widget(GNT_BOX(box), aboot);
 
@@ -307,6 +396,10 @@
 	box = gnt_hbox_new(FALSE);
 	gnt_box_add_widget(GNT_BOX(window), box);
 
+	button = gnt_button_new(_("Install Plugin..."));
+	gnt_box_add_widget(GNT_BOX(box), button);
+	g_signal_connect(G_OBJECT(button), "activate", G_CALLBACK(install_plugin_cb), NULL);
+
 	button = gnt_button_new(_("Close"));
 	gnt_box_add_widget(GNT_BOX(box), button);
 	g_signal_connect_swapped(G_OBJECT(button), "activate",
--- a/finch/gntpounce.c	Wed Sep 26 06:56:02 2007 +0000
+++ b/finch/gntpounce.c	Fri Oct 12 02:06:38 2007 +0000
@@ -964,10 +964,15 @@
 						PURPLE_CALLBACK(signed_on_off_cb), NULL);
 }
 
+static void
+dummy_pounce_cb(PurplePounce *pounce, PurplePounceEvent events, void *data)
+{
+}
+
 /* XXX: There's no such thing in pidgin. Perhaps there should be? */
 void finch_pounces_uninit()
 {
-	purple_pounces_register_handler(FINCH_UI, NULL, NULL, NULL);
+	purple_pounces_register_handler(FINCH_UI, dummy_pounce_cb, NULL, NULL);
 
 	purple_signals_disconnect_by_handle(finch_pounces_get_handle());
 }
--- a/finch/gntrequest.c	Wed Sep 26 06:56:02 2007 +0000
+++ b/finch/gntrequest.c	Fri Oct 12 02:06:38 2007 +0000
@@ -76,6 +76,8 @@
 static void
 setup_default_callback(GntWidget *window, gpointer default_cb, gpointer data)
 {
+	if (default_cb == NULL)
+		return;
 	g_object_set_data(G_OBJECT(window), "default-callback", default_cb);
 	g_signal_connect_swapped(G_OBJECT(window), "destroy", G_CALLBACK(default_cb), data);
 }
--- a/finch/libgnt/gntbindable.h	Wed Sep 26 06:56:02 2007 +0000
+++ b/finch/libgnt/gntbindable.h	Fri Oct 12 02:06:38 2007 +0000
@@ -105,73 +105,75 @@
 /*GntBindableAction *gnt_bindable_action_parse(const char *name);*/
 
 /**
- * 
- * @param action
+ * Free a bindable action.
+ *
+ * @param action The bindable action.
  */
 void gnt_bindable_action_free(GntBindableAction *action);
 
 /**
- * 
- * @param param
+ * Free a GntBindableActionParam.
+ *
+ * @param param  The GntBindableActionParam to free.
  */
 void gnt_bindable_action_param_free(GntBindableActionParam *param);
 
 /**
- * 
- * @param klass
- * @param name
- * @param callback
- * @param trigger
+ * Register a bindable action for a class.
+ *
+ * @param klass      The class the binding is for.
+ * @param name       The name of the binding.
+ * @param callback   The callback  for the binding.
+ * @param trigger    The default trigger for the binding, or @c NULL, followed by a NULL-terminated
+ *                   list of default parameters.
  */
 void gnt_bindable_class_register_action(GntBindableClass *klass, const char *name, GntBindableActionCallback callback, const char *trigger, ...);
 
 /**
- * 
- * @param klass
- * @param name
- * @param trigger
+ * Register a key-binding to an existing action.
+ *
+ * @param klass     The class the binding is for.
+ * @param name      The name of the binding.
+ * @param trigger   A new trigger for the binding, followed by a @c NULL-terminated list of parameters for the callback.
  */
 void gnt_bindable_register_binding(GntBindableClass *klass, const char *name, const char *trigger, ...);
 
 /**
- * 
- * @param bindable
- * @param keys
+ * Perform an action from a keybinding.
  *
- * @return
+ * @param bindable  The bindable object.
+ * @param keys      The key to trigger the action.
+ *
+ * @return  @c TRUE if the action was performed successfully, @c FALSE otherwise.
  */
 gboolean gnt_bindable_perform_action_key(GntBindable *bindable, const char *keys);
 
 /**
- * 
- * @param bindable
- * @param name
+ * Perform an action on a bindable object.
  *
- * @return
+ * @param bindable  The bindable object.
+ * @param name      The action to perform, followed by a @c NULL-terminated list of parameters.
+ *
+ * @return  @c TRUE if the action was performed successfully, @c FALSE otherwise.
  */
 gboolean gnt_bindable_perform_action_named(GntBindable *bindable, const char *name, ...);
 
 /**
-* Returns a GntTree populated with "key" -> "binding" for the widget.
-*/
-/**
-* 
-* @param widget
-*
-* @return
-*/
+ * Returns a GntTree populated with "key" -> "binding" for the widget.
+ * 
+ * @param widget  The object to list the bindings for.
+ *
+ * @return   The GntTree.
+ */
 GntBindable * gnt_bindable_bindings_view(GntBindable *bind);
 
 /**
- *
- * Builds a window that list the key bindings for a GntBindable object.  From this window a user can select a listing to rebind a new key for the given action.
- *
- */
-/**
+ * Builds a window that list the key bindings for a GntBindable object.
+ * From this window a user can select a listing to rebind a new key for the given action.
  * 
- * @param bindable
+ * @param bindable   The object to list the bindings for.
  *	
- * @return
+ * @return  @c TRUE
  */
 
 gboolean gnt_bindable_build_help_window(GntBindable *bindable);
--- a/finch/libgnt/gntbutton.h	Wed Sep 26 06:56:02 2007 +0000
+++ b/finch/libgnt/gntbutton.h	Fri Oct 12 02:06:38 2007 +0000
@@ -73,17 +73,16 @@
 G_BEGIN_DECLS
 
 /**
- * 
- *
- * @return
+ * @return  GType for Gntbutton
  */
 GType gnt_button_get_gtype(void);
 
 /**
- * 
- * @param text
+ * Create a new button.
  *
- * @return
+ * @param text   The text for the button.
+ *
+ * @return  The newly created button.
  */
 GntWidget * gnt_button_new(const char *text);
 
--- a/finch/libgnt/gntcheckbox.h	Wed Sep 26 06:56:02 2007 +0000
+++ b/finch/libgnt/gntcheckbox.h	Fri Oct 12 02:06:38 2007 +0000
@@ -68,32 +68,33 @@
 G_BEGIN_DECLS
 
 /**
- * 
- *
- * @return
+ * @return GType for GntCheckBox
  */
 GType gnt_check_box_get_gtype(void);
 
 /**
- * 
- * @param text
+ * Create a new checkbox.
  *
- * @return
+ * @param text The text for the checkbox.
+ *
+ * @return  The newly created checkbox.
  */
 GntWidget * gnt_check_box_new(const char *text);
 
 /**
- * 
- * @param box
- * @param set
+ * Set whether the checkbox should be checked or not.
+ *
+ * @param box   The checkbox.
+ * @param set   @c TRUE if the checkbox should be selected, @c FALSE otherwise.
  */
 void gnt_check_box_set_checked(GntCheckBox *box, gboolean set);
 
 /**
- * 
- * @param box
+ * Return the checked state of the checkbox.
  *
- * @return
+ * @param box  The checkbox.
+ *
+ * @return     @c TRUE if the checkbox is selected, @c FALSE otherwise.
  */
 gboolean gnt_check_box_get_checked(GntCheckBox *box);
 
--- a/finch/libgnt/gntclipboard.c	Wed Sep 26 06:56:02 2007 +0000
+++ b/finch/libgnt/gntclipboard.c	Fri Oct 12 02:06:38 2007 +0000
@@ -48,7 +48,7 @@
  *****************************************************************************/
 
 void
-gnt_clipboard_set_string(GntClipboard *clipboard, gchar *string)
+gnt_clipboard_set_string(GntClipboard *clipboard, const gchar *string)
 {
 	g_free(clipboard->string);
 	clipboard->string = g_strdup(string);
--- a/finch/libgnt/gntclipboard.h	Wed Sep 26 06:56:02 2007 +0000
+++ b/finch/libgnt/gntclipboard.h	Fri Oct 12 02:06:38 2007 +0000
@@ -60,26 +60,27 @@
 G_BEGIN_DECLS
 
 /**
- * 
- *
- * @return
+ * @return GType for GntClipboard.
  */
 GType gnt_clipboard_get_gtype(void);
 
 /**
- * 
- * @param clip
+ * Get the current text from the clipboard.
  *
- * @return
+ * @param clip  The clipboard.
+ *
+ * @return  A copy of the string in the clipboard. The caller should free the
+ *          returned value.
  */
 gchar * gnt_clipboard_get_string(GntClipboard *clip);
 
 /**
- * 
- * @param clip
- * @param string
+ * Set the text in the clipboard.
+ *
+ * @param clip     The clipboard.
+ * @param string   New string for the clipboard.
  */
-void gnt_clipboard_set_string(GntClipboard *clip, gchar *string);
+void gnt_clipboard_set_string(GntClipboard *clip, const gchar *string);
 
 G_END_DECLS
 
--- a/finch/libgnt/gntcolors.h	Wed Sep 26 06:56:02 2007 +0000
+++ b/finch/libgnt/gntcolors.h	Fri Oct 12 02:06:38 2007 +0000
@@ -29,6 +29,9 @@
 
 #include <glib.h>
 
+/**
+ * Different classes of colors.
+ */
 typedef enum
 {
 	GNT_COLOR_NORMAL = 1,
@@ -58,27 +61,28 @@
 	GNT_TOTAL_COLORS
 };
 
-/* populate some default colors */
 /**
- * 
+ * Initialize the colors.
  */
 void gnt_init_colors(void);
 
 /**
- * 
+ * Uninitialize the colors.
  */
 void gnt_uninit_colors(void);
 
 #if GLIB_CHECK_VERSION(2,6,0)
 /**
- * 
- * @param kfile
+ * Parse color information from a file.
+ *
+ * @param kfile  The file containing color information.
  */
 void gnt_colors_parse(GKeyFile *kfile);
 
 /**
- * 
- * @param kfile
+ * Parse color-pair information from a file.
+ *
+ * @param kfile The file containing the color-pair information.
  */
 void gnt_color_pairs_parse(GKeyFile *kfile);
 
--- a/finch/libgnt/gntcombobox.h	Wed Sep 26 06:56:02 2007 +0000
+++ b/finch/libgnt/gntcombobox.h	Fri Oct 12 02:06:38 2007 +0000
@@ -69,15 +69,11 @@
 G_BEGIN_DECLS
 
 /**
- *
- * Get the GType for GntComboBox
- *
- * @return
+ * @return  Get the GType for GntComboBox
  */
 GType gnt_combo_box_get_gtype(void);
 
 /**
- * 
  * Create a new GntComboBox
  *
  * @return A new GntComboBox
@@ -85,7 +81,6 @@
 GntWidget * gnt_combo_box_new(void);
 
 /**
- * 
  * Add an entry
  *
  * @param box The GntComboBox
@@ -95,7 +90,6 @@
 void gnt_combo_box_add_data(GntComboBox *box, gpointer key, const char *text);
 
 /**
- *
  * Remove an entry
  * 
  * @param box The GntComboBox
@@ -104,7 +98,6 @@
 void gnt_combo_box_remove(GntComboBox *box, gpointer key);
 
 /**
- * 
  * Remove all entries
  *
  * @param box The GntComboBox
@@ -112,7 +105,6 @@
 void gnt_combo_box_remove_all(GntComboBox *box);
 
 /**
- * 
  * Get the data that is currently selected
  *
  * @param box The GntComboBox
@@ -122,7 +114,6 @@
 gpointer gnt_combo_box_get_selected_data(GntComboBox *box);
 
 /**
- * 
  * Set the current selection to a specific entry
  *
  * @param box The GntComboBox
--- a/finch/libgnt/gntentry.h	Wed Sep 26 06:56:02 2007 +0000
+++ b/finch/libgnt/gntentry.h	Fri Oct 12 02:06:38 2007 +0000
@@ -100,95 +100,115 @@
 G_BEGIN_DECLS
 
 /**
- * 
- *
- * @return
+ * @return GType for GntEntry.
  */
 GType gnt_entry_get_gtype(void);
 
 /**
- * 
- * @param text
+ * Create a new GntEntry.
  *
- * @return
+ * @param text   The text in the new entry box.
+ *
+ * @return  The newly created entry box.
  */
 GntWidget * gnt_entry_new(const char *text);
 
 /**
- * 
- * @param entry
- * @param max
+ * Set the maximum length of the text in the entry box.
+ *
+ * @param entry  The entry box.
+ * @param max    The maximum length for text. A value of 0 means infinite length.
  */
 void gnt_entry_set_max(GntEntry *entry, int max);
 
 /**
- * 
- * @param entry
- * @param text
+ * Set the text in an entry box.
+ *
+ * @param entry The entry box.
+ * @param text  The text to set in the box.
  */
 void gnt_entry_set_text(GntEntry *entry, const char *text);
 
 /**
- * 
- * @param entry
- * @param flag
+ * Set flags an entry box.
+ *
+ * @param entry  The entry box.
+ * @param flag   The flags to set for the entry box.
  */
 void gnt_entry_set_flag(GntEntry *entry, GntEntryFlag flag);
 
+/**
+ * Get the text in an entry box.
+ *
+ * @param entry  The entry box.
+ *
+ * @return   The current text in the entry box.
+ */
 const char *gnt_entry_get_text(GntEntry *entry);
 
 /**
- * 
- * @param entry
+ * Clear the text in the entry box.
+ *
+ * @param entry  The entry box.
  */
 void gnt_entry_clear(GntEntry *entry);
 
 /**
- * 
- * @param entry
- * @param set
+ * Set whether the text in the entry box should be masked for display.
+ *
+ * @param entry  The entry box.
+ * @param set    @c TRUE if the text should be masked, @c FALSE otherwise.
  */
 void gnt_entry_set_masked(GntEntry *entry, gboolean set);
 
 /**
- * 
- * @param entry
- * @param text
+ * Add a text to the history list for the text. The history length for the
+ * entry box needs to be set first by gnt_entry_set_history_length.
+ *
+ * @param entry  The entry box.
+ * @param text   A new entry for the history list.
  */
 void gnt_entry_add_to_history(GntEntry *entry, const char *text);
 
 /**
- * 
- * @param entry
- * @param num
+ * Set the length of history for the entry box.
+ *
+ * @param entry  The entry box.
+ * @param num    The maximum length of the history.
  */
 void gnt_entry_set_history_length(GntEntry *entry, int num);
 
 /**
- * 
- * @param entry
- * @param word
+ * Set whether the suggestions are for the entire entry box, or for each
+ * individual word in the entry box.
+ *
+ * @param entry   The entry box.
+ * @param word    @c TRUE if the suggestions are for individual words, @c FALSE otherwise.
  */
 void gnt_entry_set_word_suggest(GntEntry *entry, gboolean word);
 
 /**
- * 
- * @param entry
- * @param always
+ * Set whether to always display the suggestions list, or only when the
+ * tab-completion key is pressed (the TAB key, by default).
+ *
+ * @param entry    The entry box.
+ * @param always   @c TRUE if the suggestion list should always be displayed.
  */
 void gnt_entry_set_always_suggest(GntEntry *entry, gboolean always);
 
 /**
- * 
- * @param entry
- * @param text
+ * Add an item to the suggestion list.
+ *
+ * @param entry  The entry box.
+ * @param text   An item to add to the suggestion list.
  */
 void gnt_entry_add_suggest(GntEntry *entry, const char *text);
 
 /**
- * 
- * @param entry
- * @param text
+ * Remove an entry from the suggestion list.
+ *
+ * @param entry  The entry box.
+ * @param text   The item to remove from the suggestion list.
  */
 void gnt_entry_remove_suggest(GntEntry *entry, const char *text);
 
--- a/finch/libgnt/gntmain.c	Wed Sep 26 06:56:02 2007 +0000
+++ b/finch/libgnt/gntmain.c	Fri Oct 12 02:06:38 2007 +0000
@@ -223,6 +223,7 @@
 	char keys[256];
 	int rd;
 	char *k;
+	char *cvrt = NULL;
 
 	if (wm->mode == GNT_KP_MODE_WAIT_ON_CHILD)
 		return FALSE;
@@ -243,15 +244,16 @@
 		raise(SIGABRT);
 	}
 
-	gnt_wm_set_event_stack(wm, TRUE);
 	rd += HOLDING_ESCAPE;
-	keys[rd] = 0;
-	if (mouse_enabled && detect_mouse_action(keys))
-		goto end;
-
 	if (HOLDING_ESCAPE)
 		keys[0] = '\033';
-	k = keys;
+	keys[rd] = 0;
+	gnt_wm_set_event_stack(wm, TRUE);
+
+	cvrt = g_locale_to_utf8(keys, rd, (gsize*)&rd, NULL, NULL);
+	k = cvrt ? cvrt : keys;
+	if (mouse_enabled && detect_mouse_action(k))
+		goto end;
 
 #if 0
 	/* I am not sure what's happening here. If this actually does something,
@@ -290,6 +292,7 @@
 	}
 end:
 	gnt_wm_set_event_stack(wm, FALSE);
+	g_free(cvrt);
 	return TRUE;
 }
 
--- a/finch/libgnt/gntmenu.c	Wed Sep 26 06:56:02 2007 +0000
+++ b/finch/libgnt/gntmenu.c	Fri Oct 12 02:06:38 2007 +0000
@@ -459,3 +459,34 @@
 	menu->list = g_list_append(menu->list, item);
 }
 
+GntMenuItem *gnt_menu_get_item(GntMenu *menu, const char *id)
+{
+	GntMenuItem *item = NULL;
+	GList *iter = menu->list;
+
+	if (!id || !*id)
+		return NULL;
+
+	for (; iter; iter = iter->next) {
+		GntMenu *sub;
+		item = iter->data;
+		sub = gnt_menuitem_get_submenu(item);
+		if (sub) {
+			item = gnt_menu_get_item(sub, id);
+			if (item)
+				break;
+		} else {
+			const char *itid = gnt_menuitem_get_id(item);
+			if (itid && strcmp(itid, id) == 0)
+				break;
+			/* XXX: Perhaps look at the menu-label as well? */
+		}
+		item = NULL;
+	}
+
+	if (item)
+		menuitem_activate(menu, item);
+
+	return item;
+}
+
--- a/finch/libgnt/gntmenu.h	Wed Sep 26 06:56:02 2007 +0000
+++ b/finch/libgnt/gntmenu.h	Fri Oct 12 02:06:38 2007 +0000
@@ -86,27 +86,37 @@
 G_BEGIN_DECLS
 
 /**
- * 
- *
- * @return
+ * @return  The GType for GntMenu.
  */
 GType gnt_menu_get_gtype(void);
 
 /**
- * 
- * @param type
+ * Create a new menu.
  *
- * @return
+ * @param type  The type of the menu, whether it's a toplevel menu or a popup menu.
+ *
+ * @return  The newly created menu.
  */
 GntWidget * gnt_menu_new(GntMenuType type);
 
 /**
- * 
- * @param menu
- * @param item
+ * Add an item to the menu.
+ *
+ * @param menu   The menu.
+ * @param item   The item to add to the menu.
  */
 void gnt_menu_add_item(GntMenu *menu, GntMenuItem *item);
 
+/**
+ * Get the GntMenuItem with the given ID.
+ *
+ * @param menu   The menu.
+ * @param id     The ID for an item.
+ *
+ * @return  The menuitem with the given ID, or @c NULL.
+ */
+GntMenuItem *gnt_menu_get_item(GntMenu *menu, const char *id);
+
 G_END_DECLS
 
 #endif /* GNT_MENU_H */
--- a/finch/libgnt/gntmenuitem.c	Wed Sep 26 06:56:02 2007 +0000
+++ b/finch/libgnt/gntmenuitem.c	Fri Oct 12 02:06:38 2007 +0000
@@ -33,6 +33,7 @@
 	item->text = NULL;
 	if (item->submenu)
 		gnt_widget_destroy(GNT_WIDGET(item->submenu));
+	g_free(item->priv.id);
 	parent_class->dispose(obj);
 }
 
@@ -104,6 +105,11 @@
 	item->submenu = menu;
 }
 
+GntMenu *gnt_menuitem_get_submenu(GntMenuItem *item)
+{
+	return item->submenu;
+}
+
 void gnt_menuitem_set_trigger(GntMenuItem *item, char trigger)
 {
 	item->priv.trigger = trigger;
@@ -114,3 +120,14 @@
 	return item->priv.trigger;
 }
 
+void gnt_menuitem_set_id(GntMenuItem *item, const char *id)
+{
+	g_free(item->priv.id);
+	item->priv.id = g_strdup(id);
+}
+
+const char * gnt_menuitem_get_id(GntMenuItem *item)
+{
+	return item->priv.id;
+}
+
--- a/finch/libgnt/gntmenuitem.h	Wed Sep 26 06:56:02 2007 +0000
+++ b/finch/libgnt/gntmenuitem.h	Fri Oct 12 02:06:38 2007 +0000
@@ -53,6 +53,7 @@
 	int x;
 	int y;
 	char trigger;
+	char *id;
 };
 
 typedef void (*GntMenuItemCallback)(GntMenuItem *item, gpointer data);
@@ -86,36 +87,46 @@
 G_BEGIN_DECLS
 
 /**
- * 
- *
- * @return
+ * @return GType for GntMenuItem.
  */
 GType gnt_menuitem_get_gtype(void);
 
 /**
- * 
- * @param text
+ * Create a new menuitem.
  *
- * @return
+ * @param text   Label for the menuitem.
+ *
+ * @return  The newly created menuitem.
  */
 GntMenuItem * gnt_menuitem_new(const char *text);
 
 /**
- * 
- * @param item
- * @param callback
- * @param data
+ * Set a callback function for a menuitem.
+ *
+ * @param item       The menuitem.
+ * @param callback   The callback function.
+ * @param data       Data to send to the callback function.
  */
 void gnt_menuitem_set_callback(GntMenuItem *item, GntMenuItemCallback callback, gpointer data);
 
 /**
- * 
- * @param item
- * @param menu
+ * Set a submenu for a menuitem. A menuitem with a submenu cannot have a callback.
+ *
+ * @param item  The menuitem.
+ * @param menu  The submenu.
  */
 void gnt_menuitem_set_submenu(GntMenuItem *item, GntMenu *menu);
 
 /**
+ * Get the submenu for a menuitem.
+ *
+ * @param item   The menuitem.
+ *
+ * @return  The submenu, or @c NULL.
+ */
+GntMenu *gnt_menuitem_get_submenu(GntMenuItem *item);
+
+/**
  * Set a trigger key for the item.
  *
  * @param item     The menuitem
@@ -134,6 +145,23 @@
  */
 char gnt_menuitem_get_trigger(GntMenuItem *item);
 
+/**
+ * Set an ID for the menuitem.
+ *
+ * @param item   The menuitem.
+ * @param id     The ID for the menuitem.
+ */
+void gnt_menuitem_set_id(GntMenuItem *item, const char *id);
+
+/**
+ * Get the ID of the menuitem.
+ *
+ * @param item   The menuitem.
+ *
+ * @return  The ID for the menuitem.
+ */
+const char * gnt_menuitem_get_id(GntMenuItem *item);
+
 G_END_DECLS
 
 #endif /* GNT_MENUITEM_H */
--- a/finch/libgnt/gntstyle.c	Wed Sep 26 06:56:02 2007 +0000
+++ b/finch/libgnt/gntstyle.c	Fri Oct 12 02:06:38 2007 +0000
@@ -26,7 +26,6 @@
 
 #include <glib.h>
 #include <ctype.h>
-#include <glib/gprintf.h>
 #include <stdlib.h>
 #include <string.h>
 
@@ -228,6 +227,65 @@
 #endif
 }
 
+gboolean gnt_style_read_menu_accels(const char *name, GHashTable *table)
+{
+#if GLIB_CHECK_VERSION(2,6,0)
+	char *kname;
+	GError *error = NULL;
+	gboolean ret = FALSE;
+
+	kname = g_strdup_printf("%s::menu", name);
+
+	if (g_key_file_has_group(gkfile, kname))
+	{
+		gsize len = 0;
+		char **keys;
+		
+		keys = g_key_file_get_keys(gkfile, kname, &len, &error);
+		if (error)
+		{
+			g_printerr("GntStyle: %s\n", error->message);
+			g_error_free(error);
+			g_free(kname);
+			return ret;
+		}
+
+		while (len--)
+		{
+			char *key, *menuid;
+
+			key = g_strdup(keys[len]);
+			menuid = g_key_file_get_string(gkfile, kname, keys[len], &error);
+
+			if (error)
+			{
+				g_printerr("GntStyle: %s\n", error->message);
+				g_error_free(error);
+				error = NULL;
+			}
+			else
+			{
+				const char *keycode = parse_key(key);
+				if (keycode == NULL) {
+					g_printerr("GntStyle: Invalid key-binding %s\n", key);
+				} else {
+					ret = TRUE;
+					g_hash_table_replace(table, g_strdup(keycode), menuid);
+					menuid = NULL;
+				}
+			}
+			g_free(key);
+			g_free(menuid);
+		}
+		g_strfreev(keys);
+	}
+
+	g_free(kname);
+	return ret;
+#endif
+	return FALSE;
+}
+
 void gnt_styles_get_keyremaps(GType type, GHashTable *hash)
 {
 #if GLIB_CHECK_VERSION(2,6,0)
--- a/finch/libgnt/gntstyle.h	Wed Sep 26 06:56:02 2007 +0000
+++ b/finch/libgnt/gntstyle.h	Fri Oct 12 02:06:38 2007 +0000
@@ -89,6 +89,16 @@
  */
 void gnt_style_read_actions(GType type, GntBindableClass *klass);
 
+/*
+ * Read menu-accels from ~/.gntrc
+ *
+ * @param name  The name of the window.
+ * @param table The hastable to store the accel information.
+ *
+ * @return  @c TRUE if some accels were read, @c FALSE otherwise.
+ */
+gboolean gnt_style_read_menu_accels(const char *name, GHashTable *table);
+
 void gnt_style_read_workspaces(GntWM *wm);
 
 /**
--- a/finch/libgnt/gnttree.c	Wed Sep 26 06:56:02 2007 +0000
+++ b/finch/libgnt/gnttree.c	Fri Oct 12 02:06:38 2007 +0000
@@ -1265,8 +1265,10 @@
 {
 	GntTreeRow *pr = NULL;
 
+	row->tree = tree;
+	row->key = key;
+	row->data = NULL;
 	g_hash_table_replace(tree->hash, key, row);
-	row->tree = tree;
 
 	if (bigbro == NULL && tree->priv->compare)
 	{
@@ -1327,9 +1329,6 @@
 		}
 	}
 
-	row->key = key;
-	row->data = NULL;
-
 	redraw_tree(tree);
 
 	return row;
--- a/finch/libgnt/gntwindow.c	Wed Sep 26 06:56:02 2007 +0000
+++ b/finch/libgnt/gntwindow.c	Fri Oct 12 02:06:38 2007 +0000
@@ -25,6 +25,11 @@
 
 #include <string.h>
 
+struct _GntWindowPriv
+{
+	GHashTable *accels;   /* key => menuitem-id */
+};
+
 enum
 {
 	SIG_WORKSPACE_HIDE,
@@ -55,6 +60,10 @@
 	GntWindow *window = GNT_WINDOW(widget);
 	if (window->menu)
 		gnt_widget_destroy(GNT_WIDGET(window->menu));
+	if (window->priv) {
+		g_hash_table_destroy(window->priv->accels);
+		g_free(window->priv);
+	}
 	org_destroy(widget);
 }
 
@@ -98,8 +107,11 @@
 gnt_window_init(GTypeInstance *instance, gpointer class)
 {
 	GntWidget *widget = GNT_WIDGET(instance);
+	GntWindow *win = GNT_WINDOW(widget);
 	GNT_WIDGET_UNSET_FLAGS(widget, GNT_WIDGET_NO_BORDER | GNT_WIDGET_NO_SHADOW);
 	GNT_WIDGET_SET_FLAGS(widget, GNT_WIDGET_CAN_TAKE_FOCUS);
+	win->priv = g_new0(GntWindowPriv, 1);
+	win->priv->accels = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
 	GNTDEBUG;
 }
 
@@ -170,8 +182,23 @@
 void gnt_window_set_menu(GntWindow *window, GntMenu *menu)
 {
 	/* If a menu already existed, then destroy that first. */
+	const char *name = gnt_widget_get_name(GNT_WIDGET(window));
 	if (window->menu)
 		gnt_widget_destroy(GNT_WIDGET(window->menu));
 	window->menu = menu;
+	if (name && window->priv) {
+		if (!gnt_style_read_menu_accels(name, window->priv->accels)) {
+			g_hash_table_destroy(window->priv->accels);
+			g_free(window->priv);
+			window->priv = NULL;
+		}
+	}
 }
 
+const char * gnt_window_get_accel_item(GntWindow *window, const char *key)
+{
+	if (window->priv)
+		return g_hash_table_lookup(window->priv->accels, key);
+	return NULL;
+}
+
--- a/finch/libgnt/gntwindow.h	Wed Sep 26 06:56:02 2007 +0000
+++ b/finch/libgnt/gntwindow.h	Fri Oct 12 02:06:38 2007 +0000
@@ -52,6 +52,7 @@
 {
 	GntBox parent;
 	GntMenu *menu;
+	GntWindowPriv *priv;
 };
 
 struct _GntWindowClass
@@ -99,6 +100,8 @@
  */
 void gnt_window_set_menu(GntWindow *window, GntMenu *menu);
 
+const char * gnt_window_get_accel_item(GntWindow *window, const char *key);
+
 void gnt_window_workspace_hiding(GntWindow *);
 void gnt_window_workspace_showing(GntWindow *);
 
--- a/finch/libgnt/gntwm.c	Wed Sep 26 06:56:02 2007 +0000
+++ b/finch/libgnt/gntwm.c	Fri Oct 12 02:06:38 2007 +0000
@@ -27,8 +27,8 @@
 
 #include "config.h"
 
+#include <glib.h>
 #include <ctype.h>
-#include <glib/gprintf.h>
 #include <gmodule.h>
 #include <stdlib.h>
 #include <string.h>
@@ -1853,8 +1853,19 @@
 		ret = gnt_widget_key_pressed(GNT_WIDGET(wm->menu), keys);
 	else if (wm->_list.window)
 		ret = gnt_widget_key_pressed(wm->_list.window, keys);
-	else if (wm->cws->ordered)
-		ret = gnt_widget_key_pressed(GNT_WIDGET(wm->cws->ordered->data), keys);
+	else if (wm->cws->ordered) {
+		GntWidget *win = wm->cws->ordered->data;
+		if (GNT_IS_WINDOW(win)) {
+			GntMenu *menu = GNT_WINDOW(win)->menu;
+			if (menu) {
+				const char *id = gnt_window_get_accel_item(GNT_WINDOW(win), keys);
+				if (id)
+					ret = (gnt_menu_get_item(menu, id) != NULL);
+			}
+		}
+		if (!ret)
+			ret = gnt_widget_key_pressed(win, keys);
+	}
 	return ret;
 }
 
--- a/finch/libgnt/pygnt/gntmodule.c	Wed Sep 26 06:56:02 2007 +0000
+++ b/finch/libgnt/pygnt/gntmodule.c	Fri Oct 12 02:06:38 2007 +0000
@@ -1,7 +1,74 @@
 #include <pygobject.h>
+#include "gnt.h"
  
 void gnt_register_classes (PyObject *d); 
 extern PyMethodDef gnt_functions[];
+
+static void
+gnt_add_string_constants(PyObject *module)
+{
+#define define_key(x) if (GNT_KEY_##x && *(GNT_KEY_##x))  PyModule_AddStringConstant(module, "KEY_" #x, GNT_KEY_##x)
+
+	define_key(POPUP);
+
+	define_key(LEFT);
+	define_key(RIGHT);
+	define_key(UP);
+	define_key(DOWN);
+
+	define_key(CTRL_UP);
+	define_key(CTRL_DOWN);
+	define_key(CTRL_RIGHT);
+	define_key(CTRL_LEFT);
+
+	define_key(PGUP);
+	define_key(PGDOWN);
+	define_key(HOME);
+	define_key(END);
+
+	define_key(ENTER);
+
+	define_key(BACKSPACE);
+	define_key(DEL);
+	define_key(INS);
+	define_key(BACK_TAB);
+
+	define_key(CTRL_A);
+	define_key(CTRL_B);
+	define_key(CTRL_D);
+	define_key(CTRL_E);
+	define_key(CTRL_F);
+	define_key(CTRL_G);
+	define_key(CTRL_H);
+	define_key(CTRL_I);
+	define_key(CTRL_J);
+	define_key(CTRL_K);
+	define_key(CTRL_L);
+	define_key(CTRL_M);
+	define_key(CTRL_N);
+	define_key(CTRL_O);
+	define_key(CTRL_P);
+	define_key(CTRL_R);
+	define_key(CTRL_T);
+	define_key(CTRL_U);
+	define_key(CTRL_V);
+	define_key(CTRL_W);
+	define_key(CTRL_X);
+	define_key(CTRL_Y);
+
+	define_key(F1);
+	define_key(F2);
+	define_key(F3);
+	define_key(F4);
+	define_key(F5);
+	define_key(F6);
+	define_key(F7);
+	define_key(F8);
+	define_key(F9);
+	define_key(F10);
+	define_key(F11);
+	define_key(F12);
+}
  
 DL_EXPORT(void)
 initgnt(void)
@@ -19,5 +86,8 @@
     if (PyErr_Occurred ()) {
         Py_FatalError ("can't initialise module sad");
     }
+
+	gnt_init();
+	gnt_add_string_constants(m);
 }
 
--- a/finch/libgnt/pygnt/gnttree.override	Wed Sep 26 06:56:02 2007 +0000
+++ b/finch/libgnt/pygnt/gnttree.override	Fri Oct 12 02:06:38 2007 +0000
@@ -62,13 +62,14 @@
 {
 	static char *kwlist[] = {"key", "row", "parent", "bigbro", NULL};
 	PyObject *py_list;
-	gpointer key, parent, bigbro;
+	gpointer key, parent, bigbro = NULL;
 	int len, i;
 	GList *list = NULL;
 	GntTreeRow *row;
+	gboolean insert_last = FALSE;
 
 	if (!PyArg_ParseTuple(args,
-				"OOOO:GntTree.add_row_after",
+				"OOO|O:GntTree.add_row_after",
 				&key,
 				&py_list,
 				&parent,
@@ -92,13 +93,20 @@
 		parent = NULL;
 	if (bigbro == Py_None)
 		bigbro = NULL;
+	else if (bigbro == NULL)
+		insert_last = TRUE;
+
+	Py_INCREF((PyObject*)key);
 
 	list = g_list_reverse(list);
 	row = gnt_tree_create_row_from_list(GNT_TREE(self->obj), list);
-	gnt_tree_add_row_after(GNT_TREE(self->obj),
-			key,
-			row,
-			parent, bigbro);
+	if (insert_last)
+		gnt_tree_add_row_last(GNT_TREE(self->obj),
+				key, row, parent);
+	else
+		gnt_tree_add_row_after(GNT_TREE(self->obj),
+				key, row,
+				parent, bigbro);
 	g_list_free(list);
 
 	Py_INCREF(Py_None);
@@ -179,4 +187,27 @@
 	Py_INCREF(Py_None);
 	return Py_None;
 }
+%%
+override gnt_tree_set_compare_func
+static PyObject *
+_wrap_gnt_tree_set_compare_func(PyGObject *self, PyObject *args)
+{
+	static char *kwlist[] = {"compare_func", NULL};
+	PyGObject *compare;
 
+	if (!PyArg_ParseTuple(args, "O:GntTree.set_compare_func", &compare)) {
+		return NULL;
+	}
+
+	if (!PyCallable_Check(compare)) {
+		PyErr_SetString(PyExc_TypeError, "the callback must be callable ... doh!");
+		return NULL;
+	}
+
+	Py_INCREF(compare);
+	gnt_tree_set_compare_func(GNT_TREE(self->obj), (GCompareFunc)compare->obj);
+
+	Py_INCREF(Py_None);
+	return Py_None;
+}
+
--- a/finch/plugins/lastlog.c	Wed Sep 26 06:56:02 2007 +0000
+++ b/finch/plugins/lastlog.c	Fri Oct 12 02:06:38 2007 +0000
@@ -96,6 +96,7 @@
 {
 	cmd = purple_cmd_register("lastlog", "s", PURPLE_CMD_P_DEFAULT,
 			PURPLE_CMD_FLAG_CHAT | PURPLE_CMD_FLAG_IM, NULL,
+			/* Translator Note: The "backlog" is the conversation buffer/history. */
 			lastlog_cb, _("lastlog: Searches for a substring in the backlog."), NULL);
 	return TRUE;
 }
--- a/libpurple/Makefile.am	Wed Sep 26 06:56:02 2007 +0000
+++ b/libpurple/Makefile.am	Fri Oct 12 02:06:38 2007 +0000
@@ -114,6 +114,7 @@
 	privacy.h \
 	proxy.h \
 	prpl.h \
+	purple.h \
 	request.h \
 	roomlist.h \
 	savedstatuses.h \
--- a/libpurple/account.c	Wed Sep 26 06:56:02 2007 +0000
+++ b/libpurple/account.c	Fri Oct 12 02:06:38 2007 +0000
@@ -63,7 +63,10 @@
 	PurpleAccountRequestType type;
 	PurpleAccount *account;
 	void *ui_handle;
-
+	char *user;
+	gpointer userdata;
+	PurpleAccountRequestAuthorizationCb auth_cb;
+	PurpleAccountRequestAuthorizationCb deny_cb;
 } PurpleAccountRequestInfo;
 
 static PurpleAccountUiOps *account_ui_ops = NULL;
@@ -1157,6 +1160,28 @@
 	}
 }
 
+static void
+request_auth_cb(void *data)
+{
+	PurpleAccountRequestInfo *info = data;
+	info->auth_cb(info->userdata);
+	purple_signal_emit(purple_accounts_get_handle(),
+			"account-authorization-granted", info->account, info->user);
+	g_free(info->user);
+	g_free(info);
+}
+
+static void
+request_deny_cb(void *data)
+{
+	PurpleAccountRequestInfo *info = data;
+	info->deny_cb(info->userdata);
+	purple_signal_emit(purple_accounts_get_handle(),
+			"account-authorization-denied", info->account, info->user);
+	g_free(info->user);
+	g_free(info);
+}
+
 void *
 purple_account_request_authorization(PurpleAccount *account, const char *remote_user,
 				     const char *id, const char *alias, const char *message, gboolean on_list,
@@ -1164,18 +1189,35 @@
 {
 	PurpleAccountUiOps *ui_ops;
 	PurpleAccountRequestInfo *info;
+	int plugin_return;
 
 	g_return_val_if_fail(account     != NULL, NULL);
 	g_return_val_if_fail(remote_user != NULL, NULL);
 
 	ui_ops = purple_accounts_get_ui_ops();
 
+	plugin_return = GPOINTER_TO_INT(
+			purple_signal_emit_return_1(purple_accounts_get_handle(),
+				"account-authorization-requested", account, remote_user));
+
+	if (plugin_return > 0) {
+		auth_cb(user_data);
+		return NULL;
+	} else if (plugin_return < 0) {
+		deny_cb(user_data);
+		return NULL;
+	}
+
 	if (ui_ops != NULL && ui_ops->request_authorize != NULL) {
 		info            = g_new0(PurpleAccountRequestInfo, 1);
 		info->type      = PURPLE_ACCOUNT_REQUEST_AUTHORIZATION;
 		info->account   = account;
+		info->auth_cb   = auth_cb;
+		info->deny_cb   = deny_cb;
+		info->userdata  = user_data;
+		info->user      = g_strdup(remote_user);
 		info->ui_handle = ui_ops->request_authorize(account, remote_user, id, alias, message,
-							    on_list, auth_cb, deny_cb, user_data);
+							    on_list, request_auth_cb, request_deny_cb, info);
 
 		handles = g_list_append(handles, info);
 		return info->ui_handle;
@@ -2452,6 +2494,25 @@
 							 			PURPLE_SUBTYPE_ACCOUNT),
 						 purple_value_new(PURPLE_TYPE_STRING));
 
+	purple_signal_register(handle, "account-authorization-requested",
+						purple_marshal_INT__POINTER_POINTER,
+						purple_value_new(PURPLE_TYPE_INT), 2,
+						purple_value_new(PURPLE_TYPE_SUBTYPE,
+										PURPLE_SUBTYPE_ACCOUNT),
+						purple_value_new(PURPLE_TYPE_STRING));
+
+	purple_signal_register(handle, "account-authorization-denied",
+						purple_marshal_VOID__POINTER_POINTER, NULL, 2,
+						purple_value_new(PURPLE_TYPE_SUBTYPE,
+										PURPLE_SUBTYPE_ACCOUNT),
+						purple_value_new(PURPLE_TYPE_STRING));
+
+	purple_signal_register(handle, "account-authorization-granted",
+						purple_marshal_VOID__POINTER_POINTER, NULL, 2,
+						purple_value_new(PURPLE_TYPE_SUBTYPE,
+										PURPLE_SUBTYPE_ACCOUNT),
+						purple_value_new(PURPLE_TYPE_STRING));
+
 	load_accounts();
 
 }
--- a/libpurple/blist.h	Wed Sep 26 06:56:02 2007 +0000
+++ b/libpurple/blist.h	Fri Oct 12 02:06:38 2007 +0000
@@ -41,6 +41,8 @@
 typedef struct _PurpleContact PurpleContact;
 typedef struct _PurpleBuddy PurpleBuddy;
 
+typedef gboolean (*PurpleFilterBlistFunc)(PurpleBlistNode *node);
+
 /**************************************************************************/
 /* Enumerations                                                           */
 /**************************************************************************/
@@ -66,9 +68,12 @@
 typedef enum
 {
 	PURPLE_BLIST_NODE_FLAG_NO_SAVE      = 1 << 0, /**< node should not be saved with the buddy list */
+	PURPLE_BLIST_NODE_HAS_CONVERSATION  = 1 << 1, /**< node (buddy or chat) has an open conversation */
 
 } PurpleBlistNodeFlags;
 
+#define PURPLE_BLIST_NODE_SET_FLAG(node, f)    (((PurpleBlistNode *)node)->flags |= (f))
+#define PURPLE_BLIST_NODE_UNSET_FLAG(node, f)  (((PurpleBlistNode *)node)->flags &= ~(f))
 #define PURPLE_BLIST_NODE_HAS_FLAG(b, f) (((PurpleBlistNode*)(b))->flags & (f))
 #define PURPLE_BLIST_NODE_SHOULD_SAVE(b) (! PURPLE_BLIST_NODE_HAS_FLAG(b, PURPLE_BLIST_NODE_FLAG_NO_SAVE))
 
--- a/libpurple/certificate.c	Wed Sep 26 06:56:02 2007 +0000
+++ b/libpurple/certificate.c	Fri Oct 12 02:06:38 2007 +0000
@@ -540,7 +540,7 @@
 
 	/* Determine whether the name matches */
 	if (purple_certificate_check_subject_name(crt, vrq->subject_name)) {
-		cn_match = _("");
+		cn_match = "";
 	} else {
 		cn_match = _("(DOES NOT MATCH)");
 	}
@@ -1460,15 +1460,14 @@
 	
 	tls_peers = purple_certificate_find_pool(x509_tls_cached.scheme_name,tls_peers_name);
 
-	/* TODO: This should probably just prompt the user instead of throwing
-	   an angry fit */
 	if (!tls_peers) {
 		purple_debug_error("certificate/x509/tls_cached",
-				   "Couldn't find local peers cache %s\nReturning INVALID to callback\n",
+				   "Couldn't find local peers cache %s\nPrompting the user\n",
 				   tls_peers_name);
 
-		purple_certificate_verify_complete(vrq,
-						   PURPLE_CERTIFICATE_INVALID);
+
+		/* vrq now becomes the problem of unknown_peer */
+		x509_tls_cached_unknown_peer(vrq);
 		return;
 	}
 	
--- a/libpurple/conversation.c	Wed Sep 26 06:56:02 2007 +0000
+++ b/libpurple/conversation.c	Fri Oct 12 02:06:38 2007 +0000
@@ -225,6 +225,7 @@
 	msg->flags = flags;
 	msg->what = g_strdup(message);
 	msg->when = when;
+	msg->conv = conv;
 
 	conv->message_history = g_list_prepend(conv->message_history, msg);
 }
--- a/libpurple/conversation.h	Wed Sep 26 06:56:02 2007 +0000
+++ b/libpurple/conversation.h	Fri Oct 12 02:06:38 2007 +0000
@@ -293,6 +293,7 @@
 	char *what;
 	PurpleMessageFlags flags;
 	time_t when;
+	PurpleConversation *conv;
 };
 
 /**
--- a/libpurple/plugin.c	Wed Sep 26 06:56:02 2007 +0000
+++ b/libpurple/plugin.c	Fri Oct 12 02:06:38 2007 +0000
@@ -1275,13 +1275,9 @@
 		if (basename)
 			basename = purple_plugin_get_basename(filename);
 
-		if ((plugin = purple_plugins_find_with_filename(filename)) != NULL)
-		{
-			purple_debug_info("plugins", "Loading saved plugin %s\n",
-							plugin->path);
-			purple_plugin_load(plugin);
-		}
-		else if (basename && (plugin = purple_plugins_find_with_basename(basename)) != NULL)
+		if (((plugin = purple_plugins_find_with_filename(filename)) != NULL) ||
+				(basename && (plugin = purple_plugins_find_with_basename(basename)) != NULL) ||
+				((plugin = purple_plugin_probe(filename)) != NULL))
 		{
 			purple_debug_info("plugins", "Loading saved plugin %s\n",
 							plugin->path);
--- a/libpurple/plugins/signals-test.c	Wed Sep 26 06:56:02 2007 +0000
+++ b/libpurple/plugins/signals-test.c	Fri Oct 12 02:06:38 2007 +0000
@@ -76,6 +76,28 @@
 					old, purple_account_get_alias(account));
 }
 
+static int
+account_authorization_requested_cb(PurpleAccount *account, const char *user, gpointer data)
+{
+	purple_debug_misc("signals test", "account-authorization-requested (%s, %s)\n",
+			purple_account_get_username(account), user);
+	return 0;
+}
+
+static void
+account_authorization_granted_cb(PurpleAccount *account, const char *user, gpointer data)
+{
+	purple_debug_misc("signals test", "account-authorization-granted (%s, %s)\n",
+			purple_account_get_username(account), user);
+}
+
+static void
+account_authorization_denied_cb(PurpleAccount *account, const char *user, gpointer data)
+{
+	purple_debug_misc("signals test", "account-authorization-denied (%s, %s)\n",
+			purple_account_get_username(account), user);
+}
+
 /**************************************************************************
  * Buddy Icons signal callbacks
  **************************************************************************/
@@ -568,6 +590,12 @@
 						plugin, PURPLE_CALLBACK(account_status_changed), NULL);
 	purple_signal_connect(accounts_handle, "account-alias-changed",
 						plugin, PURPLE_CALLBACK(account_alias_changed), NULL);
+	purple_signal_connect(accounts_handle, "account-authorization-requested",
+						plugin, PURPLE_CALLBACK(account_authorization_requested_cb), NULL);
+	purple_signal_connect(accounts_handle, "account-authorization-denied",
+						plugin, PURPLE_CALLBACK(account_authorization_denied_cb), NULL);
+	purple_signal_connect(accounts_handle, "account-authorization-granted",
+						plugin, PURPLE_CALLBACK(account_authorization_granted_cb), NULL);
 
 	/* Buddy List subsystem signals */
 	purple_signal_connect(blist_handle, "buddy-status-changed",
--- a/libpurple/plugins/ssl/ssl-gnutls.c	Wed Sep 26 06:56:02 2007 +0000
+++ b/libpurple/plugins/ssl/ssl-gnutls.c	Fri Oct 12 02:06:38 2007 +0000
@@ -111,7 +111,7 @@
 	PurpleSslGnutlsData *gnutls_data = PURPLE_SSL_GNUTLS_DATA(gsc);
 	ssize_t ret;
 
-	purple_debug_info("gnutls", "Handshaking with %s\n", gsc->host);
+	/*purple_debug_info("gnutls", "Handshaking with %s\n", gsc->host);*/
 	ret = gnutls_handshake(gnutls_data->session);
 
 	if(ret == GNUTLS_E_AGAIN || ret == GNUTLS_E_INTERRUPTED)
@@ -163,9 +163,9 @@
 		g_list_free(peers);
 
 		{
-			const gnutls_datum_t *cert_list;
+			const gnutls_datum *cert_list;
 			unsigned int cert_list_size = 0;
-			gnutls_session_t session=gnutls_data->session;
+			gnutls_session session=gnutls_data->session;
 			int i;
 
 			cert_list =
@@ -182,7 +182,7 @@
 				gchar tbuf[256];
 				gsize tsz=sizeof(tbuf);
 				gchar * tasc = NULL;
-				gnutls_x509_crt_t cert;
+				gnutls_x509_crt cert;
 
 				gnutls_x509_crt_init(&cert);
 				gnutls_x509_crt_import (cert, &cert_list[i],
@@ -271,6 +271,8 @@
 	gnutls_data->handshake_handler = purple_input_add(gsc->fd,
 		PURPLE_INPUT_READ, ssl_gnutls_handshake_cb, gsc);
 
+	purple_debug_info("gnutls", "Starting handshake with %s\n", gsc->host);
+
 	/* Orborde asks: Why are we configuring a callback, then
 	   immediately calling it?
 
@@ -363,7 +365,7 @@
 
 /* Forward declarations are fun! */
 static PurpleCertificate *
-x509_import_from_datum(const gnutls_datum_t dt, gnutls_x509_crt_fmt_t mode);
+x509_import_from_datum(const gnutls_datum dt, gnutls_x509_crt_fmt mode);
 
 static GList *
 ssl_gnutls_get_peer_certificates(PurpleSslConnection * gsc)
@@ -374,7 +376,7 @@
 	GList * peer_certs = NULL;
 
 	/* List of raw certificates as given by GnuTLS */
-	const gnutls_datum_t *cert_list;
+	const gnutls_datum *cert_list;
 	unsigned int cert_list_size = 0;
 
 	unsigned int i;
@@ -412,7 +414,7 @@
 /** Refcounted GnuTLS certificate data instance */
 typedef struct {
 	gint refcount;
-	gnutls_x509_crt_t crt;
+	gnutls_x509_crt crt;
 } x509_crtdata_t;
 
 /** Helper functions for reference counting */
@@ -434,9 +436,6 @@
 
 	/* If the refcount reaches zero, kill the structure */
 	if (cd->refcount <= 0) {
-		purple_debug_info("gnutls/x509",
-				  "Freeing unused cert data at %p\n",
-				  cd);
 		/* Kill the internal data */
 		gnutls_x509_crt_deinit( cd->crt );
 		/* And kill the struct */
@@ -447,7 +446,7 @@
 /** Helper macro to retrieve the GnuTLS crt_t from a PurpleCertificate */
 #define X509_GET_GNUTLS_DATA(pcrt) ( ((x509_crtdata_t *) (pcrt->data))->crt)
 
-/** Transforms a gnutls_datum_t containing an X.509 certificate into a Certificate instance under the x509_gnutls scheme
+/** Transforms a gnutls_datum containing an X.509 certificate into a Certificate instance under the x509_gnutls scheme
  *
  * @param dt   Datum to transform
  * @param mode GnuTLS certificate format specifier (GNUTLS_X509_FMT_PEM for
@@ -457,7 +456,7 @@
  * @return A newly allocated Certificate structure of the x509_gnutls scheme
  */
 static PurpleCertificate *
-x509_import_from_datum(const gnutls_datum_t dt, gnutls_x509_crt_fmt_t mode)
+x509_import_from_datum(const gnutls_datum dt, gnutls_x509_crt_fmt mode)
 {
 	/* Internal certificate data structure */
 	x509_crtdata_t *certdat;
@@ -492,7 +491,7 @@
 	PurpleCertificate *crt;  /* Certificate being constructed */
 	gchar *buf;        /* Used to load the raw file data */
 	gsize buf_sz;      /* Size of the above */
-	gnutls_datum_t dt; /* Struct to pass down to GnuTLS */
+	gnutls_datum dt; /* Struct to pass down to GnuTLS */
 
 	purple_debug_info("gnutls",
 			  "Attempting to load X.509 certificate from %s\n",
@@ -533,7 +532,7 @@
 static gboolean
 x509_export_certificate(const gchar *filename, PurpleCertificate *crt)
 {
-	gnutls_x509_crt_t crt_dat; /* GnuTLS cert struct */
+	gnutls_x509_crt crt_dat; /* GnuTLS cert struct */
 	int ret;
 	gchar * out_buf; /* Data to output */
 	size_t out_size; /* Output size */
@@ -640,8 +639,8 @@
 x509_certificate_signed_by(PurpleCertificate * crt,
 			   PurpleCertificate * issuer)
 {
-	gnutls_x509_crt_t crt_dat;
-	gnutls_x509_crt_t issuer_dat;
+	gnutls_x509_crt crt_dat;
+	gnutls_x509_crt issuer_dat;
 	unsigned int verify; /* used to store result from GnuTLS verifier */
 	int ret;
 
@@ -674,10 +673,10 @@
 			purple_debug_info("gnutls/x509",
 					  "Certificate for %s claims to be "
 					  "issued by %s, but the certificate "
-					  "for %s does not match. A strcmp "
-					  "says %d\n",
-					  crt_id, crt_issuer_id, issuer_id,
-					  strcmp(crt_issuer_id, issuer_id));
+					  "for %s does not match.\n",
+					  crt_id ? crt_id : "(null)",
+					  crt_issuer_id ? crt_issuer_id : "(null)",
+					  issuer_id ? issuer_id : "(null)");
 			g_free(crt_id);
 			g_free(issuer_id);
 			g_free(crt_issuer_id);
@@ -727,7 +726,7 @@
 {
 	size_t hashlen = 20; /* SHA1 hashes are 20 bytes */
 	size_t tmpsz = hashlen; /* Throw-away variable for GnuTLS to stomp on*/
-	gnutls_x509_crt_t crt_dat;
+	gnutls_x509_crt crt_dat;
 	GByteArray *hash; /**< Final hash container */
 	guchar hashbuf[hashlen]; /**< Temporary buffer to contain hash */
 
@@ -754,7 +753,7 @@
 static gchar *
 x509_cert_dn (PurpleCertificate *crt)
 {
-	gnutls_x509_crt_t cert_dat;
+	gnutls_x509_crt cert_dat;
 	gchar *dn = NULL;
 	size_t dn_size;
 
@@ -770,7 +769,10 @@
 	gnutls_x509_crt_get_dn(cert_dat, dn, &dn_size);
 
 	/* Now allocate and get the Distinguished Name */
-	dn = g_new0(gchar, dn_size);
+	/* Old versions of GnuTLS have an off-by-one error in reporting
+	   the size of the needed buffer in some functions, so allocate
+	   an extra byte */
+	dn = g_new0(gchar, ++dn_size);
 	if (0 != gnutls_x509_crt_get_dn(cert_dat, dn, &dn_size)) {
 		purple_debug_error("gnutls/x509",
 				   "Failed to get Distinguished Name\n");
@@ -784,7 +786,7 @@
 static gchar *
 x509_issuer_dn (PurpleCertificate *crt)
 {
-	gnutls_x509_crt_t cert_dat;
+	gnutls_x509_crt cert_dat;
 	gchar *dn = NULL;
 	size_t dn_size;
 
@@ -800,7 +802,10 @@
 	gnutls_x509_crt_get_issuer_dn(cert_dat, dn, &dn_size);
 
 	/* Now allocate and get the Distinguished Name */
-	dn = g_new0(gchar, dn_size);
+	/* Old versions of GnuTLS have an off-by-one error in reporting
+	   the size of the needed buffer in some functions, so allocate
+	   an extra byte */
+	dn = g_new0(gchar, ++dn_size);
 	if (0 != gnutls_x509_crt_get_issuer_dn(cert_dat, dn, &dn_size)) {
 		purple_debug_error("gnutls/x509",
 				   "Failed to get issuer's Distinguished "
@@ -815,7 +820,7 @@
 static gchar *
 x509_common_name (PurpleCertificate *crt)
 {
-	gnutls_x509_crt_t cert_dat;
+	gnutls_x509_crt cert_dat;
 	gchar *cn = NULL;
 	size_t cn_size;
 	int ret;
@@ -836,7 +841,10 @@
 				      cn, &cn_size);
 
 	/* Now allocate and get the Common Name */
-	cn = g_new0(gchar, cn_size);
+	/* Old versions of GnuTLS have an off-by-one error in reporting
+	   the size of the needed buffer in some functions, so allocate
+	   an extra byte */
+	cn = g_new0(gchar, ++cn_size);
 	ret = gnutls_x509_crt_get_dn_by_oid(cert_dat,
 					    GNUTLS_OID_X520_COMMON_NAME,
 					    0, /* First CN found, please */
@@ -855,7 +863,7 @@
 static gboolean
 x509_check_name (PurpleCertificate *crt, const gchar *name)
 {
-	gnutls_x509_crt_t crt_dat;
+	gnutls_x509_crt crt_dat;
 
 	g_return_val_if_fail(crt, FALSE);
 	g_return_val_if_fail(crt->scheme == &x509_gnutls, FALSE);
@@ -873,7 +881,7 @@
 static gboolean
 x509_times (PurpleCertificate *crt, time_t *activation, time_t *expiration)
 {
-	gnutls_x509_crt_t crt_dat;
+	gnutls_x509_crt crt_dat;
 	/* GnuTLS time functions return this on error */
 	const time_t errval = (time_t) (-1);
 
--- a/libpurple/protocols/irc/msgs.c	Wed Sep 26 06:56:02 2007 +0000
+++ b/libpurple/protocols/irc/msgs.c	Fri Oct 12 02:06:38 2007 +0000
@@ -751,7 +751,10 @@
 		}
 		purple_conversation_set_data(convo, IRC_NAMES_FLAG,
 					   GINT_TO_POINTER(FALSE));
-		purple_conversation_present(convo);
+		/* Until purple_conversation_present does something that
+                 * one would expect in Pidgin, this call produces buggy
+                 * behavior both for the /join and auto-join cases. */
+		/* purple_conversation_present(convo); */
 		return;
 	}
 
--- a/libpurple/protocols/jabber/google.c	Wed Sep 26 06:56:02 2007 +0000
+++ b/libpurple/protocols/jabber/google.c	Fri Oct 12 02:06:38 2007 +0000
@@ -64,7 +64,8 @@
 	message = xmlnode_get_child(child, "mail-thread-info");
 
 	if (count == 0 || !message) {
-		purple_notify_emails(js->gc, count, FALSE, NULL, NULL, (const char**) default_tos, NULL, NULL, NULL);
+		if (count > 0)
+			purple_notify_emails(js->gc, count, FALSE, NULL, NULL, (const char**) default_tos, NULL, NULL, NULL);
 		g_free(default_tos[0]);
 		return;
 	}
--- a/libpurple/protocols/jabber/jutil.h	Wed Sep 26 06:56:02 2007 +0000
+++ b/libpurple/protocols/jabber/jutil.h	Fri Oct 12 02:06:38 2007 +0000
@@ -22,8 +22,6 @@
 #ifndef _PURPLE_JABBER_JUTIL_H_
 #define _PURPLE_JABBER_JUTIL_H_
 
-#include "account.h"
-
 typedef struct _JabberID {
 	char *node;
 	char *domain;
--- a/libpurple/protocols/msn/contact.c	Wed Sep 26 06:56:02 2007 +0000
+++ b/libpurple/protocols/msn/contact.c	Fri Oct 12 02:06:38 2007 +0000
@@ -31,8 +31,8 @@
 
 const char *MsnSoapPartnerScenarioText[] =
 {
-        "Initial",
-        "ContactSave",
+	"Initial",
+	"ContactSave",
 	"MessengerPendingList",
 	"ContactMsgrAPI",
 	"BlockUnblock"
@@ -72,32 +72,23 @@
 msn_callback_state_new(void)
 {
 	return g_new0(MsnCallbackState, 1);
-}	
+}
 
 void
 msn_callback_state_free(MsnCallbackState *state)
 {
 	if (state == NULL)
 		return;
-	
-	if (state->who != NULL)
-		g_free(state->who);
-	
-	if (state->uid != NULL)
-		g_free(state->uid);
 
-	if (state->old_group_name != NULL)
-		g_free(state->old_group_name);
-	
-	if (state->new_group_name != NULL)
-		g_free(state->new_group_name);
-	
-	if (state->guid != NULL)
-		g_free(state->guid);
+	g_free(state->who);
+	g_free(state->uid);
+	g_free(state->old_group_name);
+	g_free(state->new_group_name);
+	g_free(state->guid);
 
 	g_free(state);
 }
-	
+
 void
 msn_callback_state_set_who(MsnCallbackState *state, const gchar *who)
 {
@@ -107,27 +98,23 @@
 
 	if (who != NULL)
 		new_str = g_strdup(who);
-	
-	if (state->who != NULL)
-		g_free(state->who);
-	
+
+	g_free(state->who);
 	state->who = new_str;
 }
 
 void
 msn_callback_state_set_uid(MsnCallbackState *state, const gchar *uid)
 {
-        gchar *new_str = NULL;
+	gchar *new_str = NULL;
 
-        g_return_if_fail(state != NULL);
+	g_return_if_fail(state != NULL);
 
-        if (uid != NULL)
-                new_str = g_strdup(uid);
+	if (uid != NULL)
+		new_str = g_strdup(uid);
 
-        if (state->uid != NULL)
-                g_free(state->uid);
-
-        state->uid = new_str;
+	g_free(state->uid);
+	state->uid = new_str;
 }
 
 void
@@ -140,9 +127,7 @@
 	if (old_group_name != NULL)
 		new_str = g_strdup(old_group_name);
 
-	if (state->old_group_name != NULL)
-		g_free(state->old_group_name);
-	
+	g_free(state->old_group_name);
 	state->old_group_name = new_str;
 }
 
@@ -156,9 +141,7 @@
 	if (new_group_name != NULL)
 		new_str = g_strdup(new_group_name);
 
-	if (state->new_group_name != NULL)
-		g_free(state->new_group_name);
-	
+	g_free(state->new_group_name);
 	state->new_group_name = new_str;
 }
 
@@ -171,10 +154,8 @@
 
 	if (guid != NULL)
 		new_str = g_strdup(guid);
-	
-	if (state->guid != NULL)
-		g_free(state->guid);
-	
+
+	g_free(state->guid);
 	state->guid = new_str;
 }
 
@@ -183,7 +164,7 @@
 msn_callback_state_set_list_id(MsnCallbackState *state, MsnListId list_id)
 {
 	g_return_if_fail(state != NULL);
-	
+
 	state->list_id = list_id;
 }
 
@@ -191,10 +172,10 @@
 msn_callback_state_set_action(MsnCallbackState *state, MsnCallbackAction action)
 {
 	g_return_if_fail(state != NULL);
-	
+
 	state->action |= action;
 }
-		       
+
 /*contact SOAP server login error*/
 static void
 msn_contact_login_error_cb(MsnSoapConn *soapconn, PurpleSslConnection *gsc, PurpleSslErrorType error)
@@ -227,9 +208,10 @@
 
 /*get MSN member role utility*/
 static MsnListId
-msn_get_memberrole(char * role)
+msn_get_memberrole(char *role)
 {
-	
+	g_return_val_if_fail(role != NULL, 0);
+
 	if (!strcmp(role,"Allow")) {
 		return MSN_LIST_AL;
 	} else if (!strcmp(role,"Block")) {
@@ -243,9 +225,11 @@
 }
 
 /*get User Type*/
-static int 
-msn_get_user_type(char * type)
+static int
+msn_get_user_type(char *type)
 {
+	g_return_val_if_fail(type != NULL, 0);
+
 	if (!strcmp(type,"Regular")) {
 		return MSN_USER_TYPE_PASSPORT;
 	}
@@ -327,9 +311,7 @@
 	xmlnode *fault, *faultstringnode, *faultdetail, *errorcode;
 	xmlnode *node, *body, *response, *result, *services;
 	xmlnode *service, *memberships, *info, *handle, *handletype;
-	xmlnode *LastChangeNode;
 	xmlnode *membershipnode, *members, *member, *passportNode;
-	char *LastChangeStr;
 
 	session = contact->session;
 	node = xmlnode_from_str(contact->soapconn->body, contact->soapconn->body_len);
@@ -341,8 +323,12 @@
 
 	purple_debug_misc("MSNCL","Parsing contact list with size %d\n", contact->soapconn->body_len);
 
-	purple_debug_misc("MSNCL","Root node @ %p: Name: '%s', child: '%s', lastchild: '%s'\n",node,node->name,node->child->name,node->lastchild->name);
-	body = xmlnode_get_child(node,"Body");
+	purple_debug_misc("MSNCL","Root node @ %p: Name: '%s', child: '%s', lastchild: '%s'\n", node,
+		node->name ? node->name : "(null)",
+		(node->child && node->child->name) ? node->child->name : "(null)",
+		(node->lastchild && node->lastchild->name) ? node->lastchild->name : "(null)");
+
+	body = xmlnode_get_child(node, "Body");
 
 	if (body == NULL) {
 		purple_debug_warning("MSNCL", "Failed to parse contact list Body node\n");
@@ -357,20 +343,20 @@
 
 		if ( (faultstringnode = xmlnode_get_child(fault, "faultstring")) != NULL ) {
 			gchar * faultstring = xmlnode_get_data(faultstringnode);
-			purple_debug_info("MSNCL","Faultstring: %s\n", faultstring);
+			purple_debug_info("MSNCL", "Faultstring: %s\n", faultstring ? faultstring : "(null)");
 			g_free(faultstring);
 		}
 		if ( (faultdetail = xmlnode_get_child(fault, "detail")) != NULL ) {
 			purple_debug_info("MSNCL","detail @ %p, name: %s\n",faultdetail, faultdetail->name);
 
 			if ( (errorcode = xmlnode_get_child(faultdetail, "errorcode")) != NULL ) {
-				purple_debug_info("MSNCL","errorcode @ %p, name: %s\n",errorcode, errorcode->name);
+				purple_debug_info("MSNCL","errorcode @ %p, name: %s\n", errorcode, errorcode->name);
 
 				if (errorcode->child != NULL) {
 					gchar *errorcodestring = xmlnode_get_data(errorcode);
-					purple_debug_info("MSNCL", "Error Code: %s\n", errorcodestring);
+					purple_debug_info("MSNCL", "Error Code: %s\n", errorcodestring ? errorcodestring : "(null)");
 
-					if ( !strncmp(errorcodestring, "ABDoesNotExist", 14) ) {
+					if (errorcodestring && !strncmp(errorcodestring, "ABDoesNotExist", 14) ) {
 						xmlnode_free(node);
 						g_free(errorcodestring);
 						msn_create_address_book(contact);
@@ -418,7 +404,7 @@
 	for (service = xmlnode_get_child(services, "Service"); service;
 	                                service = xmlnode_get_next_twin(service)) {
 		purple_debug_info("MSNCL","Service @ %p\n",service);
-	
+
 		if ( (info = xmlnode_get_child(service,"Info")) == NULL ) {
 			purple_debug_error("MSNCL","Error getting 'Info' child node\n");
 			continue;
@@ -434,7 +420,6 @@
 
 		if ( (typedata = xmlnode_get_data(handletype)) == NULL) {
 			purple_debug_error("MSNCL","Error retrieving data from 'Type' child node\n");
-			g_free(typedata);
 			continue;
 		}
 
@@ -447,11 +432,13 @@
 		}
 
 		if ( !g_strcasecmp(typedata, "Messenger") ) {
+			char *LastChangeStr = NULL;
+			xmlnode *LastChangeNode;
 
 			/*Last Change Node*/
-			LastChangeNode = xmlnode_get_child(service, "LastChange");
-			LastChangeStr = xmlnode_get_data(LastChangeNode);
-			purple_debug_info("MSNCL","LastChangeNode: '%s'\n",LastChangeStr);	
+			if ((LastChangeNode = xmlnode_get_child(service, "LastChange")))
+				LastChangeStr = xmlnode_get_data(LastChangeNode);
+			purple_debug_info("MSNCL","LastChangeNode: '%s'\n",LastChangeStr ? LastChangeStr : "(null)");
 			purple_account_set_string(session->account, "CLLastChange", LastChangeStr);
 			g_free(LastChangeStr);
 
@@ -466,35 +453,44 @@
 			for (membershipnode = xmlnode_get_child(memberships, "Membership"); membershipnode;
 							membershipnode = xmlnode_get_next_twin(membershipnode)){
 				xmlnode *roleNode;
-				char *role;
+				char *role = NULL;
+				list = 0;
 
-				roleNode = xmlnode_get_child(membershipnode,"MemberRole");
-				role = xmlnode_get_data(roleNode);
-				list = msn_get_memberrole(role);
+				if ((roleNode = xmlnode_get_child(membershipnode,"MemberRole"))) {
+					role = xmlnode_get_data(roleNode);
+					list = msn_get_memberrole(role);
+				}
 				list_op = 1 << list;
 
-				purple_debug_info("MSNCL","MemberRole role: %s, list_op: %d\n",role,list_op);
-				
+				purple_debug_info("MSNCL","MemberRole role: %s, list_op: %d\n", role ? role : "(null)", list_op);
+
 				g_free(role);
-				
-				members = xmlnode_get_child(membershipnode,"Members");
+
+				members = xmlnode_get_child(membershipnode, "Members");
 				for (member = xmlnode_get_child(members, "Member"); member;
 						member = xmlnode_get_next_twin(member)){
 					MsnUser *user = NULL;
-					xmlnode *typeNode, *membershipIdNode=NULL;
+					xmlnode *typeNode, *membershipIdNode = NULL;
 					gchar *type, *membershipId = NULL;
+					const char *member_type = xmlnode_get_attrib(member, "type");
+
+					if (!member_type) {
+						purple_debug_error("msn", "No Member Type specified for Member.\n");
+						continue;
+					}
 
-					purple_debug_info("MSNCL","Member type: %s\n", xmlnode_get_attrib(member,"type"));
-					
-					if( !g_strcasecmp(xmlnode_get_attrib(member,"type"), "PassportMember") ) {
-						passportNode = xmlnode_get_child(member,"PassportName");
-						passport = xmlnode_get_data(passportNode);
-						typeNode = xmlnode_get_child(member,"Type");
-						type = xmlnode_get_data(typeNode);
-						purple_debug_info("MSNCL","Passport name: '%s', Type: %s\n",passport,type);
+					if(!g_strcasecmp(member_type, "PassportMember") ) {
+						passport = type = NULL;
+						if ((passportNode = xmlnode_get_child(member, "PassportName")))
+							passport = xmlnode_get_data(passportNode);
+						if ((typeNode = xmlnode_get_child(member, "Type")))
+							type = xmlnode_get_data(typeNode);
+						purple_debug_info("MSNCL","Passport name: '%s', Type: %s\n", passport ? passport : "(null)", type ? type : "(null)");
+						/* Why do we even bother parsing it just to free it??? */
 						g_free(type);
 
 						user = msn_userlist_find_add_user(session->userlist,passport,NULL);
+						g_free(passport);
 
 						membershipIdNode = xmlnode_get_child(member,"MembershipId");
 						if (membershipIdNode != NULL) {
@@ -504,23 +500,23 @@
 								g_free(membershipId);
 							}
 						}
-							
+
 						msn_got_lst_user(session, user, list_op, NULL);
-
-						g_free(passport);
 					}
-					
-					if (!g_strcasecmp(xmlnode_get_attrib(member,"type"),"PhoneMember")) {
+					else if (!g_strcasecmp(member_type, "PhoneMember")) {
+						purple_debug_info("msn", "Recieved Phone Member; ignoring.\n");
 					}
-					
-					if (!g_strcasecmp(xmlnode_get_attrib(member,"type"),"EmailMember")) {
+					else if (!g_strcasecmp(member_type, "EmailMember")) {
 						xmlnode *emailNode;
+						passport = NULL;
 
-						emailNode = xmlnode_get_child(member,"Email");
-						passport = xmlnode_get_data(emailNode);
-						purple_debug_info("MSNCL","Email Member: Name: '%s', list_op: %d\n", passport, list_op);
+						if ((emailNode = xmlnode_get_child(member, "Email")))
+							passport = xmlnode_get_data(emailNode);
+						purple_debug_info("MSNCL","Email Member: Name: '%s', list_op: %d\n", passport ? passport : "(null)", list_op);
+
 						user = msn_userlist_find_add_user(session->userlist, passport, NULL);
-						
+						g_free(passport);
+
 						membershipIdNode = xmlnode_get_child(member,"MembershipId");
 						if (membershipIdNode != NULL) {
 							membershipId = xmlnode_get_data(membershipIdNode);
@@ -529,9 +525,10 @@
 								g_free(membershipId);
 							}
 						}
-						
+
 						msn_got_lst_user(session, user, list_op, NULL);
-						g_free(passport);
+					} else {
+						purple_debug_info("msn", "Unknown Member type: %s\n", member_type);
 					}
 				}
 			}
@@ -600,8 +597,8 @@
 msn_get_contact_list(MsnContact * contact, const MsnSoapPartnerScenario partner_scenario, const char *update_time)
 {
 	MsnSoapReq *soap_request;
-	gchar *body = NULL;
-	gchar * update_str;
+	gchar *body;
+	gchar *update_str = NULL;
 	const gchar *partner_scenario_str = MsnSoapPartnerScenarioText[partner_scenario];
 
 	purple_debug_misc("MSNCL","Getting Contact List.\n");
@@ -609,11 +606,9 @@
 	if ( update_time != NULL ) {
 		purple_debug_info("MSNCL","Last update time: %s\n",update_time);
 		update_str = g_strdup_printf(MSN_GET_CONTACT_UPDATE_XML,update_time);
-	} else {
-		update_str = g_strdup("");
 	}
 
-	body = g_strdup_printf(MSN_GET_CONTACT_TEMPLATE, partner_scenario_str, update_str);
+	body = g_strdup_printf(MSN_GET_CONTACT_TEMPLATE, partner_scenario_str, update_str ? update_str : "");
 	g_free(update_str);
 
 	soap_request = msn_soap_request_new(MSN_CONTACT_SERVER,
@@ -639,13 +634,12 @@
 	for(group = xmlnode_get_child(node, "Group"); group;
 					group = xmlnode_get_next_twin(group)){
 		xmlnode *groupId, *groupInfo, *groupname;
-		char *group_id, *group_name;
+		char *group_id = NULL, *group_name = NULL;
 
-		groupId = xmlnode_get_child(group,"groupId");
-		group_id = xmlnode_get_data(groupId);
-		groupInfo = xmlnode_get_child(group,"groupInfo");
-		groupname = xmlnode_get_child(groupInfo,"name");
-		group_name = xmlnode_get_data(groupname);
+		if ((groupId = xmlnode_get_child(group, "groupId")))
+			group_id = xmlnode_get_data(groupId);
+		if ((groupInfo = xmlnode_get_child(group, "groupInfo")) && (groupname = xmlnode_get_child(groupInfo, "name")))
+			group_name = xmlnode_get_data(groupname);
 
 		msn_group_new(session->userlist, group_id, group_name);
 
@@ -655,7 +649,7 @@
 			continue;
 		}
 
-		purple_debug_info("MsnAB","group_id: %s, name: %s\n",group_id,group_name);
+		purple_debug_info("MsnAB","group_id: %s, name: %s\n", group_id, group_name ? group_name : "(null)");
 		if ((purple_find_group(group_name)) == NULL){
 			PurpleGroup *g = purple_group_new(group_name);
 			purple_blist_add_group(g, NULL);
@@ -670,34 +664,40 @@
 {
 	MsnSession *session = contact->session;
 	xmlnode *contactNode;
+	char *passport = NULL, *Name = NULL, *uid = NULL, *type = NULL;
 
 	for(contactNode = xmlnode_get_child(node, "Contact"); contactNode;
-				contactNode = xmlnode_get_next_twin(contactNode)){
-		xmlnode *contactId,*contactInfo,*contactType,*passportName,*displayName,*guid;
-		xmlnode *groupIds;
+				contactNode = xmlnode_get_next_twin(contactNode)) {
+		xmlnode *contactId, *contactInfo, *contactType, *passportName, *displayName, *guid, *groupIds;
 		MsnUser *user;
 		MsnUserType usertype;
-		char *passport = NULL, *Name = NULL, *uid = NULL, *type = NULL;
+
+		if (!(contactId = xmlnode_get_child(contactNode,"contactId"))
+				|| !(contactInfo = xmlnode_get_child(contactNode, "contactInfo"))
+				|| !(contactType = xmlnode_get_child(contactInfo, "contactType")))
+			continue;
 
-		contactId= xmlnode_get_child(contactNode,"contactId");
+		g_free(passport);
+		g_free(Name);
+		g_free(uid);
+		g_free(type);
+		passport = Name = uid = type = NULL;
+
 		uid = xmlnode_get_data(contactId);
-
-		contactInfo = xmlnode_get_child(contactNode,"contactInfo");
-		contactType = xmlnode_get_child(contactInfo,"contactType");
 		type = xmlnode_get_data(contactType);
 
 		/*setup the Display Name*/
-		if (!strcmp(type, "Me")){
-			char *friendly;
-			friendly = xmlnode_get_data(xmlnode_get_child(contactInfo, "displayName"));
-			purple_connection_set_display_name(session->account->gc, purple_url_decode(friendly));
+		if (type && !strcmp(type, "Me")){
+			char *friendly = NULL;
+			if ((displayName = xmlnode_get_child(contactInfo, "displayName")))
+				friendly = xmlnode_get_data(displayName);
+			purple_connection_set_display_name(session->account->gc, friendly ? purple_url_decode(friendly) : NULL);
 			g_free(friendly);
-			g_free(uid);
-			g_free(type);
 			continue; /* Not adding own account as buddy to buddylist */
 		}
+
 		usertype = msn_get_user_type(type);
-		passportName = xmlnode_get_child(contactInfo,"passportName");
+		passportName = xmlnode_get_child(contactInfo, "passportName");
 		if (passportName == NULL) {
 			xmlnode *emailsNode, *contactEmailNode, *emailNode;
 			xmlnode *messengerEnabledNode;
@@ -705,103 +705,93 @@
 
 			/*TODO: add it to the none-instant Messenger group and recognize as email Membership*/
 			/*Yahoo User?*/
-			emailsNode = xmlnode_get_child(contactInfo,"emails");
+			emailsNode = xmlnode_get_child(contactInfo, "emails");
 			if (emailsNode == NULL) {
 				/*TODO:  need to support the Mobile type*/
-				g_free(uid);
-				g_free(type);
 				continue;
 			}
-			for(contactEmailNode = xmlnode_get_child(emailsNode,"ContactEmail");contactEmailNode;
+			for(contactEmailNode = xmlnode_get_child(emailsNode, "ContactEmail"); contactEmailNode;
 					contactEmailNode = xmlnode_get_next_twin(contactEmailNode) ){
-				messengerEnabledNode = xmlnode_get_child(contactEmailNode,"isMessengerEnabled");
-				if(messengerEnabledNode == NULL){
-					g_free(uid);
-					g_free(type);
+				if (!(messengerEnabledNode = xmlnode_get_child(contactEmailNode, "isMessengerEnabled"))) {
+					/* XXX: Should this be a continue instead of a break? It seems like it'd cause unpredictable results otherwise. */
 					break;
 				}
+
 				msnEnabled = xmlnode_get_data(messengerEnabledNode);
-				if(!strcmp(msnEnabled,"true")){
-					/*Messenger enabled, Get the Passport*/
-					emailNode = xmlnode_get_child(contactEmailNode,"email");
+
+				if ((emailNode = xmlnode_get_child(contactEmailNode, "email"))) {
+					g_free(passport);
 					passport = xmlnode_get_data(emailNode);
-					purple_debug_info("MsnAB","Yahoo User %s\n",passport);
+				}
+
+				if(msnEnabled && !strcmp(msnEnabled, "true")) {
+					/*Messenger enabled, Get the Passport*/
+					purple_debug_info("MsnAB", "Yahoo User %s\n", passport ? passport : "(null)");
 					usertype = MSN_USER_TYPE_YAHOO;
-					g_free(uid);
-					g_free(type);
-					g_free(passport);
 					g_free(msnEnabled);
 					break;
-				}else{
+				} else {
 					/*TODO maybe we can just ignore it in Purple?*/
-					emailNode = xmlnode_get_child(contactEmailNode,"email");
-					passport = xmlnode_get_data(emailNode);
-					purple_debug_info("MSNAB","Other type user\n");
+					purple_debug_info("MSNAB", "Other type user\n");
 				}
+
 				g_free(msnEnabled);
 			}
 		} else {
 			passport = xmlnode_get_data(passportName);
 		}
 
-		if (passport == NULL) {
-			g_free(uid);
-			g_free(type);
+		if (passport == NULL)
 			continue;
-		}
 
-		displayName = xmlnode_get_child(contactInfo,"displayName");
-		if (displayName == NULL) {
+		if ((displayName = xmlnode_get_child(contactInfo, "displayName")))
+			Name = xmlnode_get_data(displayName);
+		else
 			Name = g_strdup(passport);
-		} else {
-			Name = xmlnode_get_data(displayName);
-		}
 
 		purple_debug_misc("MsnAB","passport:{%s} uid:{%s} display:{%s}\n",
-						passport,uid,Name);
+						passport, uid ? uid : "(null)", Name ? Name : "(null)");
 
-		user = msn_userlist_find_add_user(session->userlist, passport,Name);
-		msn_user_set_uid(user,uid);
+		user = msn_userlist_find_add_user(session->userlist, passport, Name);
+		msn_user_set_uid(user, uid);
 		msn_user_set_type(user, usertype);
-		g_free(Name);
-		g_free(passport);
-		g_free(uid);
-		g_free(type);
 
-		purple_debug_misc("MsnAB","parse guid...\n");
-		groupIds = xmlnode_get_child(contactInfo,"groupIds");
+		groupIds = xmlnode_get_child(contactInfo, "groupIds");
 		if (groupIds) {
-			for (guid = xmlnode_get_child(groupIds, "guid");guid;
+			for (guid = xmlnode_get_child(groupIds, "guid"); guid;
 							guid = xmlnode_get_next_twin(guid)){
-				char *group_id;
-				group_id = xmlnode_get_data(guid);
-				msn_user_add_group_id(user,group_id);
-				purple_debug_misc("MsnAB","guid:%s\n",group_id);
+				char *group_id = xmlnode_get_data(guid);
+				msn_user_add_group_id(user, group_id);
+				purple_debug_misc("MsnAB", "guid:%s\n", group_id ? group_id : "(null)");
 				g_free(group_id);
 			}
 		} else {
+			purple_debug_info("msn", "User not in any groups, adding to default group.\n");
 			/*not in any group,Then set default group*/
 			msn_user_add_group_id(user, MSN_INDIVIDUALS_GROUP_ID);
 		}
 
 		msn_got_lst_user(session, user, MSN_LIST_FL_OP, NULL);
 	}
+
+	g_free(passport);
+	g_free(Name);
+	g_free(uid);
+	g_free(type);
 }
 
 static gboolean
 msn_parse_addressbook(MsnContact * contact)
 {
-	MsnSession * session;
+	MsnSession *session;
 	xmlnode * node,*body,*response,*result;
 	xmlnode *groups;
-	xmlnode	*contacts;
+	xmlnode *contacts;
 	xmlnode *abNode;
 	xmlnode *fault, *faultstringnode, *faultdetail, *errorcode;
 
 	session = contact->session;
 
-	
-
 	node = xmlnode_from_str(contact->soapconn->body, contact->soapconn->body_len);
 	if ( node == NULL ) {
 		purple_debug_error("MSN AddressBook","Error parsing Address Book with size %d\n", contact->soapconn->body_len);
@@ -810,17 +800,21 @@
 
 	purple_debug_misc("MSN AddressBook", "Parsing Address Book with size %d\n", contact->soapconn->body_len);
 
-	purple_debug_misc("MSN AddressBook","node{%p},name:%s,child:%s,last:%s\n",node,node->name,node->child->name,node->lastchild->name);
-	
+	purple_debug_misc("MSN AddressBook","node{%p},name:%s,child:%s,last:%s\n", node,
+		node->name ? node->name : "(null)",
+		(node->child && node->child->name) ? node->child->name : "(null)",
+		(node->lastchild && node->lastchild->name) ? node->lastchild->name : "(null)");
+
 	body = xmlnode_get_child(node,"Body");
 	purple_debug_misc("MSN AddressBook","body{%p},name:%s\n",body,body->name);
-	
+
+	/* TODO: This appears to be used in a number of places and should be de-duplicated */
 	if ( (fault = xmlnode_get_child(body, "Fault")) != NULL) {
 		purple_debug_info("MSN AddressBook","Fault received from SOAP server!\n");
 		
 		if ( (faultstringnode = xmlnode_get_child(fault, "faultstring")) != NULL ) {
 			gchar *faultstring = xmlnode_get_data(faultstringnode);
-			purple_debug_info("MSN AddressBook","Faultstring: %s\n", faultstring);
+			purple_debug_info("MSN AddressBook","Faultstring: %s\n", faultstring ? faultstring : "(null)");
 			g_free(faultstring);
 		}
 		if ( (faultdetail = xmlnode_get_child(fault, "detail")) != NULL ) {
@@ -831,9 +825,9 @@
 				purple_debug_info("MSN AddressBook","errorcode @ %p, name: %s\n",errorcode, errorcode->name);
 
 				errorcodestring = xmlnode_get_data(errorcode);
-				purple_debug_info("MSN AddressBook", "Error Code: %s\n", errorcodestring);
+				purple_debug_info("MSN AddressBook", "Error Code: %s\n", errorcodestring ? errorcodestring : "(null)");
 						
-				if ( !strncmp(errorcodestring, "ABDoesNotExist", 14) ) {
+				if (errorcodestring && !strncmp(errorcodestring, "ABDoesNotExist", 14) ) {
 					g_free(errorcodestring);
 					xmlnode_free(node);
 					return TRUE;
@@ -895,20 +889,20 @@
 
 	abNode =xmlnode_get_child(result,"ab");
 	if(abNode != NULL){
-		xmlnode *LastChangeNode, *DynamicItemLastChangedNode;
-		char *lastchange, *dynamicChange;
+		xmlnode *node2;
+		char *tmp = NULL;
 
-		LastChangeNode = xmlnode_get_child(abNode,"lastChange");
-		lastchange = xmlnode_get_data(LastChangeNode);
-		purple_debug_info("MsnAB"," lastchanged Time:{%s}\n",lastchange);
-		purple_account_set_string(session->account, "ablastChange", lastchange);
+		if ((node2 = xmlnode_get_child(abNode, "lastChange")))
+			tmp = xmlnode_get_data(node2);
+		purple_debug_info("MsnAB"," lastchanged Time:{%s}\n", tmp ? tmp : "(null)");
+		purple_account_set_string(session->account, "ablastChange", tmp);
 
-		DynamicItemLastChangedNode = xmlnode_get_child(abNode,"DynamicItemLastChanged");
-		dynamicChange = xmlnode_get_data(DynamicItemLastChangedNode);
-		purple_debug_info("MsnAB"," DynamicItemLastChanged :{%s}\n",dynamicChange);
-		purple_account_set_string(session->account, "DynamicItemLastChanged", lastchange);
-		g_free(dynamicChange);
-		g_free(lastchange);
+		g_free(tmp); tmp = NULL;
+		if ((node2 = xmlnode_get_child(abNode, "DynamicItemLastChanged")))
+			tmp = xmlnode_get_data(node2);
+		purple_debug_info("MsnAB"," DynamicItemLastChanged :{%s}\n", tmp ? tmp : "(null)");
+		purple_account_set_string(session->account, "DynamicItemLastChanged", tmp);
+		g_free(tmp);
 	}
 
 	xmlnode_free(node);
@@ -969,27 +963,19 @@
 msn_get_address_book(MsnContact *contact, const MsnSoapPartnerScenario partner_scenario, const char *LastChanged, const char *dynamicItemLastChange)
 {
 	MsnSoapReq *soap_request;
-	char *body = NULL;
-	char *ab_update_str,*update_str;
+	char *body;
+	char *update_str = NULL;
 
 	purple_debug_misc("MSN AddressBook","Getting Address Book\n");
 
 	/*build SOAP and POST it*/
-	if ( LastChanged != NULL ) {
-		ab_update_str = g_strdup_printf(MSN_GET_ADDRESS_UPDATE_XML,LastChanged);
-	} else {
-		ab_update_str = g_strdup("");
-	}
-	if ( dynamicItemLastChange != NULL ) {
-		update_str = g_strdup_printf(MSN_GET_ADDRESS_UPDATE_XML,
-									 dynamicItemLastChange);
-	} else {
-		update_str = g_strdup(ab_update_str);
-	}
-	g_free(ab_update_str);
-	
+	if (dynamicItemLastChange != NULL)
+		update_str = g_strdup_printf(MSN_GET_ADDRESS_UPDATE_XML, dynamicItemLastChange);
+	else if (LastChanged != NULL)
+		update_str = g_strdup_printf(MSN_GET_ADDRESS_UPDATE_XML, LastChanged);
 
-	body = g_strdup_printf(MSN_GET_ADDRESS_TEMPLATE, MsnSoapPartnerScenarioText[partner_scenario], update_str);
+
+	body = g_strdup_printf(MSN_GET_ADDRESS_TEMPLATE, MsnSoapPartnerScenarioText[partner_scenario], update_str ? update_str : "");
 	g_free(update_str);
 
 	soap_request = msn_soap_request_new(MSN_CONTACT_SERVER,
@@ -1063,7 +1049,8 @@
 	MsnSoapReq *soap_request;
 	gchar *body = NULL;
 	gchar *contact_xml = NULL;
-	gchar *soap_action;
+
+	g_return_if_fail(passport != NULL);
 /*	gchar *escaped_displayname;
 
 
@@ -1084,11 +1071,10 @@
 	g_free(contact_xml);
 
 	/*build SOAP and POST it*/
-	soap_action = g_strdup(MSN_CONTACT_ADD_SOAP_ACTION);
 
 	soap_request = msn_soap_request_new(MSN_CONTACT_SERVER,
 					MSN_ADDRESS_BOOK_POST_URL,
-					soap_action,
+					MSN_CONTACT_ADD_SOAP_ACTION,
 					body,
 					state,
 					msn_add_contact_read_cb,
@@ -1096,7 +1082,6 @@
 					msn_contact_connect_init);
 	msn_soap_post(contact->soapconn,soap_request);
 
-	g_free(soap_action);
 	g_free(body);
 }
 
@@ -1165,7 +1150,7 @@
 	MsnSoapReq *soap_request;
 	MsnUserList *userlist;
 	MsnUser *user;
-	gchar *body = NULL, *soap_action, *contact_xml;
+	gchar *body = NULL, *contact_xml;
 
 	g_return_if_fail(passport != NULL);
 	g_return_if_fail(groupId != NULL);
@@ -1214,11 +1199,10 @@
 	g_free(contact_xml);
 
 	/*build SOAP and POST it*/
-	soap_action = g_strdup(MSN_ADD_CONTACT_GROUP_SOAP_ACTION);
 
 	soap_request = msn_soap_request_new(MSN_CONTACT_SERVER,
 					MSN_ADDRESS_BOOK_POST_URL,
-					soap_action,
+					MSN_ADD_CONTACT_GROUP_SOAP_ACTION,
 					body,
 					state,
 					msn_add_contact_to_group_read_cb,
@@ -1226,7 +1210,6 @@
 					msn_contact_connect_init);
 	msn_soap_post(contact->soapconn,soap_request);
 
-	g_free(soap_action);
 	g_free(body);
 }
 
@@ -1340,7 +1323,7 @@
 	MsnUserList * userlist;
 	MsnUser *user;
 	MsnCallbackState *state;
-	gchar *body = NULL, *soap_action, *contact_id_xml;
+	gchar *body = NULL, *contact_id_xml;
 	const gchar *groupId;
 	
 	g_return_if_fail(passport != NULL);
@@ -1375,25 +1358,22 @@
 	msn_callback_state_set_who(state, passport);
 	msn_callback_state_set_guid(state, groupId);
 	msn_callback_state_set_old_group_name(state, group_name);
-		
+
 	contact_id_xml = g_strdup_printf(MSN_CONTACT_ID_XML, user->uid);
 	body = g_strdup_printf(MSN_CONTACT_DEL_GROUP_TEMPLATE, contact_id_xml, groupId);
 	g_free(contact_id_xml);
-	
+
 	/*build SOAP and POST it*/
-	soap_action = g_strdup(MSN_CONTACT_DEL_GROUP_SOAP_ACTION);
-	
 	soap_request = msn_soap_request_new(MSN_CONTACT_SERVER,
 					    MSN_ADDRESS_BOOK_POST_URL,
-					    soap_action,
+					    MSN_CONTACT_DEL_GROUP_SOAP_ACTION,
 					    body,
 					    state,
 					    msn_del_contact_from_group_read_cb,
 					    msn_del_contact_from_group_written_cb,
 					    msn_contact_connect_init);
 	msn_soap_post(contact->soapconn,soap_request);
-	
-	g_free(soap_action);
+
 	g_free(body);
 }
 
@@ -1422,14 +1402,17 @@
 msn_update_contact(MsnContact *contact, const char* nickname)
 {
 	MsnSoapReq *soap_request;
-	gchar *body = NULL, *escaped_nickname;
+	gchar *body, *escaped_nickname;
+
+	/* I'm not sure this is right, but if it isn't, the rest of this function will need to be fixed */
+	g_return_if_fail(nickname != NULL);
 
 	purple_debug_info("MSN CL","Update contact information with new friendly name: %s\n", nickname);
-	
+
 	escaped_nickname = g_markup_escape_text(nickname, -1);
 
 	body = g_strdup_printf(MSN_CONTACT_UPDATE_TEMPLATE, escaped_nickname);
-	
+
 	g_free(escaped_nickname);
 	/*build SOAP and POST it*/
 	soap_request = msn_soap_request_new(MSN_CONTACT_SERVER,
--- a/libpurple/protocols/msn/notification.c	Wed Sep 26 06:56:02 2007 +0000
+++ b/libpurple/protocols/msn/notification.c	Fri Oct 12 02:06:38 2007 +0000
@@ -101,7 +101,8 @@
 	MsnCmdProc *cmdproc;
 	MsnSession *session;
 	PurpleAccount *account;
-	char **a, **c, *vers;
+	GString *vers;
+	const char *ver_str;
 	int i;
 
 	g_return_if_fail(servconn != NULL);
@@ -110,31 +111,24 @@
 	session = servconn->session;
 	account = session->account;
 
-	/* Allocate an array for CVR0, NULL, and all the versions */
-//	a = c = g_new0(char *, session->protocol_ver - WLM_MIN_PROTOCOL + 3);
-	a = c = g_new0(char *, WLM_MAX_PROTOCOL - WLM_MIN_PROTOCOL + 3);
+	vers = g_string_new("");
 
-//	for (i = session->protocol_ver; i >= WLM_MIN_PROTOCOL; i--)
+/*	for (i = session->protocol_ver; i >= WLM_MIN_PROTOCOL; i--) */
 	for (i = WLM_MAX_PROTOCOL; i >= WLM_MIN_PROTOCOL; i--)
-		*c++ = g_strdup_printf("MSNP%d", i);
+		g_string_append_printf(vers, " MSNP%d", i);
 
-	*c++ = g_strdup("CVR0");
-
-	vers = g_strjoinv(" ", a);
+	g_string_append(vers, " CVR0");
 
 	if (session->login_step == MSN_LOGIN_STEP_START)
-	{
 		msn_session_set_login_step(session, MSN_LOGIN_STEP_HANDSHAKE);
-	}
 	else
-	{
 		msn_session_set_login_step(session, MSN_LOGIN_STEP_HANDSHAKE2);
-	}
 
-	msn_cmdproc_send(cmdproc, "VER", "%s", vers);
+	/* Skip the initial space */
+	ver_str = (vers->str + 1);
+	msn_cmdproc_send(cmdproc, "VER", "%s", ver_str);
 
-	g_strfreev(a);
-	g_free(vers);
+	g_string_free(vers, TRUE);
 }
 
 gboolean
@@ -189,7 +183,7 @@
 			const char *group_name;
 			group_name = msn_userlist_find_group_name(session->userlist,group_id);
 			reason = g_strdup_printf(_("%s is not a valid group."),
-									 group_name);
+									 group_name ? group_name : "");
 		}
 	}
 	else
@@ -590,57 +584,59 @@
 {
 	xmlnode *d_node,*c_node;
 	char **tokens;
-	char *email,*domain;
-	char *list_op_str,*type_str;
+	const char *email,*domain;
+	char fmt_str[3];
+
+	g_return_if_fail(passport != NULL);
 
 	purple_debug_info("MSNP14","Passport: %s, type: %d\n", passport, type);
 	tokens = g_strsplit(passport, "@", 2);
 	email = tokens[0];
 	domain = tokens[1];
 
+	if (email == NULL || domain == NULL) {
+		purple_debug_error("msn", "Invalid passport (%s) specified to add to contact xml.\n", passport);
+		g_strfreev(tokens);
+		g_return_if_reached();
+	}
+
 	/*find a domain Node*/
 	for(d_node = xmlnode_get_child(mlNode,"d"); d_node; d_node = xmlnode_get_next_twin(d_node))
 	{
-		const char * attr = NULL;
-		purple_debug_info("MSNP14","d_node: %s\n",d_node->name);
-		attr = xmlnode_get_attrib(d_node,"n");
-		if(attr == NULL){
+		const char *attr = xmlnode_get_attrib(d_node,"n");
+		if (attr == NULL)
 			continue;
-		}
-		if(!strcmp(attr,domain)){
+		if (!strcmp(attr,domain))
 			break;
-		}
 	}
+
 	if(d_node == NULL)
 	{
 		/*domain not found, create a new domain Node*/
-		purple_debug_info("MSNP14","get No d_node\n");
+		purple_debug_info("msn", "Didn't find existing domain node, adding one.\n");
 		d_node = xmlnode_new("d");
-		xmlnode_set_attrib(d_node,"n",domain);
-		xmlnode_insert_child(mlNode,d_node);
+		xmlnode_set_attrib(d_node, "n", domain);
+		xmlnode_insert_child(mlNode, d_node);
 	}
 
 	/*create contact node*/
 	c_node = xmlnode_new("c");
-	xmlnode_set_attrib(c_node,"n",email);
+	xmlnode_set_attrib(c_node, "n", email);
 
-	list_op_str = g_strdup_printf("%d",list_op);
-	purple_debug_info("MSNP14","list_op: %d\n",list_op);
-	xmlnode_set_attrib(c_node,"l",list_op_str);
-	g_free(list_op_str);
+	purple_debug_info("MSNP14", "list_op: %d\n", list_op);
+	g_snprintf(fmt_str, sizeof(fmt_str), "%d", list_op);
+	xmlnode_set_attrib(c_node, "l", fmt_str);
 
-	if (type != MSN_USER_TYPE_UNKNOWN) {
-		type_str = g_strdup_printf("%d", type);
-	} else {
-		if (msn_user_is_yahoo(session->account, passport))
-			type_str = g_strdup_printf("%d", MSN_USER_TYPE_YAHOO);
-		else
-			type_str = g_strdup_printf("%d", MSN_USER_TYPE_PASSPORT);
-	}
+	if (type != MSN_USER_TYPE_UNKNOWN)
+		g_snprintf(fmt_str, sizeof(fmt_str), "%d", type);
+	else if (msn_user_is_yahoo(session->account, passport))
+		g_snprintf(fmt_str, sizeof(fmt_str), "%d", MSN_USER_TYPE_YAHOO);
+	else
+		g_snprintf(fmt_str, sizeof(fmt_str), "%d", MSN_USER_TYPE_PASSPORT);
+
 	/*mobile*/
 	//type_str = g_strdup_printf("4");
-	xmlnode_set_attrib(c_node,"t",type_str);
-	g_free(type_str);
+	xmlnode_set_attrib(c_node, "t", fmt_str);
 
 	xmlnode_insert_child(d_node, c_node);
 
--- a/libpurple/protocols/msn/switchboard.c	Wed Sep 26 06:56:02 2007 +0000
+++ b/libpurple/protocols/msn/switchboard.c	Fri Oct 12 02:06:38 2007 +0000
@@ -958,25 +958,14 @@
 nudge_msg(MsnCmdProc *cmdproc, MsnMessage *msg)
 {
 	MsnSwitchBoard *swboard;
-	char *username, *str;
 	PurpleAccount *account;
-	PurpleBuddy *buddy;
 	const char *user;
 
-	str = NULL;
-
 	swboard = cmdproc->data;
 	account = cmdproc->session->account;
 	user = msg->remote_user;
 
-	if ((buddy = purple_find_buddy(account, user)) != NULL)
-		username = g_markup_escape_text(purple_buddy_get_alias(buddy), -1);
-	else
-		username = g_markup_escape_text(user, -1);
-
-	serv_got_attention(account->gc, buddy->name, MSN_NUDGE);
-	g_free(username);
-	g_free(str);
+	serv_got_attention(account->gc, user, MSN_NUDGE);
 }
 
 /**************************************************************************
--- a/libpurple/protocols/msn/userlist.c	Wed Sep 26 06:56:02 2007 +0000
+++ b/libpurple/protocols/msn/userlist.c	Fri Oct 12 02:06:38 2007 +0000
@@ -41,16 +41,20 @@
 msn_accept_add_cb(gpointer data)
 {
 	MsnPermitAdd *pa = data;
-	MsnSession *session = pa->gc->proto_data;
-	MsnUserList *userlist = session->userlist;
-	MsnUser *user = msn_userlist_find_add_user(userlist, pa->who, pa->who);
-	
+
 	purple_debug_misc("MSN Userlist", "Accepted the new buddy: %s\n", pa->who);
 
-	msn_userlist_add_buddy_to_list(userlist, pa->who, MSN_LIST_AL);
+	if (PURPLE_CONNECTION_IS_VALID(pa->gc))
+	{
+		MsnSession *session = pa->gc->proto_data;
+		MsnUserList *userlist = session->userlist;
+		MsnUser *user = msn_userlist_find_add_user(userlist, pa->who, pa->who);
 
-	if (msn_userlist_user_is_in_list(user, MSN_LIST_FL)) {
-		msn_del_contact_from_list(session->contact, NULL, pa->who, MSN_LIST_PL);
+
+		msn_userlist_add_buddy_to_list(userlist, pa->who, MSN_LIST_AL);
+
+		if (msn_userlist_user_is_in_list(user, MSN_LIST_FL))
+			msn_del_contact_from_list(session->contact, NULL, pa->who, MSN_LIST_PL);
 	}
 
 	g_free(pa->who);
@@ -63,14 +67,14 @@
 {
 	MsnPermitAdd *pa = data;
 
-	purple_debug_misc("MSN Userlist", "Deniedthe new buddy: %s\n", pa->who);
+	purple_debug_misc("MSN Userlist", "Denied the new buddy: %s\n", pa->who);
 
-	if (g_list_find(purple_connections_get_all(), pa->gc) != NULL)
+	if (PURPLE_CONNECTION_IS_VALID(pa->gc))
 	{
 		MsnSession *session = pa->gc->proto_data;
 		MsnUserList *userlist = session->userlist;
 		MsnCallbackState *state = msn_callback_state_new();
-	
+
 		msn_callback_state_set_action(state, MSN_DENIED_BUDDY);
 
 		msn_userlist_add_buddy_to_list(userlist, pa->who, MSN_LIST_BL);
@@ -85,16 +89,18 @@
 static void
 got_new_entry(PurpleConnection *gc, const char *passport, const char *friendly)
 {
+	PurpleAccount *acct;
 	MsnPermitAdd *pa;
 
 	pa = g_new0(MsnPermitAdd, 1);
 	pa->who = g_strdup(passport);
 	pa->friendly = g_strdup(friendly);
 	pa->gc = gc;
-	
-	purple_account_request_authorization(purple_connection_get_account(gc), passport, NULL, friendly, NULL,
-					   purple_find_buddy(purple_connection_get_account(gc), passport) != NULL,
-					   msn_accept_add_cb, msn_cancel_add_cb, pa);
+
+	acct = purple_connection_get_account(gc);
+	purple_account_request_authorization(acct, passport, NULL, friendly, NULL,
+										 purple_find_buddy(acct, passport) != NULL,
+										 msn_accept_add_cb, msn_cancel_add_cb, pa);
 
 }
 
--- a/libpurple/protocols/myspace/markup.c	Wed Sep 26 06:56:02 2007 +0000
+++ b/libpurple/protocols/myspace/markup.c	Fri Oct 12 02:06:38 2007 +0000
@@ -258,8 +258,8 @@
 	}
 
 
-	*begin = gs_begin->str;
-	*end = gs_end->str;
+	*begin = g_string_free(gs_begin, FALSE);
+	*end = g_string_free(gs_end, FALSE);
 }
 
 /** Convert a msim markup color to a color suitable for libpurple.
@@ -444,7 +444,8 @@
 		*begin = g_strdup_printf("<f s='%d'>", MSIM_TEXT_UNDERLINE);
 		*end = g_strdup("</f>");
 	} else if (!purple_utf8_strcasecmp(root->name, "a")) {
-		const gchar *href, *link_text;
+		const gchar *href;
+		gchar *link_text;
 
 		href = xmlnode_get_attrib(root, "href");
 
@@ -476,6 +477,7 @@
 
 		/* Sorry, kid. MySpace doesn't support you within <a> tags. */
 		xmlnode_free(root->child);
+		g_free(link_text);
 		root->child = NULL;
 
 		*end = g_strdup("");
@@ -568,10 +570,7 @@
 	
 		case XMLNODE_TYPE_DATA:
 			/* Literal text. */
-			inner = g_new0(char, node->data_sz + 1);
-			strncpy(inner, node->data, node->data_sz);
-			inner[node->data_sz] = 0;
-
+			inner = g_strndup(node->data, node->data_sz);
 			purple_debug_info("msim", " ** node data=%s\n", 
 					inner ? inner : "(NULL)");
 			break;
@@ -584,6 +583,8 @@
 
 		if (inner) {
 			g_string_append(final, inner);
+			g_free(inner);
+			inner = NULL;
 		}
 	}
 
@@ -593,10 +594,13 @@
 	 * Comment out this line below to see. */
 	g_string_append(final, end);
 
+	g_free(begin);
+	g_free(end);
+
 	purple_debug_info("msim", "msim_markup_xmlnode_to_gtkhtml: RETURNING %s\n",
 			(final && final->str) ? final->str : "(NULL)");
 
-	return final->str;
+	return g_string_free(final, FALSE);
 }
 
 /** Convert XML to something based on MSIM_XMLNODE_CONVERT. */
--- a/libpurple/protocols/myspace/message.c	Wed Sep 26 06:56:02 2007 +0000
+++ b/libpurple/protocols/myspace/message.c	Fri Oct 12 02:06:38 2007 +0000
@@ -80,7 +80,7 @@
 	purple_debug_info("msim", "msim_escape: msg=%s, ret=%s\n", msg, gs->str);
 #endif
 
-	return gs->str;
+	return g_string_free(gs, FALSE);
 }
 
 /**
@@ -120,7 +120,7 @@
 	purple_debug_info("msim", "msim_unescape: msg=%s, ret=%s\n", msg, gs->str);
 #endif
 
-	return gs->str;
+	return g_string_free(gs, FALSE);
 }
 
 /** Create a new MsimMessage. 
@@ -691,7 +691,7 @@
 				++i;
 			}
 			
-			string = gs->str;
+			string = g_string_free(gs, FALSE);
 			break;
 
 		default:
@@ -798,7 +798,7 @@
 					g_string_append(gs, "|");
 			}
 			
-			return gs->str;
+			return g_string_free(gs, FALSE);
 
 		default:
 			purple_debug_info("msim", "field %s, unknown type %d\n", 
@@ -1337,9 +1337,7 @@
 			gs = (GString *)elem->data;
 
 			/* Duplicate data, so caller can g_free() it. */
-			*binary_data = g_new0(char, gs->len);
-			memcpy(*binary_data, gs->str, gs->len);
-
+			*binary_data = g_memdup(gs->str, gs->len);
 			*binary_length = gs->len;
 
 			return TRUE;
--- a/libpurple/protocols/myspace/myspace.c	Wed Sep 26 06:56:02 2007 +0000
+++ b/libpurple/protocols/myspace/myspace.c	Fri Oct 12 02:06:38 2007 +0000
@@ -43,7 +43,7 @@
 
 static int msim_send_really_raw(PurpleConnection *gc, const char *buf, int total_bytes);
 static gboolean msim_login_challenge(MsimSession *session, MsimMessage *msg);
-static const gchar *msim_compute_login_response(const gchar nonce[2 * NONCE_SIZE], const gchar *email, const gchar *password, guint *response_len);
+static gchar *msim_compute_login_response(const gchar nonce[2 * NONCE_SIZE], const gchar *email, const gchar *password, guint *response_len);
 
 static gboolean msim_incoming_bm_record_cv(MsimSession *session, MsimMessage *msg);
 static gboolean msim_incoming_bm(MsimSession *session, MsimMessage *msg);
@@ -289,7 +289,7 @@
 				(int)strlen(acct->password));
 
 		/* Notify an error message also, because this is important! */
-		purple_notify_error(acct, g_strdup(_("MySpaceIM Error")), str, NULL);
+		purple_notify_error(acct, _("MySpaceIM Error"), str, NULL);
 
 		gc->wants_to_die = TRUE;
 		purple_connection_error(gc, str);
@@ -332,10 +332,11 @@
 msim_login_challenge(MsimSession *session, MsimMessage *msg) 
 {
 	PurpleAccount *account;
-	const gchar *response;
+	gchar *response;
 	guint response_len;
 	gchar *nc;
 	gsize nc_len;
+	gboolean ret;
 
 	g_return_val_if_fail(MSIM_SESSION_VALID(session), FALSE);
 	g_return_val_if_fail(msg != NULL, FALSE);
@@ -363,11 +364,11 @@
 
 	g_free(nc);
 
-	return msim_send(session, 
+	ret = msim_send(session,
 			"login2", MSIM_TYPE_INTEGER, MSIM_AUTH_ALGORITHM,
 			/* This is actually user's email address. */
 			"username", MSIM_TYPE_STRING, g_strdup(account->username),
-			/* GString and gchar * response will be freed in msim_msg_free() in msim_send(). */
+			/* GString will be freed in msim_msg_free() in msim_send(). */
 			"response", MSIM_TYPE_BINARY, g_string_new_len(response, response_len),
 			"clientver", MSIM_TYPE_INTEGER, MSIM_CLIENT_VERSION,
 			"langid", MSIM_TYPE_INTEGER, MSIM_LANGUAGE_ID_ENGLISH,
@@ -376,6 +377,10 @@
 			"status", MSIM_TYPE_INTEGER, 100,
 			"id", MSIM_TYPE_INTEGER, 1,
 			NULL);
+
+	g_free(response);
+
+	return ret;
 }
 
 /**
@@ -389,7 +394,7 @@
  * @return Binary login challenge response, ready to send to the server. 
  * Must be g_free()'d when finished. NULL if error.
  */
-static const gchar *
+static gchar *
 msim_compute_login_response(const gchar nonce[2 * NONCE_SIZE], 
 		const gchar *email, const gchar *password, guint *response_len)
 {
@@ -487,8 +492,13 @@
 	purple_cipher_context_encrypt(rc4, (const guchar *)data, 
 			data_len, data_out, &data_out_len);
 	purple_cipher_context_destroy(rc4);
-
-	g_assert(data_out_len == data_len);
+	g_free(data);
+
+	if (data_out_len != data_len) {
+		purple_debug_info("msim", "msim_compute_login_response: "
+				"data length mismatch: %d != %d\n",
+				data_out_len, data_len);
+	}
 
 #ifdef MSIM_DEBUG_LOGIN_CHALLENGE
 	purple_debug_info("msim", "response=<%s>\n", data_out);
@@ -496,7 +506,7 @@
 
 	*response_len = data_out_len;
 
-	return (const gchar *)data_out;
+	return (gchar *)data_out;
 }
 
 /**
@@ -626,6 +636,7 @@
 	g_return_val_if_fail(username != NULL, FALSE);
 	if (!cv) {
 		/* No client version to record, don't worry about it. */
+		g_free(username);
 		return FALSE;
 	}
 
@@ -815,6 +826,7 @@
 	serv_got_typing_stopped(session->gc, username);
 
 	g_free(username);
+	g_free(text);
 
 	return TRUE;
 }
@@ -963,7 +975,6 @@
 	purple_debug_info("msim", "msim_get_info_cb: username=%s\n", username);
 
 	purple_notify_user_info_destroy(user_info);
-	/* TODO: do not free username, since it will be used by user_info? */
 
 	if (temporary_user) {
 		g_free(user->client_info);
@@ -977,7 +988,7 @@
 		g_free(user->image_url);
 		g_free(user);
 	}
-
+	g_free(username);
 }
 
 /** Retrieve a user's profile. 
@@ -1156,7 +1167,7 @@
 	/* TODO: more elegant solution than below. attach whole message? */
 	/* Special elements name beginning with '_', we'll use internally within the
 	 * program (did not come directly from the wire). */
-	msg = msim_msg_append(msg, "_username", MSIM_TYPE_STRING, username);
+	msg = msim_msg_append(msg, "_username", MSIM_TYPE_STRING, username); /* This makes 'msg' the owner of 'username' */
   
 	/* TODO: attach more useful information, like ImageURL */
 
@@ -1297,7 +1308,6 @@
 msim_check_inbox_cb(MsimSession *session, MsimMessage *reply, gpointer data)
 {
 	MsimMessage *body;
-	GString *notification;
 	guint old_inbox_status;
 	guint i, n;
 	const gchar *froms[5], *tos[5], *urls[5], *subjects[5];
@@ -1331,8 +1341,6 @@
 	body = msim_msg_get_dictionary(reply, "body");
 	g_return_if_fail(body != NULL);
 
-	notification = g_string_new("");
-
 	old_inbox_status = session->inbox_status;
 
 	n = 0;
@@ -1795,8 +1803,7 @@
 		}
 		purple_connection_error(session->gc, full_errmsg);
 	} else {
-		purple_notify_error(session->account, g_strdup(_("MySpaceIM Error")), 
-				full_errmsg, NULL);
+		purple_notify_error(session->account, _("MySpaceIM Error"), full_errmsg, NULL);
 	}
 
 	g_free(full_errmsg);
@@ -2667,6 +2674,7 @@
 	/* TODO: other fields, store in 'user' */
 
 	msim_msg_free(contact_info);
+	g_free(username);
 }
 
 /** Add first ContactID in contact_info to buddy's list. Used to add
@@ -3009,7 +3017,7 @@
 	msg = msim_msg_new(NULL);      /* Create a new, empty message. */
 
 	/* Append some new elements. */
-	msg = msim_msg_append(msg, "bx", MSIM_TYPE_BINARY, g_string_new_len(g_strdup("XXX"), 3));
+	msg = msim_msg_append(msg, "bx", MSIM_TYPE_BINARY, g_string_new_len("XXX", 3));
 	msg = msim_msg_append(msg, "k1", MSIM_TYPE_STRING, g_strdup("v1"));
 	msg = msim_msg_append(msg, "k1", MSIM_TYPE_INTEGER, GUINT_TO_POINTER(42));
 	msg = msim_msg_append(msg, "k1", MSIM_TYPE_STRING, g_strdup("v43"));
--- a/libpurple/protocols/myspace/user.c	Wed Sep 26 06:56:02 2007 +0000
+++ b/libpurple/protocols/myspace/user.c	Fri Oct 12 02:06:38 2007 +0000
@@ -30,10 +30,10 @@
 static gchar *
 msim_format_now_playing(gchar *band, gchar *song)
 {
-	if ((band && strlen(band)) || (song && strlen(song))) {
+	if ((band && *band) || (song && *song)) {
 		return g_strdup_printf("%s - %s",
-			(band && strlen(band)) ? band : "Unknown Artist",
-			(song && strlen(song)) ? song : "Unknown Song");
+			(band && *band) ? band : "Unknown Artist",
+			(song && *song) ? song : "Unknown Song");
 	} else {
 		return NULL;
 	}
@@ -99,58 +99,63 @@
 
 	if (full) {
 		/* TODO: link to username, if available */
-		purple_notify_user_info_add_pair(user_info, _("Profile"),
-				g_strdup_printf("<a href=\"http://myspace.com/%d\">http://myspace.com/%d</a>",
-					uid, uid));
+		char *profile = g_strdup_printf("<a href=\"http://myspace.com/%d\">http://myspace.com/%d</a>",
+				uid, uid);
+		purple_notify_user_info_add_pair(user_info, _("Profile"), profile);
+		g_free(profile);
 	}
 
 
 	/* a/s/l...the vitals */
 	if (user->age) {
-		purple_notify_user_info_add_pair(user_info, _("Age"),
-				g_strdup_printf("%d", user->age));
+		char age[16];
+		g_snprintf(age, sizeof(age), "%d", user->age);
+		purple_notify_user_info_add_pair(user_info, _("Age"), age);
 	}
 
-	if (user->gender && strlen(user->gender)) {
+	if (user->gender && *user->gender) {
 		purple_notify_user_info_add_pair(user_info, _("Gender"), user->gender);
 	}
 
-	if (user->location && strlen(user->location)) {
+	if (user->location && *user->location) {
 		purple_notify_user_info_add_pair(user_info, _("Location"), user->location);
 	}
 
 	/* Other information */
-	if (user->headline && strlen(user->headline)) {
+	if (user->headline && *user->headline) {
 		purple_notify_user_info_add_pair(user_info, _("Headline"), user->headline);
 	}
 
 	str = msim_format_now_playing(user->band_name, user->song_name);
-	if (str && strlen(str)) {
+	if (str && *str) {
 		purple_notify_user_info_add_pair(user_info, _("Song"), str);
 	}
+	g_free(str);
 
 	/* Note: total friends only available if looked up by uid, not username. */
 	if (user->total_friends) {
-		purple_notify_user_info_add_pair(user_info, _("Total Friends"),
-			g_strdup_printf("%d", user->total_friends));
+		char friends[16];
+		g_snprintf(friends, sizeof(friends), "%d", user->total_friends);
+		purple_notify_user_info_add_pair(user_info, _("Total Friends"), friends);
 	}
 
 	if (full) {
 		/* Client information */
+		char *client = NULL;
 
 		str = user->client_info;
 		cv = user->client_cv;
 
 		if (str && cv != 0) {
-			purple_notify_user_info_add_pair(user_info, _("Client Version"),
-					g_strdup_printf("%s (build %d)", str, cv));
+			client = g_strdup_printf("%s (build %d)", str, cv);
 		} else if (str) {
-			purple_notify_user_info_add_pair(user_info, _("Client Version"),
-					g_strdup(str));
+			client = g_strdup(str);
 		} else if (cv) {
-			purple_notify_user_info_add_pair(user_info, _("Client Version"),
-					g_strdup_printf("Build %d", cv));
+			client = g_strdup_printf("Build %d", cv);
 		}
+		if (client && *client)
+			purple_notify_user_info_add_pair(user_info, _("Client Version"), client);
+		g_free(client);
 	}
 }
 
--- a/libpurple/protocols/myspace/zap.c	Wed Sep 26 06:56:02 2007 +0000
+++ b/libpurple/protocols/myspace/zap.c	Fri Oct 12 02:06:38 2007 +0000
@@ -41,15 +41,52 @@
 		types = g_list_append(types, attn);
 
 		/* TODO: icons for each zap */
+
+		/* Lots of comments for translators: */
+
+		/* Zap means "to strike suddenly and forcefully as if with a
+		 * projectile or weapon."  This term often has an electrical
+		 * connotation, for example, "he was zapped by electricity when
+		 * he put a fork in the toaster." */
 		_MSIM_ADD_NEW_ATTENTION(NULL, _("Zap"), _("%s has zapped you!"), _("Zapping %s..."));
+
+		/* Whack means "to hit or strike someone with a sharp blow" */
 		_MSIM_ADD_NEW_ATTENTION(NULL, _("Whack"), _("%s has whacked you!"), _("Whacking %s..."));
+
+		/* Torch means "to set on fire."  Don't worry, this doesn't
+		 * make a whole lot of sense in English, either.  Feel free
+		 * to translate it literally. */
 		_MSIM_ADD_NEW_ATTENTION(NULL, _("Torch"), _("%s has torched you!"), _("Torching %s..."));
+
+		/* Smooch means "to kiss someone, often enthusiastically" */
 		_MSIM_ADD_NEW_ATTENTION(NULL, _("Smooch"), _("%s has smooched you!"), _("Smooching %s..."));
+
+		/* A hug is a display of affection; wrapping your arms around someone */
 		_MSIM_ADD_NEW_ATTENTION(NULL, _("Hug"), _("%s has hugged you!"), _("Hugging %s..."));
+
+		/* Slap means "to hit someone with an open/flat hand" */
 		_MSIM_ADD_NEW_ATTENTION(NULL, _("Slap"), _("%s has slapped you!"), _("Slapping %s..."));
+
+		/* Goose means "to pinch someone on their butt" */
 		_MSIM_ADD_NEW_ATTENTION(NULL, _("Goose"), _("%s has goosed you!"), _("Goosing %s..."));
+
+		/* A high-five is when two people's hands slap each other
+		 * in the air above their heads.  It is done to celebrate
+		 * something, often a victory, or to congratulate someone. */
 		_MSIM_ADD_NEW_ATTENTION(NULL, _("High-five"), _("%s has high-fived you!"), _("High-fiving %s..."));
+
+		/* We're not entirely sure what the MySpace people mean by
+		 * this... but we think it's the equivalent of "prank."  Or, for
+		 * someone to perform a mischievous trick or practical joke. */
 		_MSIM_ADD_NEW_ATTENTION(NULL, _("Punk"), _("%s has punk'd you!"), _("Punking %s..."));
+
+		/* Raspberry is a slang term for the vibrating sound made
+		 * when you stick your tongue out of your mouth with your
+		 * lips closed and blow.  It is typically done when
+		 * gloating or bragging.  Nowadays it's a pretty silly
+		 * gesture, so it does not carry a harsh negative
+		 * connotation.  It is generally used in a playful tone
+		 * with friends. */
 		_MSIM_ADD_NEW_ATTENTION(NULL, _("Raspberry"), _("%s has raspberried you!"), _("Raspberrying %s..."));
 	}
 
--- a/libpurple/protocols/oscar/family_auth.c	Wed Sep 26 06:56:02 2007 +0000
+++ b/libpurple/protocols/oscar/family_auth.c	Fri Oct 12 02:06:38 2007 +0000
@@ -438,10 +438,6 @@
  * login request instead of the normal SNAC one.
  *
  * As soon as AOL makes ICQ log in the same way as AIM, this is /gone/.
- *
- * XXX This may cause problems if the client relies on callbacks only
- * being called from the context of aim_rxdispatch()...
- *
  */
 static int
 goddamnicq(OscarData *od, FlapConnection *conn, const char *sn)
--- a/libpurple/protocols/oscar/odc.c	Wed Sep 26 06:56:02 2007 +0000
+++ b/libpurple/protocols/oscar/odc.c	Fri Oct 12 02:06:38 2007 +0000
@@ -27,6 +27,8 @@
 #include "imgstore.h"
 #include "util.h"
 
+#define DIRECTIM_MAX_FILESIZE 52428800
+
 /**
  * Free any ODC related data and print a message to the conversation
  * window based on conn->disconnect_reason.
@@ -587,6 +589,27 @@
 
 	if (frame->payload.len > 0)
 	{
+		if (frame->payload.len > DIRECTIM_MAX_FILESIZE)
+		{
+			gchar *tmp, *size1, *size2;
+			PurpleAccount *account;
+			PurpleConversation *conv;
+
+			size1 = purple_str_size_to_units(frame->payload.len);
+			size2 = purple_str_size_to_units(DIRECTIM_MAX_FILESIZE);
+			tmp = g_strdup_printf(_("%s tried to send you a %s file, but we only allow files up to %s over Direct IM.  Try using file transfer instead.\n"), conn->sn, size1, size2);
+			g_free(size1);
+			g_free(size2);
+
+			account = purple_connection_get_account(conn->od->gc);
+			conv = purple_conversation_new(PURPLE_CONV_TYPE_IM, account, conn->sn);
+			purple_conversation_write(conv, NULL, tmp, PURPLE_MESSAGE_SYSTEM, time(NULL));
+			g_free(tmp);
+
+			peer_connection_destroy(conn, OSCAR_DISCONNECT_LOCAL_CLOSED, NULL);
+			return;
+		}
+
 		/* We have payload data!  Switch to the ODC watcher to read it. */
 		frame->payload.data = g_new(guint8, frame->payload.len);
 		frame->payload.offset = 0;
--- a/libpurple/protocols/oscar/oscar.c	Wed Sep 26 06:56:02 2007 +0000
+++ b/libpurple/protocols/oscar/oscar.c	Fri Oct 12 02:06:38 2007 +0000
@@ -1873,6 +1873,10 @@
 			saved_b16 = purple_buddy_icons_get_checksum_for_user(b);
 
 		if (!b16 || !saved_b16 || strcmp(b16, saved_b16)) {
+			/* Invalidate the old icon for this user */
+			purple_buddy_icons_set_for_user(account, info->sn, NULL, 0, NULL);
+
+			/* Fetch the new icon (if we're not already doing so) */
 			if (g_slist_find_custom(od->requesticon, info->sn,
 					(GCompareFunc)aim_sncmp) == NULL)
 			{
@@ -2180,12 +2184,14 @@
 {
 	PurpleConnection *gc;
 	OscarData *od;
+	PurpleAccount *account;
 	PurpleBuddy *buddy;
 	PurpleGroup *group;
 
 	gc = data->gc;
 	od = gc->proto_data;
-	buddy = purple_find_buddy(purple_connection_get_account(gc), data->name);
+	account = purple_connection_get_account(gc);
+	buddy = purple_find_buddy(account, data->name);
 	if (buddy != NULL)
 		group = purple_buddy_get_group(buddy);
 	else
@@ -2197,7 +2203,19 @@
 				   buddy->name, group->name);
 		aim_ssi_sendauthrequest(od, data->name, msg ? msg : _("Please authorize me so I can add you to my buddy list."));
 		if (!aim_ssi_itemlist_finditem(od->ssi.local, group->name, buddy->name, AIM_SSI_TYPE_BUDDY))
+		{
 			aim_ssi_addbuddy(od, buddy->name, group->name, NULL, purple_buddy_get_alias_only(buddy), NULL, NULL, TRUE);
+
+			/* Mobile users should always be online */
+			if (buddy->name[0] == '+') {
+				purple_prpl_got_user_status(account,
+						purple_buddy_get_name(buddy),
+						OSCAR_STATUS_ID_AVAILABLE, NULL);
+				purple_prpl_got_user_status(account,
+						purple_buddy_get_name(buddy),
+						OSCAR_STATUS_ID_MOBILE, NULL);
+			}
+		}
 	}
 }
 
@@ -4617,12 +4635,16 @@
 
 void
 oscar_add_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group) {
-	OscarData *od = (OscarData *)gc->proto_data;
+	OscarData *od;
+	PurpleAccount *account;
+
+	od = (OscarData *)gc->proto_data;
+	account = purple_connection_get_account(gc);
 
 	if (!aim_snvalid(buddy->name)) {
 		gchar *buf;
 		buf = g_strdup_printf(_("Could not add the buddy %s because the screen name is invalid.  Screen names must be a valid email address, or start with a letter and contain only letters, numbers and spaces, or contain only numbers."), buddy->name);
-		if (!purple_conv_present_error(buddy->name, purple_connection_get_account(gc), buf))
+		if (!purple_conv_present_error(buddy->name, account, buf))
 			purple_notify_error(gc, NULL, _("Unable To Add"), buf);
 		g_free(buf);
 
@@ -4636,6 +4658,16 @@
 		purple_debug_info("oscar",
 				   "ssi: adding buddy %s to group %s\n", buddy->name, group->name);
 		aim_ssi_addbuddy(od, buddy->name, group->name, NULL, purple_buddy_get_alias_only(buddy), NULL, NULL, 0);
+
+		/* Mobile users should always be online */
+		if (buddy->name[0] == '+') {
+			purple_prpl_got_user_status(account,
+					purple_buddy_get_name(buddy),
+					OSCAR_STATUS_ID_AVAILABLE, NULL);
+			purple_prpl_got_user_status(account,
+					purple_buddy_get_name(buddy),
+					OSCAR_STATUS_ID_MOBILE, NULL);
+		}
 	}
 
 	/* XXX - Should this be done from AIM accounts, as well? */
@@ -4951,6 +4983,17 @@
 							g_free(comment);
 						}
 					}
+
+					/* Mobile users should always be online */
+					if (b->name[0] == '+') {
+						purple_prpl_got_user_status(account,
+								purple_buddy_get_name(b),
+								OSCAR_STATUS_ID_AVAILABLE, NULL);
+						purple_prpl_got_user_status(account,
+								purple_buddy_get_name(b),
+								OSCAR_STATUS_ID_MOBILE, NULL);
+					}
+
 					g_free(gname_utf8);
 					g_free(alias_utf8);
 				}
@@ -5141,6 +5184,17 @@
 		purple_debug_info("oscar",
 				   "ssi: adding buddy %s to group %s to local list\n", name, gname_utf8 ? gname_utf8 : _("Orphans"));
 		purple_blist_add_buddy(b, NULL, g, NULL);
+
+		/* Mobile users should always be online */
+		if (b->name[0] == '+') {
+			purple_prpl_got_user_status(account,
+					purple_buddy_get_name(b),
+					OSCAR_STATUS_ID_AVAILABLE, NULL);
+			purple_prpl_got_user_status(account,
+					purple_buddy_get_name(b),
+					OSCAR_STATUS_ID_MOBILE, NULL);
+		}
+
 	}
 
 	g_free(gname_utf8);
--- a/libpurple/protocols/yahoo/yahoo.c	Wed Sep 26 06:56:02 2007 +0000
+++ b/libpurple/protocols/yahoo/yahoo.c	Fri Oct 12 02:06:38 2007 +0000
@@ -862,10 +862,13 @@
 			/* If a Doodle session doesn't exist between this user */
 			if(wb == NULL)
 			{
+				doodle_session *ds;
 				wb = purple_whiteboard_create(gc->account, im->from, DOODLE_STATE_REQUESTED);
-
-				yahoo_doodle_command_send_request(gc, im->from);
-				yahoo_doodle_command_send_ready(gc, im->from);
+				ds = wb->proto_data;
+				ds->imv_key = g_strdup(imv);
+
+				yahoo_doodle_command_send_request(gc, im->from, imv);
+				yahoo_doodle_command_send_ready(gc, im->from, imv);
 			}
 		}
 	}
--- a/libpurple/protocols/yahoo/yahoo_doodle.c	Wed Sep 26 06:56:02 2007 +0000
+++ b/libpurple/protocols/yahoo/yahoo_doodle.c	Fri Oct 12 02:06:38 2007 +0000
@@ -118,19 +118,19 @@
 		/* Insert this 'session' in the list.  At this point, it's only a
 		 * requested session.
 		 */
-		purple_whiteboard_create(account, to, DOODLE_STATE_REQUESTING);
+		wb = purple_whiteboard_create(account, to, DOODLE_STATE_REQUESTING);
 	}
 
 	/* NOTE Perhaps some careful handling of remote assumed established
 	 * sessions
 	 */
 
-	yahoo_doodle_command_send_ready(gc, to);
-	yahoo_doodle_command_send_request(gc, to);
+	yahoo_doodle_command_send_ready(gc, to, DOODLE_IMV_KEY);
+	yahoo_doodle_command_send_request(gc, to, DOODLE_IMV_KEY);
 
 }
 
-static void yahoo_doodle_command_got_request(PurpleConnection *gc, const char *from)
+static void yahoo_doodle_command_got_request(PurpleConnection *gc, const char *from, const char *imv_key)
 {
 	PurpleAccount *account;
 	PurpleWhiteboard *wb;
@@ -147,6 +147,7 @@
 	/* If a session with the remote user doesn't exist */
 	if(wb == NULL)
 	{
+		doodle_session *ds;
 		/* Ask user if they wish to accept the request for a doodle session */
 		/* TODO Ask local user to start Doodle session with remote user */
 		/* NOTE This if/else statement won't work right--must use dialog
@@ -160,9 +161,11 @@
 		dialog_message, NULL, NULL, NULL);
 		*/
 
-		purple_whiteboard_create(account, from, DOODLE_STATE_REQUESTED);
+		wb = purple_whiteboard_create(account, from, DOODLE_STATE_REQUESTED);
+		ds = wb->proto_data;
+		ds->imv_key = g_strdup(imv_key);
 
-		yahoo_doodle_command_send_ready(gc, from);
+		yahoo_doodle_command_send_ready(gc, from, imv_key);
 	}
 
 	/* TODO Might be required to clear the canvas of an existing doodle
@@ -170,7 +173,7 @@
 	 */
 }
 
-static void yahoo_doodle_command_got_ready(PurpleConnection *gc, const char *from)
+static void yahoo_doodle_command_got_ready(PurpleConnection *gc, const char *from, const char *imv_key)
 {
 	PurpleAccount *account;
 	PurpleWhiteboard *wb;
@@ -189,11 +192,15 @@
 
 	if(wb->state == DOODLE_STATE_REQUESTING)
 	{
+		doodle_session *ds = wb->proto_data;
 		purple_whiteboard_start(wb);
 
 		wb->state = DOODLE_STATE_ESTABLISHED;
 
-		yahoo_doodle_command_send_confirm(gc, from);
+		yahoo_doodle_command_send_confirm(gc, from, imv_key);
+		/* Let's steal the imv_key and reuse it */
+		g_free(ds->imv_key);
+		ds->imv_key = g_strdup(imv_key);
 	}
 	else if(wb->state == DOODLE_STATE_ESTABLISHED)
 	{
@@ -208,7 +215,7 @@
 	else if(wb->state == DOODLE_STATE_REQUESTED)
 	{
 		/* purple_whiteboard_start(wb); */
-		yahoo_doodle_command_send_ready(gc, from);
+		yahoo_doodle_command_send_ready(gc, from, imv_key);
 	}
 }
 
@@ -296,14 +303,14 @@
 
 
 static void
-yahoo_doodle_command_got_extra(PurpleConnection *gc, const char *from, const char *message)
+yahoo_doodle_command_got_extra(PurpleConnection *gc, const char *from, const char *message, const char *imv_key)
 {
 	purple_debug_info("yahoo", "doodle: Got Extra (%s)\n", from);
 
 	/* I do not like these 'extra' features, so I'll only handle them in one
 	 * way, which is returning them with the command/packet to turn them off
 	 */
-	yahoo_doodle_command_send_extra(gc, from, DOODLE_EXTRA_NONE);
+	yahoo_doodle_command_send_extra(gc, from, DOODLE_EXTRA_NONE, imv_key);
 }
 
 static void yahoo_doodle_command_got_confirm(PurpleConnection *gc, const char *from)
@@ -399,34 +406,34 @@
 	yahoo_packet_send_and_free(pkt, yd);
 }
 
-void yahoo_doodle_command_send_ready(PurpleConnection *gc, const char *to)
+void yahoo_doodle_command_send_ready(PurpleConnection *gc, const char *to, const char *imv_key)
 {
-	yahoo_doodle_command_send_generic("Ready", gc, to, "1", DOODLE_CMD_READY, NULL, "1");
+	yahoo_doodle_command_send_generic("Ready", gc, to, "1", DOODLE_CMD_READY, imv_key, "1");
 }
 
-void yahoo_doodle_command_send_request(PurpleConnection *gc, const char *to)
+void yahoo_doodle_command_send_request(PurpleConnection *gc, const char *to, const char *imv_key)
 {
-	yahoo_doodle_command_send_generic("Request", gc, to, "", DOODLE_CMD_REQUEST, NULL, "0");
+	yahoo_doodle_command_send_generic("Request", gc, to, "", DOODLE_CMD_REQUEST, imv_key, "0");
 }
 
-void yahoo_doodle_command_send_draw(PurpleConnection *gc, const char *to, const char *message)
+void yahoo_doodle_command_send_draw(PurpleConnection *gc, const char *to, const char *message, const char *imv_key)
 {
-	yahoo_doodle_command_send_generic("Draw", gc, to, message, DOODLE_CMD_DRAW, NULL, "1");
+	yahoo_doodle_command_send_generic("Draw", gc, to, message, DOODLE_CMD_DRAW, imv_key, "1");
 }
 
-void yahoo_doodle_command_send_clear(PurpleConnection *gc, const char *to)
+void yahoo_doodle_command_send_clear(PurpleConnection *gc, const char *to, const char *imv_key)
 {
-	yahoo_doodle_command_send_generic("Clear", gc, to, " ", DOODLE_CMD_CLEAR, NULL, "1");
+	yahoo_doodle_command_send_generic("Clear", gc, to, " ", DOODLE_CMD_CLEAR, imv_key, "1");
 }
 
-void yahoo_doodle_command_send_extra(PurpleConnection *gc, const char *to, const char *message)
+void yahoo_doodle_command_send_extra(PurpleConnection *gc, const char *to, const char *message, const char *imv_key)
 {
-	yahoo_doodle_command_send_generic("Extra", gc, to, message, DOODLE_CMD_EXTRA, NULL, "1");
+	yahoo_doodle_command_send_generic("Extra", gc, to, message, DOODLE_CMD_EXTRA, imv_key, "1");
 }
 
-void yahoo_doodle_command_send_confirm(PurpleConnection *gc, const char *to)
+void yahoo_doodle_command_send_confirm(PurpleConnection *gc, const char *to, const char *imv_key)
 {
-	yahoo_doodle_command_send_generic("Confirm", gc, to, "1", DOODLE_CMD_CONFIRM, NULL, "1");
+	yahoo_doodle_command_send_generic("Confirm", gc, to, "1", DOODLE_CMD_CONFIRM, imv_key, "1");
 }
 
 void yahoo_doodle_command_send_shutdown(PurpleConnection *gc, const char *to)
@@ -450,12 +457,14 @@
 void yahoo_doodle_end(PurpleWhiteboard *wb)
 {
 	PurpleConnection *gc = purple_account_get_connection(wb->account);
+	doodle_session *ds = wb->proto_data;
 
 	/* g_debug_debug("yahoo", "doodle: yahoo_doodle_end()\n"); */
 
 	if (gc && wb->state != DOODLE_STATE_CANCELED)
 		yahoo_doodle_command_send_shutdown(gc, wb->who);
 
+	g_free(ds->imv_key);
 	g_free(wb->proto_data);
 }
 
@@ -492,13 +501,14 @@
 	g_return_if_fail(draw_list != NULL);
 
 	message = yahoo_doodle_build_draw_string(ds, draw_list);
-	yahoo_doodle_command_send_draw(wb->account->gc, wb->who, message);
+	yahoo_doodle_command_send_draw(wb->account->gc, wb->who, message, ds->imv_key);
 	g_free(message);
 }
 
 void yahoo_doodle_clear(PurpleWhiteboard *wb)
 {
-	yahoo_doodle_command_send_clear(wb->account->gc, wb->who);
+	doodle_session *ds = wb->proto_data;
+	yahoo_doodle_command_send_clear(wb->account->gc, wb->who, ds->imv_key);
 }
 
 
@@ -551,14 +561,14 @@
 
 void yahoo_doodle_get_brush(const PurpleWhiteboard *wb, int *size, int *color)
 {
-	doodle_session *ds = (doodle_session *)wb->proto_data;
+	doodle_session *ds = wb->proto_data;
 	*size = ds->brush_size;
 	*color = ds->brush_color;
 }
 
 void yahoo_doodle_set_brush(PurpleWhiteboard *wb, int size, int color)
 {
-	doodle_session *ds = (doodle_session *)wb->proto_data;
+	doodle_session *ds = wb->proto_data;
 	ds->brush_size = size;
 	ds->brush_color = color;
 
@@ -567,7 +577,7 @@
 }
 
 void yahoo_doodle_process(PurpleConnection *gc, const char *me, const char *from,
-						  const char *command, const char *message)
+						  const char *command, const char *message, const char *imv_key)
 {
 	if(!command)
 		return;
@@ -576,11 +586,11 @@
 	switch(atoi(command))
 	{
 		case DOODLE_CMD_REQUEST:
-			yahoo_doodle_command_got_request(gc, from);
+			yahoo_doodle_command_got_request(gc, from, imv_key);
 			break;
 
 		case DOODLE_CMD_READY:
-			yahoo_doodle_command_got_ready(gc, from);
+			yahoo_doodle_command_got_ready(gc, from, imv_key);
 			break;
 
 		case DOODLE_CMD_CLEAR:
@@ -592,7 +602,7 @@
 			break;
 
 		case DOODLE_CMD_EXTRA:
-			yahoo_doodle_command_got_extra(gc, from, message);
+			yahoo_doodle_command_got_extra(gc, from, message, imv_key);
 			break;
 
 		case DOODLE_CMD_CONFIRM:
--- a/libpurple/protocols/yahoo/yahoo_doodle.h	Wed Sep 26 06:56:02 2007 +0000
+++ b/libpurple/protocols/yahoo/yahoo_doodle.h	Fri Oct 12 02:06:38 2007 +0000
@@ -31,7 +31,7 @@
 #include "whiteboard.h"
 #include "cmds.h"
 
-#define DOODLE_IMV_KEY "doodle;103"
+#define DOODLE_IMV_KEY "doodle;106"
 
 /******************************************************************************
  * Defines
@@ -94,6 +94,7 @@
 {
 	int brush_size;  /* Size of drawing brush */
 	int brush_color; /* Color of drawing brush */
+	gchar *imv_key;
 } doodle_session;
 
 /******************************************************************************
@@ -104,17 +105,17 @@
 									   char **error, void *data);
 
 void yahoo_doodle_process(PurpleConnection *gc, const char *me, const char *from,
-						  const char *command, const char *message);
+						  const char *command, const char *message, const char *imv_key);
 void yahoo_doodle_initiate(PurpleConnection *gc, const char *to);
 
 void yahoo_doodle_command_got_shutdown(PurpleConnection *gc, const char *from);
 
-void yahoo_doodle_command_send_request(PurpleConnection *gc, const char *to);
-void yahoo_doodle_command_send_ready(PurpleConnection *gc, const char *to);
-void yahoo_doodle_command_send_draw(PurpleConnection *gc, const char *to, const char *message);
-void yahoo_doodle_command_send_clear(PurpleConnection *gc, const char *to);
-void yahoo_doodle_command_send_extra(PurpleConnection *gc, const char *to, const char *message);
-void yahoo_doodle_command_send_confirm(PurpleConnection *gc, const char *to);
+void yahoo_doodle_command_send_request(PurpleConnection *gc, const char *to, const char *imv_key);
+void yahoo_doodle_command_send_ready(PurpleConnection *gc, const char *to, const char *imv_key);
+void yahoo_doodle_command_send_draw(PurpleConnection *gc, const char *to, const char *message, const char *imv_key);
+void yahoo_doodle_command_send_clear(PurpleConnection *gc, const char *to, const char *imv_key);
+void yahoo_doodle_command_send_extra(PurpleConnection *gc, const char *to, const char *message, const char *imv_key);
+void yahoo_doodle_command_send_confirm(PurpleConnection *gc, const char *to, const char *imv_key);
 void yahoo_doodle_command_send_shutdown(PurpleConnection *gc, const char *to);
 
 void yahoo_doodle_start(PurpleWhiteboard *wb);
--- a/libpurple/protocols/yahoo/yahoo_filexfer.c	Wed Sep 26 06:56:02 2007 +0000
+++ b/libpurple/protocols/yahoo/yahoo_filexfer.c	Fri Oct 12 02:06:38 2007 +0000
@@ -452,26 +452,29 @@
 	{
 		struct yahoo_pair *pair = l->data;
 
-		if(pair->key == 5)         /* Get who the packet is for */
+		switch(pair->key) {
+		case 5:         /* Get who the packet is for */
 			me = pair->value;
-
-		if(pair->key == 4)         /* Get who the packet is from */
+			break;
+		case 4:         /* Get who the packet is from */
 			from = pair->value;
-
-		if(pair->key == 49)        /* Get the type of service */
+			break;
+		case 49:        /* Get the type of service */
 			service = pair->value;
-
-		if(pair->key == 14)        /* Get the 'message' of the packet */
+			break;
+		case 14:        /* Get the 'message' of the packet */
 			message = pair->value;
-
-		if(pair->key == 13)        /* Get the command associated with this packet */
+			break;
+		case 13:        /* Get the command associated with this packet */
 			command = pair->value;
-
-		if(pair->key == 63)        /* IMVironment name and version */
+			break;
+		case 63:        /* IMVironment name and version */
 			imv = pair->value;
-
-		if(pair->key == 64)        /* Not sure, but it does vary with initialization of Doodle */
+			break;
+		case 64:        /* Not sure, but it does vary with initialization of Doodle */
 			unknown = pair->value; /* So, I'll keep it (for a little while atleast) */
+			break;
+		}
 
 		l = l->next;
 	}
@@ -481,7 +484,7 @@
 	{
 		/* Check for a Doodle packet and handle it accordingly */
 		if(strstr(imv, "doodle;") != NULL)
-			yahoo_doodle_process(gc, me, from, command, message);
+			yahoo_doodle_process(gc, me, from, command, message, imv);
 
 		/* If an IMVIRONMENT packet comes without a specific imviroment name */
 		if(!strcmp(imv, ";0"))
@@ -513,24 +516,35 @@
 	for (l = pkt->hash; l; l = l->next) {
 		struct yahoo_pair *pair = l->data;
 
-		if (pair->key == 4)
+		switch (pair->key) {
+		case 4:
 			from = pair->value;
-		if (pair->key == 5)
+			break;
+		case 5:
 			to = pair->value;
-		if (pair->key == 14)
+			break;
+		case 14:
 			msg = pair->value;
-		if (pair->key == 20)
+			break;
+		case 20:
 			url = pair->value;
-		if (pair->key == 38)
+			break;
+		case 38:
 			expires = strtol(pair->value, NULL, 10);
-		if (pair->key == 27)
+			break;
+		case 27:
 			filename = pair->value;
-		if (pair->key == 28)
+			break;
+		case 28:
 			filesize = atol(pair->value);
-		if (pair->key == 49)
+			break;
+		case 49:
 			service = pair->value;
-		if (pair->key == 63)
+			break;
+		case 63:
 			imv = pair->value;
+			break;
+		}
 	}
 
 	/*
--- a/libpurple/purple-remote	Wed Sep 26 06:56:02 2007 +0000
+++ b/libpurple/purple-remote	Fri Oct 12 02:06:38 2007 +0000
@@ -9,7 +9,12 @@
 
 xml.dom.minidom.Element.all   = xml.dom.minidom.Element.getElementsByTagName
 
-obj = dbus.SessionBus().get_object("im.pidgin.purple.PurpleService", "/im/pidgin/purple/PurpleObject")
+obj = None
+try:
+    obj = dbus.SessionBus().get_object("im.pidgin.purple.PurpleService", "/im/pidgin/purple/PurpleObject")
+except:
+    pass
+
 purple = dbus.Interface(obj, "im.pidgin.purple.PurpleInterface")
 
 class CheckedObject:
@@ -212,7 +217,10 @@
 
 if len(sys.argv) == 1:
     show_help()
-
+elif (obj == None):
+    print "No existing libpurple instance detected."
+    sys.exit(1);
+    
 for arg in sys.argv[1:]:
     output = execute(arg)
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/purple.h	Fri Oct 12 02:06:38 2007 +0000
@@ -0,0 +1,92 @@
+/**
+ * @file purple.h  Header files and defines
+ * This file contains all the necessary preprocessor directives to include
+ * libpurple's headers and other preprocessor directives required for plugins
+ * or UIs to build.  Inlcuding this file eliminates the need to directly
+ * include any other libpurple files.  It will still be necessary for plugins
+ * to define @c PURPLE_PLUGINS before including this header.
+ *
+ * @ingroup core libpurple
+ */
+
+/* purple
+ *
+ * Purple is the legal property of its developers, whose names are too numerous
+ * to list here.  Please refer to the COPYRIGHT file distributed with this
+ * source distribution.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
+ */
+#ifndef _PURPLE_H
+#define _PURPLE_H
+
+#ifndef G_GNUC_NULL_TERMINATED
+#	if     __GNUC__ >= 4
+#		define G_GNUC_NULL_TERMINATED __attribute__((__sentinel__))
+#	else
+#		define G_GNUC_NULL_TERMINATED
+#	endif
+#endif
+
+#include <account.h>
+#include <accountopt.h>
+#include <blist.h>
+#include <buddyicon.h>
+#include <certificate.h>
+#include <cipher.h>
+#include <circbuffer.h>
+#include <cmds.h>
+#include <connection.h>
+#include <conversation.h>
+#include <core.h>
+#include <debug.h>
+#include <desktopitem.h>
+#include <dnsquery.h>
+#include <dnssrv.h>
+#include <eventloop.h>
+#include <ft.h>
+#include <idle.h>
+#include <imgstore.h>
+#include <log.h>
+#include <mime.h>
+#include <nat-pmp.h>
+#include <network.h>
+#include <ntlm.h>
+#include <notify.h>
+#include <plugin.h>
+#include <pluginpref.h>
+#include <pounce.h>
+#include <prefs.h>
+#include <privacy.h>
+#include <proxy.h>
+#include <prpl.h>
+#include <request.h>
+#include <roomlist.h>
+#include <savedstatuses.h>
+#include <server.h>
+#include <signals.h>
+#include <status.h>
+#include <stringref.h>
+#include <stun.h>
+#include <sound.h>
+#include <sslconn.h>
+#include <upnp.h>
+#include <util.h>
+#include <value.h>
+#include <version.h>
+#include <xmlnode.h>
+#include <whiteboard.h>
+
+#endif
--- a/libpurple/request.c	Wed Sep 26 06:56:02 2007 +0000
+++ b/libpurple/request.c	Fri Oct 12 02:06:38 2007 +0000
@@ -377,6 +377,13 @@
 		g_hash_table_destroy(field->u.list.item_data);
 		g_hash_table_destroy(field->u.list.selected_table);
 	}
+	else if (field->type == PURPLE_REQUEST_FIELD_BLIST)
+	{
+		if (field->u.blist.default_nodes)
+			g_list_free(field->u.blist.default_nodes);
+		if (field->u.blist.selecteds)
+			g_list_free(field->u.blist.selecteds);
+	}
 
 	g_free(field);
 }
@@ -1133,6 +1140,85 @@
 
 /* -- */
 
+PurpleRequestField *purple_request_field_blist_nodes_new(const char *id,
+		const char *text, PurpleRequestBlistFlags flags, GList *selected)
+{
+	PurpleRequestField *field;
+
+	g_return_val_if_fail(id   != NULL, NULL);
+	g_return_val_if_fail(text != NULL, NULL);
+
+	field = purple_request_field_new(id, text, PURPLE_REQUEST_FIELD_BLIST);
+
+	field->u.blist.flags = flags;
+	field->u.blist.default_nodes = selected;
+	purple_request_field_blist_set_selection_list(field, selected);
+
+	return field;
+}
+
+PurpleFilterBlistFunc
+purple_request_field_blist_set_filter(PurpleRequestField *field, PurpleFilterBlistFunc filter)
+{
+	PurpleFilterBlistFunc old;
+	g_return_val_if_fail(field != NULL, NULL);
+	g_return_val_if_fail(field->type == PURPLE_REQUEST_FIELD_BLIST, NULL);
+	old = field->u.blist.filter;
+	field->u.blist.filter = filter;
+	return old;
+}
+
+PurpleFilterBlistFunc
+purple_request_field_blist_get_filter(const PurpleRequestField *field)
+{
+	g_return_val_if_fail(field != NULL, NULL);
+	g_return_val_if_fail(field->type == PURPLE_REQUEST_FIELD_BLIST, NULL);
+	return field->u.blist.filter;
+}
+
+GList *purple_request_field_blist_get_selection_list(const PurpleRequestField *field)
+{
+	g_return_val_if_fail(field != NULL, NULL);
+	g_return_val_if_fail(field->type == PURPLE_REQUEST_FIELD_BLIST, NULL);
+	return field->u.blist.selecteds;
+}
+
+gboolean purple_request_field_blist_add(PurpleRequestField *field, PurpleBlistNode *node)
+{
+	g_return_val_if_fail(field != NULL, FALSE);
+	g_return_val_if_fail(field->type == PURPLE_REQUEST_FIELD_BLIST, FALSE);
+	if (!g_list_find(field->u.blist.selecteds, node)) {
+		field->u.blist.selecteds = g_list_append(field->u.blist.selecteds, node);
+		return TRUE;
+	} else {
+		return FALSE;
+	}
+}
+
+gboolean purple_request_field_blist_remove(PurpleRequestField *field, PurpleBlistNode *node)
+{
+	GList *search;
+	g_return_val_if_fail(field != NULL, FALSE);
+	g_return_val_if_fail(field->type == PURPLE_REQUEST_FIELD_BLIST, FALSE);
+	if ((search = g_list_find(field->u.blist.selecteds, node)) != NULL) {
+		field->u.blist.selecteds = g_list_delete_link(field->u.blist.selecteds, search);
+		return TRUE;
+	} else {
+		return FALSE;
+	}
+}
+
+void purple_request_field_blist_set_selection_list(PurpleRequestField *field, GList *selecteds)
+{
+	g_return_if_fail(field != NULL);
+	g_return_if_fail(field->type == PURPLE_REQUEST_FIELD_BLIST);
+	if (field->u.blist.selecteds)
+		g_list_free(field->u.blist.selecteds);
+	field->u.blist.selecteds = selecteds;
+}
+
+/* -- */
+
 void *
 purple_request_input(void *handle, const char *title, const char *primary,
 				   const char *secondary, const char *default_value,
--- a/libpurple/request.h	Wed Sep 26 06:56:02 2007 +0000
+++ b/libpurple/request.h	Fri Oct 12 02:06:38 2007 +0000
@@ -61,7 +61,8 @@
 	PURPLE_REQUEST_FIELD_LIST,
 	PURPLE_REQUEST_FIELD_LABEL,
 	PURPLE_REQUEST_FIELD_IMAGE,
-	PURPLE_REQUEST_FIELD_ACCOUNT
+	PURPLE_REQUEST_FIELD_ACCOUNT,
+	PURPLE_REQUEST_FIELD_BLIST,
 
 } PurpleRequestFieldType;
 
@@ -94,6 +95,17 @@
 } PurpleRequestFieldGroup;
 
 /**
+ * Flags that can be used for Buddylist Fields.
+ */
+typedef enum
+{
+	PURPLE_REQUEST_BLIST_FLAG_BUDDY         = 0x01,  /**< Include buddies in the list. */
+	PURPLE_REQUEST_BLIST_FLAG_CHAT          = 0x02,  /**< Include chats in the list. */
+	PURPLE_REQUEST_BLIST_FLAG_GROUP         = 0x04,  /**< Include groups in the list. */
+	PURPLE_REQUEST_BLIST_FLAG_ALLOW_OFFLINE = 0x08,  /**< Include offline buddies in the list. */
+} PurpleRequestBlistFlags;
+
+/**
  * A request field.
  */
 typedef struct
@@ -172,6 +184,14 @@
 			gsize size;
 		} image;
 
+		struct
+		{
+			GList *default_nodes;
+			PurpleRequestBlistFlags flags;
+			GList *selecteds;
+			PurpleFilterBlistFunc filter;
+		} blist;
+
 	} u;
 
 	void *ui_data;
@@ -1151,6 +1171,84 @@
 /*@}*/
 
 /**************************************************************************/
+/** @name Buddylist Field API                                             */
+/**************************************************************************/
+/*@{*/
+
+/**
+ * Creates a buddylist field.
+ *
+ * @param id       The field ID.
+ * @param text     The label for the field.
+ * @param flag     Flags dictating what kind of blist nodes should be
+ *                 included in the request field.
+ * @param selected A list of PurpleBlistNode's to select by default, or @c NULL.
+ *
+ * @return  The new field.
+ */
+PurpleRequestField *purple_request_field_blist_nodes_new(const char *id, const char *text,
+		PurpleRequestBlistFlags flag, GList *selected);
+
+/**
+ * Set a filter for the request field.
+ *
+ * @param field   The request field.
+ * @param filter  The filter function.
+ *
+ * @return  The old filter function, or @c NULL if there was none.
+ */
+PurpleFilterBlistFunc purple_request_field_blist_set_filter(PurpleRequestField *field, PurpleFilterBlistFunc filter);
+
+/**
+ * Get the filter function for the request field.
+ *
+ * @param field  The request field.
+ *
+ * @return  The filter function, or @c NULL if there isn't any.
+ */
+PurpleFilterBlistFunc purple_request_field_blist_get_filter(const PurpleRequestField *field);
+
+/**
+ * Add a PurpleBlistNode to the selected list.
+ *
+ * @param field  The request field.
+ * @param node   The buddylist node to add to the list.
+ *
+ * @return  @c TRUE if the node is added to the list, @c FALSE if it was already in the list.
+ */
+gboolean purple_request_field_blist_add(PurpleRequestField *field, PurpleBlistNode *node);
+
+/**
+ * Remove a PurpleBlistNode from the selected list.
+ *
+ * @param field   The request field.
+ * @param node    The buddylist node to remove from the list.
+ *
+ * @return @c TRUE if the node is removed from the list, @c FALSE if the node is not in the list.
+ */
+gboolean purple_request_field_blist_remove(PurpleRequestField *field, PurpleBlistNode *node);
+
+/**
+ * Set the list of selected nodes in the request field.
+ *
+ * @param field      The request field.
+ * @param selecteds  The list of selected PurpleBlistNode's. Note that the request field
+ *                   becomes the owner of the list, and so the caller should not modify it.
+ */
+void purple_request_field_blist_set_selection_list(PurpleRequestField *field, GList *selecteds);
+
+/**
+ * Get a list of the selected buddylist nodes.
+ *
+ * @param field  The request field.
+ *
+ * @return A GList of PurpleBlistNode's.
+ */
+GList *purple_request_field_blist_get_selection_list(const PurpleRequestField *field);
+
+/*@}*/
+
+/**************************************************************************/
 /** @name Request API                                                     */
 /**************************************************************************/
 /*@{*/
--- a/libpurple/signals.c	Wed Sep 26 06:56:02 2007 +0000
+++ b/libpurple/signals.c	Fri Oct 12 02:06:38 2007 +0000
@@ -794,6 +794,19 @@
 		*return_val = GINT_TO_POINTER(ret_val);
 }
 
+void
+purple_marshal_INT__POINTER_POINTER(PurpleCallback cb, va_list args, void *data,
+                                      void **return_val)
+{
+	gint ret_val;
+	void *arg1 = va_arg(args, void *);
+	void *arg2 = va_arg(args, void *);
+
+	ret_val = ((gint (*)(void *, void *, void *))cb)(arg1, arg2, data);
+
+	if (return_val != NULL)
+		*return_val = GINT_TO_POINTER(ret_val);
+}
 
 void
 purple_marshal_INT__POINTER_POINTER_POINTER_POINTER_POINTER(
--- a/libpurple/signals.h	Wed Sep 26 06:56:02 2007 +0000
+++ b/libpurple/signals.h	Fri Oct 12 02:06:38 2007 +0000
@@ -307,6 +307,8 @@
 		PurpleCallback cb, va_list args, void *data, void **return_val);
 void purple_marshal_INT__INT_INT(
 		PurpleCallback cb, va_list args, void *data, void **return_val);
+void purple_marshal_INT__POINTER_POINTER(
+		PurpleCallback cb, va_list args, void *data, void **return_val);
 void purple_marshal_INT__POINTER_POINTER_POINTER_POINTER_POINTER(
 		PurpleCallback cb, va_list args, void *data, void **return_val);
 
--- a/pidgin/Makefile.am	Wed Sep 26 06:56:02 2007 +0000
+++ b/pidgin/Makefile.am	Fri Oct 12 02:06:38 2007 +0000
@@ -29,6 +29,7 @@
 		win32/nsis/langmacros.nsh \
 		win32/nsis/translations/afrikaans.nsh \
 		win32/nsis/translations/albanian.nsh \
+		win32/nsis/translations/arabic.nsh \
 		win32/nsis/translations/basque.nsh \
 		win32/nsis/translations/bulgarian.nsh \
 		win32/nsis/translations/catalan.nsh \
--- a/pidgin/eggtrayicon.c	Wed Sep 26 06:56:02 2007 +0000
+++ b/pidgin/eggtrayicon.c	Fri Oct 12 02:06:38 2007 +0000
@@ -23,7 +23,6 @@
 
 #include "eggtrayicon.h"
 
-#include <gdk/gdkx.h>
 #include <X11/Xatom.h>
 
 #define _(x) x
--- a/pidgin/gtkconv.c	Wed Sep 26 06:56:02 2007 +0000
+++ b/pidgin/gtkconv.c	Fri Oct 12 02:06:38 2007 +0000
@@ -6835,7 +6835,9 @@
 
 	event = gtk_event_box_new();
 	gtk_container_add(GTK_CONTAINER(gtkconv->u.im->icon_container), event);
+#if GTK_CHECK_VERSION(2,4,0)
 	gtk_event_box_set_visible_window(GTK_EVENT_BOX(event), FALSE);
+#endif
 	gtk_widget_add_events(event,
                               GDK_POINTER_MOTION_MASK | GDK_LEAVE_NOTIFY_MASK);
 	g_signal_connect(G_OBJECT(event), "button-press-event",
@@ -7379,6 +7381,17 @@
 	pidgin_conv_update_fields(conv, PIDGIN_CONV_TOPIC);
 }
 
+/* Message history stuff */
+
+/* Compare two PurpleConvMessage's, according to time in ascending order. */
+static int
+message_compare(gconstpointer p1, gconstpointer p2)
+{
+	const PurpleConvMessage *m1 = p1, *m2 = p2;
+	return (m1->when > m2->when);
+}
+
+/* Adds some message history to the gtkconv. This happens in a idle-callback. */
 static gboolean
 add_message_history_to_gtkconv(gpointer data)
 {
@@ -7386,49 +7399,106 @@
 	int count = 0;
 	int timer = gtkconv->attach.timer;
 	time_t when = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(gtkconv->entry), "attach-start-time"));
+	gboolean im = (gtkconv->active_conv->type == PURPLE_CONV_TYPE_IM);
 
 	gtkconv->attach.timer = 0;
 	while (gtkconv->attach.current && count < 100) {  /* XXX: 100 is a random value here */
 		PurpleConvMessage *msg = gtkconv->attach.current->data;
-		if (when && when < msg->when) {