changeset 30186:06fa97f637a7

merged with im.pidgin.pidgin
author Yoshiki Yazawa <yaz@honeyplanet.jp>
date Thu, 22 Apr 2010 14:45:57 +0900
parents bd0ce3844104 (current diff) 9e60e300541a (diff)
children c9413547ee33
files configure.ac libpurple/protocols/jabber/jabber.c libpurple/protocols/msn/msn.c libpurple/protocols/oscar/family_icbm.c libpurple/protocols/oscar/oscar.h libpurple/util.c libpurple/util.h pidgin/gtkblist.c pidgin/gtkimhtml.c pidgin/gtkutils.c
diffstat 36 files changed, 527 insertions(+), 98 deletions(-) [+]
line wrap: on
line diff
--- a/COPYRIGHT	Thu Apr 15 16:48:50 2010 +0900
+++ b/COPYRIGHT	Thu Apr 22 14:45:57 2010 +0900
@@ -245,6 +245,7 @@
 Jochen Kemnade
 Yann Kerherve
 Gordian Klein
+Marten Klencke
 Krzysztof Klinikowski
 Akuke Kok
 Kir Kolyshkin
@@ -530,6 +531,7 @@
 Zac West
 Daniel Westermann-Clark
 Andrew Whewell
+Stephen Whitmore
 Simon Wilkinson
 Dan Willemsen
 Justin Williams (Jaywalker)
--- a/ChangeLog	Thu Apr 15 16:48:50 2010 +0900
+++ b/ChangeLog	Thu Apr 22 14:45:57 2010 +0900
@@ -18,6 +18,8 @@
 	  GnuTLS manual for documentation on the format of the priority
 	  strings.
 	* Fix autoconf detection of Python.  (Brad Smith)
+	* Fix a crash when a Windows proxy (in IE) does not have a port.
+	  (Marten Klencke)
 
 	Pidgin:
 	* Moved the "Debugging Information" section of the About box to a
@@ -45,6 +47,8 @@
 	  buddy icons.
 	* The 'Message Timestamp Formats' plugin allows changing the timestamp
 	  format from the timestamps' context menu in conversation log.
+	* Fix pastes from Chrome (rich-text pastes and probably URLs
+	  having garbage appended to them)
 
 	Bonjour:
 	* Added support for IPv6. (Thanks to T_X for testing)
@@ -82,6 +86,10 @@
 	  BoB XEP).
 	* Present a better error message when authentication fails while trying
 	  to connect to Facebook.  (David Reiss, Facebook)
+	* Send whitespace keepalives if we haven't sent data in a while (2
+	  minutes).  This fixes an issue with Openfire disconnecting a
+	  libpurple-baesd client that has just been quiet for about 6
+	  minutes.
 
 	Yahoo/Yahoo JAPAN:
 	* Attempt to better handle transparent proxies interfering with HTTP-based
--- a/ChangeLog.API	Thu Apr 15 16:48:50 2010 +0900
+++ b/ChangeLog.API	Thu Apr 22 14:45:57 2010 +0900
@@ -8,6 +8,8 @@
 		   * account-signed-off
 		   * account-connection-error
 		* purple_account_get_name_for_display
+		* purple_account_get_privacy_type
+		* purple_account_set_privacy_type
 		* purple_buddy_get_media_caps
 		* purple_buddy_set_media_caps
 		* purple_contact_get_group
@@ -15,11 +17,20 @@
 		* purple_media_codec_copy
 		* purple_media_manager_get_backend_type
 		* purple_media_manager_set_backend_type
-		* purple_network_get_all_local_system_ips, which returns all local
-		  IPs on the system.  On systems with the getifaddrs() function,
-		  this will return both IPv4 and IPv6 addresses (excluding link-local
-		  and loopback addresses).  On others, it returns just IPv4 addresses.
+		* purple_network_get_all_local_system_ips, which returns all
+		  local IPs on the system.  On systems with the getifaddrs()
+		  function, this will return both IPv4 and IPv6 addresses
+		  (excluding link-local and loopback addresses).  On others,
+		  it returns just IPv4 addresses.
+		* purple_network_listen_family and
+		  purple_network_listen_range_family.  These will replace the
+		  versions without _family in 3.0.0 and allow the caller to
+		  specifically request either an IPv4 or IPv6 socket.  IPv6 is
+		  only supported if the getaddrinfo() function is available
+		  at build-time (not the case on Windows, currently).
 		* purple_prpl_got_media_caps
+		* purple_socket_get_family
+		* purple_socket_speaks_ipv4
 		* purple_unescape_text
 		* purple_uuid_random
 		* media_caps to the PurpleBuddy struct
@@ -28,9 +39,10 @@
 		* sent-attention conversation signal
 		* got-attention conversation signal
 		* PurpleMood struct in status.h
-		* purple_certificates_import for importing multiple certificates from
-		  a single file (and corresponding import_certificates member of
-		  PurpleCertificateScheme struct)
+		* purple_certificates_import for importing multiple
+		  certificates from a single file (and corresponding
+		  import_certificates member of PurpleCertificateScheme struct)
+		* autojoin connection signal 
 
 	Pidgin:
 		Added:
@@ -48,7 +60,7 @@
 		  purple_xfer_request_denied if an error is found when selecting
 		  a file to send. Request denied is still used when a receive
 		  request is not allowed.
