changeset 29216:7819d3819f4c

propagate from branch 'im.pidgin.pidgin' (head 0d33eae1cfd57cb5f459518ad5db14230f13792b) to branch 'im.pidgin.pidgin.next.minor' (head b5b9f7898db2efebf9a130b493f8448adab61d20)
author maiku@pidgin.im
date Wed, 21 Oct 2009 18:09:47 +0000
parents d1c18bd588e2 (diff) 8a50c08c289d (current diff)
children a78a44f0cdcd
files ChangeLog ChangeLog.API ChangeLog.win32 configure.ac libpurple/dnssrv.c libpurple/media.c libpurple/protocols/jabber/jabber.c libpurple/protocols/jabber/presence.c libpurple/protocols/yahoo/libymsg.c pidgin/gtkcelllayout.c pidgin/gtkcelllayout.h pidgin/gtkcellrendererprogress.c pidgin/gtkcellrendererprogress.h pidgin/gtkcellview.c pidgin/gtkcellview.h pidgin/gtkcellviewmenuitem.c pidgin/gtkcellviewmenuitem.h pidgin/gtkexpander.c pidgin/gtkexpander.h pidgin/gtkutils.c pidgin/pidgincombobox.c pidgin/pidgincombobox.h po/ChangeLog
diffstat 23 files changed, 323 insertions(+), 64 deletions(-) [+]
line wrap: on
line diff
--- a/COPYRIGHT	Tue Oct 20 21:39:16 2009 +0000
+++ b/COPYRIGHT	Wed Oct 21 18:09:47 2009 +0000
@@ -406,11 +406,10 @@
 Carsten Schaar
 Toby Schaffer
 Jonathan Schleifer <js-pidgin@webkeks.org>
-Matteo Settenvini
-Colin Seymour
 Luke Schierer
 Ralph Schmieder
 David Schmitt
+Heiko Schmitt
 Mark Schneider
 Evan Schoenberg
 Gabriel Schulhof
@@ -420,6 +419,8 @@
 Peter Seebach
 Don Seiler
 Leonardo Serra
+Matteo Settenvini
+Colin Seymour
 Jim Seymour
 Javeed Shaikh
 Joe Shaw
--- a/ChangeLog	Tue Oct 20 21:39:16 2009 +0000
+++ b/ChangeLog	Wed Oct 21 18:09:47 2009 +0000
@@ -13,16 +13,19 @@
 	* Moved the Translator information from the About box to a "Translator
 	  Information" dialog accessible on the Help menu.
 
-version 2.6.3 (??/??/20??):
+version 2.6.4 (??/??/20??):
+	libpurple:
+	* Actually emit the hold signal for media calls.
+
 	General:
 	* New 'plugins' sub-command to 'debug' command (i.e. '/debug plugins')
 	  to announce the list of loaded plugins (in both Finch and Pidgin).
-	* Fix a crash when performing DNS queries on Unixes that use the
-	  blocking DNS lookups.  (Brian Lu)
 	* Fix building the GnuTLS plugin with older versions of GnuTLS.
+	* Fix DNS TXT query resolution.
 
 	MSN:
 	* Don't forget display names for buddies.
+	* Fix a random crash that might occur when idle.
 
 	XMPP:
 	* Users connecting to Google Talk now have an "Initiate Chat" context menu
@@ -39,6 +42,7 @@
 	* Add support for adding OCS and Sametime buddies.  OCS users are added
 	  as "ocs/user@domain.tld" and Sametime users are added as
 	  "ibm/sametime_id".  (Jason Cohen)
+
 	Finch:
 	* The TinyURL plugin now creates shorter URLs for long non-conversation
 	  URLs, e.g. URLs to open Inbox in Yahoo/MSN protocols, or the Yahoo
@@ -46,7 +50,18 @@
 
 	Pidgin:
 	* The userlist in a multiuser chat can be styled via gtkrc by using the
-	  widget name "pidgin_conv_userlist".
+	  widget name "pidgin_conv_userlist". (Heiko Schmitt)
+	* Add a hold button to the media window.
+
+version 2.6.3 (10/16/2009):
+	General:
+	* Fix a crash when performing DNS queries on Unixes that use the
+	  blocking DNS lookups.  (Brian Lu)
+
+	AIM and ICQ:
+	* Fix a crash when some clients send contacts in a format we don't
+	  understand.
+	* Fix blocking and other privacy lists.  (Thanks to AOL)
 
 version 2.6.2 (09/05/2009):
 	libpurple:
