changeset 19578:f96cceb75fbb

merge of '3f25aaeb59668d0654c46b32af65735a2aa35fb9' and '97d7ebae82d528f45307b69c9b2510dfb2542c92'
author Richard Laager <rlaager@wiktel.com>
date Mon, 03 Sep 2007 01:09:08 +0000
parents 0eff4257d55b (current diff) 6e08084a27aa (diff)
children 46fd9b9a667d
files libpurple/plugins/ssl/ssl-gnutls.c
diffstat 32 files changed, 677 insertions(+), 364 deletions(-) [+]
line wrap: on
line diff
--- a/COPYRIGHT	Mon Sep 03 01:07:47 2007 +0000
+++ b/COPYRIGHT	Mon Sep 03 01:09:08 2007 +0000
@@ -348,6 +348,7 @@
 Sony Computer Entertainment America, Inc.
 Andy Spencer
 Mark Spencer
+Peter Speybrouck
 Lex Spoon
 Chris Stafford
 Kevin Stange
--- a/ChangeLog.API	Mon Sep 03 01:07:47 2007 +0000
+++ b/ChangeLog.API	Mon Sep 03 01:09:08 2007 +0000
@@ -6,6 +6,9 @@
 		* PURPLE_MESSAGE_INVISIBLE flag, which can be used by
 		  purple_conv_im_send_with_flags to send a message, but not display it
 		  in the conversation
+		* serv_send_attention(), serv_got_attention(), as well as send_attention 
+		  and attention_types in PurplePluginProtocolInfo. This new API is used
+		  for zapping in MySpaceIM, buzzing in Yahoo, and nudging in MSN.
 		Changed:
 		* purple_prefs_load is now called within purple_prefs_init.
 		  The UI no longer needs to call it.
@@ -14,6 +17,9 @@
 		Added:
 		* pidgin_set_accessible_relations, sets up label-for and labelled-by
 		  ATK relations (broken out from pidgin_set_accessible_label)
+		* pidgin_conv_attach_to_conversation, to reattach the Pidgin UI to a
+		  conversation
+		* conversation-hiding and conversation-displayed signals.
 
 	Finch:
 		Added:
--- a/ChangeLog.win32	Mon Sep 03 01:07:47 2007 +0000
+++ b/ChangeLog.win32	Mon Sep 03 01:09:08 2007 +0000
@@ -1,4 +1,5 @@
 version 2.2.0 (??/??/2007):
+	* Updated gtkspell to 2.0.11
 	* Upgrade SILC to use the 1.1.2 toolkit
 
 version 2.1.1 (08/20/2007):
--- a/doc/gtkconv-signals.dox	Mon Sep 03 01:07:47 2007 +0000
+++ b/doc/gtkconv-signals.dox	Mon Sep 03 01:09:08 2007 +0000
@@ -8,6 +8,8 @@
   @signal displaying-chat-msg
   @signal displayed-chat-msg
   @signal conversation-switched
+  @signal conversation-hiding
+  @signal conversation-displayed
  @endsignals
 
  <hr>
@@ -116,5 +118,23 @@
   @param new_conv The now active conversation.
  @endsignaldef
 
+ @signaldef conversation-hiding
+  @signalproto
+void (*conversation_hiding)(PidginConversation *gtkconv);
+  @endsignalproto
+  @signaldesc
+   Emitted immediately before an existing conversation is hidden.
+  @param gtkconv  The PidginConversation
+ @endsignaldef
+
+ @signaldef conversation-displayed
+  @signalproto
+void (*conversation_displayed)(PidginConversation *gtkconv);
+  @endsignalproto
+  @signaldesc
+   Emitted right after the Pidgin UI is reattached to a conversation.
+  @param gtkconv  The PidginConversation
+ @endsignaldef
+
 */
 // vim: syntax=c tw=75 et
--- a/finch/plugins/gntclipboard.c	Mon Sep 03 01:07:47 2007 +0000
+++ b/finch/plugins/gntclipboard.c	Mon Sep 03 01:09:08 2007 +0000
@@ -38,10 +38,12 @@
 #include <plugin.h>
 #include <version.h>
 #include <debug.h>
+#include <notify.h>
 #include <gntwm.h>
 
 #include <gntplugin.h>
 
+#ifdef HAVE_X11
 static pid_t child = 0;
 
 static gulong sig_handle;
@@ -49,7 +51,6 @@
 static void
 set_clip(gchar *string)
 {
-#ifdef HAVE_X11
 	Window w;
 	XEvent e, respond;
 	XSelectionRequestEvent *req;
@@ -89,14 +90,12 @@
 			return;
 		}
 	}
-#endif
 	return;
 }
 
 static void
 clipboard_changed(GntWM *wm, gchar *string)
 {
-#ifdef HAVE_X11
 	if (child) {
 		kill(child, SIGTERM);
 	}
@@ -104,8 +103,8 @@
 		set_clip(string);
 		_exit(0);
 	}
+}
 #endif
-}
 
 static gboolean
 plugin_load(PurplePlugin *plugin)
@@ -113,25 +112,35 @@
 #ifdef HAVE_X11
 	if (!XOpenDisplay(NULL)) {
 		purple_debug_warning("gntclipboard", "Couldn't find X display\n");
+		purple_notify_error(NULL, _("Error"), _("Error loading the plugin."),
+				_("Couldn't find X display"));
 		return FALSE;
 	}
-#endif
 	if (!getenv("WINDOWID")) {
 		purple_debug_warning("gntclipboard", "Couldn't find window\n");
+		purple_notify_error(NULL, _("Error"), _("Error loading the plugin."),
+				_("Couldn't find window"));
 		return FALSE;
 	}
 	sig_handle = g_signal_connect(G_OBJECT(gnt_get_clipboard()), "clipboard_changed", G_CALLBACK(clipboard_changed), NULL);
 	return TRUE;
+#else
+	purple_notify_error(NULL, _("Error"), _("Error loading the plugin."),
+			_("This plugin cannot be loaded because it was not built with X11 support."));
+	return FALSE;
+#endif
 }
 
 static gboolean
 plugin_unload(PurplePlugin *plugin)
 {
+#ifdef HAVE_X11
 	if (child) {
 		kill(child, SIGTERM);
 		child = 0;
 	}
 	g_signal_handler_disconnect(G_OBJECT(gnt_get_clipboard()), sig_handle);
+#endif
 	return TRUE;
 }
 
--- a/libpurple/certificate.c	Mon Sep 03 01:07:47 2007 +0000
+++ b/libpurple/certificate.c	Mon Sep 03 01:09:08 2007 +0000
@@ -1083,11 +1083,14 @@
 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);
 
+	/* Show the certificate AFTER re-opening the dialog so that this
+	   appears above the other */
+	purple_certificate_display_x509(disp_crt);
+
 	x509_tls_cached_ua_ctx_free(c);
 }
 
@@ -1205,7 +1208,14 @@
 	/* Load up the cached certificate */
 	cached_crt = purple_certificate_pool_retrieve(
 		tls_peers, vrq->subject_name);
-	g_assert(cached_crt);
+	if ( !cached_crt ) {
+		purple_debug_error("certificate/x509/tls_cached",
+				   "Lookup failed on cached certificate!\n"
+				   "It was here just a second ago. Forwarding "
+				   "to cert_changed.\n");
+		/* vrq now becomes the problem of cert_changed */
+		x509_tls_cached_peer_cert_changed(vrq);
+	}
 
 	/* Now get SHA1 sums for both and compare them */
 	/* TODO: This is not an elegant way to compare certs */
@@ -1338,7 +1348,14 @@
 
 	ca_crt = purple_certificate_pool_retrieve(ca, ca_id);
 	g_free(ca_id);