-		* xmlnode_from_str now properly handles paring an attribute which
+		* xmlnode_from_str now properly handles parsing an attribute which
 		  contain "&lt;br&gt;", which were previously transformed into a
 		  newline character (libxml2 unescapes all entities except
 		  representations of '&', and libpurple's purple_unescape_html
--- a/configure.ac	Thu Apr 15 16:48:50 2010 +0900
+++ b/configure.ac	Thu Apr 22 14:45:57 2010 +0900
@@ -184,6 +184,12 @@
 	[Define if struct sockaddr has an sa_len member])],[:],
 	[#include <sys/socket.h>])
 
+dnl Check for v6-only sockets
+AC_CHECK_DECL([IPV6_V6ONLY],
+	[AC_DEFINE([HAVE_IPV6_V6ONLY],[1],
+	[Define if the IPV6_V6ONLY setsockopt option exists])],
+	[], [#include <netinet/in.h>])
+
 dnl to prevent the g_stat()/g_unlink() crash,
 dnl (09:50:07) Robot101: LSchiere2: it's easy. +LC_SYS_LARGEFILE somewhere in configure.ac
 AC_SYS_LARGEFILE
@@ -1791,13 +1797,13 @@
 AC_SUBST(GNUTLS_LIBS)
 
 if test "x$enable_gnutls" = "xyes"; then
-	AC_MSG_CHECKING(for gnutls_priority_set_direct)
+	AC_MSG_CHECKING(for gnutls_priority_set_direct and gnutls_priority_set)
 	LIBS_save="$LIBS"
 	LIBS="$LIBS $GNUTLS_LIBS"
 	CPPFLAGS_save="$CPPFLAGS"
 	CPPFLAGS="$CPPFLAGS $GNUTLS_CFLAGS"
 	AC_LINK_IFELSE([AC_LANG_PROGRAM([[#include <gnutls/gnutls.h>]],
-                                        [[gnutls_session s; gnutls_priority_set_direct(s, NULL, NULL);]])],
+                                        [[gnutls_session s; gnutls_priority_set_direct(s, NULL, NULL); gnutls_priority_set(s, NULL);]])],
 	               [AC_DEFINE([HAVE_GNUTLS_PRIORITY_FUNCS], 1,
                                   [Define if your gnutls has gnutls_priority_set_direct and friends])
 	                AC_MSG_RESULT(yes)],
--- a/doc/blist-signals.dox	Thu Apr 15 16:48:50 2010 +0900
+++ b/doc/blist-signals.dox	Thu Apr 22 14:45:57 2010 +0900
@@ -11,6 +11,8 @@
   @signal buddy-removed
   @signal buddy-icon-changed
   @signal blist-node-aliased
+  @signal buddy-caps-changed
+  @signal ui-caps-changed
  @endsignals
 
  @see blist.h
@@ -124,5 +126,29 @@
    Emitted when a blist node (buddy, chat, or contact) is aliased.
   @endsignaldef
 
+ @signaldef buddy-caps-changed
+  @signalproto
+void (*buddy_caps_changed)(PurpleBuddy *buddy, PurpleMediaCaps newcaps,
+    PurpleMediaCaps oldcaps)
+  @endsignalproto
+  @signaldesc
+    Emitted when updating a buddy's media capabilities.
+  @param buddy	  The buddy
+  @param newcaps
+  @param oldcaps
+  @since 2.7.0
+ @endsignaldef
+
+ @signaldef ui-caps-changed
+  @signalproto
+void (*ui_caps_changed)(PurpleMediaCaps newcaps, PurpleMediaCaps oldcaps)
+  @endsignalproto
+  @signaldesc
+    Emitted when updating the media capabilities of the UI.
+  @param newcaps
+  @param oldcaps
+  @since 2.7.0
+ @endsignaldef
+ 
  */
 // vim: syntax=c.doxygen tw=75 et
--- a/doc/connection-signals.dox	Thu Apr 15 16:48:50 2010 +0900
+++ b/doc/connection-signals.dox	Thu Apr 22 14:45:57 2010 +0900
@@ -3,6 +3,7 @@
  @signals
   @signal signing-on
   @signal signed-on
+  @signal autojoin
   @signal signing-off
   @signal signed-off
   @signal connection-error
@@ -30,6 +31,22 @@
   @param gc The connection that has signed on.
  @endsignaldef
 
+ @signaldef autojoin
+  @signalproto
+gboolean (*autojoin)(PurpleConnection *gc);
+  @endsignalproto
+  @signaldesc
+   Emitted when a connection has signed on, after the signed-on signal, to
+   signal UIs to autojoin chats if they wish.  UIs should connect to this
+   with @c PURPLE_SIGNAL_PRIORITY_HIGHEST to allow plugins to block this
+   signal before the UI sees it and then re-emit it later.
+  @param gc The connection that has signed on.
+  @return @c TRUE if the signal was handled or @c FALSE otherwise.  In
+          practice, the return value is irrelevant, as it really only
+          exists so plugins can block the UI's autojoin.
+  @since 2.7.0
+ @endsignaldef
+
  @signaldef signing-off
   @signalproto
 void (*signing_off)(PurpleConnection *gc);
--- a/doc/conversation-signals.dox	Thu Apr 15 16:48:50 2010 +0900
+++ b/doc/conversation-signals.dox	Thu Apr 22 14:45:57 2010 +0900
@@ -33,6 +33,8 @@
   @signal chat-left
   @signal chat-topic-changed
   @signal conversation-extended-menu
+  @signal sent-attention
+  @signal got-attention
  @endsignals
 
  @see conversation.h
@@ -476,5 +478,33 @@
   @param list   A pointer to the list of actions.
   @since 2.1.0
  @endsignaldef
+
+ @signaldef sent-attention
+  @signalproto
+void (*got_attention)(PurpleAccount *account, const char *who, 
+	PurpleConversation *conv, guint type)
+  @endsignalproto
+  @signaldesc
+    Emitted when receiving an attention message (buzz, nudge, etc.).
+  @param account  The account
+  @param who      The name of the person receiving the attention
+  @param conv     The conversation
+  @param type     The attention type (an index starting at 0)
+  @since 2.7.0
+ @endsignaldef
+
+ @signaldef got-attention
+  @signalproto
+void (*got_attention)(PurpleAccount *account, const char *who, 
+	PurpleConversation *conv, guint type)
+  @endsignalproto
+  @signaldesc
+    Emitted when receiving an attention message (buzz, nudge, etc.).
+  @param account  The account
+  @param who      The name of the person sending the attention
+  @param conv     The conversation
+  @param type     The attention type (an index starting at 0)
+  @since 2.7.0
+ @endsignaldef
 */
 // vim: syntax=c.doxygen tw=75 et
--- a/doc/gtkimhtml-signals.dox	Thu Apr 15 16:48:50 2010 +0900
+++ b/doc/gtkimhtml-signals.dox	Thu Apr 22 14:45:57 2010 +0900
@@ -63,7 +63,7 @@
  	@signalproto
 void (*paste) (GtkIMHtml *imhtml, char *format)
 	@endsignalproto
-	@signaldef Emitted when paste from the clipboard is requested.
+	@signaldesc Emitted when paste from the clipboard is requested.
 	@param imhtml  The GtkIMHtml emitting the signal.
 	@param format  If 'text', then the formatting of the clipboard content
 	               will be removed before pasting. If empty or 'html', then
--- a/finch/gntblist.c	Thu Apr 15 16:48:50 2010 +0900
+++ b/finch/gntblist.c	Thu Apr 22 14:45:57 2010 +0900
@@ -144,7 +144,7 @@
 static void blist_show(PurpleBuddyList *list);
 static void update_node_display(PurpleBlistNode *buddy, FinchBlist *ggblist);
 static void update_buddy_display(PurpleBuddy *buddy, FinchBlist *ggblist);
-static void account_signed_on_cb(PurpleConnection *pc, gpointer null);
+static gboolean account_autojoin_cb(PurpleConnection *pc, gpointer null);
 static void finch_request_add_buddy(PurpleAccount *account, const char *username, const char *grp, const char *alias);
 static void menu_group_set_cb(GntMenuItem *item, gpointer null);
 
@@ -2213,8 +2213,10 @@
 	purple_prefs_connect_callback(finch_blist_get_handle(),
 			PREF_ROOT "/grouping", redraw_blist, NULL);
 
-	purple_signal_connect(purple_connections_get_handle(), "signed-on", purple_blist_get_handle(),
-			G_CALLBACK(account_signed_on_cb), NULL);
+	purple_signal_connect_priority(purple_connections_get_handle(),
+	                               "autojoin", purple_blist_get_handle(),
+			               G_CALLBACK(account_autojoin_cb), NULL,
+	                               PURPLE_SIGNAL_PRIORITY_HIGHEST);
 
 	finch_blist_install_manager(&default_manager);
 
@@ -2684,10 +2686,11 @@
 	return FALSE;
 }
 
-static void
-account_signed_on_cb(PurpleConnection *gc, gpointer null)
+static gboolean
+account_autojoin_cb(PurpleConnection *gc, gpointer null)
 {
 	g_idle_add(auto_join_chats, gc);
+	return TRUE;
 }
 
 static void toggle_pref_cb(GntMenuItem *item, gpointer n)
--- a/libpurple/account.c	Thu Apr 15 16:48:50 2010 +0900
+++ b/libpurple/account.c	Thu Apr 22 14:45:57 2010 +0900
@@ -1709,6 +1709,14 @@
 }
 
 void
+purple_account_set_privacy_type(PurpleAccount *account, PurplePrivacyType privacy_type)
+{
+	g_return_if_fail(account != NULL);
+
+	account->perm_deny = privacy_type;
+}
+
+void
 purple_account_set_status_types(PurpleAccount *account, GList *status_types)
 {
 	g_return_if_fail(account != NULL);
@@ -2105,6 +2113,14 @@
 	return account->proxy_info;
 }
 
+PurplePrivacyType
+purple_account_get_privacy_type(const PurpleAccount *account)
+{
+	g_return_val_if_fail(account != NULL, PURPLE_PRIVACY_ALLOW_ALL);
+
+	return account->perm_deny;
+}
+
 PurpleStatus *
 purple_account_get_active_status(const PurpleAccount *account)
 {
--- a/libpurple/account.h	Thu Apr 15 16:48:50 2010 +0900
+++ b/libpurple/account.h	Thu Apr 22 14:45:57 2010 +0900
@@ -414,6 +414,16 @@
 void purple_account_set_proxy_info(PurpleAccount *account, PurpleProxyInfo *info);
 
 /**
+ * Sets the account's privacy type.
+ *
+ * @param account      The account.
+ * @param privacy_type The privacy type.
+ *
+ * @since 2.7.0
+ */
+void purple_account_set_privacy_type(PurpleAccount *account, PurplePrivacyType privacy_type);
+
+/**
  * Sets the account's status types.
  *
  * @param account      The account.
@@ -683,6 +693,17 @@
 PurpleProxyInfo *purple_account_get_proxy_info(const PurpleAccount *account);
 
 /**
+ * Returns the account's privacy type.
+ *
+ * @param account   The account.
+ *
+ * @return The privacy type.
+ *
+ * @since 2.7.0
+ */
+PurplePrivacyType purple_account_get_privacy_type(const PurpleAccount *account);
+
+/**
  * Returns the active status for this account.  This looks through
  * the PurplePresence associated with this account and returns the
  * PurpleStatus that has its active flag set to "TRUE."  There can be
--- a/libpurple/connection.c	Thu Apr 15 16:48:50 2010 +0900
+++ b/libpurple/connection.c	Thu Apr 22 14:45:57 2010 +0900
@@ -372,6 +372,7 @@
 		purple_blist_add_account(account);
 
 		purple_signal_emit(purple_connections_get_handle(), "signed-on", gc);
+		purple_signal_emit_return_1(purple_connections_get_handle(), "autojoin", gc);
 
 		serv_set_permit_deny(gc);
 
@@ -715,6 +716,11 @@
 	                       purple_value_new(PURPLE_TYPE_ENUM),
 	                       purple_value_new(PURPLE_TYPE_STRING));
 
+	purple_signal_register(handle, "autojoin",
+	                       purple_marshal_BOOLEAN__POINTER, NULL, 1,
+	                       purple_value_new(PURPLE_TYPE_SUBTYPE,
+	                                        PURPLE_SUBTYPE_CONNECTION));
+
 }
 
 void
--- a/libpurple/network.c	Thu Apr 15 16:48:50 2010 +0900
+++ b/libpurple/network.c	Thu Apr 22 14:45:57 2010 +0900
@@ -394,7 +394,7 @@
 }
 
 static PurpleNetworkListenData *
-purple_network_do_listen(unsigned short port, int socket_type, PurpleNetworkListenCallback cb, gpointer cb_data)
+purple_network_do_listen(unsigned short port, int socket_family, int socket_type, PurpleNetworkListenCallback cb, gpointer cb_data)
 {
 	int listenfd = -1;
 	int flags;
@@ -412,7 +412,7 @@
 	g_snprintf(serv, sizeof(serv), "%hu", port);
 	memset(&hints, 0, sizeof(struct addrinfo));
 	hints.ai_flags = AI_PASSIVE;
-	hints.ai_family = AF_UNSPEC;
+	hints.ai_family = socket_family;
 	hints.ai_socktype = socket_type;
 	errnum = getaddrinfo(NULL /* any IP */, serv, &hints, &res);
 	if (errnum != 0) {
@@ -436,7 +436,7 @@
 		if (listenfd < 0)
 			continue;
 		if (setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) != 0)
-			purple_debug_warning("network", "setsockopt: %s\n", g_strerror(errno));
+			purple_debug_warning("network", "setsockopt(SO_REUSEADDR): %s\n", g_strerror(errno));
 		if (bind(listenfd, next->ai_addr, next->ai_addrlen) == 0)
 			break; /* success */
 		/* XXX - It is unclear to me (datallah) whether we need to be
@@ -451,6 +451,13 @@
 #else
 	struct sockaddr_in sockin;
 
+	if (socket_family != AF_INET && socket_family != AF_UNSPEC) {
+		purple_debug_warning("network", "Address family %d only "
+		                     "supported when built with getaddrinfo() "
+		                     "support\n", socket_family);
+		return NULL;
+	}
+
 	if ((listenfd = socket(AF_INET, socket_type, 0)) < 0) {
 		purple_debug_warning("network", "socket: %s\n", g_strerror(errno));
 		return NULL;
@@ -492,7 +499,8 @@
 	listen_data->cb_data = cb_data;
 	listen_data->socket_type = socket_type;
 
-	if (!listen_map_external || !purple_prefs_get_bool("/purple/network/map_ports"))
+	if (!purple_socket_speaks_ipv4(listenfd) || !listen_map_external ||
+			!purple_prefs_get_bool("/purple/network/map_ports"))
 	{
 		purple_debug_info("network", "Skipping external port mapping.\n");
 		/* The pmp_map_cb does what we want to do */
@@ -519,17 +527,29 @@
 }
 
 PurpleNetworkListenData *
-purple_network_listen(unsigned short port, int socket_type,
-		PurpleNetworkListenCallback cb, gpointer cb_data)
+purple_network_listen_family(unsigned short port, int socket_family,
+                             int socket_type, PurpleNetworkListenCallback cb,
+                             gpointer cb_data)
 {
 	g_return_val_if_fail(port != 0, NULL);
 
-	return purple_network_do_listen(port, socket_type, cb, cb_data);
+	return purple_network_do_listen(port, socket_family, socket_type,
+	                                cb, cb_data);
 }
 
 PurpleNetworkListenData *
-purple_network_listen_range(unsigned short start, unsigned short end,
-		int socket_type, PurpleNetworkListenCallback cb, gpointer cb_data)
+purple_network_listen(unsigned short port, int socket_type,
+		PurpleNetworkListenCallback cb, gpointer cb_data)
+{
+	return purple_network_listen_family(port, AF_UNSPEC, socket_type,
+	                                    cb, cb_data);
+}
+
+PurpleNetworkListenData *
+purple_network_listen_range_family(unsigned short start, unsigned short end,
+                                   int socket_family, int socket_type,
+                                   PurpleNetworkListenCallback cb,
+                                   gpointer cb_data)
 {
 	PurpleNetworkListenData *ret = NULL;
 
@@ -542,7 +562,7 @@
 	}
 
 	for (; start <= end; start++) {
-		ret = purple_network_do_listen(start, socket_type, cb, cb_data);
+		ret = purple_network_do_listen(start, AF_UNSPEC, socket_type, cb, cb_data);
 		if (ret != NULL)
 			break;
 	}
@@ -550,6 +570,15 @@
 	return ret;
 }
 