--- a/ChangeLog.API	Tue Oct 20 21:39:16 2009 +0000
+++ b/ChangeLog.API	Wed Oct 21 18:09:47 2009 +0000
@@ -17,6 +17,9 @@
 		* pidgin_dialogs_developers (should not be used by anything but Pidgin)
 		* pidgin_dialogs_translators (should not be used by anything but Pidgin)
 
+version 2.6.3 (10/16/2009):
+	No changes
+
 version 2.6.2 (09/05/2009):
 	Perl:
 		Added:
--- a/ChangeLog.win32	Tue Oct 20 21:39:16 2009 +0000
+++ b/ChangeLog.win32	Wed Oct 21 18:09:47 2009 +0000
@@ -2,6 +2,9 @@
 	* Minimum required GTK+ version increased to 2.14.0
 	* Win9x no longer supported.
 
+version 2.6.3 (10/16/2009):
+	* No changes
+
 version 2.6.2 (09/05/2009):
 	* No changes
 
--- a/NEWS	Tue Oct 20 21:39:16 2009 +0000
+++ b/NEWS	Wed Oct 21 18:09:47 2009 +0000
@@ -2,6 +2,11 @@
 
 Our development blog is available at: http://planet.pidgin.im
 
+2.6.3 (10/16/2009):
+	Mark: Someone reported a fairly serious bug in our AIM/ICQ code
+	so we're releasing a special "severe bug fix only" build.  See the
+	ChangeLog for details.  Enjoy!
+
 2.6.2 (09/05/2009):
 	Mark: Woo boy it's been a busy two weeks.  There was a lot of new code
 	in 2.6.0, and with new code comes new bugs.  The cadre of relentless