-	g_assert(ca_crt);
+	if (!ca_crt) {
+		purple_debug_error("certificate/x509/tls_cached",
+				   "Certificate authority disappeared out "
+				   "underneath me!\n");
+		purple_certificate_verify_complete(vrq,
+						   PURPLE_CERTIFICATE_INVALID);
+		return;
+	}
 	
 	/* Check the signature */
 	if ( !purple_certificate_signed_by(end_crt, ca_crt) ) {
@@ -1375,9 +1392,11 @@
 						 "tls_peers");
 
 	if (tls_peers) {
-		g_assert(purple_certificate_pool_store(tls_peers,
-						       vrq->subject_name,
-						       peer_crt) );
+		if (!purple_certificate_pool_store(tls_peers,vrq->subject_name,
+						   peer_crt) ) {
+			purple_debug_error("certificate/x509/tls_cached",
+					   "FAILED to cache peer certificate\n");
+		}
 	} else {
 		purple_debug_error("certificate/x509/tls_cached",
 				   "Unable to locate tls_peers certificate "
@@ -1790,7 +1809,6 @@
 	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;
 
@@ -1807,7 +1825,11 @@
 	/* Get the certificate times */
 	/* TODO: Check the times against localtime */
 	/* TODO: errorcheck? */
-	g_assert(purple_certificate_get_times(crt, &activation, &expiration));
+	if (!purple_certificate_get_times(crt, &activation, &expiration)) {
+		purple_debug_error("certificate",
+				   "Failed to get certificate times!\n");
+		activation = expiration = 0;
+	}
 	activ_str = g_strdup(ctime(&activation));
 	expir_str = g_strdup(ctime(&expiration));
 
--- a/libpurple/example/nullclient.c	Mon Sep 03 01:07:47 2007 +0000
+++ b/libpurple/example/nullclient.c	Mon Sep 03 01:09:08 2007 +0000
@@ -197,7 +197,7 @@
 	purple_util_set_user_dir(CUSTOM_USER_DIRECTORY);
 
 	/* We do not want any debugging for now to keep the noise to a minimum. */
-	purple_debug_set_enabled(TRUE);
+	purple_debug_set_enabled(FALSE);
 
 	/* Set the core-uiops, which is used to
 	 * 	- initialize the ui specific preferences.
@@ -257,24 +257,6 @@
 				PURPLE_CALLBACK(signed_on), NULL);
 }
 
-
-
-
-void signedOn( PurpleConnection *gc, gpointer dummy ) { 
-
-    
-    if( gc ) {
-
-        PurpleAccount* a = purple_connection_get_account( gc );
-
-        if( a ) {
-            
-            purple_presence_set_idle( purple_account_get_presence( a ), TRUE, time( NULL ) );
-        }
-    }    
-}
-
-
 int main()
 {
 	GList *iter;
@@ -300,26 +282,30 @@
 			names = g_list_append(names, info->id);
 		}
 	}
+	printf("Select the protocol [0-%d]: ", i-1);
+	fgets(name, sizeof(name), stdin);
+	sscanf(name, "%d", &num);
+	prpl = g_list_nth_data(names, num);
+
+	printf("Username: ");
+	fgets(name, sizeof(name), stdin);
+	name[strlen(name) - 1] = 0;  /* strip the \n at the end */
 
 	/* Create the account */
-	account = purple_account_new("msimprpl@xyzzy.cjb.net", "prpl-myspace" );
-	purple_account_set_password(account, "4224jc" );
+	account = purple_account_new(name, prpl);
+
+	/* Get the password for the account */
+	password = getpass("Password: ");
+	purple_account_set_password(account, password);
 
 	/* It's necessary to enable the account first. */
 	purple_account_set_enabled(account, UI_ID, TRUE);
 
-#if 0 
-	static int handle;    
-	purple_signal_connect( purple_connections_get_handle(), 
-                           "signed-on", &handle,
-                           PURPLE_CALLBACK( signedOn ), 
-                           NULL );
-    
 	/* Now, to connect the account(s), create a status and activate it. */
-	purple_savedstatus_activate( purple_savedstatus_get_default() );
+	status = purple_savedstatus_new(NULL, PURPLE_STATUS_AVAILABLE);
+	purple_savedstatus_activate(status);
 
 	connect_to_signals_for_demonstration_purposes_only();
-#endif
 
 	g_main_loop_run(loop);
 
--- a/libpurple/internal.h	Mon Sep 03 01:07:47 2007 +0000
+++ b/libpurple/internal.h	Mon Sep 03 01:09:08 2007 +0000
@@ -29,6 +29,10 @@
 # include <config.h>
 #endif
 
+/* for SIOCGIFCONF  in SKYOS */
+#ifdef SKYOS
+#include <net/sockios.h>
+#endif
 /*
  * If we're using NLS, make sure gettext works.  If not, then define
  * dummy macros in place of the normal gettext macros.
--- a/libpurple/plugins/ssl/ssl-gnutls.c	Mon Sep 03 01:07:47 2007 +0000
+++ b/libpurple/plugins/ssl/ssl-gnutls.c	Mon Sep 03 01:09:08 2007 +0000
@@ -60,7 +60,7 @@
 		(gnutls_realloc_function) g_realloc, /* realloc */
 		(gnutls_free_function)    g_free     /* free */
 		);
-	
+
 	gnutls_global_init();
 
 	gnutls_certificate_allocate_credentials(&xcred);
@@ -73,7 +73,7 @@
 static gboolean
 ssl_gnutls_init(void)
 {
-   return TRUE;
+	return TRUE;
 }
 
 static void
@@ -130,17 +130,18 @@
 
 		purple_ssl_close(gsc);
 	} else {
-		purple_debug_info("gnutls", "Handshake complete\n");
-
-		/* 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;
+
+		/* TODO: Remove all this debugging babble */
+		purple_debug_info("gnutls", "Handshake complete\n");
+
 		for (l=peers; l; l = l->next) {
 			PurpleCertificate *crt = l->data;
 			GByteArray *z =
@@ -155,71 +156,71 @@
 
 			/* 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((guchar *)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((guchar *)tbuf, tsz);
-		      purple_debug_info("gnutls",
-					"Serial: %s\n",
-					tasc);
-		      g_free(tasc);
+			const gnutls_datum_t *cert_list;
+			unsigned int cert_list_size = 0;
+			gnutls_session_t session=gnutls_data->session;
+			int i;
+
+			cert_list =
+				gnutls_certificate_get_peers(session, &cert_list_size);
+
+			purple_debug_info("gnutls",
+					    "Peer provided %d certs\n",
+					    cert_list_size);
+			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);
 
-		      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);
+				fpr_asc =
+						purple_base16_encode_chunked((const guchar *)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((const guchar *)tbuf, tsz);
+				purple_debug_info("gnutls",
+						"Serial: %s\n",
+						tasc);
+				g_free(tasc);
 
-		      g_free(fpr_asc); fpr_asc = NULL;
-		      gnutls_x509_crt_deinit(cert);
-		    }
-		  
+				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 */
@@ -377,7 +378,7 @@
 	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);
 
@@ -425,12 +426,14 @@
 static void
 x509_crtdata_delref(x509_crtdata_t *cd)
 {
-	g_assert(cd->refcount > 0);
-	
 	(cd->refcount)--;
 
+	if (cd->refcount < 0)
+		g_critical("Refcount of x509_crtdata_t is %d, which is less "
+				"than zero!\n", cd->refcount);
+
 	/* If the refcount reaches zero, kill the structure */
-	if (cd->refcount == 0) {
+	if (cd->refcount <= 0) {
 		purple_debug_info("gnutls/x509",
 				  "Freeing unused cert data at %p\n",
 				  cd);
@@ -465,11 +468,11 @@
 	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;
@@ -494,7 +497,7 @@
 	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? */
@@ -505,7 +508,7 @@
 			    NULL      /* No error checking for now */
 		),
 		NULL);
-	
+
 	/* Load the datum struct */
 	dt.data = (unsigned char *) buf;
 	dt.size = buf_sz;
@@ -513,7 +516,7 @@
 	/* Perform the conversion */
 	crt = x509_import_from_datum(dt,
 				     GNUTLS_X509_FMT_PEM); // files should be in PEM format
-	
+
 	/* Cleanup */
 	g_free(buf);
 
@@ -570,7 +573,6 @@
 	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;
@@ -595,10 +597,10 @@
 }
 /** 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
+ * 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
@@ -621,7 +623,7 @@
 	/* 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);
 }
@@ -642,7 +644,7 @@
 	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);
 
@@ -684,7 +686,7 @@
 		/* 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 */
@@ -695,7 +697,7 @@
 					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);
@@ -712,7 +714,7 @@
 				  issuer_id, crt_id);
 		g_free(crt_id);
 		g_free(issuer_id);
-		
+
 		return FALSE;
 	} /* if (ret, etc.) */
 
@@ -741,7 +743,7 @@
 
 	/* 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);
@@ -775,7 +777,7 @@
 		g_free(dn);
 		return NULL;
 	}
-	
+
 	return dn;
 }
 
@@ -806,7 +808,7 @@
 		g_free(dn);
 		return NULL;
 	}
-	
+
 	return dn;
 }
 
@@ -847,7 +849,6 @@
 		return NULL;
 	}
 
-	
 	return cn;
 }
 
@@ -892,7 +893,7 @@
 	if (*activation == errval || *expiration == errval) {
 		return FALSE;
 	}
-	
+
 	return TRUE;
 }
 
--- a/libpurple/plugins/tcl/tcl_cmds.c	Mon Sep 03 01:07:47 2007 +0000
+++ b/libpurple/plugins/tcl/tcl_cmds.c	Mon Sep 03 01:09:08 2007 +0000
@@ -544,12 +544,16 @@
 
 int tcl_cmd_cmd(ClientData unused, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[])
 {
-	const char *cmds[] = { "register", "unregister", NULL };
-	enum { CMD_CMD_REGISTER, CMD_CMD_UNREGISTER } cmd;
+	const char *cmds[] = { "do", "help", "list", "register", "unregister", NULL };
+	enum { CMD_CMD_DO, CMD_CMD_HELP, CMD_CMD_LIST, CMD_CMD_REGISTER, CMD_CMD_UNREGISTER } cmd;
 	struct tcl_cmd_handler *handler;
-	Tcl_Obj *result = Tcl_GetObjResult(interp);
+	Tcl_Obj *list, *elem, *result = Tcl_GetObjResult(interp);
+	PurpleConversation *convo;
 	PurpleCmdId id;
+	PurpleCmdStatus status;
 	int error;
+	GList *l, *cur;
+	gchar *escaped, *errstr = NULL;
 
 	if (objc < 2) {
 		Tcl_WrongNumArgs(interp, 1, objv, "subcommand ?args?");
@@ -560,6 +564,57 @@
 		return error;
 
 	switch (cmd) {
+	case CMD_CMD_DO:
+		if (objc != 4) {
+			Tcl_WrongNumArgs(interp, 2, objv, "conversation command");
+			return TCL_ERROR;
+		}
+		if ((convo = tcl_validate_conversation(objv[2], interp)) == NULL)
+			return TCL_ERROR;
+		escaped = g_markup_escape_text(Tcl_GetString(objv[3]), -1);
+		status = purple_cmd_do_command(convo, Tcl_GetString(objv[3]),
+				escaped, &errstr);
+		g_free(escaped);
+		Tcl_SetStringObj(result, errstr ? (char *)errstr : "", -1);
+		g_free(errstr);
+		if (status != PURPLE_CMD_STATUS_OK) {
+			return TCL_ERROR;
+		}
+		break;
+	case CMD_CMD_HELP:
+		if (objc != 4) {
+			Tcl_WrongNumArgs(interp, 2, objv, "conversation name");
+			return TCL_ERROR;
+		}
+		if ((convo = tcl_validate_conversation(objv[2], interp)) == NULL)
+			return TCL_ERROR;
+		l = cur = purple_cmd_help(convo, Tcl_GetString(objv[3]));
+		list = Tcl_NewListObj(0, NULL);
+		while (cur != NULL) {
+			elem = Tcl_NewStringObj((char *)cur->data, -1);
+			Tcl_ListObjAppendElement(interp, list, elem);
+			cur = g_list_next(cur);
+		}
+		g_list_free(l);
+		Tcl_SetObjResult(interp, list);
+		break;
+	case CMD_CMD_LIST:
+		if (objc != 3) {
+			Tcl_WrongNumArgs(interp, 2, objv, "conversation");
+			return TCL_ERROR;
+		}
+		if ((convo = tcl_validate_conversation(objv[2], interp)) == NULL)
+			return TCL_ERROR;
+		l = cur = purple_cmd_list(convo);
+		list = Tcl_NewListObj(0, NULL);
+		while (cur != NULL) {
+			elem = Tcl_NewStringObj((char *)cur->data, -1);
+			Tcl_ListObjAppendElement(interp, list, elem);
+			cur = g_list_next(cur);
+		}
+		g_list_free(l);
+		Tcl_SetObjResult(interp, list);
+		break;
 	case CMD_CMD_REGISTER:
 		if (objc != 9) {
 			Tcl_WrongNumArgs(interp, 2, objv, "cmd arglist priority flags prpl_id proc helpstr");
--- a/libpurple/protocols/bonjour/bonjour.c	Mon Sep 03 01:07:47 2007 +0000
+++ b/libpurple/protocols/bonjour/bonjour.c	Mon Sep 03 01:09:08 2007 +0000
@@ -235,6 +235,13 @@
 	g_free(stripped);
 }
 
+static void bonjour_remove_buddy(PurpleConnection *pc, PurpleBuddy *buddy, PurpleGroup *group) {
+	if (buddy->proto_data) {
+		bonjour_buddy_delete(buddy->proto_data);
+		buddy->proto_data = NULL;
+	}
+}
+
 static GList *
 bonjour_status_types(PurpleAccount *account)
 {
@@ -395,7 +402,7 @@
 	NULL,                                                    /* change_passwd */
 	NULL,                                                    /* add_buddy */
 	NULL,                                                    /* add_buddies */
-	NULL,                                                    /* remove_buddy */
+	bonjour_remove_buddy,                                    /* remove_buddy */
 	NULL,                                                    /* remove_buddies */
 	NULL,                                                    /* add_permit */
 	NULL,                                                    /* add_deny */
--- a/libpurple/protocols/bonjour/buddy.c	Mon Sep 03 01:07:47 2007 +0000
+++ b/libpurple/protocols/bonjour/buddy.c	Mon Sep 03 01:09:08 2007 +0000
@@ -121,9 +121,8 @@
  * the buddy.
  */
 void