+PurpleNetworkListenData *
+purple_network_listen_range(unsigned short start, unsigned short end,
+                            int socket_type, PurpleNetworkListenCallback cb,
+                            gpointer cb_data)
+{
+	return purple_network_listen_range_family(start, end, AF_UNSPEC,
+	                                          socket_type, cb, cb_data);
+}
+
 void purple_network_listen_cancel(PurpleNetworkListenData *listen_data)
 {
 	if (listen_data->mapping_data != NULL)
--- a/libpurple/network.h	Thu Apr 15 16:48:50 2010 +0900
+++ b/libpurple/network.h	Thu Apr 22 14:45:57 2010 +0900
@@ -138,8 +138,8 @@
  *
  * This opens a listening port. The caller will want to set up a watcher
  * of type PURPLE_INPUT_READ on the fd returned in cb. It will probably call
- * accept in the watcher callback, and then possibly remove the watcher and close
- * the listening socket, and add a new watcher on the new socket accept
+ * accept in the watcher callback, and then possibly remove the watcher and
+ * close the listening socket, and add a new watcher on the new socket accept
  * returned.
  *
  * @param port The port number to bind to.  Must be greater than 0.
@@ -158,6 +158,27 @@
 		int socket_type, PurpleNetworkListenCallback cb, gpointer cb_data);
 
 /**
+ * \copydoc purple_network_listen
+ *
+ * Libpurple does not currently do any port mapping (stateful firewall hole
+ * poking) for IPv6-only listeners (if an IPv6 socket supports v4-mapped
+ * addresses, a mapping is done).
+ * 
+ * @param socket_family The protocol family of the socket.  This should be
+ *                      AF_INET for IPv4 or AF_INET6 for IPv6.  IPv6 sockets
+ *                      may or may not be able to accept IPv4 connections
+ *                      based on the system configuration (use
+ *                      purple_socket_speaks_ipv4 to check).  If an IPv6
+ *                      socket doesn't accept V4-mapped addresses, you will
+ *                      need a second listener to support both v4 and v6.
+ * @since 2.7.0
+ * @deprecated This function will be renamed to purple_network_listen in 3.0.0.
+ */
+PurpleNetworkListenData *purple_network_listen_family(unsigned short port,
+	int socket_family, int socket_type, PurpleNetworkListenCallback cb,
+	gpointer cb_data);
+
+/**
  * Opens a listening port selected from a range of ports.  The range of
  * ports used is chosen in the following manner:
  * If a range is specified in preferences, these values are used.
@@ -192,6 +213,28 @@
 		PurpleNetworkListenCallback cb, gpointer cb_data);
 
 /**
+ * \copydoc purple_network_listen_range
+ * 
+ * Libpurple does not currently do any port mapping (stateful firewall hole
+ * poking) for IPv6-only listeners (if an IPv6 socket supports v4-mapped
+ * addresses, a mapping is done).
+ * 
+ * @param socket_family The protocol family of the socket.  This should be
+ *                      AF_INET for IPv4 or AF_INET6 for IPv6.  IPv6 sockets
+ *                      may or may not be able to accept IPv4 connections
+ *                      based on the system configuration (use
+ *                      purple_socket_speaks_ipv4 to check).  If an IPv6
+ *                      socket doesn't accept V4-mapped addresses, you will
+ *                      need a second listener to support both v4 and v6.
+ * @since 2.7.0
+ * @deprecated This function will be renamed to purple_network_listen_range
+ *             in 3.0.0.
+ */
+PurpleNetworkListenData *purple_network_listen_range_family(
+	unsigned short start, unsigned short end, int socket_family,
+	int socket_type, PurpleNetworkListenCallback cb, gpointer cb_data);
+
+/**
  * This can be used to cancel any in-progress listener connection
  * by passing in the return value from either purple_network_listen()
  * or purple_network_listen_range().
--- a/libpurple/protocols/gg/gg.c	Thu Apr 15 16:48:50 2010 +0900
+++ b/libpurple/protocols/gg/gg.c	Thu Apr 22 14:45:57 2010 +0900
@@ -1034,6 +1034,7 @@
 		case GG_STATUS_DND:
 		case GG_STATUS_DND_DESCR:
 			st = purple_primitive_get_id_from_type(PURPLE_STATUS_UNAVAILABLE);
+			break;
 		case GG_STATUS_BLOCKED:
 			/* user is blocking us.... */
 			st = "blocked";