--- a/libpurple/certificate.c	Tue Oct 20 21:39:16 2009 +0000
+++ b/libpurple/certificate.c	Wed Oct 21 18:09:47 2009 +0000
@@ -1402,13 +1402,15 @@
 		if (flags & PURPLE_CERTIFICATE_NAME_MISMATCH) {
 			gchar *sn = purple_certificate_get_subject_name(peer_crt);
 
-			g_string_append_printf(errors, _("The certificate claims to be "
-						"from \"%s\" instead. This could mean that you are "
-						"not connecting to the service you believe you are."),
-						sn);
-			g_free(sn);
+			if (sn) {
+				g_string_append_printf(errors, _("The certificate claims to be "
+							"from \"%s\" instead. This could mean that you are "
+							"not connecting to the service you believe you are."),
+							sn);
+				g_free(sn);
 
-			flags &= ~PURPLE_CERTIFICATE_NAME_MISMATCH;
+				flags &= ~PURPLE_CERTIFICATE_NAME_MISMATCH;
+			}
 		}
 
 		while (i != PURPLE_CERTIFICATE_LAST) {
--- a/libpurple/dnssrv.c	Tue Oct 20 21:39:16 2009 +0000
+++ b/libpurple/dnssrv.c	Wed Oct 21 18:09:47 2009 +0000
@@ -363,8 +363,12 @@
 		/* TODO: Check return value */
 		if (query.type == T_SRV)
 			write(out, ret->data, sizeof(PurpleSrvResponse));
-		if (query.type == T_TXT)
-			write(out, ret->data, sizeof(PurpleTxtResponse));
+		if (query.type == T_TXT) {
+			PurpleTxtResponse *response = ret->data;
+			gsize l = strlen(response->content) + 1 /* null byte */;
+			write(out, &l, sizeof(l));
+			write(out, response->content, l);
+		}
 
 		g_free(ret->data);
 		ret = g_list_remove(ret, ret->data);
@@ -431,21 +435,38 @@
 					PurpleTxtCallback cb = query_data->cb.txt;
 					ssize_t red;
 					purple_debug_info("dnssrv","found %d TXT entries\n", size);
-					res = g_new0(PurpleTxtResponse, 1);
 					for (i = 0; i < size; i++) {
-						red = read(source, res, sizeof(PurpleTxtResponse));
-						if (red != sizeof(PurpleTxtResponse)) {
+						gsize len;
+
+						red = read(source, &len, sizeof(len));
+						if (red != sizeof(len)) {
 							purple_debug_error("dnssrv","unable to read txt "
-									"response: %s\n", g_strerror(errno));
+									"response length: %s\n", g_strerror(errno));
 							size = 0;
-							g_free(res);
 							g_list_foreach(responses, (GFunc)purple_txt_response_destroy, NULL);
 							g_list_free(responses);
 							responses = NULL;
 							break;
 						}
+
+						res = g_new0(PurpleTxtResponse, 1);
+						res->content = g_new0(gchar, len);
+
+						red = read(source, res->content, len);
+						if (red != len) {
+							purple_debug_error("dnssrv","unable to read txt "
+									"response: %s\n", g_strerror(errno));
+							size = 0;
+							purple_txt_response_destroy(res);
+							g_list_foreach(responses, (GFunc)purple_txt_response_destroy, NULL);
+							g_list_free(responses);
+							responses = NULL;
+							break;
+						}
+						responses = g_list_prepend(responses, res);
 					}
 
+					responses = g_list_reverse(responses);
 					cb(responses, query_data->extradata);
 				} else {
 					purple_debug_error("dnssrv", "type unknown of DNS result entry; errno is %i\n", errno);
@@ -790,6 +811,7 @@
 
 	internal_query.type = T_TXT;
 	strncpy(internal_query.query, query, 255);
+	internal_query.query[255] = '\0';
 
 	if (write(in[1], &internal_query, sizeof(internal_query)) < 0)
 		purple_debug_error("dnssrv", "Could not write to TXT resolver\n");
--- a/libpurple/ft.h	Tue Oct 20 21:39:16 2009 +0000
+++ b/libpurple/ft.h	Wed Oct 21 18:09:47 2009 +0000
@@ -674,7 +674,7 @@
 void purple_xfer_ui_ready(PurpleXfer *xfer);
 
 /**
- * Allows the prpl to signal it's readh to send/receive data (depending on
+ * Allows the prpl to signal it's ready to send/receive data (depending on
  * the direction of the file transfer. Used when the prpl provides read/write
  * ops and cannot/does not provide a raw fd to the core.
  *
--- a/libpurple/media.c	Tue Oct 20 21:39:16 2009 +0000
+++ b/libpurple/media.c	Wed Oct 21 18:09:47 2009 +0000
@@ -103,6 +103,8 @@
 	gboolean initiator;
 	gboolean accepted;
 	gboolean candidates_prepared;
+	gboolean held;
+	gboolean paused;
 
 	GList *active_local_candidates;
 	GList *active_remote_candidates;
@@ -281,7 +283,7 @@
 			{ PURPLE_MEDIA_INFO_HOLD,
 					"PURPLE_MEDIA_INFO_HOLD", "hold" },
 			{ PURPLE_MEDIA_INFO_UNHOLD,
-					"PURPLE_MEDIA_INFO_HOLD", "unhold" },
+					"PURPLE_MEDIA_INFO_UNHOLD", "unhold" },
 			{ 0, NULL, NULL }
 		};
 		type = g_enum_register_static("PurpleMediaInfoType", values);
@@ -2364,11 +2366,46 @@
 		for (; streams; streams = g_list_delete_link(streams, streams)) {
 			PurpleMediaStream *stream = streams->data;
 			if (stream->session->type & PURPLE_MEDIA_SEND_VIDEO) {
+				stream->paused = active;
+
+				if (!stream->held)
+					g_object_set(stream->stream, "direction",
+							purple_media_to_fs_stream_direction(
+							stream->session->type & ((active) ?
+							~PURPLE_MEDIA_SEND_VIDEO :
+							PURPLE_MEDIA_VIDEO)), NULL);
+			}
+		}
+	} else if (local == TRUE && (type == PURPLE_MEDIA_INFO_HOLD ||
+			type == PURPLE_MEDIA_INFO_UNHOLD)) {
+		GList *streams;
+		gboolean active = (type == PURPLE_MEDIA_INFO_HOLD);
+
+		g_return_if_fail(PURPLE_IS_MEDIA(media));
+
+		streams = purple_media_get_streams(media,
+				session_id, participant);
+		for (; streams; streams = g_list_delete_link(streams, streams)) {
+			PurpleMediaStream *stream = streams->data;
+			stream->held = active;
+			if (stream->session->type & PURPLE_MEDIA_VIDEO) {
+				FsStreamDirection direction;
+
+				direction = ((active) ?
+						~PURPLE_MEDIA_VIDEO :
+						PURPLE_MEDIA_VIDEO);
+				if (!active && stream->paused)
+					direction &= ~PURPLE_MEDIA_SEND_VIDEO;
+
+				g_object_set(stream->stream, "direction",
+						purple_media_to_fs_stream_direction(
+						stream->session->type & direction), NULL);
+			} else if (stream->session->type & PURPLE_MEDIA_AUDIO) {
 				g_object_set(stream->stream, "direction",
 						purple_media_to_fs_stream_direction(
 						stream->session->type & ((active) ?
-						~PURPLE_MEDIA_SEND_VIDEO :
-						PURPLE_MEDIA_VIDEO)), NULL);
+						~PURPLE_MEDIA_AUDIO :
+						PURPLE_MEDIA_AUDIO)), NULL);
 			}
 		}
 	}
--- a/libpurple/protocols/jabber/chat.c	Tue Oct 20 21:39:16 2009 +0000
+++ b/libpurple/protocols/jabber/chat.c	Wed Oct 21 18:09:47 2009 +0000
@@ -106,7 +106,7 @@
 	{
 		char *room_jid = g_strdup_printf("%s@%s", room, server);
 
-		chat = g_hash_table_lookup(js->chats, jabber_normalize(NULL, room_jid));
+		chat = g_hash_table_lookup(js->chats, room_jid);
 		g_free(room_jid);
 	}
 
@@ -177,10 +177,21 @@
 		xmlnode_insert_data(body, msg, -1);
 	} else {
 		xmlnode_set_attrib(message, "to", name);
+		/*
+		 * Putting the reason into the body was an 'undocumented protocol,
+		 * ...not part of "groupchat 1.0"'.
+		 * http://xmpp.org/extensions/attic/jep-0045-1.16.html#invite
+		 *
+		 * Left here for compatibility.
+		 */
 		body = xmlnode_new_child(message, "body");
 		xmlnode_insert_data(body, msg, -1);
+
 		x = xmlnode_new_child(message, "x");
 		xmlnode_set_attrib(x, "jid", room_jid);
+
+		/* The better place for it! XEP-0249 style. */
+		xmlnode_set_attrib(x, "reason", msg);
 		xmlnode_set_namespace(x, "jabber:x:conference");
 	}
 
