changeset 25553:7f8cf35fc99b

propagate from branch 'im.pidgin.pidgin' (head c323420a0b3b17b1eba64763c01038ddf05ff0c2) to branch 'im.pidgin.pidgin.yaz' (head 3dc04de4d17fdd9333be36950cfe86115e1b48a5)
author Yoshiki Yazawa <yaz@honeyplanet.jp>
date Tue, 28 Aug 2007 09:20:27 +0000
parents 5e76304ebcc8 (current diff) c88e64f1ed76 (diff)
children ee5551542eee
files configure.ac libpurple/conversation.c libpurple/protocols/irc/irc.c libpurple/protocols/jabber/jabber.c libpurple/protocols/msn/msn.c libpurple/protocols/msn/switchboard.c libpurple/protocols/yahoo/yahoo.c libpurple/util.c libpurple/util.h pidgin/gtkconv.c pidgin/gtkimhtmltoolbar.h pidgin/gtkmain.c pidgin/gtkprefs.c
diffstat 90 files changed, 7540 insertions(+), 1867 deletions(-) [+]
line wrap: on
line diff
--- a/.mtn-ignore	Sat Aug 25 08:42:33 2007 +0000
+++ b/.mtn-ignore	Tue Aug 28 09:20:27 2007 +0000
@@ -33,7 +33,7 @@
 pidgin-.*.tar.gz
 pidgin-.*.tar.bz2
 pidgin/pidgin$
-pidgin/pixmaps/emotes/default/22/theme
+pidgin/pixmaps/emotes/default/24/theme
 pidgin/pixmaps/emotes/none/theme
 pidgin/plugins/musicmessaging/music-messaging-bindings.c
 pidgin/plugins/perl/common/Makefile.PL$
--- a/COPYRIGHT	Sat Aug 25 08:42:33 2007 +0000
+++ b/COPYRIGHT	Tue Aug 28 09:20:27 2007 +0000
@@ -156,12 +156,14 @@
 Casey Harkins
 Andy Harrison
 Andrew Hart (arhart)
+Anders Hasselqvist
 Rene Hausleitner
 Will Hawkins
 G. Sumner Hayes
 Michael R. Head
 Nick Hebner
 Mike Heffner
+Justin Heiner
 Benjamin Herrenschmidt
 Fernando Herrera
 hjheins
@@ -305,6 +307,7 @@
 Bob Rossi
 Jason Roth
 Jean-Francois Roy
+Peter Ruibal
 Sam S.
 Pradyumna Sampath
 Arvind Samptur
--- a/ChangeLog	Sat Aug 25 08:42:33 2007 +0000
+++ b/ChangeLog	Tue Aug 28 09:20:27 2007 +0000
@@ -1,9 +1,20 @@
 Pidgin and Finch: The Pimpin' Penguin IM Clients That're Good for the Soul
 
-version 2.1.2:
+version 2.2.0:
+	Libpurple:
 	* New protocol plugin: MySpaceIM (Jeff Connelly, Google Summer of
 	  Code)
 
+	Pidgin:
+	* Insert Horizontal Rules and Strikethrough text from toolbar
+	* Option to show protocol icons in the buddy list, from the
+	  Buddies > Show menu (Justin Heiner)
+	* Ability to build with native, non-X11 GTK+ on OSX (Anders
+   	  Hasselqvist)
+
+	Finch:
+	* Per-conversation mute and logging options (accessible from the menu)
+
 version 2.1.1 (08/20/2007):
 	Yahoo:
 	* Added an account action to open your inbox in the yahoo prpl.
--- a/ChangeLog.API	Sat Aug 25 08:42:33 2007 +0000
+++ b/ChangeLog.API	Tue Aug 28 09:20:27 2007 +0000
@@ -6,6 +6,18 @@
 		* pidgin_set_accessible_relations, sets up label-for and labelled-by
 		  ATK relations (broken out from pidgin_set_accessible_label)
 
+	Finch:
+		Added:
+		* finch_sound_is_enabled
+		* The reserved field in the FinchConv is now used to store information
+		  about the conversation (using FinchConversationFlag)
+
+		libgnt:
+		* gnt_slider_set_small_step, gnt_slider_set_large_step to allow more
+		  fine tuned updates of a GntSlider
+		* gnt_util_parse_xhtml_to_textview to parse XHTML strings in a
+		  GntTextView (this works only if libxml2 is available)
+
 Version 2.1.1 (08/20/2007):
 	libpurple:
 		Changed:
--- a/Makefile.mingw	Sat Aug 25 08:42:33 2007 +0000
+++ b/Makefile.mingw	Tue Aug 28 09:20:27 2007 +0000
@@ -51,6 +51,7 @@
 	silc.dll \
 	silcclient.dll \
 	softokn3.dll \
+	smime3.dll \
 	ssl3.dll
 
 #build an expression for `find` to use to ignore the above files
--- a/configure.ac	Sat Aug 25 08:42:33 2007 +0000
+++ b/configure.ac	Tue Aug 28 09:20:27 2007 +0000
@@ -275,6 +275,8 @@
 AC_SUBST(GLIB_CFLAGS)
 AC_SUBST(GLIB_LIBS)
 
+AC_ARG_WITH(x, [],
+	with_x="$withval", with_x="yes")
 AC_ARG_ENABLE(gtkui, [AC_HELP_STRING([--disable-gtkui],
 		[compile without GTK+ user interface])],
 	enable_gtkui="$enableval", enable_gtkui="yes")
@@ -309,7 +311,10 @@
 	[AC_HELP_STRING([--disable-cap],
 		[compile without Contact Availability Prediction plugin])],
 	enable_cap="$enableval", enable_cap="yes")
-
+AC_ARG_ENABLE(gestures,
+	[AC_HELP_STRING([--disable-gestures],
+		[compile without the gestures plugin])],
+	enable_gestures="$enableval", enable_gestures="yes")
 
 AC_PATH_XTRA
 # We can't assume that $x_libraries will be set, because autoconf does not
@@ -343,50 +348,79 @@
 			AC_DEFINE(HAVE_PANGO14, 1, [Define if we have Pango 1.4 or newer.]),:)
 
 	dnl #######################################################################
+	dnl # Check if we should compile with X support
+	dnl #######################################################################
+	if test "x$with_x" = "xyes" ; then
+		PKG_CHECK_MODULES(X11, x11,
+			[AC_DEFINE(HAVE_X, 1, [Define to 1 if you have X11])],
+			[AC_MSG_RESULT(no)
+			with_x=no])
+		AC_SUBST(X11_LIBS)
+		AC_SUBST(X11_CFLAGS)
+	fi
+	
+	dnl #######################################################################
 	dnl # Check for XScreenSaver
 	dnl #######################################################################
 	if test "x$enable_screensaver" = "xyes" ; then
-		old_LIBS="$LIBS"
-		LIBS="$LIBS $GTK_LIBS $x_libpath_add"
-		XSS_LIBS=""
-		XSS_HEADERS=""
-		AC_CHECK_LIB(Xext, XScreenSaverRegister,[XSS_LIBS="$X_LIBS $X_PRE_LIBS -lX11 -lXext $X_EXTRA_LIBS"],[],[-lX11 -lXext -lm])
-		AC_CHECK_LIB(Xss, XScreenSaverRegister,[XSS_LIBS="$X_LIBS $X_PRE_LIBS -lX11 -lXext $X_LIBS $X_EXTRA_LIBS -lXss"],[],[-lX11 -lXext -lm])
-		if test "x$XSS_LIBS" != "x"; then
-			oldCPPFLAGS="$CPPFLAGS"
-			CPPFLAGS="$CPPFLAGS $x_incpath_add"
-			AC_TRY_COMPILE([
+		if test "x$with_x" = "xyes" ; then
+			old_LIBS="$LIBS"
+			LIBS="$LIBS $GTK_LIBS $x_libpath_add"
+			XSS_LIBS=""
+			XSS_HEADERS=""
+			AC_CHECK_LIB(Xext, XScreenSaverRegister,[XSS_LIBS="$X_LIBS $X_PRE_LIBS -lX11 -lXext $X_EXTRA_LIBS"],[],[-lX11 -lXext -lm])
+			AC_CHECK_LIB(Xss, XScreenSaverRegister,[XSS_LIBS="$X_LIBS $X_PRE_LIBS -lX11 -lXext $X_LIBS $X_EXTRA_LIBS -lXss"],[],[-lX11 -lXext -lm])
+			if test "x$XSS_LIBS" != "x"; then
+				oldCPPFLAGS="$CPPFLAGS"
+				CPPFLAGS="$CPPFLAGS $x_incpath_add"
+				AC_TRY_COMPILE([
 					#include <X11/Xlib.h>
 					#include <X11/extensions/scrnsaver.h>
-				], [], [], [enable_screensaver=no])
-			CPPFLAGS="$oldCPPFLAGS"
+					], [], [], [enable_screensaver=no])
+				CPPFLAGS="$oldCPPFLAGS"
+			else
+				enable_screensaver=no
+			fi
+			LIBS="$old_LIBS"
+
+			if test "x$enable_screensaver" = "xyes" ; then
+				AC_DEFINE(USE_SCREENSAVER, 1, [Define if we're using XScreenSaver.])
+				AC_SUBST(XSS_LIBS)
+			fi
 		else
 			enable_screensaver=no
 		fi
-		LIBS="$old_LIBS"
-
-		if test "x$enable_screensaver" = "xyes" ; then
-			AC_DEFINE(USE_SCREENSAVER, 1, [Define if we're using XScreenSaver.])
-			AC_SUBST(XSS_LIBS)
-		fi
 	fi
 
 	dnl #######################################################################
 	dnl # Check for X session management libs
 	dnl #######################################################################
 	if test "x$enable_sm" = "xyes"; then
-		enable_sm=no
-		AC_CHECK_LIB(SM, SmcSaveYourselfDone, found_sm_lib=true, , [$x_libpath_add -lICE])
-		if test "x$found_sm_lib" = "xtrue"; then
-			oldCPPFLAGS="$CPPFLAGS"
-			CPPFLAGS="$CPPFLAGS $x_incpath_add"
-			AC_CHECK_HEADERS(X11/SM/SMlib.h, SM_LIBS="$x_libpath_add -lSM -lICE" enable_sm=yes)
-			CPPFLAGS="$oldCPPFLAGS"
+		if test "x$with_x" = "xyes" ; then
+			enable_sm=no
+			AC_CHECK_LIB(SM, SmcSaveYourselfDone, found_sm_lib=true, , [$x_libpath_add -lICE])
+			if test "x$found_sm_lib" = "xtrue"; then
+				oldCPPFLAGS="$CPPFLAGS"
+				CPPFLAGS="$CPPFLAGS $x_incpath_add"
+				AC_CHECK_HEADERS(X11/SM/SMlib.h, SM_LIBS="$x_libpath_add -lSM -lICE" enable_sm=yes)
+				CPPFLAGS="$oldCPPFLAGS"
+			fi
+
+			if test "x$enable_sm" = "xyes"; then
+				AC_DEFINE(USE_SM, 1, [Define if we're using X Session Management.])
+				AC_SUBST(SM_LIBS)
+			fi
+		else
+			enable_sm=no
 		fi
+	fi
 
-		if test "x$enable_sm" = "xyes"; then
-			AC_DEFINE(USE_SM, 1, [Define if we're using X Session Management.])
-			AC_SUBST(SM_LIBS)
+	dnl #######################################################################
+	dnl # Check for X11 to allow the gestures plugin
+	dnl #######################################################################
+	if test "x$enable_gestures" = "xyes"; then
+		if test "x$with_x" = "xno" ; then
+			enable_gestures=no
 		fi
 	fi
 
@@ -450,10 +484,11 @@
 	dnl #######################################################################
 	if test "x$enable_cap" = "xyes"; then
 		PKG_CHECK_MODULES(SQLITE3, sqlite3 >= 3.3,,[
-													AC_MSG_RESULT(no)
-													enable_cap="no"
-													])
+			AC_MSG_RESULT(no)
+			enable_cap="no"
+			])
 	fi
+        
 
 else # GTK
 	enable_cap=no
@@ -467,6 +502,8 @@
 AM_CONDITIONAL(ENABLE_GTK, test "x$enable_gtkui" = "xyes")
 AM_CONDITIONAL(BUILD_GEVOLUTION, test "x$enable_gevolution" = "xyes")
 AM_CONDITIONAL(ENABLE_CAP, test "x$enable_cap" = "xyes")
+AM_CONDITIONAL(ENABLE_GESTURES, test "x$enable_gestures" = "xyes")
+
 
 dnl #######################################################################
 dnl # Check for ncurses and other things used by the console UI
@@ -531,11 +568,6 @@
 			fi
 		fi
 	fi
-
-	PKG_CHECK_MODULES(X11, x11,
-		[AC_DEFINE(HAVE_X11, 1, [Define to 1 if you have X11])], [AC_MSG_RESULT(no)])
-	AC_SUBST(X11_LIBS)
-	AC_SUBST(X11_CFLAGS)
 fi
 
 AC_SUBST(GNT_LIBS)
@@ -2141,6 +2173,7 @@
 		   pidgin/pixmaps/status/Makefile
 		   pidgin/pixmaps/status/11/Makefile
 		   pidgin/pixmaps/status/11/scalable/Makefile
+		   pidgin/pixmaps/status/11/rtl/Makefile
 		   pidgin/pixmaps/status/16/Makefile
 		   pidgin/pixmaps/status/16/rtl/Makefile
 		   pidgin/pixmaps/status/16/scalable/Makefile
@@ -2207,6 +2240,7 @@
 		   libpurple/version.h
 		   share/Makefile
 		   share/sounds/Makefile
+		   share/ca-certs/Makefile
 		   finch/Makefile
 		   finch/libgnt/Makefile
 		   finch/libgnt/gnt.pc
@@ -2223,7 +2257,9 @@
 echo
 echo Build GTK+ 2.x UI............. : $enable_gtkui
 echo Build console UI.............. : $enable_consoleui
+echo Build for X11................. : $with_x
 echo
+echo Enable Gestures............... : $enable_gestures
 echo Protocols to build dynamically : $DYNAMIC_PRPLS
 echo Protocols to link statically.. : $STATIC_PRPLS
 echo
--- a/doc/Makefile.am	Sat Aug 25 08:42:33 2007 +0000
+++ b/doc/Makefile.am	Tue Aug 28 09:20:27 2007 +0000
@@ -3,11 +3,13 @@
 EXTRA_DIST = \
 	C-HOWTO.dox \
 	PERL-HOWTO.dox \
+	SIGNAL-HOWTO.dox \
 	TCL-HOWTO.dox \
 	TracFooter.html \
 	TracHeader.html \
 	account-signals.dox \
 	blist-signals.dox \
+	certificate-signals.dox \
 	cipher-signals.dox \
 	connection-signals.dox \
 	conversation-signals.dox \
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/SIGNAL-HOWTO.dox	Tue Aug 28 09:20:27 2007 +0000
@@ -0,0 +1,137 @@
+/** @page signal-howto Signals HOWTO
+
+ @section Introduction
+  The libpurple signals interface is used for general event notification, such
+  as plugins being loaded or unloaded, allowing the GUI frontend to respond
+  appropriately to changing internal data. Unfortunately, its use is not at all
+  obvious from the information in the header files. This document uses code
+  snippets from the Pidgin/libpurple plugin systems to illustrate the proper
+  use of signals.
+
+ @section overview Overview of Signals
+  Signals in libpurple are very similar to those in GTK+. When certain events
+  happen, a named signal is "emitted" from a certain object. Emitting the
+  signal triggers a series of callbacks that have been "connected" to that
+  signal for that object. These callbacks take appropriate action in response
+  to the signal.
+
+ @section registering_signal Registering a Signal
+  The first step of using a signal is registering it with libpurple so that
+  callbacks may be connected to it. This is done using purple_signal_register()
+  Here is a slightly modified example from @c purple_plugins_init in 
+  @c libpurple/plugin.c :
+
+  @code
+	purple_signal_register( purple_plugins_get_handle(), /* Instance */
+				"plugin-load",               /* Signal name */
+				purple_marshal_VOID__POINTER,/* Marshal function */
+				NULL,                        /* Callback return value type */
+				1,                           /* Number of callback arguments (not including void *data) */
+				purple_value_new(PURPLE_TYPE_SUBTYPE,PURPLE_SUBTYPE_PLUGIN) /* Type of first callback argument */
+				);
+  @endcode
+
+  @subsection Instance
+  A reference to the object from which this signal is emitted, and to which
+  potential callbacks should be connected. In this case, it will be the entire
+  plugin module emitting the signal.
+  
+  @subsection signalname Signal Name
+  Unique identifier for the signal itself.
+
+  @subsection therest Callback function definition
+  The rest of the arguments specify the form of the callback function.
+
+  @subsubsection marshalfunc Marshal Function
+  @c purple_marshal_VOID__POINTER represents the callback function prototype,
+  not including a "data" argument, explained later. The form is 
+  @c purple_marshal_RETURNVALUETYPE__ARG1TYPE_ARG2TYPE_ETC. See signals.h for
+  more possible types.
+
+  In this case, the callback will have the form
+  @code
+	void cb(void *arg1, void *data)
+  @endcode
+
+  If @c purple_marshal_BOOLEAN__POINTER_POINTER_POINTER were specified, it
+  would be:
+  @code
+	gboolean cb(void *arg1, void *arg2, void *arg3, void *data)
+  @endcode
+
+  The @c void @c *data argument at the end of each callback function
+  provides the data argument given to purple_signal_connect() .
+
+  @subsubsection cb_ret_type Callback return value type
+  In our case, this is NULL, meaning "returns void".
+  @todo This could be described better.
+
+  @subsubsection num_args Number of arguments
+  The number of arguments (not including @c data ) that the callback function
+  will take.
+
+  @subsubsection type_arg Type of argument
+  @c purple_value_new(PURPLE_TYPE_SUBTYPE,PURPLE_SUBTYPE_PLUGIN) specifies that
+  the first argument given to the callback will be a @c PurplePlugin* . You
+  will need as many "type of argument" arguments to purple_signal_register() as
+  you specified in "Number of arguments" above.
+
+  @todo Describe this more.
+
+  @See value.h
+
+  @section connect Connecting to the signal
+  Once the signal is registered, you can connect callbacks to it. First, you
+  must define a callback function, such as this one from gtkplugin.c :
+  @code
+static void plugin_load_cb(PurplePlugin *plugin, gpointer data)
+{
+	GtkTreeView *view = (GtkTreeView *)data;
+	plugin_loading_common(plugin, view, TRUE);
+}
+  @endcode
+  Note that the callback function prototype matches that specified in the call
+  to purple_signal_register() above.
+
+  Once the callback function is defined, you can connect it to the signal.
+  Again from gtkplugin.c , in @c pidgin_plugin_dialog_show() :
+  @code
+	purple_signal_connect(purple_plugins_get_handle(), "plugin-load", /* What to connect to */
+		plugin_dialog, /* Object receiving the signal */
+		PURPLE_CALLBACK(plugin_load_cb), /* Callback function */
+		event_view, /* Data to pass to the callback function
+		);
+  @endcode
+
+  The first two arguments ("What to connect to") specify the object emitting
+  the signal (the plugin module) and what signal to listen for ("plugin-load").
+
+  The object receiving the signal is @c plugin_dialog , the Pidgin plugins
+  dialog. When @c plugin_dialog is deleted, then 
+  @c purple_signals_disconnect_by_handle(plugin_dialog) should be called to
+  remove all signal connections it is associated with.
+
+  The callback function is given using a helper macro, and finally the
+  @c data argument to be passed to @c plugin_load_cb is given as @c event_view,
+  a pointer to the GTK widget that @c plugin_load_cb needs to update.
+
+  @section emit-signal Emitting a signal
+  Connecting callbacks to signals is all well and good, but how do you "fire"
+  the signal and trigger the callback? At some point, you must "emit" the
+  signal, which immediately calls all connected callbacks.
+
+  As seen in @c purple_plugin_load() in plugin.c :
+  @code
+	purple_signal_emit(purple_plugins_get_handle(), "plugin-load", plugin);
+  @endcode
+  This causes the signal "plugin-load" to be emitted from the plugin module
+  (given by @c purple_plugins_get_handle() ), with the newly loaded plugin as
+  the argument to pass to any registered callback functions.
+
+  In our example, @c plugin_load_cb is called immediately as
+  @code
+	plugin_load_cb(plugin, event_view);
+  @endcode
+  and does whatever it does.
+  
+ */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/certificate-signals.dox	Tue Aug 28 09:20:27 2007 +0000
@@ -0,0 +1,31 @@
+/** @page certificate-signals Certificate Signals
+
+ @signals
+  @signal certificate-stored
+  @signal certificate-deleted
+ @endsignals
+
+ <hr>
+
+ @signaldef certificate-stored
+  @signalproto
+void (*certificate_stored)(PurpleCertificatePool *pool, const gchar *id, gpointer data);
+  @endsignalproto
+  @signaldesc
+   Emitted when a pool stores a certificate. Connect to the pool instance.
+  @param pool    Pool the certificate has been stored into
+  @param id      Key the certificate was stored under
+ @endsignaldef
+
+ @signaldef certificate-deleted
+  @signalproto
+void (*certificate_deleted)(PurpleCertificatePool *pool, const gchar *id, gpointer data);
+  @endsignalproto
+  @signaldesc
+   Emitted when a pool deletes a certificate. Connect to the pool instance.
+  @param pool    Pool the certificate was deleted from
+  @param id      Key that was deleted
+ @endsignaldef
+
+ */
+// vim: syntax=c tw=75 et
--- a/doc/funniest_home_convos.txt	Sat Aug 25 08:42:33 2007 +0000
+++ b/doc/funniest_home_convos.txt	Tue Aug 28 09:20:27 2007 +0000
@@ -474,3 +474,16 @@
 
 19:23 <-- elb has quit (K-lined)
 
+19:01 <user> whoa
+19:01 <user> okay
+19:01 <user> now when i try to go into the left over files after the 
+             uninstall
+19:01 <user> something is seriously wrong because it says "the files on the 
+             c drive are not formatted, would you like to format?"
+19:03 <user> manually deleting the folder from command gives me a "Data 
+             error (cyclic redundancy check)."
+19:03 <elb> remember, one line per thought
+19:03 <elb> and yes, you have something wrong with your computer, we've 
+            established that
+19:03 <user> its functioning just fine
+
--- a/finch/gntconv.c	Sat Aug 25 08:42:33 2007 +0000
+++ b/finch/gntconv.c	Tue Aug 28 09:20:27 2007 +0000
@@ -37,6 +37,7 @@
 #include "gntdebug.h"
 #include "gntplugin.h"
 #include "gntprefs.h"
+#include "gntsound.h"
 #include "gntstatus.h"
 #include "gntpounce.h"
 
@@ -347,6 +348,53 @@
 }
 
 static void
+toggle_logging_cb(GntMenuItem *item, gpointer ggconv)
+{
+	FinchConv *fc = ggconv;
+	PurpleConversation *conv = fc->active_conv;
+	gboolean logging = gnt_menuitem_check_get_checked(GNT_MENU_ITEM_CHECK(item));
+	GList *iter;
+
+	if (logging == purple_conversation_is_logging(conv))
+		return;
+
+	/* Xerox */
+	if (logging) {
+		/* Enable logging first so the message below can be logged. */
+		purple_conversation_set_logging(conv, TRUE);
+
+		purple_conversation_write(conv, NULL,
+				_("Logging started. Future messages in this conversation will be logged."),
+				conv->logs ? (PURPLE_MESSAGE_SYSTEM) :
+				(PURPLE_MESSAGE_SYSTEM | PURPLE_MESSAGE_NO_LOG),
+				time(NULL));
+	} else {
+		purple_conversation_write(conv, NULL,
+				_("Logging stopped. Future messages in this conversation will not be logged."),
+				conv->logs ? (PURPLE_MESSAGE_SYSTEM) :
+				(PURPLE_MESSAGE_SYSTEM | PURPLE_MESSAGE_NO_LOG),
+				time(NULL));
+
+		/* Disable the logging second, so that the above message can be logged. */
+		purple_conversation_set_logging(conv, FALSE);
+	}
+
+	/* Each conversation with the same person will have the same logging setting */
+	for (iter = fc->list; iter; iter = iter->next) {
+		if (iter->data == conv)
+			continue;
+		purple_conversation_set_logging(iter->data, logging);
+	}
+}
+
+static void
+toggle_sound_cb(GntMenuItem *item, gpointer ggconv)
+{
+	FinchConv *fc = ggconv;
+	fc->flags ^= FINCH_CONV_NO_SOUND;
+}
+
+static void
 send_to_cb(GntMenuItem *m, gpointer n)
 {
 	PurpleAccount *account = g_object_get_data(G_OBJECT(m), "purple_account");
@@ -452,6 +500,18 @@
 
 		generate_send_to_menu(ggc);
 	}
+
+	item = gnt_menuitem_check_new(_("Enable Logging"));
+	gnt_menuitem_check_set_checked(GNT_MENU_ITEM_CHECK(item),
+			purple_conversation_is_logging(ggc->active_conv));
+	gnt_menu_add_item(GNT_MENU(sub), item);
+	gnt_menuitem_set_callback(item, toggle_logging_cb, ggc);
+
+	item = gnt_menuitem_check_new(_("Enable Sounds"));
+	gnt_menuitem_check_set_checked(GNT_MENU_ITEM_CHECK(item),
+			!(ggc->flags & FINCH_CONV_NO_SOUND));
+	gnt_menu_add_item(GNT_MENU(sub), item);
+	gnt_menuitem_set_callback(item, toggle_sound_cb, ggc);
 }
 
 static void
@@ -497,6 +557,12 @@
 	else
 		ggc = g_new0(FinchConv, 1);
 
+	/* Each conversation with the same person will have the same logging setting */
+	if (ggc->list) {
+		purple_conversation_set_logging(conv,
+				purple_conversation_is_logging(ggc->list->data));
+	}
+
 	ggc->list = g_list_prepend(ggc->list, conv);
 	ggc->active_conv = conv;
 	conv->ui_data = ggc;
@@ -586,6 +652,9 @@
 		g_signal_connect(G_OBJECT(ggc->entry), "text_changed", G_CALLBACK(send_typing_notification), ggc);
 	}
 
+	if (!finch_sound_is_enabled())
+		ggc->flags |= FINCH_CONV_NO_SOUND;
+
 	gg_create_menu(ggc);
 
 	g_free(title);
@@ -830,6 +899,23 @@
 	gnt_tree_change_text(GNT_TREE(ggc->u.chat->userlist), (gpointer)user, 0, chat_flag_text(cb->flags));
 }
 
+static void
+finch_conv_present(PurpleConversation *conv)
+{
+	FinchConv *fc = FINCH_CONV(conv);
+	if (fc && fc->window)
+		return gnt_window_present(fc->window);
+}
+
+static gboolean
+finch_conv_has_focus(PurpleConversation *conv)
+{
+	FinchConv *fc = FINCH_CONV(conv);
+	if (fc && fc->window)
+		return gnt_widget_has_focus(fc->window);
+	return FALSE;
+}
+
 static PurpleConversationUiOps conv_ui_ops = 
 {
 	finch_create_conversation,
@@ -841,8 +927,8 @@
 	finch_chat_rename_user,
 	finch_chat_remove_users,
 	finch_chat_update_user,
-	NULL, /* present */
-	NULL, /* has_focus */
+	finch_conv_present, /* present */
+	finch_conv_has_focus, /* has_focus */
 	NULL, /* custom_smiley_add */
 	NULL, /* custom_smiley_write */
 	NULL, /* custom_smiley_close */
--- a/finch/gntconv.h	Sat Aug 25 08:42:33 2007 +0000
+++ b/finch/gntconv.h	Tue Aug 28 09:20:27 2007 +0000
@@ -43,6 +43,11 @@
 typedef struct _FinchConvChat FinchConvChat;
 typedef struct _FinchConvIm FinchConvIm;
 
+typedef enum
+{
+	FINCH_CONV_NO_SOUND     = 1 << 0,
+} FinchConversationFlag;
+
 struct _FinchConv
 {
 	GList *list;
@@ -53,7 +58,7 @@
 	GntWidget *tv;            /* text-view */
 	GntWidget *menu;
 	GntWidget *info;
-	void *pad;
+	FinchConversationFlag flags;
 
 	union
 	{
--- a/finch/gntsound.c	Sat Aug 25 08:42:33 2007 +0000
+++ b/finch/gntsound.c	Tue Aug 28 09:20:27 2007 +0000
@@ -40,6 +40,8 @@
 #include "sound.h"
 #include "util.h"
 
+#include "gntconv.h"
+
 #include "gntbox.h"
 #include "gntwindow.h"
 #include "gntcombobox.h"
@@ -173,7 +175,8 @@
 
 		has_focus = purple_conversation_has_focus(conv);
 
-		if (has_focus && !purple_prefs_get_bool(make_pref("/conv_focus")))
+		if ((gntconv->flags & FINCH_CONV_NO_SOUND) ||
+			(has_focus && !purple_prefs_get_bool(make_pref("/conv_focus"))))
 		{
 			return;
 		}
@@ -1061,7 +1064,22 @@
 	load_pref_window(finch_sound_get_active_profile());
 
 	gnt_widget_show(win);
-}	
+}
+
+gboolean finch_sound_is_enabled(void)
+{
+	const char *pref = make_pref("/method");
+	const char *method = purple_prefs_get_string(pref);
+
+	if (!method)
+		return FALSE;
+	if (strcmp(method, "nosound") == 0)
+		return FALSE;
+	if (purple_prefs_get_int(make_pref("/volume")) <= 0)
+		return FALSE;
+
+	return TRUE;
+}
 
 static PurpleSoundUiOps sound_ui_ops =
 {
--- a/finch/gntsound.h	Sat Aug 25 08:42:33 2007 +0000
+++ b/finch/gntsound.h	Tue Aug 28 09:20:27 2007 +0000
@@ -55,6 +55,14 @@
 GList *finch_sound_get_profiles(void);
 
 /**
+ * Determine whether any sound will be played or not.
+ *
+ * @return Returns FALSE if preference is set to 'No sound', or if volume is
+ *         set to zero.
+ */
+gboolean finch_sound_is_enabled(void);
+
+/**
  * Gets GNT sound UI ops.
  *
  * @return The UI operations structure.
--- a/libpurple/Makefile.am	Sat Aug 25 08:42:33 2007 +0000
+++ b/libpurple/Makefile.am	Tue Aug 28 09:20:27 2007 +0000
@@ -36,6 +36,7 @@
 	accountopt.c \
 	blist.c \
 	buddyicon.c \
+	certificate.c \
 	cipher.c \
 	circbuffer.c \
 	cmds.c \
@@ -85,6 +86,7 @@
 	accountopt.h \
 	blist.h \
 	buddyicon.h \
+	certificate.h \
 	cipher.h \
 	circbuffer.h \
 	cmds.h \
--- a/libpurple/Makefile.mingw	Sat Aug 25 08:42:33 2007 +0000
+++ b/libpurple/Makefile.mingw	Tue Aug 28 09:20:27 2007 +0000
@@ -33,6 +33,7 @@
 			accountopt.c \
 			blist.c \
 			buddyicon.c \
+			certificate.c \
 			cipher.c \
 			cmds.c \
 			connection.c \
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/certificate.c	Tue Aug 28 09:20:27 2007 +0000
@@ -0,0 +1,1824 @@
+/**
+ * @file certificate.c Public-Key Certificate API
+ * @ingroup core
+ */
+
+/*
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#include <glib.h>
+
+#include "internal.h"
+#include "certificate.h"
+#include "debug.h"
+#include "request.h"
+#include "signals.h"
+#include "util.h"
+
+/** List holding pointers to all registered certificate schemes */
+static GList *cert_schemes = NULL;
+/** List of registered Verifiers */
+static GList *cert_verifiers = NULL;
+/** List of registered Pools */
+static GList *cert_pools = NULL;
+
+void
+purple_certificate_verify (PurpleCertificateVerifier *verifier,
+			   const gchar *subject_name, GList *cert_chain,
+			   PurpleCertificateVerifiedCallback cb,
+			   gpointer cb_data)
+{
+	PurpleCertificateVerificationRequest *vrq;
+	PurpleCertificateScheme *scheme;
+	
+	g_return_if_fail(subject_name != NULL);
+	/* If you don't have a cert to check, why are you requesting that it
+	   be verified? */
+	g_return_if_fail(cert_chain != NULL);
+	g_return_if_fail(cb != NULL);
+
+	/* Look up the CertificateScheme */
+	scheme = purple_certificate_find_scheme(verifier->scheme_name);
+	g_return_if_fail(scheme);
+
+	/* Check that at least the first cert in the chain matches the
+	   Verifier scheme */
+	g_return_if_fail(scheme ==
+			 ((PurpleCertificate *) (cert_chain->data))->scheme);
+
+	/* Construct and fill in the request fields */
+	vrq = g_new0(PurpleCertificateVerificationRequest, 1);
+	vrq->verifier = verifier;
+	vrq->scheme = scheme;
+	vrq->subject_name = g_strdup(subject_name);
+	vrq->cert_chain = purple_certificate_copy_list(cert_chain);
+	vrq->cb = cb;
+	vrq->cb_data = cb_data;
+
+	/* Initiate verification */
+	(verifier->start_verification)(vrq);
+}
+
+void
+purple_certificate_verify_complete(PurpleCertificateVerificationRequest *vrq,
+				   PurpleCertificateVerificationStatus st)
+{
+	PurpleCertificateVerifier *vr;
+
+	g_return_if_fail(vrq);
+
+	/* Pass the results on to the request's callback */
+	(vrq->cb)(st, vrq->cb_data);
+
+	/* And now to eliminate the request */
+	/* Fetch the Verifier responsible... */
+	vr = vrq->verifier;
+	/* ...and order it to KILL */
+	(vr->destroy_request)(vrq);
+
+	/* Now the internals have been cleaned up, so clean up the libpurple-
+	   created elements */
+	g_free(vrq->subject_name);
+	purple_certificate_destroy_list(vrq->cert_chain);
+
+	/*  A structure born
+	 *          to much ado
+	 *                   and with so much within.
+	 * It reaches now
+	 *             its quiet end. */
+	g_free(vrq);
+}
+
+
+PurpleCertificate *
+purple_certificate_copy(PurpleCertificate *crt)
+{
+	g_return_val_if_fail(crt, NULL);
+	g_return_val_if_fail(crt->scheme, NULL);
+	g_return_val_if_fail(crt->scheme->copy_certificate, NULL);
+
+	return (crt->scheme->copy_certificate)(crt);
+}
+
+GList *
+purple_certificate_copy_list(GList *crt_list)
+{
+	GList *new, *l;
+
+	/* First, make a shallow copy of the list */
+	new = g_list_copy(crt_list);
+
+	/* Now go through and actually duplicate each certificate */
+	for (l = new; l; l = l->next) {
+		l->data = purple_certificate_copy(l->data);
+	}
+
+	return new;
+}
+
+void
+purple_certificate_destroy (PurpleCertificate *crt)
+{
+	PurpleCertificateScheme *scheme;
+	
+	if (NULL == crt) return;
+
+	scheme = crt->scheme;
+
+	(scheme->destroy_certificate)(crt);
+}
+
+void
+purple_certificate_destroy_list (GList * crt_list)
+{
+	PurpleCertificate *crt;
+	GList *l;
+
+	for (l=crt_list; l; l = l->next) {
+		crt = (PurpleCertificate *) l->data;
+		purple_certificate_destroy(crt);
+	}
+
+	g_list_free(crt_list);
+}
+
+gboolean
+purple_certificate_signed_by(PurpleCertificate *crt, PurpleCertificate *issuer)
+{
+	PurpleCertificateScheme *scheme;
+
+	g_return_val_if_fail(crt, FALSE);
+	g_return_val_if_fail(issuer, FALSE);
+
+	scheme = crt->scheme;
+	g_return_val_if_fail(scheme, FALSE);
+	/* We can't compare two certs of unrelated schemes, obviously */
+	g_return_val_if_fail(issuer->scheme == scheme, FALSE);
+
+	return (scheme->signed_by)(crt, issuer);
+}
+
+gboolean
+purple_certificate_check_signature_chain(GList *chain)
+{
+	GList *cur;
+	PurpleCertificate *crt, *issuer;
+	gchar *uid;
+
+	g_return_val_if_fail(chain, FALSE);
+
+	uid = purple_certificate_get_unique_id((PurpleCertificate *) chain->data);
+	purple_debug_info("certificate",
+			  "Checking signature chain for uid=%s\n",
+			  uid);
+	g_free(uid);
+	
+	/* If this is a single-certificate chain, say that it is valid */
+	if (chain->next == NULL) {
+		purple_debug_info("certificate",
+				  "...Singleton. We'll say it's valid.\n");
+		return TRUE;
+	}
+
+	/* Load crt with the first certificate */
+	crt = (PurpleCertificate *)(chain->data);
+	/* And start with the second certificate in the chain */
+	for ( cur = chain->next; cur; cur = cur->next ) {
+		
+		issuer = (PurpleCertificate *)(cur->data);
+		
+		/* Check the signature for this link */
+		if (! purple_certificate_signed_by(crt, issuer) ) {
+			uid = purple_certificate_get_unique_id(issuer);
+			purple_debug_info("certificate",
+					  "...Bad or missing signature by %s\nChain is INVALID\n",
+					  uid);
+			g_free(uid);
+		
+			return FALSE;
+		}
+
+		uid = purple_certificate_get_unique_id(issuer);
+		purple_debug_info("certificate",
+				  "...Good signature by %s\n",
+				  uid);
+		g_free(uid);
+		
+		/* The issuer is now the next crt whose signature is to be
+		   checked */
+		crt = issuer;
+	}
+
+	/* If control reaches this point, the chain is valid */
+	purple_debug_info("certificate", "Chain is VALID\n");
+	return TRUE;
+}
+
+PurpleCertificate *
+purple_certificate_import(PurpleCertificateScheme *scheme, const gchar *filename)
+{
+	g_return_val_if_fail(scheme, NULL);
+	g_return_val_if_fail(scheme->import_certificate, NULL);
+	g_return_val_if_fail(filename, NULL);
+
+	return (scheme->import_certificate)(filename);
+}
+
+gboolean
+purple_certificate_export(const gchar *filename, PurpleCertificate *crt)
+{
+	PurpleCertificateScheme *scheme;
+
+	g_return_val_if_fail(filename, FALSE);
+	g_return_val_if_fail(crt, FALSE);
+	g_return_val_if_fail(crt->scheme, FALSE);
+
+	scheme = crt->scheme;
+	g_return_val_if_fail(scheme->export_certificate, FALSE);
+
+	return (scheme->export_certificate)(filename, crt);
+}
+
+GByteArray *
+purple_certificate_get_fingerprint_sha1(PurpleCertificate *crt)
+{
+	PurpleCertificateScheme *scheme;
+	GByteArray *fpr;
+
+	g_return_val_if_fail(crt, NULL);
+	g_return_val_if_fail(crt->scheme, NULL);
+
+	scheme = crt->scheme;
+	
+	g_return_val_if_fail(scheme->get_fingerprint_sha1, NULL);
+
+	fpr = (scheme->get_fingerprint_sha1)(crt);
+
+	return fpr;
+}
+
+gchar *
+purple_certificate_get_unique_id(PurpleCertificate *crt)
+{
+	g_return_val_if_fail(crt, NULL);
+	g_return_val_if_fail(crt->scheme, NULL);
+	g_return_val_if_fail(crt->scheme->get_unique_id, NULL);
+
+	return (crt->scheme->get_unique_id)(crt);
+}
+
+gchar *
+purple_certificate_get_issuer_unique_id(PurpleCertificate *crt)
+{
+	g_return_val_if_fail(crt, NULL);
+	g_return_val_if_fail(crt->scheme, NULL);
+	g_return_val_if_fail(crt->scheme->get_issuer_unique_id, NULL);
+
+	return (crt->scheme->get_issuer_unique_id)(crt);
+}
+
+gchar *
+purple_certificate_get_subject_name(PurpleCertificate *crt)
+{
+	PurpleCertificateScheme *scheme;
+	gchar *subject_name;
+
+	g_return_val_if_fail(crt, NULL);
+	g_return_val_if_fail(crt->scheme, NULL);
+
+	scheme = crt->scheme;
+
+	g_return_val_if_fail(scheme->get_subject_name, NULL);
+
+	subject_name = (scheme->get_subject_name)(crt);
+
+	return subject_name;
+}
+
+gboolean
+purple_certificate_check_subject_name(PurpleCertificate *crt, const gchar *name)
+{
+	PurpleCertificateScheme *scheme;
+
+	g_return_val_if_fail(crt, FALSE);
+	g_return_val_if_fail(crt->scheme, FALSE);
+	g_return_val_if_fail(name, FALSE);
+
+	scheme = crt->scheme;
+
+	/* TODO: Instead of failing, maybe use get_subject_name and strcmp? */
+	g_return_val_if_fail(scheme->check_subject_name, FALSE);
+
+	return (scheme->check_subject_name)(crt, name);
+}
+
+gboolean
+purple_certificate_get_times(PurpleCertificate *crt, time_t *activation, time_t *expiration)
+{
+	PurpleCertificateScheme *scheme;
+
+	g_return_val_if_fail(crt, FALSE);
+
+	scheme = crt->scheme;
+	
+	g_return_val_if_fail(scheme, FALSE);
+
+	/* If both provided references are NULL, what are you doing calling
+	   this? */
+	g_return_val_if_fail( (activation != NULL) || (expiration != NULL), FALSE);
+
+	/* Throw the request on down to the certscheme */
+	return (scheme->get_times)(crt, activation, expiration);
+}
+
+
+gchar *
+purple_certificate_pool_mkpath(PurpleCertificatePool *pool, const gchar *id)
+{
+	gchar *path;
+	gchar *esc_scheme_name, *esc_name, *esc_id;
+	
+	g_return_val_if_fail(pool, NULL);
+	g_return_val_if_fail(pool->scheme_name, NULL);
+	g_return_val_if_fail(pool->name, NULL);
+
+	/* Escape all the elements for filesystem-friendliness */
+	esc_scheme_name = pool ? g_strdup(purple_escape_filename(pool->scheme_name)) : NULL;
+	esc_name = pool ? g_strdup(purple_escape_filename(pool->name)) : NULL;
+	esc_id = id ? g_strdup(purple_escape_filename(id)) : NULL;
+	
+	path = g_build_filename(purple_user_dir(),
+				"certificates", /* TODO: constantize this? */
+				esc_scheme_name,
+				esc_name,
+				esc_id,
+				NULL);
+
+	g_free(esc_scheme_name);
+	g_free(esc_name);
+	g_free(esc_id);
+	return path;
+}
+
+gboolean
+purple_certificate_pool_usable(PurpleCertificatePool *pool)
+{
+	g_return_val_if_fail(pool, FALSE);
+	g_return_val_if_fail(pool->scheme_name, FALSE);
+
+	/* Check that the pool's scheme is loaded */
+	if (purple_certificate_find_scheme(pool->scheme_name) == NULL) {
+		return FALSE;
+	}
+	
+	return TRUE;
+}
+
+PurpleCertificateScheme *
+purple_certificate_pool_get_scheme(PurpleCertificatePool *pool)
+{
+	g_return_val_if_fail(pool, NULL);
+	g_return_val_if_fail(pool->scheme_name, NULL);
+
+	return purple_certificate_find_scheme(pool->scheme_name);
+}
+
+gboolean
+purple_certificate_pool_contains(PurpleCertificatePool *pool, const gchar *id)
+{
+	g_return_val_if_fail(pool, FALSE);
+	g_return_val_if_fail(id, FALSE);
+	g_return_val_if_fail(pool->cert_in_pool, FALSE);
+
+	return (pool->cert_in_pool)(id);
+}
+
+PurpleCertificate *
+purple_certificate_pool_retrieve(PurpleCertificatePool *pool, const gchar *id)
+{
+	g_return_val_if_fail(pool, NULL);
+	g_return_val_if_fail(id, NULL);
+	g_return_val_if_fail(pool->get_cert, NULL);
+
+	return (pool->get_cert)(id);
+}
+
+gboolean
+purple_certificate_pool_store(PurpleCertificatePool *pool, const gchar *id, PurpleCertificate *crt)
+{
+	gboolean ret = FALSE;
+	
+	g_return_val_if_fail(pool, FALSE);
+	g_return_val_if_fail(id, FALSE);
+	g_return_val_if_fail(pool->put_cert, FALSE);
+
+	/* Whether crt->scheme matches find_scheme(pool->scheme_name) is not
+	   relevant... I think... */
+	g_return_val_if_fail(
+		g_ascii_strcasecmp(pool->scheme_name, crt->scheme->name) == 0,
+		FALSE);
+
+	ret = (pool->put_cert)(id, crt);
+
+	/* Signal that the certificate was stored if success*/
+	if (ret) {
+		purple_signal_emit(pool, "certificate-stored",
+				   pool, id);
+	}
+
+	return ret;
+}	
+
+gboolean
+purple_certificate_pool_delete(PurpleCertificatePool *pool, const gchar *id)
+{
+	gboolean ret = FALSE;
+	
+	g_return_val_if_fail(pool, FALSE);
+	g_return_val_if_fail(id, FALSE);
+	g_return_val_if_fail(pool->delete_cert, FALSE);
+
+	ret = (pool->delete_cert)(id);
+
+	/* Signal that the certificate was deleted if success */
+	if (ret) {
+		purple_signal_emit(pool, "certificate-deleted",
+				   pool, id);
+	}
+
+	return ret;
+}
+
+GList *
+purple_certificate_pool_get_idlist(PurpleCertificatePool *pool)
+{
+	g_return_val_if_fail(pool, NULL);
+	g_return_val_if_fail(pool->get_idlist, NULL);
+
+	return (pool->get_idlist)();
+}
+
+void
+purple_certificate_pool_destroy_idlist(GList *idlist)
+{
+	GList *l;
+	
+	/* Iterate through and free them strings */
+	for ( l = idlist; l; l = l->next ) {
+		g_free(l->data);
+	}
+
+	g_list_free(idlist);
+}
+
+
+/****************************************************************************/
+/* Builtin Verifiers, Pools, etc.                                           */
+/****************************************************************************/
+
+static void
+x509_singleuse_verify_cb (PurpleCertificateVerificationRequest *vrq, gint id)
+{
+	g_return_if_fail(vrq);
+
+	purple_debug_info("certificate/x509_singleuse",
+			  "VRQ on cert from %s gave %d\n",
+			  vrq->subject_name, id);
+
+	/* Signal what happened back to the caller */
+	if (1 == id) {		
+		/* Accepted! */
+		purple_certificate_verify_complete(vrq,
+						   PURPLE_CERTIFICATE_VALID);
+	} else {
+		/* Not accepted */
+		purple_certificate_verify_complete(vrq,
+						   PURPLE_CERTIFICATE_INVALID);
+
+	}
+}
+
+static void
+x509_singleuse_start_verify (PurpleCertificateVerificationRequest *vrq)
+{
+	gchar *sha_asc;
+	GByteArray *sha_bin;
+	gchar *cn;
+	const gchar *cn_match;
+	gchar *primary, *secondary;
+	PurpleCertificate *crt = (PurpleCertificate *) vrq->cert_chain->data;
+
+	/* Pull out the SHA1 checksum */
+	sha_bin = purple_certificate_get_fingerprint_sha1(crt);
+	/* Now decode it for display */
+	sha_asc = purple_base16_encode_chunked(sha_bin->data,
+					       sha_bin->len);
+
+	/* Get the cert Common Name */
+	cn = purple_certificate_get_subject_name(crt);
+
+	/* Determine whether the name matches */
+	if (purple_certificate_check_subject_name(crt, vrq->subject_name)) {
+		cn_match = _("");
+	} else {
+		cn_match = _("(DOES NOT MATCH)");
+	}
+	
+	/* Make messages */
+	primary = g_strdup_printf(_("%s has presented the following certificate for just-this-once use:"), vrq->subject_name);
+	secondary = g_strdup_printf(_("Common name: %s %s\nFingerprint (SHA1): %s"), cn, cn_match, sha_asc);
+	
+	/* Make a semi-pretty display */
+	purple_request_accept_cancel(
+		vrq->cb_data, /* TODO: Find what the handle ought to be */
+		_("Single-use Certificate Verification"),
+		primary,
+		secondary,
+		1,            /* Accept by default */
+		NULL,         /* No account */
+		NULL,         /* No other user */
+		NULL,         /* No associated conversation */
+		vrq,
+		x509_singleuse_verify_cb,
+		x509_singleuse_verify_cb );
+	
+	/* Cleanup */
+	g_free(primary);
+	g_free(secondary);
+	g_free(sha_asc);
+	g_byte_array_free(sha_bin, TRUE);
+}
+
+static void
+x509_singleuse_destroy_request (PurpleCertificateVerificationRequest *vrq)
+{
+	/* I don't do anything! */
+}
+
+PurpleCertificateVerifier x509_singleuse = {
+	"x509",                         /* Scheme name */
+	"singleuse",                    /* Verifier name */
+	x509_singleuse_start_verify,    /* start_verification function */
+	x509_singleuse_destroy_request  /* Request cleanup operation */
+};
+
+
+
+/***** X.509 Certificate Authority pool, keyed by Distinguished Name *****/
+/* This is implemented in what may be the most inefficient and bugprone way
+   possible; however, future optimizations should not be difficult. */
+
+static PurpleCertificatePool x509_ca;
+
+/** Holds a key-value pair for quickish certificate lookup */
+typedef struct {
+	gchar *dn;
+	PurpleCertificate *crt;
+} x509_ca_element;
+
+static void
+x509_ca_element_free(x509_ca_element *el)
+{
+	if (NULL == el) return;
+
+	g_free(el->dn);
+	purple_certificate_destroy(el->crt);
+	g_free(el);
+}
+
+/** System directory to probe for CA certificates */
+/* This is set in the lazy_init function */
+static const gchar *x509_ca_syspath = NULL;
+
+/** A list of loaded CAs, populated from the above path whenever the lazy_init
+    happens. Contains pointers to x509_ca_elements */
+static GList *x509_ca_certs = NULL;
+
+/** Used for lazy initialization purposes. */
+static gboolean x509_ca_initialized = FALSE;
+
+/** Adds a certificate to the in-memory cache, doing nothing else */
+static gboolean
+x509_ca_quiet_put_cert(PurpleCertificate *crt)
+{
+	x509_ca_element *el;
+
+	/* lazy_init calls this function, so calling lazy_init here is a
+	   Bad Thing */
+	
+	g_return_val_if_fail(crt, FALSE);
+	g_return_val_if_fail(crt->scheme, FALSE);
+	/* Make sure that this is some kind of X.509 certificate */
+	/* TODO: Perhaps just check crt->scheme->name instead? */
+	g_return_val_if_fail(crt->scheme == purple_certificate_find_scheme(x509_ca.scheme_name), FALSE);
+	
+	el = g_new0(x509_ca_element, 1);
+	el->dn = purple_certificate_get_unique_id(crt);
+	el->crt = purple_certificate_copy(crt);
+	x509_ca_certs = g_list_prepend(x509_ca_certs, el);
+
+	return TRUE;
+}
+
+/* Since the libpurple CertificatePools get registered before plugins are
+   loaded, an X.509 Scheme is generally not available when x509_ca_init is
+   called, but x509_ca requires X.509 operations in order to properly load.
+
+   To solve this, I present the lazy_init function. It attempts to finish
+   initialization of the Pool, but it usually fails when it is called from
+   x509_ca_init. However, this is OK; initialization is then simply deferred
+   until someone tries to use functions from the pool. */
+static gboolean
+x509_ca_lazy_init(void)
+{
+	PurpleCertificateScheme *x509;
+	GDir *certdir;
+	const gchar *entry;
+	GPatternSpec *pempat;
+	
+	if (x509_ca_initialized) return TRUE;
+
+	/* Check that X.509 is registered */
+	x509 = purple_certificate_find_scheme(x509_ca.scheme_name);
+	if ( !x509 ) {
+		purple_debug_info("certificate/x509/ca",
+				  "Lazy init failed because an X.509 Scheme "
+				  "is not yet registered. Maybe it will be "
+				  "better later.\n");
+		return FALSE;
+	}
+
+	/* Attempt to point at the appropriate system path */
+	if (NULL == x509_ca_syspath) {
+#ifdef _WIN32
+		x509_ca_syspath = g_build_filename(DATADIR,
+						   "ca-certs", NULL);
+#else
+		x509_ca_syspath = g_build_filename(DATADIR,
+						   "purple", "ca-certs", NULL);
+#endif
+	}
+
+	/* Populate the certificates pool from the system path */
+	certdir = g_dir_open(x509_ca_syspath, 0, NULL);
+	g_return_val_if_fail(certdir, FALSE);
+
+	/* Use a glob to only read .pem files */
+	pempat = g_pattern_spec_new("*.pem");
+	
+	while ( (entry = g_dir_read_name(certdir)) ) {
+		gchar *fullpath;
+		PurpleCertificate *crt;
+
+		if ( !g_pattern_match_string(pempat, entry) ) {
+			continue;
+		}
+
+		fullpath = g_build_filename(x509_ca_syspath, entry, NULL);
+		
+		/* TODO: Respond to a failure in the following? */
+		crt = purple_certificate_import(x509, fullpath);
+
+		if (x509_ca_quiet_put_cert(crt)) {
+			purple_debug_info("certificate/x509/ca",
+					  "Loaded %s\n",
+					  fullpath);
+		} else {
+			purple_debug_error("certificate/x509/ca",
+					  "Failed to load %s\n",
+					  fullpath);
+		}
+
+		purple_certificate_destroy(crt);
+		g_free(fullpath);
+	}
+
+	g_pattern_spec_free(pempat);
+	g_dir_close(certdir);
+	
+	purple_debug_info("certificate/x509/ca",
+			  "Lazy init completed.\n");
+	x509_ca_initialized = TRUE;
+	return TRUE;
+}
+
+static gboolean
+x509_ca_init(void)
+{
+	/* Attempt to initialize now, but if it doesn't work, that's OK;
+	   it will get done later */
+	if ( ! x509_ca_lazy_init()) {
+		purple_debug_info("certificate/x509/ca",
+				  "Init failed, probably because a "
+				  "dependency is not yet registered. "
+				  "It has been deferred to later.\n");
+	}
+	
+	return TRUE;
+}
+
+static void
+x509_ca_uninit(void)
+{
+	GList *l;
+
+	for (l = x509_ca_certs; l; l = l->next) {
+		x509_ca_element *el = l->data;
+		x509_ca_element_free(el);
+	}
+	g_list_free(x509_ca_certs);
+	x509_ca_certs = NULL;
+	x509_ca_initialized = FALSE;
+}
+
+/** Look up a ca_element by dn */
+static x509_ca_element *
+x509_ca_locate_cert(GList *lst, const gchar *dn)
+{
+	GList *cur;
+
+	for (cur = lst; cur; cur = cur->next) {
+		x509_ca_element *el = cur->data;
+		/* TODO: Unsafe? */
+		if ( !strcmp(dn, el->dn) ) {
+			return el;
+		}
+	}
+	return NULL;
+}
+
+static gboolean
+x509_ca_cert_in_pool(const gchar *id)
+{
+	g_return_val_if_fail(x509_ca_lazy_init(), FALSE);
+	g_return_val_if_fail(id, FALSE);
+
+	if (x509_ca_locate_cert(x509_ca_certs, id) != NULL) {
+		return TRUE;
+	} else {
+		return FALSE;
+	}
+
+	return FALSE;
+}
+
+static PurpleCertificate *
+x509_ca_get_cert(const gchar *id)
+{
+	PurpleCertificate *crt = NULL;
+	x509_ca_element *el;
+
+	g_return_val_if_fail(x509_ca_lazy_init(), NULL);
+	g_return_val_if_fail(id, NULL);
+
+	/* Search the memory-cached pool */
+	el = x509_ca_locate_cert(x509_ca_certs, id);
+
+	if (el != NULL) {
+		/* Make a copy of the memcached one for the function caller
+		   to play with */
+		crt = purple_certificate_copy(el->crt);
+	} else {
+		crt = NULL;
+	}
+	
+	return crt;
+}
+
+static gboolean
+x509_ca_put_cert(const gchar *id, PurpleCertificate *crt)
+{
+	gboolean ret = FALSE;
+	
+	g_return_val_if_fail(x509_ca_lazy_init(), FALSE);
+
+	/* TODO: This is a quick way of doing this. At some point the change
+	   ought to be flushed to disk somehow. */
+	ret = x509_ca_quiet_put_cert(crt);
+
+	return ret;
+}
+
+static gboolean
+x509_ca_delete_cert(const gchar *id)
+{
+	x509_ca_element *el;
+	
+	g_return_val_if_fail(x509_ca_lazy_init(), FALSE);
+	g_return_val_if_fail(id, FALSE);
+
+	/* Is the id even in the pool? */
+	el = x509_ca_locate_cert(x509_ca_certs, id);
+	if ( el == NULL ) {
+		purple_debug_warning("certificate/x509/ca",
+				     "Id %s wasn't in the pool\n",
+				     id);
+		return FALSE;
+	}
+
+	/* Unlink it from the memory cache and destroy it */
+	x509_ca_certs = g_list_remove(x509_ca_certs, el);
+	x509_ca_element_free(el);
+	
+	return TRUE;
+}
+
+static GList *
+x509_ca_get_idlist(void)
+{
+	GList *l, *idlist;
+	
+	g_return_val_if_fail(x509_ca_lazy_init(), NULL);
+
+	idlist = NULL;
+	for (l = x509_ca_certs; l; l = l->next) {
+		x509_ca_element *el = l->data;
+		idlist = g_list_prepend(idlist, g_strdup(el->dn));
+	}
+	
+	return idlist;
+}
+
+
+static PurpleCertificatePool x509_ca = {
+	"x509",                       /* Scheme name */
+	"ca",                         /* Pool name */
+	N_("Certificate Authorities"),/* User-friendly name */
+	NULL,                         /* Internal data */
+	x509_ca_init,                 /* init */
+	x509_ca_uninit,               /* uninit */
+	x509_ca_cert_in_pool,         /* Certificate exists? */
+	x509_ca_get_cert,             /* Cert retriever */
+	x509_ca_put_cert,             /* Cert writer */
+	x509_ca_delete_cert,          /* Cert remover */
+	x509_ca_get_idlist            /* idlist retriever */
+};
+
+
+
+/***** Cache of certificates given by TLS/SSL peers *****/
+static PurpleCertificatePool x509_tls_peers;
+
+static gboolean
+x509_tls_peers_init(void)
+{
+	gchar *poolpath;
+	int ret;
+	
+	/* Set up key cache here if it isn't already done */
+	poolpath = purple_certificate_pool_mkpath(&x509_tls_peers, NULL);
+	ret = purple_build_dir(poolpath, 0700); /* Make it this user only */
+
+	g_free(poolpath);
+
+	g_return_val_if_fail(ret == 0, FALSE);
+	return TRUE;
+}
+
+static gboolean
+x509_tls_peers_cert_in_pool(const gchar *id)
+{
+	gchar *keypath;
+	gboolean ret = FALSE;
+	
+	g_return_val_if_fail(id, FALSE);
+
+	keypath = purple_certificate_pool_mkpath(&x509_tls_peers, id);
+
+	ret = g_file_test(keypath, G_FILE_TEST_IS_REGULAR);
+	
+	g_free(keypath);
+	return ret;
+}
+
+static PurpleCertificate *
+x509_tls_peers_get_cert(const gchar *id)
+{
+	PurpleCertificateScheme *x509;
+	PurpleCertificate *crt;
+	gchar *keypath;
+	
+	g_return_val_if_fail(id, NULL);
+
+	/* Is it in the pool? */
+	if ( !x509_tls_peers_cert_in_pool(id) ) {
+		return NULL;
+	}
+	
+	/* Look up the X.509 scheme */
+	x509 = purple_certificate_find_scheme("x509");
+	g_return_val_if_fail(x509, NULL);
+
+	/* Okay, now find and load that key */
+	keypath = purple_certificate_pool_mkpath(&x509_tls_peers, id);
+	crt = purple_certificate_import(x509, keypath);
+
+	g_free(keypath);
+
+	return crt;
+}
+
+static gboolean
+x509_tls_peers_put_cert(const gchar *id, PurpleCertificate *crt)
+{
+	gboolean ret = FALSE;
+	gchar *keypath;
+
+	g_return_val_if_fail(crt, FALSE);
+	g_return_val_if_fail(crt->scheme, FALSE);
+	/* Make sure that this is some kind of X.509 certificate */
+	/* TODO: Perhaps just check crt->scheme->name instead? */
+	g_return_val_if_fail(crt->scheme == purple_certificate_find_scheme(x509_tls_peers.scheme_name), FALSE);
+
+	/* Work out the filename and export */
+	keypath = purple_certificate_pool_mkpath(&x509_tls_peers, id);
+	ret = purple_certificate_export(keypath, crt);
+	
+	g_free(keypath);
+	return ret;
+}
+
+static gboolean
+x509_tls_peers_delete_cert(const gchar *id)
+{
+	gboolean ret = FALSE;
+	gchar *keypath;
+
+	g_return_val_if_fail(id, FALSE);
+
+	/* Is the id even in the pool? */
+	if (!x509_tls_peers_cert_in_pool(id)) {
+		purple_debug_warning("certificate/tls_peers",
+				     "Id %s wasn't in the pool\n",
+				     id);
+		return FALSE;
+	}
+
+	/* OK, so work out the keypath and delete the thing */
+	keypath = purple_certificate_pool_mkpath(&x509_tls_peers, id);	
+	if ( unlink(keypath) != 0 ) {
+		purple_debug_error("certificate/tls_peers",
+				   "Unlink of %s failed!\n",
+				   keypath);
+		ret = FALSE;
+	} else {
+		ret = TRUE;
+	}
+
+	g_free(keypath);
+	return ret;
+}
+
+static GList *
+x509_tls_peers_get_idlist(void)
+{
+	GList *idlist = NULL;
+	GDir *dir;
+	const gchar *entry;
+	gchar *poolpath;
+
+	/* Get a handle on the pool directory */
+	poolpath = purple_certificate_pool_mkpath(&x509_tls_peers, NULL);
+	dir = g_dir_open(poolpath,
+			 0,     /* No flags */
+			 NULL); /* Not interested in what the error is */
+	g_free(poolpath);
+
+	g_return_val_if_fail(dir, NULL);
+
+	/* Traverse the directory listing and create an idlist */
+	while ( (entry = g_dir_read_name(dir)) != NULL ) {
+		/* Unescape the filename */
+		const char *unescaped = purple_unescape_filename(entry);
+		
+		/* Copy the entry name into our list (GLib owns the original
+		   string) */
+		idlist = g_list_prepend(idlist, g_strdup(unescaped));
+	}
+
+	/* Release the directory */
+	g_dir_close(dir);
+	
+	return idlist;
+}
+
+static PurpleCertificatePool x509_tls_peers = {
+	"x509",                       /* Scheme name */
+	"tls_peers",                  /* Pool name */
+	N_("SSL Peers Cache"),        /* User-friendly name */
+	NULL,                         /* Internal data */
+	x509_tls_peers_init,          /* init */
+	NULL,                         /* uninit not required */
+	x509_tls_peers_cert_in_pool,  /* Certificate exists? */
+	x509_tls_peers_get_cert,      /* Cert retriever */
+	x509_tls_peers_put_cert,      /* Cert writer */
+	x509_tls_peers_delete_cert,   /* Cert remover */
+	x509_tls_peers_get_idlist     /* idlist retriever */
+};
+
+
+/***** A Verifier that uses the tls_peers cache and the CA pool to validate certificates *****/
+static PurpleCertificateVerifier x509_tls_cached;
+
+
+/* The following is several hacks piled together and needs to be fixed.
+ * It exists because show_cert (see its comments) needs the original reason
+ * given to user_auth in order to rebuild the dialog.
+ */
+/* TODO: This will cause a ua_ctx to become memleaked if the request(s) get
+   closed by handle or otherwise abnormally. */
+typedef struct {
+	PurpleCertificateVerificationRequest *vrq;
+	gchar *reason;
+} x509_tls_cached_ua_ctx;
+
+static x509_tls_cached_ua_ctx *
+x509_tls_cached_ua_ctx_new(PurpleCertificateVerificationRequest *vrq,
+			   const gchar *reason)
+{
+	x509_tls_cached_ua_ctx *c;
+
+	c = g_new0(x509_tls_cached_ua_ctx, 1);
+	c->vrq = vrq;
+	c->reason = g_strdup(reason);
+
+	return c;
+}
+
+
+static void
+x509_tls_cached_ua_ctx_free(x509_tls_cached_ua_ctx *c)
+{
+	g_return_if_fail(c);
+	g_free(c->reason);
+	g_free(c);
+}
+
+static void
+x509_tls_cached_user_auth(PurpleCertificateVerificationRequest *vrq,
+			  const gchar *reason);
+
+static void
+x509_tls_cached_show_cert(x509_tls_cached_ua_ctx *c, gint id)
+{
+	PurpleCertificate *disp_crt = c->vrq->cert_chain->data;
+	purple_certificate_display_x509(disp_crt);
+
+	/* Since clicking a button closes the request, show it again */
+	x509_tls_cached_user_auth(c->vrq, c->reason);
+
+	x509_tls_cached_ua_ctx_free(c);
+}
+
+static void
+x509_tls_cached_user_auth_cb (x509_tls_cached_ua_ctx *c, gint id)
+{
+	PurpleCertificateVerificationRequest *vrq;
+	PurpleCertificatePool *tls_peers;
+
+	g_return_if_fail(c);
+	g_return_if_fail(c->vrq);
+	
+	vrq = c->vrq;
+
+	x509_tls_cached_ua_ctx_free(c);
+
+	tls_peers = purple_certificate_find_pool("x509","tls_peers");
+
+	if (2 == id) {
+		gchar *cache_id = vrq->subject_name;
+		purple_debug_info("certificate/x509/tls_cached",
+				  "User ACCEPTED cert\nCaching first in chain for future use as %s...\n",
+				  cache_id);
+		
+		purple_certificate_pool_store(tls_peers, cache_id,
+					      vrq->cert_chain->data);
+
+		purple_certificate_verify_complete(vrq,
+						   PURPLE_CERTIFICATE_VALID);
+	} else {
+		purple_debug_info("certificate/x509/tls_cached",
+				  "User REJECTED cert\n");
+		purple_certificate_verify_complete(vrq,
+						   PURPLE_CERTIFICATE_INVALID);
+	}
+}
+
+/** Validates a certificate by asking the user
+ * @param reason    String to explain why the user needs to accept/refuse the
+ *                  certificate.
+ * @todo Needs a handle argument
+ */
+static void
+x509_tls_cached_user_auth(PurpleCertificateVerificationRequest *vrq,
+			  const gchar *reason)
+{
+	gchar *primary;
+
+	/* Make messages */
+	primary = g_strdup_printf(_("Accept certificate for %s?"),
+				  vrq->subject_name);
+		
+	/* Make a semi-pretty display */
+	purple_request_action(
+		vrq->cb_data, /* TODO: Find what the handle ought to be */
+		_("SSL Certificate Verification"),
+		primary,
+		reason,
+		2,            /* Accept by default */
+		NULL,         /* No account */
+		NULL,         /* No other user */
+		NULL,         /* No associated conversation */
+		x509_tls_cached_ua_ctx_new(vrq, reason),
+		3,            /* Number of actions */
+		_("Yes"), x509_tls_cached_user_auth_cb,
+		_("No"),  x509_tls_cached_user_auth_cb,
+		_("_View Certificate..."), x509_tls_cached_show_cert);
+	
+	/* Cleanup */
+	g_free(primary);
+}
+
+static void
+x509_tls_cached_peer_cert_changed(PurpleCertificateVerificationRequest *vrq)
+{
+	/* TODO: Prompt the user, etc. */
+
+	purple_debug_info("certificate/x509/tls_cached",
+			  "Certificate for %s does not match cached. "
+			  "Auto-rejecting!\n",
+			  vrq->subject_name);
+	
+	purple_certificate_verify_complete(vrq, PURPLE_CERTIFICATE_INVALID);
+	return;
+}
+
+static void
+x509_tls_cached_cert_in_cache(PurpleCertificateVerificationRequest *vrq)
+{
+	/* TODO: Looking this up by name over and over is expensive.
+	   Fix, please! */
+	PurpleCertificatePool *tls_peers =
+		purple_certificate_find_pool(x509_tls_cached.scheme_name,
+					     "tls_peers");
+
+	/* The peer's certificate should be the first in the list */
+	PurpleCertificate *peer_crt =
+		(PurpleCertificate *) vrq->cert_chain->data;
+	
+	PurpleCertificate *cached_crt;
+	GByteArray *peer_fpr, *cached_fpr;
+
+	/* Load up the cached certificate */
+	cached_crt = purple_certificate_pool_retrieve(
+		tls_peers, vrq->subject_name);
+	g_assert(cached_crt);
+
+	/* Now get SHA1 sums for both and compare them */
+	/* TODO: This is not an elegant way to compare certs */
+	peer_fpr = purple_certificate_get_fingerprint_sha1(peer_crt);
+	cached_fpr = purple_certificate_get_fingerprint_sha1(cached_crt);
+	if (!memcmp(peer_fpr->data, cached_fpr->data, peer_fpr->len)) {
+		purple_debug_info("certificate/x509/tls_cached",
+				  "Peer cert matched cached\n");
+		/* vrq is now finished */
+		purple_certificate_verify_complete(vrq,
+						   PURPLE_CERTIFICATE_VALID);
+	} else {
+		purple_debug_info("certificate/x509/tls_cached",
+				  "Peer cert did NOT match cached\n");
+		/* vrq now becomes the problem of cert_changed */
+		x509_tls_cached_peer_cert_changed(vrq);
+	}
+	
+	purple_certificate_destroy(cached_crt);
+	g_byte_array_free(peer_fpr, TRUE);
+	g_byte_array_free(cached_fpr, TRUE);
+}
+
+/* For when we've never communicated with this party before */
+static void
+x509_tls_cached_unknown_peer(PurpleCertificateVerificationRequest *vrq)
+{
+	PurpleCertificatePool *ca, *tls_peers;
+	PurpleCertificate *end_crt, *ca_crt, *peer_crt;
+	GList *chain = vrq->cert_chain;
+	GList *last;
+	gchar *ca_id;
+
+	peer_crt = (PurpleCertificate *) chain->data;
+
+	/* First, check that the hostname matches */
+	if ( ! purple_certificate_check_subject_name(peer_crt,
+						     vrq->subject_name) ) {
+		gchar *sn = purple_certificate_get_subject_name(peer_crt);
+		gchar *msg;
+		
+		purple_debug_info("certificate/x509/tls_cached",
+				  "Name mismatch: Certificate given for %s "
+				  "has a name of %s\n",
+				  vrq->subject_name, sn);
+
+		/* Prompt the user to authenticate the certificate */
+		/* TODO: Provide the user with more guidance about why he is
+		   being prompted */
+		/* vrq will be completed by user_auth */
+		msg = g_strdup_printf(_("The certificate presented by \"%s\" "
+					"claims to be from \"%s\" instead.  "
+					"This could mean that you are not "
+					"connecting to the service you "
+					"believe you are."),
+				      vrq->subject_name, sn);
+				      
+		x509_tls_cached_user_auth(vrq,msg);
+
+		g_free(sn);
+		g_free(msg);
+		return;
+	} /* if (name mismatch) */
+
+			
+	
+	/* Next, check that the certificate chain is valid */
+	if ( ! purple_certificate_check_signature_chain(chain) ) {
+		/* TODO: Tell the user where the chain broke? */
+		/* TODO: This error will hopelessly confuse any
+		   non-elite user. */
+		gchar *secondary;
+
+		secondary = g_strdup_printf(_("The certificate chain presented"
+					      " for %s is not valid."),
+					    vrq->subject_name);
+
+		/* TODO: Make this error either block the ensuing SSL
+		   connection error until the user dismisses this one, or
+		   stifle it. */
+		purple_notify_error(NULL, /* TODO: Probably wrong. */
+				    _("SSL Certificate Error"),
+				    _("Invalid certificate chain"),
+				    secondary );
+		g_free(secondary);
+
+		/* Okay, we're done here */
+		purple_certificate_verify_complete(vrq,
+						   PURPLE_CERTIFICATE_INVALID);
+	} /* if (signature chain not good) */
+
+	/* Next, attempt to verify the last certificate against a CA */
+	ca = purple_certificate_find_pool(x509_tls_cached.scheme_name, "ca");
+
+	/* If, for whatever reason, there is no Certificate Authority pool
+	   loaded, we will simply present it to the user for checking. */
+	if ( !ca ) {
+		purple_debug_error("certificate/x509/tls_cached",
+				   "No X.509 Certificate Authority pool "
+				   "could be found!\n");
+
+		/* vrq will be completed by user_auth */
+		x509_tls_cached_user_auth(vrq,_("You have no database of root "
+						"certificates, so this "
+						"certificate cannot be "
+						"validated."));
+		return;
+	}
+
+	last = g_list_last(chain);
+	end_crt = (PurpleCertificate *) last->data;
+
+	/* Attempt to look up the last certificate's issuer */
+	ca_id = purple_certificate_get_issuer_unique_id(end_crt);
+	purple_debug_info("certificate/x509/tls_cached",
+			  "Checking for a CA with DN=%s\n",
+			  ca_id);
+	if ( !purple_certificate_pool_contains(ca, ca_id) ) {
+		purple_debug_info("certificate/x509/tls_cached",
+				  "Certificate Authority with DN='%s' not "
+				  "found. I'll prompt the user, I guess.\n",
+				  ca_id);
+		g_free(ca_id);
+		/* vrq will be completed by user_auth */
+		x509_tls_cached_user_auth(vrq,_("The root certificate this "
+						"one claims to be issued by "
+						"is unknown to Pidgin."));
+		return;
+	}
+
+	ca_crt = purple_certificate_pool_retrieve(ca, ca_id);
+	g_free(ca_id);
+	g_assert(ca_crt);
+	
+	/* Check the signature */
+	if ( !purple_certificate_signed_by(end_crt, ca_crt) ) {
+		/* TODO: If signed_by ever returns a reason, maybe mention
+		   that, too. */
+		/* TODO: Also mention the CA involved. While I could do this
+		   now, a full DN is a little much with which to assault the
+		   user's poor, leaky eyes. */
+		/* TODO: This error message makes my eyes cross, and I wrote it */
+		gchar * secondary =
+			g_strdup_printf(_("The certificate chain presented by "
+					  "%s does not have a valid digital "
+					  "signature from the Certificate "
+					  "Authority from which it claims to "
+					  "have a signature."),
+					vrq->subject_name);
+		
+		purple_notify_error(NULL, /* TODO: Probably wrong */
+				    _("SSL Certificate Error"),
+				    _("Invalid certificate authority"
+				      " signature"),
+				    secondary);
+		g_free(secondary);
+
+		/* Signal "bad cert" */
+		purple_certificate_verify_complete(vrq,
+						   PURPLE_CERTIFICATE_INVALID);
+		return;
+	} /* if (CA signature not good) */
+
+	/* If we reach this point, the certificate is good. */
+	/* Look up the local cache and store it there for future use */
+	tls_peers = purple_certificate_find_pool(x509_tls_cached.scheme_name,
+						 "tls_peers");
+
+	if (tls_peers) {
+		g_assert(purple_certificate_pool_store(tls_peers,
+						       vrq->subject_name,
+						       peer_crt) );
+	} else {
+		purple_debug_error("certificate/x509/tls_cached",
+				   "Unable to locate tls_peers certificate "
+				   "cache.\n");
+	}
+	
+	/* Whew! Done! */
+	purple_certificate_verify_complete(vrq, PURPLE_CERTIFICATE_VALID);
+}
+
+static void
+x509_tls_cached_start_verify(PurpleCertificateVerificationRequest *vrq)
+{
+	const gchar *tls_peers_name = "tls_peers"; /* Name of local cache */
+	PurpleCertificatePool *tls_peers;
+
+	g_return_if_fail(vrq);
+
+	purple_debug_info("certificate/x509/tls_cached",
+			  "Starting verify for %s\n",
+			  vrq->subject_name);
+	
+	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",
+				   tls_peers_name);
+
+		purple_certificate_verify_complete(vrq,
+						   PURPLE_CERTIFICATE_INVALID);
+		return;
+	}
+	
+	/* Check if the peer has a certificate cached already */
+	purple_debug_info("certificate/x509/tls_cached",
+			  "Checking for cached cert...\n");
+	if (purple_certificate_pool_contains(tls_peers, vrq->subject_name)) {
+		purple_debug_info("certificate/x509/tls_cached",
+				  "...Found cached cert\n");
+		/* vrq is now the responsibility of cert_in_cache */
+		x509_tls_cached_cert_in_cache(vrq);
+	} else {
+		purple_debug_info("certificate/x509/tls_cached",
+				  "...Not in cache\n");
+		/* vrq now becomes the problem of unknown_peer */
+		x509_tls_cached_unknown_peer(vrq);
+	}
+}
+
+static void
+x509_tls_cached_destroy_request(PurpleCertificateVerificationRequest *vrq)
+{
+	g_return_if_fail(vrq);
+}
+
+static PurpleCertificateVerifier x509_tls_cached = {
+	"x509",                         /* Scheme name */
+	"tls_cached",                   /* Verifier name */
+	x509_tls_cached_start_verify,   /* Verification begin */
+	x509_tls_cached_destroy_request /* Request cleanup */
+};
+
+/****************************************************************************/
+/* Subsystem                                                                */
+/****************************************************************************/
+void
+purple_certificate_init(void)
+{
+	/* Register builtins */
+	purple_certificate_register_verifier(&x509_singleuse);
+	purple_certificate_register_pool(&x509_ca);
+	purple_certificate_register_pool(&x509_tls_peers);
+	purple_certificate_register_verifier(&x509_tls_cached);
+}
+
+void
+purple_certificate_uninit(void)
+{
+	GList *full_list, *l;
+
+	/* Unregister all Schemes */
+	full_list = g_list_copy(cert_schemes); /* Make a working copy */
+	for (l = full_list; l; l = l->next) {
+		purple_certificate_unregister_scheme(
+			(PurpleCertificateScheme *) l->data );
+	}
+	g_list_free(full_list);
+
+	/* Unregister all Verifiers */
+	full_list = g_list_copy(cert_verifiers); /* Make a working copy */
+	for (l = full_list; l; l = l->next) {
+		purple_certificate_unregister_verifier(
+			(PurpleCertificateVerifier *) l->data );
+	}
+	g_list_free(full_list);
+
+	/* Unregister all Pools */
+	full_list = g_list_copy(cert_pools); /* Make a working copy */
+	for (l = full_list; l; l = l->next) {
+		purple_certificate_unregister_pool(
+			(PurpleCertificatePool *) l->data );
+	}
+	g_list_free(full_list);
+}
+
+gpointer
+purple_certificate_get_handle(void)
+{
+	static gint handle;
+	return &handle;
+}
+
+PurpleCertificateScheme *
+purple_certificate_find_scheme(const gchar *name)
+{
+	PurpleCertificateScheme *scheme = NULL;
+	GList *l;
+
+	g_return_val_if_fail(name, NULL);
+
+	/* Traverse the list of registered schemes and locate the
+	   one whose name matches */
+	for(l = cert_schemes; l; l = l->next) {
+		scheme = (PurpleCertificateScheme *)(l->data);
+
+		/* Name matches? that's our man */
+		if(!g_ascii_strcasecmp(scheme->name, name))
+			return scheme;
+	}
+
+	purple_debug_warning("certificate",
+			     "CertificateScheme %s requested but not found.\n",
+			     name);
+
+	/* TODO: Signalling and such? */
+	
+	return NULL;
+}
+
+GList *
+purple_certificate_get_schemes(void)
+{
+	return cert_schemes;
+}
+
+gboolean
+purple_certificate_register_scheme(PurpleCertificateScheme *scheme)
+{
+	g_return_val_if_fail(scheme != NULL, FALSE);
+
+	/* Make sure no scheme is registered with the same name */
+	if (purple_certificate_find_scheme(scheme->name) != NULL) {
+		return FALSE;
+	}
+
+	/* Okay, we're golden. Register it. */
+	cert_schemes = g_list_prepend(cert_schemes, scheme);
+
+	/* TODO: Signalling and such? */
+
+	purple_debug_info("certificate",
+			  "CertificateScheme %s registered\n",
+			  scheme->name);
+	
+	return TRUE;
+}
+
+gboolean
+purple_certificate_unregister_scheme(PurpleCertificateScheme *scheme)
+{
+	if (NULL == scheme) {
+		purple_debug_warning("certificate",
+				     "Attempting to unregister NULL scheme\n");
+		return FALSE;
+	}
+
+	/* TODO: signalling? */
+
+	/* TODO: unregister all CertificateVerifiers for this scheme?*/
+	/* TODO: unregister all CertificatePools for this scheme? */
+	/* Neither of the above should be necessary, though */
+	cert_schemes = g_list_remove(cert_schemes, scheme);
+
+	purple_debug_info("certificate",
+			  "CertificateScheme %s unregistered\n",
+			  scheme->name);
+
+
+	return TRUE;
+}
+
+PurpleCertificateVerifier *
+purple_certificate_find_verifier(const gchar *scheme_name, const gchar *ver_name)
+{
+	PurpleCertificateVerifier *vr = NULL;
+	GList *l;
+
+	g_return_val_if_fail(scheme_name, NULL);
+	g_return_val_if_fail(ver_name, NULL);
+
+	/* Traverse the list of registered verifiers and locate the
+	   one whose name matches */
+	for(l = cert_verifiers; l; l = l->next) {
+		vr = (PurpleCertificateVerifier *)(l->data);
+
+		/* Scheme and name match? */
+		if(!g_ascii_strcasecmp(vr->scheme_name, scheme_name) &&
+		   !g_ascii_strcasecmp(vr->name, ver_name))
+			return vr;
+	}
+
+	purple_debug_warning("certificate",
+			     "CertificateVerifier %s, %s requested but not found.\n",
+			     scheme_name, ver_name);
+
+	/* TODO: Signalling and such? */
+	
+	return NULL;
+}
+
+
+GList *
+purple_certificate_get_verifiers(void)
+{
+	return cert_verifiers;
+}
+
+gboolean
+purple_certificate_register_verifier(PurpleCertificateVerifier *vr)
+{
+	g_return_val_if_fail(vr != NULL, FALSE);
+
+	/* Make sure no verifier is registered with the same scheme/name */
+	if (purple_certificate_find_verifier(vr->scheme_name, vr->name) != NULL) {
+		return FALSE;
+	}
+
+	/* Okay, we're golden. Register it. */
+	cert_verifiers = g_list_prepend(cert_verifiers, vr);
+
+	/* TODO: Signalling and such? */
+
+	purple_debug_info("certificate",
+			  "CertificateVerifier %s registered\n",
+			  vr->name);
+	return TRUE;
+}
+
+gboolean
+purple_certificate_unregister_verifier(PurpleCertificateVerifier *vr)
+{
+	if (NULL == vr) {
+		purple_debug_warning("certificate",
+				     "Attempting to unregister NULL verifier\n");
+		return FALSE;
+	}
+
+	/* TODO: signalling? */
+
+	cert_verifiers = g_list_remove(cert_verifiers, vr);
+
+
+	purple_debug_info("certificate",
+			  "CertificateVerifier %s unregistered\n",
+			  vr->name);
+
+	return TRUE;
+}
+
+PurpleCertificatePool *
+purple_certificate_find_pool(const gchar *scheme_name, const gchar *pool_name)
+{
+	PurpleCertificatePool *pool = NULL;
+	GList *l;
+
+	g_return_val_if_fail(scheme_name, NULL);
+	g_return_val_if_fail(pool_name, NULL);
+
+	/* Traverse the list of registered pools and locate the
+	   one whose name matches */
+	for(l = cert_pools; l; l = l->next) {
+		pool = (PurpleCertificatePool *)(l->data);
+
+		/* Scheme and name match? */
+		if(!g_ascii_strcasecmp(pool->scheme_name, scheme_name) &&
+		   !g_ascii_strcasecmp(pool->name, pool_name))
+			return pool;
+	}
+
+	purple_debug_warning("certificate",
+			     "CertificatePool %s, %s requested but not found.\n",
+			     scheme_name, pool_name);
+
+	/* TODO: Signalling and such? */
+	
+	return NULL;
+
+}
+
+GList *
+purple_certificate_get_pools(void)
+{
+	return cert_pools;
+}
+
+gboolean
+purple_certificate_register_pool(PurpleCertificatePool *pool)
+{
+	gboolean success = FALSE;
+	g_return_val_if_fail(pool, FALSE);
+	g_return_val_if_fail(pool->scheme_name, FALSE);
+	g_return_val_if_fail(pool->name, FALSE);
+	g_return_val_if_fail(pool->fullname, FALSE);
+
+	/* Make sure no pools are registered under this name */
+	if (purple_certificate_find_pool(pool->scheme_name, pool->name)) {
+		return FALSE;
+	}
+
+	/* Initialize the pool if needed */
+	if (pool->init) {
+		success = pool->init();
+	} else {
+		success = TRUE;
+	}
+	
+	if (success) {
+		/* Register the Pool */
+		cert_pools = g_list_prepend(cert_pools, pool);
+
+		/* TODO: Emit a signal that the pool got registered */
+
+		purple_signal_register(pool, /* Signals emitted from pool */
+				       "certificate-stored",
+				       purple_marshal_VOID__POINTER_POINTER,
+				       NULL, /* No callback return value */
+				       2,    /* Two non-data arguments */
+				       purple_value_new(PURPLE_TYPE_SUBTYPE,
+							PURPLE_SUBTYPE_CERTIFICATEPOOL),
+				       purple_value_new(PURPLE_TYPE_STRING));
+
+		purple_signal_register(pool, /* Signals emitted from pool */
+				       "certificate-deleted",
+				       purple_marshal_VOID__POINTER_POINTER,
+				       NULL, /* No callback return value */
+				       2,    /* Two non-data arguments */
+				       purple_value_new(PURPLE_TYPE_SUBTYPE,
+							PURPLE_SUBTYPE_CERTIFICATEPOOL),
+				       purple_value_new(PURPLE_TYPE_STRING));
+
+
+		purple_debug_info("certificate",
+			  "CertificatePool %s registered\n",
+			  pool->name);
+		return TRUE;
+	} else {
+		return FALSE;
+	}
+	
+	/* Control does not reach this point */
+}
+
+gboolean
+purple_certificate_unregister_pool(PurpleCertificatePool *pool)
+{
+	if (NULL == pool) {
+		purple_debug_warning("certificate",
+				     "Attempting to unregister NULL pool\n");
+		return FALSE;
+	}
+
+	/* Check that the pool is registered */
+	if (!g_list_find(cert_pools, pool)) {
+		purple_debug_warning("certificate",
+				     "Pool to unregister isn't registered!\n");
+
+		return FALSE;
+	}
+
+	/* Uninit the pool if needed */
+	if (pool->uninit) {
+		pool->uninit();
+	}
+
+	cert_pools = g_list_remove(cert_pools, pool);
+	
+	/* TODO: Signalling? */
+	purple_signal_unregister(pool, "certificate-stored");
+	purple_signal_unregister(pool, "certificate-deleted");
+		
+	purple_debug_info("certificate",
+			  "CertificatePool %s unregistered\n",
+			  pool->name);
+	return TRUE;
+}
+
+/****************************************************************************/
+/* Scheme-specific functions                                                */
+/****************************************************************************/
+
+void
+purple_certificate_display_x509(PurpleCertificate *crt)
+{
+	gchar *sha_asc;
+	GByteArray *sha_bin;
+	gchar *cn;
+	time_t activation, expiration;
+	/* Length of these buffers is dictated by 'man ctime_r' */
+	gchar *activ_str, *expir_str;
+	gchar *secondary;
+
+	/* Pull out the SHA1 checksum */
+	sha_bin = purple_certificate_get_fingerprint_sha1(crt);
+	/* Now decode it for display */
+	sha_asc = purple_base16_encode_chunked(sha_bin->data,
+					       sha_bin->len);
+
+	/* Get the cert Common Name */
+	/* TODO: Will break on CA certs */
+	cn = purple_certificate_get_subject_name(crt);
+
+	/* Get the certificate times */
+	/* TODO: Check the times against localtime */
+	/* TODO: errorcheck? */
+	g_assert(purple_certificate_get_times(crt, &activation, &expiration));
+	activ_str = g_strdup(ctime(&activation));
+	expir_str = g_strdup(ctime(&expiration));
+
+	/* Make messages */
+	secondary = g_strdup_printf(_("Common name: %s\n\n"
+				      "Fingerprint (SHA1): %s\n\n"
+				      "Activation date: %s\n"
+				      "Expiration date: %s\n"),
+				    cn, sha_asc, activ_str, expir_str);
+
+	/* Make a semi-pretty display */
+	purple_notify_info(
+		NULL,         /* TODO: Find what the handle ought to be */
+		_("Certificate Information"),
+		"",
+		secondary);
+
+	/* Cleanup */
+	g_free(cn);
+	g_free(secondary);
+	g_free(sha_asc);
+	g_free(activ_str);
+	g_free(expir_str);
+	g_byte_array_free(sha_bin, TRUE);
+}
+
+
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/certificate.h	Tue Aug 28 09:20:27 2007 +0000
@@ -0,0 +1,779 @@
+/**
+ * @file certificate.h Public-Key Certificate API
+ * @ingroup core
+ */
+
+/*
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#ifndef _PURPLE_CERTIFICATE_H
+#define _PURPLE_CERTIFICATE_H
+
+#include <glib.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+
+typedef enum
+{
+	PURPLE_CERTIFICATE_INVALID = 0,
+	PURPLE_CERTIFICATE_VALID = 1
+} PurpleCertificateVerificationStatus;
+
+typedef struct _PurpleCertificate PurpleCertificate;
+typedef struct _PurpleCertificatePool PurpleCertificatePool;
+typedef struct _PurpleCertificateScheme PurpleCertificateScheme;
+typedef struct _PurpleCertificateVerifier PurpleCertificateVerifier;
+typedef struct _PurpleCertificateVerificationRequest PurpleCertificateVerificationRequest;
+
+/**
+ * Callback function for the results of a verification check
+ * @param st       Status code
+ * @param userdata User-defined data
+ */
+typedef void (*PurpleCertificateVerifiedCallback)
+		(PurpleCertificateVerificationStatus st,
+		 gpointer userdata);
+							  
+/** A certificate instance
+ *
+ *  An opaque data structure representing a single certificate under some
+ *  CertificateScheme
+ */
+struct _PurpleCertificate
+{
+	/** Scheme this certificate is under */
+	PurpleCertificateScheme * scheme;
+	/** Opaque pointer to internal data */
+	gpointer data;
+};
+
+/**
+ * Database for retrieval or storage of Certificates
+ *
+ * More or less a hash table; all lookups and writes are controlled by a string
+ * key.
+ */
+struct _PurpleCertificatePool
+{
+	/** Scheme this Pool operates for */
+	gchar *scheme_name;
+	/** Internal name to refer to the pool by */
+	gchar *name;
+
+	/** User-friendly name for this type
+	 *  ex: N_("SSL Servers")
+	 *  When this is displayed anywhere, it should be i18ned
+	 *  ex: _(pool->fullname)
+	 */
+	gchar *fullname;
+
+	/** Internal pool data */
+	gpointer data;
+	
+	/**
+	 * Set up the Pool's internal state
+	 *
+	 * Upon calling purple_certificate_register_pool() , this function will
+	 * be called. May be NULL.
+	 * @return TRUE if the initialization succeeded, otherwise FALSE
+	 */
+	gboolean (* init)(void);
+
+	/**
+	 * Uninit the Pool's internal state
+	 *
+	 * Will be called by purple_certificate_unregister_pool() . May be NULL
+	 */
+	void (* uninit)(void);
+
+	/** Check for presence of a certificate in the pool using unique ID */
+	gboolean (* cert_in_pool)(const gchar *id);
+	/** Retrieve a PurpleCertificate from the pool */
+	PurpleCertificate * (* get_cert)(const gchar *id);
+	/** Add a certificate to the pool. Must overwrite any other
+	 *  certificates sharing the same ID in the pool.
+	 *  @return TRUE if the operation succeeded, otherwise FALSE
+	 */
+	gboolean (* put_cert)(const gchar *id, PurpleCertificate *crt);
+	/** Delete a certificate from the pool */
+	gboolean (* delete_cert)(const gchar *id);
+
+	/** Returns a list of IDs stored in the pool */
+	GList * (* get_idlist)(void);
+};
+
+/** A certificate type
+ *
+ *  A CertificateScheme must implement all of the fields in the structure,
+ *  and register it using purple_certificate_register_scheme()
+ *
+ *  There may be only ONE CertificateScheme provided for each certificate
+ *  type, as specified by the "name" field.
+ */
+struct _PurpleCertificateScheme
+{
+	/** Name of the certificate type
+	 *  ex: "x509", "pgp", etc.
+	 *  This must be globally unique - you may not register more than one
+	 *  CertificateScheme of the same name at a time.
+	 */
+	gchar * name;
+
+	/** User-friendly name for this type
+	 *  ex: N_("X.509 Certificates")
+	 *  When this is displayed anywhere, it should be i18ned
+	 *  ex: _(scheme->fullname)
+	 */
+	gchar * fullname;
+
+	/** Imports a certificate from a file
+	 *
+	 *  @param filename   File to import the certificate from
+	 *  @return           Pointer to the newly allocated Certificate struct
+	 *                    or NULL on failure.
+	 */
+	PurpleCertificate * (* import_certificate)(const gchar * filename);
+
+	/**
+	 * Exports a certificate to a file
+	 *
+	 * @param filename    File to export the certificate to
+	 * @param crt         Certificate to export
+	 * @return TRUE if the export succeeded, otherwise FALSE
+	 * @see purple_certificate_export()
+	 */
+	gboolean (* export_certificate)(const gchar *filename, PurpleCertificate *crt);
+
+	/**
+	 * Duplicates a certificate
+	 *
+	 * Certificates are generally assumed to be read-only, so feel free to
+	 * do any sort of reference-counting magic you want here. If this ever
+	 * changes, please remember to change the magic accordingly.
+	 * @return Reference to the new copy
+	 */
+	PurpleCertificate * (* copy_certificate)(PurpleCertificate *crt);
+
+	/** Destroys and frees a Certificate structure
+	 *
+	 *  Destroys a Certificate's internal data structures and calls
+	 *  free(crt)
+	 *
+	 *  @param crt  Certificate instance to be destroyed. It WILL NOT be
+	 *              destroyed if it is not of the correct
+	 *              CertificateScheme. Can be NULL
+	 */
+	void (* destroy_certificate)(PurpleCertificate * crt);
+
+	/** Find whether "crt" has a valid signature from issuer "issuer"
+	 *  @see purple_certificate_signed_by() */
+	gboolean (*signed_by)(PurpleCertificate *crt, PurpleCertificate *issuer);
+	/**
+	 * Retrieves the certificate public key fingerprint using SHA1
+	 *
+	 * @param crt   Certificate instance
+	 * @return Binary representation of SHA1 hash - must be freed using
+	 *         g_byte_array_free()
+	 */
+	GByteArray * (* get_fingerprint_sha1)(PurpleCertificate *crt);
+
+	/**
+	 * Retrieves a unique certificate identifier
+	 *
+	 * @param crt   Certificate instance
+	 * @return Newly allocated string that can be used to uniquely
+	 *         identify the certificate.
+	 */
+	gchar * (* get_unique_id)(PurpleCertificate *crt);
+
+	/**
+	 * Retrieves a unique identifier for the certificate's issuer
+	 *
+	 * @param crt   Certificate instance
+	 * @return Newly allocated string that can be used to uniquely
+	 *         identify the issuer's certificate.
+	 */
+	gchar * (* get_issuer_unique_id)(PurpleCertificate *crt);
+
+	/**
+	 * Gets the certificate subject's name
+	 *
+	 * For X.509, this is the "Common Name" field, as we're only using it
+	 * for hostname verification at the moment
+	 *
+	 * @see purple_certificate_get_subject_name()
+	 *
+	 * @param crt   Certificate instance
+	 * @return Newly allocated string with the certificate subject.
+	 */
+	gchar * (* get_subject_name)(PurpleCertificate *crt);
+
+	/**
+	 * Check the subject name against that on the certificate
+	 * @see purple_certificate_check_subject_name()
+	 * @return TRUE if it is a match, else FALSE
+	 */
+	gboolean (* check_subject_name)(PurpleCertificate *crt, const gchar *name);
+
+	/** Retrieve the certificate activation/expiration times */
+	gboolean (* get_times)(PurpleCertificate *crt, time_t *activation, time_t *expiration);
+	
+	/* TODO: Fill out this structure */
+};
+
+/** A set of operations used to provide logic for verifying a Certificate's
+ *  authenticity.
+ *
+ * A Verifier provider must fill out these fields, then register it using
+ * purple_certificate_register_verifier()
+ *
+ * The (scheme_name, name) value must be unique for each Verifier - you may not
+ * register more than one Verifier of the same name for each Scheme
+ */
+struct _PurpleCertificateVerifier
+{
+	/** Name of the scheme this Verifier operates on
+	 *
+	 * The scheme will be looked up by name when a Request is generated
+	 * using this Verifier
+	 */
+	gchar *scheme_name;
+
+	/** Name of the Verifier - case insensitive */
+	gchar *name;
+	
+	/**
+	 * Start the verification process
+	 *
+	 * To be called from purple_certificate_verify once it has
+	 * constructed the request. This will use the information in the
+	 * given VerificationRequest to check the certificate and callback
+	 * the requester with the verification results.
+	 *
+	 * @param vrq      Request to process
+	 */
+	void (* start_verification)(PurpleCertificateVerificationRequest *vrq);
+
+	/**
+	 * Destroy a completed Request under this Verifier
+	 * The function pointed to here is only responsible for cleaning up
+	 * whatever PurpleCertificateVerificationRequest::data points to.
+	 * It should not call free(vrq)
+	 *
+	 * @param vrq       Request to destroy
+	 */
+	void (* destroy_request)(PurpleCertificateVerificationRequest *vrq);
+};
+
+/** Structure for a single certificate request
+ *
+ *  Useful for keeping track of the state of a verification that involves
+ *  several steps
+ */
+struct _PurpleCertificateVerificationRequest
+{
+	/** Reference to the verification logic used */
+	PurpleCertificateVerifier *verifier;
+	/** Reference to the scheme used.
+	 *
+	 * This is looked up from the Verifier when the Request is generated
+	 */
+	PurpleCertificateScheme *scheme;
+
+	/**
+	 * Name to check that the certificate is issued to
+	 *
+	 * For X.509 certificates, this is the Common Name
+	 */
+	gchar *subject_name;
+	
+	/** List of certificates in the chain to be verified (such as that returned by purple_ssl_get_peer_certificates )
+	 *
+	 * This is most relevant for X.509 certificates used in SSL sessions.
+	 * The list order should be: certificate, issuer, issuer's issuer, etc.
+	 */
+	GList *cert_chain;
+	
+	/** Internal data used by the Verifier code */
+	gpointer data;
+
+	/** Function to call with the verification result */
+	PurpleCertificateVerifiedCallback cb;
+	/** Data to pass to the post-verification callback */
+	gpointer cb_data;
+};
+
+/*****************************************************************************/
+/** @name Certificate Verification Functions                                 */
+/*****************************************************************************/
+/*@{*/
+
+/**
+ * Constructs a verification request and passed control to the specified Verifier
+ *
+ * It is possible that the callback will be called immediately upon calling
+ * this function. Plan accordingly.
+ *
+ * @param verifier      Verification logic to use.
+ *                      @see purple_certificate_find_verifier()
+ *
+ * @param subject_name  Name that should match the first certificate in the
+ *                      chain for the certificate to be valid. Will be strdup'd
+ *                      into the Request struct
+ *
+ * @param cert_chain    Certificate chain to check. If there is more than one
+ *                      certificate in the chain (X.509), the peer's
+ *                      certificate comes first, then the issuer/signer's
+ *                      certificate, etc. The whole list is duplicated into the
+ *                      Request struct.
+ *
+ * @param cb            Callback function to be called with whether the
+ *                      certificate was approved or not.
+ * @param cb_data       User-defined data for the above.
+ */
+void
+purple_certificate_verify (PurpleCertificateVerifier *verifier,
+			   const gchar *subject_name, GList *cert_chain,
+			   PurpleCertificateVerifiedCallback cb,
+			   gpointer cb_data);
+
+/**
+ * Completes and destroys a VerificationRequest
+ *
+ * @param vrq           Request to conclude
+ * @param st            Success/failure code to pass to the request's
+ *                      completion callback.
+ */
+void
+purple_certificate_verify_complete(PurpleCertificateVerificationRequest *vrq,
+				   PurpleCertificateVerificationStatus st);
+
+/*@}*/
+
+/*****************************************************************************/
+/** @name Certificate Functions                                              */
+/*****************************************************************************/
+/*@{*/
+
+/**
+ * Makes a duplicate of a certificate
+ *
+ * @param crt        Instance to duplicate
+ * @return Pointer to new instance
+ */
+PurpleCertificate *
+purple_certificate_copy(PurpleCertificate *crt);
+
+/**
+ * Duplicates an entire list of certificates
+ *
+ * @param crt_list   List to duplicate
+ * @return New list copy
+ */
+GList *
+purple_certificate_copy_list(GList *crt_list);
+
+/**
+ * Destroys and free()'s a Certificate
+ *
+ * @param crt        Instance to destroy. May be NULL.
+ */
+void
+purple_certificate_destroy (PurpleCertificate *crt);
+
+/**
+ * Destroy an entire list of Certificate instances and the containing list
+ *
+ * @param crt_list   List of certificates to destroy. May be NULL.
+ */
+void
+purple_certificate_destroy_list (GList * crt_list);
+
+/**
+ * Check whether 'crt' has a valid signature made by 'issuer'
+ *
+ * @param crt        Certificate instance to check signature of
+ * @param issuer     Certificate thought to have signed 'crt'
+ *
+ * @return TRUE if 'crt' has a valid signature made by 'issuer',
+ *         otherwise FALSE
+ * @TODO Find a way to give the reason (bad signature, not the issuer, etc.) 
+ */
+gboolean
+purple_certificate_signed_by(PurpleCertificate *crt, PurpleCertificate *issuer);
+
+/**
+ * Check that a certificate chain is valid
+ *
+ * Uses purple_certificate_signed_by() to verify that each PurpleCertificate
+ * in the chain carries a valid signature from the next. A single-certificate
+ * chain is considered to be valid.
+ *
+ * @param chain      List of PurpleCertificate instances comprising the chain,
+ *                   in the order certificate, issuer, issuer's issuer, etc.
+ * @return TRUE if the chain is valid. See description.
+ * @TODO Specify which certificate in the chain caused a failure
+ */
+gboolean
+purple_certificate_check_signature_chain(GList *chain);
+
+/**
+ * Imports a PurpleCertificate from a file
+ *
+ * @param scheme      Scheme to import under
+ * @param filename    File path to import from
+ * @return Pointer to a new PurpleCertificate, or NULL on failure
+ */
+PurpleCertificate *
+purple_certificate_import(PurpleCertificateScheme *scheme, const gchar *filename);
+
+/**
+ * Exports a PurpleCertificate to a file
+ *
+ * @param filename    File to export the certificate to
+ * @param crt         Certificate to export
+ * @return TRUE if the export succeeded, otherwise FALSE
+ */
+gboolean
+purple_certificate_export(const gchar *filename, PurpleCertificate *crt);
+
+
+/**
+ * Retrieves the certificate public key fingerprint using SHA1.
+ *
+ * @param crt        Certificate instance
+ * @return Binary representation of the hash. You are responsible for free()ing
+ *         this.
+ * @see purple_base16_encode_chunked()
+ */
+GByteArray *
+purple_certificate_get_fingerprint_sha1(PurpleCertificate *crt);
+
+/**
+ * Get a unique identifier for the certificate
+ *
+ * @param crt        Certificate instance
+ * @return String representing the certificate uniquely. Must be g_free()'ed
+ */
+gchar *
+purple_certificate_get_unique_id(PurpleCertificate *crt);
+
+/**
+ * Get a unique identifier for the certificate's issuer
+ *
+ * @param crt        Certificate instance
+ * @return String representing the certificate's issuer uniquely. Must be
+ *         g_free()'ed
+ */
+gchar *
+purple_certificate_get_issuer_unique_id(PurpleCertificate *crt);
+
+/**
+ * Gets the certificate subject's name
+ *
+ * For X.509, this is the "Common Name" field, as we're only using it
+ * for hostname verification at the moment
+ *
+ * @param crt   Certificate instance
+ * @return Newly allocated string with the certificate subject.
+ */
+gchar *
+purple_certificate_get_subject_name(PurpleCertificate *crt);
+
+/**
+ * Check the subject name against that on the certificate
+ * @param crt   Certificate instance
+ * @param name  Name to check. 
+ * @return TRUE if it is a match, else FALSE
+ */
+gboolean
+purple_certificate_check_subject_name(PurpleCertificate *crt, const gchar *name);
+
+/**
+ * Get the expiration/activation times.
+ *
+ * @param crt          Certificate instance
+ * @param activation   Reference to store the activation time at. May be NULL
+ *                     if you don't actually want it.
+ * @param expiration   Reference to store the expiration time at. May be NULL
+ *                     if you don't actually want it.
+ * @return TRUE if the requested values were obtained, otherwise FALSE.
+ */
+gboolean
+purple_certificate_get_times(PurpleCertificate *crt, time_t *activation, time_t *expiration);
+
+/*@}*/
+
+/*****************************************************************************/
+/** @name Certificate Pool Functions                                         */
+/*****************************************************************************/
+/*@{*/
+/**
+ * Helper function for generating file paths in ~/.purple/certificates for
+ * CertificatePools that use them.
+ *
+ * All components will be escaped for filesystem friendliness.
+ *
+ * @param pool   CertificatePool to build a path for
+ * @param id     Key to look up a Certificate by. May be NULL.
+ * @return A newly allocated path of the form
+ *         ~/.purple/certificates/scheme_name/pool_name/unique_id
+ */
+gchar *
+purple_certificate_pool_mkpath(PurpleCertificatePool *pool, const gchar *id);
+
+/**
+ * Determines whether a pool can be used.
+ *
+ * Checks whether the associated CertificateScheme is loaded.
+ *
+ * @param pool   Pool to check
+ *
+ * @return TRUE if the pool can be used, otherwise FALSE
+ */
+gboolean
+purple_certificate_pool_usable(PurpleCertificatePool *pool);
+
+/**
+ * Looks up the scheme the pool operates under
+ *
+ * @param pool   Pool to get the scheme of
+ *
+ * @return Pointer to the pool's scheme, or NULL if it isn't loaded.
+ * @see purple_certificate_pool_usable()
+ */
+PurpleCertificateScheme *
+purple_certificate_pool_get_scheme(PurpleCertificatePool *pool);
+
+/**
+ * Check for presence of an ID in a pool.
+ * @param pool   Pool to look in
+ * @param id     ID to look for
+ * @return TRUE if the ID is in the pool, else FALSE
+ */
+gboolean
+purple_certificate_pool_contains(PurpleCertificatePool *pool, const gchar *id);
+
+/**
+ * Retrieve a certificate from a pool.
+ * @param pool   Pool to fish in
+ * @param id     ID to look up
+ * @return Retrieved certificate, or NULL if it wasn't there
+ */
+PurpleCertificate *
+purple_certificate_pool_retrieve(PurpleCertificatePool *pool, const gchar *id);
+
+/**
+ * Add a certificate to a pool
+ *
+ * Any pre-existing certificate of the same ID will be overwritten.
+ *
+ * @param pool   Pool to add to
+ * @param id     ID to store the certificate with
+ * @param crt    Certificate to store
+ * @return TRUE if the operation succeeded, otherwise FALSE
+ */
+gboolean
+purple_certificate_pool_store(PurpleCertificatePool *pool, const gchar *id, PurpleCertificate *crt);
+
+/**
+ * Remove a certificate from a pool
+ *
+ * @param pool   Pool to remove from
+ * @param id     ID to remove
+ * @return TRUE if the operation succeeded, otherwise FALSE
+ */
+gboolean
+purple_certificate_pool_delete(PurpleCertificatePool *pool, const gchar *id);
+
+/**
+ * Get the list of IDs currently in the pool.
+ *
+ * @param pool   Pool to enumerate
+ * @return GList pointing to newly-allocated id strings. Free using
+ *         purple_certificate_pool_destroy_idlist()
+ */
+GList *
+purple_certificate_pool_get_idlist(PurpleCertificatePool *pool);
+
+/**
+ * Destroys the result given by purple_certificate_pool_get_idlist()
+ *
+ * @param idlist ID List to destroy
+ */
+void
+purple_certificate_pool_destroy_idlist(GList *idlist);
+
+/*@}*/
+
+/*****************************************************************************/
+/** @name Certificate Subsystem API                                          */
+/*****************************************************************************/
+/*@{*/
+
+/**
+ * Initialize the certificate system
+ */
+void
+purple_certificate_init(void);
+
+/**
+ * Un-initialize the certificate system
+ */
+void
+purple_certificate_uninit(void);
+
+/**
+ * Get the Certificate subsystem handle for signalling purposes
+ */
+gpointer
+purple_certificate_get_handle(void);
+
+/** Look up a registered CertificateScheme by name
+ * @param name   The scheme name. Case insensitive.
+ * @return Pointer to the located Scheme, or NULL if it isn't found.
+ */
+PurpleCertificateScheme *
+purple_certificate_find_scheme(const gchar *name);
+
+/**
+ * Get all registered CertificateSchemes
+ *
+ * @return GList pointing to all registered CertificateSchemes . This value
+ *         is owned by libpurple
+ */
+GList *
+purple_certificate_get_schemes(void);
+
+/** Register a CertificateScheme with libpurple
+ *
+ * No two schemes can be registered with the same name; this function enforces
+ * that.
+ *
+ * @param scheme  Pointer to the scheme to register.
+ * @return TRUE if the scheme was successfully added, otherwise FALSE
+ */
+gboolean
+purple_certificate_register_scheme(PurpleCertificateScheme *scheme);
+
+/** Unregister a CertificateScheme from libpurple
+ *
+ * @param scheme    Scheme to unregister.
+ *                  If the scheme is not registered, this is a no-op.
+ *
+ * @return TRUE if the unregister completed successfully
+ */
+gboolean
+purple_certificate_unregister_scheme(PurpleCertificateScheme *scheme);
+
+/** Look up a registered PurpleCertificateVerifier by scheme and name
+ * @param scheme_name  Scheme name. Case insensitive.
+ * @param ver_name     The verifier name. Case insensitive.
+ * @return Pointer to the located Verifier, or NULL if it isn't found.
+ */
+PurpleCertificateVerifier *
+purple_certificate_find_verifier(const gchar *scheme_name, const gchar *ver_name);
+
+/**
+ * Get the list of registered CertificateVerifiers
+ *
+ * @return GList of all registered PurpleCertificateVerifier. This value
+ *         is owned by libpurple
+ */
+GList *
+purple_certificate_get_verifiers(void);
+
+/**
+ * Register a CertificateVerifier with libpurple
+ *
+ * @param vr     Verifier to register.
+ * @return TRUE if register succeeded, otherwise FALSE
+ */
+gboolean
+purple_certificate_register_verifier(PurpleCertificateVerifier *vr);
+
+/**
+ * Unregister a CertificateVerifier with libpurple
+ *
+ * @param vr     Verifier to unregister.
+ * @return TRUE if unregister succeeded, otherwise FALSE
+ */
+gboolean
+purple_certificate_unregister_verifier(PurpleCertificateVerifier *vr);
+
+/** Look up a registered PurpleCertificatePool by scheme and name
+ * @param scheme_name  Scheme name. Case insensitive.
+ * @param pool_name    Pool name. Case insensitive.
+ * @return Pointer to the located Pool, or NULL if it isn't found.
+ */
+PurpleCertificatePool *
+purple_certificate_find_pool(const gchar *scheme_name, const gchar *pool_name);
+
+/**
+ * Get the list of registered Pools
+ *
+ * @return GList of all registered PurpleCertificatePool s. This value
+ *         is owned by libpurple
+ */
+GList *
+purple_certificate_get_pools(void);
+
+/**
+ * Register a CertificatePool with libpurple and call its init function
+ *
+ * @param pool   Pool to register.
+ * @return TRUE if the register succeeded, otherwise FALSE
+ */
+gboolean
+purple_certificate_register_pool(PurpleCertificatePool *pool);
+
+/**
+ * Unregister a CertificatePool with libpurple and call its uninit function
+ *
+ * @param pool   Pool to unregister.
+ * @return TRUE if the unregister succeeded, otherwise FALSE
+ */
+gboolean
+purple_certificate_unregister_pool(PurpleCertificatePool *pool);
+
+/*@}*/
+
+
+/**
+ * Displays a window showing X.509 certificate information
+ *
+ * @param crt    Certificate under an "x509" Scheme
+ * @TODO Will break on CA certs, as they have no Common Name
+ */
+void
+purple_certificate_display_x509(PurpleCertificate *crt);
+
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* _PURPLE_CERTIFICATE_H */
--- a/libpurple/core.c	Sat Aug 25 08:42:33 2007 +0000
+++ b/libpurple/core.c	Tue Aug 28 09:20:27 2007 +0000
@@ -24,6 +24,7 @@
  */
 #include "internal.h"
 #include "cipher.h"
+#include "certificate.h"
 #include "connection.h"
 #include "conversation.h"
 #include "core.h"
@@ -141,6 +142,7 @@
 	purple_accounts_init();
 	purple_savedstatuses_init();
 	purple_notify_init();
+	purple_certificate_init();
 	purple_connections_init();
 	purple_conversations_init();
 	purple_blist_init();
@@ -159,9 +161,8 @@
 	/*
 	 * Call this early on to try to auto-detect our IP address and
 	 * hopefully save some time later.
-	 * TODO: do this here after purple_prefs_load() has been moved into purple_prefs_init()
 	 */
-	/*purple_network_get_my_ip(-1);*/
+	purple_network_get_my_ip(-1);
 
 	if (ops != NULL && ops->ui_init != NULL)
 		ops->ui_init();
@@ -192,6 +193,7 @@
 	purple_notify_uninit();
 	purple_conversations_uninit();
 	purple_connections_uninit();
+	purple_certificate_uninit();
 	purple_buddy_icons_uninit();
 	purple_accounts_uninit();
 	purple_savedstatuses_uninit();
--- a/libpurple/plugins/ssl/Makefile.mingw	Sat Aug 25 08:42:33 2007 +0000
+++ b/libpurple/plugins/ssl/Makefile.mingw	Tue Aug 28 09:20:27 2007 +0000
@@ -18,6 +18,7 @@
 			$(NSS_TOP)/lib/nss3.dll \
 			$(NSS_TOP)/lib/nssckbi.dll \
 			$(NSS_TOP)/lib/softokn3.dll \
+			$(NSS_TOP)/lib/smime3.dll \
 			$(NSS_TOP)/lib/ssl3.dll \
 			$(NSPR_TOP)/lib/nspr4.dll \
 			$(NSPR_TOP)/lib/plc4.dll \
@@ -59,7 +60,8 @@
 			-lpurple \
 			-lnss3 \
 			-lnspr4 \
-			-lssl3
+			-lssl3 \
+			-lsmime3
 
 include $(PIDGIN_COMMON_RULES)
 
--- a/libpurple/plugins/ssl/ssl-gnutls.c	Sat Aug 25 08:42:33 2007 +0000
+++ b/libpurple/plugins/ssl/ssl-gnutls.c	Tue Aug 28 09:20:27 2007 +0000
@@ -21,15 +21,18 @@
  */
 #include "internal.h"
 #include "debug.h"
+#include "certificate.h"
 #include "plugin.h"
 #include "sslconn.h"
 #include "version.h"
+#include "util.h"
 
 #define SSL_GNUTLS_PLUGIN_ID "ssl-gnutls"
 
 #ifdef HAVE_GNUTLS
 
 #include <gnutls/gnutls.h>
+#include <gnutls/x509.h>
 
 typedef struct
 {
@@ -44,9 +47,25 @@
 static void
 ssl_gnutls_init_gnutls(void)
 {
+	/* Configure GnuTLS to use glib memory management */
+	/* I expect that this isn't really necessary, but it may prevent
+	   some bugs */
+	/* TODO: It may be necessary to wrap this allocators for GnuTLS.
+	   If there are strange bugs, perhaps look here (yes, I am a
+	   hypocrite) */
+	gnutls_global_set_mem_functions(
+		(gnutls_alloc_function)   g_malloc0, /* malloc */
+		(gnutls_alloc_function)   g_malloc0, /* secure malloc */
+		NULL,      /* mem_is_secure */
+		(gnutls_realloc_function) g_realloc, /* realloc */
+		(gnutls_free_function)    g_free     /* free */
+		);
+	
 	gnutls_global_init();
 
 	gnutls_certificate_allocate_credentials(&xcred);
+
+	/* TODO: I can likely remove this */
 	gnutls_certificate_set_x509_trust_file(xcred, "ca.pem",
 		GNUTLS_X509_FMT_PEM);
 }
@@ -65,6 +84,25 @@
 	gnutls_certificate_free_credentials(xcred);
 }
 
+static void
+ssl_gnutls_verified_cb(PurpleCertificateVerificationStatus st,
+		       gpointer userdata)
+{
+	PurpleSslConnection *gsc = (PurpleSslConnection *) userdata;
+
+	if (st == PURPLE_CERTIFICATE_VALID) {
+		/* Certificate valid? Good! Do the connection! */
+		gsc->connect_cb(gsc->connect_cb_data, gsc, PURPLE_INPUT_READ);
+	} else {
+		/* Otherwise, signal an error */
+		if(gsc->error_cb != NULL)
+			gsc->error_cb(gsc, PURPLE_SSL_CERTIFICATE_INVALID,
+				      gsc->connect_cb_data);
+		purple_ssl_close(gsc);
+	}
+}
+
+
 
 static void ssl_gnutls_handshake_cb(gpointer data, gint source,
 		PurpleInputCondition cond)
@@ -73,7 +111,7 @@
 	PurpleSslGnutlsData *gnutls_data = PURPLE_SSL_GNUTLS_DATA(gsc);
 	ssize_t ret;
 
-	purple_debug_info("gnutls", "Handshaking\n");
+	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)
@@ -94,7 +132,117 @@
 	} else {
 		purple_debug_info("gnutls", "Handshake complete\n");
 
-		gsc->connect_cb(gsc->connect_cb_data, gsc, cond);
+		/* TODO: Remove all this debugging babble */
+		/* Now we are cooking with gas! */
+		PurpleSslOps *ops = purple_ssl_get_ops();
+		GList * peers = ops->get_peer_certificates(gsc);
+		
+		PurpleCertificateScheme *x509 =
+			purple_certificate_find_scheme("x509");
+
+		GList * l;
+		for (l=peers; l; l = l->next) {
+			PurpleCertificate *crt = l->data;
+			GByteArray *z =
+				x509->get_fingerprint_sha1(crt);
+			gchar * fpr =
+				purple_base16_encode_chunked(z->data,
+							     z->len);
+
+			purple_debug_info("gnutls/x509",
+					  "Key print: %s\n",
+					  fpr);
+
+			/* Kill the cert! */
+			x509->destroy_certificate(crt);
+			
+			g_free(fpr);
+			g_byte_array_free(z, TRUE);
+		}
+		g_list_free(peers);
+		
+		{
+		  const gnutls_datum_t *cert_list;
+		  unsigned int cert_list_size = 0;
+		  gnutls_session_t session=gnutls_data->session;
+		  
+		  cert_list =
+		    gnutls_certificate_get_peers(session, &cert_list_size);
+		  
+		  purple_debug_info("gnutls",
+				    "Peer provided %d certs\n",
+				    cert_list_size);
+		  int i;
+		  for (i=0; i<cert_list_size; i++)
+		    {
+		      gchar fpr_bin[256];
+		      gsize fpr_bin_sz = sizeof(fpr_bin);
+		      gchar * fpr_asc = NULL;
+		      gchar tbuf[256];
+		      gsize tsz=sizeof(tbuf);
+		      gchar * tasc = NULL;
+		      gnutls_x509_crt_t cert;
+		      
+		      gnutls_x509_crt_init(&cert);
+		      gnutls_x509_crt_import (cert, &cert_list[i],
+					      GNUTLS_X509_FMT_DER);
+		      
+		      gnutls_x509_crt_get_fingerprint(cert, GNUTLS_MAC_SHA,
+						      fpr_bin, &fpr_bin_sz);
+		      
+		      fpr_asc =
+			purple_base16_encode_chunked(fpr_bin,fpr_bin_sz);
+		      
+		      purple_debug_info("gnutls", 
+					"Lvl %d SHA1 fingerprint: %s\n",
+					i, fpr_asc);
+		      
+		      tsz=sizeof(tbuf);
+		      gnutls_x509_crt_get_serial(cert,tbuf,&tsz);
+		      tasc=
+			purple_base16_encode_chunked(tbuf, tsz);
+		      purple_debug_info("gnutls",
+					"Serial: %s\n",
+					tasc);
+		      g_free(tasc);
+
+		      tsz=sizeof(tbuf);
+		      gnutls_x509_crt_get_dn (cert, tbuf, &tsz);
+		      purple_debug_info("gnutls",
+					"Cert DN: %s\n",
+					tbuf);
+		      tsz=sizeof(tbuf);
+		      gnutls_x509_crt_get_issuer_dn (cert, tbuf, &tsz);
+		      purple_debug_info("gnutls",
+					"Cert Issuer DN: %s\n",
+					tbuf);
+
+		      g_free(fpr_asc); fpr_asc = NULL;
+		      gnutls_x509_crt_deinit(cert);
+		    }
+		  
+		}
+
+		/* TODO: The following logic should really be in libpurple */
+		/* If a Verifier was given, hand control over to it */
+		if (gsc->verifier) {
+			GList *peers;
+			/* First, get the peer cert chain */
+			peers = purple_ssl_get_peer_certificates(gsc);
+
+			/* Now kick off the verification process */
+			purple_certificate_verify(gsc->verifier,
+						  gsc->host,
+						  peers,
+						  ssl_gnutls_verified_cb,
+						  gsc);
+
+			purple_certificate_destroy_list(peers);
+		} else {
+			/* Otherwise, just call the "connection complete"
+			   callback */
+			gsc->connect_cb(gsc->connect_cb_data, gsc, cond);
+		}
 	}
 
 }
@@ -213,6 +361,559 @@
 	return s;
 }
 
+/* Forward declarations are fun! */
+static PurpleCertificate *
+x509_import_from_datum(const gnutls_datum_t dt, gnutls_x509_crt_fmt_t mode);
+
+static GList *
+ssl_gnutls_get_peer_certificates(PurpleSslConnection * gsc)
+{
+	PurpleSslGnutlsData *gnutls_data = PURPLE_SSL_GNUTLS_DATA(gsc);
+
+	/* List of Certificate instances to return */
+	GList * peer_certs = NULL;
+
+	/* List of raw certificates as given by GnuTLS */
+	const gnutls_datum_t *cert_list;
+	unsigned int cert_list_size = 0;
+
+	unsigned int i;
+	
+	/* This should never, ever happen. */
+	g_return_val_if_fail( gnutls_certificate_type_get (gnutls_data->session) == GNUTLS_CRT_X509, NULL);
+
+	/* Get the certificate list from GnuTLS */
+	/* TODO: I am _pretty sure_ this doesn't block or do other exciting things */
+	cert_list = gnutls_certificate_get_peers(gnutls_data->session,
+						 &cert_list_size);
+
+	/* Convert each certificate to a Certificate and append it to the list */
+	for (i = 0; i < cert_list_size; i++) {
+		PurpleCertificate * newcrt = x509_import_from_datum(cert_list[i],
+							      GNUTLS_X509_FMT_DER);
+		/* Append is somewhat inefficient on linked lists, but is easy
+		   to read. If someone complains, I'll change it.
+		   TODO: Is anyone complaining? (Maybe elb?) */
+		peer_certs = g_list_append(peer_certs, newcrt);
+	}
+
+	/* cert_list doesn't need free()-ing */
+
+	return peer_certs;
+}
+
+/************************************************************************/
+/* X.509 functionality                                                  */
+/************************************************************************/
+const gchar * SCHEME_NAME = "x509";
+
+static PurpleCertificateScheme x509_gnutls;
+
+/** Refcounted GnuTLS certificate data instance */
+typedef struct {
+	gint refcount;
+	gnutls_x509_crt_t crt;
+} x509_crtdata_t;
+
+/** Helper functions for reference counting */
+static x509_crtdata_t *
+x509_crtdata_addref(x509_crtdata_t *cd)
+{
+	(cd->refcount)++;
+	return cd;
+}
+
+static void
+x509_crtdata_delref(x509_crtdata_t *cd)
+{
+	g_assert(cd->refcount > 0);
+	
+	(cd->refcount)--;
+
+	/* 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 */
+		g_free( cd );
+	}
+}
+
+/** 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
+ *
+ * @param dt   Datum to transform
+ * @param mode GnuTLS certificate format specifier (GNUTLS_X509_FMT_PEM for
+ *             reading from files, and GNUTLS_X509_FMT_DER for converting
+ *             "over the wire" certs for SSL)
+ *
+ * @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)
+{
+	/* Internal certificate data structure */
+	x509_crtdata_t *certdat;
+	/* New certificate to return */
+	PurpleCertificate * crt;
+
+	/* Allocate and prepare the internal certificate data */
+	certdat = g_new0(x509_crtdata_t, 1);
+	gnutls_x509_crt_init(&(certdat->crt));
+	certdat->refcount = 0;
+	
+	/* Perform the actual certificate parse */
+	/* Yes, certdat->crt should be passed as-is */
+	gnutls_x509_crt_import(certdat->crt, &dt, mode);
+	
+	/* Allocate the certificate and load it with data */
+	crt = g_new0(PurpleCertificate, 1);
+	crt->scheme = &x509_gnutls;
+	crt->data = x509_crtdata_addref(certdat);
+
+	return crt;
+}
+
+/** Imports a PEM-formatted X.509 certificate from the specified file.
+ * @param filename Filename to import from. Format is PEM
+ *
+ * @return A newly allocated Certificate structure of the x509_gnutls scheme
+ */
+static PurpleCertificate *
+x509_import_from_file(const gchar * filename)
+{
+	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 */
+
+	purple_debug_info("gnutls",
+			  "Attempting to load X.509 certificate from %s\n",
+			  filename);
+	
+	/* Next, we'll simply yank the entire contents of the file
+	   into memory */
+	/* TODO: Should I worry about very large files here? */
+	g_return_val_if_fail(
+		g_file_get_contents(filename,
+			    &buf,
+			    &buf_sz,
+			    NULL      /* No error checking for now */
+		),
+		NULL);
+	
+	/* Load the datum struct */
+	dt.data = (unsigned char *) buf;
+	dt.size = buf_sz;
+
+	/* Perform the conversion */
+	crt = x509_import_from_datum(dt,
+				     GNUTLS_X509_FMT_PEM); // files should be in PEM format
+	
+	/* Cleanup */
+	g_free(buf);
+
+	return crt;
+}
+
+/**
+ * Exports a PEM-formatted X.509 certificate to the specified file.
+ * @param filename Filename to export to. Format will be PEM
+ * @param crt      Certificate to export
+ *
+ * @return TRUE if success, otherwise FALSE
+ */
+static gboolean
+x509_export_certificate(const gchar *filename, PurpleCertificate *crt)
+{
+	gnutls_x509_crt_t crt_dat; /* GnuTLS cert struct */
+	int ret;
+	gchar * out_buf; /* Data to output */
+	size_t out_size; /* Output size */
+	gboolean success = FALSE;
+
+	/* Paranoia paranoia paranoia! */
+	g_return_val_if_fail(filename, FALSE);
+	g_return_val_if_fail(crt, FALSE);
+	g_return_val_if_fail(crt->scheme == &x509_gnutls, FALSE);
+	g_return_val_if_fail(crt->data, FALSE);
+
+	crt_dat = X509_GET_GNUTLS_DATA(crt);
+
+	/* Obtain the output size required */
+	out_size = 0;
+	ret = gnutls_x509_crt_export(crt_dat, GNUTLS_X509_FMT_PEM,
+				     NULL, /* Provide no buffer yet */
+				     &out_size /* Put size here */
+		);
+	g_return_val_if_fail(ret == GNUTLS_E_SHORT_MEMORY_BUFFER, FALSE);
+
+	/* Now allocate a buffer and *really* export it */
+	out_buf = g_new0(gchar, out_size);
+	ret = gnutls_x509_crt_export(crt_dat, GNUTLS_X509_FMT_PEM,
+				     out_buf, /* Export to our new buffer */
+				     &out_size /* Put size here */
+		);
+	if (ret != 0) {
+		purple_debug_error("gnutls/x509",
+				   "Failed to export cert to buffer with code %d\n",
+				   ret);
+		g_free(out_buf);
+		return FALSE;
+	}
+
+	/* Write it out to an actual file */
+	success = purple_util_write_data_to_file_absolute(filename,
+							  out_buf, out_size);
+
+	
+	g_free(out_buf);
+	g_return_val_if_fail(success, FALSE);
+	return success;
+}
+
+static PurpleCertificate *
+x509_copy_certificate(PurpleCertificate *crt)
+{
+	x509_crtdata_t *crtdat;
+	PurpleCertificate *newcrt;
+
+	g_return_val_if_fail(crt, NULL);
+	g_return_val_if_fail(crt->scheme == &x509_gnutls, NULL);
+
+	crtdat = (x509_crtdata_t *) crt->data;
+
+	newcrt = g_new0(PurpleCertificate, 1);
+	newcrt->scheme = &x509_gnutls;
+	newcrt->data = x509_crtdata_addref(crtdat);
+
+	return newcrt;
+}
+/** Frees a Certificate
+ *
+ *  Destroys a Certificate's internal data structures and frees the pointer
+ *  given.
+ *  @param crt  Certificate instance to be destroyed. It WILL NOT be destroyed
+ *              if it is not of the correct CertificateScheme. Can be NULL
+ *
+ */
+static void
+x509_destroy_certificate(PurpleCertificate * crt)
+{
+	if (NULL == crt) return;
+
+	/* Check that the scheme is x509_gnutls */
+	if ( crt->scheme != &x509_gnutls ) {
+		purple_debug_error("gnutls",
+				   "destroy_certificate attempted on certificate of wrong scheme (scheme was %s, expected %s)\n",
+				   crt->scheme->name,
+				   SCHEME_NAME);
+		return;
+	}
+
+	g_return_if_fail(crt->data != NULL);
+	g_return_if_fail(crt->scheme != NULL);
+
+	/* Use the reference counting system to free (or not) the
+	   underlying data */
+	x509_crtdata_delref((x509_crtdata_t *)crt->data);
+	
+	/* Kill the structure itself */
+	g_free(crt);
+}
+
+/** Determines whether one certificate has been issued and signed by another
+ *
+ * @param crt       Certificate to check the signature of
+ * @param issuer    Issuer's certificate
+ *
+ * @return TRUE if crt was signed and issued by issuer, otherwise FALSE
+ * @TODO  Modify this function to return a reason for invalidity?
+ */
+static gboolean
+x509_certificate_signed_by(PurpleCertificate * crt,
+			   PurpleCertificate * issuer)
+{
+	gnutls_x509_crt_t crt_dat;
+	gnutls_x509_crt_t issuer_dat;
+	unsigned int verify; /* used to store result from GnuTLS verifier */
+	int ret;
+	
+	g_return_val_if_fail(crt, FALSE);
+	g_return_val_if_fail(issuer, FALSE);
+
+	/* Verify that both certs are the correct scheme */
+	g_return_val_if_fail(crt->scheme == &x509_gnutls, FALSE);
+	g_return_val_if_fail(issuer->scheme == &x509_gnutls, FALSE);
+
+	/* TODO: check for more nullness? */
+
+	crt_dat = X509_GET_GNUTLS_DATA(crt);
+	issuer_dat = X509_GET_GNUTLS_DATA(issuer);
+
+	/* First, let's check that crt.issuer is actually issuer */
+	ret = gnutls_x509_crt_check_issuer(crt_dat, issuer_dat);
+	if (ret <= 0) {
+
+		if (ret < 0) {
+			purple_debug_error("gnutls/x509",
+					   "GnuTLS error %d while checking certificate issuer match.",
+					   ret);
+		} else {
+			gchar *crt_id, *issuer_id, *crt_issuer_id;
+			crt_id = purple_certificate_get_unique_id(crt);
+			issuer_id = purple_certificate_get_unique_id(issuer);
+			crt_issuer_id =
+				purple_certificate_get_issuer_unique_id(crt);
+			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));
+			g_free(crt_id);
+			g_free(issuer_id);
+			g_free(crt_issuer_id);
+		}
+
+		/* The issuer is not correct, or there were errors */
+		return FALSE;
+	}
+	
+	/* Now, check the signature */
+	/* The second argument is a ptr to an array of "trusted" issuer certs,
+	   but we're only using one trusted one */
+	ret = gnutls_x509_crt_verify(crt_dat, &issuer_dat, 1,
+				     /* Permit signings by X.509v1 certs
+					(Verisign and possibly others have
+					root certificates that predate the
+					current standard) */
+				     GNUTLS_VERIFY_ALLOW_X509_V1_CA_CRT,
+				     &verify);
+	
+	if (ret != 0) {
+		purple_debug_error("gnutls/x509",
+				   "Attempted certificate verification caused a GnuTLS error code %d. I will just say the signature is bad, but you should look into this.\n", ret);
+		return FALSE;
+	}
+
+	if (verify & GNUTLS_CERT_INVALID) {
+		/* Signature didn't check out, but at least
+		   there were no errors*/
+		gchar *crt_id = purple_certificate_get_unique_id(crt);
+		gchar *issuer_id = purple_certificate_get_issuer_unique_id(crt);
+		purple_debug_info("gnutls/x509",
+				  "Bad signature for %s on %s\n",
+				  issuer_id, crt_id);
+		g_free(crt_id);
+		g_free(issuer_id);
+		
+		return FALSE;
+	} /* if (ret, etc.) */
+
+	/* If we got here, the signature is good */
+	return TRUE;
+}
+
+static GByteArray *
+x509_sha1sum(PurpleCertificate *crt)
+{
+	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;
+	GByteArray *hash; /**< Final hash container */
+	guchar hashbuf[hashlen]; /**< Temporary buffer to contain hash */
+
+	g_return_val_if_fail(crt, NULL);
+
+	crt_dat = X509_GET_GNUTLS_DATA(crt);
+
+	/* Extract the fingerprint */
+	g_return_val_if_fail(
+		0 == gnutls_x509_crt_get_fingerprint(crt_dat, GNUTLS_MAC_SHA,
+						     hashbuf, &tmpsz),
+		NULL);
+
+	/* This shouldn't happen */
+	g_return_val_if_fail(tmpsz == hashlen, NULL);
+	
+	/* Okay, now create and fill hash array */
+	hash = g_byte_array_new();
+	g_byte_array_append(hash, hashbuf, hashlen);
+
+	return hash;
+}
+
+static gchar *
+x509_cert_dn (PurpleCertificate *crt)
+{
+	gnutls_x509_crt_t cert_dat;
+	gchar *dn = NULL;
+	size_t dn_size;
+
+	g_return_val_if_fail(crt, NULL);
+	g_return_val_if_fail(crt->scheme == &x509_gnutls, NULL);
+
+	cert_dat = X509_GET_GNUTLS_DATA(crt);
+
+	/* Figure out the length of the Distinguished Name */
+	/* Claim that the buffer is size 0 so GnuTLS just tells us how much
+	   space it needs */
+	dn_size = 0;
+	gnutls_x509_crt_get_dn(cert_dat, dn, &dn_size);
+
+	/* Now allocate and get the Distinguished Name */
+	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");
+		g_free(dn);
+		return NULL;
+	}
+	
+	return dn;
+}
+
+static gchar *
+x509_issuer_dn (PurpleCertificate *crt)
+{
+	gnutls_x509_crt_t cert_dat;
+	gchar *dn = NULL;
+	size_t dn_size;
+
+	g_return_val_if_fail(crt, NULL);
+	g_return_val_if_fail(crt->scheme == &x509_gnutls, NULL);
+
+	cert_dat = X509_GET_GNUTLS_DATA(crt);
+
+	/* Figure out the length of the Distinguished Name */
+	/* Claim that the buffer is size 0 so GnuTLS just tells us how much
+	   space it needs */
+	dn_size = 0;
+	gnutls_x509_crt_get_issuer_dn(cert_dat, dn, &dn_size);
+
+	/* Now allocate and get the Distinguished Name */
+	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 "
+				   "Name\n");
+		g_free(dn);
+		return NULL;
+	}
+	
+	return dn;
+}
+
+static gchar *
+x509_common_name (PurpleCertificate *crt)
+{
+	gnutls_x509_crt_t cert_dat;
+	gchar *cn = NULL;
+	size_t cn_size;
+	int ret;
+
+	g_return_val_if_fail(crt, NULL);
+	g_return_val_if_fail(crt->scheme == &x509_gnutls, NULL);
+
+	cert_dat = X509_GET_GNUTLS_DATA(crt);
+
+	/* Figure out the length of the Common Name */
+	/* Claim that the buffer is size 0 so GnuTLS just tells us how much
+	   space it needs */
+	cn_size = 0;
+	gnutls_x509_crt_get_dn_by_oid(cert_dat,
+				      GNUTLS_OID_X520_COMMON_NAME,
+				      0, /* First CN found, please */
+				      0, /* Not in raw mode */
+				      cn, &cn_size);
+
+	/* Now allocate and get the Common Name */
+	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 */
+					    0, /* Not in raw mode */
+					    cn, &cn_size);
+	if (ret != 0) {
+		purple_debug_error("gnutls/x509",
+				   "Failed to get Common Name\n");
+		g_free(cn);
+		return NULL;
+	}
+
+	
+	return cn;
+}
+
+static gboolean
+x509_check_name (PurpleCertificate *crt, const gchar *name)
+{
+	gnutls_x509_crt_t crt_dat;
+
+	g_return_val_if_fail(crt, FALSE);
+	g_return_val_if_fail(crt->scheme == &x509_gnutls, FALSE);
+	g_return_val_if_fail(name, FALSE);
+
+	crt_dat = X509_GET_GNUTLS_DATA(crt);
+
+	if (gnutls_x509_crt_check_hostname(crt_dat, name)) {
+		return TRUE;
+	} else {
+		return FALSE;
+	}
+}
+
+static gboolean
+x509_times (PurpleCertificate *crt, time_t *activation, time_t *expiration)
+{
+	gnutls_x509_crt_t crt_dat;
+	/* GnuTLS time functions return this on error */
+	const time_t errval = (time_t) (-1);
+
+
+	g_return_val_if_fail(crt, FALSE);
+	g_return_val_if_fail(crt->scheme == &x509_gnutls, FALSE);
+
+	crt_dat = X509_GET_GNUTLS_DATA(crt);
+
+	if (activation) {
+		*activation = gnutls_x509_crt_get_activation_time(crt_dat);
+	}
+	if (expiration) {
+		*expiration = gnutls_x509_crt_get_expiration_time(crt_dat);
+	}
+
+	if (*activation == errval || *expiration == errval) {
+		return FALSE;
+	}
+	
+	return TRUE;
+}
+
+/* X.509 certificate operations provided by this plugin */
+static PurpleCertificateScheme x509_gnutls = {
+	"x509",                          /* Scheme name */
+	N_("X.509 Certificates"),        /* User-visible scheme name */
+	x509_import_from_file,           /* Certificate import function */
+	x509_export_certificate,         /* Certificate export function */
+	x509_copy_certificate,           /* Copy */
+	x509_destroy_certificate,        /* Destroy cert */
+	x509_certificate_signed_by,      /* Signature checker */
+	x509_sha1sum,                    /* SHA1 fingerprint */
+	x509_cert_dn,                    /* Unique ID */
+	x509_issuer_dn,                  /* Issuer Unique ID */
+	x509_common_name,                /* Subject name */
+	x509_check_name,                 /* Check subject name */
+	x509_times                       /* Activation/Expiration time */
+};
+
 static PurpleSslOps ssl_ops =
 {
 	ssl_gnutls_init,
@@ -221,11 +922,11 @@
 	ssl_gnutls_close,
 	ssl_gnutls_read,
 	ssl_gnutls_write,
+	ssl_gnutls_get_peer_certificates,
 
 	/* padding */
 	NULL,
 	NULL,
-	NULL,
 	NULL
 };
 
@@ -242,6 +943,9 @@
 	/* Init GNUTLS now so others can use it even if sslconn never does */
 	ssl_gnutls_init_gnutls();
 
+	/* Register that we're providing an X.509 CertScheme */
+	purple_certificate_register_scheme( &x509_gnutls );
+
 	return TRUE;
 #else
 	return FALSE;
@@ -255,6 +959,8 @@
 	if(purple_ssl_get_ops() == &ssl_ops) {
 		purple_ssl_set_ops(NULL);
 	}
+
+	purple_certificate_unregister_scheme( &x509_gnutls );
 #endif
 
 	return TRUE;
--- a/libpurple/plugins/ssl/ssl-nss.c	Sat Aug 25 08:42:33 2007 +0000
+++ b/libpurple/plugins/ssl/ssl-nss.c	Tue Aug 28 09:20:27 2007 +0000
@@ -21,6 +21,7 @@
  */
 #include "internal.h"
 #include "debug.h"
+#include "certificate.h"
 #include "plugin.h"
 #include "sslconn.h"
 #include "version.h"
@@ -360,6 +361,286 @@
 	return ret;
 }
 
+static GList *
+ssl_nss_peer_certs(PurpleSslConnection *gsc)
+{
+	PurpleSslNssData *nss_data = PURPLE_SSL_NSS_DATA(gsc);
+	GList *chain = NULL;
+	CERTCertificate *cert;
+	void *pinArg;
+	SECStatus status;
+
+	/* TODO: this is a blind guess */
+	cert = SSL_PeerCertificate(nss_data->fd);
+
+	
+
+	return NULL;
+}
+
+/************************************************************************/
+/* X.509 functionality                                                  */
+/************************************************************************/
+static PurpleCertificateScheme x509_nss;
+
+/** Helpr macro to retrieve the NSS certdata from a PurpleCertificate */
+#define X509_NSS_DATA(pcrt) ( (CERTCertificate * ) (pcrt->data) )
+
+/** Imports a PEM-formatted X.509 certificate from the specified file.
+ * @param filename Filename to import from. Format is PEM
+ *
+ * @return A newly allocated Certificate structure of the x509_gnutls scheme
+ */
+static PurpleCertificate *
+x509_import_from_file(const gchar *filename)
+{
+	gchar *rawcert;
+	gsize len = 0;
+	CERTCertificate *crt_dat;
+	PurpleCertificate *crt;
+
+	g_return_val_if_fail(filename, NULL);
+
+	purple_debug_info("nss/x509",
+			  "Loading certificate from %s\n",
+			  filename);
+	
+	/* Load the raw data up */
+	g_return_val_if_fail(
+		g_file_get_contents(filename,
+				    &rawcert, &len,
+				    NULL ),
+		NULL);
+
+	/* Decode the certificate */
+	crt_dat = CERT_DecodeCertFromPackage(rawcert, len);
+	g_free(rawcert);
+
+	g_return_val_if_fail(crt_dat, NULL);
+	
+	crt = g_new0(PurpleCertificate, 1);
+	crt->scheme = &x509_nss;
+	crt->data = crt_dat;
+	
+	return crt;
+}
+
+/**
+ * Exports a PEM-formatted X.509 certificate to the specified file.
+ * @param filename Filename to export to. Format will be PEM
+ * @param crt      Certificate to export
+ *
+ * @return TRUE if success, otherwise FALSE
+ */
+static gboolean
+x509_export_certificate(const gchar *filename, PurpleCertificate *crt)
+{
+	/* TODO: WRITEME */
+	return FALSE;
+}
+
+static PurpleCertificate *
+x509_copy_certificate(PurpleCertificate *crt)
+{
+	CERTCertificate *crt_dat;
+	PurpleCertificate *newcrt;
+
+	g_return_val_if_fail(crt, NULL);
+	g_return_val_if_fail(crt->scheme == &x509_nss, NULL);
+
+	crt_dat = X509_NSS_DATA(crt);
+	g_return_val_if_fail(crt_dat, NULL);
+
+	/* Create the certificate copy */
+	newcrt = g_new0(PurpleCertificate, 1);
+	newcrt->scheme = &x509_nss;
+	/* NSS does refcounting automatically */
+	newcrt->data = CERT_DupCertificate(crt_dat);
+	
+	return newcrt;
+}
+
+/** Frees a Certificate
+ *
+ *  Destroys a Certificate's internal data structures and frees the pointer
+ *  given.
+ *  @param crt  Certificate instance to be destroyed. It WILL NOT be destroyed
+ *              if it is not of the correct CertificateScheme. Can be NULL
+ *
+ */
+static void
+x509_destroy_certificate(PurpleCertificate * crt)
+{
+	CERTCertificate *crt_dat;
+
+	g_return_if_fail(crt);
+	g_return_if_fail(crt->scheme == &x509_nss);
+
+	crt_dat = X509_NSS_DATA(crt);
+	g_return_if_fail(crt_dat);
+
+	/* Finally we have the certificate. So let's kill it */
+	/* NSS does refcounting automatically */
+	CERT_DestroyCertificate(crt_dat);
+
+	/* Delete the PurpleCertificate as well */
+	g_free(crt);
+}
+
+/** Determines whether one certificate has been issued and signed by another
+ *
+ * @param crt       Certificate to check the signature of
+ * @param issuer    Issuer's certificate
+ *
+ * @return TRUE if crt was signed and issued by issuer, otherwise FALSE
+ * @TODO  Modify this function to return a reason for invalidity?
+ */
+static gboolean
+x509_certificate_signed_by(PurpleCertificate * crt,
+			   PurpleCertificate * issuer)
+{
+	return FALSE;
+}
+
+static GByteArray *
+x509_sha1sum(PurpleCertificate *crt)
+{
+	CERTCertificate *crt_dat;
+	size_t hashlen = 20; /* Size of an sha1sum */
+	GByteArray *sha1sum;
+	SECItem *derCert; /* DER representation of the cert */
+	SECStatus st;
+
+	g_return_val_if_fail(crt, NULL);
+	g_return_val_if_fail(crt->scheme == &x509_nss, NULL);
+
+	crt_dat = X509_NSS_DATA(crt);
+	g_return_val_if_fail(crt_dat, NULL);
+
+	/* Get the certificate DER representation */
+	derCert = &(crt_dat->derCert);
+
+	/* Make a hash! */
+	sha1sum = g_byte_array_sized_new(hashlen);
+	st = PK11_HashBuf(SEC_OID_SHA1, sha1sum->data,
+			  derCert->data, derCert->len);
+
+	/* Check for errors */
+	if (st != SECSuccess) {
+		g_byte_array_free(sha1sum, TRUE);
+		purple_debug_error("nss/x509",
+				   "Error: hashing failed!\n");
+		return NULL;
+	}
+
+	return sha1sum;
+}
+
+static gchar *
+x509_common_name (PurpleCertificate *crt)
+{
+	CERTCertificate *crt_dat;
+	char *nss_cn;
+	gchar *ret_cn;
+	
+	g_return_val_if_fail(crt, NULL);
+	g_return_val_if_fail(crt->scheme == &x509_nss, NULL);
+
+	crt_dat = X509_NSS_DATA(crt);
+	g_return_val_if_fail(crt_dat, NULL);
+
+	/* Q:
+	   Why get a newly allocated string out of NSS, strdup it, and then
+	   return the new copy?
+
+	   A:
+	   The NSS LXR docs state that I should use the NSPR free functions on
+	   the strings that the NSS cert functions return. Since the libpurple
+	   API expects a g_free()-able string, we make our own copy and return
+	   that.
+
+	   NSPR is something of a prima donna. */
+
+	nss_cn = CERT_GetCommonName( &(crt_dat->subject) );
+	ret_cn = g_strdup(nss_cn);
+	PORT_Free(nss_cn);
+
+	return ret_cn;
+}
+
+static gboolean
+x509_check_name (PurpleCertificate *crt, const gchar *name)
+{
+	CERTCertificate *crt_dat;
+	SECStatus st;
+	
+	g_return_val_if_fail(crt, FALSE);
+	g_return_val_if_fail(crt->scheme == &x509_nss, FALSE);
+
+	crt_dat = X509_NSS_DATA(crt);
+	g_return_val_if_fail(crt_dat, FALSE);
+
+	st = CERT_VerifyCertName(crt_dat, name);
+
+	if (st == SECSuccess) {
+		return TRUE;
+	}
+	else if (st == SECFailure) {
+		return FALSE;
+	}
+	
+	/* If we get here...bad things! */
+	purple_debug_error("nss/x509",
+			   "x509_check_name fell through where it shouldn't "
+			   "have.\n");
+	return FALSE;
+}
+
+static gboolean
+x509_times (PurpleCertificate *crt, time_t *activation, time_t *expiration)
+{
+	CERTCertificate *crt_dat;
+	PRTime nss_activ, nss_expir;
+	
+	g_return_val_if_fail(crt, FALSE);
+	g_return_val_if_fail(crt->scheme == &x509_nss, FALSE);
+
+	crt_dat = X509_NSS_DATA(crt);
+	g_return_val_if_fail(crt_dat, FALSE);
+
+	/* Extract the times into ugly PRTime thingies */
+	/* TODO: Maybe this shouldn't throw an error? */
+	g_return_val_if_fail(
+		SECSuccess == CERT_GetCertTimes(crt_dat,
+						&nss_activ, &nss_expir),
+		FALSE);
+
+	if (activation) {
+		*activation = nss_activ;
+	}
+	if (expiration) {
+		*expiration = nss_expir;
+	}
+	
+	return TRUE;
+}
+
+static PurpleCertificateScheme x509_nss = {
+	"x509",                          /* Scheme name */
+	N_("X.509 Certificates"),        /* User-visible scheme name */
+	x509_import_from_file,           /* Certificate import function */
+	x509_export_certificate,         /* Certificate export function */
+	x509_copy_certificate,           /* Copy */
+	x509_destroy_certificate,        /* Destroy cert */
+	NULL,                            /* Signed-by */
+	x509_sha1sum,                    /* SHA1 fingerprint */
+	NULL,                            /* Unique ID */
+	NULL,                            /* Issuer Unique ID */
+	x509_common_name,                /* Subject name */
+	x509_check_name,                 /* Check subject name */
+	x509_times                       /* Activation/Expiration time */
+};
+
 static PurpleSslOps ssl_ops =
 {
 	ssl_nss_init,
@@ -368,11 +649,11 @@
 	ssl_nss_close,
 	ssl_nss_read,
 	ssl_nss_write,
+	ssl_nss_peer_certs,
 
 	/* padding */
 	NULL,
 	NULL,
-	NULL,
 	NULL
 };
 
@@ -390,6 +671,9 @@
 	/* Init NSS now, so others can use it even if sslconn never does */
 	ssl_nss_init_nss();
 
+	/* Register the X.509 functions we provide */
+	purple_certificate_register_scheme(&x509_nss);
+
 	return TRUE;
 #else
 	return FALSE;
@@ -403,6 +687,9 @@
 	if (purple_ssl_get_ops() == &ssl_ops) {
 		purple_ssl_set_ops(NULL);
 	}
+
+	/* Unregister our X.509 functions */
+	purple_certificate_unregister_scheme(&x509_nss);
 #endif
 
 	return TRUE;
--- a/libpurple/prefs.c	Sat Aug 25 08:42:33 2007 +0000
+++ b/libpurple/prefs.c	Tue Aug 28 09:20:27 2007 +0000
@@ -1446,6 +1446,9 @@
 	purple_prefs_remove("/purple/contact/offline_score");
 	purple_prefs_remove("/purple/contact/away_score");
 	purple_prefs_remove("/purple/contact/idle_score");
+
+	purple_prefs_load();
+	purple_prefs_update_old();
 }
 
 void
--- a/libpurple/prefs.h	Sat Aug 25 08:42:33 2007 +0000
+++ b/libpurple/prefs.h	Tue Aug 28 09:20:27 2007 +0000
@@ -55,7 +55,9 @@
 #endif
 
 /**************************************************************************/
-/** @name Prefs API                                                       */
+/** @name Prefs API                                                       
+    Preferences are named according to a directory-like structure.        
+    Example: "/plugins/core/potato/is_from_idaho" (probably a boolean)    */
 /**************************************************************************/
 /*@{*/
 
--- a/libpurple/protocols/Makefile.am	Sat Aug 25 08:42:33 2007 +0000
+++ b/libpurple/protocols/Makefile.am	Tue Aug 28 09:20:27 2007 +0000
@@ -1,5 +1,5 @@
 EXTRA_DIST = Makefile.mingw
 
-DIST_SUBDIRS = bonjour gg irc jabber msn novell null oscar qq sametime silc silc10 toc simple yahoo zephyr
+DIST_SUBDIRS = bonjour gg irc jabber msn myspace novell null oscar qq sametime silc silc10 toc simple yahoo zephyr
 
 SUBDIRS = $(DYNAMIC_PRPLS) $(STATIC_PRPLS)
--- a/libpurple/protocols/Makefile.mingw	Sat Aug 25 08:42:33 2007 +0000
+++ b/libpurple/protocols/Makefile.mingw	Tue Aug 28 09:20:27 2007 +0000
@@ -8,7 +8,7 @@
 PIDGIN_TREE_TOP := ../..
 include $(PIDGIN_TREE_TOP)/libpurple/win32/global.mak
 
-SUBDIRS = gg irc jabber msn novell null oscar qq sametime silc10 simple yahoo bonjour
+SUBDIRS = gg irc jabber msn novell null oscar qq sametime silc10 simple yahoo bonjour myspace
 
 .PHONY: all install clean
 
--- a/libpurple/protocols/irc/irc.c	Sat Aug 25 08:42:33 2007 +0000
+++ b/libpurple/protocols/irc/irc.c	Tue Aug 28 09:20:27 2007 +0000
@@ -433,14 +433,7 @@
 
 	irc->gsc = NULL;
 
-	switch(error) {
-		case PURPLE_SSL_CONNECT_FAILED:
-			purple_connection_error(gc, _("Connection Failed"));
-			break;
-		case PURPLE_SSL_HANDSHAKE_FAILED:
-			purple_connection_error(gc, _("SSL Handshake Failed"));
-			break;
-	}
+	purple_connection_error(gc, purple_ssl_strerror(error));
 }
 
 static void irc_close(PurpleConnection *gc)
--- a/libpurple/protocols/jabber/jabber.c	Sat Aug 25 08:42:33 2007 +0000
+++ b/libpurple/protocols/jabber/jabber.c	Tue Aug 28 09:20:27 2007 +0000
@@ -495,29 +495,20 @@
 	js = gc->proto_data;
 	js->gsc = NULL;
 
-	switch(error) {
-		case PURPLE_SSL_CONNECT_FAILED:
-			purple_connection_error(gc, _("Connection Failed"));
-			break;
-		case PURPLE_SSL_HANDSHAKE_FAILED:
-			purple_connection_error(gc, _("SSL Handshake Failed"));
-			break;
-	}
+	purple_connection_error(gc, purple_ssl_strerror(error));
 }
 
 static void tls_init(JabberStream *js)
 {
 	purple_input_remove(js->gc->inpa);
 	js->gc->inpa = 0;
-	js->gsc = purple_ssl_connect_fd(js->gc->account, js->fd,
-			jabber_login_callback_ssl, jabber_ssl_connect_failure, js->gc);
+	js->gsc = purple_ssl_connect_with_host_fd(js->gc->account, js->fd,
+			jabber_login_callback_ssl, jabber_ssl_connect_failure, js->serverFQDN, js->gc);
 }
 
 static void jabber_login_connect(JabberStream *js, const char *fqdn, const char *host, int port)
 {
-#ifdef HAVE_CYRUS_SASL
 	js->serverFQDN = g_strdup(fqdn);
-#endif
 
 	if (purple_proxy_connect(js->gc, js->gc->account, host,
 			port, jabber_login_callback, js->gc) == NULL)
@@ -1026,9 +1017,9 @@
 		g_string_free(js->sasl_mechs, TRUE);
 	if(js->sasl_cb)
 		g_free(js->sasl_cb);
+#endif
 	if(js->serverFQDN)
 		g_free(js->serverFQDN);
-#endif
 	g_free(js->server_name);
 	g_free(js->gmail_last_time);
 	g_free(js->gmail_last_tid);
--- a/libpurple/protocols/jabber/jabber.h	Sat Aug 25 08:42:33 2007 +0000
+++ b/libpurple/protocols/jabber/jabber.h	Tue Aug 28 09:20:27 2007 +0000
@@ -136,6 +136,8 @@
 	char *gmail_last_time;
 	char *gmail_last_tid;
 
+    char *serverFQDN;
+
 	/* OK, this stays at the end of the struct, so plugins can depend
 	 * on the rest of the stuff being in the right place
 	 */
@@ -150,7 +152,6 @@
 	int sasl_state;
 	int sasl_maxbuf;
 	GString *sasl_mechs;
-	char *serverFQDN;
 
 	gboolean vcard_fetched;
 
--- a/libpurple/protocols/jabber/roster.c	Sat Aug 25 08:42:33 2007 +0000
+++ b/libpurple/protocols/jabber/roster.c	Tue Aug 28 09:20:27 2007 +0000
@@ -58,6 +58,7 @@
 {
 	GSList *buddies, *g2, *l;
 	gchar *my_bare_jid;
+	GList *pool = NULL;
 
 	buddies = purple_find_buddies(js->gc->account, jid);
 
@@ -89,13 +90,20 @@
 			g_free(l->data);
 			g2 = g_slist_delete_link(g2, l);
 		} else {
-			purple_blist_remove_buddy(b);
+			pool = g_list_prepend(pool, b);
 		}
 	}
 
 	while(g2) {
-		PurpleBuddy *b = purple_buddy_new(js->gc->account, jid, alias);
 		PurpleGroup *g = purple_find_group(g2->data);
+		PurpleBuddy *b = NULL;
+
+		if (pool) {
+			b = pool->data;
+			pool = g_list_delete_link(pool, pool);
+		} else {			
+			b = purple_buddy_new(js->gc->account, jid, alias);
+		}
 
 		if(!g) {
 			g = purple_group_new(g2->data);
@@ -121,6 +129,12 @@
 		g2 = g_slist_delete_link(g2, g2);
 	}
 
+	while (pool) {
+		PurpleBuddy *b = pool->data;
+		purple_blist_remove_buddy(b);
+		pool = g_list_delete_link(pool, pool);
+	}
+
 	g_free(my_bare_jid);
 	g_slist_free(buddies);
 }
--- a/libpurple/protocols/msn/msn.c	Sat Aug 25 08:42:33 2007 +0000
+++ b/libpurple/protocols/msn/msn.c	Tue Aug 28 09:20:27 2007 +0000
@@ -100,25 +100,62 @@
 	return buf;
 }
 
-static PurpleCmdRet
-msn_cmd_nudge(PurpleConversation *conv, const gchar *cmd, gchar **args, gchar **error, void *data)
+static gboolean
+msn_send_attention(PurpleConnection *gc, const char *username, guint type)
 {
-	PurpleAccount *account = purple_conversation_get_account(conv);
-	PurpleConnection *gc = purple_account_get_connection(account);
 	MsnMessage *msg;
 	MsnSession *session;
 	MsnSwitchBoard *swboard;
 
 	msg = msn_message_new_nudge();
 	session = gc->proto_data;
-	swboard = msn_session_get_swboard(session, purple_conversation_get_name(conv), MSN_SB_FLAG_IM);
+	swboard = msn_session_get_swboard(session, username, MSN_SB_FLAG_IM);
 
 	if (swboard == NULL)
-		return PURPLE_CMD_RET_FAILED;
+		return FALSE;
 
 	msn_switchboard_send_msg(swboard, msg, TRUE);
 
+	return TRUE;
+}
+
+#ifdef MSN_USE_ATTENTION_API
+static GList *
+msn_attention_types(PurpleAccount *account)
+{
+	PurpleAttentionType *attn;
+	static GList *list = NULL;
+
+	if (!list) {
+		attn = g_new0(PurpleAttentionType, 1);
+		attn->name = _("nudge");
+		attn->incoming_description = _("nudged");
+		attn->outgoing_description = _("Nudging");
+		list = g_list_append(list, attn);
+	}
+
+	return list;
+}
+#endif
+
+
+static PurpleCmdRet
+msn_cmd_nudge(PurpleConversation *conv, const gchar *cmd, gchar **args, gchar **error, void *data)
+{
+	PurpleAccount *account = purple_conversation_get_account(conv);
+	PurpleConnection *gc = purple_account_get_connection(account);
+	const gchar *username;
+
+	username = purple_conversation_get_name(conv);
+
+#ifdef MSN_USE_ATTENTION_API
+	serv_send_attention(gc, username, MSN_NUDGE);
+#else
+	if (!msn_send_attention(gc, username, MSN_NUDGE))
+		return PURPLE_CMD_RET_FAILED;
+
 	purple_conversation_write(conv, NULL, _("You have just sent a Nudge!"), PURPLE_MESSAGE_SYSTEM, time(NULL));
+#endif
 
 	return PURPLE_CMD_RET_OK;
 }
@@ -2127,9 +2164,14 @@
 	NULL,					/* send_raw */
 	NULL,					/* roomlist_room_serialize */
 
+#ifdef MSN_USE_ATTENTION_API
+	msn_send_attention,                     /* send_attention */
+	msn_attention_types,                    /* attention_types */
+#else
 	/* padding */
 	NULL,
 	NULL,
+#endif
 	NULL,
 	NULL
 };
--- a/libpurple/protocols/msn/msn.h	Sat Aug 25 08:42:33 2007 +0000
+++ b/libpurple/protocols/msn/msn.h	Tue Aug 28 09:20:27 2007 +0000
@@ -79,6 +79,10 @@
 	"Client-Name: Purple/" VERSION "\r\n" \
 	"Chat-Logging: Y\r\n"
 
+/* Index into attention_types */
+#define MSN_NUDGE 0
+
+#define MSN_USE_ATTENTION_API
 
 typedef enum
 {
--- a/libpurple/protocols/msn/switchboard.c	Sat Aug 25 08:42:33 2007 +0000
+++ b/libpurple/protocols/msn/switchboard.c	Tue Aug 28 09:20:27 2007 +0000
@@ -955,6 +955,8 @@
 	PurpleBuddy *buddy;
 	const char *user;
 
+	str = NULL;
+
 	swboard = cmdproc->data;
 	account = cmdproc->session->account;
 	user = msg->remote_user;
@@ -964,9 +966,13 @@
 	else
 		username = g_markup_escape_text(user, -1);
 
+#ifdef MSN_USE_ATTENTION_API
+	serv_got_attention(account->gc, buddy->name, MSN_NUDGE);
+#else
 	str = g_strdup_printf(_("%s just sent you a Nudge!"), username);
+	msn_switchboard_report_user(swboard, PURPLE_MESSAGE_SYSTEM|PURPLE_MESSAGE_NOTIFY, str);
+#endif
 	g_free(username);
-	msn_switchboard_report_user(swboard, PURPLE_MESSAGE_SYSTEM|PURPLE_MESSAGE_NOTIFY, str);
 	g_free(str);
 }
 
--- a/libpurple/protocols/myspace/Makefile.am	Sat Aug 25 08:42:33 2007 +0000
+++ b/libpurple/protocols/myspace/Makefile.am	Tue Aug 28 09:20:27 2007 +0000
@@ -2,7 +2,18 @@
 
 pkgdir = $(libdir)/purple-$(PURPLE_MAJOR_VERSION)
 
-SOURCES = myspace.c message.c
+SOURCES = myspace.c \
+	  myspace.h \
+	  persist.h \
+	  message.c \
+	  message.h \
+	  zap.c \
+	  session.c \
+	  session.h \
+	  markup.c \
+	  markup.h \
+	  user.c \
+	  user.h
 
 AM_CFLAGS = $(st)
 
--- a/libpurple/protocols/myspace/Makefile.mingw	Sat Aug 25 08:42:33 2007 +0000
+++ b/libpurple/protocols/myspace/Makefile.mingw	Tue Aug 28 09:20:27 2007 +0000
@@ -37,7 +37,7 @@
 ##
 ##  SOURCES, OBJECTS
 ##
-C_SRC =			myspace.c message.c
+C_SRC =			myspace.c message.c zap.c session.c markup.c user.c
 
 OBJECTS = $(C_SRC:%.c=%.o)
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/myspace/markup.c	Tue Aug 28 09:20:27 2007 +0000
@@ -0,0 +1,689 @@
+/* MySpaceIM Protocol Plugin - markup
+ *
+ * Copyright (C) 2007, Jeff Connelly <jeff2@soc.pidgin.im>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#include "myspace.h"
+
+typedef void (*MSIM_XMLNODE_CONVERT)(MsimSession *, xmlnode *, gchar **, gchar **);
+
+/* Internal functions */
+
+static guint msim_point_to_purple_size(MsimSession *session, guint point);
+static guint msim_purple_size_to_point(MsimSession *session, guint size);
+static guint msim_height_to_point(MsimSession *session, guint height);
+static guint msim_point_to_height(MsimSession *session, guint point);
+
+static void msim_markup_tag_to_html(MsimSession *, xmlnode *root, gchar **begin, gchar **end);
+static void html_tag_to_msim_markup(MsimSession *, xmlnode *root, gchar **begin, gchar **end);
+static gchar *msim_convert_xml(MsimSession *, const gchar *raw, MSIM_XMLNODE_CONVERT f);
+static gchar *msim_convert_smileys_to_markup(gchar *before);
+static double msim_round(double round);
+
+
+/* Globals */
+
+/* The names in in emoticon_names (for <i n=whatever>) map to corresponding 
+ * entries in emoticon_symbols (for the ASCII representation of the emoticon).
+ *
+ * Multiple emoticon symbols in Pidgin can map to one name. List the
+ * canonical form, as inserted by the "Smile!" dialog, first. For example,
+ * :) comes before :-), because although both are recognized as 'happy',
+ * the first is inserted by the smiley button (first symbol in theme).
+ *
+ * Note that symbols are case-sensitive in Pidgin -- :-X is not :-x. */
+static struct MSIM_EMOTICON
+{
+	gchar *name;
+	gchar *symbol;
+} msim_emoticons[] = {
+	/* Unfortunately, this list duplicates much of the file
+	 * pidgin/pidgin/pixmaps/emotes/default/22/default.theme.in, because
+	 * that file is part of Pidgin, but we're part of libpurple.
+	 */
+	{ "bigsmile", ":D" },
+	{ "bigsmile", ":-D" },
+	{ "devil", "}:)" },
+	{ "frazzled", ":Z" },
+	{ "geek", "B)" },
+	{ "googles", "%)" },
+	{ "growl", ":E" },
+	{ "laugh", ":))" },		/* Must be before ':)' */
+	{ "happy", ":)" },
+	{ "happy", ":-)" },
+	{ "happi", ":)" },
+	{ "heart", ":X" },
+	{ "mohawk", "-:" },
+	{ "mad", "X(" },
+	{ "messed", "X)" },
+	{ "nerd", "Q)" },
+	{ "oops", ":G" },
+	{ "pirate", "P)" },
+	{ "scared", ":O" },
+	{ "sidefrown", ":{" },
+	{ "sinister", ":B" },
+	{ "smirk", ":," },
+	{ "straight", ":|" },
+	{ "tongue", ":P" },
+	{ "tongue", ":p" },
+	{ "tongy", ":P" },
+	{ "upset", "B|" },
+	{ "wink", ";-)" },
+	{ "wink", ";)" },
+	{ "winc", ";)" },
+	{ "worried", ":[" },
+	{ "kiss", ":x" },
+	{ NULL, NULL }
+};
+
+
+
+/* Indexes of this array + 1 map HTML font size to scale of normal font size. *
+ * Based on _point_sizes from libpurple/gtkimhtml.c 
+ *                                 1    2  3    4     5      6       7 */
+static gdouble _font_scale[] = { .85, .95, 1, 1.2, 1.44, 1.728, 2.0736 };
+
+#define MAX_FONT_SIZE                   7       /* Purple maximum font size */
+#define POINTS_PER_INCH                 72      /* How many pt's in an inch */
+
+/* Text formatting bits for <f s=#> */
+#define MSIM_TEXT_BOLD                  1
+#define MSIM_TEXT_ITALIC                2   
+#define MSIM_TEXT_UNDERLINE             4
+
+/* Default baseline size of purple's fonts, in points. What is size 3 in points. 
+ * _font_scale specifies scaling factor relative to this point size. Note this 
+ * is only the default; it is configurable in account options. */
+#define MSIM_BASE_FONT_POINT_SIZE       8
+
+/* Default display's DPI. 96 is common but it can differ. Also configurable
+ * in account options. */
+#define MSIM_DEFAULT_DPI                96
+
+
+/* round is part of C99, but sometimes is unavailable before then.
+ * Based on http://forums.belution.com/en/cpp/000/050/13.shtml
+ */
+double msim_round(double value)
+{
+	if (value < 0) {
+		return -(floor(-value + 0.5));
+	} else {
+		return   floor( value + 0.5);
+	}
+}
+
+
+/** Convert typographical font point size to HTML font size. 
+ * Based on libpurple/gtkimhtml.c */
+static guint
+msim_point_to_purple_size(MsimSession *session, guint point)
+{
+	guint size, this_point, base;
+	gdouble scale;
+	
+	base = purple_account_get_int(session->account, "base_font_size", MSIM_BASE_FONT_POINT_SIZE);
+   
+	for (size = 0; 
+			size < sizeof(_font_scale) / sizeof(_font_scale[0]);
+			++size) {
+		scale = _font_scale[CLAMP(size, 1, MAX_FONT_SIZE) - 1];
+		this_point = (guint)msim_round(scale * base);
+
+		if (this_point >= point) {
+			purple_debug_info("msim", "msim_point_to_purple_size: %d pt -> size=%d\n",
+					point, size);
+			return size;
+		}
+	}
+
+	/* No HTML font size was this big; return largest possible. */
+	return this_point;
+}
+
+/** Convert HTML font size to point size. */
+static guint
+msim_purple_size_to_point(MsimSession *session, guint size)
+{
+	gdouble scale;
+	guint point;
+	guint base;
+
+	scale = _font_scale[CLAMP(size, 1, MAX_FONT_SIZE) - 1];
+
+	base = purple_account_get_int(session->account, "base_font_size", MSIM_BASE_FONT_POINT_SIZE);
+
+	point = (guint)msim_round(scale * base);
+
+	purple_debug_info("msim", "msim_purple_size_to_point: size=%d -> %d pt\n",
+					size, point);
+
+	return point;
+}
+
+/** Convert a msim markup font pixel height to the more usual point size, for incoming messages. */
+static guint 
+msim_height_to_point(MsimSession *session, guint height)
+{
+	guint dpi;
+
+	dpi = purple_account_get_int(session->account, "port", MSIM_DEFAULT_DPI);
+
+	return (guint)msim_round((POINTS_PER_INCH * 1. / dpi) * height);
+
+	/* See also: libpurple/protocols/bonjour/jabber.c
+	 * _font_size_ichat_to_purple */
+}
+
+/** Convert point size to msim pixel height font size specification, for outgoing messages. */
+static guint
+msim_point_to_height(MsimSession *session, guint point)
+{
+	guint dpi;
+
+	dpi = purple_account_get_int(session->account, "port", MSIM_DEFAULT_DPI);
+
+	return (guint)msim_round((dpi * 1. / POINTS_PER_INCH) * point);
+}
+
+/** Convert the msim markup <f> (font) tag into HTML. */
+static void 
+msim_markup_f_to_html(MsimSession *session, xmlnode *root, gchar **begin, gchar **end)
+{
+	const gchar *face, *height_str, *decor_str;
+	GString *gs_end, *gs_begin;
+	guint decor, height;
+
+	face = xmlnode_get_attrib(root, "f");
+	height_str = xmlnode_get_attrib(root, "h");
+	decor_str = xmlnode_get_attrib(root, "s");
+
+	if (height_str) {
+		height = atol(height_str);
+	} else {
+		height = 12;
+	}
+
+	if (decor_str) {
+		decor = atol(decor_str);
+	} else {
+		decor = 0;
+	}
+
+	gs_begin = g_string_new("");
+	/* TODO: get font size working */
+	if (height && !face) {
+		g_string_printf(gs_begin, "<font size='%d'>", 
+				msim_point_to_purple_size(session, msim_height_to_point(session, height)));
+	} else if (height && face) {
+		g_string_printf(gs_begin, "<font face='%s' size='%d'>", face,  
+				msim_point_to_purple_size(session, msim_height_to_point(session, height)));
+	} else {
+		g_string_printf(gs_begin, "<font>");
+	}
+
+	/* No support for font-size CSS? */
+	/* g_string_printf(gs_begin, "<span style='font-family: %s; font-size: %dpt'>", face, 
+			msim_height_to_point(height)); */
+
+	gs_end = g_string_new("</font>");
+
+	if (decor & MSIM_TEXT_BOLD) {
+		g_string_append(gs_begin, "<b>");
+		g_string_prepend(gs_end, "</b>");
+	}
+
+	if (decor & MSIM_TEXT_ITALIC) {
+		g_string_append(gs_begin, "<i>");
+		g_string_append(gs_end, "</i>");
+	}
+
+	if (decor & MSIM_TEXT_UNDERLINE) {
+		g_string_append(gs_begin, "<u>");
+		g_string_append(gs_end, "</u>");
+	}
+
+
+	*begin = gs_begin->str;
+	*end = gs_end->str;
+}
+
+/** Convert a msim markup color to a color suitable for libpurple.
+  *
+  * @param msim Either a color name, or an rgb(x,y,z) code.
+  *
+  * @return A new string, either a color name or #rrggbb code. Must g_free(). 
+  */
+static char *
+msim_color_to_purple(const char *msim)
+{
+	guint red, green, blue;
+
+	if (!msim) {
+		return g_strdup("black");
+	}
+
+	if (sscanf(msim, "rgb(%d,%d,%d)", &red, &green, &blue) != 3) {
+		/* Color name. */
+		return g_strdup(msim);
+	}
+	/* TODO: rgba (alpha). */
+
+	return g_strdup_printf("#%.2x%.2x%.2x", red, green, blue);
+}
+
+/** Convert the msim markup <a> (anchor) tag into HTML. */
+static void 
+msim_markup_a_to_html(MsimSession *session, xmlnode *root, gchar **begin, gchar **end)
+{
+	const gchar *href;
+
+	href = xmlnode_get_attrib(root, "h");
+	if (!href) {
+		href = "";
+	}
+
+	*begin = g_strdup_printf("<a href=\"%s\">%s", href, href);
+	*end = g_strdup("</a>");
+}
+
+/** Convert the msim markup <p> (paragraph) tag into HTML. */
+static void 
+msim_markup_p_to_html(MsimSession *session, xmlnode *root, gchar **begin, gchar **end)
+{
+	/* Just pass through unchanged. 
+	 *
+	 * Note: attributes currently aren't passed, if there are any. */
+	*begin = g_strdup("<p>");
+	*end = g_strdup("</p>");
+}
+
+/** Convert the msim markup <c> tag (text color) into HTML. TODO: Test */
+static void 
+msim_markup_c_to_html(MsimSession *session, xmlnode *root, gchar **begin, gchar **end)
+{
+	const gchar *color;
+	gchar *purple_color;
+
+	color = xmlnode_get_attrib(root, "v");
+	if (!color) {
+		purple_debug_info("msim", "msim_markup_c_to_html: <c> tag w/o v attr");
+		*begin = g_strdup("");
+		*end = g_strdup("");
+		/* TODO: log as unrecognized */
+		return;
+	}
+
+	purple_color = msim_color_to_purple(color);
+
+	*begin = g_strdup_printf("<font color='%s'>", purple_color); 
+
+	g_free(purple_color);
+
+	/* *begin = g_strdup_printf("<span style='color: %s'>", color); */
+	*end = g_strdup("</font>");
+}
+
+/** Convert the msim markup <b> tag (background color) into HTML. TODO: Test */
+static void 
+msim_markup_b_to_html(MsimSession *session, xmlnode *root, gchar **begin, gchar **end)
+{
+	const gchar *color;
+	gchar *purple_color;
+
+	color = xmlnode_get_attrib(root, "v");
+	if (!color) {
+		*begin = g_strdup("");
+		*end = g_strdup("");
+		purple_debug_info("msim", "msim_markup_b_to_html: <b> w/o v attr");
+		/* TODO: log as unrecognized. */
+		return;
+	}
+
+	purple_color = msim_color_to_purple(color);
+
+	/* TODO: find out how to set background color. */
+	*begin = g_strdup_printf("<span style='background-color: %s'>", 
+			purple_color);
+	g_free(purple_color);
+
+	*end = g_strdup("</p>");
+}
+
+/** Convert the msim markup <i> tag (emoticon image) into HTML. */
+static void 
+msim_markup_i_to_html(MsimSession *session, xmlnode *root, gchar **begin, gchar **end)
+{
+	const gchar *name;
+	guint i;
+	struct MSIM_EMOTICON *emote;
+
+	name = xmlnode_get_attrib(root, "n");
+	if (!name) {
+		purple_debug_info("msim", "msim_markup_i_to_html: <i> w/o n");
+		*begin = g_strdup("");
+		*end = g_strdup("");
+		/* TODO: log as unrecognized */
+		return;
+	}
+
+	/* Find and use canonical form of smiley symbol. */
+	for (i = 0; (emote = &msim_emoticons[i]) && emote->name != NULL; ++i) {
+		if (g_str_equal(name, emote->name)) {
+			*begin = g_strdup(emote->symbol);
+			*end = g_strdup("");
+			return;
+		}
+	}
+
+	/* Couldn't find it, sorry. Try to degrade gracefully. */
+	*begin = g_strdup_printf("**%s**", name);
+	*end = g_strdup("");
+}
+
+/** Convert an individual msim markup tag to HTML. */
+static void 
+msim_markup_tag_to_html(MsimSession *session, xmlnode *root, gchar **begin, 
+		gchar **end)
+{
+	if (g_str_equal(root->name, "f")) {
+		msim_markup_f_to_html(session, root, begin, end);
+	} else if (g_str_equal(root->name, "a")) {
+		msim_markup_a_to_html(session, root, begin, end);
+	} else if (g_str_equal(root->name, "p")) {
+		msim_markup_p_to_html(session, root, begin, end);
+	} else if (g_str_equal(root->name, "c")) {
+		msim_markup_c_to_html(session, root, begin, end);
+	} else if (g_str_equal(root->name, "b")) {
+		msim_markup_b_to_html(session, root, begin, end);
+	} else if (g_str_equal(root->name, "i")) {
+		msim_markup_i_to_html(session, root, begin, end);
+	} else {
+		purple_debug_info("msim", "msim_markup_tag_to_html: "
+				"unknown tag name=%s, ignoring", 
+				(root && root->name) ? root->name : "(NULL)");
+		*begin = g_strdup("");
+		*end = g_strdup("");
+	}
+}
+
+/** Convert an individual HTML tag to msim markup. */
+static void 
+html_tag_to_msim_markup(MsimSession *session, xmlnode *root, gchar **begin, 
+		gchar **end)
+{
+	/* TODO: Coalesce nested tags into one <f> tag!
+	 * Currently, the 's' value will be overwritten when b/i/u is nested
+	 * within another one, and only the inner-most formatting will be 
+	 * applied to the text. */
+	if (!purple_utf8_strcasecmp(root->name, "root")) {
+		*begin = g_strdup("");
+		*end = g_strdup("");
+	} else if (!purple_utf8_strcasecmp(root->name, "b")) {
+		*begin = g_strdup_printf("<f s='%d'>", MSIM_TEXT_BOLD);
+		*end = g_strdup("</f>");
+	} else if (!purple_utf8_strcasecmp(root->name, "i")) {
+		*begin = g_strdup_printf("<f s='%d'>", MSIM_TEXT_ITALIC);
+		*end = g_strdup("</f>");
+	} else if (!purple_utf8_strcasecmp(root->name, "u")) {
+		*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;
+
+		href = xmlnode_get_attrib(root, "href");
+
+		if (!href) {
+			href = xmlnode_get_attrib(root, "HREF");
+		}
+
+		link_text = xmlnode_get_data(root);
+
+		if (href) {
+			if (g_str_equal(link_text, href)) {
+				/* Purple gives us: <a href="URL">URL</a>
+				 * Translate to <a h='URL' />
+				 * Displayed as text of URL with link to URL
+				 */
+				*begin = g_strdup_printf("<a h='%s' />", href);
+			} else {
+				/* But if we get: <a href="URL">text</a>
+				 * Translate to: text: <a h='URL' />
+				 *
+				 * Because official client only supports self-closed <a>
+				 * tags; you can't change the link text.
+				 */
+				*begin = g_strdup_printf("%s: <a h='%s' />", link_text, href);
+			}
+		} else {
+			*begin = g_strdup("<a />");
+		}
+
+		/* Sorry, kid. MySpace doesn't support you within <a> tags. */
+		xmlnode_free(root->child);
+		root->child = NULL;
+
+		*end = g_strdup("");
+	} else if (!purple_utf8_strcasecmp(root->name, "font")) {
+		const gchar *size;
+		const gchar *face;
+
+		size = xmlnode_get_attrib(root, "size");
+		face = xmlnode_get_attrib(root, "face");
+
+		if (face && size) {
+			*begin = g_strdup_printf("<f f='%s' h='%d'>", face, 
+					msim_point_to_height(session,
+						msim_purple_size_to_point(session, atoi(size))));
+		} else if (face) {
+			*begin = g_strdup_printf("<f f='%s'>", face);
+		} else if (size) {
+			*begin = g_strdup_printf("<f h='%d'>", 
+					 msim_point_to_height(session,
+						 msim_purple_size_to_point(session, atoi(size))));
+		} else {
+			*begin = g_strdup("<f>");
+		}
+
+		*end = g_strdup("</f>");
+
+		/* TODO: color (bg uses <body>), emoticons */
+	} else {
+		*begin = g_strdup_printf("[%s]", root->name);
+		*end = g_strdup_printf("[/%s]", root->name);
+	}
+}
+
+/** Convert an xmlnode of msim markup or HTML to an HTML string or msim markup.
+ *
+ * @param f Function to convert tags.
+ *
+ * @return An HTML string. Caller frees.
+ */
+static gchar *
+msim_convert_xmlnode(MsimSession *session, xmlnode *root, MSIM_XMLNODE_CONVERT f)
+{
+	xmlnode *node;
+	gchar *begin, *inner, *end;
+	GString *final;
+
+	if (!root || !root->name) {
+		return g_strdup("");
+	}
+
+	purple_debug_info("msim", "msim_convert_xmlnode: got root=%s\n",
+			root->name);
+
+	begin = inner = end = NULL;
+
+	final = g_string_new("");
+
+	f(session, root, &begin, &end);
+	
+	g_string_append(final, begin);
+
+	/* Loop over all child nodes. */
+	for (node = root->child; node != NULL; node = node->next) {
+		switch (node->type) {
+		case XMLNODE_TYPE_ATTRIB:
+			/* Attributes handled above. */
+			break;
+
+		case XMLNODE_TYPE_TAG:
+			/* A tag or tag with attributes. Recursively descend. */
+			inner = msim_convert_xmlnode(session, node, f);
+			g_return_val_if_fail(inner != NULL, NULL);
+
+			purple_debug_info("msim", " ** node name=%s\n", 
+					(node && node->name) ? node->name : "(NULL)");
+			break;
+	
+		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;
+
+			purple_debug_info("msim", " ** node data=%s\n", 
+					inner ? inner : "(NULL)");
+			break;
+			
+		default:
+			purple_debug_info("msim",
+					"msim_convert_xmlnode: strange node\n");
+			inner = g_strdup("");
+		}
+
+		if (inner) {
+			g_string_append(final, inner);
+		}
+	}
+
+	/* TODO: Note that msim counts each piece of text enclosed by <f> as
+	 * a paragraph and will display each on its own line. You actually have
+	 * to _nest_ <f> tags to intersperse different text in one paragraph!
+	 * Comment out this line below to see. */
+	g_string_append(final, end);
+
+	purple_debug_info("msim", "msim_markup_xmlnode_to_gtkhtml: RETURNING %s\n",
+			(final && final->str) ? final->str : "(NULL)");
+
+	return final->str;
+}
+
+/** Convert XML to something based on MSIM_XMLNODE_CONVERT. */
+static gchar *
+msim_convert_xml(MsimSession *session, const gchar *raw, MSIM_XMLNODE_CONVERT f)
+{
+	xmlnode *root;
+	gchar *str;
+	gchar *enclosed_raw;
+
+	g_return_val_if_fail(raw != NULL, NULL);
+
+	/* Enclose text in one root tag, to try to make it valid XML for parsing. */
+	enclosed_raw = g_strconcat("<root>", raw, "</root>", NULL);
+
+	root = xmlnode_from_str(enclosed_raw, -1);
+
+	if (!root) {
+		purple_debug_info("msim", "msim_markup_to_html: couldn't parse "
+				"%s as XML, returning raw: %s\n", enclosed_raw, raw);
+		/* TODO: msim_unrecognized */
+		g_free(enclosed_raw);
+		return g_strdup(raw);
+	}
+
+	g_free(enclosed_raw);
+
+	str = msim_convert_xmlnode(session, root, f);
+	g_return_val_if_fail(str != NULL, NULL);
+	purple_debug_info("msim", "msim_markup_to_html: returning %s\n", str);
+
+	xmlnode_free(root);
+
+	return str;
+}
+
+/** Convert plaintext smileys to <i> markup tags.
+ *
+ * @param before Original text with ASCII smileys. Will be freed.
+ * @return A new string with <i> tags, if applicable. Must be g_free()'d.
+ */
+static gchar *
+msim_convert_smileys_to_markup(gchar *before)
+{
+	gchar *old, *new, *replacement;
+	guint i;
+	struct MSIM_EMOTICON *emote;
+
+	old = before;
+	new = NULL;
+
+	for (i = 0; (emote = &msim_emoticons[i]) && emote->name != NULL; ++i) {
+		gchar *name, *symbol;
+
+		name = emote->name;
+		symbol = emote->symbol;
+
+		replacement = g_strdup_printf("<i n=\"%s\"/>", name);
+
+		purple_debug_info("msim", "msim_convert_smileys_to_markup: %s->%s\n",
+				symbol ? symbol : "(NULL)", 
+				replacement ? replacement : "(NULL)");
+		new = purple_strreplace(old, symbol, replacement);
+		
+		g_free(replacement);
+		g_free(old);
+
+		old = new;
+	}
+
+	return new;
+}
+	
+
+/** High-level function to convert MySpaceIM markup to Purple (HTML) markup. 
+ *
+ * @return Purple markup string, must be g_free()'d. */
+gchar *
+msim_markup_to_html(MsimSession *session, const gchar *raw)
+{
+	return msim_convert_xml(session, raw, 
+			(MSIM_XMLNODE_CONVERT)(msim_markup_tag_to_html));
+}
+
+/** High-level function to convert Purple (HTML) to MySpaceIM markup.
+ *
+ * @return HTML markup string, must be g_free()'d. */
+gchar *
+html_to_msim_markup(MsimSession *session, const gchar *raw)
+{
+	gchar *markup;
+
+	markup = msim_convert_xml(session, raw,
+			(MSIM_XMLNODE_CONVERT)(html_tag_to_msim_markup));
+	
+	if (purple_account_get_bool(session->account, "emoticons", TRUE)) {
+		/* Frees markup and allocates a new one. */
+		markup = msim_convert_smileys_to_markup(markup);
+	}
+
+	return markup;
+}
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/myspace/markup.h	Tue Aug 28 09:20:27 2007 +0000
@@ -0,0 +1,27 @@
+/* MySpaceIM Protocol Plugin - markup
+ *
+ * Copyright (C) 2007, Jeff Connelly <jeff2@soc.pidgin.im>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#ifndef _MYSPACE_MARKUP_H
+#define _MYSPACE_MARKUP_H
+
+/* High-level msim markup <=> Purple html conversion functions. */
+gchar *msim_markup_to_html(MsimSession *, const gchar *raw);
+gchar *html_to_msim_markup(MsimSession *, const gchar *raw);
+
+#endif /* !_MYSPACE_MARKUP_H */
--- a/libpurple/protocols/myspace/message.c	Sat Aug 25 08:42:33 2007 +0000
+++ b/libpurple/protocols/myspace/message.c	Tue Aug 28 09:20:27 2007 +0000
@@ -250,13 +250,21 @@
 	return new_list;
 }
 
-/** Free a GList * of gchar * strings. */
+/** Free a GList * of MsimMessageElement *'s. */
 void
 msim_msg_list_free(GList *l)
 {
 
 	for (; l != NULL; l = g_list_next(l)) {
-		g_free((gchar *)(l->data));
+		MsimMessageElement *elem;
+
+		elem = (MsimMessageElement *)l->data;
+
+		/* Note that name is almost never dynamically allocated elsewhere;
+		 * it is usually a static string, but not in lists. So cast it. */
+		g_free((gchar *)elem->name);
+		g_free(elem->data);
+		g_free(elem);
 	}
 	g_list_free(l);
 }
@@ -275,7 +283,19 @@
 	/* TODO: escape/unescape /3 <-> | within list elements */
 	
 	for (i = 0; array[i] != NULL; ++i) {
-		list = g_list_append(list, g_strdup(array[i]));
+		MsimMessageElement *elem;
+
+		/* Freed in msim_msg_list_free() */
+		elem = g_new0(MsimMessageElement, 1);
+
+		/* Give the element a name for debugging purposes.
+		 * Not supposed to be looked up by this name; instead,
+		 * lookup the elements by indexing the array. */
+		elem->name = g_strdup_printf("(list item #%d)", i);
+		elem->type = MSIM_TYPE_RAW;
+		elem->data = g_strdup(array[i]);
+
+		list = g_list_append(list, elem);
 	}
 
 	g_strfreev(array);
--- a/libpurple/protocols/myspace/myspace.c	Sat Aug 25 08:42:33 2007 +0000
+++ b/libpurple/protocols/myspace/myspace.c	Tue Aug 28 09:20:27 2007 +0000
@@ -33,135 +33,37 @@
 
 #define PURPLE_PLUGIN
 
-#include "message.h"
-#include "persist.h"
 #include "myspace.h"
 
-/* Globals */
-
-/* The names in in emoticon_names (for <i n=whatever>) map to corresponding 
- * entries in emoticon_symbols (for the ASCII representation of the emoticon).
- *
- * Multiple emoticon symbols in Pidgin can map to one name. List the
- * canonical form, as inserted by the "Smile!" dialog, first. For example,
- * :) comes before :-), because although both are recognized as 'happy',
- * the first is inserted by the smiley button (first symbol in theme).
- *
- * Note that symbols are case-sensitive in Pidgin -- :-X is not :-x. */
-static struct MSIM_EMOTICON
-{
-	gchar *name;
-	gchar *symbol;
-} msim_emoticons[] = {
-	/* Unfortunately, this list duplicates much of the file
-	 * pidgin/pidgin/pixmaps/emotes/default/22/default.theme.in, because
-	 * that file is part of Pidgin, but we're part of libpurple.
-	 */
-	{ "bigsmile", ":D" },
-	{ "bigsmile", ":-D" },
-	{ "devil", "}:)" },
-	{ "frazzled", ":Z" },
-	{ "geek", "B)" },
-	{ "googles", "%)" },
-	{ "growl", ":E" },
-	{ "laugh", ":))" },		/* Must be before ':)' */
-	{ "happy", ":)" },
-	{ "happy", ":-)" },
-	{ "happi", ":)" },
-	{ "heart", ":X" },
-	{ "mohawk", "-:" },
-	{ "mad", "X(" },
-	{ "messed", "X)" },
-	{ "nerd", "Q)" },
-	{ "oops", ":G" },
-	{ "pirate", "P)" },
-	{ "scared", ":O" },
-	{ "sidefrown", ":{" },
-	{ "sinister", ":B" },
-	{ "smirk", ":," },
-	{ "straight", ":|" },
-	{ "tongue", ":P" },
-	{ "tongue", ":p" },
-	{ "tongy", ":P" },
-	{ "upset", "B|" },
-	{ "wink", ";-)" },
-	{ "wink", ";)" },
-	{ "winc", ";)" },
-	{ "worried", ":[" },
-	{ "kiss", ":x" },
-	{ NULL, NULL }
-};
-
 /* Internal functions */
-static gboolean msim_send_zap(MsimSession *session, const gchar *username, guint code);
-static void msim_send_zap_from_menu(PurpleBlistNode *node, gpointer zap_num_ptr);
 
 #ifdef MSIM_DEBUG_MSG
 static void print_hash_item(gpointer key, gpointer value, gpointer user_data);
 #endif
 
-static int msim_send_really_raw(PurpleConnection *gc, const char *buf,
-		int total_bytes);
+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 gboolean msim_send_bm(MsimSession *session, const gchar *who, 
-		const gchar *text, int type);
-
-static guint msim_point_to_purple_size(MsimSession *session, guint point);
-static guint msim_purple_size_to_point(MsimSession *session, guint size);
-static guint msim_height_to_point(MsimSession *session, guint height);
-static guint msim_point_to_height(MsimSession *session, guint point);
-
-static void msim_unrecognized(MsimSession *session, MsimMessage *msg, gchar *note);
-
-static void msim_markup_tag_to_html(MsimSession *, xmlnode *root, 
-		gchar **begin, gchar **end);
-static void html_tag_to_msim_markup(MsimSession *, xmlnode *root, 
-		gchar **begin, gchar **end);
-static gchar *msim_convert_xml(MsimSession *, const gchar *raw, 
-		MSIM_XMLNODE_CONVERT f);
-static gchar *msim_convert_smileys_to_markup(gchar *before);
-
-/* High-level msim markup <=> html conversion functions. */
-static gchar *msim_markup_to_html(MsimSession *, const gchar *raw);
-static gchar *html_to_msim_markup(MsimSession *, const gchar *raw);
-
-static MsimUser *msim_get_user_from_buddy(PurpleBuddy *buddy);
-static MsimUser *msim_find_user(MsimSession *session, const gchar *username);
-
-static gboolean msim_incoming_bm_record_cv(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 gboolean msim_incoming_bm_record_cv(MsimSession *session, MsimMessage *msg);
 static gboolean msim_incoming_bm(MsimSession *session, MsimMessage *msg);
 static gboolean msim_incoming_status(MsimSession *session, MsimMessage *msg);
 static gboolean msim_incoming_im(MsimSession *session, MsimMessage *msg);
-static gboolean msim_incoming_zap(MsimSession *session, MsimMessage *msg);
+/* static gboolean msim_incoming_zap(MsimSession *session, MsimMessage *msg); - in zap.c */
 static gboolean msim_incoming_action(MsimSession *session, MsimMessage *msg);
 static gboolean msim_incoming_media(MsimSession *session, MsimMessage *msg);
 static gboolean msim_incoming_unofficial_client(MsimSession *session, 
 		MsimMessage *msg);
 
 #ifdef MSIM_SEND_CLIENT_VERSION
-static gboolean msim_send_unofficial_client(MsimSession *session, 
-		gchar *username);
+static gboolean msim_send_unofficial_client(MsimSession *session, gchar *username);
 #endif
 
 static void msim_get_info_cb(MsimSession *session, MsimMessage *userinfo, gpointer data);
-static gchar *msim_format_now_playing(gchar *band, gchar *song);
-
-static void msim_set_status_code(MsimSession *session, guint code, 
-		gchar *statstring);
-
-static void msim_append_user_info(MsimSession *session, PurpleNotifyUserInfo *user_info, MsimUser *user, gboolean full);
-
-static void msim_downloaded_buddy_icon(PurpleUtilFetchUrlData *url_data, gpointer user_data, const gchar *url_text,
-		gsize len, const gchar *error_message);
-
-static void msim_store_user_info_each(const gchar *key_str, gchar *value_str, MsimUser *user);
-static gboolean msim_store_user_info(MsimSession *session, MsimMessage *msg, MsimUser *user);
-static gboolean msim_process_server_info(MsimSession *session, 
-		MsimMessage *msg);
+
+static void msim_set_status_code(MsimSession *session, guint code, gchar *statstring);
+
+static gboolean msim_process_server_info(MsimSession *session, MsimMessage *msg);
 static gboolean msim_web_challenge(MsimSession *session, MsimMessage *msg); 
 static gboolean msim_process_reply(MsimSession *session, MsimMessage *msg);
 
@@ -175,50 +77,27 @@
 
 static gboolean msim_process(MsimSession *session, MsimMessage *msg);
 
-static MsimMessage *msim_do_postprocessing(MsimMessage *msg, 
-		const gchar *uid_field_name, const gchar *uid_before, guint uid);
-static void msim_postprocess_outgoing_cb(MsimSession *session, 
-		MsimMessage *userinfo, gpointer data);
-static gboolean msim_postprocess_outgoing(MsimSession *session, 
-		MsimMessage *msg, const gchar *username, const gchar *uid_field_name, 
-		const gchar *uid_before); 
+static MsimMessage *msim_do_postprocessing(MsimMessage *msg, const gchar *uid_field_name, const gchar *uid_before, guint uid);
+static void msim_postprocess_outgoing_cb(MsimSession *session, MsimMessage *userinfo, gpointer data);
+static gboolean msim_postprocess_outgoing(MsimSession *session, MsimMessage *msg, const gchar *username, const gchar *uid_field_name, const gchar *uid_before); 
 
 static gboolean msim_error(MsimSession *session, MsimMessage *msg);
 
-static void msim_check_inbox_cb(MsimSession *session, MsimMessage *userinfo, 
-		gpointer data);
+static void msim_check_inbox_cb(MsimSession *session, MsimMessage *userinfo, gpointer data);
 static gboolean msim_check_inbox(gpointer data);
 
-static void msim_input_cb(gpointer gc_uncasted, gint source, 
-		PurpleInputCondition cond);
-
-static guint msim_new_reply_callback(MsimSession *session, 
-		MSIM_USER_LOOKUP_CB cb, gpointer data);
-
-static void msim_connect_cb(gpointer data, gint source, 
-		const gchar *error_message);
-
-static gboolean msim_is_userid(const gchar *user);
-static gboolean msim_is_email(const gchar *user);
-
-static void msim_lookup_user(MsimSession *session, const gchar *user, 
-		MSIM_USER_LOOKUP_CB cb, gpointer data);
+static void msim_input_cb(gpointer gc_uncasted, gint source, PurpleInputCondition cond);
+
+
+static void msim_connect_cb(gpointer data, gint source, const gchar *error_message);
 
 static void msim_import_friends(PurplePluginAction *action);
-
-double msim_round(double round);
-
-/* round is part of C99, but sometimes is unavailable before then.
- * Based on http://forums.belution.com/en/cpp/000/050/13.shtml
- */
-double msim_round(double value)
-{
-	if (value < 0) {
-		return -(floor(-value + 0.5));
-	} else {
-		return   floor( value + 0.5);
-	}
-}
+static void msim_import_friends_cb(MsimSession *session, MsimMessage *reply, gpointer user_data);
+static gboolean msim_get_contact_list(MsimSession *session, int what_to_do_after);
+
+static gboolean msim_uri_handler(const gchar *proto, const gchar *cmd, GHashTable *params);
+static void msim_uri_handler_addContact_cb(MsimSession *session, MsimMessage *userinfo, gpointer data);
+static void msim_uri_handler_sendIM_cb(MsimSession *session, MsimMessage *userinfo, gpointer data);
 
 /** 
  * Load the plugin.
@@ -283,195 +162,6 @@
 	return types;
 }
 
-/** Get zap types. */
-GList *
-msim_attention_types(PurpleAccount *acct)
-{
-	static GList *types = NULL;
-	MsimAttentionType* attn;
-
-	if (!types) {
-#define _MSIM_ADD_NEW_ATTENTION(icn, des, incoming, outgoing)              \
-		attn = g_new0(MsimAttentionType, 1);                     \
-		attn->icon = icn;                                          \
-		attn->description = des;                                   \
-		attn->incoming_description = incoming;                     \
-		attn->outgoing_description = outgoing;                     \
-		types = g_list_append(types, attn);
-
-		/* TODO: icons for each zap */
-		_MSIM_ADD_NEW_ATTENTION(NULL, _("zap"), _("zapped"), _("Zapping"));
-		_MSIM_ADD_NEW_ATTENTION(NULL, _("whack"), _("whacked"), _("Whacking"));
-		_MSIM_ADD_NEW_ATTENTION(NULL, _("torch"), _("torched"), _("Torching"));
-		_MSIM_ADD_NEW_ATTENTION(NULL, _("smooch"), _("smooched"), _("Smooching"));
-		_MSIM_ADD_NEW_ATTENTION(NULL, _("hug"), _("hugged"), _("Hugging"));
-		_MSIM_ADD_NEW_ATTENTION(NULL, _("bslap"), _("bslapped"), _("Bslapping"));
-		_MSIM_ADD_NEW_ATTENTION(NULL, _("goose"), _("goosed"), _("Goosing"));
-		_MSIM_ADD_NEW_ATTENTION(NULL, _("hi-five"), _("hi-fived"), _("Hi-fiving"));
-		_MSIM_ADD_NEW_ATTENTION(NULL, _("punk"), _("punk'd"), _("Punking"));
-		_MSIM_ADD_NEW_ATTENTION(NULL, _("raspberry"), _("raspberried"), _("Raspberry'ing"));
-	}
-
-	return types;
-}
-
-/** Send a zap */
-gboolean
-msim_send_attention(PurpleConnection *gc, gchar *username, guint code)
-{
-	GList *types;
-	MsimSession *session;
-	MsimAttentionType *attn;
-	PurpleBuddy *buddy;
-
-	session = (MsimSession *)gc->proto_data;
-
-	/* Look for this attention type, by the code index given. */
-	types = msim_attention_types(gc->account);
-	attn = (MsimAttentionType *)g_list_nth_data(types, code);
-
-	if (!attn) {
-		purple_debug_info("msim_send_attention", "got invalid zap code %d\n", code);
-		return FALSE;
-	}
-
-	buddy = purple_find_buddy(session->account, username);
-	if (!buddy) {
-		return FALSE;
-	}
-
-	/* TODO: make use of the MsimAttentionType we found, instead of
-	 * doing it all over in msim_send_zap_from_menu. */
-	msim_send_zap_from_menu(&buddy->node, GUINT_TO_POINTER(code));
-
-	return TRUE;
-}
-
-/** Send a zap to a user. */
-static gboolean
-msim_send_zap(MsimSession *session, const gchar *username, guint code)
-{
-	gchar *zap_string;
-#ifndef MSIM_USE_ATTENTION_API
-	gchar *zap_description;
-#endif
-	GList *types;
-	MsimAttentionType *attn;
-	gboolean rc;
-
-	g_return_val_if_fail(session != NULL, FALSE);
-	g_return_val_if_fail(username != NULL, FALSE);
-
-	types = msim_attention_types(session->account);
-
-	attn = g_list_nth_data(types, code);
-	if (!attn) {
-		return FALSE;
-	}
-
-
-#ifdef MSIM_USE_ATTENTION_API
-	serv_got_attention(session->gc, username, attn, FALSE);
-#else
-	zap_description = g_strdup_printf("*** Attention: %s %s ***", attn->outgoing_description,
-			username);
-
-	serv_got_im(session->gc, username, zap_description,
-			PURPLE_MESSAGE_SEND | PURPLE_MESSAGE_SYSTEM, time(NULL));
-
-	g_free(zap_description);
-#endif
-
-	/* Construct and send the actual zap command. */
-	zap_string = g_strdup_printf("!!!ZAP_SEND!!!=RTE_BTN_ZAPS_%d", code);
-
-	if (!msim_send_bm(session, username, zap_string, MSIM_BM_ACTION)) {
-		purple_debug_info("msim_send_zap_from_menu", "msim_send_bm failed: zapping %s with %s",
-				username, zap_string);
-		rc = FALSE;
-	} else {
-		rc = TRUE;
-	}
-	
-	g_free(zap_string);
-
-	return rc;
-
-}
-
-/** Zap someone. Callback from msim_blist_node_menu zap menu. */
-static void
-msim_send_zap_from_menu(PurpleBlistNode *node, gpointer zap_num_ptr)
-{
-	PurpleBuddy *buddy;
-	PurpleAccount *account;
-	PurpleConnection *gc;
-	MsimSession *session;
-	guint zap;
-
-	if (!PURPLE_BLIST_NODE_IS_BUDDY(node)) {
-		/* Only know about buddies for now. */
-		return;
-	}
-
-	g_return_if_fail(PURPLE_BLIST_NODE_IS_BUDDY(node));
-
-	buddy = (PurpleBuddy *)node;
-
-	/* Find the session */
-	account = buddy->account;
-	gc = purple_account_get_connection(account);
-	session = (MsimSession *)gc->proto_data;
-
-	zap = GPOINTER_TO_INT(zap_num_ptr);
-
-	g_return_if_fail(msim_send_zap(session, buddy->name, zap));
-}
-
-
-/** Return menu, if any, for a buddy list node. */
-GList *
-msim_blist_node_menu(PurpleBlistNode *node)
-{
-	GList *menu, *zap_menu;
-	GList *types;
-	PurpleMenuAction *act;
-	/* Warning: hardcoded to match that in msim_attention_types. */
-	const gchar *zap_names[10];
-	guint i;
-
-	if (!PURPLE_BLIST_NODE_IS_BUDDY(node)) {
-		/* Only know about buddies for now. */
-		return NULL;
-	}
-
-	/* Names from official client. */
-	types = msim_attention_types(NULL);
-	i = 0;
-	do
-	{
-		MsimAttentionType *attn;
-
-		attn = (MsimAttentionType *)types->data;
-		zap_names[i] = attn->description;
-		++i;
-	} while ((types = g_list_next(types)));
-
-	menu = zap_menu = NULL;
-
-	/* TODO: get rid of once is accessible directly in GUI */
-	for (i = 0; i < sizeof(zap_names) / sizeof(zap_names[0]); ++i) {
-		act = purple_menu_action_new(zap_names[i], PURPLE_CALLBACK(msim_send_zap_from_menu),
-				GUINT_TO_POINTER(i), NULL);
-		zap_menu = g_list_append(zap_menu, act);
-	}
-
-	act = purple_menu_action_new(_("Zap"), NULL, NULL, zap_menu);
-	menu = g_list_append(menu, act);
-
-	return menu;
-}
-
 /**
  * Return the icon name for a buddy and account.
  *
@@ -488,32 +178,6 @@
 	return "myspace";
 }
 
-/**
- * Replace 'old' with 'new' in 'str'.
- *
- * @param str The original string.
- * @param old The substring of 'str' to replace.
- * @param new The replacement for 'old' within 'str'.
- *
- * @return A _new_ string, based on 'str', with 'old' replaced
- *         by 'new'. Must be g_free()'d by caller.
- *
- * This string replace method is based on
- * http://mail.gnome.org/archives/gtk-app-devel-list/2000-July/msg00201.html
- *
- */
-gchar *
-str_replace(const gchar *str, const gchar *old, const gchar *new)
-{
-	gchar **items;
-	gchar *ret;
-
-	items = g_strsplit(str, old, -1);
-	ret = g_strjoinv(new, items);
-	g_free(items);
-	return ret;
-}
-
 #ifdef MSIM_DEBUG_MSG
 static void 
 print_hash_item(gpointer key, gpointer value, gpointer user_data)
@@ -911,7 +575,7 @@
  * Buddy messages ('bm') include instant messages, action messages, status messages, etc.
  *
  */
-static gboolean 
+gboolean 
 msim_send_bm(MsimSession *session, const gchar *who, const gchar *text, 
 		int type)
 {
@@ -945,613 +609,6 @@
 	return rc;
 }
 
-/* Indexes of this array + 1 map HTML font size to scale of normal font size. *
- * Based on _point_sizes from libpurple/gtkimhtml.c 
- *                                 1    2  3    4     5      6       7 */
-static gdouble _font_scale[] = { .85, .95, 1, 1.2, 1.44, 1.728, 2.0736 };
-
-#define MAX_FONT_SIZE                   7       /* Purple maximum font size */
-#define POINTS_PER_INCH                 72      /* How many pt's in an inch */
-
-/** Convert typographical font point size to HTML font size. 
- * Based on libpurple/gtkimhtml.c */
-static guint
-msim_point_to_purple_size(MsimSession *session, guint point)
-{
-	guint size, this_point, base;
-	gdouble scale;
-	
-	base = purple_account_get_int(session->account, "base_font_size", MSIM_BASE_FONT_POINT_SIZE);
-   
-	for (size = 0; 
-			size < sizeof(_font_scale) / sizeof(_font_scale[0]);
-			++size) {
-		scale = _font_scale[CLAMP(size, 1, MAX_FONT_SIZE) - 1];
-		this_point = (guint)msim_round(scale * base);
-
-		if (this_point >= point) {
-			purple_debug_info("msim", "msim_point_to_purple_size: %d pt -> size=%d\n",
-					point, size);
-			return size;
-		}
-	}
-
-	/* No HTML font size was this big; return largest possible. */
-	return this_point;
-}
-
-/** Convert HTML font size to point size. */
-static guint
-msim_purple_size_to_point(MsimSession *session, guint size)
-{
-	gdouble scale;
-	guint point;
-	guint base;
-
-	scale = _font_scale[CLAMP(size, 1, MAX_FONT_SIZE) - 1];
-
-	base = purple_account_get_int(session->account, "base_font_size", MSIM_BASE_FONT_POINT_SIZE);
-
-	point = (guint)msim_round(scale * base);
-
-	purple_debug_info("msim", "msim_purple_size_to_point: size=%d -> %d pt\n",
-					size, point);
-
-	return point;
-}
-
-/** Convert a msim markup font pixel height to the more usual point size, for incoming messages. */
-static guint 
-msim_height_to_point(MsimSession *session, guint height)
-{
-	guint dpi;
-
-	dpi = purple_account_get_int(session->account, "port", MSIM_DEFAULT_DPI);
-
-	return (guint)msim_round((POINTS_PER_INCH * 1. / dpi) * height);
-
-	/* See also: libpurple/protocols/bonjour/jabber.c
-	 * _font_size_ichat_to_purple */
-}
-
-/** Convert point size to msim pixel height font size specification, for outgoing messages. */
-static guint
-msim_point_to_height(MsimSession *session, guint point)
-{
-	guint dpi;
-
-	dpi = purple_account_get_int(session->account, "port", MSIM_DEFAULT_DPI);
-
-	return (guint)msim_round((dpi * 1. / POINTS_PER_INCH) * point);
-}
-
-/** Convert the msim markup <f> (font) tag into HTML. */
-static void 
-msim_markup_f_to_html(MsimSession *session, xmlnode *root, gchar **begin, gchar **end)
-{
-	const gchar *face, *height_str, *decor_str;
-	GString *gs_end, *gs_begin;
-	guint decor, height;
-
-	face = xmlnode_get_attrib(root, "f");
-	height_str = xmlnode_get_attrib(root, "h");
-	decor_str = xmlnode_get_attrib(root, "s");
-
-	if (height_str) {
-		height = atol(height_str);
-	} else {
-		height = 12;
-	}
-
-	if (decor_str) {
-		decor = atol(decor_str);
-	} else {
-		decor = 0;
-	}
-
-	gs_begin = g_string_new("");
-	/* TODO: get font size working */
-	if (height && !face) {
-		g_string_printf(gs_begin, "<font size='%d'>", 
-				msim_point_to_purple_size(session, msim_height_to_point(session, height)));
-	} else if (height && face) {
-		g_string_printf(gs_begin, "<font face='%s' size='%d'>", face,  
-				msim_point_to_purple_size(session, msim_height_to_point(session, height)));
-	} else {
-		g_string_printf(gs_begin, "<font>");
-	}
-
-	/* No support for font-size CSS? */
-	/* g_string_printf(gs_begin, "<span style='font-family: %s; font-size: %dpt'>", face, 
-			msim_height_to_point(height)); */
-
-	gs_end = g_string_new("</font>");
-
-	if (decor & MSIM_TEXT_BOLD) {
-		g_string_append(gs_begin, "<b>");
-		g_string_prepend(gs_end, "</b>");
-	}
-
-	if (decor & MSIM_TEXT_ITALIC) {
-		g_string_append(gs_begin, "<i>");
-		g_string_append(gs_end, "</i>");
-	}
-
-	if (decor & MSIM_TEXT_UNDERLINE) {
-		g_string_append(gs_begin, "<u>");
-		g_string_append(gs_end, "</u>");
-	}
-
-
-	*begin = gs_begin->str;
-	*end = gs_end->str;
-}
-
-/** Convert a msim markup color to a color suitable for libpurple.
-  *
-  * @param msim Either a color name, or an rgb(x,y,z) code.
-  *
-  * @return A new string, either a color name or #rrggbb code. Must g_free(). 
-  */
-static char *
-msim_color_to_purple(const char *msim)
-{
-	guint red, green, blue;
-
-	if (!msim) {
-		return g_strdup("black");
-	}
-
-	if (sscanf(msim, "rgb(%d,%d,%d)", &red, &green, &blue) != 3) {
-		/* Color name. */
-		return g_strdup(msim);
-	}
-	/* TODO: rgba (alpha). */
-
-	return g_strdup_printf("#%.2x%.2x%.2x", red, green, blue);
-}
-
-/** Convert the msim markup <a> (anchor) tag into HTML. */
-static void 
-msim_markup_a_to_html(MsimSession *session, xmlnode *root, gchar **begin, gchar **end)
-{
-	const gchar *href;
-
-	href = xmlnode_get_attrib(root, "h");
-	if (!href) {
-		href = "";
-	}
-
-	*begin = g_strdup_printf("<a href=\"%s\">%s", href, href);
-	*end = g_strdup("</a>");
-}
-
-/** Convert the msim markup <p> (paragraph) tag into HTML. */
-static void 
-msim_markup_p_to_html(MsimSession *session, xmlnode *root, gchar **begin, gchar **end)
-{
-	/* Just pass through unchanged. 
-	 *
-	 * Note: attributes currently aren't passed, if there are any. */
-	*begin = g_strdup("<p>");
-	*end = g_strdup("</p>");
-}
-
-/** Convert the msim markup <c> tag (text color) into HTML. TODO: Test */
-static void 
-msim_markup_c_to_html(MsimSession *session, xmlnode *root, gchar **begin, gchar **end)
-{
-	const gchar *color;
-	gchar *purple_color;
-
-	color = xmlnode_get_attrib(root, "v");
-	if (!color) {
-		purple_debug_info("msim", "msim_markup_c_to_html: <c> tag w/o v attr");
-		*begin = g_strdup("");
-		*end = g_strdup("");
-		/* TODO: log as unrecognized */
-		return;
-	}
-
-	purple_color = msim_color_to_purple(color);
-
-	*begin = g_strdup_printf("<font color='%s'>", purple_color); 
-
-	g_free(purple_color);
-
-	/* *begin = g_strdup_printf("<span style='color: %s'>", color); */
-	*end = g_strdup("</font>");
-}
-
-/** Convert the msim markup <b> tag (background color) into HTML. TODO: Test */
-static void 
-msim_markup_b_to_html(MsimSession *session, xmlnode *root, gchar **begin, gchar **end)
-{
-	const gchar *color;
-	gchar *purple_color;
-
-	color = xmlnode_get_attrib(root, "v");
-	if (!color) {
-		*begin = g_strdup("");
-		*end = g_strdup("");
-		purple_debug_info("msim", "msim_markup_b_to_html: <b> w/o v attr");
-		/* TODO: log as unrecognized. */
-		return;
-	}
-
-	purple_color = msim_color_to_purple(color);
-
-	/* TODO: find out how to set background color. */
-	*begin = g_strdup_printf("<span style='background-color: %s'>", 
-			purple_color);
-	g_free(purple_color);
-
-	*end = g_strdup("</p>");
-}
-
-/** Convert the msim markup <i> tag (emoticon image) into HTML. */
-static void 
-msim_markup_i_to_html(MsimSession *session, xmlnode *root, gchar **begin, gchar **end)
-{
-	const gchar *name;
-	guint i;
-	struct MSIM_EMOTICON *emote;
-
-	name = xmlnode_get_attrib(root, "n");
-	if (!name) {
-		purple_debug_info("msim", "msim_markup_i_to_html: <i> w/o n");
-		*begin = g_strdup("");
-		*end = g_strdup("");
-		/* TODO: log as unrecognized */
-		return;
-	}
-
-	/* Find and use canonical form of smiley symbol. */
-	for (i = 0; (emote = &msim_emoticons[i]) && emote->name != NULL; ++i) {
-		if (!strcmp(name, emote->name)) {
-			*begin = g_strdup(emote->symbol);
-			*end = g_strdup("");
-			return;
-		}
-	}
-
-	/* Couldn't find it, sorry. Try to degrade gracefully. */
-	*begin = g_strdup_printf("**%s**", name);
-	*end = g_strdup("");
-}
-
-/** Convert an individual msim markup tag to HTML. */
-static void 
-msim_markup_tag_to_html(MsimSession *session, xmlnode *root, gchar **begin, 
-		gchar **end)
-{
-	if (!strcmp(root->name, "f")) {
-		msim_markup_f_to_html(session, root, begin, end);
-	} else if (!strcmp(root->name, "a")) {
-		msim_markup_a_to_html(session, root, begin, end);
-	} else if (!strcmp(root->name, "p")) {
-		msim_markup_p_to_html(session, root, begin, end);
-	} else if (!strcmp(root->name, "c")) {
-		msim_markup_c_to_html(session, root, begin, end);
-	} else if (!strcmp(root->name, "b")) {
-		msim_markup_b_to_html(session, root, begin, end);
-	} else if (!strcmp(root->name, "i")) {
-		msim_markup_i_to_html(session, root, begin, end);
-	} else {
-		purple_debug_info("msim", "msim_markup_tag_to_html: "
-				"unknown tag name=%s, ignoring", 
-				(root && root->name) ? root->name : "(NULL)");
-		*begin = g_strdup("");
-		*end = g_strdup("");
-	}
-}
-
-/** Convert an individual HTML tag to msim markup. */
-static void 
-html_tag_to_msim_markup(MsimSession *session, xmlnode *root, gchar **begin, 
-		gchar **end)
-{
-	/* TODO: Coalesce nested tags into one <f> tag!
-	 * Currently, the 's' value will be overwritten when b/i/u is nested
-	 * within another one, and only the inner-most formatting will be 
-	 * applied to the text. */
-	if (!purple_utf8_strcasecmp(root->name, "root")) {
-		*begin = g_strdup("");
-		*end = g_strdup("");
-	} else if (!purple_utf8_strcasecmp(root->name, "b")) {
-		*begin = g_strdup_printf("<f s='%d'>", MSIM_TEXT_BOLD);
-		*end = g_strdup("</f>");
-	} else if (!purple_utf8_strcasecmp(root->name, "i")) {
-		*begin = g_strdup_printf("<f s='%d'>", MSIM_TEXT_ITALIC);
-		*end = g_strdup("</f>");
-	} else if (!purple_utf8_strcasecmp(root->name, "u")) {
-		*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;
-
-		href = xmlnode_get_attrib(root, "href");
-
-		if (!href) {
-			href = xmlnode_get_attrib(root, "HREF");
-		}
-
-		link_text = xmlnode_get_data(root);
-
-		if (href) {
-			if (!strcmp(link_text, href)) {
-				/* Purple gives us: <a href="URL">URL</a>
-				 * Translate to <a h='URL' />
-				 * Displayed as text of URL with link to URL
-				 */
-				*begin = g_strdup_printf("<a h='%s' />", href);
-			} else {
-				/* But if we get: <a href="URL">text</a>
-				 * Translate to: text: <a h='URL' />
-				 *
-				 * Because official client only supports self-closed <a>
-				 * tags; you can't change the link text.
-				 */
-				*begin = g_strdup_printf("%s: <a h='%s' />", link_text, href);
-			}
-		} else {
-			*begin = g_strdup("<a />");
-		}
-
-		/* Sorry, kid. MySpace doesn't support you within <a> tags. */
-		xmlnode_free(root->child);
-		root->child = NULL;
-
-		*end = g_strdup("");
-	} else if (!purple_utf8_strcasecmp(root->name, "font")) {
-		const gchar *size;
-		const gchar *face;
-
-		size = xmlnode_get_attrib(root, "size");
-		face = xmlnode_get_attrib(root, "face");
-
-		if (face && size) {
-			*begin = g_strdup_printf("<f f='%s' h='%d'>", face, 
-					msim_point_to_height(session,
-						msim_purple_size_to_point(session, atoi(size))));
-		} else if (face) {
-			*begin = g_strdup_printf("<f f='%s'>", face);
-		} else if (size) {
-			*begin = g_strdup_printf("<f h='%d'>", 
-					 msim_point_to_height(session,
-						 msim_purple_size_to_point(session, atoi(size))));
-		} else {
-			*begin = g_strdup("<f>");
-		}
-
-		*end = g_strdup("</f>");
-
-		/* TODO: color (bg uses <body>), emoticons */
-	} else {
-		*begin = g_strdup_printf("[%s]", root->name);
-		*end = g_strdup_printf("[/%s]", root->name);
-	}
-}
-
-/** Convert an xmlnode of msim markup or HTML to an HTML string or msim markup.
- *
- * @param f Function to convert tags.
- *
- * @return An HTML string. Caller frees.
- */
-static gchar *
-msim_convert_xmlnode(MsimSession *session, xmlnode *root, MSIM_XMLNODE_CONVERT f)
-{
-	xmlnode *node;
-	gchar *begin, *inner, *end;
-	GString *final;
-
-	if (!root || !root->name) {
-		return g_strdup("");
-	}
-
-	purple_debug_info("msim", "msim_convert_xmlnode: got root=%s\n",
-			root->name);
-
-	begin = inner = end = NULL;
-
-	final = g_string_new("");
-
-	f(session, root, &begin, &end);
-	
-	g_string_append(final, begin);
-
-	/* Loop over all child nodes. */
-	for (node = root->child; node != NULL; node = node->next) {
-		switch (node->type) {
-		case XMLNODE_TYPE_ATTRIB:
-			/* Attributes handled above. */
-			break;
-
-		case XMLNODE_TYPE_TAG:
-			/* A tag or tag with attributes. Recursively descend. */
-			inner = msim_convert_xmlnode(session, node, f);
-			g_return_val_if_fail(inner != NULL, NULL);
-
-			purple_debug_info("msim", " ** node name=%s\n", 
-					(node && node->name) ? node->name : "(NULL)");
-			break;
-	
-		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;
-
-			purple_debug_info("msim", " ** node data=%s\n", 
-					inner ? inner : "(NULL)");
-			break;
-			
-		default:
-			purple_debug_info("msim",
-					"msim_convert_xmlnode: strange node\n");
-			inner = g_strdup("");
-		}
-
-		if (inner) {
-			g_string_append(final, inner);
-		}
-	}
-
-	/* TODO: Note that msim counts each piece of text enclosed by <f> as
-	 * a paragraph and will display each on its own line. You actually have
-	 * to _nest_ <f> tags to intersperse different text in one paragraph!
-	 * Comment out this line below to see. */
-	g_string_append(final, end);
-
-	purple_debug_info("msim", "msim_markup_xmlnode_to_gtkhtml: RETURNING %s\n",
-			(final && final->str) ? final->str : "(NULL)");
-
-	return final->str;
-}
-
-/** Convert XML to something based on MSIM_XMLNODE_CONVERT. */
-static gchar *
-msim_convert_xml(MsimSession *session, const gchar *raw, MSIM_XMLNODE_CONVERT f)
-{
-	xmlnode *root;
-	gchar *str;
-	gchar *enclosed_raw;
-
-	g_return_val_if_fail(raw != NULL, NULL);
-
-	/* Enclose text in one root tag, to try to make it valid XML for parsing. */
-	enclosed_raw = g_strconcat("<root>", raw, "</root>", NULL);
-
-	root = xmlnode_from_str(enclosed_raw, -1);
-
-	if (!root) {
-		purple_debug_info("msim", "msim_markup_to_html: couldn't parse "
-				"%s as XML, returning raw: %s\n", enclosed_raw, raw);
-		/* TODO: msim_unrecognized */
-		g_free(enclosed_raw);
-		return g_strdup(raw);
-	}
-
-	g_free(enclosed_raw);
-
-	str = msim_convert_xmlnode(session, root, f);
-	g_return_val_if_fail(str != NULL, NULL);
-	purple_debug_info("msim", "msim_markup_to_html: returning %s\n", str);
-
-	xmlnode_free(root);
-
-	return str;
-}
-
-/** Convert plaintext smileys to <i> markup tags.
- *
- * @param before Original text with ASCII smileys. Will be freed.
- * @return A new string with <i> tags, if applicable. Must be g_free()'d.
- */
-static gchar *
-msim_convert_smileys_to_markup(gchar *before)
-{
-	gchar *old, *new, *replacement;
-	guint i;
-	struct MSIM_EMOTICON *emote;
-
-	old = before;
-	new = NULL;
-
-	for (i = 0; (emote = &msim_emoticons[i]) && emote->name != NULL; ++i) {
-		gchar *name, *symbol;
-
-		name = emote->name;
-		symbol = emote->symbol;
-
-		replacement = g_strdup_printf("<i n=\"%s\"/>", name);
-
-		purple_debug_info("msim", "msim_convert_smileys_to_markup: %s->%s\n",
-				symbol ? symbol : "(NULL)", 
-				replacement ? replacement : "(NULL)");
-		new = str_replace(old, symbol, replacement);
-		
-		g_free(replacement);
-		g_free(old);
-
-		old = new;
-	}
-
-	return new;
-}
-	
-
-/** High-level function to convert MySpaceIM markup to Purple (HTML) markup. 
- *
- * @return Purple markup string, must be g_free()'d. */
-static gchar *
-msim_markup_to_html(MsimSession *session, const gchar *raw)
-{
-	return msim_convert_xml(session, raw, 
-			(MSIM_XMLNODE_CONVERT)(msim_markup_tag_to_html));
-}
-
-/** High-level function to convert Purple (HTML) to MySpaceIM markup.
- *
- * @return HTML markup string, must be g_free()'d. */
-static gchar *
-html_to_msim_markup(MsimSession *session, const gchar *raw)
-{
-	gchar *markup;
-
-	markup = msim_convert_xml(session, raw,
-			(MSIM_XMLNODE_CONVERT)(html_tag_to_msim_markup));
-	
-	if (purple_account_get_bool(session->account, "emoticons", TRUE)) {
-		/* Frees markup and allocates a new one. */
-		markup = msim_convert_smileys_to_markup(markup);
-	}
-
-	return markup;
-}
-
-/** Get the MsimUser from a PurpleBuddy, creating it if needed. */
-static MsimUser *
-msim_get_user_from_buddy(PurpleBuddy *buddy)
-{
-	MsimUser *user;
-
-	if (!buddy) {
-		return NULL;
-	}
-
-	if (!buddy->proto_data) {
-		/* No MsimUser for this buddy; make one. */
-
-		/* TODO: where is this freed? */
-		user = g_new0(MsimUser, 1);
-		user->buddy = buddy;
-		buddy->proto_data = (gpointer)user;
-	} 
-
-	user = (MsimUser *)(buddy->proto_data);
-
-	return user;
-}
-
-/** Find and return an MsimUser * representing a user on the buddy list, or NULL. */
-static MsimUser *
-msim_find_user(MsimSession *session, const gchar *username)
-{
-	PurpleBuddy *buddy;
-	MsimUser *user;
-
-	buddy = purple_find_buddy(session->account, username);
-	if (!buddy) {
-		return NULL;
-	}
-
-	user = msim_get_user_from_buddy(buddy);
-
-	return user;
-}
-
 
 /** Record the client version in the buddy list, from an incoming message. */
 static gboolean
@@ -1655,7 +712,7 @@
  * @param msg An MsimMessage that was unrecognized, or NULL.
  * @param note Information on what was unrecognized, or NULL.
  */
-static void 
+void 
 msim_unrecognized(MsimSession *session, MsimMessage *msg, gchar *note)
 {
 	/* TODO: Some more context, outwardly equivalent to a backtrace, 
@@ -1680,60 +737,6 @@
 	}
 }
 
-/** Process an incoming zap. */
-static gboolean
-msim_incoming_zap(MsimSession *session, MsimMessage *msg)
-{
-	gchar *msg_text, *username;
-	gint zap;
-	const gchar *zap_past_tense[10];
-#ifdef MSIM_USE_ATTENTION_API
-	MsimAttentionType attn;
-#else
-	gchar *zap_text;
-#endif
-
-	zap_past_tense[0] = _("zapped");
-	zap_past_tense[1] = _("whacked");
-	zap_past_tense[2] = _("torched");
-	zap_past_tense[3] = _("smooched");
-	zap_past_tense[4] = _("hugged");
-	zap_past_tense[5] = _("bslapped");
-	zap_past_tense[6] = _("goosed");
-	zap_past_tense[7] = _("hi-fived");
-	zap_past_tense[8] = _("punk'd");
-	zap_past_tense[9] = _("raspberried");
-
-	msg_text = msim_msg_get_string(msg, "msg");
-	username = msim_msg_get_string(msg, "_username");
-
-	g_return_val_if_fail(msg_text != NULL, FALSE);
-	g_return_val_if_fail(username != NULL, FALSE);
-
-	g_return_val_if_fail(sscanf(msg_text, "!!!ZAP_SEND!!!=RTE_BTN_ZAPS_%d", &zap) == 1, FALSE);
-
-	zap = CLAMP(zap, 0, sizeof(zap_past_tense) / sizeof(zap_past_tense[0]));
-
-	/* TODO:ZAP: use msim_attention_types */
-#ifdef MSIM_USE_ATTENTION_API
-	attn.incoming_description = zap_past_tense[zap];
-	attn.outgoing_description = NULL;
-	attn.icon = NULL;		/* TODO: icon */
-
-	serv_got_attention(session->gc, username, &attn, TRUE);
-#else
-	zap_text = g_strdup_printf(_("*** You have been %s! ***"), zap_past_tense[zap]);
-	serv_got_im(session->gc, username, zap_text, 
-			PURPLE_MESSAGE_RECV | PURPLE_MESSAGE_SYSTEM, time(NULL));
-	g_free(zap_text);
-#endif
-
-	g_free(msg_text);
-	g_free(username);
-
-	return TRUE;
-}
-
 /**
  * Handle an incoming action message.
  *
@@ -1761,13 +764,13 @@
 	purple_debug_info("msim", "msim_incoming_action: action <%s> from <%d>\n", 
 			msg_text, username);
 
-	if (strcmp(msg_text, "%typing%") == 0) {
+	if (g_str_equal(msg_text, "%typing%")) {
 		/* TODO: find out if msim repeatedly sends typing messages, so we can 
 		 * give it a timeout. Right now, there does seem to be an inordinately 
 		 * amount of time between typing stopped-typing notifications. */
 		serv_got_typing(session->gc, username, 0, PURPLE_TYPING);
 		rc = TRUE;
-	} else if (strcmp(msg_text, "%stoptyping%") == 0) {
+	} else if (g_str_equal(msg_text, "%stoptyping%")) {
 		serv_got_typing_stopped(session->gc, username);
 		rc = TRUE;
 	} else if (strstr(msg_text, "!!!ZAP_SEND!!!=RTE_BTN_ZAPS_")) {
@@ -1902,95 +905,7 @@
 	return 0;
 }
 
-/** Format the "now playing" indicator, showing the artist and song.
- * @return Return a new string (must be g_free()'d), or NULL.
- */
-static gchar *
-msim_format_now_playing(gchar *band, gchar *song)
-{
-	if ((band && strlen(band)) || (song && strlen(song))) {
-		return g_strdup_printf("%s - %s",
-			(band && strlen(band)) ? band : "Unknown Artist",
-			(song && strlen(song)) ? song : "Unknown Song");
-	} else {
-		return NULL;
-	}
-}
-
-/** Append user information to a PurpleNotifyUserInfo, given an MsimUser. 
- * Used by msim_tooltip_text() and msim_get_info_cb() to show a user's profile.
- */
-static void
-msim_append_user_info(MsimSession *session, PurpleNotifyUserInfo *user_info, MsimUser *user, gboolean full)
-{
-	gchar *str;
-	guint uid;
-	guint cv;
-
-	/* Useful to identify the account the tooltip refers to. 
-	 *  Other prpls show this. */
-	if (user->username) {
-		purple_notify_user_info_add_pair(user_info, _("User"), user->username);
-	}
-
-	uid = purple_blist_node_get_int(&user->buddy->node, "UserID");
-
-	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));
-	}
-
-
-	/* a/s/l...the vitals */
-	if (user->age) {
-		purple_notify_user_info_add_pair(user_info, _("Age"),
-				g_strdup_printf("%d", user->age));
-	}
-
-	if (user->gender && strlen(user->gender)) {
-		purple_notify_user_info_add_pair(user_info, _("Gender"), user->gender);
-	}
-
-	if (user->location && strlen(user->location)) {
-		purple_notify_user_info_add_pair(user_info, _("Location"), user->location);
-	}
-
-	/* Other information */
-	if (user->headline && strlen(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)) {
-		purple_notify_user_info_add_pair(user_info, _("Song"), 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));
-	}
-
-	if (full) {
-		/* Client information */
-
-		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));
-		} else if (str) {
-			purple_notify_user_info_add_pair(user_info, _("Client Version"),
-					g_strdup(str));
-		} else if (cv) {
-			purple_notify_user_info_add_pair(user_info, _("Client Version"),
-					g_strdup_printf("Build %d", cv));
-		}
-	}
-}
+
 
 /** Callback for msim_get_info(), for when user info is received. */
 static void 
@@ -2377,42 +1292,27 @@
 	guint i, n;
 	const gchar *froms[5], *tos[5], *urls[5], *subjects[5];
 
-	/* Three parallel arrays for each new inbox message type. */
-	static const gchar *inbox_keys[] = 
-	{ 
-		"Mail", 
-		"BlogComment", 
-		"ProfileComment", 
-		"FriendRequest", 
-		"PictureComment" 
+	/* Information for each new inbox message type. */
+	static struct 
+	{
+		const gchar *key;
+		guint bit;
+		const gchar *url;
+		const gchar *text;
+	} message_types[] = {
+		{ "Mail", MSIM_INBOX_MAIL, "http://messaging.myspace.com/index.cfm?fuseaction=mail.inbox", NULL },
+		{ "BlogComment", MSIM_INBOX_BLOG_COMMENT, "http://blog.myspace.com/index.cfm?fuseaction=blog", NULL },
+		{ "ProfileComment", MSIM_INBOX_PROFILE_COMMENT, "http://home.myspace.com/index.cfm?fuseaction=user", NULL },
+		{ "FriendRequest", MSIM_INBOX_FRIEND_REQUEST, "http://messaging.myspace.com/index.cfm?fuseaction=mail.friendRequests", NULL },
+		{ "PictureComment", MSIM_INBOX_PICTURE_COMMENT, "http://home.myspace.com/index.cfm?fuseaction=user", NULL }
 	};
 
-	static const guint inbox_bits[] = 
-	{ 
-		MSIM_INBOX_MAIL, 
-		MSIM_INBOX_BLOG_COMMENT,
-		MSIM_INBOX_PROFILE_COMMENT,
-		MSIM_INBOX_FRIEND_REQUEST,
-		MSIM_INBOX_PICTURE_COMMENT
-	};
-
-	static const gchar *inbox_urls[] =
-	{
-		"http://messaging.myspace.com/index.cfm?fuseaction=mail.inbox",
-		"http://blog.myspace.com/index.cfm?fuseaction=blog",
-		"http://home.myspace.com/index.cfm?fuseaction=user",
-		"http://messaging.myspace.com/index.cfm?fuseaction=mail.friendRequests",
-		"http://home.myspace.com/index.cfm?fuseaction=user"
-	};
-
-	static const gchar *inbox_text[5];
-
 	/* Can't write _()'d strings in array initializers. Workaround. */
-	inbox_text[0] = _("New mail messages");
-	inbox_text[1] = _("New blog comments");
-	inbox_text[2] = _("New profile comments");
-	inbox_text[3] = _("New friend requests!");
-	inbox_text[4] = _("New picture comments");
+	message_types[0].text = _("New mail messages");
+	message_types[1].text = _("New blog comments");
+	message_types[2].text = _("New profile comments");
+	message_types[3].text = _("New friend requests!");
+	message_types[4].text = _("New picture comments");
 
 	g_return_if_fail(reply != NULL);
 
@@ -2427,12 +1327,12 @@
 
 	n = 0;
 
-	for (i = 0; i < sizeof(inbox_keys) / sizeof(inbox_keys[0]); ++i) {
+	for (i = 0; i < sizeof(message_types) / sizeof(message_types[0]); ++i) {
 		const gchar *key;
 		guint bit;
 		
-		key = inbox_keys[i];
-		bit = inbox_bits[i];
+		key = message_types[i].key;
+		bit = message_types[i].bit;
 
 		if (msim_msg_get(body, key)) {
 			/* Notify only on when _changes_ from no mail -> has mail
@@ -2441,13 +1341,13 @@
 				purple_debug_info("msim", "msim_check_inbox_cb: got %s, at %d\n",
 						key ? key : "(NULL)", n);
 
-				subjects[n] = inbox_text[i];
+				subjects[n] = message_types[i].text;
 				froms[n] = _("MySpace");
 				tos[n] = session->username;
 				/* TODO: append token, web challenge, so automatically logs in.
 				 * Would also need to free strings because they won't be static
 				 */
-				urls[n] = inbox_urls[i];
+				urls[n] = message_types[i].url;
 
 				++n;
 			} else {
@@ -2520,6 +1420,9 @@
 	 * some of the time, but can vary. This is our own user ID. */
 	session->userid = msim_msg_get_integer(msg, "userid");
 
+	/* Save uid to account so this account can be looked up by uid. */
+	purple_account_set_int(session->account, "uid", session->userid);
+
 	/* Not sure what profileid is used for. */
 	if (msim_msg_get_integer(msg, "profileid") != session->userid) {
 		msim_unrecognized(session, msg, 
@@ -2531,6 +1434,10 @@
 	 * address and not username. Will be freed in msim_session_destroy(). */
 	session->username = msim_msg_get_string(msg, "uniquenick");
 
+	/* If a local alias wasn't set, set it to user's username. */
+	if (!session->account->alias || !strlen(session->account->alias))
+		session->account->alias = session->username;
+
 	/* The session is now set up, ready to be connected. This emits the
 	 * signedOn signal, so clients can now do anything with msimprpl, and
 	 * we're ready for it (session key, userid, username all setup). */
@@ -2597,10 +1504,14 @@
 			(GSourceFunc)msim_check_alive, session);
 #endif
 
-	purple_timeout_add(MSIM_MAIL_INTERVAL_CHECK, 
-			(GSourceFunc)msim_check_inbox, session);
-
-	msim_check_inbox(session);
+	/* Check mail if they want to. */
+	if (purple_account_get_check_mail(session->account)) {
+		purple_timeout_add(MSIM_MAIL_INTERVAL_CHECK, 
+				(GSourceFunc)msim_check_inbox, session);
+		msim_check_inbox(session);
+	}
+
+	msim_get_contact_list(session, MSIM_CONTACT_LIST_INITIAL_FRIENDS);
 
 	return TRUE;
 }
@@ -2641,185 +1552,6 @@
 	}
 }
 
-/** Callback for when a buddy icon finished being downloaded. */
-static void
-msim_downloaded_buddy_icon(PurpleUtilFetchUrlData *url_data,
-		gpointer user_data,
-		const gchar *url_text,
-		gsize len,
-		const gchar *error_message)
-{
-	MsimUser *user;
-
-	user = (MsimUser *)user_data;
-
-	purple_debug_info("msim_downloaded_buddy_icon",
-			"Downloaded %d bytes\n", len);
-
-	if (!url_text) {
-		purple_debug_info("msim_downloaded_buddy_icon",
-				"failed to download icon for %s",
-				user->buddy->name);
-		return;
-	}
-
-	purple_buddy_icons_set_for_user(user->buddy->account,
-			user->buddy->name,
-			g_memdup((gchar *)url_text, len), len, 
-			/* Use URL itself as buddy icon "checksum" (TODO: ETag) */
-			user->image_url);		/* checksum */
-}
-
-/** Store a field of information about a buddy. */
-static void 
-msim_store_user_info_each(const gchar *key_str, gchar *value_str, MsimUser *user)
-{
-	if (!strcmp(key_str, "UserID") || !strcmp(key_str, "ContactID")) {
-		/* Save to buddy list, if it exists, for quick cached uid lookup with msim_uid2username_from_blist(). */
-		if (user->buddy)
-		{
-			purple_debug_info("msim", "associating uid %s with username %s\n", key_str, user->buddy->name);
-			purple_blist_node_set_int(&user->buddy->node, "UserID", atol(value_str));
-		}
-		/* Need to store in MsimUser, too? What if not on blist? */
-	} else if (!strcmp(key_str, "Age")) {
-		user->age = atol(value_str);
-	} else if (!strcmp(key_str, "Gender")) {
-		user->gender = g_strdup(value_str);
-	} else if (!strcmp(key_str, "Location")) {
-		user->location = g_strdup(value_str);
-	} else if (!strcmp(key_str, "TotalFriends")) {
-		user->total_friends = atol(value_str);
-	} else if (!strcmp(key_str, "DisplayName")) {
-		user->display_name = g_strdup(value_str);
-	} else if (!strcmp(key_str, "BandName")) {
-		user->band_name = g_strdup(value_str);
-	} else if (!strcmp(key_str, "SongName")) {
-		user->song_name = g_strdup(value_str);
-	} else if (!strcmp(key_str, "UserName") || !strcmp(key_str, "IMName") || !strcmp(key_str, "NickName")) {
-		/* Ignore because PurpleBuddy knows this already */
-		;
-	} else if (!strcmp(key_str, "ImageURL") || !strcmp(key_str, "AvatarURL")) {
-		const gchar *previous_url;
-
-		user->image_url = g_strdup(value_str);
-
-		/* Instead of showing 'no photo' picture, show nothing. */
-		if (!strcmp(user->image_url, "http://x.myspace.com/images/no_pic.gif"))
-		{
-			purple_buddy_icons_set_for_user(user->buddy->account,
-				user->buddy->name,
-				NULL, 0, NULL);
-			return;
-		}
-		
-		previous_url = purple_buddy_icons_get_checksum_for_user(user->buddy);
-
-		/* Only download if URL changed */
-		if (!previous_url || strcmp(previous_url, user->image_url)) {
-			purple_util_fetch_url(user->image_url, TRUE, NULL, TRUE, msim_downloaded_buddy_icon, (gpointer)user);
-		}
-	} else if (!strcmp(key_str, "LastImageUpdated")) {
-		/* TODO: use somewhere */
-		user->last_image_updated = atol(value_str);
-	} else if (!strcmp(key_str, "Headline")) {
-		user->headline = g_strdup(value_str);
-	} else {
-		/* TODO: other fields in MsimUser */
-		gchar *msg;
-
-		msg = g_strdup_printf("msim_store_user_info_each: unknown field %s=%s",
-				key_str, value_str);
-
-		msim_unrecognized(NULL, NULL, msg);
-
-		g_free(msg);
-	}
-}
-
-/** Save buddy information to the buddy list from a user info reply message.
- *
- * @param session
- * @param msg The user information reply, with any amount of information.
- * @param user The structure to save to, or NULL to save in PurpleBuddy->proto_data.
- *
- * Variable information is saved to the passed MsimUser structure. Permanent
- * information (UserID) is stored in the blist node of the buddy list (and
- * ends up in blist.xml, persisted to disk) if it exists.
- *
- * If the function has no buddy information, this function
- * is a no-op (and returns FALSE).
- *
- */
-static gboolean 
-msim_store_user_info(MsimSession *session, MsimMessage *msg, MsimUser *user)
-{
-	gchar *username;
-	MsimMessage *body, *body_node;
-
-	g_return_val_if_fail(MSIM_SESSION_VALID(session), FALSE);
-	g_return_val_if_fail(msg != NULL, FALSE);
-
-	body = msim_msg_get_dictionary(msg, "body");
-	if (!body) {
-		return FALSE;
-	}
-
-	username = msim_msg_get_string(body, "UserName");
-
-	if (!username) {
-		purple_debug_info("msim", 
-			"msim_process_reply: not caching body, no UserName\n");
-		msim_msg_free(body);
-		g_free(username);
-		return FALSE;
-	}
-	
-	/* Null user = find and store in PurpleBuddy's proto_data */
-	if (!user) {
-		user = msim_find_user(session, username);
-		if (!user) {
-			msim_msg_free(body);
-			g_free(username);
-			return FALSE;
-		}
-	}
-
-	/* TODO: make looping over MsimMessage's easier. */
-	for (body_node = body; 
-		body_node != NULL; 
-		body_node = msim_msg_get_next_element_node(body_node))
-	{
-		const gchar *key_str;
-		gchar *value_str;
-		MsimMessageElement *elem;
-
-		elem = (MsimMessageElement *)body_node->data;
-		key_str = elem->name;
-
-		value_str = msim_msg_get_string_from_element(elem);
-		msim_store_user_info_each(key_str, value_str, user);
-		g_free(value_str);
-	}
-
-	if (msim_msg_get_integer(msg, "dsn") == MG_OWN_IM_INFO_DSN &&
-		msim_msg_get_integer(msg, "lid") == MG_OWN_IM_INFO_LID) {
-		/* TODO: do something with our own IM info, if we need it for some
-		 * specific purpose. Otherwise it is available on the buddy list,
-		 * if the user has themselves as their own buddy. 
-		 *
-		 * However, much of the info is already available in MsimSession,
-		 * stored in msim_we_are_logged_on(). */
-	} else if (msim_msg_get_integer(msg, "dsn") == MG_OWN_MYSPACE_INFO_DSN &&
-			msim_msg_get_integer(msg, "lid") == MG_OWN_MYSPACE_INFO_LID) {
-		/* TODO: same as above, but for MySpace info. */
-	}
-
-	msim_msg_free(body);
-
-	return TRUE;
-}
-
 /** Process the initial server information from the server. */
 static gboolean
 msim_process_server_info(MsimSession *session, MsimMessage *msg)
@@ -3005,9 +1737,9 @@
 	 */
 	list = msim_msg_get_list(msg, "msg");
 
-	status_code = atoi(g_list_nth_data(list, MSIM_STATUS_ORDINAL_ONLINE));
+	status_code = msim_msg_get_integer_from_element(g_list_nth_data(list, MSIM_STATUS_ORDINAL_ONLINE));
 	purple_debug_info("msim", "msim_status: %s's status code = %d\n", username, status_code);
-	status_headline = g_list_nth_data(list, MSIM_STATUS_ORDINAL_HEADLINE);
+	status_headline = msim_msg_get_string_from_element(g_list_nth_data(list, MSIM_STATUS_ORDINAL_HEADLINE));
 
 	blist = purple_get_blist();
 
@@ -3031,7 +1763,8 @@
 		purple_debug_info("msim", "msim_status: found buddy %s\n", username);
 	}
 
-	user->headline = g_strdup(status_headline);
+	/* don't copy; let the MsimUser own the headline, memory-wise */
+	user->headline = status_headline;
   
 	/* Set user status */
 	switch (status_code) {
@@ -3053,9 +1786,9 @@
 			break;
 
 		default:
-				purple_debug_info("msim", "msim_status for %s, unknown status code %d, treating as available\n",
+			purple_debug_info("msim", "msim_status for %s, unknown status code %d, treating as available\n",
 						username, status_code);
-				purple_status_code = PURPLE_STATUS_AVAILABLE;
+			purple_status_code = PURPLE_STATUS_AVAILABLE;
 	}
 
 	purple_prpl_got_user_status(session->account, username, purple_primitive_get_id_from_type(purple_status_code), NULL);
@@ -3075,6 +1808,14 @@
 	}
 #endif
 
+	if (status_code != MSIM_STATUS_CODE_OFFLINE_OR_HIDDEN) {
+		/* Get information when they come online.
+		 * TODO: periodically refresh?
+		 */
+		purple_debug_info("msim_incoming_status", "%s came online, looking up\n", username);
+		msim_lookup_user(session, username, NULL, NULL);
+	}
+
 	g_free(username);
 	msim_msg_list_free(list);
 
@@ -3188,7 +1929,7 @@
 		fmt_string = msim_msg_pack_element_data(elem);
 
 		uid_str = g_strdup_printf("%d", uid);
-		new_str = str_replace(fmt_string, "<uid>", uid_str);
+		new_str = purple_strreplace(fmt_string, "<uid>", uid_str);
 		g_free(uid_str);
 		g_free(fmt_string);
 
@@ -3565,7 +2306,7 @@
  * 1) MSIM_USER_LOOKUP_CB - make it for PERSIST_REPLY, not just user lookup
  * 2) data - make it an MsimMessage?
  */
-static guint 
+guint 
 msim_new_reply_callback(MsimSession *session, MSIM_USER_LOOKUP_CB cb, 
 		gpointer data)
 {
@@ -3613,79 +2354,7 @@
 	gc->inpa = purple_input_add(source, PURPLE_INPUT_READ, msim_input_cb, gc);
 }
 
-/* Session methods */
-
-/**
- * Create a new MSIM session.
- *
- * @param acct The account to create the session from.
- *
- * @return Pointer to a new session. Free with msim_session_destroy.
- */
-MsimSession *
-msim_session_new(PurpleAccount *acct)
-{
-	MsimSession *session;
-
-	g_return_val_if_fail(acct != NULL, NULL);
-
-	session = g_new0(MsimSession, 1);
-
-	session->magic = MSIM_SESSION_STRUCT_MAGIC;
-	session->account = acct;
-	session->gc = purple_account_get_connection(acct);
-	session->sesskey = 0;
-	session->userid = 0;
-	session->username = NULL;
-	session->fd = -1;
-
-	/* TODO: Remove. */
-	session->user_lookup_cb = g_hash_table_new_full(g_direct_hash, 
-			g_direct_equal, NULL, NULL);  /* do NOT free function pointers! (values) */
-	session->user_lookup_cb_data = g_hash_table_new_full(g_direct_hash, 
-			g_direct_equal, NULL, NULL);/* TODO: we don't know what the values are,
-											 they could be integers inside gpointers
-											 or strings, so I don't freed them.
-											 Figure this out, once free cache. */
-
-	/* Created in msim_process_server_info() */
-	session->server_info = NULL;
-
-	session->rxoff = 0;
-	session->rxbuf = g_new0(gchar, MSIM_READ_BUF_SIZE);
-	session->next_rid = 1;
-	session->last_comm = time(NULL);
-	session->inbox_status = 0;
-	
-	return session;
-}
-
-/**
- * Free a session.
- *
- * @param session The session to destroy.
- */
-void 
-msim_session_destroy(MsimSession *session)
-{
-	g_return_if_fail(MSIM_SESSION_VALID(session));
-	
-	session->magic = -1;
-
-	g_free(session->rxbuf);
-	g_free(session->username);
-
-	/* TODO: Remove. */
-	g_hash_table_destroy(session->user_lookup_cb);
-	g_hash_table_destroy(session->user_lookup_cb_data);
-
-	if (session->server_info) {
-		msim_msg_free(session->server_info);
-	}
-	
-	g_free(session);
-}
-				 
+			 
 /** 
  * Close the connection.
  * 
@@ -3719,106 +2388,6 @@
 
 
 /**
- * Check if a string is a userid (all numeric).
- *
- * @param user The user id, email, or name.
- *
- * @return TRUE if is userid, FALSE if not.
- */
-static gboolean 
-msim_is_userid(const gchar *user)
-{
-	g_return_val_if_fail(user != NULL, FALSE);
-
-	return strspn(user, "0123456789") == strlen(user);
-}
-
-/**
- * Check if a string is an email address (contains an @).
- *
- * @param user The user id, email, or name.
- *
- * @return TRUE if is an email, FALSE if not.
- *
- * This function is not intended to be used as a generic
- * means of validating email addresses, but to distinguish
- * between a user represented by an email address from
- * other forms of identification.
- */ 
-static gboolean 
-msim_is_email(const gchar *user)
-{
-	g_return_val_if_fail(user != NULL, FALSE);
-
-	return strchr(user, '@') != NULL;
-}
-
-
-/**
- * Asynchronously lookup user information, calling callback when receive result.
- *
- * @param session
- * @param user The user id, email address, or username. Not freed.
- * @param cb Callback, called with user information when available.
- * @param data An arbitray data pointer passed to the callback.
- */
-/* TODO: change to not use callbacks */
-static void 
-msim_lookup_user(MsimSession *session, const gchar *user, 
-		MSIM_USER_LOOKUP_CB cb, gpointer data)
-{
-	MsimMessage *body;
-	gchar *field_name;
-	guint rid, cmd, dsn, lid;
-
-	g_return_if_fail(MSIM_SESSION_VALID(session));
-	g_return_if_fail(user != NULL);
-	g_return_if_fail(cb != NULL);
-
-	purple_debug_info("msim", "msim_lookup_userid: "
-			"asynchronously looking up <%s>\n", user);
-
-	msim_msg_dump("msim_lookup_user: data=%s\n", (MsimMessage *)data);
-
-	/* Setup callback. Response will be associated with request using 'rid'. */
-	rid = msim_new_reply_callback(session, cb, data);
-
-	/* Send request */
-
-	cmd = MSIM_CMD_GET;
-
-	if (msim_is_userid(user)) {
-		field_name = "UserID";
-		dsn = MG_MYSPACE_INFO_BY_ID_DSN; 
-		lid = MG_MYSPACE_INFO_BY_ID_LID; 
-	} else if (msim_is_email(user)) {
-		field_name = "Email";
-		dsn = MG_MYSPACE_INFO_BY_STRING_DSN;
-		lid = MG_MYSPACE_INFO_BY_STRING_LID;
-	} else {
-		field_name = "UserName";
-		dsn = MG_MYSPACE_INFO_BY_STRING_DSN;
-		lid = MG_MYSPACE_INFO_BY_STRING_LID;
-	}
-
-	body = msim_msg_new(
-			field_name, MSIM_TYPE_STRING, g_strdup(user),
-			NULL);
-
-	g_return_if_fail(msim_send(session,
-			"persist", MSIM_TYPE_INTEGER, 1,
-			"sesskey", MSIM_TYPE_INTEGER, session->sesskey,
-			"cmd", MSIM_TYPE_INTEGER, 1,
-			"dsn", MSIM_TYPE_INTEGER, dsn,
-			"uid", MSIM_TYPE_INTEGER, session->userid,
-			"lid", MSIM_TYPE_INTEGER, lid,
-			"rid", MSIM_TYPE_INTEGER, rid,
-			"body", MSIM_TYPE_DICTIONARY, body,
-			NULL));
-} 
-
-
-/**
  * Obtain the status text for a buddy.
  *
  * @param buddy The buddy to obtain status text for.
@@ -4033,7 +2602,7 @@
 
 		elem = (MsimMessageElement *)body_node->data;
 
-		if (!strcmp(elem->name, "ContactID"))
+		if (g_str_equal(elem->name, "ContactID"))
 		{
 			/* Will look for first contact in body_node */
 			if (msim_add_contact_from_server(session, body_node)) {
@@ -4042,15 +2611,43 @@
 		}
 	}
 
-	msg = g_strdup_printf(_("%d buddies were added or updated"), buddy_count);
-
-	purple_notify_info(session->account, _("Add contacts from server"), msg, NULL);
-
-	g_free(msg);
+	switch (GPOINTER_TO_UINT(user_data)) {
+		case MSIM_CONTACT_LIST_IMPORT_ALL_FRIENDS:
+			msg = g_strdup_printf(_("%d buddies were added or updated"), buddy_count);
+			purple_notify_info(session->account, _("Add contacts from server"), msg, NULL);
+			g_free(msg);
+			break;
+
+		case MSIM_CONTACT_LIST_IMPORT_TOP_FRIENDS:
+			/* TODO */
+			break;
+		
+		case MSIM_CONTACT_LIST_INITIAL_FRIENDS:
+			/* Nothing */
+			break;
+	}
 
 	msim_msg_free(body);
 }
 
+/* Get contact list, calling msim_got_contact_list() with what_to_do_after as user_data gpointer. */
+static gboolean
+msim_get_contact_list(MsimSession *session, int what_to_do_after)
+{
+	return msim_send(session, 
+			"persist", MSIM_TYPE_INTEGER, 1,
+			"sesskey", MSIM_TYPE_INTEGER, session->sesskey,
+			"cmd", MSIM_TYPE_INTEGER, MSIM_CMD_GET,
+			"dsn", MSIM_TYPE_INTEGER, MG_LIST_ALL_CONTACTS_DSN,
+			"lid", MSIM_TYPE_INTEGER, MG_LIST_ALL_CONTACTS_LID,
+			"uid", MSIM_TYPE_INTEGER, session->userid,
+			"rid", MSIM_TYPE_INTEGER, 
+				msim_new_reply_callback(session, msim_got_contact_list, GUINT_TO_POINTER(what_to_do_after)), 
+			"body", MSIM_TYPE_STRING, g_strdup(""),
+			NULL);
+}
+
+
 /** Called when friends have been imported to buddy list on server. */
 static void 
 msim_import_friends_cb(MsimSession *session, MsimMessage *reply, gpointer user_data)
@@ -4065,7 +2662,7 @@
 	completed = msim_msg_get_string(body, "Completed");
 	g_return_if_fail(body != NULL);
 	msim_msg_free(body);
-	if (strcmp(completed, "True"))
+	if (!g_str_equal(completed, "True"))
 	{
 		purple_debug_info("msim_import_friends_cb",
 				"failed to import friends: %s", completed);
@@ -4079,17 +2676,7 @@
 	purple_debug_info("msim_import_friends_cb",
 			"added friends to server-side buddy list, requesting new contacts from server");
 
-	g_return_if_fail(msim_send(session, 
-			"persist", MSIM_TYPE_INTEGER, 1,
-			"sesskey", MSIM_TYPE_INTEGER, session->sesskey,
-			"cmd", MSIM_TYPE_INTEGER, MSIM_CMD_GET,
-			"dsn", MSIM_TYPE_INTEGER, MG_LIST_ALL_CONTACTS_DSN,
-			"lid", MSIM_TYPE_INTEGER, MG_LIST_ALL_CONTACTS_LID,
-			"uid", MSIM_TYPE_INTEGER, session->userid,
-			"rid", MSIM_TYPE_INTEGER, 
-				msim_new_reply_callback(session, msim_got_contact_list, NULL),
-			"body", MSIM_TYPE_STRING, g_strdup(""),
-			NULL));
+	msim_get_contact_list(session, MSIM_CONTACT_LIST_IMPORT_ALL_FRIENDS);
 
 	/* TODO: show, X friends have been added */
 }
@@ -4318,7 +2905,7 @@
 	packed_expected = "\\bx\\WFhY\\k1\\v1\\k1\\42\\k1"
 		"\\v43\\k1\\v52/1xxx/2yyy\\k1\\v7\\final\\";
 
-	if (0 != strcmp(packed, packed_expected)) {
+	if (!g_str_equal(packed, packed_expected)) {
 		purple_debug_info("msim", "!!!(%d), msim_msg_pack not what expected: %s != %s\n",
 				++failures, packed, packed_expected);
 	}
@@ -4328,7 +2915,7 @@
 	packed_cloned = msim_msg_pack(msg_cloned);
 
 	purple_debug_info("msim", "msg cloned=%s\n", packed_cloned);
-	if (0 != strcmp(packed, packed_cloned)) {
+	if (!g_str_equal(packed, packed_cloned)) {
 		purple_debug_info("msim", "!!!(%d), msim_msg_pack on cloned message not equal to original: %s != %s\n",
 				++failures, packed_cloned, packed);
 	}
@@ -4383,7 +2970,7 @@
 	escaped = msim_escape(raw);
 	purple_debug_info("msim", "msim_test_escaping: raw=%s, escaped=%s\n", raw, escaped);
 	expected = "hello/1world/2hello/1world";
-	if (0 != strcmp(escaped, expected)) {
+	if (!g_str_equal(escaped, expected)) {
 		purple_debug_info("msim", "!!!(%d), msim_escape failed: %s != %s\n",
 				++failures, escaped, expected);
 	}
@@ -4392,7 +2979,7 @@
 	unescaped = msim_unescape(escaped);
 	g_free(escaped);
 	purple_debug_info("msim", "msim_test_escaping: unescaped=%s\n", unescaped);
-	if (0 != strcmp(raw, unescaped)) {
+	if (!g_str_equal(raw, unescaped)) {
 		purple_debug_info("msim", "!!!(%d), msim_unescape failed: %s != %s\n",
 				++failures, raw, unescaped);
 	}
@@ -4401,16 +2988,156 @@
 }
 #endif
 
+static gboolean
+msim_uri_handler(const gchar *proto, const gchar *cmd, GHashTable *params)
+{
+	PurpleAccount *account;
+	MsimSession *session;
+	GList *l;
+	gchar *uid_str, *cid_str;
+	guint uid, cid;
+
+	if (g_ascii_strcasecmp(proto, "myim"))
+		return FALSE;
+
+	/* Parameters are case-insensitive. */
+	uid_str = g_hash_table_lookup(params, "uid");
+	cid_str = g_hash_table_lookup(params, "cid");
+
+	uid = uid_str ? atol(uid_str) : 0;
+	cid = cid_str ? atol(cid_str) : 0;
+
+	/* Need a contact. */
+	g_return_val_if_fail(cid != 0, FALSE);
+
+	/* TODO: if auto=true, "Add all the people on this page to my IM List!", on
+	 * http://collect.myspace.com/index.cfm?fuseaction=im.friendslist. Don't need a cid. */
+
+	/* Convert numeric contact ID back to a string. Needed for looking up. Don't just
+	 * directly use cid directly from parameters, because it might not be numeric. 
+	 * It is trivial to change this to allow cID to be a username, but that's not how
+	 * the official MySpaceIM client works, so don't provide that functionality. */
+	cid_str = g_strdup_printf("%d", cid);
+
+
+	/* Find our account with specified user id, or use first connected account if uid=0. */
+	account = NULL;
+	l = purple_accounts_get_all();
+	while (l) {
+		if (purple_account_is_connected(l->data) &&
+			(uid == 0 || purple_account_get_int(l->data, "uid", 0) == uid)) {
+			account = l->data;
+			break;
+		}
+		l = l->next;
+	}
+
+	if (!account) {
+		purple_notify_error(NULL, _("myim URL handler"), 
+				_("No suitable MySpaceIM account could be found to open this myim URL."),
+				_("Enable the proper MySpaceIM account and try again."));
+		g_free(cid_str);
+		return FALSE;
+	}
+
+	session = (MsimSession *)account->gc->proto_data;
+	g_return_val_if_fail(session != NULL, FALSE);
+
+	/* Lookup userid to username. TODO: push this down, to IM sending/contact 
+	 * adding functions. */
+
+	/* myim:sendIM?uID=USERID&cID=CONTACTID */
+	if (!g_ascii_strcasecmp(cmd, "sendIM")) {
+		msim_lookup_user(session, cid_str, (MSIM_USER_LOOKUP_CB)msim_uri_handler_sendIM_cb, NULL);
+		g_free(cid_str);
+		return TRUE;
+
+	/* myim:addContact?uID=USERID&cID=CONTACTID */
+	} else if (!g_ascii_strcasecmp(cmd, "addContact")) {
+		msim_lookup_user(session, cid_str, (MSIM_USER_LOOKUP_CB)msim_uri_handler_addContact_cb, NULL);
+		g_free(cid_str);
+		return TRUE;
+	}
+
+	return FALSE;
+}
+
+/* TODO: move uid->username resolving to IM sending and buddy adding functions,
+ * so that user can manually add or IM by userid and username automatically
+ * looked up if possible? */
+ 
+/** Handle a myim:sendIM URI command, after username has been looked up. */
+static void
+msim_uri_handler_sendIM_cb(MsimSession *session, MsimMessage *userinfo, gpointer data)
+{
+	PurpleConversation *conv;
+	MsimMessage *body;
+	gchar *username;
+
+	body = msim_msg_get_dictionary(userinfo, "body");
+	username = msim_msg_get_string(body, "UserName");
+	msim_msg_free(body);
+
+	if (!username) {
+		guint uid;
+
+		uid = msim_msg_get_integer(userinfo, "UserID");
+		g_return_if_fail(uid != 0);
+
+		username = g_strdup_printf("%d", uid);
+	}
+
+
+	conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, username, session->account);
+	if (!conv)  {
+		purple_debug_info("msim_uri_handler", "creating new conversation for %s\n", username);
+		conv = purple_conversation_new(PURPLE_CONV_TYPE_IM, session->account, username);
+	}
+
+	/* Just open the window so the user can send an IM. */
+	purple_conversation_present(conv);
+
+	g_free(username);
+}
+
+/** Handle a myim:addContact command, after username has been looked up. */
+static void
+msim_uri_handler_addContact_cb(MsimSession *session, MsimMessage *userinfo, gpointer data)
+{
+	MsimMessage *body;
+	gchar *username;
+
+	body = msim_msg_get_dictionary(userinfo, "body");
+	username = msim_msg_get_string(body, "UserName");
+	msim_msg_free(body);
+
+	if (!username) {
+		guint uid;
+
+		uid = msim_msg_get_integer(userinfo, "UserID");
+		g_return_if_fail(uid != 0);
+
+		username = g_strdup_printf("%d", uid);
+	}
+
+
+	purple_blist_request_add_buddy(session->account, username, _("Buddies"), NULL);
+
+	g_free(username);
+}
+
 /** Initialize plugin. */
 void 
 init_plugin(PurplePlugin *plugin) 
 {
-	PurpleAccountOption *option;
 #ifdef MSIM_SELF_TEST
 	msim_test_all();
 	exit(0);
 #endif /* MSIM_SELF_TEST */
 
+	PurpleAccountOption *option;
+	static gboolean initialized = FALSE;
+
 
 	/* TODO: default to automatically try different ports. Make the user be
 	 * able to set the first port to try (like LastConnectedPort in Windows client).  */
@@ -4441,21 +3168,14 @@
 	prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
 #endif
 
-	/* TODO: /zap command. Problem with this is that there are different kinds of zaps,
-	 * and the selection is best made available in a drop-down menu, instead of forcing
-	 * the user to type the kind of zap and memorizing available zaps (or putting it in the
-	 * help menu). A new "attention" API, for zap/buzz/nudge (different protocols) will
-	 * solve this. */
-#if 0
-	purple_cmd_register("zap",                                        /* cmd */
-			"w",                                              /* args - accept a single word */
-			PURPLE_CMD_P_PRPL,                                /* priority */
-			PURPLE_CMD_FLAG_IM | PURPLE_CMD_FLAG_PRPL_ONLY,   /* flags */
-			"prpl-myspace",                                   /* prpl_id */
-			msim_cmd_zap,                                     /* func */
-			_("zap: zap a user to get their attention"),      /* helpstr */
-			NULL);                                            /* data */
-#endif
+	/* Code below only runs once. Based on oscar.c's oscar_init(). */
+	if (initialized) 
+		return;
+
+	initialized = TRUE;
+
+	purple_signal_connect(purple_get_core(), "uri-handler", &initialized,
+			PURPLE_CALLBACK(msim_uri_handler), NULL);
 }
 
 PURPLE_INIT_PLUGIN(myspace, init_plugin, info);
--- a/libpurple/protocols/myspace/myspace.h	Sat Aug 25 08:42:33 2007 +0000
+++ b/libpurple/protocols/myspace/myspace.h	Tue Aug 28 09:20:27 2007 +0000
@@ -46,9 +46,15 @@
 #include "util.h"       /* for base64 */
 #include "debug.h"      /* for purple_debug_info */
 #include "xmlnode.h"
+#include "core.h"
 
 /* MySpaceIM includes */
+#include "persist.h"
 #include "message.h"
+#include "session.h"
+#include "zap.h"
+#include "markup.h"
+#include "user.h"
 
 /* Conditional compilation options */
 /* Send third-party client version? (Recognized by us and Miranda's plugin) */
@@ -69,7 +75,7 @@
 
 /* Use the attention API for zaps? */
 /* Can't have until >=2.2.0, since is a new API. */
-/*#define MSIM_USE_ATTENTION_API 	 */
+#define MSIM_USE_ATTENTION_API
 
 /* Constants */
 
@@ -157,78 +163,18 @@
 #define MSIM_STATUS_CODE_IDLE                 2
 #define MSIM_STATUS_CODE_AWAY                 5
 
-/* Text formatting bits for <f s=#> */
-#define MSIM_TEXT_BOLD                  1
-#define MSIM_TEXT_ITALIC                2   
-#define MSIM_TEXT_UNDERLINE             4
 
-/* Default baseline size of purple's fonts, in points. What is size 3 in points. 
- * _font_scale specifies scaling factor relative to this point size. Note this 
- * is only the default; it is configurable in account options. */
-#define MSIM_BASE_FONT_POINT_SIZE       8
-
-/* Default display's DPI. 96 is common but it can differ. Also configurable
- * in account options. */
-#define MSIM_DEFAULT_DPI                96
-
-
-/* Random number in every MsimSession, to ensure it is valid. */
-#define MSIM_SESSION_STRUCT_MAGIC       0xe4a6752b
-
-/* Inbox status bitfield values for MsimSession.inbox_status */
+/* Inbox status bitfield values for MsimSession.inbox_status. */
 #define MSIM_INBOX_MAIL                 (1 << 0)
 #define MSIM_INBOX_BLOG_COMMENT         (1 << 1)
 #define MSIM_INBOX_PROFILE_COMMENT      (1 << 2)
 #define MSIM_INBOX_FRIEND_REQUEST       (1 << 3)
 #define MSIM_INBOX_PICTURE_COMMENT      (1 << 4)
 
-/* Everything needed to keep track of a session (proto_data field in PurpleConnection) */
-typedef struct _MsimSession
-{
-	guint magic;                        /**< MSIM_SESSION_STRUCT_MAGIC */
-	PurpleAccount *account;
-	PurpleConnection *gc;
-	guint sesskey;                      /**< Session key from server */
-	guint userid;                       /**< This user's numeric user ID */
-	gchar *username;                    /**< This user's unique username */
-	gint fd;                            /**< File descriptor to/from server */
-
-	/* TODO: Remove. */
-	GHashTable *user_lookup_cb;         /**< Username -> userid lookup callback */
-	GHashTable *user_lookup_cb_data;    /**< Username -> userid lookup callback data */
-
-	MsimMessage *server_info;           /**< Parameters from server */
-
-	gchar *rxbuf;                       /**< Receive buffer */
-	guint rxoff;                        /**< Receive buffer offset */
-	guint next_rid;                     /**< Next request/response ID */
-	time_t last_comm;                   /**< Time received last communication */
-	guint inbox_status;                 /**< Bit field of inbox notifications */
-} MsimSession;
-
-/* Check if an MsimSession is valid */
-#define MSIM_SESSION_VALID(s) (session != NULL && session->magic == MSIM_SESSION_STRUCT_MAGIC)
-
-/* Hold ephemeral information about buddies, for proto_data of PurpleBuddy. */
-/* GHashTable? */
-typedef struct _MsimUser
-{
-	PurpleBuddy *buddy;
-	guint client_cv;
-	gchar *client_info;
-	guint age;
-	gchar *gender;
-	gchar *location;
-	guint total_friends;
-	gchar *headline;
-	gchar *display_name;
-	/* Note: uid is in &buddy->node (set_blist_node_int), since it never changes */
-	gchar *username;
-	gchar *band_name, *song_name;
-	gchar *image_url;
-	guint last_image_updated;
-} MsimUser;
-
+/* Codes for msim_got_contact_list(), to tell what to do afterwards. */
+#define MSIM_CONTACT_LIST_INITIAL_FRIENDS	0
+#define MSIM_CONTACT_LIST_IMPORT_ALL_FRIENDS	1
+#define MSIM_CONTACT_LIST_IMPORT_TOP_FRIENDS	2
 
 #ifdef MSIM_USE_ATTENTION_API
 #define MsimAttentionType PurpleAttentionType
@@ -247,33 +193,17 @@
 };
 #endif
 
-gchar *str_replace(const gchar *str, const gchar *old, const gchar *new);
-
-/* Callback function pointer type for when a user's information is received, 
- * initiated from a user lookup. */
-typedef void (*MSIM_USER_LOOKUP_CB)(MsimSession *session, MsimMessage *userinfo, gpointer data);
-
 /* Functions */
 gboolean msim_load(PurplePlugin *plugin);
 GList *msim_status_types(PurpleAccount *acct);
 
-GList *msim_attention_types(PurpleAccount *acct);
-gboolean msim_send_attention(PurpleConnection *gc, gchar *username, guint code);
-
-GList *msim_blist_node_menu(PurpleBlistNode *node);
-
 const gchar *msim_list_icon(PurpleAccount *acct, PurpleBuddy *buddy);
-
 gboolean msim_send_raw(MsimSession *session, const gchar *msg);
 
 void msim_login(PurpleAccount *acct);
-
-int msim_send_im(PurpleConnection *gc, const gchar *who, const gchar *message, 
-PurpleMessageFlags flags);
+int msim_send_im(PurpleConnection *gc, const gchar *who, const gchar *message, PurpleMessageFlags flags);
+unsigned int msim_send_typing(PurpleConnection *gc, const gchar *name, PurpleTypingState state);
 
-typedef void (*MSIM_XMLNODE_CONVERT)(MsimSession *, xmlnode *, gchar **, gchar **);
-
-unsigned int msim_send_typing(PurpleConnection *gc, const gchar *name, PurpleTypingState state);
 void msim_get_info(PurpleConnection *gc, const gchar *name);
 
 void msim_set_status(PurpleAccount *account, PurpleStatus *status);
@@ -284,9 +214,6 @@
 
 gboolean msim_offline_message(const PurpleBuddy *buddy);
 
-MsimSession *msim_session_new(PurpleAccount *acct);
-void msim_session_destroy(MsimSession *session);
-
 void msim_close(PurpleConnection *gc);
 
 char *msim_status_text(PurpleBuddy *buddy);
@@ -299,6 +226,13 @@
 int msim_test_escaping(void);
 #endif
 
+gboolean msim_send_bm(MsimSession *session, const gchar *who, const gchar *text, int type);
+
+
+void msim_unrecognized(MsimSession *session, MsimMessage *msg, gchar *note);
+guint msim_new_reply_callback(MsimSession *session, MSIM_USER_LOOKUP_CB cb, gpointer data);
+
+
 void init_plugin(PurplePlugin *plugin);
 
 #endif /* !_MYSPACE_MYSPACE_H */
--- a/libpurple/protocols/myspace/persist.h	Sat Aug 25 08:42:33 2007 +0000
+++ b/libpurple/protocols/myspace/persist.h	Tue Aug 28 09:20:27 2007 +0000
@@ -42,8 +42,8 @@
 
 /** Define a set of _DSN and _LID constants for a persistance request. */
 #define MSIM_PERSIST_DSN_LID(name,dsn,lid)             \
-    const int name##_DSN = dsn;                        \
-    const int name##_LID = lid;                        
+    static const int name##_DSN = dsn;                 \
+    static const int name##_LID = lid;                        
 
 /* Can't do this, errors:
  *     persist.h:51:3: error: '#' is not followed by a macro parameter
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/myspace/session.c	Tue Aug 28 09:20:27 2007 +0000
@@ -0,0 +1,95 @@
+/* MySpaceIM Protocol Plugin, session
+ *
+ * Copyright (C) 2007, Jeff Connelly <jeff2@soc.pidgin.im>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+
+#include "myspace.h"
+
+/* Session methods */
+
+/**
+ * Create a new MSIM session.
+ *
+ * @param acct The account to create the session from.
+ *
+ * @return Pointer to a new session. Free with msim_session_destroy.
+ */
+MsimSession *
+msim_session_new(PurpleAccount *acct)
+{
+	MsimSession *session;
+
+	g_return_val_if_fail(acct != NULL, NULL);
+
+	session = g_new0(MsimSession, 1);
+
+	session->magic = MSIM_SESSION_STRUCT_MAGIC;
+	session->account = acct;
+	session->gc = purple_account_get_connection(acct);
+	session->sesskey = 0;
+	session->userid = 0;
+	session->username = NULL;
+	session->fd = -1;
+
+	/* TODO: Remove. */
+	session->user_lookup_cb = g_hash_table_new_full(g_direct_hash, 
+			g_direct_equal, NULL, NULL);  /* do NOT free function pointers! (values) */
+	session->user_lookup_cb_data = g_hash_table_new_full(g_direct_hash, 
+			g_direct_equal, NULL, NULL);/* TODO: we don't know what the values are,
+											 they could be integers inside gpointers
+											 or strings, so I don't freed them.
+											 Figure this out, once free cache. */
+
+	/* Created in msim_process_server_info() */
+	session->server_info = NULL;
+
+	session->rxoff = 0;
+	session->rxbuf = g_new0(gchar, MSIM_READ_BUF_SIZE);
+	session->next_rid = 1;
+	session->last_comm = time(NULL);
+	session->inbox_status = 0;
+	
+	return session;
+}
+
+/**
+ * Free a session.
+ *
+ * @param session The session to destroy.
+ */
+void 
+msim_session_destroy(MsimSession *session)
+{
+	g_return_if_fail(MSIM_SESSION_VALID(session));
+	
+	session->magic = -1;
+
+	g_free(session->rxbuf);
+	g_free(session->username);
+
+	/* TODO: Remove. */
+	g_hash_table_destroy(session->user_lookup_cb);
+	g_hash_table_destroy(session->user_lookup_cb_data);
+
+	if (session->server_info) {
+		msim_msg_free(session->server_info);
+	}
+	
+	g_free(session);
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/myspace/session.h	Tue Aug 28 09:20:27 2007 +0000
@@ -0,0 +1,57 @@
+/* MySpaceIM Protocol Plugin, session
+ *
+ * Copyright (C) 2007, Jeff Connelly <jeff2@soc.pidgin.im>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#ifndef _MYSPACE_SESSION_H
+#define _MYSPACE_SESSION_H
+
+/* Random number in every MsimSession, to ensure it is valid. */
+#define MSIM_SESSION_STRUCT_MAGIC       0xe4a6752b
+
+/* Everything needed to keep track of a session (proto_data field in PurpleConnection) */
+typedef struct _MsimSession
+{
+	guint magic;                        /**< MSIM_SESSION_STRUCT_MAGIC */
+	PurpleAccount *account;
+	PurpleConnection *gc;
+	guint sesskey;                      /**< Session key from server */
+	guint userid;                       /**< This user's numeric user ID */
+	gchar *username;                    /**< This user's unique username */
+	gint fd;                            /**< File descriptor to/from server */
+
+	/* TODO: Remove. */
+	GHashTable *user_lookup_cb;         /**< Username -> userid lookup callback */
+	GHashTable *user_lookup_cb_data;    /**< Username -> userid lookup callback data */
+
+	MsimMessage *server_info;           /**< Parameters from server */
+
+	gchar *rxbuf;                       /**< Receive buffer */
+	guint rxoff;                        /**< Receive buffer offset */
+	guint next_rid;                     /**< Next request/response ID */
+	time_t last_comm;                   /**< Time received last communication */
+	guint inbox_status;                 /**< Bit field of inbox notifications */
+} MsimSession;
+
+/* Check if an MsimSession is valid */
+#define MSIM_SESSION_VALID(s) (session != NULL && session->magic == MSIM_SESSION_STRUCT_MAGIC)
+
+
+MsimSession *msim_session_new(PurpleAccount *acct);
+void msim_session_destroy(MsimSession *session);
+
+#endif /* !_MYSPACE_SESSION_H */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/myspace/user.c	Tue Aug 28 09:20:27 2007 +0000
@@ -0,0 +1,437 @@
+/* MySpaceIM Protocol Plugin, header file
+ *
+ * Copyright (C) 2007, Jeff Connelly <jeff2@soc.pidgin.im>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#include "myspace.h"
+
+static void msim_store_user_info_each(const gchar *key_str, gchar *value_str, MsimUser *user);
+static gchar *msim_format_now_playing(gchar *band, gchar *song);
+static void msim_downloaded_buddy_icon(PurpleUtilFetchUrlData *url_data, gpointer user_data, const gchar *url_text,
+		gsize len, const gchar *error_message);
+
+/** Format the "now playing" indicator, showing the artist and song.
+ * @return Return a new string (must be g_free()'d), or NULL.
+ */
+static gchar *
+msim_format_now_playing(gchar *band, gchar *song)
+{
+	if ((band && strlen(band)) || (song && strlen(song))) {
+		return g_strdup_printf("%s - %s",
+			(band && strlen(band)) ? band : "Unknown Artist",
+			(song && strlen(song)) ? song : "Unknown Song");
+	} else {
+		return NULL;
+	}
+}
+/** Get the MsimUser from a PurpleBuddy, creating it if needed. */
+MsimUser *
+msim_get_user_from_buddy(PurpleBuddy *buddy)
+{
+	MsimUser *user;
+
+	if (!buddy) {
+		return NULL;
+	}
+
+	if (!buddy->proto_data) {
+		/* No MsimUser for this buddy; make one. */
+
+		/* TODO: where is this freed? */
+		user = g_new0(MsimUser, 1);
+		user->buddy = buddy;
+		buddy->proto_data = (gpointer)user;
+	} 
+
+	user = (MsimUser *)(buddy->proto_data);
+
+	return user;
+}
+
+/** Find and return an MsimUser * representing a user on the buddy list, or NULL. */
+MsimUser *
+msim_find_user(MsimSession *session, const gchar *username)
+{
+	PurpleBuddy *buddy;
+	MsimUser *user;
+
+	buddy = purple_find_buddy(session->account, username);
+	if (!buddy) {
+		return NULL;
+	}
+
+	user = msim_get_user_from_buddy(buddy);
+
+	return user;
+}
+
+/** Append user information to a PurpleNotifyUserInfo, given an MsimUser. 
+ * Used by msim_tooltip_text() and msim_get_info_cb() to show a user's profile.
+ */
+void
+msim_append_user_info(MsimSession *session, PurpleNotifyUserInfo *user_info, MsimUser *user, gboolean full)
+{
+	gchar *str;
+	guint uid;
+	guint cv;
+
+	/* Useful to identify the account the tooltip refers to. 
+	 *  Other prpls show this. */
+	if (user->username) {
+		purple_notify_user_info_add_pair(user_info, _("User"), user->username);
+	}
+
+	uid = purple_blist_node_get_int(&user->buddy->node, "UserID");
+
+	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));
+	}
+
+
+	/* a/s/l...the vitals */
+	if (user->age) {
+		purple_notify_user_info_add_pair(user_info, _("Age"),
+				g_strdup_printf("%d", user->age));
+	}
+
+	if (user->gender && strlen(user->gender)) {
+		purple_notify_user_info_add_pair(user_info, _("Gender"), user->gender);
+	}
+
+	if (user->location && strlen(user->location)) {
+		purple_notify_user_info_add_pair(user_info, _("Location"), user->location);
+	}
+
+	/* Other information */
+	if (user->headline && strlen(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)) {
+		purple_notify_user_info_add_pair(user_info, _("Song"), 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));
+	}
+
+	if (full) {
+		/* Client information */
+
+		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));
+		} else if (str) {
+			purple_notify_user_info_add_pair(user_info, _("Client Version"),
+					g_strdup(str));
+		} else if (cv) {
+			purple_notify_user_info_add_pair(user_info, _("Client Version"),
+					g_strdup_printf("Build %d", cv));
+		}
+	}
+}
+
+/** Store a field of information about a buddy. */
+void 
+msim_store_user_info_each(const gchar *key_str, gchar *value_str, MsimUser *user)
+{
+	if (g_str_equal(key_str, "UserID") || g_str_equal(key_str, "ContactID")) {
+		/* Save to buddy list, if it exists, for quick cached uid lookup with msim_uid2username_from_blist(). */
+		if (user->buddy)
+		{
+			purple_debug_info("msim", "associating uid %s with username %s\n", key_str, user->buddy->name);
+			purple_blist_node_set_int(&user->buddy->node, "UserID", atol(value_str));
+		}
+		/* Need to store in MsimUser, too? What if not on blist? */
+	} else if (g_str_equal(key_str, "Age")) {
+		user->age = atol(value_str);
+	} else if (g_str_equal(key_str, "Gender")) {
+		user->gender = g_strdup(value_str);
+	} else if (g_str_equal(key_str, "Location")) {
+		user->location = g_strdup(value_str);
+	} else if (g_str_equal(key_str, "TotalFriends")) {
+		user->total_friends = atol(value_str);
+	} else if (g_str_equal(key_str, "DisplayName")) {
+		user->display_name = g_strdup(value_str);
+	} else if (g_str_equal(key_str, "BandName")) {
+		user->band_name = g_strdup(value_str);
+	} else if (g_str_equal(key_str, "SongName")) {
+		user->song_name = g_strdup(value_str);
+	} else if (g_str_equal(key_str, "UserName") || g_str_equal(key_str, "IMName") || g_str_equal(key_str, "NickName")) {
+		/* Ignore because PurpleBuddy knows this already */
+		;
+	} else if (g_str_equal(key_str, "ImageURL") || g_str_equal(key_str, "AvatarURL")) {
+		const gchar *previous_url;
+
+		user->image_url = g_strdup(value_str);
+
+		/* Instead of showing 'no photo' picture, show nothing. */
+		if (g_str_equal(user->image_url, "http://x.myspace.com/images/no_pic.gif"))
+		{
+			purple_buddy_icons_set_for_user(user->buddy->account,
+				user->buddy->name,
+				NULL, 0, NULL);
+			return;
+		}
+	
+		/* TODO: use ETag for checksum */
+		previous_url = purple_buddy_icons_get_checksum_for_user(user->buddy);
+
+		/* Only download if URL changed */
+		if (!previous_url || !g_str_equal(previous_url, user->image_url)) {
+			purple_util_fetch_url(user->image_url, TRUE, NULL, TRUE, msim_downloaded_buddy_icon, (gpointer)user);
+		}
+	} else if (g_str_equal(key_str, "LastImageUpdated")) {
+		/* TODO: use somewhere */
+		user->last_image_updated = atol(value_str);
+	} else if (g_str_equal(key_str, "Headline")) {
+		user->headline = g_strdup(value_str);
+	} else {
+		/* TODO: other fields in MsimUser */
+		gchar *msg;
+
+		msg = g_strdup_printf("msim_store_user_info_each: unknown field %s=%s",
+				key_str, value_str);
+
+		msim_unrecognized(NULL, NULL, msg);
+
+		g_free(msg);
+	}
+}
+
+/** Save buddy information to the buddy list from a user info reply message.
+ *
+ * @param session
+ * @param msg The user information reply, with any amount of information.
+ * @param user The structure to save to, or NULL to save in PurpleBuddy->proto_data.
+ *
+ * Variable information is saved to the passed MsimUser structure. Permanent
+ * information (UserID) is stored in the blist node of the buddy list (and
+ * ends up in blist.xml, persisted to disk) if it exists.
+ *
+ * If the function has no buddy information, this function
+ * is a no-op (and returns FALSE).
+ *
+ */
+gboolean 
+msim_store_user_info(MsimSession *session, MsimMessage *msg, MsimUser *user)
+{
+	gchar *username;
+	MsimMessage *body, *body_node;
+
+	g_return_val_if_fail(MSIM_SESSION_VALID(session), FALSE);
+	g_return_val_if_fail(msg != NULL, FALSE);
+
+	body = msim_msg_get_dictionary(msg, "body");
+	if (!body) {
+		return FALSE;
+	}
+
+	username = msim_msg_get_string(body, "UserName");
+
+	if (!username) {
+		purple_debug_info("msim", 
+			"msim_process_reply: not caching body, no UserName\n");
+		msim_msg_free(body);
+		g_free(username);
+		return FALSE;
+	}
+	
+	/* Null user = find and store in PurpleBuddy's proto_data */
+	if (!user) {
+		user = msim_find_user(session, username);
+		if (!user) {
+			msim_msg_free(body);
+			g_free(username);
+			return FALSE;
+		}
+	}
+
+	/* TODO: make looping over MsimMessage's easier. */
+	for (body_node = body; 
+		body_node != NULL; 
+		body_node = msim_msg_get_next_element_node(body_node))
+	{
+		const gchar *key_str;
+		gchar *value_str;
+		MsimMessageElement *elem;
+
+		elem = (MsimMessageElement *)body_node->data;
+		key_str = elem->name;
+
+		value_str = msim_msg_get_string_from_element(elem);
+		msim_store_user_info_each(key_str, value_str, user);
+		g_free(value_str);
+	}
+
+	if (msim_msg_get_integer(msg, "dsn") == MG_OWN_IM_INFO_DSN &&
+		msim_msg_get_integer(msg, "lid") == MG_OWN_IM_INFO_LID) {
+		/* TODO: do something with our own IM info, if we need it for some
+		 * specific purpose. Otherwise it is available on the buddy list,
+		 * if the user has themselves as their own buddy. 
+		 *
+		 * However, much of the info is already available in MsimSession,
+		 * stored in msim_we_are_logged_on(). */
+	} else if (msim_msg_get_integer(msg, "dsn") == MG_OWN_MYSPACE_INFO_DSN &&
+			msim_msg_get_integer(msg, "lid") == MG_OWN_MYSPACE_INFO_LID) {
+		/* TODO: same as above, but for MySpace info. */
+	}
+
+	msim_msg_free(body);
+
+	return TRUE;
+}
+
+/**
+ * Asynchronously lookup user information, calling callback when receive result.
+ *
+ * @param session
+ * @param user The user id, email address, or username. Not freed.
+ * @param cb Callback, called with user information when available.
+ * @param data An arbitray data pointer passed to the callback.
+ */
+/* TODO: change to not use callbacks */
+void 
+msim_lookup_user(MsimSession *session, const gchar *user, MSIM_USER_LOOKUP_CB cb, gpointer data)
+{
+	MsimMessage *body;
+	gchar *field_name;
+	guint rid, cmd, dsn, lid;
+
+	g_return_if_fail(MSIM_SESSION_VALID(session));
+	g_return_if_fail(user != NULL);
+	/* Callback can be null to not call anything, just lookup & store information. */
+	/*g_return_if_fail(cb != NULL);*/
+
+	purple_debug_info("msim", "msim_lookup_userid: "
+			"asynchronously looking up <%s>\n", user);
+
+	msim_msg_dump("msim_lookup_user: data=%s\n", (MsimMessage *)data);
+
+	/* Setup callback. Response will be associated with request using 'rid'. */
+	rid = msim_new_reply_callback(session, cb, data);
+
+	/* Send request */
+
+	cmd = MSIM_CMD_GET;
+
+	if (msim_is_userid(user)) {
+		field_name = "UserID";
+		dsn = MG_MYSPACE_INFO_BY_ID_DSN; 
+		lid = MG_MYSPACE_INFO_BY_ID_LID; 
+	} else if (msim_is_email(user)) {
+		field_name = "Email";
+		dsn = MG_MYSPACE_INFO_BY_STRING_DSN;
+		lid = MG_MYSPACE_INFO_BY_STRING_LID;
+	} else {
+		field_name = "UserName";
+		dsn = MG_MYSPACE_INFO_BY_STRING_DSN;
+		lid = MG_MYSPACE_INFO_BY_STRING_LID;
+	}
+
+	body = msim_msg_new(
+			field_name, MSIM_TYPE_STRING, g_strdup(user),
+			NULL);
+
+	g_return_if_fail(msim_send(session,
+			"persist", MSIM_TYPE_INTEGER, 1,
+			"sesskey", MSIM_TYPE_INTEGER, session->sesskey,
+			"cmd", MSIM_TYPE_INTEGER, 1,
+			"dsn", MSIM_TYPE_INTEGER, dsn,
+			"uid", MSIM_TYPE_INTEGER, session->userid,
+			"lid", MSIM_TYPE_INTEGER, lid,
+			"rid", MSIM_TYPE_INTEGER, rid,
+			"body", MSIM_TYPE_DICTIONARY, body,
+			NULL));
+} 
+
+
+/**
+ * Check if a string is a userid (all numeric).
+ *
+ * @param user The user id, email, or name.
+ *
+ * @return TRUE if is userid, FALSE if not.
+ */
+gboolean 
+msim_is_userid(const gchar *user)
+{
+	g_return_val_if_fail(user != NULL, FALSE);
+
+	return strspn(user, "0123456789") == strlen(user);
+}
+
+/**
+ * Check if a string is an email address (contains an @).
+ *
+ * @param user The user id, email, or name.
+ *
+ * @return TRUE if is an email, FALSE if not.
+ *
+ * This function is not intended to be used as a generic
+ * means of validating email addresses, but to distinguish
+ * between a user represented by an email address from
+ * other forms of identification.
+ */ 
+gboolean 
+msim_is_email(const gchar *user)
+{
+	g_return_val_if_fail(user != NULL, FALSE);
+
+	return strchr(user, '@') != NULL;
+}
+
+
+/** Callback for when a buddy icon finished being downloaded. */
+static void
+msim_downloaded_buddy_icon(PurpleUtilFetchUrlData *url_data,
+		gpointer user_data,
+		const gchar *url_text,
+		gsize len,
+		const gchar *error_message)
+{
+	MsimUser *user;
+
+	user = (MsimUser *)user_data;
+
+	purple_debug_info("msim_downloaded_buddy_icon",
+			"Downloaded %d bytes\n", len);
+
+	if (!url_text) {
+		purple_debug_info("msim_downloaded_buddy_icon",
+				"failed to download icon for %s",
+				user->buddy->name);
+		return;
+	}
+
+	purple_buddy_icons_set_for_user(user->buddy->account,
+			user->buddy->name,
+			g_memdup((gchar *)url_text, len), len, 
+			/* Use URL itself as buddy icon "checksum" (TODO: ETag) */
+			user->image_url);		/* checksum */
+}
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/myspace/user.h	Tue Aug 28 09:20:27 2007 +0000
@@ -0,0 +1,55 @@
+/* MySpaceIM Protocol Plugin, header file
+ *
+ * Copyright (C) 2007, Jeff Connelly <jeff2@soc.pidgin.im>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#ifndef _MYSPACE_USER_H
+#define _MYSPACE_USER_H
+
+/* Hold ephemeral information about buddies, for proto_data of PurpleBuddy. */
+/* GHashTable? */
+typedef struct _MsimUser
+{
+	PurpleBuddy *buddy;
+	guint client_cv;
+	gchar *client_info;
+	guint age;
+	gchar *gender;
+	gchar *location;
+	guint total_friends;
+	gchar *headline;
+	gchar *display_name;
+	/* Note: uid is in &buddy->node (set_blist_node_int), since it never changes */
+	gchar *username;
+	gchar *band_name, *song_name;
+	gchar *image_url;
+	guint last_image_updated;
+} MsimUser;
+
+/* Callback function pointer type for when a user's information is received, 
+ * initiated from a user lookup. */
+typedef void (*MSIM_USER_LOOKUP_CB)(MsimSession *session, MsimMessage *userinfo, gpointer data);
+
+MsimUser *msim_get_user_from_buddy(PurpleBuddy *buddy);
+MsimUser *msim_find_user(MsimSession *session, const gchar *username);
+void msim_append_user_info(MsimSession *session, PurpleNotifyUserInfo *user_info, MsimUser *user, gboolean full);
+gboolean msim_store_user_info(MsimSession *session, MsimMessage *msg, MsimUser *user);
+gboolean msim_is_userid(const gchar *user);
+gboolean msim_is_email(const gchar *user);
+void msim_lookup_user(MsimSession *session, const gchar *user, MSIM_USER_LOOKUP_CB cb, gpointer data);
+
+#endif /* !_MYSPACE_USER_H */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/myspace/zap.c	Tue Aug 28 09:20:27 2007 +0000
@@ -0,0 +1,265 @@
+/* MySpaceIM Protocol Plugin - zap support
+ *
+ * Copyright (C) 2007, Jeff Connelly <jeff2@soc.pidgin.im>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#include "myspace.h"
+#include "zap.h"
+
+static gboolean msim_send_zap(MsimSession *session, const gchar *username, guint code);
+static void msim_send_zap_from_menu(PurpleBlistNode *node, gpointer zap_num_ptr);
+
+
+/** Get zap types. */
+GList *
+msim_attention_types(PurpleAccount *acct)
+{
+	static GList *types = NULL;
+	MsimAttentionType* attn;
+
+	if (!types) {
+#define _MSIM_ADD_NEW_ATTENTION(icn, nme, incoming, outgoing)              \
+		attn = g_new0(MsimAttentionType, 1);                       \
+		attn->icon_name = icn;                                     \
+		attn->name = nme;                                          \
+		attn->incoming_description = incoming;                     \
+		attn->outgoing_description = outgoing;                     \
+		types = g_list_append(types, attn);
+
+		/* TODO: icons for each zap */
+		_MSIM_ADD_NEW_ATTENTION(NULL, _("zap"), _("zapped"), _("Zapping"));
+		_MSIM_ADD_NEW_ATTENTION(NULL, _("whack"), _("whacked"), _("Whacking"));
+		_MSIM_ADD_NEW_ATTENTION(NULL, _("torch"), _("torched"), _("Torching"));
+		_MSIM_ADD_NEW_ATTENTION(NULL, _("smooch"), _("smooched"), _("Smooching"));
+		_MSIM_ADD_NEW_ATTENTION(NULL, _("hug"), _("hugged"), _("Hugging"));
+		_MSIM_ADD_NEW_ATTENTION(NULL, _("bslap"), _("bslapped"), _("Bslapping"));
+		_MSIM_ADD_NEW_ATTENTION(NULL, _("goose"), _("goosed"), _("Goosing"));
+		_MSIM_ADD_NEW_ATTENTION(NULL, _("hi-five"), _("hi-fived"), _("Hi-fiving"));
+		_MSIM_ADD_NEW_ATTENTION(NULL, _("punk"), _("punk'd"), _("Punking"));
+		_MSIM_ADD_NEW_ATTENTION(NULL, _("raspberry"), _("raspberried"), _("Raspberry'ing"));
+	}
+
+	return types;
+}
+
+/** Send a zap */
+gboolean
+msim_send_attention(PurpleConnection *gc, const gchar *username, guint code)
+{
+	GList *types;
+	MsimSession *session;
+	MsimAttentionType *attn;
+	PurpleBuddy *buddy;
+
+	session = (MsimSession *)gc->proto_data;
+
+	/* Look for this attention type, by the code index given. */
+	types = msim_attention_types(gc->account);
+	attn = (MsimAttentionType *)g_list_nth_data(types, code);
+
+	if (!attn) {
+		purple_debug_info("msim_send_attention", "got invalid zap code %d\n", code);
+		return FALSE;
+	}
+
+	buddy = purple_find_buddy(session->account, username);
+	if (!buddy) {
+		return FALSE;
+	}
+
+	msim_send_zap(session, username, code);
+
+	return TRUE;
+}
+
+/** Send a zap to a user. */
+static gboolean
+msim_send_zap(MsimSession *session, const gchar *username, guint code)
+{
+	gchar *zap_string;
+	gboolean rc;
+#ifndef MSIM_USE_ATTENTION_API
+	GList *types;
+	MsimAttentionType *attn;
+	gchar *zap_description;
+#endif
+
+	g_return_val_if_fail(session != NULL, FALSE);
+	g_return_val_if_fail(username != NULL, FALSE);
+
+
+#ifdef MSIM_USE_ATTENTION_API
+	/* serv_send_attention(session->gc, username, code); */
+#else
+	types = msim_attention_types(session->account);
+
+	attn = g_list_nth_data(types, code);
+	if (!attn) {
+		return FALSE;
+	}
+
+
+	zap_description = g_strdup_printf("*** Attention: %s %s ***", attn->outgoing_description,
+			username);
+
+	serv_got_im(session->gc, username, zap_description,
+			PURPLE_MESSAGE_SEND | PURPLE_MESSAGE_SYSTEM, time(NULL));
+
+	g_free(zap_description);
+#endif
+
+	/* Construct and send the actual zap command. */
+	zap_string = g_strdup_printf("!!!ZAP_SEND!!!=RTE_BTN_ZAPS_%d", code);
+
+	if (!msim_send_bm(session, username, zap_string, MSIM_BM_ACTION)) {
+		purple_debug_info("msim_send_zap_from_menu", "msim_send_bm failed: zapping %s with %s",
+				username, zap_string);
+		rc = FALSE;
+	} else {
+		rc = TRUE;
+	}
+	
+	g_free(zap_string);
+
+	return rc;
+
+}
+
+/** Zap someone. Callback from msim_blist_node_menu zap menu. */
+static void
+msim_send_zap_from_menu(PurpleBlistNode *node, gpointer zap_num_ptr)
+{
+	PurpleBuddy *buddy;
+	PurpleAccount *account;
+	PurpleConnection *gc;
+	MsimSession *session;
+	guint zap;
+
+	if (!PURPLE_BLIST_NODE_IS_BUDDY(node)) {
+		/* Only know about buddies for now. */
+		return;
+	}
+
+	g_return_if_fail(PURPLE_BLIST_NODE_IS_BUDDY(node));
+
+	buddy = (PurpleBuddy *)node;
+
+	/* Find the session */
+	account = buddy->account;
+	gc = purple_account_get_connection(account);
+	session = (MsimSession *)gc->proto_data;
+
+	zap = GPOINTER_TO_INT(zap_num_ptr);
+
+#ifdef MSIM_USE_ATTENTION_API
+	serv_send_attention(session->gc, buddy->name, zap);
+#else
+	g_return_if_fail(msim_send_zap(session, buddy->name, zap));
+#endif
+}
+
+/** Return menu, if any, for a buddy list node. */
+GList *
+msim_blist_node_menu(PurpleBlistNode *node)
+{
+	GList *menu, *zap_menu;
+	GList *types;
+	PurpleMenuAction *act;
+	/* Warning: hardcoded to match that in msim_attention_types. */
+	const gchar *zap_names[10];
+	guint i;
+
+	if (!PURPLE_BLIST_NODE_IS_BUDDY(node)) {
+		/* Only know about buddies for now. */
+		return NULL;
+	}
+
+	/* Names from official client. */
+	types = msim_attention_types(NULL);
+	i = 0;
+	do
+	{
+		MsimAttentionType *attn;
+
+		attn = (MsimAttentionType *)types->data;
+		zap_names[i] = attn->name;
+		++i;
+	} while ((types = g_list_next(types)));
+
+	menu = zap_menu = NULL;
+
+	/* TODO: get rid of once is accessible directly in GUI */
+	for (i = 0; i < sizeof(zap_names) / sizeof(zap_names[0]); ++i) {
+		act = purple_menu_action_new(zap_names[i], PURPLE_CALLBACK(msim_send_zap_from_menu),
+				GUINT_TO_POINTER(i), NULL);
+		zap_menu = g_list_append(zap_menu, act);
+	}
+
+	act = purple_menu_action_new(_("Zap"), NULL, NULL, zap_menu);
+	menu = g_list_append(menu, act);
+
+	return menu;
+}
+
+/** Process an incoming zap. */
+gboolean
+msim_incoming_zap(MsimSession *session, MsimMessage *msg)
+{
+	gchar *msg_text, *username;
+	gint zap;
+#ifndef MSIM_USE_ATTENTION_API
+	const gchar *zap_past_tense[10];
+	gchar *zap_text;
+
+	zap_past_tense[0] = _("zapped");
+	zap_past_tense[1] = _("whacked");
+	zap_past_tense[2] = _("torched");
+	zap_past_tense[3] = _("smooched");
+	zap_past_tense[4] = _("hugged");
+	zap_past_tense[5] = _("bslapped");
+	zap_past_tense[6] = _("goosed");
+	zap_past_tense[7] = _("hi-fived");
+	zap_past_tense[8] = _("punk'd");
+	zap_past_tense[9] = _("raspberried");
+#endif
+
+	msg_text = msim_msg_get_string(msg, "msg");
+	username = msim_msg_get_string(msg, "_username");
+
+	g_return_val_if_fail(msg_text != NULL, FALSE);
+	g_return_val_if_fail(username != NULL, FALSE);
+
+	g_return_val_if_fail(sscanf(msg_text, "!!!ZAP_SEND!!!=RTE_BTN_ZAPS_%d", &zap) == 1, FALSE);
+
+	zap = CLAMP(zap, 0, 9);
+
+#ifdef MSIM_USE_ATTENTION_API
+	serv_got_attention(session->gc, username, zap);
+#else
+	zap_text = g_strdup_printf(_("*** You have been %s! ***"), zap_past_tense[zap]);
+	serv_got_im(session->gc, username, zap_text, 
+			PURPLE_MESSAGE_RECV | PURPLE_MESSAGE_SYSTEM, time(NULL));
+	g_free(zap_text);
+#endif
+
+	g_free(msg_text);
+	g_free(username);
+
+	return TRUE;
+}
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/myspace/zap.h	Tue Aug 28 09:20:27 2007 +0000
@@ -0,0 +1,28 @@
+/* MySpaceIM Protocol Plugin - zap support
+ *
+ * Copyright (C) 2007, Jeff Connelly <jeff2@soc.pidgin.im>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#ifndef _MYSPACE_ZAP_H
+#define _MYSPACE_ZAP_H
+
+GList *msim_attention_types(PurpleAccount *acct);
+gboolean msim_send_attention(PurpleConnection *gc, const gchar *username, guint code);
+GList *msim_blist_node_menu(PurpleBlistNode *node);
+gboolean msim_incoming_zap(MsimSession *session, MsimMessage *msg);
+
+#endif /* !_MYSPACE_ZAP_H */
--- a/libpurple/protocols/yahoo/yahoo.c	Sat Aug 25 08:42:33 2007 +0000
+++ b/libpurple/protocols/yahoo/yahoo.c	Tue Aug 28 09:20:27 2007 +0000
@@ -238,14 +238,18 @@
 		case 8: /* how many online buddies we have */
 			break;
 		case 7: /* the current buddy */
-			if (name && f) /* update the previous buddy before changing the variables */
-				yahoo_update_status(gc, name, f);
-			name = pair->value;
-			if (name && g_utf8_validate(name, -1, NULL))
+			/* update the previous buddy before changing the variables */
+			if (f) {
+				if (message)
+					yahoo_friend_set_status_message(f, yahoo_string_decode(gc, message, unicode));
+				if (name)
+					yahoo_update_status(gc, name, f);
+			}
+			name = message = NULL;
+			f = NULL;
+			if (pair->value && g_utf8_validate(pair->value, -1, NULL)) {
+				name = pair->value;
 				f = yahoo_friend_find_or_new(gc, name);
-			else {
-				f = NULL;
-				name = NULL;
 			}
 			break;
 		case 10: /* state */
@@ -779,7 +783,7 @@
 		purple_conversation_write(conv, NULL, buf, PURPLE_MESSAGE_SYSTEM|PURPLE_MESSAGE_NOTIFY, time(NULL));
 		g_free(buf);
 	}
-    	
+
 }
 
 
@@ -895,6 +899,8 @@
 			PurpleConversation *c;
 			char *username, *str;
 
+			str = NULL;
+
 			account = purple_connection_get_account(gc);
 			c = purple_conversation_new(PURPLE_CONV_TYPE_IM, account, im->from);
 
@@ -903,10 +909,13 @@
 			else
 				username = g_markup_escape_text(im->from, -1);
 
+#ifdef YAHOO_USE_ATTENTION_API
+			serv_got_attention(gc, username, YAHOO_BUZZ);
+#else
 			str = g_strdup_printf(_("%s just sent you a Buzz!"), username);
 
 			purple_conversation_write(c, NULL, str, PURPLE_MESSAGE_SYSTEM|PURPLE_MESSAGE_NOTIFY, im->time);
-
+#endif
 			g_free(username);
 			g_free(str);
 			g_free(m);
@@ -4016,17 +4025,24 @@
 
 static PurpleCmdRet
 yahoopurple_cmd_buzz(PurpleConversation *c, const gchar *cmd, gchar **args, gchar **error, void *data) {
-
 	PurpleAccount *account = purple_conversation_get_account(c);
+#ifndef YAHOO_USE_ATTENTION_API
 	const char *username = purple_account_get_username(account);
+#endif
 
 	if (*args && args[0])
 		return PURPLE_CMD_RET_FAILED;
 
+#ifdef YAHOO_USE_ATTENTION_API
+	serv_send_attention(account->gc, c->name, YAHOO_BUZZ);
+#else
+
 	purple_debug(PURPLE_DEBUG_INFO, "yahoo",
 	           "Sending <ding> on account %s to buddy %s.\n", username, c->name);
 	purple_conv_im_send(PURPLE_CONV_IM(c), "<ding>");
 	purple_conversation_write(c, NULL, _("You have just sent a Buzz!"), PURPLE_MESSAGE_SYSTEM, time(NULL));
+#endif
+
 	return PURPLE_CMD_RET_OK;
 }
 
@@ -4076,6 +4092,42 @@
 {
 	return TRUE;
 }
+	
+gboolean yahoo_send_attention(PurpleConnection *gc, const char *username, guint type)
+{
+	PurpleConversation *c;
+
+	c = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, 
+			username, gc->account);
+
+	g_return_val_if_fail(c != NULL, FALSE);
+
+	purple_debug(PURPLE_DEBUG_INFO, "yahoo",
+	           "Sending <ding> on account %s to buddy %s.\n", username, c->name);
+	/* TODO: find out how to send a <ding> without showing up as a blank line on
+	 * the conversation window. */
+	purple_conv_im_send(PURPLE_CONV_IM(c), "<ding>");
+
+	return TRUE;
+}
+
+GList *yahoo_attention_types(PurpleAccount *account)
+{
+	PurpleAttentionType *attn;
+	static GList *list = NULL;
+
+	if (!list) {
+		/* Yahoo only supports one attention command: the 'buzz'. */
+		/* This is index number YAHOO_BUZZ. */
+		attn = g_new0(PurpleAttentionType, 1);
+		attn->name = _("buzz");
+		attn->incoming_description = _("buzzed");
+		attn->outgoing_description = _("Buzzing");
+		list = g_list_append(list, attn);
+	} 
+
+	return list;
+}
 
 /************************** Plugin Initialization ****************************/
 static void
@@ -4285,9 +4337,15 @@
 	NULL, /* send_raw */
 	NULL, /* roomlist_room_serialize */
 
-	/* padding */
+#ifdef YAHOO_USE_ATTENTION_API
+	yahoo_send_attention,
+	yahoo_attention_types,
+#else
 	NULL,
 	NULL,
+#endif
+
+	/* padding */
 	NULL,
 	NULL
 };
--- a/libpurple/protocols/yahoo/yahoo.h	Sat Aug 25 08:42:33 2007 +0000
+++ b/libpurple/protocols/yahoo/yahoo.h	Tue Aug 28 09:20:27 2007 +0000
@@ -67,6 +67,13 @@
 #define YAHOO_STATUS_TYPE_INVISIBLE "invisible"
 #define YAHOO_STATUS_TYPE_MOBILE "mobile"
 
+#define YAHOO_USE_ATTENTION_API
+
+#ifdef YAHOO_USE_ATTENTION_API
+/* Index into attention types list. */
+#define YAHOO_BUZZ 0
+#endif
+
 enum yahoo_status {
 	YAHOO_STATUS_AVAILABLE = 0,
 	YAHOO_STATUS_BRB,
@@ -213,4 +220,7 @@
 gboolean yahoo_privacy_check
 	(PurpleConnection *gc, const char *who);
 
+gboolean yahoo_send_attention(PurpleConnection *gc, const char *username, guint type);
+GList *yahoo_attention_types(PurpleAccount *account);
+
 #endif /* _YAHOO_H_ */
--- a/libpurple/prpl.c	Sat Aug 25 08:42:33 2007 +0000
+++ b/libpurple/prpl.c	Tue Aug 28 09:20:27 2007 +0000
@@ -200,6 +200,7 @@
 			continue;
 
 		purple_status_set_active(status, FALSE);
+		purple_blist_update_buddy_status(buddy, status);
 	}
 
 	g_slist_free(list);
--- a/libpurple/prpl.h	Sat Aug 25 08:42:33 2007 +0000
+++ b/libpurple/prpl.h	Tue Aug 28 09:20:27 2007 +0000
@@ -30,6 +30,7 @@
 #define _PURPLE_PRPL_H_
 
 typedef struct _PurplePluginProtocolInfo PurplePluginProtocolInfo;
+typedef struct _PurpleAttentionType PurpleAttentionType;
 
 /**************************************************************************/
 /** @name Basic Protocol Information                                      */
@@ -91,6 +92,20 @@
 	gboolean secret;
 };
 
+struct _PurpleAttentionType
+{
+	const char *name;                  /**< Shown in GUI elements */
+	const char *incoming_description;  /**< Shown when sent */
+	const char *outgoing_description;  /**< Shown when receied */
+	const char *icon_name;             /**< Icon to display (optional) */
+
+	/* Reserved fields for future purposes */
+	gpointer _reserved1;
+	gpointer _reserved2;
+	gpointer _reserved3;
+	gpointer _reserved4;
+};
+
 /**
  * Protocol options
  *
@@ -332,8 +347,10 @@
 	/* room list serialize */
 	char *(*roomlist_room_serialize)(PurpleRoomlistRoom *room);
 
-	void (*_purple_reserved1)(void);
-	void (*_purple_reserved2)(void);
+	/* Attention API for sending & receiving zaps/nudges/buzzes etc. */
+	gboolean (*send_attention)(PurpleConnection *gc, const char *username, guint type);
+	GList *(*attention_types)(PurpleAccount *acct);
+
 	void (*_purple_reserved3)(void);
 	void (*_purple_reserved4)(void);
 };
--- a/libpurple/server.c	Sat Aug 25 08:42:33 2007 +0000
+++ b/libpurple/server.c	Tue Aug 28 09:20:27 2007 +0000
@@ -242,6 +242,107 @@
 	}
 }
 
+PurpleAttentionType *purple_get_attention_type_from_code(PurpleAccount *account, guint type_code)
+{
+	PurplePlugin *prpl;
+	PurpleAttentionType* attn;
+	GList *(*get_attention_types)(PurpleAccount *);
+
+	g_return_val_if_fail(account != NULL, NULL);
+
+	prpl = purple_find_prpl(purple_account_get_protocol_id(account));
+
+	/* Lookup the attention type in the protocol's attention_types list, if any. */
+	get_attention_types = PURPLE_PLUGIN_PROTOCOL_INFO(prpl)->attention_types;
+	if (get_attention_types) {
+		GList *attention_types;
+
+		attention_types = get_attention_types(account);
+		attn = (PurpleAttentionType *)g_list_nth_data(attention_types, type_code);
+	} else {
+		attn = NULL;
+	}
+
+	return attn;
+}
+
+void
+serv_send_attention(PurpleConnection *gc, const char *who, guint type_code)
+{
+	PurpleAttentionType *attn;
+	PurpleMessageFlags flags;
+	PurplePlugin *prpl;
+	gboolean (*send_attention)(PurpleConnection *, const char *, guint);
+	
+	gchar *description;
+	time_t mtime;
+
+	g_return_if_fail(gc != NULL);
+	g_return_if_fail(who != NULL);
+
+	prpl = purple_find_prpl(purple_account_get_protocol_id(gc->account));
+	send_attention = PURPLE_PLUGIN_PROTOCOL_INFO(prpl)->send_attention;
+	g_return_if_fail(send_attention != NULL);
+
+	mtime = time(NULL);
+
+	attn = purple_get_attention_type_from_code(gc->account, type_code);
+
+	if (attn && attn->outgoing_description) {
+		description = g_strdup_printf(_("Attention! %s %s."), attn->outgoing_description, who);
+	} else {
+		description = g_strdup(_("Attention!"));
+	}
+	
+	flags = PURPLE_MESSAGE_SEND | PURPLE_MESSAGE_NOTIFY | PURPLE_MESSAGE_SYSTEM;
+
+	purple_debug_info("server", "serv_send_attention: sending '%s' to %s\n",
+			description, who);
+
+	if (!send_attention(gc, who, type_code))
+		return;
+
+	/* TODO: icons, sound, shaking... same as serv_got_attention(). */
+	serv_got_im(gc, who, description, flags, mtime);
+
+	g_free(description);
+}
+
+void
+serv_got_attention(PurpleConnection *gc, const char *who, guint type_code)
+{
+	PurpleMessageFlags flags;
+	PurpleAttentionType *attn;
+	gchar *description;
+	time_t mtime;
+
+	mtime = time(NULL);
+
+	attn = purple_get_attention_type_from_code(gc->account, type_code);
+
+	/* PURPLE_MESSAGE_NOTIFY is for attention messages. */
+	flags = PURPLE_MESSAGE_SYSTEM | PURPLE_MESSAGE_NOTIFY | PURPLE_MESSAGE_RECV;
+
+	/* TODO: if (attn->icon_name) is non-null, use it to lookup an emoticon and display
+	 * it next to the attention command. And if it is null, display a generic icon. */
+
+	if (attn && attn->incoming_description) {
+		description = g_strdup_printf(_("Attention! You have been %s."), attn->incoming_description);
+	} else {
+		description = g_strdup(_("Attention!"));
+	}
+
+	purple_debug_info("server", "serv_got_attention: got '%s' from %s\n",
+			description, who);
+
+	serv_got_im(gc, who, description, flags, mtime);
+	
+	/* TODO: sounds (depending on PurpleAttentionType), shaking, etc. */
+
+	g_free(description);
+}
+
+
 /*
  * Move a buddy from one group to another on server.
  *
--- a/libpurple/server.h	Sat Aug 25 08:42:33 2007 +0000
+++ b/libpurple/server.h	Tue Aug 28 09:20:27 2007 +0000
@@ -53,6 +53,35 @@
 
 void serv_move_buddy(PurpleBuddy *, PurpleGroup *, PurpleGroup *);
 int  serv_send_im(PurpleConnection *, const char *, const char *, PurpleMessageFlags flags);
+
+/** Get information about an account's attention commands, from the prpl. 
+ * 
+ * @return The attention command numbered 'code' from the prpl's attention_types, or NULL.
+ */
+PurpleAttentionType *purple_get_attention_type_from_code(PurpleAccount *account, guint type_code);
+
+/** Send an attention request message.
+ *
+ * @param gc The connection to send the message on.
+ * @param who Whose attention to request.
+ * @param type An index into the prpl's attention_types list determining the type
+ * 	of the attention request command to send. 0 if prpl only defines one
+ * 	(for example, Yahoo and MSN), but some protocols define more (MySpaceIM).
+ *
+ * Note that you can't send arbitrary PurpleAttentionType's, because there is
+ * only a fixed set of attention commands.
+ */
+void serv_send_attention(PurpleConnection *gc, const char *who, guint type_code);
+
+/** Process an incoming attention message. 
+ *
+ * @param gc The connection that received the attention message.
+ * @param who Who requested your attention.
+ * @param type An index into the prpl's attention_types list determining the type
+ * 	of the attention request command to send. 
+ */
+void serv_got_attention(PurpleConnection *gc, const char *who, guint type_code);
+
 void serv_get_info(PurpleConnection *, const char *);
 void serv_set_info(PurpleConnection *, const char *);
 
@@ -68,6 +97,7 @@
 void serv_alias_buddy(PurpleBuddy *);
 void serv_got_alias(PurpleConnection *gc, const char *who, const char *alias);
 
+
 /**
  * Receive a typing message from a remote user.  Either PURPLE_TYPING
  * or PURPLE_TYPED.  If the user has stopped typing then use
--- a/libpurple/sslconn.c	Sat Aug 25 08:42:33 2007 +0000
+++ b/libpurple/sslconn.c	Tue Aug 28 09:20:27 2007 +0000
@@ -24,6 +24,7 @@
  */
 #include "internal.h"
 
+#include "certificate.h"
 #include "debug.h"
 #include "sslconn.h"
 
@@ -117,6 +118,9 @@
 	gsc->connect_cb      = func;
 	gsc->error_cb        = error_func;
 
+	/* TODO: Move this elsewhere */
+	gsc->verifier = purple_certificate_find_verifier("x509","tls_cached");
+
 	gsc->connect_data = purple_proxy_connect(NULL, account, host, port, purple_ssl_connect_cb, gsc);
 
 	if (gsc->connect_data == NULL)
@@ -151,10 +155,37 @@
 	gsc->inpa = purple_input_add(gsc->fd, PURPLE_INPUT_READ, recv_cb, gsc);
 }
 
+const gchar *
+purple_ssl_strerror(PurpleSslErrorType error)
+{
+	switch(error) {
+		case PURPLE_SSL_CONNECT_FAILED:
+			return _("SSL Connection Failed");
+		case PURPLE_SSL_HANDSHAKE_FAILED:
+			return _("SSL Handshake Failed");
+		case PURPLE_SSL_CERTIFICATE_INVALID:
+			return _("SSL peer presented an invalid certificate");
+		default:
+			purple_debug_warning("sslconn", "Unknown SSL error code %d\n", error);
+			return _("Unknown SSL error");
+	}
+}
+
 PurpleSslConnection *
 purple_ssl_connect_fd(PurpleAccount *account, int fd,
 					PurpleSslInputFunction func,
-					PurpleSslErrorFunction error_func, void *data)
+					PurpleSslErrorFunction error_func,
+                    void *data)
+{
+    return purple_ssl_connect_with_host_fd(account, fd, func, error_func, NULL, data);
+}
+
+PurpleSslConnection *
+purple_ssl_connect_with_host_fd(PurpleAccount *account, int fd,
+                      PurpleSslInputFunction func,
+                      PurpleSslErrorFunction error_func,
+                      const char *host,
+                      void *data)
 {
 	PurpleSslConnection *gsc;
 	PurpleSslOps *ops;
@@ -175,7 +206,13 @@
 	gsc->connect_cb      = func;
 	gsc->error_cb        = error_func;
 	gsc->fd              = fd;
+    if(host)
+        gsc->host            = g_strdup(host);
 
+	/* TODO: Move this elsewhere */
+	gsc->verifier = purple_certificate_find_verifier("x509","tls_cached");
+
+    
 	ops = purple_ssl_get_ops();
 	ops->connectfunc(gsc);
 
@@ -231,6 +268,17 @@
 	return (ops->write)(gsc, data, len);
 }
 
+GList *
+purple_ssl_get_peer_certificates(PurpleSslConnection *gsc)
+{
+	PurpleSslOps *ops;
+
+	g_return_val_if_fail(gsc != NULL, NULL);
+
+	ops = purple_ssl_get_ops();
+	return (ops->get_peer_certificates)(gsc);
+}
+
 void
 purple_ssl_set_ops(PurpleSslOps *ops)
 {
@@ -246,8 +294,12 @@
 void
 purple_ssl_init(void)
 {
-	/* This doesn't do anything at the moment. All the actual init work
-	 * is handled by purple_ssl_is_supported upon demand. */
+	/* Although purple_ssl_is_supported will do the initialization on
+	   command, SSL plugins tend to register CertificateSchemes as well
+	   as providing SSL ops. */
+	if (!ssl_init()) {
+		purple_debug_error("sslconn", "Unable to initialize SSL.\n");
+	}
 }
 
 void
--- a/libpurple/sslconn.h	Sat Aug 25 08:42:33 2007 +0000
+++ b/libpurple/sslconn.h	Tue Aug 28 09:20:27 2007 +0000
@@ -25,6 +25,7 @@
 #ifndef _PURPLE_SSLCONN_H_
 #define _PURPLE_SSLCONN_H_
 
+#include "certificate.h"
 #include "proxy.h"
 
 #define PURPLE_SSL_DEFAULT_PORT 443
@@ -32,7 +33,8 @@
 typedef enum
 {
 	PURPLE_SSL_HANDSHAKE_FAILED = 1,
-	PURPLE_SSL_CONNECT_FAILED = 2
+	PURPLE_SSL_CONNECT_FAILED = 2,
+	PURPLE_SSL_CERTIFICATE_INVALID = 3
 } PurpleSslErrorType;
 
 typedef struct _PurpleSslConnection PurpleSslConnection;
@@ -69,6 +71,9 @@
 
 	/** Internal connection data managed by the SSL backend (GnuTLS/LibNSS/whatever) */
 	void *private_data;
+
+	/** Verifier to use in authenticating the peer */
+	PurpleCertificateVerifier *verifier;
 };
 
 /**
@@ -107,8 +112,17 @@
 	* @return	The number of bytes written (may be less than len) or <0 on error
 	*/
 	size_t (*write)(PurpleSslConnection *gsc, const void *data, size_t len);
-
-	void (*_purple_reserved1)(void);
+	/** Obtains the certificate chain provided by the peer
+	 *
+	 * @param gsc   Connection context
+	 * @return      A newly allocated list containing the certificates
+	 *              the peer provided.
+	 * @see PurpleCertificate
+	 * @todo        Decide whether the ordering of certificates in this
+	 *              list can be guaranteed.
+	 */
+	GList * (* get_peer_certificates)(PurpleSslConnection * gsc);
+	
 	void (*_purple_reserved2)(void);
 	void (*_purple_reserved3)(void);
 	void (*_purple_reserved4)(void);
@@ -131,6 +145,14 @@
 gboolean purple_ssl_is_supported(void);
 
 /**
+ * Returns a human-readable string for an SSL error
+ *
+ * @param error      Error code
+ * @return Human-readable error explanation
+ */
+const gchar * purple_ssl_strerror(PurpleSslErrorType error);
+
+/**
  * Makes a SSL connection to the specified host and port.  The caller
  * should keep track of the returned value and use it to cancel the
  * connection, if needed.
@@ -154,6 +176,7 @@
 
 /**
  * Makes a SSL connection using an already open file descriptor.
+ * DEPRECATED. Use purple_ssl_connect_with_host_fd instead.
  *
  * @param account    The account making the connection.
  * @param fd         The file descriptor.
@@ -166,7 +189,25 @@
 PurpleSslConnection *purple_ssl_connect_fd(PurpleAccount *account, int fd,
 									   PurpleSslInputFunction func,
 									   PurpleSslErrorFunction error_func,
-									   void *data);
+ 									   void *data);
+
+/**
+  * Makes a SSL connection using an already open file descriptor.
+  *
+  * @param account    The account making the connection.
+  * @param fd         The file descriptor.
+  * @param func       The SSL input handler function.
+  * @param error_func The SSL error handler function.
+  * @param host       The hostname of the other peer (to verify the CN)
+  * @param data       User-defined data.
+  *
+  * @return The SSL connection handle.
+  */
+PurpleSslConnection *purple_ssl_connect_with_host_fd(PurpleAccount *account, int fd,
+                                           PurpleSslInputFunction func,
+                                           PurpleSslErrorFunction error_func,
+                                           const char *host,
+                                           void *data);
 
 /**
  * Adds an input watcher for the specified SSL connection.
@@ -208,6 +249,16 @@
  */
 size_t purple_ssl_write(PurpleSslConnection *gsc, const void *buffer, size_t len);
 
+/**
+ * Obtains the peer's presented certificates
+ *
+ * @param gsc    The SSL connection handle
+ *
+ * @return The peer certificate chain, in the order of certificate, issuer,
+ *         issuer's issuer, etc. NULL if no certificates have been provided,
+ */
+GList * purple_ssl_get_peer_certificates(PurpleSslConnection *gsc);
+
 /*@}*/
 
 /**************************************************************************/
--- a/libpurple/util.c	Sat Aug 25 08:42:33 2007 +0000
+++ b/libpurple/util.c	Tue Aug 28 09:20:27 2007 +0000
@@ -2544,10 +2544,8 @@
 purple_util_write_data_to_file(const char *filename, const char *data, gssize size)
 {
 	const char *user_dir = purple_user_dir();
-	gchar *filename_temp, *filename_full;
-	FILE *file;
-	size_t real_size, byteswritten;
-	struct stat st;
+	gchar *filename_full;
+	gboolean ret = FALSE;
 
 	g_return_val_if_fail(user_dir != NULL, FALSE);
 
@@ -2566,6 +2564,25 @@
 	}
 
 	filename_full = g_strdup_printf("%s" G_DIR_SEPARATOR_S "%s", user_dir, filename);
+
+	ret = purple_util_write_data_to_file_absolute(filename_full,
+						      data,size);
+
+	g_free(filename_full);
+	return ret;
+}
+
+gboolean
+purple_util_write_data_to_file_absolute(const char *filename_full, const char *data, size_t size)
+{
+	gchar *filename_temp;
+	FILE *file;
+	size_t real_size, byteswritten;
+	struct stat st;
+
+	purple_debug_info("util", "Writing file %s\n",
+					filename_full);
+
 	filename_temp = g_strdup_printf("%s.save", filename_full);
 
 	/* Remove an old temporary file, if one exists */
@@ -2573,8 +2590,9 @@
 	{
 		if (g_unlink(filename_temp) == -1)
 		{
-			purple_debug_error("util", "Error removing old file %s: %s\n",
-							 filename_temp, strerror(errno));
+			purple_debug_error("util", "Error removing old file "
+					   "%s: %s\n",
+					   filename_temp, strerror(errno));
 		}
 	}
 
@@ -2582,9 +2600,9 @@
 	file = g_fopen(filename_temp, "wb");
 	if (file == NULL)
 	{
-		purple_debug_error("util", "Error opening file %s for writing: %s\n",
-						 filename_temp, strerror(errno));
-		g_free(filename_full);
+		purple_debug_error("util", "Error opening file %s for "
+				   "writing: %s\n",
+				   filename_temp, strerror(errno));
 		g_free(filename_temp);
 		return FALSE;
 	}
@@ -2597,8 +2615,7 @@
 	if (fclose(file) != 0)
 	{
 		purple_debug_error("util", "Error closing file %s: %s\n",
-						 filename_temp, strerror(errno));
-		g_free(filename_full);
+				   filename_temp, strerror(errno));
 		g_free(filename_temp);
 		return FALSE;
 	}
@@ -2606,10 +2623,11 @@
 	/* Ensure the file is the correct size */
 	if (byteswritten != real_size)
 	{
-		purple_debug_error("util", "Error writing to file %s: Wrote %" G_GSIZE_FORMAT " bytes "
-						 "but should have written %" G_GSIZE_FORMAT "; is your disk full?\n",
-						 filename_temp, byteswritten, real_size);
-		g_free(filename_full);
+		purple_debug_error("util", "Error writing to file %s: Wrote %"
+				   G_GSIZE_FORMAT " bytes "
+				   "but should have written %" G_GSIZE_FORMAT
+				   "; is your disk full?\n",
+				   filename_temp, byteswritten, real_size);
 		g_free(filename_temp);
 		return FALSE;
 	}
@@ -2617,9 +2635,9 @@
 	if ((g_stat(filename_temp, &st) == -1) || (st.st_size != real_size))
 	{
 		purple_debug_error("util", "Error writing data to file %s: "
-						 "Incomplete file written; is your disk full?\n",
-						 filename_temp);
-		g_free(filename_full);
+				   "Incomplete file written; is your disk "
+				   "full?\n",
+				   filename_temp);
 		g_free(filename_temp);
 		return FALSE;
 	}
@@ -2637,10 +2655,10 @@
 	if (g_rename(filename_temp, filename_full) == -1)
 	{
 		purple_debug_error("util", "Error renaming %s to %s: %s\n",
-						 filename_temp, filename_full, strerror(errno));
+				   filename_temp, filename_full,
+				   strerror(errno));
 	}
 
-	g_free(filename_full);
 	g_free(filename_temp);
 
 	return TRUE;
--- a/libpurple/util.h	Sat Aug 25 08:42:33 2007 +0000
+++ b/libpurple/util.h	Tue Aug 28 09:20:27 2007 +0000
@@ -589,6 +589,26 @@
 									  gssize size);
 
 /**
+ * Write data to a file using the absolute path.
+ *
+ * This exists for Glib backwards compatibility reasons.
+ *
+ * @param filename_full Filename to write to
+ * @param data          A null-terminated string of data to write.
+ * @param size          The size of the data to save.  If data is
+ *                      null-terminated you can pass in -1.
+ *
+ * @return TRUE if the file was written successfully.  FALSE otherwise.
+ *
+ * @todo Remove this function (use g_file_set_contents instead) when 3.0.0
+ *       rolls around.
+ * @see purple_util_write_data_to_file()
+ *
+ */
+gboolean
+purple_util_write_data_to_file_absolute(const char *filename_full, const char *data, size_t size);
+
+/**
  * Read the contents of a given file and parse the results into an
  * xmlnode tree structure.  This is intended to be used to read
  * Purple's configuration xml files (prefs.xml, pounces.xml, etc.)
--- a/libpurple/value.h	Sat Aug 25 08:42:33 2007 +0000
+++ b/libpurple/value.h	Tue Aug 28 09:20:27 2007 +0000
@@ -77,7 +77,8 @@
 	PURPLE_SUBTYPE_SAVEDSTATUS,
 	PURPLE_SUBTYPE_XMLNODE,
 	PURPLE_SUBTYPE_USERINFO,
-	PURPLE_SUBTYPE_STORED_IMAGE
+	PURPLE_SUBTYPE_STORED_IMAGE,
+	PURPLE_SUBTYPE_CERTIFICATEPOOL
 } PurpleSubType;
 
 /**
--- a/pidgin/Makefile.am	Sat Aug 25 08:42:33 2007 +0000
+++ b/pidgin/Makefile.am	Tue Aug 28 09:20:27 2007 +0000
@@ -81,6 +81,7 @@
 	gtkcellrendererprogress.c \
 	gtkcellview.c \
 	gtkcellviewmenuitem.c \
+	gtkcertmgr.c \
 	gtkconn.c \
 	gtkconv.c \
 	gtkdebug.c \
@@ -128,6 +129,7 @@
 	gtkcellviewmenuitem.h \
 	gtkcellview.h \
 	gtkcellviewmenuitem.h \
+	gtkcertmgr.h \
 	pidgincombobox.h \
 	gtkconn.h \
 	gtkconv.h \
--- a/pidgin/Makefile.mingw	Sat Aug 25 08:42:33 2007 +0000
+++ b/pidgin/Makefile.mingw	Tue Aug 28 09:20:27 2007 +0000
@@ -56,10 +56,11 @@
 PIDGIN_C_SRC =	\
 			gtkaccount.c \
 			gtkblist.c \
+			gtkcertmgr.c \
+			gtkcellrendererexpander.c \
+			gtkcellrendererprogress.c \
 			gtkconn.c \
 			gtkconv.c \
-			gtkcellrendererexpander.c \
-			gtkcellrendererprogress.c \
 			gtkdebug.c \
 			gtkdialogs.c \
 			gtkdnd-hints.c \
--- a/pidgin/gtkaccount.c	Sat Aug 25 08:42:33 2007 +0000
+++ b/pidgin/gtkaccount.c	Tue Aug 28 09:20:27 2007 +0000
@@ -136,9 +136,6 @@
 	GtkWidget *proxy_user_entry;
 	GtkWidget *proxy_pass_entry;
 
-	/* Are we registering? */
-	gboolean   registering;
-
 } AccountPrefsDialog;
 
 typedef struct
@@ -1147,7 +1144,7 @@
 	account_win_destroy_cb(NULL, NULL, dialog);
 }
 
-static PurpleAccount*
+static void
 ok_account_prefs_cb(GtkWidget *w, AccountPrefsDialog *dialog)
 {
 	PurpleProxyInfo *proxy_info = NULL;
@@ -1155,22 +1152,58 @@
 	const char *value;
 	char *username;
 	char *tmp;
-	gboolean new = FALSE, icon_change = FALSE;
+	gboolean new_acct = FALSE, icon_change = FALSE;
 	PurpleAccount *account;
 	PurplePluginProtocolInfo *prpl_info;
 
+	/* Build the username string. */
+	username = g_strdup(gtk_entry_get_text(GTK_ENTRY(dialog->screenname_entry)));
+
+	if (dialog->prpl_info != NULL)
+	{
+		for (l = dialog->prpl_info->user_splits,
+			 l2 = dialog->user_split_entries;
+			 l != NULL && l2 != NULL;
+			 l = l->next, l2 = l2->next)
+		{
+			PurpleAccountUserSplit *split = l->data;
+			GtkEntry *entry = l2->data;
+			char sep[2] = " ";
+
+			value = gtk_entry_get_text(entry);
+
+			*sep = purple_account_user_split_get_separator(split);
+
+			tmp = g_strconcat(username, sep,
+					(*value ? value :
+					 purple_account_user_split_get_default_value(split)),
+					NULL);
+
+			g_free(username);
+			username = tmp;
+		}
+	}
+
 	if (dialog->account == NULL)
 	{
-		const char *screenname;
+		if (purple_accounts_find(username, dialog->protocol_id) != NULL) {
+			purple_debug_warning("gtkaccount", "Trying to add a duplicate %s account (%s).\n",
+				dialog->protocol_id, username);
+
+			purple_notify_error(NULL, NULL, _("Unable to save new account"),
+				_("An account already exists with the specified criteria."));
+
+			g_free(username);
+			return;
+		}
 
 		if (purple_accounts_get_all() == NULL) {
 			/* We're adding our first account.  Be polite and show the buddy list */
 			purple_blist_set_visible(TRUE);
 		}
 
-		screenname = gtk_entry_get_text(GTK_ENTRY(dialog->screenname_entry));
-		account = purple_account_new(screenname, dialog->protocol_id);
-		new = TRUE;
+		account = purple_account_new(username, dialog->protocol_id);
+		new_acct = TRUE;
 	}
 	else
 	{
@@ -1194,7 +1227,7 @@
 	{
 		const char *filename;
 
-		if (new || purple_account_get_bool(account, "use-global-buddyicon", TRUE) ==
+		if (new_acct || purple_account_get_bool(account, "use-global-buddyicon", TRUE) ==
 			gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(dialog->icon_check)))
 		{
 			icon_change = TRUE;
@@ -1247,40 +1280,11 @@
 	 * the account editor (but has not checked the 'save' box), then we
 	 * don't want to prompt them.
 	 */
-	if ((purple_account_get_remember_password(account) || new) && (*value != '\0'))
+	if ((purple_account_get_remember_password(account) || new_acct) && (*value != '\0'))
 		purple_account_set_password(account, value);
 	else
 		purple_account_set_password(account, NULL);
 
-	/* Build the username string. */
-	username =
-		g_strdup(gtk_entry_get_text(GTK_ENTRY(dialog->screenname_entry)));
-
-	if (dialog->prpl_info != NULL)
-	{
-		for (l = dialog->prpl_info->user_splits,
-			 l2 = dialog->user_split_entries;
-			 l != NULL && l2 != NULL;
-			 l = l->next, l2 = l2->next)
-		{
-			PurpleAccountUserSplit *split = l->data;
-			GtkEntry *entry = l2->data;
-			char sep[2] = " ";
-
-			value = gtk_entry_get_text(entry);
-
-			*sep = purple_account_user_split_get_separator(split);
-
-			tmp = g_strconcat(username, sep,
-					(*value ? value :
-					 purple_account_user_split_get_default_value(split)),
-					NULL);
-
-			g_free(username);
-			username = tmp;
-		}
-	}
-
 	purple_account_set_username(account, username);
 	g_free(username);
 
@@ -1389,7 +1393,7 @@
 	}
 
 	/* If this is a new account, add it to our list */
-	if (new)
+	if (new_acct)
 		purple_accounts_add(account);
 	else
 		purple_signal_emit(pidgin_account_get_handle(), "account-modified", account);
@@ -1397,7 +1401,7 @@
 	/* If this is a new account, then sign on! */
 	if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(dialog->register_button))) {
 		purple_account_register(account);
-	} else if (new) {
+	} else if (new_acct) {
 		const PurpleSavedStatus *saved_status;
 
 		saved_status = purple_savedstatus_get_current();
@@ -1410,7 +1414,6 @@
 	/* We no longer need the data from the dialog window */
 	account_win_destroy_cb(NULL, NULL, dialog);
 
-	return account;
 }
 
 static const GtkTargetEntry dnd_targets[] = {
--- a/pidgin/gtkblist.c	Sat Aug 25 08:42:33 2007 +0000
+++ b/pidgin/gtkblist.c	Tue Aug 28 09:20:27 2007 +0000
@@ -42,6 +42,7 @@
 #include "gtkaccount.h"
 #include "gtkblist.h"
 #include "gtkcellrendererexpander.h"
+#include "gtkcertmgr.h"
 #include "gtkconv.h"
 #include "gtkdebug.h"
 #include "gtkdialogs.h"
@@ -1538,6 +1539,12 @@
 	pidgin_clear_cursor(gtkblist->window);
 }
 
+static void pidgin_blist_show_protocol_icons_cb(gpointer data, guint action, GtkWidget *item)
+{
+	purple_prefs_set_bool(PIDGIN_PREFS_ROOT "/blist/show_protocol_icons",
+			      gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(item)));
+}
+
 static void pidgin_blist_show_empty_groups_cb(gpointer data, guint action, GtkWidget *item)
 {
 	pidgin_set_cursor(gtkblist->window, GDK_WATCH);
@@ -2855,10 +2862,12 @@
 	{ N_("/Buddies/Get User _Info..."), "<CTL>I", pidgin_dialogs_info, 0, "<StockItem>", PIDGIN_STOCK_TOOLBAR_USER_INFO },
 	{ N_("/Buddies/View User _Log..."), "<CTL>L", pidgin_dialogs_log, 0, "<Item>", NULL },
 	{ "/Buddies/sep1", NULL, NULL, 0, "<Separator>", NULL },
-	{ N_("/Buddies/Show _Offline Buddies"), NULL, pidgin_blist_edit_mode_cb, 1, "<CheckItem>", NULL },
-	{ N_("/Buddies/Show _Empty Groups"), NULL, pidgin_blist_show_empty_groups_cb, 1, "<CheckItem>", NULL },
-	{ N_("/Buddies/Show Buddy _Details"), NULL, pidgin_blist_buddy_details_cb, 1, "<CheckItem>", NULL },
-	{ N_("/Buddies/Show Idle _Times"), NULL, pidgin_blist_show_idle_time_cb, 1, "<CheckItem>", NULL },
+	{ N_("/Buddies/Show"), NULL, NULL, 0, "<Branch>", NULL},
+	{ N_("/Buddies/Show/Show _Offline Buddies"), NULL, pidgin_blist_edit_mode_cb, 1, "<CheckItem>", NULL },
+	{ N_("/Buddies/Show/Show _Empty Groups"), NULL, pidgin_blist_show_empty_groups_cb, 1, "<CheckItem>", NULL },
+	{ N_("/Buddies/Show/Show Buddy _Details"), NULL, pidgin_blist_buddy_details_cb, 1, "<CheckItem>", NULL },
+	{ N_("/Buddies/Show/Show Idle _Times"), NULL, pidgin_blist_show_idle_time_cb, 1, "<CheckItem>", NULL },
+	{ N_("/Buddies/Show/Show _Protocol Icons"), NULL, pidgin_blist_show_protocol_icons_cb, 1, "<CheckItem>", NULL },
 	{ N_("/Buddies/_Sort Buddies"), NULL, NULL, 0, "<Branch>", NULL },
 	{ "/Buddies/sep2", NULL, NULL, 0, "<Separator>", NULL },
 	{ N_("/Buddies/_Add Buddy..."), "<CTL>B", pidgin_blist_add_buddy_cb, 0, "<StockItem>", GTK_STOCK_ADD },
@@ -2874,6 +2883,7 @@
 	/* Tools */
 	{ N_("/_Tools"), NULL, NULL, 0, "<Branch>", NULL },
 	{ N_("/Tools/Buddy _Pounces"), NULL, pidgin_pounces_manager_show, 0, "<Item>", NULL },
+	{ N_("/Tools/_Certificates"), NULL, pidgin_certmgr_show, 0, "<Item>", NULL },
 	{ N_("/Tools/Plu_gins"), "<CTL>U", pidgin_plugin_dialog_show, 0, "<StockItem>", PIDGIN_STOCK_TOOLBAR_PLUGINS },
 	{ N_("/Tools/Pr_eferences"), "<CTL>P", pidgin_prefs_show, 0, "<StockItem>", GTK_STOCK_PREFERENCES },
 	{ N_("/Tools/Pr_ivacy"), NULL, pidgin_privacy_dialog_show, 0, "<Item>", NULL },
@@ -3143,10 +3153,11 @@
 			return ret;
 		}
 
-		if (((struct _pidgin_blist_node*)(node->parent->ui_data))->contact_expanded)
+		if (((struct _pidgin_blist_node*)(node->parent->ui_data))->contact_expanded) {
+			if (purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/show_protocol_icons"))
+				return NULL;
 			return pidgin_create_prpl_icon(((PurpleBuddy*)node)->account, PIDGIN_PRPL_ICON_SMALL);
-	} else if(PURPLE_BLIST_NODE_IS_CHAT(node)) {
-		return pidgin_create_prpl_icon(((PurpleChat*)node)->account, PIDGIN_PRPL_ICON_SMALL);
+		}
 	} else {
 		return NULL;
 	}
@@ -4405,7 +4416,10 @@
 						 G_TYPE_BOOLEAN,  /* Contact expander */
 						 G_TYPE_BOOLEAN,  /* Contact expander visible */
 						 GDK_TYPE_PIXBUF, /* Emblem */
-						 G_TYPE_BOOLEAN); /* Emblem visible */
+						 G_TYPE_BOOLEAN,  /* Emblem visible */
+						 GDK_TYPE_PIXBUF, /* Protocol icon */
+						 G_TYPE_BOOLEAN   /* Protocol visible */
+						);
 
 	gtkblist->treeview = gtk_tree_view_new_with_model(GTK_TREE_MODEL(gtkblist->treemodel));
 
@@ -4520,6 +4534,17 @@
 							  "visible", EMBLEM_VISIBLE_COLUMN, NULL);
 
 	rend = gtk_cell_renderer_pixbuf_new();
+	gtk_tree_view_column_pack_start(column, rend, FALSE);	
+	gtk_tree_view_column_set_attributes(column, rend,
+					   "pixbuf", PROTOCOL_ICON_COLUMN,
+					   "visible", PROTOCOL_ICON_VISIBLE_COLUMN,
+#if GTK_CHECK_VERSION(2,6,0)
+					   "cell-background-gdk", BGCOLOR_COLUMN,
+#endif
+					  NULL);
+	g_object_set(rend, "xalign", 0.0, "xpad", 3, "ypad", 0, NULL);
+
+	rend = gtk_cell_renderer_pixbuf_new();
 	g_object_set(rend, "xalign", 1.0, "ypad", 0, NULL);
 	gtk_tree_view_column_pack_start(column, rend, FALSE);
 	gtk_tree_view_column_set_attributes(column, rend, "pixbuf", BUDDY_ICON_COLUMN,
@@ -4565,21 +4590,24 @@
 	/* set the Show Offline Buddies option. must be done
 	 * after the treeview or faceprint gets mad. -Robot101
 	 */
-	gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(gtk_item_factory_get_item (gtkblist->ift, N_("/Buddies/Show Offline Buddies"))),
+	gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(gtk_item_factory_get_item (gtkblist->ift, N_("/Buddies/Show/Show Offline Buddies"))),
 			purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/show_offline_buddies"));
 
-	gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(gtk_item_factory_get_item (gtkblist->ift, N_("/Buddies/Show Empty Groups"))),
+	gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(gtk_item_factory_get_item (gtkblist->ift, N_("/Buddies/Show/Show Empty Groups"))),
 			purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/show_empty_groups"));
 
 	gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(gtk_item_factory_get_item (gtkblist->ift, N_("/Tools/Mute Sounds"))),
 			purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/sound/mute"));
 
-	gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(gtk_item_factory_get_item (gtkblist->ift, N_("/Buddies/Show Buddy Details"))),
+	gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(gtk_item_factory_get_item (gtkblist->ift, N_("/Buddies/Show/Show Buddy Details"))),
 			purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/show_buddy_icons"));
 
-	gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(gtk_item_factory_get_item (gtkblist->ift, N_("/Buddies/Show Idle Times"))),
+	gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(gtk_item_factory_get_item (gtkblist->ift, N_("/Buddies/Show/Show Idle Times"))),
 			purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/show_idle_time"));
 
+	gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(gtk_item_factory_get_item (gtkblist->ift, N_("/Buddies/Show/Show Protocol Icons"))),
+			purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/show_protocol_icons"));
+
 	if(!strcmp(purple_prefs_get_string(PIDGIN_PREFS_ROOT "/sound/method"), "none"))
 		gtk_widget_set_sensitive(gtk_item_factory_get_widget(gtkblist->ift, N_("/Tools/Mute Sounds")), FALSE);
 
@@ -4609,6 +4637,8 @@
 			_prefs_change_redo_list, NULL);
 	purple_prefs_connect_callback(handle, PIDGIN_PREFS_ROOT "/blist/show_offline_buddies",
 			_prefs_change_redo_list, NULL);
+	purple_prefs_connect_callback(handle, PIDGIN_PREFS_ROOT "/blist/show_protocol_icons",
+			_prefs_change_redo_list, NULL);
 
 	/* sorting */
 	purple_prefs_connect_callback(handle, PIDGIN_PREFS_ROOT "/blist/sort_type",
@@ -5040,6 +5070,8 @@
 			   BUDDY_ICON_VISIBLE_COLUMN, biglist,
 			   EMBLEM_COLUMN, emblem,
 			   EMBLEM_VISIBLE_COLUMN, emblem,
+			   PROTOCOL_ICON_COLUMN, pidgin_create_prpl_icon(buddy->account, PIDGIN_PRPL_ICON_SMALL),
+			   PROTOCOL_ICON_VISIBLE_COLUMN, purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/show_protocol_icons"),
 			   BGCOLOR_COLUMN, NULL,
 			   CONTACT_EXPANDER_COLUMN, NULL,
 			   CONTACT_EXPANDER_VISIBLE_COLUMN, expanded,
@@ -5200,6 +5232,8 @@
 				BUDDY_ICON_VISIBLE_COLUMN,  purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/show_buddy_icons"),
 			        EMBLEM_COLUMN, emblem,
 				EMBLEM_VISIBLE_COLUMN, emblem != NULL,
+		 	        PROTOCOL_ICON_COLUMN, pidgin_create_prpl_icon(chat->account, PIDGIN_PRPL_ICON_SMALL),
+				PROTOCOL_ICON_VISIBLE_COLUMN, purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/show_protocol_icons"),
 				NAME_COLUMN, mark,
 				GROUP_EXPANDER_VISIBLE_COLUMN, FALSE,
 				-1);
@@ -6093,6 +6127,7 @@
 	purple_prefs_add_bool(PIDGIN_PREFS_ROOT "/blist/show_empty_groups", FALSE);
 	purple_prefs_add_bool(PIDGIN_PREFS_ROOT "/blist/show_idle_time", TRUE);
 	purple_prefs_add_bool(PIDGIN_PREFS_ROOT "/blist/show_offline_buddies", FALSE);
+	purple_prefs_add_bool(PIDGIN_PREFS_ROOT "/blist/show_protocol_icons", FALSE);
 	purple_prefs_add_bool(PIDGIN_PREFS_ROOT "/blist/list_visible", FALSE);
 	purple_prefs_add_bool(PIDGIN_PREFS_ROOT "/blist/list_maximized", FALSE);
 	purple_prefs_add_string(PIDGIN_PREFS_ROOT "/blist/sort_type", "alphabetical");
--- a/pidgin/gtkblist.h	Sat Aug 25 08:42:33 2007 +0000
+++ b/pidgin/gtkblist.h	Tue Aug 28 09:20:27 2007 +0000
@@ -43,6 +43,8 @@
 	CONTACT_EXPANDER_VISIBLE_COLUMN,
 	EMBLEM_COLUMN,
 	EMBLEM_VISIBLE_COLUMN,
+	PROTOCOL_ICON_COLUMN,
+	PROTOCOL_ICON_VISIBLE_COLUMN,
 	BLIST_COLUMNS
 
 };
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pidgin/gtkcertmgr.c	Tue Aug 28 09:20:27 2007 +0000
@@ -0,0 +1,686 @@
+/*
+ * @file gtkcertmgr.c GTK+ Certificate Manager API
+ * @ingroup pidgin
+ *
+ * pidgin
+ *
+ * Pidgin 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ */
+
+#include <glib.h>
+
+#include "core.h"
+#include "internal.h"
+#include "pidgin.h"
+#include "pidginstock.h"
+
+#include "certificate.h"
+#include "debug.h"
+#include "notify.h"
+#include "request.h"
+
+#include "gtkblist.h"
+#include "gtkutils.h"
+
+#include "gtkcertmgr.h"
+
+/*****************************************************************************
+ * X.509 tls_peers management interface                                      *
+ *****************************************************************************/
+
+typedef struct {
+	GtkWidget *mgmt_widget;
+	GtkTreeView *listview;
+	GtkTreeSelection *listselect;
+	GtkWidget *importbutton;
+	GtkWidget *exportbutton;
+	GtkWidget *infobutton;
+	GtkWidget *deletebutton;
+	PurpleCertificatePool *tls_peers;
+} tls_peers_mgmt_data;
+
+tls_peers_mgmt_data *tpm_dat = NULL;
+
+/* Columns
+   See http://developer.gnome.org/doc/API/2.0/gtk/TreeWidget.html */
+enum
+{
+	TPM_HOSTNAME_COLUMN,
+	TPM_N_COLUMNS
+};
+
+static void
+tls_peers_mgmt_destroy(GtkWidget *mgmt_widget, gpointer data)
+{
+	purple_debug_info("certmgr",
+			  "tls peers self-destructs\n");
+
+	purple_signals_disconnect_by_handle(tpm_dat);
+	purple_request_close_with_handle(tpm_dat);
+	g_free(tpm_dat); tpm_dat = NULL;
+}
+
+static void
+tls_peers_mgmt_repopulate_list(void)
+{
+	GtkTreeView *listview = tpm_dat->listview;
+	PurpleCertificatePool *tls_peers;
+	GList *idlist, *l;
+	
+	GtkListStore *store = GTK_LIST_STORE(
+		gtk_tree_view_get_model(GTK_TREE_VIEW(listview)));
+	
+	/* First, delete everything in the list */
+	gtk_list_store_clear(store);
+
+	/* Locate the "tls_peers" pool */
+	tls_peers = purple_certificate_find_pool("x509", "tls_peers");
+	g_return_if_fail(tls_peers);
+
+	/* Grab the loaded certificates */
+	idlist = purple_certificate_pool_get_idlist(tls_peers);
+
+	/* Populate the listview */
+	for (l = idlist; l; l = l->next) {
+		GtkTreeIter iter;
+		gtk_list_store_append(store, &iter);
+
+		gtk_list_store_set(GTK_LIST_STORE(store), &iter,
+				   TPM_HOSTNAME_COLUMN, l->data,
+				   -1);
+	}
+	purple_certificate_pool_destroy_idlist(idlist);
+}
+
+static void
+tls_peers_mgmt_mod_cb(PurpleCertificatePool *pool, const gchar *id, gpointer data)
+{
+	g_assert (pool == tpm_dat->tls_peers);
+
+	tls_peers_mgmt_repopulate_list();
+}
+
+static void
+tls_peers_mgmt_select_chg_cb(GtkTreeSelection *ignored, gpointer data)
+{
+	GtkTreeSelection *select = tpm_dat->listselect;
+	GtkTreeIter iter;
+	GtkTreeModel *model;
+
+	/* See if things are selected */
+	if (gtk_tree_selection_get_selected(select, &model, &iter)) {
+		/* Enable buttons if something is selected */
+		gtk_widget_set_sensitive(GTK_WIDGET(tpm_dat->exportbutton), TRUE);
+		gtk_widget_set_sensitive(GTK_WIDGET(tpm_dat->infobutton), TRUE);
+		gtk_widget_set_sensitive(GTK_WIDGET(tpm_dat->deletebutton), TRUE);
+	} else {
+		/* Otherwise, disable them */
+		gtk_widget_set_sensitive(GTK_WIDGET(tpm_dat->exportbutton), FALSE);
+		gtk_widget_set_sensitive(GTK_WIDGET(tpm_dat->infobutton), FALSE);
+		gtk_widget_set_sensitive(GTK_WIDGET(tpm_dat->deletebutton), FALSE);
+
+	}
+}
+
+static void
+tls_peers_mgmt_import_ok2_cb(gpointer data, const char *result)
+{
+	PurpleCertificate *crt = (PurpleCertificate *) data;
+	const char *id = result;
+
+	/* TODO: Perhaps prompt if you're overwriting a cert? */
+
+	/* Drop the certificate into the pool */
+	purple_certificate_pool_store(tpm_dat->tls_peers, id, crt);
+
+	/* And this certificate is not needed any more */
+	purple_certificate_destroy(crt);
+}
+
+static void
+tls_peers_mgmt_import_cancel2_cb(gpointer data, const char *result)
+{
+	PurpleCertificate *crt = (PurpleCertificate *) data;
+	purple_certificate_destroy(crt);
+}
+
+static void
+tls_peers_mgmt_import_ok_cb(gpointer data, const char *filename)
+{
+	PurpleCertificateScheme *x509;
+	PurpleCertificate *crt;
+
+	/* Load the scheme of our tls_peers pool (ought to be x509) */
+	x509 = purple_certificate_pool_get_scheme(tpm_dat->tls_peers);
+
+	/* Now load the certificate from disk */
+	crt = purple_certificate_import(x509, filename);
+
+	/* Did it work? */
+	if (crt != NULL) {
+		gchar *default_hostname;
+		/* Get name to add to pool as */
+		/* Make a guess about what the hostname should be */
+		 default_hostname = purple_certificate_get_subject_name(crt);
+		/* TODO: Find a way to make sure that crt gets destroyed
+		   if the window gets closed unusually, such as by handle
+		   deletion */
+		/* TODO: Display some more information on the certificate? */
+		purple_request_input(tpm_dat,
+				     _("Certificate Import"),
+				     _("Specify a hostname"),
+				     _("Type the host name this certificate is for."),
+				     default_hostname,
+				     FALSE, /* Not multiline */
+				     FALSE, /* Not masked? */
+				     NULL,  /* No hints? */
+				     _("OK"),
+				     G_CALLBACK(tls_peers_mgmt_import_ok2_cb),
+				     _("Cancel"),
+				     G_CALLBACK(tls_peers_mgmt_import_cancel2_cb),
+				     NULL, NULL, NULL, /* No account/who/conv*/
+				     crt    /* Pass cert instance to callback*/
+				     );
+		
+		g_free(default_hostname);
+	} else {
+		/* Errors! Oh no! */
+		/* TODO: Perhaps find a way to be specific about what just
+		   went wrong? */
+		gchar * secondary;
+
+		secondary = g_strdup_printf(_("File %s could not be imported.\nMake sure that the file is readable and in PEM format.\n"), filename);
+		purple_notify_error(NULL,
+				    _("Certificate Import Error"),
+				    _("X.509 certificate import failed"),
+				    secondary);
+		g_free(secondary);
+	}
+}
+
+static void
+tls_peers_mgmt_import_cb(GtkWidget *button, gpointer data)
+{
+	/* TODO: need to tell the user that we want a .PEM file! */
+	purple_request_file(tpm_dat,
+			    _("Select a PEM certificate"),
+			    "certificate.pem",
+			    FALSE, /* Not a save dialog */
+			    G_CALLBACK(tls_peers_mgmt_import_ok_cb),
+			    NULL,  /* Do nothing if cancelled */
+			    NULL, NULL, NULL, NULL );/* No account,conv,etc. */
+}
+
+static void
+tls_peers_mgmt_export_ok_cb(gpointer data, const char *filename)
+{
+	PurpleCertificate *crt = (PurpleCertificate *) data;
+
+	g_assert(filename);
+	
+	if (!purple_certificate_export(filename, crt)) {
+		/* Errors! Oh no! */
+		/* TODO: Perhaps find a way to be specific about what just
+		   went wrong? */
+		gchar * secondary;
+
+		secondary = g_strdup_printf(_("Export to file %s failed.\nCheck that you have write permission to the target path\n"), filename);
+		purple_notify_error(NULL,
+				    _("Certificate Export Error"),
+				    _("X.509 certificate export failed"),
+				    secondary);
+		g_free(secondary);
+	}
+
+	purple_certificate_destroy(crt);
+}
+
+static void
+tls_peers_mgmt_export_cancel_cb(gpointer data, const char *filename)
+{
+	PurpleCertificate *crt = (PurpleCertificate *) data;
+	/* Pressing cancel just frees the duplicated certificate */
+	purple_certificate_destroy(crt);
+}
+	
+static void
+tls_peers_mgmt_export_cb(GtkWidget *button, gpointer data)
+{
+	PurpleCertificate *crt;
+	GtkTreeSelection *select = tpm_dat->listselect;
+	GtkTreeIter iter;
+	GtkTreeModel *model;
+	gchar *id;
+
+	/* See if things are selected */
+	if (!gtk_tree_selection_get_selected(select, &model, &iter)) {
+		purple_debug_warning("gtkcertmgr/tls_peers_mgmt",
+				     "Export clicked with no selection?\n");
+		return;
+	}
+
+	/* Retrieve the selected hostname */
+	gtk_tree_model_get(model, &iter, TPM_HOSTNAME_COLUMN, &id, -1);
+
+	/* Extract the certificate from the pool now to make sure it doesn't
+	   get deleted out from under us */
+	crt = purple_certificate_pool_retrieve(tpm_dat->tls_peers, id);
+
+	if (NULL == crt) {
+		purple_debug_error("gtkcertmgr/tls_peers_mgmt",
+				   "Id %s was not in the peers cache?!\n",
+				   id);
+		g_free(id);
+		return;
+	}
+	g_free(id);
+
+	
+	/* TODO: inform user that it will be a PEM? */
+	purple_request_file(tpm_dat,
+			    _("PEM X.509 Certificate Export"),
+			    "certificate.pem",
+			    TRUE, /* Is a save dialog */
+			    G_CALLBACK(tls_peers_mgmt_export_ok_cb),
+			    G_CALLBACK(tls_peers_mgmt_export_cancel_cb),
+			    NULL, NULL, NULL, /* No account,conv,etc. */
+			    crt); /* Pass the certificate on to the callback */
+}
+
+static void
+tls_peers_mgmt_info_cb(GtkWidget *button, gpointer data)
+{
+	GtkTreeSelection *select = tpm_dat->listselect;
+	GtkTreeIter iter;
+	GtkTreeModel *model;
+	gchar *id;
+	PurpleCertificate *crt;
+	gchar *subject;
+	GByteArray *fpr_sha1;
+	gchar *fpr_sha1_asc;
+	gchar *primary, *secondary;
+
+	/* See if things are selected */
+	if (!gtk_tree_selection_get_selected(select, &model, &iter)) {
+		purple_debug_warning("gtkcertmgr/tls_peers_mgmt",
+				     "Info clicked with no selection?\n");
+		return;
+	}
+
+	/* Retrieve the selected hostname */
+	gtk_tree_model_get(model, &iter, TPM_HOSTNAME_COLUMN, &id, -1);
+
+	/* Now retrieve the certificate */
+	crt = purple_certificate_pool_retrieve(tpm_dat->tls_peers, id);
+	g_return_if_fail(crt);
+	
+	/* Build a notification thing */
+	/* TODO: This needs a better GUI, but a notification will do for now */
+	primary = g_strdup_printf(_("Certificate for %s"), id);
+
+	fpr_sha1 = purple_certificate_get_fingerprint_sha1(crt);
+	fpr_sha1_asc = purple_base16_encode_chunked(fpr_sha1->data,
+						    fpr_sha1->len);
+	subject = purple_certificate_get_subject_name(crt);
+
+	secondary = g_strdup_printf(_("Common name: %s\n\nSHA1 fingerprint:\n%s"), subject, fpr_sha1_asc);
+	
+	purple_notify_info(tpm_dat,
+			   _("SSL Host Certificate"),  primary, secondary );
+
+	g_free(primary);
+	g_free(secondary);
+	g_byte_array_free(fpr_sha1, TRUE);
+	g_free(fpr_sha1_asc);
+	g_free(subject);
+	g_free(id);
+	purple_certificate_destroy(crt);
+}
+
+static void
+tls_peers_mgmt_delete_confirm_cb(gchar *id, gint choice)
+{
+	if (1 == choice) {
+		/* Yes, delete was confirmed */
+		/* Now delete the thing */
+		if (!purple_certificate_pool_delete(tpm_dat->tls_peers, id)) {
+			purple_debug_warning("gtkcertmgr/tls_peers_mgmt",
+					     "Deletion failed on id %s\n",
+					     id);
+		};
+	}
+
+	g_free(id);
+}
+	
+static void
+tls_peers_mgmt_delete_cb(GtkWidget *button, gpointer data)
+{
+	GtkTreeSelection *select = tpm_dat->listselect;
+	GtkTreeIter iter;
+	GtkTreeModel *model;
+
+	/* See if things are selected */
+	if (gtk_tree_selection_get_selected(select, &model, &iter)) {
+
+		gchar *id;
+		gchar *primary;
+
+		/* Retrieve the selected hostname */
+		gtk_tree_model_get(model, &iter, TPM_HOSTNAME_COLUMN, &id, -1);
+
+		/* Prompt to confirm deletion */
+		primary = g_strdup_printf(
+			_("Really delete certificate for %s?"), id );
+		
+		purple_request_yes_no(tpm_dat, _("Confirm certificate delete"),
+				      primary, NULL, /* Can this be NULL? */
+				      2, /* NO is default action */
+				      NULL, NULL, NULL,
+				      id, /* id ownership passed to callback */
+				      tls_peers_mgmt_delete_confirm_cb,
+				      tls_peers_mgmt_delete_confirm_cb );
+		
+		g_free(primary);
+				      
+	} else {
+		purple_debug_warning("gtkcertmgr/tls_peers_mgmt",
+				     "Delete clicked with no selection?\n");
+		return;
+	}
+}
+
+static GtkWidget *
+tls_peers_mgmt_build(void)
+{
+	GtkWidget *bbox;
+	GtkListStore *store;
+
+	/* This block of variables will end up in tpm_dat */
+	GtkTreeView *listview;
+	GtkTreeSelection *select;
+	GtkWidget *importbutton;
+	GtkWidget *exportbutton;
+	GtkWidget *infobutton;
+	GtkWidget *deletebutton;
+	/** Element to return to the Certmgr window to put in the Notebook */
+	GtkWidget *mgmt_widget;
+
+	/* Create a struct to store context information about this window */
+	tpm_dat = g_new0(tls_peers_mgmt_data, 1);
+	
+	tpm_dat->mgmt_widget = mgmt_widget =
+		gtk_hbox_new(FALSE, /* Non-homogeneous */
+			     PIDGIN_HIG_BORDER);
+	gtk_widget_show(mgmt_widget);
+
+	/* Ensure that everything gets cleaned up when the dialog box
+	   is closed */
+	g_signal_connect(G_OBJECT(mgmt_widget), "destroy",
+			 G_CALLBACK(tls_peers_mgmt_destroy), NULL);
+
+	/* List view */
+	store = gtk_list_store_new(TPM_N_COLUMNS, G_TYPE_STRING);
+	
+	tpm_dat->listview = listview =
+		GTK_TREE_VIEW(gtk_tree_view_new_with_model(GTK_TREE_MODEL(store)));
+	
+	{
+		GtkCellRenderer *renderer;
+		GtkTreeViewColumn *column;
+
+		/* Set up the display columns */
+		renderer = gtk_cell_renderer_text_new();
+		column = gtk_tree_view_column_new_with_attributes(
+			"Hostname",
+			renderer,
+			"text", TPM_HOSTNAME_COLUMN,
+			NULL);
+		gtk_tree_view_append_column(GTK_TREE_VIEW(listview), column);
+	}
+	
+	/* Get the treeview selector into the struct */
+	tpm_dat->listselect = select =
+		gtk_tree_view_get_selection(GTK_TREE_VIEW(listview));
+	
+	/* Force the selection mode */
+	gtk_tree_selection_set_mode(select, GTK_SELECTION_SINGLE);
+
+	/* Use a callback to enable/disable the buttons based on whether
+	   something is selected */
+	g_signal_connect(G_OBJECT(select), "changed",
+			 G_CALLBACK(tls_peers_mgmt_select_chg_cb), NULL);
+	
+	gtk_box_pack_start(GTK_BOX(mgmt_widget), GTK_WIDGET(listview),
+			   TRUE, TRUE, /* Take up lots of space */
+			   0); /* TODO: this padding is wrong */
+	gtk_widget_show(GTK_WIDGET(listview));
+
+	/* Fill the list for the first time */
+	tls_peers_mgmt_repopulate_list();
+	
+	/* Right-hand side controls box */
+	bbox = gtk_vbutton_box_new();
+	gtk_box_pack_end(GTK_BOX(mgmt_widget), bbox,
+			 FALSE, FALSE, /* Do not take up space */
+			 0); /* TODO: this padding is probably wrong */
+	gtk_box_set_spacing(GTK_BOX(bbox), PIDGIN_HIG_BOX_SPACE);
+	gtk_button_box_set_layout(GTK_BUTTON_BOX(bbox), GTK_BUTTONBOX_START);
+	gtk_widget_show(bbox);
+
+	/* Import button */
+	/* TODO: This is the wrong stock button */
+	tpm_dat->importbutton = importbutton =
+		gtk_button_new_from_stock(GTK_STOCK_ADD);
+	gtk_box_pack_start(GTK_BOX(bbox), importbutton, FALSE, FALSE, 0);
+	gtk_widget_show(importbutton);
+	g_signal_connect(G_OBJECT(importbutton), "clicked",
+			 G_CALLBACK(tls_peers_mgmt_import_cb), NULL);
+
+
+	/* Export button */
+	/* TODO: This is the wrong stock button */
+	tpm_dat->exportbutton = exportbutton =
+		gtk_button_new_from_stock(GTK_STOCK_SAVE);
+	gtk_box_pack_start(GTK_BOX(bbox), exportbutton, FALSE, FALSE, 0);
+	gtk_widget_show(exportbutton);
+	g_signal_connect(G_OBJECT(exportbutton), "clicked",
+			 G_CALLBACK(tls_peers_mgmt_export_cb), NULL);
+
+
+	/* Info button */
+	tpm_dat->infobutton = infobutton =
+		gtk_button_new_from_stock(PIDGIN_STOCK_INFO);
+	gtk_box_pack_start(GTK_BOX(bbox), infobutton, FALSE, FALSE, 0);
+	gtk_widget_show(infobutton);
+	g_signal_connect(G_OBJECT(infobutton), "clicked",
+			 G_CALLBACK(tls_peers_mgmt_info_cb), NULL);
+
+
+	/* Delete button */
+	tpm_dat->deletebutton = deletebutton =
+		gtk_button_new_from_stock(GTK_STOCK_DELETE);
+	gtk_box_pack_start(GTK_BOX(bbox), deletebutton, FALSE, FALSE, 0);
+	gtk_widget_show(deletebutton);
+	g_signal_connect(G_OBJECT(deletebutton), "clicked",
+			 G_CALLBACK(tls_peers_mgmt_delete_cb), NULL);
+
+	/* Call the "selection changed" callback, which will probably disable
+	   all the buttons since nothing is selected yet */
+	tls_peers_mgmt_select_chg_cb(select, NULL);
+
+	/* Bind us to the tls_peers pool */
+	tpm_dat->tls_peers = purple_certificate_find_pool("x509", "tls_peers");
+	
+	/**** libpurple signals ****/
+	/* Respond to certificate add/remove by just reloading everything */
+	purple_signal_connect(tpm_dat->tls_peers, "certificate-stored",
+			      tpm_dat, PURPLE_CALLBACK(tls_peers_mgmt_mod_cb),
+			      NULL);
+	purple_signal_connect(tpm_dat->tls_peers, "certificate-deleted",
+			      tpm_dat, PURPLE_CALLBACK(tls_peers_mgmt_mod_cb),
+			      NULL);
+	
+	return mgmt_widget;
+}
+
+PidginCertificateManager tls_peers_mgmt = {
+	tls_peers_mgmt_build, /* Widget creation function */
+	N_("SSL Servers")
+};
+
+/*****************************************************************************
+ * GTK+ main certificate manager                                             *
+ *****************************************************************************/
+typedef struct
+{
+	GtkWidget *window;
+	GtkWidget *notebook;
+
+	GtkWidget *closebutton;
+} CertMgrDialog;
+
+/* If a certificate manager window is open, this will point to it.
+   So if it is set, don't open another one! */
+CertMgrDialog *certmgr_dialog = NULL;
+
+static void
+certmgr_close_cb(GtkWidget *w, CertMgrDialog *dlg)
+{
+	/* TODO: Ignoring the arguments to this function may not be ideal,
+	   but there *should* only be "one dialog to rule them all" at a time*/
+	pidgin_certmgr_hide();
+}
+
+void
+pidgin_certmgr_show(void)
+{
+	CertMgrDialog *dlg;
+	GtkWidget *win;
+	GtkWidget *vbox;
+	GtkWidget *bbox;
+
+	/* Enumerate all the certificates on file */
+	{
+		GList *idlist, *poollist;
+
+		for ( poollist = purple_certificate_get_pools();
+		      poollist;
+		      poollist = poollist->next ) {
+			PurpleCertificatePool *pool = poollist->data;
+			GList *l;
+			
+			purple_debug_info("gtkcertmgr",
+					  "Pool %s found for scheme %s -"
+					  "Enumerating certificates:\n",
+					  pool->name, pool->scheme_name);
+
+			idlist = purple_certificate_pool_get_idlist(pool);
+
+			for (l=idlist; l; l = l->next) {
+				purple_debug_info("gtkcertmgr",
+						  "- %s\n",
+						  (gchar *) l->data);
+			} /* idlist */
+			purple_certificate_pool_destroy_idlist(idlist);
+		} /* poollist */
+	}
+
+	
+	/* If the manager is already open, bring it to the front */
+	if (certmgr_dialog != NULL) {
+		gtk_window_present(GTK_WINDOW(certmgr_dialog->window));
+		return;
+	}
+
+	/* Create the dialog, and set certmgr_dialog so we never create
+	   more than one at a time */
+	dlg = certmgr_dialog = g_new0(CertMgrDialog, 1);
+
+	win = dlg->window =
+		pidgin_create_window(_("Certificate Manager"),/* Title */
+				     PIDGIN_HIG_BORDER, /*Window border*/
+				     "certmgr",         /* Role */
+				     TRUE); /* Allow resizing */
+	g_signal_connect(G_OBJECT(win), "delete_event",
+			 G_CALLBACK(certmgr_close_cb), dlg);
+
+	
+	/* TODO: Retrieve the user-set window size and use it */
+	gtk_window_set_default_size(GTK_WINDOW(win), 400, 400);
+
+	/* Main vbox */
+	vbox = gtk_vbox_new( FALSE, PIDGIN_HIG_BORDER );
+	gtk_container_add(GTK_CONTAINER(win), vbox);
+	gtk_widget_show(vbox);
+
+	/* Notebook of various certificate managers */
+	dlg->notebook = gtk_notebook_new();
+	gtk_box_pack_start(GTK_BOX(vbox), dlg->notebook,
+			   TRUE, TRUE, /* Notebook should take extra space */
+			   0);
+	gtk_widget_show(dlg->notebook);
+
+	/* Box for the close button */
+	bbox = gtk_hbutton_box_new();
+	gtk_box_set_spacing(GTK_BOX(bbox), PIDGIN_HIG_BOX_SPACE);
+	gtk_button_box_set_layout(GTK_BUTTON_BOX(bbox), GTK_BUTTONBOX_END);
+	gtk_box_pack_end(GTK_BOX(vbox), bbox, FALSE, TRUE, 0);
+	gtk_widget_show(bbox);
+
+	/* Close button */
+	dlg->closebutton = gtk_button_new_from_stock(GTK_STOCK_CLOSE);
+	gtk_box_pack_start(GTK_BOX(bbox), dlg->closebutton, FALSE, FALSE, 0);
+	gtk_widget_show(dlg->closebutton);
+	g_signal_connect(G_OBJECT(dlg->closebutton), "clicked",
+			 G_CALLBACK(certmgr_close_cb), dlg);
+
+	/* Add the defined certificate managers */
+	/* TODO: Find a way of determining whether each is shown or not */
+	/* TODO: Implement this correctly */
+	gtk_notebook_append_page(GTK_NOTEBOOK (dlg->notebook),
+				 (tls_peers_mgmt.build)(),
+				 gtk_label_new(_(tls_peers_mgmt.label)) );
+
+	gtk_widget_show(win);
+}
+
+void
+pidgin_certmgr_hide(void)
+{
+	/* If it isn't open, do nothing */
+	if (certmgr_dialog == NULL) {
+		return;
+	}
+
+	purple_signals_disconnect_by_handle(certmgr_dialog);
+	purple_prefs_disconnect_by_handle(certmgr_dialog);
+
+	gtk_widget_destroy(certmgr_dialog->window);
+	g_free(certmgr_dialog);
+	certmgr_dialog = NULL;
+
+	/* If this was the only window left, quit */
+	if (PIDGIN_BLIST(purple_get_blist())->window == NULL &&
+		purple_connections_get_all() == NULL) {
+
+		purple_core_quit();
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pidgin/gtkcertmgr.h	Tue Aug 28 09:20:27 2007 +0000
@@ -0,0 +1,62 @@
+/**
+ * @file gtkcertmgr.h GTK+ Certificate Manager API
+ * @ingroup pidgin
+ */
+/*
+ * pidgin
+ *
+ * Pidgin 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ */
+
+#ifndef _PIDGINCERTMGR_H_
+#define _PIDGINCERTMGR_H_
+
+/**************************************************************************
+ * @name Structures                                                       *
+ **************************************************************************/
+typedef struct _PidginCertificateManager PidginCertificateManager;
+
+/**
+ * GTK+ Certificate Manager subwidget
+ */
+struct _PidginCertificateManager {
+	/** Create, configure, show, and return the management interface */
+	GtkWidget * (* build)(void);
+	/** Notebook label to use in the CertMgr dialog */
+	gchar *label;
+};
+
+/**************************************************************************/
+/** @name Certificate Manager API                                         */
+/**************************************************************************/
+/*@{*/
+/**
+ * Show the certificate manager window
+ */
+void pidgin_certmgr_show(void);
+
+/**
+ * Hide the certificate manager window
+ */
+void pidgin_certmgr_hide(void);
+
+/*@}*/
+
+#endif /* _PIDGINCERTMGR_H_ */
--- a/pidgin/gtkconv.c	Sat Aug 25 08:42:33 2007 +0000
+++ b/pidgin/gtkconv.c	Tue Aug 28 09:20:27 2007 +0000
@@ -86,6 +86,7 @@
 	CONV_ICON_COLUMN,
 	CONV_TEXT_COLUMN,
 	CONV_EMBLEM_COLUMN,
+	CONV_PROTOCOL_ICON_COLUMN,
 	CONV_NUM_COLUMNS
 } PidginInfopaneColumns;
 
@@ -2275,7 +2276,7 @@
         const char *name = NULL;
         GdkPixbuf *status = NULL;
         PurpleBlistUiOps *ops = purple_blist_get_ui_ops();
-	const char *icon_size = small_icon ? "pidgin-icon-size-tango-microscopic" : PIDGIN_ICON_SIZE_TANGO_EXTRA_SMALL;
+	const char *icon_size = small_icon ? PIDGIN_ICON_SIZE_TANGO_MICROSCOPIC : PIDGIN_ICON_SIZE_TANGO_EXTRA_SMALL;
         g_return_val_if_fail(conv != NULL, NULL);
 
         account = purple_conversation_get_account(conv);
@@ -2365,6 +2366,12 @@
 			&(gtkconv->infopane_iter),
 			CONV_EMBLEM_COLUMN, emblem, -1);
 
+	if (purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/show_protocol_icons")) {
+		gtk_list_store_set(GTK_LIST_STORE(gtkconv->infopane_model),
+			&(gtkconv->infopane_iter),
+			CONV_PROTOCOL_ICON_COLUMN, pidgin_create_prpl_icon(gtkconv->active_conv->account, PIDGIN_PRPL_ICON_SMALL), -1);
+	}
+
 	/* XXX seanegan Why do I have to do this? */
 	gtk_widget_queue_draw(gtkconv->infopane);
 
@@ -4536,7 +4543,7 @@
 			G_CALLBACK(pidgin_conv_leave_cb), gtkconv);
 
 	gtkconv->infopane = gtk_cell_view_new();
-	gtkconv->infopane_model = gtk_list_store_new(CONV_NUM_COLUMNS, GDK_TYPE_PIXBUF, G_TYPE_STRING, GDK_TYPE_PIXBUF);
+	gtkconv->infopane_model = gtk_list_store_new(CONV_NUM_COLUMNS, GDK_TYPE_PIXBUF, G_TYPE_STRING, GDK_TYPE_PIXBUF, GDK_TYPE_PIXBUF);
 	gtk_cell_view_set_model(GTK_CELL_VIEW(gtkconv->infopane), 
 				GTK_TREE_MODEL(gtkconv->infopane_model));
 	gtk_list_store_append(gtkconv->infopane_model, &(gtkconv->infopane_iter));
@@ -4561,6 +4568,12 @@
 	g_object_set(rend, "ellipsize", PANGO_ELLIPSIZE_END, NULL);
 #endif
 
+
+	rend = gtk_cell_renderer_pixbuf_new();
+	gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(gtkconv->infopane), rend, FALSE);
+	gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(gtkconv->infopane), rend, "pixbuf", CONV_PROTOCOL_ICON_COLUMN, NULL);
+	g_object_set(rend, "xalign", 0.0, "xpad", 3, "ypad", 0, NULL);
+
 	rend = gtk_cell_renderer_pixbuf_new();
 	gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(gtkconv->infopane), rend, FALSE);
 	gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(gtkconv->infopane), rend, "pixbuf", CONV_EMBLEM_COLUMN, NULL);
@@ -6949,6 +6962,17 @@
 }
 
 static void
+show_protocol_icons_pref_cb(const char *name, PurplePrefType type,
+						gconstpointer value, gpointer data)
+{
+	GList *l;
+	for (l = purple_get_conversations(); l != NULL; l = l->next) {
+		PurpleConversation *conv = l->data;
+		update_tab_icon(conv);
+	}
+}
+
+static void
 conv_placement_usetabs_cb(const char *name, PurplePrefType type,
 						  gconstpointer value, gpointer data)
 {
@@ -7292,6 +7316,8 @@
 								animate_buddy_icons_pref_cb, NULL);
 	purple_prefs_connect_callback(handle, PIDGIN_PREFS_ROOT "/conversations/im/show_buddy_icons",
 								show_buddy_icons_pref_cb, NULL);
+	purple_prefs_connect_callback(handle, PIDGIN_PREFS_ROOT "/conversations/im/show_protocol_icons",
+								show_protocol_icons_pref_cb, NULL);
 	purple_prefs_connect_callback(handle, PIDGIN_PREFS_ROOT "/conversations/im/hide_new",
                                 hide_new_pref_cb, NULL);
 
--- a/pidgin/gtkdialogs.c	Sat Aug 25 08:42:33 2007 +0000
+++ b/pidgin/gtkdialogs.c	Tue Aug 28 09:20:27 2007 +0000
@@ -96,6 +96,9 @@
 static struct developer patch_writers[] = {
 	{"John 'rekkanoryo' Bailey",	NULL,	NULL},
 	{"Peter 'Bleeter' Lawler",      NULL,   NULL},
+        {"Dennis 'EvilDennisR' Ristuccia",	N_("Senior Contributor/QA"),	NULL},
+	{"Peter 'Fmoo' Ruibal",		NULL,	NULL},
+	{"Gabriel 'Nix' Schulhof", 	NULL, 	NULL},
 	{NULL, NULL, NULL}
 };
 
@@ -381,7 +384,7 @@
 		  "libpurple which is capable of connecting to "
 		  "AIM, MSN, Yahoo!, XMPP, ICQ, IRC, SILC, SIP/SIMPLE, "
 		  "Novell GroupWise, Lotus Sametime, Bonjour, Zephyr, "
-		  "Gadu-Gadu, and QQ all at once.  "
+		  "MySpaceIM, Gadu-Gadu, and QQ all at once.  "
 		  "It is written using GTK+.<BR><BR>"
 		  "You may modify and redistribute the program under "
 		  "the terms of the GPL (version 2 or later).  A copy of the GPL is "
--- a/pidgin/gtkimhtmltoolbar.c	Sat Aug 25 08:42:33 2007 +0000
+++ b/pidgin/gtkimhtmltoolbar.c	Tue Aug 28 09:20:27 2007 +0000
@@ -70,6 +70,14 @@
 }
 
 static void
+do_strikethrough(GtkWidget *strikethrough, GtkIMHtmlToolbar *toolbar)
+{
+	g_return_if_fail(toolbar != NULL);
+	gtk_imhtml_toggle_strike(GTK_IMHTML(toolbar->imhtml));
+	gtk_widget_grab_focus(toolbar->imhtml);
+}
+
+static void
 do_small(GtkWidget *smalltb, GtkIMHtmlToolbar *toolbar)
 {
 	g_return_if_fail(toolbar != NULL);
@@ -433,6 +441,17 @@
 	gtk_widget_grab_focus(toolbar->imhtml);
 }
 
+static void insert_hr_cb(GtkWidget *widget, GtkIMHtmlToolbar *toolbar)
+{
+        GtkTextIter iter;
+        GtkTextMark *ins;
+	GtkIMHtmlScalable *hr;
+
+        ins = gtk_text_buffer_get_insert(gtk_text_view_get_buffer(GTK_TEXT_VIEW(toolbar->imhtml)));
+        gtk_text_buffer_get_iter_at_mark(gtk_text_view_get_buffer(GTK_TEXT_VIEW(toolbar->imhtml)), &iter, ins);
+	hr = gtk_imhtml_hr_new();
+	gtk_imhtml_hr_add_to(hr, GTK_IMHTML(toolbar->imhtml), &iter);
+}
 
 static void
 do_insert_image_cb(GtkWidget *widget, int response, GtkIMHtmlToolbar *toolbar)
@@ -1027,6 +1046,13 @@
 			 G_CALLBACK(do_underline), toolbar);
 	toolbar->underline = button;
 
+
+	/* Strikethrough */
+	button = pidgin_pixbuf_toolbar_button_from_stock(GTK_STOCK_STRIKETHROUGH);
+	g_signal_connect(G_OBJECT(button), "clicked",
+			G_CALLBACK(do_strikethrough), toolbar);
+	toolbar->strikethrough = button;
+
 	/* Increase font size */
 	button = pidgin_pixbuf_toolbar_button_from_stock(PIDGIN_STOCK_TOOLBAR_TEXT_LARGER);
 	g_signal_connect(G_OBJECT(button), "clicked",
@@ -1124,6 +1150,7 @@
 		{_("<b>_Bold</b>"), &toolbar->bold, TRUE},
 		{_("<i>_Italic</i>"), &toolbar->italic, TRUE},
 		{_("<u>_Underline</u>"), &toolbar->underline, TRUE},
+		{_("<span strikethrough='true'>Strikethrough</span>"), &toolbar->strikethrough, TRUE},
 		{_("<span size='larger'>_Larger</span>"), &toolbar->larger_size, TRUE},
 #if 0
 		{_("_Normal"), &toolbar->normal_size, TRUE},
@@ -1230,6 +1257,11 @@
 	g_signal_connect(G_OBJECT(toolbar->link), "notify::sensitive",
 			G_CALLBACK(button_sensitiveness_changed), menuitem);
 
+	menuitem = gtk_menu_item_new_with_mnemonic(_("_Horizontal rule"));
+	g_signal_connect(G_OBJECT(menuitem), "activate"	, G_CALLBACK(insert_hr_cb), toolbar);
+	gtk_menu_shell_append(GTK_MENU_SHELL(insert_menu), menuitem);
+	toolbar->insert_hr = menuitem;	
+
 	g_signal_connect_swapped(G_OBJECT(insert_button), "button-press-event", G_CALLBACK(gtk_widget_activate), insert_button);
 	g_signal_connect(G_OBJECT(insert_button), "activate", G_CALLBACK(pidgin_menu_clicked), insert_menu);
 	g_signal_connect(G_OBJECT(insert_menu), "deactivate", G_CALLBACK(pidgin_menu_deactivate), insert_button);
--- a/pidgin/gtkimhtmltoolbar.h	Sat Aug 25 08:42:33 2007 +0000
+++ b/pidgin/gtkimhtmltoolbar.h	Tue Aug 28 09:20:27 2007 +0000
@@ -74,6 +74,8 @@
 	GtkWidget *image_dialog;
 
 	char *sml;
+	GtkWidget *strikethrough;
+	GtkWidget *insert_hr;
 };
 
 struct _GtkIMHtmlToolbarClass {
--- a/pidgin/gtkmain.c	Sat Aug 25 08:42:33 2007 +0000
+++ b/pidgin/gtkmain.c	Tue Aug 28 09:20:27 2007 +0000
@@ -770,22 +770,12 @@
 	purple_set_blist(purple_blist_new());
 	purple_blist_load();
 
-	/* TODO: Move prefs loading into purple_prefs_init() */
-	purple_prefs_load();
-	purple_prefs_update_old();
-	pidgin_prefs_update_old();
-
 	/* load plugins we had when we quit */
 	purple_plugins_load_saved(PIDGIN_PREFS_ROOT "/plugins/loaded");
 
 	/* TODO: Move pounces loading into purple_pounces_init() */
 	purple_pounces_load();
 
-	/* Call this early on to try to auto-detect our IP address and
-	 * hopefully save some time later.
-	 * TODO: move this (back) into purple_core_init() when purple_prefs_load() is in purple_prefs_init() */
-	 purple_network_get_my_ip(-1);
-
 	/* HACK BY SEANEGAN:
 	 * We've renamed prpl-oscar to prpl-aim and prpl-icq, accordingly.
 	 * Let's do that change right here... after everything's loaded, but
@@ -839,10 +829,7 @@
 			g_free(opt_login_arg);
 			opt_login_arg = NULL;
 		}
-	}
-
-	if (opt_nologin && !opt_login)
-	{
+	} else if (opt_nologin)	{
 		/* Set all accounts to "offline" */
 		PurpleSavedStatus *saved_status;
 
@@ -856,9 +843,7 @@
 
 		/* Set the status for each account */
 		purple_savedstatus_activate(saved_status);
-	}
-	else if (!opt_login)
-	{
+	} else {
 		/* Everything is good to go--sign on already */
 		if (!purple_prefs_get_bool("/purple/savedstatus/startup_current_status"))
 			purple_savedstatus_activate(purple_savedstatus_get_startup());
--- a/pidgin/gtkprefs.c	Sat Aug 25 08:42:33 2007 +0000
+++ b/pidgin/gtkprefs.c	Tue Aug 28 09:20:27 2007 +0000
@@ -887,7 +887,7 @@
 
 	ret = gtk_vbox_new(FALSE, PIDGIN_HIG_CAT_SPACE);
 	gtk_container_set_border_width(GTK_CONTAINER(ret), PIDGIN_HIG_BORDER);
-	
+
 	sg = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL);
 
 	vbox = pidgin_make_frame(ret, _("System Tray Icon"));
@@ -899,7 +899,7 @@
 					NULL);
 	gtk_size_group_add_widget(sg, label);
         gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.5);
-	
+
 	vbox = pidgin_make_frame(ret, _("Conversation Window Hiding"));
 	label = pidgin_prefs_dropdown(vbox, _("_Hide new IM conversations:"),
 					PURPLE_PREF_STRING, PIDGIN_PREFS_ROOT "/conversations/im/hide_new",
@@ -909,11 +909,11 @@
 					NULL);
 	gtk_size_group_add_widget(sg, label);
         gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.5);
-			
+
 
 	/* All the tab options! */
 	vbox = pidgin_make_frame(ret, _("Tabs"));
-	
+
 	pidgin_prefs_checkbox(_("Show IMs and chats in _tabbed windows"),
 							PIDGIN_PREFS_ROOT "/conversations/tabs", vbox);
 
@@ -947,12 +947,12 @@
 					NULL);
 	gtk_size_group_add_widget(sg, label);
         gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.5);
-		
+
 	names = pidgin_conv_placement_get_options();
 	label = pidgin_prefs_dropdown_from_list(vbox2, _("N_ew conversations:"),
 				PURPLE_PREF_STRING, PIDGIN_PREFS_ROOT "/conversations/placement", names);
 	gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.5);
-			
+
 	gtk_size_group_add_widget(sg, label);
 
 	g_list_free(names);
@@ -1623,7 +1623,7 @@
 		filename = NULL;
 
 	purple_request_file(prefs, _("Sound Selection"), filename, FALSE,
-					  G_CALLBACK(sound_chosen_cb), NULL, 
+					  G_CALLBACK(sound_chosen_cb), NULL,
 					  NULL, NULL, NULL,
 					  GINT_TO_POINTER(sound_row_sel));
 }
@@ -2013,7 +2013,7 @@
 	return ret;
 }
 
-static int 
+static int
 prefs_notebook_add_page(const char *text,
   		        GtkWidget *page,
 			int ind) {
@@ -2167,6 +2167,8 @@
 	/* Smiley Callbacks */
 	purple_prefs_connect_callback(prefs, PIDGIN_PREFS_ROOT "/smileys/theme",
 								smiley_theme_pref_cb, NULL);
+
+	pidgin_prefs_update_old();
 }
 
 void pidgin_prefs_update_old()
--- a/pidgin/pidgin.h	Sat Aug 25 08:42:33 2007 +0000
+++ b/pidgin/pidgin.h	Tue Aug 28 09:20:27 2007 +0000
@@ -26,7 +26,7 @@
 #ifndef _PIDGIN_H_
 #define _PIDGIN_H_
 
-#ifndef _WIN32
+#ifdef GDK_WINDOWING_X11
 # include <gdk/gdkx.h>
 #endif
 
--- a/pidgin/pidginstock.c	Sat Aug 25 08:42:33 2007 +0000
+++ b/pidgin/pidginstock.c	Tue Aug 28 09:20:27 2007 +0000
@@ -74,6 +74,11 @@
 	{ PIDGIN_STOCK_SIGN_OFF,        NULL,      GTK_STOCK_CLOSE            },
 	{ PIDGIN_STOCK_TYPED,           "pidgin",  "typed.png"                },
 	{ PIDGIN_STOCK_UPLOAD,          NULL,      GTK_STOCK_GO_UP            },
+#if GTK_CHECK_VERSION(2,8,0)
+	{ PIDGIN_STOCK_INFO,            NULL,      GTK_STOCK_INFO             },
+#else
+	{ PIDGIN_STOCK_INFO,            "buttons", "info.png"                 },
+#endif
 };
 
 static const GtkStockItem stock_items[] =
@@ -105,7 +110,7 @@
 	{ PIDGIN_STOCK_STATUS_AVAILABLE,   "status", "available.png", 	TRUE, TRUE, TRUE, TRUE, TRUE, FALSE, FALSE, PIDGIN_STOCK_STATUS_AVAILABLE_I },
 	{ PIDGIN_STOCK_STATUS_AWAY, 	   "status", "away.png",	TRUE, TRUE, TRUE, TRUE, TRUE, FALSE, FALSE, PIDGIN_STOCK_STATUS_AWAY_I },
 	{ PIDGIN_STOCK_STATUS_BUSY, 	"status", "busy.png", 		TRUE, TRUE, TRUE, TRUE, TRUE, FALSE, FALSE, PIDGIN_STOCK_STATUS_BUSY_I },
-	{ PIDGIN_STOCK_STATUS_CHAT, 	"status", "chat.png",		TRUE, TRUE, TRUE, TRUE, TRUE, FALSE, TRUE, NULL },
+	{ PIDGIN_STOCK_STATUS_CHAT, 	"status", "chat.png",		TRUE, TRUE, TRUE, TRUE, TRUE, FALSE, FALSE, NULL },
 	{ PIDGIN_STOCK_STATUS_INVISIBLE,"status", "invisible.png",	TRUE, TRUE, TRUE, TRUE, TRUE, FALSE, FALSE, NULL },
 	{ PIDGIN_STOCK_STATUS_XA, 	"status", "extended-away.png",	TRUE, TRUE, TRUE, TRUE, TRUE, FALSE, TRUE, PIDGIN_STOCK_STATUS_XA_I },
 	{ PIDGIN_STOCK_STATUS_LOGIN, 	"status", "log-in.png",		FALSE, TRUE, TRUE, TRUE, TRUE, FALSE, TRUE, NULL },
@@ -396,7 +401,7 @@
 
 	/* register custom icon sizes */
 	
-	microscopic =  gtk_icon_size_register("pidgin-icon-size-tango-microscopic", 11, 11);
+	microscopic =  gtk_icon_size_register(PIDGIN_ICON_SIZE_TANGO_MICROSCOPIC, 11, 11);
 	extra_small =  gtk_icon_size_register(PIDGIN_ICON_SIZE_TANGO_EXTRA_SMALL, 16, 16);
 	small =        gtk_icon_size_register(PIDGIN_ICON_SIZE_TANGO_SMALL, 22, 22);
 	medium =       gtk_icon_size_register(PIDGIN_ICON_SIZE_TANGO_MEDIUM, 32, 32);
--- a/pidgin/pidginstock.h	Sat Aug 25 08:42:33 2007 +0000
+++ b/pidgin/pidginstock.h	Tue Aug 28 09:20:27 2007 +0000
@@ -45,6 +45,7 @@
 #define PIDGIN_STOCK_FILE_CANCELED   "pidgin-file-canceled"
 #define PIDGIN_STOCK_FILE_DONE       "pidgin-file-done"
 #define PIDGIN_STOCK_IGNORE          "pidgin-ignore"
+#define PIDGIN_STOCK_INFO            "pidgin-info"
 #define PIDGIN_STOCK_INVITE          "pidgin-invite"
 #define PIDGIN_STOCK_MODIFY          "pidgin-modify"
 #define PIDGIN_STOCK_OPEN_MAIL       "pidgin-stock-open-mail"
@@ -145,6 +146,7 @@
 /**
  * For using icons that aren't one of the default GTK_ICON_SIZEs
  */
+#define PIDGIN_ICON_SIZE_TANGO_MICROSCOPIC    "pidgin-icon-size-tango-microscopic"
 #define PIDGIN_ICON_SIZE_TANGO_EXTRA_SMALL    "pidgin-icon-size-tango-extra-small"
 #define PIDGIN_ICON_SIZE_TANGO_SMALL          "pidgin-icon-size-tango-small"
 #define PIDGIN_ICON_SIZE_TANGO_MEDIUM         "pidgin-icon-size-tango-medium"
--- a/pidgin/pixmaps/Makefile.am	Sat Aug 25 08:42:33 2007 +0000
+++ b/pidgin/pixmaps/Makefile.am	Tue Aug 28 09:20:27 2007 +0000
@@ -2,6 +2,7 @@
 
 EXTRA_DIST = \
 		edit.png			\
+		info.png			\
 		logo.png			\
 		pause.png			\
 		arrow-down.xpm		\
@@ -12,7 +13,7 @@
 		pidgin.ico
 
 pidginbuttonpixdir = $(datadir)/pixmaps/pidgin/buttons
-pidginbuttonpix_DATA = edit.png pause.png 
+pidginbuttonpix_DATA = edit.png pause.png info.png
 
 pidgindistpixdir = $(datadir)/pixmaps/pidgin
 pidgindistpix_DATA = logo.png arrow-down.xpm arrow-left.xpm arrow-right.xpm arrow-up.xpm
Binary file pidgin/pixmaps/info.png has changed
--- a/pidgin/pixmaps/status/11/Makefile.am	Sat Aug 25 08:42:33 2007 +0000
+++ b/pidgin/pixmaps/status/11/Makefile.am	Tue Aug 28 09:20:27 2007 +0000
@@ -1,4 +1,4 @@
-SUBDIRS = scalable
+SUBDIRS = scalable rtl
 
 EXTRA_DIST = 	available.png \
 		away.png \
--- a/pidgin/pixmaps/status/11/Makefile.mingw	Sat Aug 25 08:42:33 2007 +0000
+++ b/pidgin/pixmaps/status/11/Makefile.mingw	Tue Aug 28 09:20:27 2007 +0000
@@ -18,3 +18,5 @@
 	  cp $(pidginstatuspix_DATA) $(pidginstatuspixdir); \
 	fi;
 
+	$(MAKE) -C rtl -f Makefile.mingw install || exit 1; \
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pidgin/pixmaps/status/11/rtl/Makefile.am	Tue Aug 28 09:20:27 2007 +0000
@@ -0,0 +1,6 @@
+EXTRA_DIST = 	extended-away.png
+
+pidginstatuspixdir = $(datadir)/pixmaps/pidgin/status/11/rtl
+
+pidginstatuspix_DATA = $(EXTRA_DIST)
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pidgin/pixmaps/status/11/rtl/Makefile.mingw	Tue Aug 28 09:20:27 2007 +0000
@@ -0,0 +1,20 @@
+#
+# Makefile.mingw
+#
+# Description: Makefile for win32 (mingw) version of Pidgin pixmaps
+#
+
+PIDGIN_TREE_TOP := ../../../../..
+include $(PIDGIN_TREE_TOP)/libpurple/win32/global.mak
+
+datadir = $(PIDGIN_INSTALL_DIR)
+include ./Makefile.am
+
+.PHONY: install
+
+install:
+	if test '$(pidginstatuspix_DATA)'; then \
+	  mkdir -p $(pidginstatuspixdir); \
+	  cp $(pidginstatuspix_DATA) $(pidginstatuspixdir); \
+	fi;
+
Binary file pidgin/pixmaps/status/11/rtl/extended-away.png has changed
--- a/pidgin/plugins/Makefile.am	Sat Aug 25 08:42:33 2007 +0000
+++ b/pidgin/plugins/Makefile.am	Tue Aug 28 09:20:27 2007 +0000
@@ -16,9 +16,13 @@
 PERL_DIR = perl
 endif
 
+if ENABLE_GESTURES
+GESTURE_DIR = gestures
+endif
+
 SUBDIRS = \
 	$(CAP_DIR) \
-	gestures \
+	$(GESTURE_DIR) \
 	$(GEVOLUTION_DIR) \
 	$(MUSICMESSAGING_DIR) \
 	$(PERL_DIR) \
--- a/pidgin/plugins/win32/transparency/win2ktrans.c	Sat Aug 25 08:42:33 2007 +0000
+++ b/pidgin/plugins/win32/transparency/win2ktrans.c	Tue Aug 28 09:20:27 2007 +0000
@@ -400,7 +400,7 @@
 
 		g_object_get(G_OBJECT(window), "has-toplevel-focus", &has_focus, NULL);
 
-		if (!has_focus)
+		if (!has_focus || !purple_prefs_get_bool(OPT_WINTRANS_IM_ONFOCUS))
 			set_conv_window_trans(NULL, win);
 
 		if (g_signal_handler_find(G_OBJECT(window), G_SIGNAL_MATCH_FUNC,
--- a/pidgin/win32/nsis/pidgin-installer.nsi	Sat Aug 25 08:42:33 2007 +0000
+++ b/pidgin/win32/nsis/pidgin-installer.nsi	Tue Aug 28 09:20:27 2007 +0000
@@ -554,6 +554,10 @@
     Push "msnim"
     Call RegisterURIHandler
   SectionEnd
+  Section /o "myim:" SecURI_MYIM
+    Push "myim"
+    Call RegisterURIHandler
+  SectionEnd
   Section /o "ymsgr:" SecURI_YMSGR
     Push "ymsgr"
     Call RegisterURIHandler
@@ -688,6 +692,9 @@
     DeleteRegValue HKLM "${STARTUP_RUN_KEY}" "Pidgin"
     ; Remove Language preference info (TODO: check if NSIS removes this)
 
+    Delete "$INSTDIR\ca-certs\Equifax_Secure_CA.pem"
+    Delete "$INSTDIR\ca-certs\Verisign_RSA_Secure_Server_CA.pem"
+    RMDir "$INSTDIR\ca-certs"
     RMDir /r "$INSTDIR\locale"
     RMDir /r "$INSTDIR\pixmaps"
     RMDir /r "$INSTDIR\perlmod"
@@ -706,6 +713,7 @@
     Delete "$INSTDIR\plugins\libicq.dll"
     Delete "$INSTDIR\plugins\libirc.dll"
     Delete "$INSTDIR\plugins\libmsn.dll"
+    Delete "$INSTDIR\plugins\libmyspace.dll"
     Delete "$INSTDIR\plugins\libnapster.dll"
     Delete "$INSTDIR\plugins\libnovell.dll"
     Delete "$INSTDIR\plugins\libqq.dll"
@@ -758,6 +766,7 @@
     Delete "$INSTDIR\plds4.dll"
     Delete "$INSTDIR\silc.dll"
     Delete "$INSTDIR\silcclient.dll"
+    Delete "$INSTDIR\smime3.dll"
     Delete "$INSTDIR\softokn3.dll"
     Delete "$INSTDIR\ssl3.dll"
     Delete "$INSTDIR\${PIDGIN_UNINST_EXE}"
--- a/po/pt_BR.po	Sat Aug 25 08:42:33 2007 +0000
+++ b/po/pt_BR.po	Tue Aug 28 09:20:27 2007 +0000
@@ -14207,7 +14207,7 @@
 "Favor especificar o que você estava fazendo na hora em que o erro ocorreu\n"
 "e enviar o backtrace do arquivo de core.  Se você não sabe\n"
 "como obter o backtrace, favor ler as instruções em\n"
-"%swiki/GetABackTrace\n"
+"%swiki/GetABacktrace\n"
 "\n"
 "Se você precisa de assistência adicional, mande uma mensagem instantânea\n"
 "para o SeanEgn ou o LSchiere (via AIM e em inglês).  Informações de contato\n"
--- a/share/Makefile.am	Sat Aug 25 08:42:33 2007 +0000
+++ b/share/Makefile.am	Tue Aug 28 09:20:27 2007 +0000
@@ -1,4 +1,4 @@
 
-SUBDIRS = sounds
+SUBDIRS = sounds ca-certs
 
 EXTRA_DIST = Makefile.mingw
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/share/ca-certs/Equifax_Secure_CA.pem	Tue Aug 28 09:20:27 2007 +0000
@@ -0,0 +1,20 @@
+-----BEGIN CERTIFICATE-----
+MIIDIDCCAomgAwIBAgIENd70zzANBgkqhkiG9w0BAQUFADBOMQswCQYDVQQG
+EwJVUzEQMA4GA1UEChMHRXF1aWZheDEtMCsGA1UECxMkRXF1aWZheCBTZWN1
+cmUgQ2VydGlmaWNhdGUgQXV0aG9yaXR5MB4XDTk4MDgyMjE2NDE1MVoXDTE4
+MDgyMjE2NDE1MVowTjELMAkGA1UEBhMCVVMxEDAOBgNVBAoTB0VxdWlmYXgx
+LTArBgNVBAsTJEVxdWlmYXggU2VjdXJlIENlcnRpZmljYXRlIEF1dGhvcml0
+eTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAwV2xWGcIYu6gmi0fCG2R
+FGiYCh7+2gRvE4RiIcPRfM6fBeC4AfBONOziipUEZKzxa1NfBbPLZ4C/QgKO
+/t0BCezhABRP/PvwDN1Dulsr4R+AcJkVV5MW8Q+XarfCaCMczE1ZMKxRHjuv
+K9buY0V7xdlfUNLjUA86iOe/FP3gx7kCAwEAAaOCAQkwggEFMHAGA1UdHwRp
+MGcwZaBjoGGkXzBdMQswCQYDVQQGEwJVUzEQMA4GA1UEChMHRXF1aWZheDEt
+MCsGA1UECxMkRXF1aWZheCBTZWN1cmUgQ2VydGlmaWNhdGUgQXV0aG9yaXR5
+MQ0wCwYDVQQDEwRDUkwxMBoGA1UdEAQTMBGBDzIwMTgwODIyMTY0MTUxWjAL
+BgNVHQ8EBAMCAQYwHwYDVR0jBBgwFoAUSOZo+SvSspXXR9gjIBBPM5iQn9Qw
+HQYDVR0OBBYEFEjmaPkr0rKV10fYIyAQTzOYkJ/UMAwGA1UdEwQFMAMBAf8w
+GgYJKoZIhvZ9B0EABA0wCxsFVjMuMGMDAgbAMA0GCSqGSIb3DQEBBQUAA4GB
+AFjOKer89961zgK5F7WF0bnj4JXMJTENAKaSbn+2kmOeUJXRmm/kEd5jhW6Y
+7qj/WsjTVbJmcVfewCHrPSqnI0kBBIZCe/zuf6IWUrVnZ9NA2zsmWLIodz2u
+FHdh1voqZiegDfqnc1zqcPGUIWVEX/r87yloqaKHee9570+sB3c4
+-----END CERTIFICATE-----
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/share/ca-certs/Makefile.am	Tue Aug 28 09:20:27 2007 +0000
@@ -0,0 +1,8 @@
+cacertsdir =	$(datadir)/purple/ca-certs
+cacerts_DATA =	\
+		Equifax_Secure_CA.pem \
+		Verisign_RSA_Secure_Server_CA.pem
+
+EXTRA_DIST = \
+                $(cacerts_DATA)
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/share/ca-certs/Makefile.mingw	Tue Aug 28 09:20:27 2007 +0000
@@ -0,0 +1,21 @@
+#
+# Makefile.mingw
+#
+# Description: Makefile for win32 (mingw) version of Pidgin ca-certs
+#
+
+PIDGIN_TREE_TOP := ../..
+include $(PIDGIN_TREE_TOP)/libpurple/win32/global.mak
+
+datadir := $(PIDGIN_INSTALL_DIR)
+include ./Makefile.am
+cacertsdir := $(PIDGIN_INSTALL_DIR)/ca-certs
+
+.PHONY: install
+
+install:
+	if test '$(cacerts_DATA)'; then \
+	  mkdir -p $(cacertsdir); \
+	  cp $(cacerts_DATA) $(cacertsdir); \
+	fi;
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/share/ca-certs/Verisign_RSA_Secure_Server_CA.pem	Tue Aug 28 09:20:27 2007 +0000
@@ -0,0 +1,15 @@
+-----BEGIN CERTIFICATE-----
+MIICNDCCAaECEAKtZn5ORf5eV288mBle3cAwDQYJKoZIhvcNAQECBQAwXzEL
+MAkGA1UEBhMCVVMxIDAeBgNVBAoTF1JTQSBEYXRhIFNlY3VyaXR5LCBJbmMu
+MS4wLAYDVQQLEyVTZWN1cmUgU2VydmVyIENlcnRpZmljYXRpb24gQXV0aG9y
+aXR5MB4XDTk0MTEwOTAwMDAwMFoXDTEwMDEwNzIzNTk1OVowXzELMAkGA1UE
+BhMCVVMxIDAeBgNVBAoTF1JTQSBEYXRhIFNlY3VyaXR5LCBJbmMuMS4wLAYD
+VQQLEyVTZWN1cmUgU2VydmVyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIGb
+MA0GCSqGSIb3DQEBAQUAA4GJADCBhQJ+AJLOesGugz5aqomDV6wlAXYMra6O
+LDfO6zV4ZFQD5YRAUcm/jwjiioII0haGN1XpsSECrXZogZoFokvJSyVmIlZs
+iAeP94FZbYQHZXATcXY+m3dM41CJVphIuR2nKRoTLkoRWZweFdVJVCxzOmmC
+sZc5nG1wZ0jl3S3WyB57AgMBAAEwDQYJKoZIhvcNAQECBQADfgBl3X7hsuyw
+4jrg7HFGmhkRuNPHoLQDQCYCPgmc4RKz0Vr2N6W3YQO2WxZpO8ZECAyIUwxr
+l0nHPjXcbLm7qt9cuzovk2C2qUtN8iD3zV9/ZHuO3ABc1/p3yjkWWW8O6tO1
+g39NTUJWdrTJXwT4OPjr0l91X817/OWOgHz8UA==
+-----END CERTIFICATE-----