--- a/libpurple/protocols/jabber/auth.c	Thu Apr 15 16:48:50 2010 +0900
+++ b/libpurple/protocols/jabber/auth.c	Thu Apr 22 14:45:57 2010 +0900
@@ -208,6 +208,11 @@
 		}
 	}
 
+	while (mechanisms) {
+		g_free(mechanisms->data);
+		mechanisms = g_slist_delete_link(mechanisms, mechanisms);
+	}
+
 	if (js->auth_mech == NULL) {
 		/* Found no good mechanisms... */
 		purple_connection_error_reason(js->gc,
--- a/libpurple/protocols/jabber/auth_scram.c	Thu Apr 15 16:48:50 2010 +0900
+++ b/libpurple/protocols/jabber/auth_scram.c	Thu Apr 22 14:45:57 2010 +0900
@@ -301,11 +301,17 @@
 #endif
 
 		ret = jabber_scram_calc_proofs(data, salt, iterations);
-		if (!ret)
+
+		g_string_free(salt, TRUE);
+		salt = NULL;
+		if (!ret) {
+			g_free(nonce);
 			return FALSE;
+		}
 
 		proof = purple_base64_encode((guchar *)data->client_proof->str, data->client_proof->len);
 		*out = g_strdup_printf("c=%s,r=%s,p=%s", "biws", nonce, proof);
+		g_free(nonce);
 		g_free(proof);
 	} else if (data->step == 2) {
 		gchar *server_sig, *enc_server_sig;
@@ -419,7 +425,7 @@
 {
 	JabberScramData *data = js->auth_mech_data;
 	xmlnode *reply;
-	gchar *enc_in, *dec_in;
+	gchar *enc_in, *dec_in = NULL;
 	gchar *enc_out = NULL, *dec_out = NULL;
 	gsize len;
 	JabberSaslState state = JABBER_SASL_STATE_FAIL;
@@ -434,7 +440,6 @@
 	}
 
 	dec_in = (gchar *)purple_base64_decode(enc_in, &len);
-	g_free(enc_in);
 	if (!dec_in || len != strlen(dec_in)) {
 		/* Danger afoot; SCRAM shouldn't contain NUL bytes */
 		reply = xmlnode_new("abort");
@@ -468,6 +473,8 @@
 	state = JABBER_SASL_STATE_CONTINUE;
 
 out:
+	g_free(enc_in);
+	g_free(dec_in);
 	g_free(enc_out);
 	g_free(dec_out);
 
@@ -506,11 +513,13 @@
 	purple_debug_misc("jabber", "decoded success: %s\n", dec_in);
 
 	if (!jabber_scram_feed_parser(data, dec_in, &dec_out) || dec_out != NULL) {
+		g_free(dec_in);
 		g_free(dec_out);
 		*error = g_strdup(_("Invalid challenge from server"));
 		return JABBER_SASL_STATE_FAIL;
 	}
 
+	g_free(dec_in);
 	/* Hooray */
 	return JABBER_SASL_STATE_OK;
 }
--- a/libpurple/protocols/jabber/bosh.c	Thu Apr 15 16:48:50 2010 +0900
+++ b/libpurple/protocols/jabber/bosh.c	Thu Apr 22 14:45:57 2010 +0900
@@ -78,13 +78,11 @@
 	} state;
 	guint8 failed_connections;
 
-	int max_inactivity;
 	int wait;
 
 	int max_requests;
 	int requests;
 
-	guint inactivity_timer;
 	guint send_timer;
 };
 