@@ -216,7 +227,8 @@
 	JabberChat *chat;
 	char *jid;
 
-	g_return_val_if_fail(jabber_chat_find(js, room, server) == NULL, NULL);
+	if (jabber_chat_find(js, room, server) != NULL)
+		return NULL;
 
 	chat = g_new0(JabberChat, 1);
 	chat->js = js;
@@ -264,7 +276,8 @@
 	char *jid;
 
 	chat = jabber_chat_new(js, room, server, handle, password, data);
-	g_return_val_if_fail(chat != NULL, NULL);
+	if (chat == NULL)
+		return NULL;
 
 	gc = js->gc;
 	account = purple_connection_get_account(gc);
@@ -371,7 +384,7 @@
 	JabberStream *js = chat->js;
 	char *room_jid = g_strdup_printf("%s@%s", chat->room, chat->server);
 
-	g_hash_table_remove(js->chats, jabber_normalize(NULL, room_jid));
+	g_hash_table_remove(js->chats, room_jid);
 	g_free(room_jid);
 }
 
--- a/libpurple/protocols/jabber/chat.h	Tue Oct 20 21:39:16 2009 +0000
+++ b/libpurple/protocols/jabber/chat.h	Wed Oct 21 18:09:47 2009 +0000
@@ -62,6 +62,8 @@
  * in-prpl function for joining a chat room. Doesn't require sticking goop
  * into a hash table.
  *
+ * @param room     The room to join. This MUST be normalized already.
+ * @param server   The server the room is on. This MUST be normalized already.
  * @param password The password (if required) to join the room. May be NULL.
  * @param data     The chat hash table.  May be NULL (it will be generated
  *                 for current core<>prpl API interface.)
--- a/libpurple/protocols/jabber/jabber.c	Tue Oct 20 21:39:16 2009 +0000
+++ b/libpurple/protocols/jabber/jabber.c	Wed Oct 21 18:09:47 2009 +0000
@@ -3239,7 +3239,7 @@
 	id = purple_cmd_register("part", "s", PURPLE_CMD_P_PRPL,
 	                  PURPLE_CMD_FLAG_CHAT | PURPLE_CMD_FLAG_PRPL_ONLY |
 	                  PURPLE_CMD_FLAG_ALLOW_WRONG_ARGS, "prpl-jabber",
-	                  jabber_cmd_chat_part, _("part [room]:  Leave the room."),
+	                  jabber_cmd_chat_part, _("part [message]:  Leave the room."),
 	                  NULL);
 	jabber_cmds = g_slist_prepend(jabber_cmds, GUINT_TO_POINTER(id));
 