-bonjour_buddy_add_to_purple(BonjourBuddy *bonjour_buddy)
+bonjour_buddy_add_to_purple(BonjourBuddy *bonjour_buddy, PurpleBuddy *buddy)
 {
-	PurpleBuddy *buddy;
 	PurpleGroup *group;
 	PurpleAccount *account = bonjour_buddy->account;
 	const char *status_id, *old_hash, *new_hash;
@@ -147,7 +146,8 @@
 	}
 
 	/* Make sure the buddy exists in our buddy list */
-	buddy = purple_find_buddy(account, bonjour_buddy->name);
+	if (buddy == NULL)
+		buddy = purple_find_buddy(account, bonjour_buddy->name);
 
 	if (buddy == NULL) {
 		buddy = purple_buddy_new(account, bonjour_buddy->name, NULL);
--- a/libpurple/protocols/bonjour/buddy.h	Mon Sep 03 01:07:47 2007 +0000
+++ b/libpurple/protocols/bonjour/buddy.h	Mon Sep 03 01:09:08 2007 +0000
@@ -92,8 +92,9 @@
 
 /**
  * If the buddy doesn't previoulsy exists, it is created. Else, its data is changed (???)
+ * purple_buddy is optional; it saves an additional lookup if we already have it
  */
-void bonjour_buddy_add_to_purple(BonjourBuddy *buddy);
+void bonjour_buddy_add_to_purple(BonjourBuddy *bonjour_buddy, PurpleBuddy *purple_buddy);
 
 /**
  * We got the buddy icon data; deal with it
--- a/libpurple/protocols/bonjour/issues.txt	Mon Sep 03 01:07:47 2007 +0000
+++ b/libpurple/protocols/bonjour/issues.txt	Mon Sep 03 01:09:08 2007 +0000
@@ -2,6 +2,5 @@
 ============= Known issues ===============
 ==========================================
 
-* Status changes don't work
 * File transfers
 * Typing notifications
--- a/libpurple/protocols/bonjour/jabber.c	Mon Sep 03 01:07:47 2007 +0000
+++ b/libpurple/protocols/bonjour/jabber.c	Mon Sep 03 01:09:08 2007 +0000
@@ -373,17 +373,19 @@
 
 	g_return_if_fail(bb != NULL);
 
-	/* Close the socket, clear the watcher and free memory */
-	bonjour_jabber_close_conversation(bb->conversation);
-	bb->conversation = NULL;
+	/* Inform the user that the conversation has been closed */
+	if (bb->conversation != NULL) {
+		conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, pb->name, pb->account);
+		if (conv != NULL) {
+			char *tmp = g_strdup_printf(_("%s has closed the conversation."), pb->name);
+			purple_conversation_write(conv, NULL, tmp, PURPLE_MESSAGE_SYSTEM, time(NULL));
+			g_free(tmp);
+		}
+		/* Close the socket, clear the watcher and free memory */
+		bonjour_jabber_close_conversation(bb->conversation);
+		bb->conversation = NULL;
+	}
 