@@ -239,8 +237,6 @@
 
 	if (conn->send_timer)
 		purple_timeout_remove(conn->send_timer);
-	if (conn->inactivity_timer)
-		purple_timeout_remove(conn->inactivity_timer);
 
 	purple_circ_buffer_destroy(conn->pending);
 
@@ -433,34 +429,14 @@
 	return FALSE;
 }
 
-static gboolean
-bosh_inactivity_cb(gpointer data)
+void
+jabber_bosh_connection_send_keepalive(PurpleBOSHConnection *bosh)
 {
-	PurpleBOSHConnection *bosh = data;
-	bosh->inactivity_timer = 0;
-
 	if (bosh->send_timer != 0)
 		purple_timeout_remove(bosh->send_timer);
 
 	/* clears bosh->send_timer */
 	send_timer_cb(bosh);
-
-	return FALSE;
-}
-
-static void
-restart_inactivity_timer(PurpleBOSHConnection *conn)
-{
-	if (conn->inactivity_timer != 0) {
-		purple_timeout_remove(conn->inactivity_timer);
-		conn->inactivity_timer = 0;
-	}
-
-	if (conn->max_inactivity != 0) {
-		conn->inactivity_timer =
-			purple_timeout_add_seconds(conn->max_inactivity - 5 /* rounding */,
-			                           bosh_inactivity_cb, conn);
-	}
 }
 
 static void jabber_bosh_connection_received(PurpleBOSHConnection *conn, xmlnode *node) {
@@ -541,19 +517,20 @@
 	}
 
 	if (inactivity) {
-		conn->max_inactivity = atoi(inactivity);
-		if (conn->max_inactivity <= 5) {
+		js->max_inactivity = atoi(inactivity);
+		if (js->max_inactivity <= 5) {
 			purple_debug_warning("jabber", "Ignoring bogusly small inactivity: %s\n",
 			                     inactivity);
-			conn->max_inactivity = 0;
+			/* Leave it at the default */
 		} else {
-			/* TODO: Integrate this with jabber.c keepalive checks... */
 			/* TODO: Can this check fail? It shouldn't */
-			if (conn->inactivity_timer == 0) {
+			js->max_inactivity -= 5; /* rounding */
+
+			if (js->inactivity_timer == 0) {
 				purple_debug_misc("jabber", "Starting BOSH inactivity timer "
 						"for %d secs (compensating for rounding)\n",
-						conn->max_inactivity - 5);
-				restart_inactivity_timer(conn);
+						js->max_inactivity);
+				jabber_stream_restart_inactivity_timer(js);
 			}
 		}
 	}
@@ -976,7 +953,7 @@
 	size_t len;
 
 	/* Sending something to the server, restart the inactivity timer */
-	restart_inactivity_timer(conn->bosh);
+	jabber_stream_restart_inactivity_timer(conn->bosh->js);
 
 	data = g_strdup_printf("POST %s HTTP/1.1\r\n"
 	                       "Host: %s\r\n"
--- a/libpurple/protocols/jabber/bosh.h	Thu Apr 15 16:48:50 2010 +0900
+++ b/libpurple/protocols/jabber/bosh.h	Thu Apr 22 14:45:57 2010 +0900
@@ -35,6 +35,7 @@
 void jabber_bosh_connection_destroy(PurpleBOSHConnection *conn);
 
 gboolean jabber_bosh_connection_is_ssl(PurpleBOSHConnection *conn);
+void jabber_bosh_connection_send_keepalive(PurpleBOSHConnection *conn);
 
 void jabber_bosh_connection_connect(PurpleBOSHConnection *conn);
 void jabber_bosh_connection_close(PurpleBOSHConnection *conn);
--- a/libpurple/protocols/jabber/jabber.c	Thu Apr 15 16:48:50 2010 +0900
+++ b/libpurple/protocols/jabber/jabber.c	Thu Apr 22 14:45:57 2010 +0900
@@ -71,6 +71,10 @@
 #include "jingle/rtp.h"
 
 #define PING_TIMEOUT 60
+/* Send a whitespace keepalive to the server if we haven't sent
+ * anything in the last 120 seconds
+ */
+#define DEFAULT_INACTIVITY_TIME 120
 
 GList *jabber_features = NULL;
 GList *jabber_identities = NULL;
@@ -166,6 +170,8 @@
 		char *msg = jabber_parse_error(js, packet, &reason);
 		purple_connection_error_reason(js->gc, reason, msg);
 		g_free(msg);
+
+		return;
 	}
 
 	jabber_session_init(js);
@@ -361,6 +367,9 @@
 	if (len == -1)
 		len = strlen(data);
 
+	if (js->state == JABBER_STREAM_CONNECTED)
+		jabber_stream_restart_inactivity_timer(js);
+
 	if (js->writeh == 0)
 		ret = jabber_do_send(js, data, len);
 	else {
@@ -842,6 +851,7 @@
 		purple_connection_error_reason(gc,
 			PURPLE_CONNECTION_ERROR_INVALID_SETTINGS,
 			_("Invalid XMPP ID"));
+		g_free(user);
 		/* Destroying the connection will free the JabberStream */
 		return NULL;
 	}
@@ -850,6 +860,7 @@
 		purple_connection_error_reason(gc,
 			PURPLE_CONNECTION_ERROR_INVALID_SETTINGS,
 			_("Invalid XMPP ID. Username portion must be set."));
+		g_free(user);
 		/* Destroying the connection will free the JabberStream */
 		return NULL;
 	}
@@ -858,6 +869,7 @@
 		purple_connection_error_reason(gc,
 			PURPLE_CONNECTION_ERROR_INVALID_SETTINGS,
 			_("Invalid XMPP ID. Domain must be set."));
+		g_free(user);
 		/* Destroying the connection will free the JabberStream */
 		return NULL;
 	}