--- a/libpurple/protocols/jabber/message.c	Tue Oct 20 21:39:16 2009 +0000
+++ b/libpurple/protocols/jabber/message.c	Wed Oct 21 18:09:47 2009 +0000
@@ -758,9 +758,22 @@
 					jm->type != JABBER_MESSAGE_ERROR) {
 				const char *jid = xmlnode_get_attrib(child, "jid");
 				if(jid) {
+					const char *reason = xmlnode_get_attrib(child, "reason");
+					const char *password = xmlnode_get_attrib(child, "password");
+
 					jm->type = JABBER_MESSAGE_GROUPCHAT_INVITE;
 					g_free(jm->to);
 					jm->to = g_strdup(jid);
+
+					if (reason) {
+						g_free(jm->body);
+						jm->body = g_strdup(reason);
+					}
+
+					if (password) {
+						g_free(jm->password);
+						jm->password = g_strdup(password);
+					}
 				}
 			} else if(!strcmp(xmlns, "http://jabber.org/protocol/muc#user") &&
 					jm->type != JABBER_MESSAGE_ERROR) {
@@ -775,8 +788,10 @@
 						g_free(jm->body);
 						jm->body = xmlnode_get_data(reason);
 					}
-					if((password = xmlnode_get_child(child, "password")))
+					if((password = xmlnode_get_child(child, "password"))) {
+						g_free(jm->password);
 						jm->password = xmlnode_get_data(password);
+					}
 
 					jm->type = JABBER_MESSAGE_GROUPCHAT_INVITE;
 				}
--- a/libpurple/protocols/jabber/presence.c	Tue Oct 20 21:39:16 2009 +0000
+++ b/libpurple/protocols/jabber/presence.c	Wed Oct 21 18:09:47 2009 +0000
@@ -480,7 +480,7 @@
 	/*
 	 * Versions of libpurple before 2.6.0 didn't advertise this capability, so
 	 * we can't yet use Entity Capabilities to determine whether or not the
-	 * other client supports Entity Capabilities.
+	 * other client supports Chat States.
 	 */
 	if (jabber_resource_has_capability(jbr, "http://jabber.org/protocol/chatstates"))
 		jbr->chat_states = JABBER_CHAT_STATES_SUPPORTED;