-	/* Inform the user that the conversation has been closed */
-	conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, pb->name, pb->account);
-	if (conv != NULL) {
-		char *tmp = g_strdup_printf(_("%s has closed the conversation."), pb->name);
-		purple_conversation_write(conv, NULL, tmp, PURPLE_MESSAGE_SYSTEM, time(NULL));
-		g_free(tmp);
-	}
 }
 
 void bonjour_jabber_stream_started(PurpleBuddy *pb) {
--- a/libpurple/protocols/bonjour/mdns_avahi.c	Mon Sep 03 01:07:47 2007 +0000
+++ b/libpurple/protocols/bonjour/mdns_avahi.c	Mon Sep 03 01:09:08 2007 +0000
@@ -33,7 +33,7 @@
 #include <avahi-glib/glib-malloc.h>
 #include <avahi-glib/glib-watch.h>
 
-/* For some reason, this is missing from the Avahi type defines */
+/* Avahi only defines the types that it actually uses (which at this time doesn't include NULL) */
 #ifndef AVAHI_DNS_TYPE_NULL
 #define AVAHI_DNS_TYPE_NULL 0x0A
 #endif
@@ -58,7 +58,8 @@
 		  const char *host_name, const AvahiAddress *a, uint16_t port, AvahiStringList *txt,
 		  AvahiLookupResultFlags flags, void *userdata) {
 
-	BonjourBuddy *buddy;
+	PurpleBuddy *pb;
+	BonjourBuddy *bb;
 	PurpleAccount *account = userdata;
 	AvahiStringList *l;
 	size_t size;
@@ -67,44 +68,62 @@
 
 	g_return_if_fail(r != NULL);
 
+	pb = purple_find_buddy(account, name);
+	bb = (pb != NULL) ? pb->proto_data : NULL;
+
 	switch (event) {
 		case AVAHI_RESOLVER_FAILURE:
 			purple_debug_error("bonjour", "_resolve_callback - Failure: %s\n",
 				avahi_strerror(avahi_client_errno(avahi_service_resolver_get_client(r))));
+
 			avahi_service_resolver_free(r);
+			if (bb != NULL) {
+				/* We've already freed the resolver */
+				if (r == ((AvahiBuddyImplData *)bb->mdns_impl_data)->resolver)
+					((AvahiBuddyImplData *)bb->mdns_impl_data)->resolver = NULL;
+				purple_blist_remove_buddy(pb);
+			}
 			break;
 		case AVAHI_RESOLVER_FOUND:
 			/* create a buddy record */
-			buddy = bonjour_buddy_new(name, account);
+			if (bb == NULL)
+				bb = bonjour_buddy_new(name, account);
 
-			((AvahiBuddyImplData *)buddy->mdns_impl_data)->resolver = r;
+			/* If we're reusing an existing buddy, make sure if it is a different resolver to clean up the old one.
+			 * I don't think this should ever happen, but I'm afraid we might get events out of sequence. */
+			if (((AvahiBuddyImplData *)bb->mdns_impl_data)->resolver != NULL
+					&& ((AvahiBuddyImplData *)bb->mdns_impl_data)->resolver != r) {
+				avahi_service_resolver_free(((AvahiBuddyImplData *)bb->mdns_impl_data)->resolver);
+			}
+			((AvahiBuddyImplData *)bb->mdns_impl_data)->resolver = r;
 
+			g_free(bb->ip);
 			/* Get the ip as a string */
-			buddy->ip = g_malloc(AVAHI_ADDRESS_STR_MAX);
-			avahi_address_snprint(buddy->ip, AVAHI_ADDRESS_STR_MAX, a);
+			bb->ip = g_malloc(AVAHI_ADDRESS_STR_MAX);
+			avahi_address_snprint(bb->ip, AVAHI_ADDRESS_STR_MAX, a);
 
-			buddy->port_p2pj = port;
+			bb->port_p2pj = port;
 
 			/* Obtain the parameters from the text_record */
-			clear_bonjour_buddy_values(buddy);
-			l = txt;
-			while (l != NULL) {
-				ret = avahi_string_list_get_pair(l, &key, &value, &size);
-				l = l->next;
-				if (ret < 0)
+			clear_bonjour_buddy_values(bb);
+			for(l = txt; l != NULL; l = l->next) {
+				if ((ret = avahi_string_list_get_pair(l, &key, &value, &size)) < 0)
 					continue;
-				set_bonjour_buddy_value(buddy, key, value, size);
+				set_bonjour_buddy_value(bb, key, value, size);
 				/* TODO: Since we're using the glib allocator, I think we
 				 * can use the values instead of re-copying them */
 				avahi_free(key);
 				avahi_free(value);
 			}
 
-			if (!bonjour_buddy_check(buddy))
-				bonjour_buddy_delete(buddy);
-			else
+			if (!bonjour_buddy_check(bb)) {
+				if (pb != NULL)
+					purple_blist_remove_buddy(pb);
+				else
+					bonjour_buddy_delete(bb);
+			} else
 				/* Add or update the buddy in our buddy list */
-				bonjour_buddy_add_to_purple(buddy);
+				bonjour_buddy_add_to_purple(bb, pb);
 
 			break;
 		default:
@@ -120,7 +139,7 @@
 		  AvahiLookupResultFlags flags, void *userdata) {
 
 	PurpleAccount *account = userdata;
-	PurpleBuddy *gb = NULL;
+	PurpleBuddy *pb = NULL;
 
 	switch (event) {
 		case AVAHI_BROWSER_FAILURE:
@@ -132,7 +151,7 @@
 			/* A new peer has joined the network and uses iChat bonjour */
 			purple_debug_info("bonjour", "_browser_callback - new service\n");
 			/* Make sure it isn't us */
-			if (g_ascii_strcasecmp(name, account->username) != 0) {
+			if (purple_utf8_strcasecmp(name, account->username) != 0) {
 				if (!avahi_service_resolver_new(avahi_service_browser_get_client(b),
 						interface, protocol, name, type, domain, AVAHI_PROTO_UNSPEC,
 						0, _resolver_callback, account)) {
@@ -143,16 +162,12 @@
 			break;
 		case AVAHI_BROWSER_REMOVE:
 			purple_debug_info("bonjour", "_browser_callback - Remove service\n");
-			gb = purple_find_buddy(account, name);
-			if (gb != NULL) {
-				bonjour_buddy_delete(gb->proto_data);
-				purple_blist_remove_buddy(gb);
-			}
+			pb = purple_find_buddy(account, name);
+			if (pb != NULL)
+				purple_blist_remove_buddy(pb);
 			break;
 		case AVAHI_BROWSER_ALL_FOR_NOW:
 		case AVAHI_BROWSER_CACHE_EXHAUSTED:
-			purple_debug_warning("bonjour", "(Browser) %s\n",
-				event == AVAHI_BROWSER_CACHE_EXHAUSTED ? "CACHE_EXHAUSTED" : "ALL_FOR_NOW");
 			break;
 		default:
 			purple_debug_info("bonjour", "Unrecognized Service browser event: %d.\n", event);
--- a/libpurple/protocols/bonjour/mdns_howl.c	Mon Sep 03 01:07:47 2007 +0000
+++ b/libpurple/protocols/bonjour/mdns_howl.c	Mon Sep 03 01:09:08 2007 +0000
@@ -71,6 +71,7 @@
 	char value[SW_TEXT_RECORD_MAX_LEN];
 	sw_uint32 value_length;
 
+	/* TODO: We want to keep listening for updates*/
 	sw_discovery_cancel(discovery, oid);
 
 	/* create a buddy record */
@@ -100,7 +101,7 @@
 	}
 
 	/* Add or update the buddy in our buddy list */
-	bonjour_buddy_add_to_purple(buddy);
+	bonjour_buddy_add_to_purple(buddy, NULL);
 
 	return SW_OKAY;
 }
@@ -149,10 +150,7 @@
 			purple_debug_info("bonjour", "_browser_reply --> Remove service\n");
 			gb = purple_find_buddy(account, name);
 			if (gb != NULL)
-			{
-				bonjour_buddy_delete(gb->proto_data);
 				purple_blist_remove_buddy(gb);
-			}
 			break;
 		case SW_DISCOVERY_BROWSE_RESOLVED:
 			purple_debug_info("bonjour", "_browse_reply --> Resolved\n");
--- a/libpurple/protocols/bonjour/mdns_win32.c	Mon Sep 03 01:07:47 2007 +0000
+++ b/libpurple/protocols/bonjour/mdns_win32.c	Mon Sep 03 01:09:08 2007 +0000
@@ -79,23 +79,23 @@
 	uint32_t ttl, void *context)
 {
 
-	if (kDNSServiceErr_NoError != errorCode)
+	if (kDNSServiceErr_NoError != errorCode) {
 		purple_debug_error("bonjour", "record query - callback error.\n");
-	else if (flags & kDNSServiceFlagsAdd)
-	{
+		/* TODO: Probably should remove the buddy when this happens */
+	} else if (flags & kDNSServiceFlagsAdd) {
 		if (rrtype == kDNSServiceType_TXT) {
 			/* New Buddy */
-			BonjourBuddy *buddy = (BonjourBuddy*) context;
-			_mdns_parse_text_record(buddy, rdata, rdlen);
-			bonjour_buddy_add_to_purple(buddy);
+			BonjourBuddy *bb = (BonjourBuddy*) context;
+			_mdns_parse_text_record(bb, rdata, rdlen);
+			bonjour_buddy_add_to_purple(bb, NULL);
 		} else if (rrtype == kDNSServiceType_NULL) {
 			/* Buddy Icon response */
-			BonjourBuddy *buddy = (BonjourBuddy*) context;
-			Win32BuddyImplData *idata = buddy->mdns_impl_data;
+			BonjourBuddy *bb = (BonjourBuddy*) context;
+			Win32BuddyImplData *idata = bb->mdns_impl_data;
 
 			g_return_if_fail(idata != NULL);
 
-			bonjour_buddy_got_buddy_icon(buddy, rdata, rdlen);
+			bonjour_buddy_got_buddy_icon(bb, rdata, rdlen);
 
 			/* We've got what we need; stop listening */
 			purple_input_remove(idata->null_query_handler);
@@ -110,32 +110,34 @@
 _mdns_resolve_host_callback(GSList *hosts, gpointer data, const char *error_message)
 {
 	ResolveCallbackArgs* args = (ResolveCallbackArgs*)data;
+	BonjourBuddy* bb = args->buddy;
 
-	if (!hosts || !hosts->data)
+	if (!hosts || !hosts->data) {
 		purple_debug_error("bonjour", "host resolution - callback error.\n");
-	else {
+		bonjour_buddy_delete(bb);
+	} else {
 		struct sockaddr_in *addr = (struct sockaddr_in*)g_slist_nth_data(hosts, 1);
-		BonjourBuddy* buddy = args->buddy;
-		Win32BuddyImplData *idata = buddy->mdns_impl_data;
+		Win32BuddyImplData *idata = bb->mdns_impl_data;
 
 		g_return_if_fail(idata != NULL);
 
-		buddy->ip = g_strdup(inet_ntoa(addr->sin_addr));
+		g_free(bb->ip);
+		bb->ip = g_strdup(inet_ntoa(addr->sin_addr));
 
 		/* finally, set up the continuous txt record watcher, and add the buddy to purple */
 
 		if (kDNSServiceErr_NoError == DNSServiceQueryRecord(&idata->txt_query, kDNSServiceFlagsLongLivedQuery,
 				kDNSServiceInterfaceIndexAny, args->full_service_name, kDNSServiceType_TXT,
-				kDNSServiceClass_IN, _mdns_record_query_callback, buddy)) {
+				kDNSServiceClass_IN, _mdns_record_query_callback, bb)) {
 
-			purple_debug_info("bonjour", "Found buddy %s at %s:%d\n", buddy->name, buddy->ip, buddy->port_p2pj);
+			purple_debug_info("bonjour", "Found buddy %s at %s:%d\n", bb->name, bb->ip, bb->port_p2pj);
 
 			idata->txt_query_handler = purple_input_add(DNSServiceRefSockFD(idata->txt_query),
 				PURPLE_INPUT_READ, _mdns_handle_event, idata->txt_query);
 
-			bonjour_buddy_add_to_purple(buddy);
+			bonjour_buddy_add_to_purple(bb, NULL);
 		} else
-			bonjour_buddy_delete(buddy);
+			bonjour_buddy_delete(bb);
 
 	}
 
@@ -202,18 +204,19 @@
     DNSServiceErrorType errorCode, const char *serviceName, const char *regtype, const char *replyDomain, void *context)
 {
 	PurpleAccount *account = (PurpleAccount*)context;
-	PurpleBuddy *gb = NULL;
+	PurpleBuddy *pb = NULL;
 
 	if (kDNSServiceErr_NoError != errorCode)
 		purple_debug_error("bonjour", "service browser - callback error");
 	else if (flags & kDNSServiceFlagsAdd) {
 		/* A presence service instance has been discovered... check it isn't us! */
-		if (g_ascii_strcasecmp(serviceName, account->username) != 0) {
+		if (purple_utf8_strcasecmp(serviceName, account->username) != 0) {
 			/* OK, lets go ahead and resolve it to add to the buddy list */
 			ResolveCallbackArgs *args = g_new0(ResolveCallbackArgs, 1);
 			args->buddy = bonjour_buddy_new(serviceName, account);
 
-			if (kDNSServiceErr_NoError != DNSServiceResolve(&args->resolver, 0, 0, serviceName, regtype, replyDomain, _mdns_service_resolve_callback, args)) {
+			if (kDNSServiceErr_NoError != DNSServiceResolve(&args->resolver, 0, 0, serviceName, regtype,
+					replyDomain, _mdns_service_resolve_callback, args)) {
 				bonjour_buddy_delete(args->buddy);
 				g_free(args);
 				purple_debug_error("bonjour", "service browser - failed to resolve service.\n");
@@ -226,11 +229,9 @@
 	} else {
 		/* A peer has sent a goodbye packet, remove them from the buddy list */
 		purple_debug_info("bonjour", "service browser - remove notification\n");
-		gb = purple_find_buddy(account, serviceName);
-		if (gb != NULL) {
-			bonjour_buddy_delete(gb->proto_data);
-			purple_blist_remove_buddy(gb);
-		}
+		pb = purple_find_buddy(account, serviceName);
+		if (pb != NULL)
+			purple_blist_remove_buddy(pb);
 	}
 }
 
--- a/libpurple/protocols/myspace/CHANGES	Mon Sep 03 01:07:47 2007 +0000
+++ b/libpurple/protocols/myspace/CHANGES	Mon Sep 03 01:09:08 2007 +0000
@@ -1,3 +1,13 @@
+2007-08-28 Jeff Connelly <pidgin@xyzzy.cjb.net> - 0.17
+* Get server-side contact list from server on sign-on (partly implements
+  server-side contacts, ticket #2658).
+* Set local alias to username on sign-on, if not already set. This fixes
+  #2793, though this may not be the best way, other fixes under consideration.
+* Support myim:sendIM and addContact URLs with cID and uID parameters.
+* Fix #2722, only check for mail if "New mail notifications" is enabled.
+* Modularize msimprpl.
+
+
 2007-08-23 Jeff Connelly <pidgin@xyzzy.cjb.net> - 0.16
 * Add option to add all friends from myspace.com to your buddy list (#2660)
 * If a user doesn't have a picture, don't display an icon (instead of
@@ -6,6 +16,7 @@
 * Fix #2752, which led to duplicate groups
 * Fix #2720, crash/disconnect when adding a buddy that doesn't exist
   (You'll now receive an error when looking up invalid usernames).
+* Source-code release only.
 
 2007-08-22 Jeff Connelly <pidgin@xyzzy.cjb.net> - 0.15
 * Incomplete implementation of adding friends from myspace.com.
--- a/libpurple/protocols/myspace/myspace.c	Mon Sep 03 01:07:47 2007 +0000
+++ b/libpurple/protocols/myspace/myspace.c	Mon Sep 03 01:09:08 2007 +0000
@@ -683,6 +683,7 @@
 msim_incoming_im(MsimSession *session, MsimMessage *msg)
 {
 	gchar *username, *msg_msim_markup, *msg_purple_markup;
+	time_t time_received;
 
 	g_return_val_if_fail(MSIM_SESSION_VALID(session), FALSE);
 	g_return_val_if_fail(msg != NULL, FALSE);
@@ -696,8 +697,12 @@
 	msg_purple_markup = msim_markup_to_html(session, msg_msim_markup);
 	g_free(msg_msim_markup);
 
-	serv_got_im(session->gc, username, msg_purple_markup, 
-			PURPLE_MESSAGE_RECV, time(NULL));
+	time_received = msim_msg_get_integer(msg, "date");
+	if (!time_received) {
+		time_received = time(NULL);
+	}
+
+	serv_got_im(session->gc, username, msg_purple_markup, PURPLE_MESSAGE_RECV, time_received);
 
 	g_free(username);
 	g_free(msg_purple_markup);
--- a/libpurple/protocols/oscar/oscarcommon.h	Mon Sep 03 01:07:47 2007 +0000
+++ b/libpurple/protocols/oscar/oscarcommon.h	Mon Sep 03 01:09:08 2007 +0000
@@ -30,7 +30,7 @@
 #include "version.h"
 #include "notify.h"
 
-#define OSCAR_DEFAULT_LOGIN_SERVER "login.oscar.aol.com"
+#define OSCAR_DEFAULT_LOGIN_SERVER "login.messaging.aol.com"
 #define OSCAR_DEFAULT_LOGIN_PORT 5190
 #ifndef _WIN32
 #define OSCAR_DEFAULT_CUSTOM_ENCODING "ISO-8859-1"
--- a/libpurple/protocols/yahoo/yahoo.c	Mon Sep 03 01:07:47 2007 +0000
+++ b/libpurple/protocols/yahoo/yahoo.c	Mon Sep 03 01:09:08 2007 +0000
@@ -969,7 +969,6 @@
 	PurpleConnection *gc;
 	char *id;
 	char *who;
-	char *msg;
 	int protocol;
 };
 
@@ -987,7 +986,6 @@
 
 	g_free(add_req->id);
 	g_free(add_req->who);
-	g_free(add_req->msg);
 	g_free(add_req);
 }
 
@@ -1018,7 +1016,6 @@
 
 	g_free(add_req->id);
 	g_free(add_req->who);
-	g_free(add_req->msg);
 	g_free(add_req);
 }
 
@@ -1132,10 +1129,18 @@
 			l = l->next;
 		}
 
-		if (add_req->id) {
-			char *alias = NULL;
+		if (add_req->id && add_req->who) {
+			char *alias = NULL, *dec_msg = NULL;
+
+			if (!yahoo_privacy_check(gc, add_req->who)) {
+				purple_debug_misc("yahoo", "Auth. request from %s dropped and automatically denied due to privacy settings!\n",
+						  add_req->who);
+				yahoo_buddy_add_deny_cb(add_req, NULL);
+				return;
+			}
+
 			if (msg)
-				add_req->msg = yahoo_string_decode(gc, msg, FALSE);
+				dec_msg = yahoo_string_decode(gc, msg, FALSE);
 
 			if (firstname && lastname)
 				alias = g_strdup_printf("%s %s", firstname, lastname);
@@ -1144,20 +1149,19 @@
 			else if (lastname)
 				alias = g_strdup(lastname);
 
-
 			/* DONE! this is almost exactly the same as what MSN does,
 			 * this should probably be moved to the core.
 			 */
 			 purple_account_request_authorization(purple_connection_get_account(gc), add_req->who, add_req->id,
-						    alias, add_req->msg, purple_find_buddy(purple_connection_get_account(gc),add_req->who) != NULL,
+						    alias, dec_msg, purple_find_buddy(purple_connection_get_account(gc), add_req->who) != NULL,
 						    yahoo_buddy_add_authorize_cb,
 						    yahoo_buddy_add_deny_reason_cb,
 						    add_req);
 			g_free(alias);
+			g_free(dec_msg);
 		} else {
 			g_free(add_req->id);
 			g_free(add_req->who);
-			/*g_free(add_req->msg);*/
 			g_free(add_req);
 		}
 	} else {
@@ -1165,6 +1169,7 @@
 	}
 }
 
+/* I don't think this happens anymore in Version 15 */
 static void yahoo_buddy_added_us(PurpleConnection *gc, struct yahoo_packet *pkt) {
 	struct yahoo_add_request *add_req;
 	char *msg = NULL;
@@ -1192,22 +1197,31 @@
 		l = l->next;
 	}
 
-	if (add_req->id) {
+	if (add_req->id && add_req->who) {
+		char *dec_msg = NULL;
+
+		if (!yahoo_privacy_check(gc, add_req->who)) {
+			purple_debug_misc("yahoo", "Auth. request from %s dropped and automatically denied due to privacy settings!\n",
+					  add_req->who);
+			yahoo_buddy_add_deny_cb(add_req, NULL);
+			return;
+		}
+
 		if (msg)
-			add_req->msg = yahoo_string_decode(gc, msg, FALSE);
+			dec_msg = yahoo_string_decode(gc, msg, FALSE);
 
 		/* DONE! this is almost exactly the same as what MSN does,
 		 * this should probably be moved to the core.
 		 */
 		 purple_account_request_authorization(purple_connection_get_account(gc), add_req->who, add_req->id,
-                                                    NULL, add_req->msg, purple_find_buddy(purple_connection_get_account(gc),add_req->who) != NULL,
+                                                    NULL, dec_msg, purple_find_buddy(purple_connection_get_account(gc),add_req->who) != NULL,
 						    yahoo_buddy_add_authorize_cb,
 						    yahoo_buddy_add_deny_reason_cb,
                                                     add_req);
+		g_free(dec_msg);
 	} else {
 		g_free(add_req->id);
 		g_free(add_req->who);
-		/*g_free(add_req->msg);*/
 		g_free(add_req);
 	}
 }
@@ -3016,6 +3030,11 @@
 	if (yd->ycht)
 		ycht_connection_close(yd->ycht);
 
+	g_free(yd->pending_chat_room);
+	g_free(yd->pending_chat_id);
+	g_free(yd->pending_chat_topic);
+	g_free(yd->pending_chat_goto);
+
 	g_free(yd);
 	gc->proto_data = NULL;
 }
--- a/libpurple/protocols/yahoo/yahoo.h	Mon Sep 03 01:07:47 2007 +0000
+++ b/libpurple/protocols/yahoo/yahoo.h	Mon Sep 03 01:09:08 2007 +0000
@@ -130,6 +130,10 @@
 	gboolean chat_online;
 	gboolean in_chat;
 	char *chat_name;
+	char *pending_chat_room;
+	char *pending_chat_id;
+	char *pending_chat_topic;
+	char *pending_chat_goto;
 	char *auth;
 	gsize auth_written;
 	char *cookie_y;
--- a/libpurple/protocols/yahoo/yahoochat.c	Mon Sep 03 01:07:47 2007 +0000
+++ b/libpurple/protocols/yahoo/yahoochat.c	Mon Sep 03 01:09:08 2007 +0000
@@ -55,16 +55,24 @@
 {
 	struct yahoo_data *yd = gc->proto_data;
 	struct yahoo_packet *pkt;
+	const char *rll;
 
 	if (yd->wm) {
 		ycht_connection_open(gc);
 		return;
 	}
 
+	rll = purple_account_get_string(purple_connection_get_account(gc),
+								  "room_list_locale", YAHOO_ROOMLIST_LOCALE);
+
 	pkt = yahoo_packet_new(YAHOO_SERVICE_CHATONLINE, YAHOO_STATUS_AVAILABLE,0);
-	yahoo_packet_hash(pkt, "ssss", 1, purple_connection_get_display_name(gc),
-	                  109, purple_connection_get_display_name(gc), 6, "abcde",
-	                  135, "ym8.1.0.415");
+	yahoo_packet_hash(pkt, "sssss",
+					  109, purple_connection_get_display_name(gc),
+					  1, purple_connection_get_display_name(gc),
+					  6, "abcde",
+					/* I'm not sure this is the correct way to set this. */
+					  98, rll,
+					  135, "ym8.1.0.415");
 	yahoo_packet_send_and_free(pkt, yd);
 }
 
@@ -125,6 +133,7 @@
 		case 1: /* us, but we already know who we are */
 			break;
 		case 57:
+			g_free(room);
 			room = yahoo_string_decode(gc, pair->value, FALSE);
 			break;
 		case 50: /* inviter */
@@ -136,6 +145,7 @@
 			g_string_append_printf(members, "%s\n", pair->value);
 			break;
 		case 58:
+			g_free(msg);
 			msg = yahoo_string_decode(gc, pair->value, FALSE);
 			break;
 		case 13: /* ? */
@@ -145,6 +155,17 @@
 
 	if (!room) {
 		g_string_free(members, TRUE);
+		g_free(msg);
+		return;
+	}
+
+	if (!yahoo_privacy_check(gc, who) ||
+			(purple_account_get_bool(purple_connection_get_account(gc), "ignore_invites", FALSE))) {
+		purple_debug_info("yahoo",
+		    "Invite to conference %s from %s has been dropped.\n", room, who);
+		g_free(room);
+		g_free(msg);
+		g_string_free(members, TRUE);
 		return;
 	}
 
@@ -153,19 +174,9 @@
 	if (msg)
 		g_hash_table_replace(components, g_strdup("topic"), msg);
 	g_hash_table_replace(components, g_strdup("type"), g_strdup("Conference"));
-	if (members) {
-		g_hash_table_replace(components, g_strdup("members"), g_strdup(members->str));
-	}
-	if (!yahoo_privacy_check(gc, who) ||
-		(purple_account_get_bool(purple_connection_get_account(gc), "ignore_invites", FALSE))) {
-		purple_debug_info("yahoo",
-		    "Invite to conference %s from %s has been dropped.\n", room, who);
-		g_string_free(members, TRUE);
-		return;
-	}
+	g_hash_table_replace(components, g_strdup("members"), g_string_free(members, FALSE));
 	serv_got_chat_invite(gc, room, who, msg, components);
 
-	g_string_free(members, TRUE);
 }
 
 void yahoo_process_conference_decline(PurpleConnection *gc, struct yahoo_packet *pkt)
@@ -180,20 +191,21 @@
 
 		switch (pair->key) {
 		case 57:
+			g_free(room);
 			room = yahoo_string_decode(gc, pair->value, FALSE);
 			break;
 		case 54:
 			who = pair->value;
 			break;
 		case 14:
+			g_free(msg);
 			msg = yahoo_string_decode(gc, pair->value, FALSE);
 			break;
 		}
 	}
 	if (!yahoo_privacy_check(gc, who)) {
 		g_free(room);
-		if (msg != NULL)
-			g_free(msg);
+		g_free(msg);
 		return;
 	}
 
@@ -209,8 +221,7 @@
 		}
 
 		g_free(room);
-		if (msg)
-			g_free(msg);
+		g_free(msg);
 	}
 }
 
@@ -226,6 +237,7 @@
 
 		switch (pair->key) {
 		case 57:
+			g_free(room);
 			room = yahoo_string_decode(gc, pair->value, FALSE);
 			break;
 		case 53:
@@ -254,6 +266,7 @@
 
 		switch (pair->key) {
 		case 57:
+			g_free(room);
 			room = yahoo_string_decode(gc, pair->value, FALSE);
 			break;
 		case 56:
@@ -276,7 +289,6 @@
 	char *room = NULL;
 	char *who = NULL;
 	char *msg = NULL;
-	char *msg2;
 	int utf8 = 0;
 	PurpleConversation *c;
 
@@ -285,6 +297,7 @@
 
 		switch (pair->key) {
 		case 57:
+			g_free(room);
 			room = yahoo_string_decode(gc, pair->value, FALSE);
 			break;
 		case 3:
@@ -299,28 +312,82 @@
 		}
 	}
 
-		if (room && who && msg) {
-			msg2 = yahoo_string_decode(gc, msg, utf8);
-			c = yahoo_find_conference(gc, room);
-			if (!c)
-				return;
-			msg = yahoo_codes_to_html(msg2);
-			serv_got_chat_in(gc, purple_conv_chat_get_id(PURPLE_CONV_CHAT(c)), who, 0, msg, time(NULL));
-			g_free(msg);
-			g_free(msg2);
+	if (room && who && msg) {
+		char *msg2;
+
+		c = yahoo_find_conference(gc, room);
+		if (!c) {
+			g_free(room);
+			return;
 		}
-		if (room)
-			g_free(room);
+
+		msg2 = yahoo_string_decode(gc, msg, utf8);
+		msg = yahoo_codes_to_html(msg2);
+		serv_got_chat_in(gc, purple_conv_chat_get_id(PURPLE_CONV_CHAT(c)), who, 0, msg, time(NULL));
+		g_free(msg);
+		g_free(msg2);
+	}
+
+	g_free(room);
 }
 
+static void yahoo_chat_join(PurpleConnection *gc, const char *dn, const char *room, const char *topic, const char *id)
+{
+	struct yahoo_data *yd = gc->proto_data;
+	struct yahoo_packet *pkt;
+	char *room2;
+	gboolean utf8 = TRUE;
+
+	if (yd->wm) {
+		g_return_if_fail(yd->ycht != NULL);
+		ycht_chat_join(yd->ycht, room);
+		return;
+	}
+
+	/* apparently room names are always utf8, or else always not utf8,
+	 * so we don't have to actually pass the flag in the packet. Or something. */
+	room2 = yahoo_string_encode(gc, room, &utf8);
+
+	pkt = yahoo_packet_new(YAHOO_SERVICE_CHATJOIN, YAHOO_STATUS_AVAILABLE, 0);
+	yahoo_packet_hash(pkt, "ssss",
+						1, purple_connection_get_display_name(gc),
+						104, room2,
+						62, "2",
+						129, id ? id : "0");
+	yahoo_packet_send_and_free(pkt, yd);
+	g_free(room2);
+}
 
 /* this is a confirmation of yahoo_chat_online(); */
 void yahoo_process_chat_online(PurpleConnection *gc, struct yahoo_packet *pkt)
 {
 	struct yahoo_data *yd = (struct yahoo_data *) gc->proto_data;
 
-	if (pkt->status == 1)
+	if (pkt->status == 1) {
 		yd->chat_online = 1;
+
+		/* We need to goto a user in chat */
+		if (yd->pending_chat_goto) {
+			struct yahoo_packet *pkt = yahoo_packet_new(YAHOO_SERVICE_CHATGOTO, YAHOO_STATUS_AVAILABLE, 0);
+			yahoo_packet_hash(pkt, "sss",
+				109, yd->pending_chat_goto,
+				1, purple_connection_get_display_name(gc),
+				62, "2");
+			yahoo_packet_send_and_free(pkt, yd);
+		} else if (yd->pending_chat_room) {
+			yahoo_chat_join(gc, purple_connection_get_display_name(gc), yd->pending_chat_room,
+				yd->pending_chat_topic, yd->pending_chat_id);
+		}
+
+		g_free(yd->pending_chat_room);
+		yd->pending_chat_room = NULL;
+		g_free(yd->pending_chat_id);
+		yd->pending_chat_id = NULL;
+		g_free(yd->pending_chat_topic);
+		yd->pending_chat_topic = NULL;
+		g_free(yd->pending_chat_goto);
+		yd->pending_chat_goto = NULL;
+	}
 }
 
 /* this is basicly the opposite of chat_online */
@@ -340,6 +407,14 @@
 
 	if (pkt->status == 1) {
 		yd->chat_online = 0;
+		g_free(yd->pending_chat_room);
+		yd->pending_chat_room = NULL;
+		g_free(yd->pending_chat_id);
+		yd->pending_chat_id = NULL;
+		g_free(yd->pending_chat_topic);
+		yd->pending_chat_topic = NULL;
+		g_free(yd->pending_chat_goto);
+		yd->pending_chat_goto = NULL;
 		if (yd->in_chat)
 			yahoo_c_leave(gc, YAHOO_CHAT_ID);
 	}
@@ -384,9 +459,11 @@
 		switch (pair->key) {
 
 		case 104:
+			g_free(room);
 			room = yahoo_string_decode(gc, pair->value, TRUE);
 			break;
 		case 105:
+			g_free(topic);
 			topic = yahoo_string_decode(gc, pair->value, TRUE);
 			break;
 		case 128:
@@ -445,8 +522,11 @@
 			purple_conversation_set_name(c, room);
 
 			c = serv_got_joined_chat(gc, YAHOO_CHAT_ID, room);
-			if (topic)
+			if (topic) {
 				purple_conv_chat_set_topic(PURPLE_CONV_CHAT(c), NULL, topic);
+				/* Also print the topic to the backlog so that the captcha link is clickable */
+				purple_conv_chat_write(PURPLE_CONV_CHAT(c), "", topic, PURPLE_MESSAGE_SYSTEM, time(NULL));
+			}
 			yd->in_chat = 1;
 			yd->chat_name = g_strdup(room);
 			purple_conv_chat_add_users(PURPLE_CONV_CHAT(c), members, NULL, flags, FALSE);
@@ -456,14 +536,22 @@
 			g_free(tmpmsg);
 		} else {
 			c = serv_got_joined_chat(gc, YAHOO_CHAT_ID, room);
-			if (topic)
+			if (topic) {
 				purple_conv_chat_set_topic(PURPLE_CONV_CHAT(c), NULL, topic);
+				/* Also print the topic to the backlog so that the captcha link is clickable */
+				purple_conv_chat_write(PURPLE_CONV_CHAT(c), "", topic, PURPLE_MESSAGE_SYSTEM, time(NULL));
+			}
 			yd->in_chat = 1;
 			yd->chat_name = g_strdup(room);
 			purple_conv_chat_add_users(PURPLE_CONV_CHAT(c), members, NULL, flags, FALSE);
 		}
 		g_list_free(flags);
 	} else if (c) {
+		if (topic) {
+			const char *cur_topic = purple_conv_chat_get_topic(PURPLE_CONV_CHAT(c));
+			if (cur_topic == NULL || strcmp(cur_topic, topic) != 0)
+				purple_conv_chat_set_topic(PURPLE_CONV_CHAT(c), NULL, topic);
+		}
 		yahoo_chat_add_users(PURPLE_CONV_CHAT(c), members);
 	}
 
@@ -497,8 +585,10 @@
 	for (l = pkt->hash; l; l = l->next) {
 		struct yahoo_pair *pair = l->data;
 
-		if (pair->key == 104)
+		if (pair->key == 104) {
+			g_free(room);
 			room = yahoo_string_decode(gc, pair->value, TRUE);
+		}
 		if (pair->key == 109)
 			who = pair->value;
 	}
@@ -529,6 +619,7 @@
 			utf8 = strtol(pair->value, NULL, 10);
 			break;
 		case 104:
+			g_free(room);
 			room = yahoo_string_decode(gc, pair->value, TRUE);
 			break;
 		case 109:
@@ -583,6 +674,7 @@
 
 		switch (pair->key) {
 		case 104:
+			g_free(room);
 			room = yahoo_string_decode(gc, pair->value, TRUE);
 			break;
 		case 129: /* room id? */
@@ -590,6 +682,7 @@
 		case 126: /* ??? */
 			break;
 		case 117:
+			g_free(msg);
 			msg = yahoo_string_decode(gc, pair->value, FALSE);
 			break;
 		case 119:
@@ -603,24 +696,21 @@
 	if (room && who) {
 		GHashTable *components;
 
+		if (!yahoo_privacy_check(gc, who) ||
+				(purple_account_get_bool(purple_connection_get_account(gc), "ignore_invites", FALSE))) {
+			purple_debug_info("yahoo", "Invite to room %s from %s has been dropped.\n", room, who);
+			g_free(room);
+			g_free(msg);
+			return;
+		}
+
 		components = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
 		g_hash_table_replace(components, g_strdup("room"), g_strdup(room));
-		if (!yahoo_privacy_check(gc, who) ||
-			(purple_account_get_bool(purple_connection_get_account(gc), "ignore_invites", FALSE))) {
-			purple_debug_info("yahoo",
-			"Invite to room %s from %s has been dropped.\n", room, who);
-			if (room != NULL)
-				g_free(room);
-			if (msg != NULL)
-				g_free(msg);
-			return;
-		}
 		serv_got_chat_invite(gc, room, who, msg, components);
 	}
-	if (room)
-		g_free(room);
-	if (msg)
-		g_free(msg);
+
+	g_free(room);
+	g_free(msg);
 }
 
 void yahoo_process_chat_goto(PurpleConnection *gc, struct yahoo_packet *pkt)
@@ -783,6 +873,14 @@
 	yahoo_packet_send_and_free(pkt, yd);
 
 	yd->chat_online = 0;
+	g_free(yd->pending_chat_room);
+	yd->pending_chat_room = NULL;
+	g_free(yd->pending_chat_id);
+	yd->pending_chat_id = NULL;
+	g_free(yd->pending_chat_topic);
+	yd->pending_chat_topic = NULL;
+	g_free(yd->pending_chat_goto);
+	yd->pending_chat_goto = NULL;
 	g_free(eroom);
 }
 
@@ -829,29 +927,6 @@
 	return 0;
 }
 
-static void yahoo_chat_join(PurpleConnection *gc, const char *dn, const char *room, const char *topic)
-{
-	struct yahoo_data *yd = gc->proto_data;
-	struct yahoo_packet *pkt;
-	char *room2;
-	gboolean utf8 = TRUE;
-
-	if (yd->wm) {
-		g_return_if_fail(yd->ycht != NULL);
-		ycht_chat_join(yd->ycht, room);
-		return;
-	}
-
-	/* apparently room names are always utf8, or else always not utf8,
-	 * so we don't have to actually pass the flag in the packet. Or something. */
-	room2 = yahoo_string_encode(gc, room, &utf8);
-
-	pkt = yahoo_packet_new(YAHOO_SERVICE_CHATJOIN, YAHOO_STATUS_AVAILABLE, 0);
-	yahoo_packet_hash(pkt, "ssss", 1, purple_connection_get_display_name(gc),
-	                  62, "2", 104, room2, 129, "0");
-	yahoo_packet_send_and_free(pkt, yd);
-	g_free(room2);
-}
 
 static void yahoo_chat_invite(PurpleConnection *gc, const char *dn, const char *buddy,
 							const char *room, const char *msg)
@@ -892,8 +967,18 @@
 		return;
 	}
 
-	if (!yd->chat_online)
+	if (!yd->chat_online) {
 		yahoo_chat_online(gc);
+		g_free(yd->pending_chat_room);
+		yd->pending_chat_room = NULL;
+		g_free(yd->pending_chat_id);
+		yd->pending_chat_id = NULL;
+		g_free(yd->pending_chat_topic);
+		yd->pending_chat_topic = NULL;
+		g_free(yd->pending_chat_goto);
+		yd->pending_chat_goto = g_strdup(name);
+		return;
+	}
 
 	pkt = yahoo_packet_new(YAHOO_SERVICE_CHATGOTO, YAHOO_STATUS_AVAILABLE, 0);
 	yahoo_packet_hash(pkt, "sss", 109, name, 1, purple_connection_get_display_name(gc), 62, "2");
@@ -988,8 +1073,7 @@
 void yahoo_c_join(PurpleConnection *gc, GHashTable *data)
 {
 	struct yahoo_data *yd;
-	char *room, *topic, *members, *type;
-	int id;
+	char *room, *topic, *type;
 	PurpleConversation *c;
 
 	yd = (struct yahoo_data *) gc->proto_data;
@@ -1004,9 +1088,9 @@
 	if (!topic)
 		topic = "";
 
-	members = g_hash_table_lookup(data, "members");
-
 	if ((type = g_hash_table_lookup(data, "type")) && !strcmp(type, "Conference")) {
+		int id;
+		const char *members = g_hash_table_lookup(data, "members");
 		id = yd->conf_id++;
 		c = serv_got_joined_chat(gc, id, room);
 		yd->confs = g_slist_prepend(yd->confs, c);
@@ -1014,13 +1098,27 @@
 		yahoo_conf_join(yd, c, purple_connection_get_display_name(gc), room, topic, members);
 		return;
 	} else {
-		if (yd->in_chat)
+		const char *id;
+		/*if (yd->in_chat)
 			yahoo_chat_leave(gc, room,
 					purple_connection_get_display_name(gc),
-					FALSE);
-		if (!yd->chat_online)
+					FALSE);*/
+
+		id = g_hash_table_lookup(data, "id");
+
+		if (!yd->chat_online) {
 			yahoo_chat_online(gc);
-		yahoo_chat_join(gc, purple_connection_get_display_name(gc), room, topic);
+			g_free(yd->pending_chat_room);
+			yd->pending_chat_room = g_strdup(room);
+			g_free(yd->pending_chat_id);
+			yd->pending_chat_id = g_strdup(id);
+			g_free(yd->pending_chat_topic);
+			yd->pending_chat_topic = g_strdup(topic);
+			g_free(yd->pending_chat_goto);
+			yd->pending_chat_goto = NULL;
+		} else {
+			yahoo_chat_join(gc, purple_connection_get_display_name(gc), room, topic, id);
+		}
 		return;
 	}
 }
@@ -1148,16 +1246,13 @@
 
 		for (i = 0; anames[i]; i++) {
 			if (!strcmp(anames[i], "id")) {
-				if (s->room.id)
-					g_free(s->room.id);
+				g_free(s->room.id);
 				s->room.id = g_strdup(avalues[i]);
 			} else if (!strcmp(anames[i], "name")) {
-				if (s->room.name)
-					g_free(s->room.name);
+				g_free(s->room.name);
 				s->room.name = g_strdup(avalues[i]);
 			} else if (!strcmp(anames[i], "topic")) {
-				if (s->room.topic)
-					g_free(s->room.topic);
+				g_free(s->room.topic);
 				s->room.topic = g_strdup(avalues[i]);
 			} else if (!strcmp(anames[i], "type")) {
 				if (!strcmp("yahoo", avalues[i]))
--- a/pidgin/gtkblist.c	Mon Sep 03 01:07:47 2007 +0000
+++ b/pidgin/gtkblist.c	Mon Sep 03 01:09:08 2007 +0000
@@ -125,7 +125,7 @@
 static PidginBuddyList *gtkblist = NULL;
 
 static gboolean pidgin_blist_refresh_timer(PurpleBuddyList *list);
-static void pidgin_blist_update_buddy(PurpleBuddyList *list, PurpleBlistNode *node, gboolean statusChange);
+static void pidgin_blist_update_buddy(PurpleBuddyList *list, PurpleBlistNode *node, gboolean status_change);
 static void pidgin_blist_selection_changed(GtkTreeSelection *selection, gpointer data);
 static void pidgin_blist_update(PurpleBuddyList *list, PurpleBlistNode *node);
 static void pidgin_blist_update_group(PurpleBuddyList *list, PurpleBlistNode *node);
@@ -614,6 +614,8 @@
 static void
 pidgin_blist_update_privacy_cb(PurpleBuddy *buddy)
 {
+	if (buddy->node.ui_data == NULL || ((struct _pidgin_blist_node*)buddy->node.ui_data)->row == NULL)
+		return;
 	pidgin_blist_update_buddy(purple_get_blist(), (PurpleBlistNode*)(buddy), TRUE);
 }
 
@@ -2620,6 +2622,7 @@
 	struct _pidgin_blist_node *gtknode;
 	GdkRectangle mon_size;
 	int sig;
+	const char *name;
 	
 	if (node == NULL)
 		return;
@@ -2677,7 +2680,9 @@
 
 	gtknode = node->ui_data;
 
+	name = gtk_window_get_title(GTK_WINDOW(gtk_widget_get_toplevel(widget)));
 	gtk_widget_set_app_paintable(gtkblist->tipwindow, TRUE);
+	gtk_window_set_title(GTK_WINDOW(gtkblist->tipwindow), name ? name : _("Buddy List"));
 	gtk_window_set_resizable(GTK_WINDOW(gtkblist->tipwindow), FALSE);
 	gtk_widget_set_name(gtkblist->tipwindow, "gtk-tooltips");
 	g_signal_connect(G_OBJECT(gtkblist->tipwindow), "expose_event",
@@ -3207,6 +3212,7 @@
 {
 	GdkPixbuf *ret;
 	const char *protoname = NULL;
+	const char *icon = NULL;
 	struct _pidgin_blist_node *gtknode = node->ui_data;
 	struct _pidgin_blist_node *gtkbuddynode = NULL;
 	PurpleBuddy *buddy = NULL;
@@ -3255,62 +3261,54 @@
 									     purple_buddy_get_name(buddy),
 									     purple_buddy_get_account(buddy));
 		PurplePresence *p;
+		gboolean trans;
+
 		if(conv != NULL) {
 			PidginConversation *gtkconv = PIDGIN_CONVERSATION(conv);
-			if(gtkconv != NULL && pidgin_conv_is_hidden(gtkconv) && size == PIDGIN_STATUS_ICON_SMALL) {
+			if((gtkconv == NULL || pidgin_conv_is_hidden(gtkconv)) && size == PIDGIN_STATUS_ICON_SMALL) {
 				return gtk_widget_render_icon (GTK_WIDGET(gtkblist->treeview), PIDGIN_STOCK_STATUS_MESSAGE,
 							       icon_size, "GtkTreeView");
 			}
 		}
+
 		p = purple_buddy_get_presence(buddy);
+		trans = (purple_presence_is_idle(p) && size == PIDGIN_STATUS_ICON_SMALL);
 
 		if (PURPLE_BUDDY_IS_ONLINE(buddy) && gtkbuddynode && gtkbuddynode->recent_signonoff)
-			ret = gtk_widget_render_icon (GTK_WIDGET(gtkblist->treeview), PIDGIN_STOCK_STATUS_LOGIN,
-					icon_size, "GtkTreeView");
+			icon = PIDGIN_STOCK_STATUS_LOGIN;
 		else if (gtkbuddynode && gtkbuddynode->recent_signonoff)
-			ret = gtk_widget_render_icon (GTK_WIDGET(gtkblist->treeview), PIDGIN_STOCK_STATUS_LOGOUT,
-					icon_size, "GtkTreeView");
+			icon = PIDGIN_STOCK_STATUS_LOGOUT;
 		else if (purple_presence_is_status_primitive_active(p, PURPLE_STATUS_UNAVAILABLE))
-			if (purple_presence_is_idle(p) && size == PIDGIN_STATUS_ICON_SMALL)
-				ret = gtk_widget_render_icon (GTK_WIDGET(gtkblist->treeview), PIDGIN_STOCK_STATUS_BUSY_I,
-						icon_size, "GtkTreeView");
+			if (trans)
+				icon = PIDGIN_STOCK_STATUS_BUSY_I;
 			else
-				ret = gtk_widget_render_icon (GTK_WIDGET(gtkblist->treeview), PIDGIN_STOCK_STATUS_BUSY,
-						icon_size, "GtkTreeView");
+				icon = PIDGIN_STOCK_STATUS_BUSY;
 		else if (purple_presence_is_status_primitive_active(p, PURPLE_STATUS_AWAY))
-		        if (purple_presence_is_idle(p) && size == PIDGIN_STATUS_ICON_SMALL)
-		                ret = gtk_widget_render_icon (GTK_WIDGET(gtkblist->treeview), PIDGIN_STOCK_STATUS_AWAY_I,
-		                                icon_size, "GtkTreeView");
+			if (trans)
+				icon = PIDGIN_STOCK_STATUS_AWAY_I;
 		 	else
-				ret = gtk_widget_render_icon (GTK_WIDGET(gtkblist->treeview), PIDGIN_STOCK_STATUS_AWAY,
-						icon_size, "GtkTreeView");
+				icon = PIDGIN_STOCK_STATUS_AWAY;
 		else if (purple_presence_is_status_primitive_active(p, PURPLE_STATUS_EXTENDED_AWAY))
-			if (purple_presence_is_idle(p) && size == PIDGIN_STATUS_ICON_SMALL)
-		        	ret = gtk_widget_render_icon (GTK_WIDGET(gtkblist->treeview), PIDGIN_STOCK_STATUS_XA_I,
-						icon_size, "GtkTreeView");
+			if (trans)
+				icon = PIDGIN_STOCK_STATUS_XA_I;
 			else
-				ret = gtk_widget_render_icon (GTK_WIDGET(gtkblist->treeview), PIDGIN_STOCK_STATUS_XA,
-						icon_size, "GtkTreeView");
+				icon = PIDGIN_STOCK_STATUS_XA;
 		else if (purple_presence_is_status_primitive_active(p, PURPLE_STATUS_OFFLINE))
-			ret = gtk_widget_render_icon (GTK_WIDGET(gtkblist->treeview), PIDGIN_STOCK_STATUS_OFFLINE,
-					icon_size, "GtkTreeView");
-		else if (purple_presence_is_idle(p) && size == PIDGIN_STATUS_ICON_SMALL)
-			ret = gtk_widget_render_icon (GTK_WIDGET(gtkblist->treeview), PIDGIN_STOCK_STATUS_AVAILABLE_I,
-					icon_size, "GtkTreeView");
+			icon = PIDGIN_STOCK_STATUS_OFFLINE;
+		else if (trans)
+			icon = PIDGIN_STOCK_STATUS_AVAILABLE_I;
 		else if (purple_presence_is_status_primitive_active(p, PURPLE_STATUS_INVISIBLE))
-			ret = gtk_widget_render_icon(GTK_WIDGET(gtkblist->treeview), PIDGIN_STOCK_STATUS_INVISIBLE,
-					icon_size, "GtkTreeView");
+			icon = PIDGIN_STOCK_STATUS_INVISIBLE;
 		else
-			ret = gtk_widget_render_icon(GTK_WIDGET(gtkblist->treeview), PIDGIN_STOCK_STATUS_AVAILABLE,
-					icon_size, "GtkTreeView");
+			icon = PIDGIN_STOCK_STATUS_AVAILABLE;
 	} else if (chat) {
-		ret = gtk_widget_render_icon (GTK_WIDGET(gtkblist->treeview), PIDGIN_STOCK_STATUS_CHAT,
-				icon_size, "GtkTreeView");
+		icon = PIDGIN_STOCK_STATUS_CHAT;
 	} else {
-		ret = gtk_widget_render_icon (GTK_WIDGET(gtkblist->treeview), PIDGIN_STOCK_STATUS_PERSON,
-				icon_size, "GtkTreeView");
-	}
-
+		icon = PIDGIN_STOCK_STATUS_PERSON;
+	}
+
+	ret = gtk_widget_render_icon (GTK_WIDGET(gtkblist->treeview), icon,
+			icon_size, "GtkTreeView");
 	return ret;
 }
 
@@ -3333,7 +3331,7 @@
 
 	if(conv != NULL) {
 		gtkconv = PIDGIN_CONVERSATION(conv);
-		if(gtkconv != NULL && pidgin_conv_is_hidden(gtkconv)) {
+		if(gtkconv == NULL || pidgin_conv_is_hidden(gtkconv)) {
 			hidden_conv = TRUE;
 		}
 	}
@@ -5158,7 +5156,7 @@
 
 
 
-static void pidgin_blist_update_buddy(PurpleBuddyList *list, PurpleBlistNode *node, gboolean statusChange)
+static void pidgin_blist_update_buddy(PurpleBuddyList *list, PurpleBlistNode *node, gboolean status_change)
 {
 	PurpleBuddy *buddy;
 	struct _pidgin_blist_node *gtkparentnode;
@@ -5211,6 +5209,10 @@
 		GdkPixbuf *emblem;
 		char *mark;
 		gboolean showicons = purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/show_buddy_icons");
+		const char *name = purple_chat_get_name(chat);
+		PurpleConversation *conv =
+				purple_find_conversation_with_account(PURPLE_CONV_TYPE_CHAT, name, chat->account);
+		gboolean hidden = (conv && !PIDGIN_CONVERSATION(conv));
 
 		if(!insert_node(list, node, &iter))
 			return;
@@ -5226,15 +5228,20 @@
 			avatar = NULL;
 
 		mark = g_markup_escape_text(purple_chat_get_name(chat), -1);
+		if (hidden) {
+			char *bold = g_strdup_printf("<b>%s</b>", mark);
+			g_free(mark);
+			mark = bold;
+		}
 
 		gtk_tree_store_set(gtkblist->treemodel, &iter,
 				STATUS_ICON_COLUMN, status,
 				STATUS_ICON_VISIBLE_COLUMN, TRUE,
 				BUDDY_ICON_COLUMN, avatar ? avatar : gtkblist->empty_avatar,
 				BUDDY_ICON_VISIBLE_COLUMN,  purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/show_buddy_icons"),
-			        EMBLEM_COLUMN, emblem,
+				EMBLEM_COLUMN, emblem,
 				EMBLEM_VISIBLE_COLUMN, emblem != NULL,
-		 	        PROTOCOL_ICON_COLUMN, pidgin_create_prpl_icon(chat->account, PIDGIN_PRPL_ICON_SMALL),
+				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,
--- a/pidgin/gtkconv.c	Mon Sep 03 01:07:47 2007 +0000
+++ b/pidgin/gtkconv.c	Mon Sep 03 01:09:08 2007 +0000
@@ -1303,7 +1303,10 @@
 menu_hide_conv_cb(gpointer data, guint action, GtkWidget *widget)
 {
 	PidginWindow *win = data;
+	PidginConversation *gtkconv = pidgin_conv_window_get_active_gtkconv(win);
 	PurpleConversation *conv = pidgin_conv_window_get_active_conversation(win);
+	purple_signal_emit(pidgin_conversations_get_handle(),
+			"conversation-hiding", gtkconv);
 	purple_conversation_set_ui_ops(conv, NULL);
 }
 
@@ -5622,6 +5625,7 @@
 		account, name, displaying, conv, flags);
 	g_free(displaying);
 }
+
 static void
 pidgin_conv_chat_add_users(PurpleConversation *conv, GList *cbuddies, gboolean new_arrivals)
 {
@@ -7206,6 +7210,8 @@
 	if (gtkconv->attach.current)
 		return TRUE;
 
+	purple_signal_emit(pidgin_conversations_get_handle(),
+			"conversation-displayed", gtkconv);
 	g_source_remove(gtkconv->attach.timer);
 	gtkconv->attach.timer = 0;
 	return FALSE;
@@ -7228,12 +7234,15 @@
 		list = g_list_last(list);
 		gtkconv->attach.current = list;
 		gtkconv->attach.timer = g_idle_add(add_message_history_to_gtkconv, gtkconv);
-	}
-
-	/* XXX: If this is a chat:
-	 * 	- populate the userlist
-	 * 	- set the topic
-	 */
+	} else {
+		purple_signal_emit(pidgin_conversations_get_handle(),
+				"conversation-displayed", gtkconv);
+	}
+
+	if (conv->type == PURPLE_CONV_TYPE_CHAT) {
+		pidgin_conv_update_fields(conv, PIDGIN_CONV_TOPIC);
+		pidgin_conv_chat_add_users(conv, PURPLE_CONV_CHAT(conv)->in_room, TRUE);
+	}
 
 	return TRUE;
 }
@@ -7415,6 +7424,16 @@
 						 purple_value_new(PURPLE_TYPE_SUBTYPE,
 										PURPLE_SUBTYPE_CONVERSATION));
 
+	purple_signal_register(handle, "conversation-hiding",
+						 purple_marshal_VOID__POINTER_POINTER, NULL, 1,
+						 purple_value_new(PURPLE_TYPE_BOXED,
+										"PidginConversation *"));
+
+	purple_signal_register(handle, "conversation-displayed",
+						 purple_marshal_VOID__POINTER_POINTER, NULL, 1,
+						 purple_value_new(PURPLE_TYPE_BOXED,
+										"PidginConversation *"));
+
 	/**********************************************************************
 	 * Register commands
 	 **********************************************************************/
--- a/pidgin/gtkconv.h	Mon Sep 03 01:07:47 2007 +0000
+++ b/pidgin/gtkconv.h	Mon Sep 03 01:09:08 2007 +0000
@@ -245,6 +245,13 @@
  */
 void pidgin_conv_present_conversation(PurpleConversation *conv);
 
+/**
+ * Reattach Pidgin UI to a conversation.
+ *
+ * @param conv  The conversation.
+ *
+ * @return  Wheter Pidgin UI was successfully attached.
+ */
 gboolean pidgin_conv_attach_to_conversation(PurpleConversation *conv);
 
 PidginWindow *pidgin_conv_get_window(PidginConversation *gtkconv);
--- a/pidgin/gtkimhtmltoolbar.c	Mon Sep 03 01:07:47 2007 +0000
+++ b/pidgin/gtkimhtmltoolbar.c	Mon Sep 03 01:09:08 2007 +0000
@@ -786,6 +786,7 @@
 	gtk_widget_set_sensitive(GTK_WIDGET(toolbar->bold), buttons & GTK_IMHTML_BOLD);
 	gtk_widget_set_sensitive(GTK_WIDGET(toolbar->italic), buttons & GTK_IMHTML_ITALIC);
 	gtk_widget_set_sensitive(GTK_WIDGET(toolbar->underline), buttons & GTK_IMHTML_UNDERLINE);
+	gtk_widget_set_sensitive(GTK_WIDGET(toolbar->strikethrough), buttons & GTK_IMHTML_STRIKE);
 
 	gtk_widget_set_sensitive(GTK_WIDGET(toolbar->larger_size), buttons & GTK_IMHTML_GROW);
 	gtk_widget_set_sensitive(GTK_WIDGET(toolbar->smaller_size), buttons & GTK_IMHTML_SHRINK);
@@ -798,6 +799,7 @@
 							 (buttons & GTK_IMHTML_BOLD ||
 							  buttons & GTK_IMHTML_ITALIC ||
 							  buttons & GTK_IMHTML_UNDERLINE ||
+							  buttons & GTK_IMHTML_STRIKE ||
 							  buttons & GTK_IMHTML_GROW ||
 							  buttons & GTK_IMHTML_SHRINK ||
 							  buttons & GTK_IMHTML_FACE ||
@@ -831,7 +833,7 @@
 
 static void update_buttons(GtkIMHtmlToolbar *toolbar)
 {
-	gboolean bold, italic, underline;
+	gboolean bold, italic, underline, strike;
 	char *tmp;
 	char *tmp2;
 	GtkLabel *label = g_object_get_data(G_OBJECT(toolbar), "font_label");
@@ -840,6 +842,7 @@
 
 	gtk_imhtml_get_current_format(GTK_IMHTML(toolbar->imhtml),
 								  &bold, &italic, &underline);
+	strike = GTK_IMHTML(toolbar->imhtml)->edit.strike;
 
 	if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(toolbar->bold)) != bold)
 		toggle_button_set_active_block(GTK_TOGGLE_BUTTON(toolbar->bold), bold,
@@ -847,10 +850,12 @@
 	if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(toolbar->italic)) != italic)
 		toggle_button_set_active_block(GTK_TOGGLE_BUTTON(toolbar->italic), italic,
 									   toolbar);
-
 	if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(toolbar->underline)) != underline)
 		toggle_button_set_active_block(GTK_TOGGLE_BUTTON(toolbar->underline),
 									   underline, toolbar);