@@ -886,6 +898,7 @@
 	js->write_buffer = purple_circ_buffer_new(512);
 	js->old_length = 0;
 	js->keepalive_timeout = 0;
+	js->max_inactivity = DEFAULT_INACTIVITY_TIME;
 	/* Set the default protocol version to 1.0. Overridden in parser.c. */
 	js->protocol_version.major = 1;
 	js->protocol_version.minor = 0;
@@ -1540,7 +1553,8 @@
 	g_free(js->avatar_hash);
 	g_free(js->caps_hash);
 
-	purple_circ_buffer_destroy(js->write_buffer);
+	if (js->write_buffer)
+		purple_circ_buffer_destroy(js->write_buffer);
 	if(js->writeh)
 		purple_input_remove(js->writeh);
 	if (js->auth_mech && js->auth_mech->dispose)
@@ -1579,6 +1593,8 @@
 
 	if (js->keepalive_timeout != 0)
 		purple_timeout_remove(js->keepalive_timeout);
+	if (js->inactivity_timer != 0)
+		purple_timeout_remove(js->inactivity_timer);
 
 	g_free(js->srv_rec);
 	js->srv_rec = NULL;
@@ -1630,6 +1646,9 @@
 		case JABBER_STREAM_CONNECTED:
 			/* Send initial presence */
 			jabber_presence_send(js, TRUE);
+			/* Start up the inactivity timer */
+			jabber_stream_restart_inactivity_timer(js);
+
 			purple_connection_set_state(js->gc, PURPLE_CONNECTED);
 			break;
 	}
@@ -1918,6 +1937,38 @@
 	       (!js->bosh && js->gsc);
 }
 
+static gboolean
+inactivity_cb(gpointer data)
+{
+	JabberStream *js = data;
+
+	/* We want whatever is sent to set this.  It's okay because
+	 * the eventloop unsets it via the return FALSE.
+	 */
+	js->inactivity_timer = 0;
+
+	if (js->bosh)
+		jabber_bosh_connection_send_keepalive(js->bosh);
+	else
+		jabber_send_raw(js, "\t", 1);
+
+	return FALSE;
+}
+
+void jabber_stream_restart_inactivity_timer(JabberStream *js)
+{
+	if (js->inactivity_timer != 0) {
+		purple_timeout_remove(js->inactivity_timer);
+		js->inactivity_timer = 0;
+	}
+
+	g_return_if_fail(js->max_inactivity > 0);
+
+	js->inactivity_timer =
+		purple_timeout_add_seconds(js->max_inactivity,
+		                           inactivity_cb, js);
+}
+
 const char *jabber_list_icon(PurpleAccount *a, PurpleBuddy *b)
 {
 	return "jabber";
@@ -3684,6 +3735,11 @@
 	jabber_presence_uninit();
 	jabber_iq_uninit();
 
+#ifdef USE_VV
+	g_signal_handlers_disconnect_by_func(G_OBJECT(purple_media_manager_get()),
+			G_CALLBACK(jabber_caps_broadcast_change), NULL);
+#endif
+
 	jabber_auth_uninit();
 	jabber_features_destroy();
 	jabber_identities_destroy();
--- a/libpurple/protocols/jabber/jabber.h	Thu Apr 15 16:48:50 2010 +0900
+++ b/libpurple/protocols/jabber/jabber.h	Thu Apr 22 14:45:57 2010 +0900
@@ -255,6 +255,8 @@
 
 	/* A purple timeout tag for the keepalive */
 	guint keepalive_timeout;
+	guint max_inactivity;
+	guint inactivity_timer;
 
 	PurpleSrvResponse *srv_rec;
 	guint srv_rec_idx;
@@ -349,6 +351,13 @@
  */
 gboolean jabber_stream_is_ssl(JabberStream *js);
 
+/**
+ * Restart the "we haven't sent anything in a while and should send
+ * something or the server will kick us off" timer (obviously
+ * called when sending something.  It's exposed for BOSH.)
+ */
+void jabber_stream_restart_inactivity_timer(JabberStream *js);
+
 /** PRPL functions */
 const char *jabber_list_icon(PurpleAccount *a, PurpleBuddy *b);
 const char* jabber_list_emblem(PurpleBuddy *b);
--- a/libpurple/protocols/jabber/presence.c	Thu Apr 15 16:48:50 2010 +0900
+++ b/libpurple/protocols/jabber/presence.c	Thu Apr 22 14:45:57 2010 +0900
@@ -823,6 +823,7 @@
 		if (presence->jb != js->user_jb) {
 			purple_debug_warning("jabber", "Got presence for unknown buddy %s on account %s (%p)\n",
 					buddy_name, purple_account_get_username(account), account);
+			g_free(buddy_name);
 			return FALSE;
 		} else {
 			/* this is a different resource of our own account. Resume even when this account isn't on our blist */
--- a/libpurple/protocols/msn/msn.c	Thu Apr 15 16:48:50 2010 +0900
+++ b/libpurple/protocols/msn/msn.c	Thu Apr 22 14:45:57 2010 +0900
@@ -610,6 +610,14 @@
 {
 	MsnSlpLink *slplink = xfer->data;
 	msn_slplink_request_ft(slplink, xfer);
+	msn_slplink_unref(slplink);
+}
+
+static void
+t_msn_xfer_cancel_send(PurpleXfer *xfer)
+{
+	MsnSlpLink *slplink = xfer->data;
+	msn_slplink_unref(slplink);
 }
 
 static PurpleXfer*
@@ -624,9 +632,10 @@
 
 	g_return_val_if_fail(xfer != NULL, NULL);
 
-	xfer->data = msn_session_get_slplink(session, who);
+	xfer->data = msn_slplink_ref(msn_session_get_slplink(session, who));
 
 	purple_xfer_set_init_fnc(xfer, t_msn_xfer_init);
+	purple_xfer_set_cancel_send_fnc(xfer, t_msn_xfer_cancel_send);
 
 	return xfer;
 }
--- a/libpurple/protocols/msn/notification.c	Thu Apr 15 16:48:50 2010 +0900
+++ b/libpurple/protocols/msn/notification.c	Thu Apr 22 14:45:57 2010 +0900
@@ -983,19 +983,12 @@
 static void
 fln_cmd(MsnCmdProc *cmdproc, MsnCommand *cmd)
 {
-	MsnSlpLink *slplink;
 	MsnUser *user;
 
 	/* Tell libpurple that the user has signed off */
 	user = msn_userlist_find_user(cmdproc->session->userlist, cmd->params[0]);
 	msn_user_set_state(user, NULL);
 	msn_user_update(user);
-
-	/* If we have an open MsnSlpLink with the user then close it */
-	slplink = msn_session_find_slplink(cmdproc->session, cmd->params[0]);
-	if (slplink != NULL)
-		msn_slplink_destroy(slplink);
-
 }
 
 static void
--- a/libpurple/protocols/msn/slplink.c	Thu Apr 15 16:48:50 2010 +0900
+++ b/libpurple/protocols/msn/slplink.c	Thu Apr 22 14:45:57 2010 +0900
@@ -78,7 +78,7 @@
 	session->slplinks =
 		g_list_append(session->slplinks, slplink);
 
-	return slplink;
+	return msn_slplink_ref(slplink);
 }
 
 void