--- a/libpurple/protocols/oscar/oscar.c	Tue Oct 20 21:39:16 2009 +0000
+++ b/libpurple/protocols/oscar/oscar.c	Wed Oct 21 18:09:47 2009 +0000
@@ -2874,25 +2874,46 @@
 			gchar **text;
 			text = g_strsplit(args->msg, "\376", 0);
 			if (text) {
-				num = 0;
-				for (i=0; i<strlen(text[0]); i++)
-					num = num*10 + text[0][i]-48;
-				for (i=0; i<num; i++) {
-					struct name_data *data = g_new(struct name_data, 1);
-					gchar *message = g_strdup_printf(_("ICQ user %u has sent you a buddy: %s (%s)"), args->uin, text[i*2+2], text[i*2+1]);
-					data->gc = gc;
-					data->name = g_strdup(text[i*2+1]);
-					data->nick = g_strdup(text[i*2+2]);
-
-					purple_request_action(gc, NULL, message,
-										_("Do you want to add this buddy "
-										  "to your buddy list?"),
-										PURPLE_DEFAULT_ACTION_NONE,
-										purple_connection_get_account(gc), data->name, NULL,
-										data, 2,
-										_("_Add"), G_CALLBACK(purple_icq_buddyadd),
-										_("_Decline"), G_CALLBACK(oscar_free_name_data));
-					g_free(message);
+				/* Read the number of contacts that we were sent */
+				errno = 0;
+				num = strtoul(text[0], NULL, 10);
+
+				if (num > 0 && errno == 0) {
+					for (i=0; i<num; i++) {
+						struct name_data *data;
+						gchar *message;
+
+						if (!text[i*2 + 1] || !text[i*2 + 2]) {
+							/* We're missing the contact name or nickname.  Bail out. */
+							gchar *tmp = g_strescape(args->msg, NULL);
+							purple_debug_error("oscar", "Unknown syntax parsing "
+									"ICQ buddies.  args->msg=%s\n", tmp);
+							g_free(tmp);
+							break;
+						}
+
+						message = g_strdup_printf(_("ICQ user %u has sent you a buddy: %s (%s)"), args->uin, text[i*2+2], text[i*2+1]);
+
+						data = g_new(struct name_data, 1);
+						data->gc = gc;
+						data->name = g_strdup(text[i*2+1]);
+						data->nick = g_strdup(text[i*2+2]);
+
+						purple_request_action(gc, NULL, message,
+								_("Do you want to add this buddy "
+								  "to your buddy list?"),
+								PURPLE_DEFAULT_ACTION_NONE,
+								purple_connection_get_account(gc), data->name, NULL,
+								data, 2,
+								_("_Add"), G_CALLBACK(purple_icq_buddyadd),
+								_("_Decline"), G_CALLBACK(oscar_free_name_data));
+						g_free(message);
+					}
+				} else {
+					gchar *tmp = g_strescape(args->msg, NULL);
+					purple_debug_error("oscar", "Unknown syntax parsing "
+							"ICQ buddies.  args->msg=%s\n", tmp);
+					g_free(tmp);
 				}
 				g_strfreev(text);
 			}
@@ -3909,12 +3930,8 @@
 	od->rights.maxpermits = (guint)maxpermits;
 	od->rights.maxdenies = (guint)maxdenies;
 
-	purple_connection_set_state(gc, PURPLE_CONNECTED);
-
 	purple_debug_info("oscar", "buddy list loaded\n");
 
-	aim_srv_clientready(od, conn);
-
 	if (purple_account_get_user_info(account) != NULL)
 		serv_set_info(gc, purple_account_get_user_info(account));
 
@@ -3957,6 +3974,22 @@
 	aim_srv_requestnew(od, SNAC_FAMILY_ALERT);
 	aim_srv_requestnew(od, SNAC_FAMILY_CHATNAV);
 
+	od->bos.have_rights = TRUE;
+
+	/*
+	 * If we've already received our feedbag data then we're not waiting on
+	 * anything else, so send the server clientready.
+	 *
+	 * Normally we get bos rights before we get our feedbag data, so this
+	 * rarely (never?) happens.  And I'm not sure it actually matters if we
+	 * wait for bos rights before calling clientready.  But it seems safer
+	 * to do it this way.
+	 */
+	if (od->ssi.received_data) {
+		aim_srv_clientready(od, conn);
+		purple_connection_set_state(gc, PURPLE_CONNECTED);
+	}
+
 	return 1;
 }
 
@@ -5396,6 +5429,15 @@
 	oscar_set_icon(gc, img);
 	purple_imgstore_unref(img);
 
+	/*
+	 * If we've already received our bos rights then we're not waiting on
+	 * anything else, so send the server clientready.
+	 */
+	if (od->bos.have_rights) {
+		aim_srv_clientready(od, conn);
+		purple_connection_set_state(gc, PURPLE_CONNECTED);
+	}
+
 	return 1;
 }
 
--- a/libpurple/protocols/oscar/oscar.h	Tue Oct 20 21:39:16 2009 +0000
+++ b/libpurple/protocols/oscar/oscar.h	Wed Oct 21 18:09:47 2009 +0000
@@ -535,6 +535,10 @@
 		struct aim_userinfo_s *userinfo;
 	} locate;
 
+	struct {
+		gboolean have_rights;
+	} bos;
+
 	/* Server-stored information (ssi) */
 	struct {
 		gboolean received_data;
--- a/libpurple/protocols/yahoo/libymsg.c	Tue Oct 20 21:39:16 2009 +0000
+++ b/libpurple/protocols/yahoo/libymsg.c	Wed Oct 21 18:09:47 2009 +0000
@@ -1140,10 +1140,12 @@
 			}
 		}
 
+		if(im->fed != YAHOO_FEDERATION_NONE)
+			g_free(fed_from);
+
 		g_free(im);
 	}
-	if (fed_from != im->from)
-		g_free(fed_from);
+
 	g_slist_free(list);
 }
 
--- a/libpurple/tests/test_jabber_jutil.c	Tue Oct 20 21:39:16 2009 +0000
+++ b/libpurple/tests/test_jabber_jutil.c	Wed Oct 21 18:09:47 2009 +0000
@@ -134,6 +134,14 @@
 	assert_invalid_jid("paul@2[::1]124/as");
 	assert_invalid_jid("paul@まつ.おおかみ/\x01");
 