+	if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(toolbar->strikethrough)) != strike)
+		toggle_button_set_active_block(GTK_TOGGLE_BUTTON(toolbar->strikethrough),
+									   strike, toolbar);
 
 	/* These buttons aren't ever "active". */
 	gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(toolbar->smaller_size), FALSE);
@@ -874,6 +879,12 @@
 		gtk_label_set_markup_with_mnemonic(label, markup);
 		g_free(markup);
 	}
+	if (strike) {
+		gchar *markup = g_strdup_printf("<s>%s</s>",
+				gtk_label_get_label(label));
+		gtk_label_set_markup_with_mnemonic(label, markup);
+		g_free(markup);
+	}
 
 	tmp = gtk_imhtml_get_current_fontface(GTK_IMHTML(toolbar->imhtml));
 	toggle_button_set_active_block(GTK_TOGGLE_BUTTON(toolbar->font),
--- a/pidgin/gtksourceundomanager.c	Mon Sep 03 01:07:47 2007 +0000
+++ b/pidgin/gtksourceundomanager.c	Mon Sep 03 01:09:08 2007 +0000
@@ -677,8 +677,6 @@
 	if (um->priv->running_not_undoable_actions > 0)
 		return;
 
-	g_return_if_fail (strlen (text) >= (guint)length);
-	
 	undo_action.action_type = GTK_SOURCE_UNDO_ACTION_INSERT;
 
 	undo_action.action.insert.pos    = gtk_text_iter_get_offset (pos);
@@ -774,7 +772,7 @@
 		*action = *undo_action;
 
 		if (action->action_type == GTK_SOURCE_UNDO_ACTION_INSERT)
-			action->action.insert.text = g_strdup (undo_action->action.insert.text);
+			action->action.insert.text = g_strndup (undo_action->action.insert.text, undo_action->action.insert.length);
 		else if (action->action_type == GTK_SOURCE_UNDO_ACTION_DELETE)
 			action->action.delete.text = g_strdup (undo_action->action.delete.text); 
 		else
--- a/pidgin/plugins/notify.c	Mon Sep 03 01:07:47 2007 +0000
+++ b/pidgin/plugins/notify.c	Mon Sep 03 01:09:08 2007 +0000
@@ -167,7 +167,7 @@
 	gboolean has_focus;
 	PidginWindow *purplewin = NULL;
 
-	if (conv == NULL)
+	if (conv == NULL || PIDGIN_CONVERSATION(conv) == NULL)
 		return 0;
 
 	/* We want to remove the notifications, but not reset the counter */
@@ -224,6 +224,8 @@
 	PidginWindow *purplewin = NULL;
 
 	g_return_if_fail(conv != NULL);
+	if (PIDGIN_CONVERSATION(conv) == NULL)
+		return;
 
 	purplewin = PIDGIN_CONVERSATION(conv)->win;
 	active_conv = pidgin_conv_window_get_active_conversation(purplewin);
@@ -417,10 +419,14 @@
 deleting_conv(PurpleConversation *conv)
 {
 	PidginWindow *purplewin = NULL;
+	PidginConversation *gtkconv = PIDGIN_CONVERSATION(conv);
+
+	if (gtkconv == NULL)
+		return;
 
 	detach_signals(conv);
 
-	purplewin = PIDGIN_CONVERSATION(conv)->win;
+	purplewin = gtkconv->win;
 
 	handle_urgent(purplewin, FALSE);
 	purple_conversation_set_data(conv, "notify-message-count", GINT_TO_POINTER(0));
--- a/pidgin/win32/nsis/pidgin-installer.nsi	Mon Sep 03 01:07:47 2007 +0000
+++ b/pidgin/win32/nsis/pidgin-installer.nsi	Mon Sep 03 01:09:08 2007 +0000
@@ -693,6 +693,8 @@
     ; Remove Language preference info (TODO: check if NSIS removes this)
 
     Delete "$INSTDIR\ca-certs\Equifax_Secure_CA.pem"
+    Delete "$INSTDIR\ca-certs\GTE_CyberTrust_Global_Root.pem"
+    Delete "$INSTDIR\ca-certs\Verisign_Class3_Primary_CA.pem"
     Delete "$INSTDIR\ca-certs\Verisign_RSA_Secure_Server_CA.pem"
     RMDir "$INSTDIR\ca-certs"
     RMDir /r "$INSTDIR\locale"
--- a/share/ca-certs/Makefile.am	Mon Sep 03 01:07:47 2007 +0000
+++ b/share/ca-certs/Makefile.am	Mon Sep 03 01:09:08 2007 +0000
@@ -5,6 +5,7 @@
 		Verisign_RSA_Secure_Server_CA.pem \
 		Verisign_Class3_Primary_CA.pem
 
-EXTRA_DIST = \
-                $(cacerts_DATA)
+EXTRA_DIST =	\
+		Makefile.mingw \
+		$(cacerts_DATA)