@@ -94,6 +94,11 @@
 	if (slplink->swboard != NULL)
 		slplink->swboard->slplinks = g_list_remove(slplink->swboard->slplinks, slplink);
 
+	if (slplink->refs > 1) {
+		slplink->refs--;
+		return;
+	}
+
 	session = slplink->session;
 
 #if 0
@@ -115,6 +120,31 @@
 }
 
 MsnSlpLink *
+msn_slplink_ref(MsnSlpLink *slplink)
+{
+	g_return_val_if_fail(slplink != NULL, NULL);
+
+	slplink->refs++;
+	if (purple_debug_is_verbose())
+		purple_debug_info("msn", "slplink ref (%p)[%d]\n", slplink, slplink->refs);
+
+	return slplink;
+}
+
+void
+msn_slplink_unref(MsnSlpLink *slplink)
+{
+	g_return_if_fail(slplink != NULL);
+
+	slplink->refs--;
+	if (purple_debug_is_verbose())
+		purple_debug_info("msn", "slplink unref (%p)[%d]\n", slplink, slplink->refs);
+
+	if (slplink->refs == 0)
+		msn_slplink_destroy(slplink);
+}
+
+MsnSlpLink *
 msn_session_find_slplink(MsnSession *session, const char *who)
 {
 	GList *l;
--- a/libpurple/protocols/msn/slplink.h	Thu Apr 15 16:48:50 2010 +0900
+++ b/libpurple/protocols/msn/slplink.h	Thu Apr 22 14:45:57 2010 +0900
@@ -43,6 +43,8 @@
 	MsnSession *session;
 	MsnSwitchBoard *swboard;
 
+	int refs;
+
 	char *remote_user;
 
 	int slp_seq_id;
@@ -55,6 +57,9 @@
 	GQueue *slp_msg_queue;
 };
 
+MsnSlpLink *msn_slplink_ref(MsnSlpLink *slplink);
+void msn_slplink_unref(MsnSlpLink *slplink);
+
 void msn_slplink_destroy(MsnSlpLink *slplink);
 
 /**
--- a/libpurple/protocols/msn/switchboard.c	Thu Apr 15 16:48:50 2010 +0900
+++ b/libpurple/protocols/msn/switchboard.c	Thu Apr 22 14:45:57 2010 +0900
@@ -937,7 +937,7 @@
 	}
 
 	imgid = purple_imgstore_add_with_id(image_data, image_len, NULL);
-	image_msg = g_strdup_printf("<IMG ID='%d'/>", imgid);
+	image_msg = g_strdup_printf("<IMG ID='%d'>", imgid);
 
 	if (swboard->current_users > 1 ||
 		((swboard->conv != NULL) &&
--- a/libpurple/protocols/oscar/family_icbm.c	Thu Apr 15 16:48:50 2010 +0900
+++ b/libpurple/protocols/oscar/family_icbm.c	Thu Apr 22 14:45:57 2010 +0900
@@ -274,8 +274,8 @@
 			| AIM_IMPARAM_FLAG_MISSED_CALLS_ENABLED
 			| AIM_IMPARAM_FLAG_EVENTS_ALLOWED
 			| AIM_IMPARAM_FLAG_SMS_SUPPORTED
-			| AIM_IMPARAM_FLAG_SEND_ME_HTML_FOR_ICQ
-			| AIM_IMPARAM_FLAG_OFFLINE_MSGS_ALLOWED;
+			| AIM_IMPARAM_FLAG_OFFLINE_MSGS_ALLOWED
+			| AIM_IMPARAM_FLAG_USE_HTML_FOR_ICQ;
 	params.maxmsglen = 8000;
 	params.minmsginterval = 0;
 
--- a/libpurple/protocols/oscar/oscar.h	Thu Apr 15 16:48:50 2010 +0900
+++ b/libpurple/protocols/oscar/oscar.h	Thu Apr 22 14:45:57 2010 +0900
@@ -746,7 +746,21 @@
 #define AIM_IMPARAM_FLAG_EVENTS_ALLOWED         0x00000008
 #define AIM_IMPARAM_FLAG_SMS_SUPPORTED          0x00000010
 #define AIM_IMPARAM_FLAG_OFFLINE_MSGS_ALLOWED   0x00000100
-#define AIM_IMPARAM_FLAG_SEND_ME_HTML_FOR_ICQ   0x00000400
+
+/**
+ * This flag tells the server that we always send HTML in messages
+ * sent from an ICQ account to an ICQ account.  (If this flag is
+ * not sent then plaintext is sent ICQ<-->ICQ (HTML is sent in all
+ * other cases)).
+ *
+ * If we send an HTML message to an old client that doesn't support
+ * HTML messages, then the oscar servers will merrily strip the HTML
+ * for us.
+ *
+ * When we receive an IM we look at the features on the ICBM to
+ * determine if the message is HTML or plaintext.
+ */
+#define AIM_IMPARAM_FLAG_USE_HTML_FOR_ICQ       0x00000400
 
 /* This is what the server will give you if you don't set them yourself. */
 /* This is probably out of date. */
--- a/libpurple/proxy.c	Thu Apr 15 16:48:50 2010 +0900
+++ b/libpurple/proxy.c	Thu Apr 22 14:45:57 2010 +0900
@@ -379,11 +379,16 @@
 	char *d;
 
 	d = g_strrstr(host, ":");
-	if (d)
+	if (d) {
 		*d = '\0';
-	d++;
-	if (*d)
-		sscanf(d, "%d", &port);
+
+		d++;
+		if (*d)
+			sscanf(d, "%d", &port);
+
+		if (port == 0)
+			port = default_port;
+	}
 
 	purple_proxy_info_set_host(info, host);
 	purple_proxy_info_set_port(info, port);
--- a/libpurple/util.c	Thu Apr 15 16:48:50 2010 +0900
+++ b/libpurple/util.c	Thu Apr 22 14:45:57 2010 +0900
@@ -586,6 +586,7 @@
 	{
 		purple_debug_error("util", "Format conversion failed in purple_utf8_strftime(): %s\n", err->message);
 		g_error_free(err);
+		err = NULL;
 		locale = g_strdup(format);
 	}
 
@@ -3011,22 +3012,86 @@
 #endif
 }
 
+typedef union purple_sockaddr {
+	struct sockaddr         sa;
+	struct sockaddr_in      sa_in;
+#if defined(AF_INET6)
+	struct sockaddr_in6     sa_in6;
+#endif
+	struct sockaddr_storage sa_stor;
+} PurpleSockaddr;
+
 char *
 purple_fd_get_ip(int fd)
 {
-	struct sockaddr addr;
+	PurpleSockaddr addr;
 	socklen_t namelen = sizeof(addr);
-	struct in_addr in;
+	int family;
 
 	g_return_val_if_fail(fd != 0, NULL);
 
-	if (getsockname(fd, &addr, &namelen))
+	if (getsockname(fd, &(addr.sa), &namelen))
 		return NULL;
 
-	in = ((struct sockaddr_in *)&addr)->sin_addr;
-	return g_strdup(inet_ntoa(in));
+	family = addr.sa.sa_family;
+
+	if (family == AF_INET) {
+		return g_strdup(inet_ntoa(addr.sa_in.sin_addr));
+	}
+#if defined(AF_INET6) && defined(HAVE_INET_NTOP)
+	else if (family == AF_INET6) {
+		char host[INET6_ADDRSTRLEN];
+		const char *tmp;
+
+		tmp = inet_ntop(family, &(addr.sa_in6.sin6_addr), host, sizeof(host));
+		return g_strdup(tmp);
+	}
+#endif
+
+	return NULL;
 }
 