+	/*
+	 * RFC 3454 Section 6 reads, in part,
+	 * "If a string contains any RandALCat character, the
+	 *  string MUST NOT contain any LCat character."
+	 * The character is U+066D (ARABIC FIVE POINTED STAR).
+	 */
+	assert_invalid_jid("foo@example.com/٭simplexe٭");
+
 	/* Ensure that jabber_id_new is properly lowercasing node and domains */
 	assert_jid_parts("paul", "darkrain42.org", "PaUL@darkrain42.org");
 	assert_jid_parts("paul", "darkrain42.org", "paul@DaRkRaIn42.org");
--- a/pidgin/gtkmedia.c	Tue Oct 20 21:39:16 2009 +0000
+++ b/pidgin/gtkmedia.c	Wed Oct 21 18:09:47 2009 +0000
@@ -89,6 +89,7 @@
 	GtkWidget *menubar;
 	GtkWidget *statusbar;
 
+	GtkWidget *hold;
 	GtkWidget *mute;
 	GtkWidget *pause;
 
@@ -187,6 +188,15 @@
 }
 
 static void
+pidgin_media_hold_toggled(GtkToggleButton *toggle, PidginMedia *media)
+{
+	purple_media_stream_info(media->priv->media,
+			gtk_toggle_button_get_active(toggle) ?
+			PURPLE_MEDIA_INFO_HOLD : PURPLE_MEDIA_INFO_UNHOLD,
+			NULL, NULL, TRUE);
+}
+
+static void
 pidgin_media_mute_toggled(GtkToggleButton *toggle, PidginMedia *media)
 {
 	purple_media_stream_info(media->priv->media,
@@ -633,6 +643,16 @@
 				FALSE, FALSE, 0);
 		gtk_widget_show(GTK_WIDGET(button_widget));
 		gtk_widget_show(send_widget);
+
+		/* Hold button */
+		gtkmedia->priv->hold =
+				gtk_toggle_button_new_with_mnemonic("_Hold");
+		g_signal_connect(gtkmedia->priv->hold, "toggled",
+				G_CALLBACK(pidgin_media_hold_toggled),
+				gtkmedia);
+		gtk_box_pack_end(GTK_BOX(button_widget), gtkmedia->priv->hold,
+				FALSE, FALSE, 0);
+		gtk_widget_show(gtkmedia->priv->hold);
 	} else {
 		send_widget = gtkmedia->priv->send_widget;
 		button_widget = gtkmedia->priv->button_widget;
--- a/pidgin/gtkroomlist.c	Tue Oct 20 21:39:16 2009 +0000
+++ b/pidgin/gtkroomlist.c	Wed Oct 21 18:09:47 2009 +0000
@@ -111,7 +111,18 @@
 static void dialog_select_account_cb(GObject *w, PurpleAccount *account,
 				     PidginRoomlistDialog *dialog)
 {
+	gboolean change = (account != dialog->account);
 	dialog->account = account;
+
+	if (change && dialog->roomlist) {
+		PidginRoomlist *rl = dialog->roomlist->ui_data;
+		if (rl->tree) {
+			gtk_widget_destroy(rl->tree);
+			rl->tree = NULL;
+		}
+		purple_roomlist_unref(dialog->roomlist);
+		dialog->roomlist = NULL;
+	}
 }
 
 static void list_button_cb(GtkButton *button, PidginRoomlistDialog *dialog)
--- a/pidgin/gtkutils.c	Tue Oct 20 21:39:16 2009 +0000
+++ b/pidgin/gtkutils.c	Wed Oct 21 18:09:47 2009 +0000
@@ -75,7 +75,7 @@
 } AopMenu;
 
 static guint accels_save_timer = 0;
-static GList *gnome_url_handlers = NULL;
+static GSList *registered_url_handlers = NULL;
 
 static gboolean
 url_clicked_idle_cb(gpointer data)
@@ -3395,7 +3395,7 @@
 				start += sizeof("/desktop/gnome/url-handlers/") - 1;
 
 				protocol = g_strdup_printf("%s:", start);
-				gnome_url_handlers = g_list_prepend(gnome_url_handlers, protocol);
+				registered_url_handlers = g_slist_prepend(registered_url_handlers, protocol);
 				gtk_imhtml_class_register_protocol(protocol, url_clicked_cb, link_context_menu);
 			}
 			start = c + 1;
@@ -3403,9 +3403,45 @@
 	}
 	g_free(tmp);
 
-	return (gnome_url_handlers != NULL);
+	return (registered_url_handlers != NULL);
 }
 