+int
+purple_socket_get_family(int fd)
+{
+	PurpleSockaddr addr;
+	socklen_t len = sizeof(addr);
+
+	g_return_val_if_fail(fd >= 0, -1);
+
+	if (getsockname(fd, &(addr.sa), &len))
+		return -1;
+
+	return addr.sa.sa_family;
+}
+
+gboolean
+purple_socket_speaks_ipv4(int fd)
+{
+	int family;
+
+	g_return_val_if_fail(fd >= 0, FALSE);
+
+	family = purple_socket_get_family(fd);
+
+	switch (family) {
+	case AF_INET:
+		return TRUE;
+#if defined(IPV6_V6ONLY)
+	case AF_INET6:
+	{
+		int val = 0;
+		guint len = sizeof(val);
+
+		if (getsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &val, &len) != 0)
+			return FALSE;
+		return !val;
+	}
+#endif
+	default:
+		return FALSE;
+	}
+}
 
 /**************************************************************************
  * String Functions
--- a/libpurple/util.h	Thu Apr 15 16:48:50 2010 +0900
+++ b/libpurple/util.h	Thu Apr 22 14:45:57 2010 +0900
@@ -819,6 +819,29 @@
  */
 char *purple_fd_get_ip(int fd);
 
+/**
+ * Returns the address family of a socket.
+ *
+ * @param fd The socket file descriptor.
+ *
+ * @return The address family of the socket (AF_INET, AF_INET6, etc) or -1
+ *         on error.
+ * @since 2.7.0
+ */
+int purple_socket_get_family(int fd);
+
+/**
+ * Returns TRUE if a socket is capable of speaking IPv4.
+ *
+ * This is the case for IPv4 sockets and, on some systems, IPv6 sockets
+ * (due to the IPv4-mapped address functionality).
+ *
+ * @param fd The socket file descriptor
+ * @return TRUE if a socket can speak IPv4.
+ * @since 2.7.0
+ */
+gboolean purple_socket_speaks_ipv4(int fd);
+
 /*@}*/
 
 
--- a/pidgin/gtkblist.c	Thu Apr 15 16:48:50 2010 +0900
+++ b/pidgin/gtkblist.c	Thu Apr 22 14:45:57 2010 +0900
@@ -7467,7 +7467,7 @@
 	return gtkblist;
 }
 
-static void account_signon_cb(PurpleConnection *gc, gpointer z)
+static gboolean autojoin_cb(PurpleConnection *gc, gpointer data)
 {
 	PurpleAccount *account = purple_connection_get_account(gc);
 	PurpleBlistNode *gnode, *cnode;
@@ -7493,6 +7493,9 @@
 				serv_join_chat(gc, chat->components);
 		}
 	}
+
+	/* Stop processing; we handled the autojoins. */
+	return TRUE;
 }
 
 void *
@@ -7569,10 +7572,6 @@
 
 	cached_emblems = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
 
-	purple_signal_connect(purple_connections_get_handle(), "signed-on",
-						gtk_blist_handle, PURPLE_CALLBACK(account_signon_cb),
-						NULL);
-
 	/* Initialize prefs */
 	purple_prefs_add_none(PIDGIN_PREFS_ROOT "/blist");
 	purple_prefs_add_bool(PIDGIN_PREFS_ROOT "/blist/show_buddy_icons", TRUE);
@@ -7631,6 +7630,9 @@
 	purple_signal_connect(purple_blist_get_handle(), "buddy-privacy-changed",
 			gtk_blist_handle, PURPLE_CALLBACK(pidgin_blist_update_privacy_cb), NULL);
 
+	purple_signal_connect_priority(purple_connections_get_handle(), "autojoin",
+	                               gtk_blist_handle, PURPLE_CALLBACK(autojoin_cb),
+	                               NULL, PURPLE_SIGNAL_PRIORITY_HIGHEST);
 }
 
 void
@@ -7746,7 +7748,6 @@
 		return;
 	}
 
-
 	if (!gtk_tree_model_iter_children(GTK_TREE_MODEL(gtkblist->treemodel), &more_z, &groupiter)) {
 		gtk_tree_store_insert(gtkblist->treemodel, iter, &groupiter, 0);
 		return;
--- a/pidgin/gtkimhtml.c	Thu Apr 15 16:48:50 2010 +0900
+++ b/pidgin/gtkimhtml.c	Thu Apr 22 14:45:57 2010 +0900
@@ -1198,8 +1198,14 @@
 		printf("\n");
 		}
 #endif
-		text = g_malloc(selection_data->length);
+
+		text = g_malloc(selection_data->length + 1);
 		memcpy(text, selection_data->data, selection_data->length);
+		/* Make sure the paste data is null-terminated.  Given that
+		 * we're passed length (but assume later that it is
+		 * null-terminated), this seems sensible to me.
+		 */
+		text[selection_data->length] = '\0';
 	}
 
 #ifdef _WIN32
--- a/pidgin/gtkutils.c	Thu Apr 15 16:48:50 2010 +0900
+++ b/pidgin/gtkutils.c	Thu Apr 22 14:45:57 2010 +0900
@@ -2469,7 +2469,7 @@
 					break;
 				}
 
-				if (spec->max_filesize == 0 || length < spec->max_filesize) {
+				if (spec->max_filesize == 0 || length <= spec->max_filesize) {
 					/* We were able to save the image as this image type and
 					   have it be within the size constraints.  Great!  Return
 					   the image. */
@@ -2507,7 +2507,7 @@
 		new_height = orig_height * scale_factor;
 		g_object_unref(G_OBJECT(pixbuf));
 		pixbuf = gdk_pixbuf_scale_simple(original, new_width, new_height, GDK_INTERP_HYPER);
-	} while (new_width > 10 || new_height > 10);
+	} while ((new_width > 10 || new_height > 10) && new_width > spec->min_width && new_height > spec->min_height);
 	g_strfreev(prpl_formats);
 	g_object_unref(G_OBJECT(pixbuf));
 	g_object_unref(G_OBJECT(original));
--- a/pidgin/plugins/ticker/gtkticker.c	Thu Apr 15 16:48:50 2010 +0900
+++ b/pidgin/plugins/ticker/gtkticker.c	Thu Apr 22 14:45:57 2010 +0900
@@ -343,7 +343,7 @@
 
 	window = gdk_window_new (gtk_widget_get_parent_window (widget),
 			&attributes, attributes_mask);
-#if GTK_CHECK_VERSION(2,14,0)
+#if GTK_CHECK_VERSION(2,18,0)
 	gtk_widget_set_window (widget, window);
 #else
 	widget->window = window;