+#ifdef _WIN32
+static void
+winpidgin_register_win32_url_handlers(void)
+{
+	int idx = 0;
+	LONG ret = ERROR_SUCCESS;
+
+	do {
+		DWORD nameSize = 256;
+		char start[256];
+		/* I don't think we need to worry about non-ASCII protocol names */
+		ret = RegEnumKeyExA(HKEY_CLASSES_ROOT, idx++, start, &nameSize,
+							NULL, NULL, NULL, NULL);
+		if (ret == ERROR_SUCCESS) {
+			HKEY reg_key = NULL;
+			ret = RegOpenKeyExA(HKEY_CLASSES_ROOT, start, 0, KEY_READ, &reg_key);
+			if (ret == ERROR_SUCCESS) {
+				ret = RegQueryValueExA(reg_key, "URL Protocol", NULL, NULL, NULL, NULL);
+				if (ret == ERROR_SUCCESS) {
+					gchar *protocol = g_strdup_printf("%s:", start);
+					registered_url_handlers = g_slist_prepend(registered_url_handlers, protocol);
+					/* We still pass everything to the "http" "open" handler for security reasons */
+					gtk_imhtml_class_register_protocol(protocol, url_clicked_cb, link_context_menu);
+				}
+				RegCloseKey(reg_key);
+			}
+			ret = ERROR_SUCCESS;
+		}
+	} while (ret == ERROR_SUCCESS);
+
+	if (ret != ERROR_NO_MORE_ITEMS)
+		purple_debug_error("winpidgin", "Error iterating HKEY_CLASSES_ROOT subkeys: %ld\n",
+						   ret);
+}
+#endif
+
 void pidgin_utils_init(void)
 {
 	gtk_imhtml_class_register_protocol("http://", url_clicked_cb, link_context_menu);
@@ -3423,6 +3459,11 @@
 	/* If we're under GNOME, try registering the system URL handlers. */
 	if (purple_running_gnome())
 		register_gnome_url_handlers();
+
+#ifdef _WIN32
+	winpidgin_register_win32_url_handlers();
+#endif
+
 }
 
 void pidgin_utils_uninit(void)
@@ -3430,16 +3471,16 @@
 	gtk_imhtml_class_register_protocol("open://", NULL, NULL);
 
 	/* If we have GNOME handlers registered, unregister them. */
-	if (gnome_url_handlers)
+	if (registered_url_handlers)
 	{
-		GList *l;
-		for (l = gnome_url_handlers ; l ; l = l->next)
+		GSList *l;
+		for (l = registered_url_handlers; l; l = l->next)
 		{
 			gtk_imhtml_class_register_protocol((char *)l->data, NULL, NULL);
 			g_free(l->data);
 		}
-		g_list_free(gnome_url_handlers);
-		gnome_url_handlers = NULL;
+		g_slist_free(registered_url_handlers);
+		registered_url_handlers = NULL;
 		return;
 	}
 
--- a/pidgin/plugins/disco/gtkdisco.c	Tue Oct 20 21:39:16 2009 +0000
+++ b/pidgin/plugins/disco/gtkdisco.c	Wed Oct 21 18:09:47 2009 +0000
@@ -141,8 +141,18 @@
 static void dialog_select_account_cb(GObject *w, PurpleAccount *account,
                                      PidginDiscoDialog *dialog)
 {
+	gboolean change = (account != dialog->account);
 	dialog->account = account;
 	gtk_widget_set_sensitive(dialog->browse_button, account != NULL);
+
+	if (change && dialog->discolist) {
+		if (dialog->discolist->tree) {
+			gtk_widget_destroy(dialog->discolist->tree);
+			dialog->discolist->tree = NULL;
+		}
+		pidgin_disco_list_unref(dialog->discolist);
+		dialog->discolist = NULL;
+	}
 }
 
 static void register_button_cb(GtkWidget *unused, PidginDiscoDialog *dialog)
--- a/po/ChangeLog	Tue Oct 20 21:39:16 2009 +0000
+++ b/po/ChangeLog	Wed Oct 21 18:09:47 2009 +0000
@@ -2,8 +2,11 @@
 
 version 2.7.0
 
+version 2.6.4
+	* Vietnamese translation updated (Clytie Siddall)
+
 version 2.6.3
-	* Vietnamese translation updated (Clytie Siddall)
+	* No changes
 
 version 2.6.2
 	* Afrikaans translation updated (Friedel Wolff)