changeset 27649:ccb1f1777df7

merge of '01d5e162e0d796e96df727bb20f12c2e2bfdd3d0' and 'f09b8fa2cb4486c1ddd4fbc6343caac76d3134a8'
author Elliott Sales de Andrade <qulogic@pidgin.im>
date Thu, 16 Jul 2009 05:17:45 +0000
parents affa3b67c651 (current diff) 36aa9ed8cd39 (diff)
children b2f0d92aa299 0f48945aa77f
files
diffstat 17 files changed, 257 insertions(+), 121 deletions(-) [+]
line wrap: on
line diff
--- a/ChangeLog	Thu Jul 16 05:15:42 2009 +0000
+++ b/ChangeLog	Thu Jul 16 05:17:45 2009 +0000
@@ -7,7 +7,7 @@
 	* Voice & Video framework in libpurple, thanks to Mike Ruprecht's summer
 	  of code project in 2008.
 	* It should no longer be possible to end up with duplicates of buddies
-	  in a group on the buddy list. (Paul Aurich)
+	  in a group on the buddy list.
 	* Removed the unmaintained and unneeded toc protocol plugin.
 	* Fixed NTLM authentication on big-endian systems.
 	* Various memory cleanups when unloading libpurple. (Nick Hebner)
@@ -35,6 +35,9 @@
 	  from you on MSN.
 	* Support sending an invite message to buddies when requesting authorization
 	  from them on MSN.
+	* Better handling of corrupt certificates in the TLS Peers cache.
+	* More efficient purple_find_buddies() and purple_find_group() functions.
+	  (Jan Kaluza and Aman Gupta)
 
 	AIM and ICQ:
 	* Preliminary support for a new authentication scheme called
--- a/ChangeLog.API	Thu Jul 16 05:15:42 2009 +0000
+++ b/ChangeLog.API	Thu Jul 16 05:17:45 2009 +0000
@@ -56,6 +56,7 @@
 		* purple_request_field_get_group
 		* purple_request_field_get_ui_data
 		* purple_request_field_set_ui_data
+		* purple_ssl_connect_with_ssl_cn
 		* purple_strequal
 		* purple_utf8_strip_unprintables
 		* purple_util_fetch_url_request_len_with_account
@@ -79,6 +80,9 @@
 		* Added a client_type field in the get_ui_info core UI op. See
 		  core.h for details.
 		* Added introspection of signals exposed via the D-Bus API.
+		* purple_find_buddies is now more efficient in the case where
+		  it is enumerating all the buddies for an account.
+		* purple_find_group is now more efficient for large numbers of groups.
 
 		Deprecated:
 		* buddy-added and buddy-removed blist signals
--- a/libpurple/blist.c	Thu Jul 16 05:15:42 2009 +0000
+++ b/libpurple/blist.c	Thu Jul 16 05:17:45 2009 +0000
@@ -48,6 +48,12 @@
  */
 static GHashTable *buddies_cache = NULL;
 
+/**
+ * A hash table used for efficient lookups of groups by name.
+ * UTF-8 collate-key => PurpleGroup*.
+ */
+static GHashTable *groups_cache = NULL;
+
 static guint          save_timer = 0;
 static gboolean       blist_loaded = FALSE;
 
@@ -704,6 +710,10 @@
 	buddies_cache = g_hash_table_new_full(g_direct_hash, g_direct_equal,
 					 NULL, (GDestroyNotify)g_hash_table_destroy);
 
+	groups_cache = g_hash_table_new_full((GHashFunc)g_str_hash,
+					 (GEqualFunc)g_str_equal,
+					 (GDestroyNotify)g_free, NULL);
+
 	for (account = purple_accounts_get_all(); account != NULL; account = account->next)
 	{
 		purple_blist_buddies_cache_add_account(account->data);
@@ -1203,6 +1213,7 @@
 	} else {
 		/* A simple rename */
 		PurpleBlistNode *cnode, *bnode;
+		gchar* key;
 
 		/* Build a GList of all buddies in this group */
 		for (cnode = ((PurpleBlistNode *)source)->child; cnode != NULL; cnode = cnode->next) {
@@ -1213,6 +1224,13 @@
 
 		old_name = source->name;
 		source->name = new_name;
+
+		key = g_utf8_collate_key(old_name, -1);
+		g_hash_table_remove(groups_cache, key);
+		g_free(key);
+
+		key = g_utf8_collate_key(new_name, -1);
+		g_hash_table_insert(groups_cache, key, source);
 	}
 
 	/* Save our changes */
@@ -1946,6 +1964,7 @@
 {
 	PurpleBlistUiOps *ops;
 	PurpleBlistNode *gnode = (PurpleBlistNode*)group;
+	gchar* key;
 
 	g_return_if_fail(group != NULL);
 	g_return_if_fail(PURPLE_BLIST_NODE_IS_GROUP((PurpleBlistNode *)group));
@@ -1989,6 +2008,9 @@
 		purplebuddylist->root = gnode;
 	}
 
+	key = g_utf8_collate_key(group->name, -1);
+	g_hash_table_insert(groups_cache, key, group);
+
 	purple_blist_schedule_save();
 
 	if (ops && ops->update) {
@@ -2174,6 +2196,7 @@
 	PurpleBlistUiOps *ops = purple_blist_get_ui_ops();
 	PurpleBlistNode *node;
 	GList *l;
+	gchar* key;
 
 	g_return_if_fail(group != NULL);
 
@@ -2191,6 +2214,10 @@
 	if (node->next)
 		node->next->prev = node->prev;
 
+	key = g_utf8_collate_key(group->name, -1);
+	g_hash_table_remove(groups_cache, key);
+	g_free(key);
+
 	purple_blist_schedule_save();
 
 	/* Update the UI */
@@ -2427,17 +2454,17 @@
 
 PurpleGroup *purple_find_group(const char *name)
 {
-	PurpleBlistNode *node;
+	gchar* key;
+	PurpleGroup *group;
 
 	g_return_val_if_fail(purplebuddylist != NULL, NULL);
 	g_return_val_if_fail((name != NULL) && (*name != '\0'), NULL);
 
-	for (node = purplebuddylist->root; node != NULL; node = node->next) {
-		if (!purple_utf8_strcasecmp(((PurpleGroup *)node)->name, name))
-			return (PurpleGroup *)node;
-	}
-
-	return NULL;
+	key = g_utf8_collate_key(name, -1);
+	group = g_hash_table_lookup(groups_cache, key);
+	g_free(key);
+
+	return group;
 }
 
 PurpleChat *
@@ -3117,6 +3144,10 @@
 
 	g_hash_table_destroy(purplebuddylist->buddies);
 	g_hash_table_destroy(buddies_cache);
+	g_hash_table_destroy(groups_cache);
+
+	buddies_cache = NULL;
+	groups_cache = NULL;
 
 	PURPLE_DBUS_UNREGISTER_POINTER(purplebuddylist);
 	g_free(purplebuddylist);
--- a/libpurple/certificate.c	Thu Jul 16 05:15:42 2009 +0000
+++ b/libpurple/certificate.c	Thu Jul 16 05:17:45 2009 +0000
@@ -1218,20 +1218,6 @@
 }
 
 static void
-x509_tls_cached_peer_cert_changed(PurpleCertificateVerificationRequest *vrq)
-{
-	/* TODO: Prompt the user, etc. */
-
-	purple_debug_info("certificate/x509/tls_cached",
-			  "Certificate for %s does not match cached. "
-			  "Auto-rejecting!\n",
-			  vrq->subject_name);
-
-	purple_certificate_verify_complete(vrq, PURPLE_CERTIFICATE_INVALID);
-	return;
-}
-
-static void
 x509_tls_cached_unknown_peer(PurpleCertificateVerificationRequest *vrq);
 
 static void
@@ -1254,12 +1240,11 @@
 	cached_crt = purple_certificate_pool_retrieve(
 		tls_peers, vrq->subject_name);
 	if ( !cached_crt ) {
-		purple_debug_error("certificate/x509/tls_cached",
+		purple_debug_warning("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);
+				   "Falling back to full verification.\n");
+		/* vrq now becomes the problem of unknown_peer */
+		x509_tls_cached_unknown_peer(vrq);
 		return;
 	}
 
--- a/libpurple/plugins/ssl/ssl-nss.c	Thu Jul 16 05:15:42 2009 +0000
+++ b/libpurple/plugins/ssl/ssl-nss.c	Thu Jul 16 05:17:45 2009 +0000
@@ -546,12 +546,12 @@
 	CERTCertificate *crt_dat;
 	PurpleCertificate *crt;
 
-	g_return_val_if_fail(filename, NULL);
+	g_return_val_if_fail(filename != NULL, NULL);
 
 	purple_debug_info("nss/x509",
 			  "Loading certificate from %s\n",
 			  filename);
-	
+
 	/* Load the raw data up */
 	if (!g_file_get_contents(filename,
 				 &rawcert, &len,
@@ -560,12 +560,20 @@
 		return NULL;
 	}
 
+	if (len == 0) {
+		purple_debug_error("nss/x509",
+				"Certificate file has no contents!\n");
+		if (rawcert)
+			g_free(rawcert);
+		return NULL;
+	}
+
 	/* Decode the certificate */
 	crt_dat = CERT_DecodeCertFromPackage(rawcert, len);
 	g_free(rawcert);
 
-	g_return_val_if_fail(crt_dat, NULL);
-	
+	g_return_val_if_fail(crt_dat != NULL, NULL);
+
 	crt = g_new0(PurpleCertificate, 1);
 	crt->scheme = &x509_nss;
 	crt->data = crt_dat;
--- a/libpurple/protocols/jabber/bosh.c	Thu Jul 16 05:15:42 2009 +0000
+++ b/libpurple/protocols/jabber/bosh.c	Thu Jul 16 05:17:45 2009 +0000
@@ -51,8 +51,13 @@
 	PurpleHTTPConnection *connections[MAX_HTTP_CONNECTIONS];
 	unsigned short failed_connections;
 
-	gboolean ready;
+	enum {
+		BOSH_CONN_OFFLINE,
+		BOSH_CONN_BOOTING,
+		BOSH_CONN_ONLINE
+	} state;
 	gboolean ssl;
+	gboolean needs_restart;
 
 	/* decoded URL */
 	char *host;
@@ -84,7 +89,11 @@
 
 	PurpleCircBuffer *write_buffer;
 
-	gboolean ready;
+	enum {
+		HTTP_CONN_OFFLINE,
+		HTTP_CONN_CONNECTING,
+		HTTP_CONN_CONNECTED
+	} state;
 	int requests; /* number of outstanding HTTP requests */
 
 	GString *buf;
@@ -129,7 +138,7 @@
 	PurpleHTTPConnection *conn = g_new0(PurpleHTTPConnection, 1);
 	conn->bosh = bosh;
 	conn->fd = -1;
-	conn->ready = FALSE;
+	conn->state = HTTP_CONN_OFFLINE;
 
 	conn->write_buffer = purple_circ_buffer_new(0 /* default grow size */);
 
@@ -176,6 +185,7 @@
 	conn->path = g_strdup_printf("/%s", path);
 	g_free(path);
 	conn->pipelining = TRUE;
+	conn->needs_restart = FALSE;
 
 	if ((user && user[0] != '\0') || (passwd && passwd[0] != '\0')) {
 		purple_debug_info("jabber", "Ignoring unexpected username and password "
@@ -198,7 +208,7 @@
 
 	conn->pending = purple_circ_buffer_new(0 /* default grow size */);
 
-	conn->ready = FALSE;
+	conn->state = BOSH_CONN_OFFLINE;
 	if (purple_strcasestr(url, "https://") != NULL)
 		conn->ssl = TRUE;
 	else
@@ -243,18 +253,28 @@
 	/* Easy solution: Does everyone involved support pipelining? Hooray! Just use
 	 * one TCP connection! */
 	if (conn->pipelining)
-		return conn->connections[0]->ready ? conn->connections[0] : NULL;
+		return conn->connections[0]->state == HTTP_CONN_CONNECTED ?
+				conn->connections[0] : NULL;
 
 	/* First loop, look for a connection that's ready */
 	for (i = 0; i < MAX_HTTP_CONNECTIONS; ++i) {
-		if (conn->connections[i] && conn->connections[i]->ready &&
-		                            conn->connections[i]->requests == 0)
+		if (conn->connections[i] &&
+				conn->connections[i]->state == HTTP_CONN_CONNECTED &&
+				conn->connections[i]->requests == 0)
 			return conn->connections[i];
 	}
 
-	/* Second loop, look for one that's NULL and create a new connection */
+	/* Second loop, is something currently connecting? If so, just queue up. */
+	for (i = 0; i < MAX_HTTP_CONNECTIONS; ++i) {
+		if (conn->connections[i] &&
+				conn->connections[i]->state == HTTP_CONN_CONNECTING)
+			return NULL;
+	}
+
+	/* Third loop, look for one that's NULL and create a new connection */
 	for (i = 0; i < MAX_HTTP_CONNECTIONS; ++i) {
 		if (!conn->connections[i]) {
+			purple_debug_info("jabber", "bosh: Creating and connecting new httpconn\n");
 			conn->connections[i] = jabber_bosh_http_connection_init(conn);
 
 			http_connection_connect(conn->connections[i]);
@@ -268,7 +288,7 @@
 
 static void
 jabber_bosh_connection_send(PurpleBOSHConnection *conn,
-                            PurpleBOSHPacketType type, const char *data)
+                            const PurpleBOSHPacketType type, const char *data)
 {
 	PurpleHTTPConnection *chosen;
 	GString *packet = NULL;
@@ -277,12 +297,12 @@
 
 	if (type != PACKET_NORMAL && !chosen) {
 		/*
-		 * For non-ordinary traffic, we don't want to 'buffer' it, so use the
+		 * For non-ordinary traffic, we can't 'buffer' it, so use the
 		 * first connection.
 		 */
 		chosen = conn->connections[0];
 
-		if (!chosen->ready) {
+		if (chosen->state != HTTP_CONN_CONNECTED) {
 			purple_debug_info("jabber", "Unable to find a ready BOSH "
 					"connection. Ignoring send of type 0x%02x.\n", type);
 			return;
@@ -300,6 +320,7 @@
 			int len = data ? strlen(data) : 0;
 			purple_circ_buffer_append(conn->pending, data, len);
 		}
+
 		return;
 	}
 
@@ -316,9 +337,11 @@
 	                conn->sid,
 	                conn->js->user->domain);
 
-	if (type == PACKET_STREAM_RESTART)
+	if (type == PACKET_STREAM_RESTART) {
 		packet = g_string_append(packet, " xmpp:restart='true'/>");
-	else {
+		/* TODO: Do we need to wait for a response? */
+		conn->needs_restart = FALSE;
+	} else {
 		gsize read_amt;
 		if (type == PACKET_TERMINATE)
 			packet = g_string_append(packet, " type='terminate'");
@@ -343,7 +366,9 @@
 	jabber_bosh_connection_send(conn, PACKET_TERMINATE, NULL);
 }
 
-static void jabber_bosh_connection_stream_restart(PurpleBOSHConnection *conn) {
+static void jabber_bosh_connection_stream_restart(PurpleBOSHConnection *conn)
+{
+	conn->needs_restart = TRUE;
 	jabber_bosh_connection_send(conn, PACKET_STREAM_RESTART, NULL);
 }
 
@@ -353,7 +378,7 @@
 	type = xmlnode_get_attrib(node, "type");
 
 	if (type != NULL && !strcmp(type, "terminate")) {
-		conn->ready = FALSE;
+		conn->state = BOSH_CONN_OFFLINE;
 		purple_connection_error_reason(conn->js->gc,
 			PURPLE_CONNECTION_ERROR_OTHER_ERROR,
 			_("The BOSH connection manager terminated your session."));
@@ -384,11 +409,6 @@
 		/* jabber_process_packet might free child */
 		xmlnode *next = child->next;
 		if (child->type == XMLNODE_TYPE_TAG) {
-			if (!strcmp(child->name, "iq")) {
-				if (xmlnode_get_child(child, "session"))
-					conn->ready = TRUE;
-			}
-
 			jabber_process_packet(js, &child);
 		}
 
@@ -476,9 +496,13 @@
 			conn->max_inactivity = 0;
 		} else {
 			/* TODO: Integrate this with jabber.c keepalive checks... */
-			conn->inactivity_timer = purple_timeout_add_seconds(
-					conn->max_inactivity - 2 /* rounding */, bosh_inactivity_cb,
-					conn);
+			if (conn->inactivity_timer == 0) {
+				purple_debug_misc("jabber", "Starting BOSH inactivity timer for %d secs (compensating for rounding)\n",
+						conn->max_inactivity - 5);
+				conn->inactivity_timer = purple_timeout_add_seconds(
+						conn->max_inactivity - 5 /* rounding */,
+						bosh_inactivity_cb, conn);
+			}
 		}
 	}
 
@@ -487,6 +511,7 @@
 
 	/* FIXME: Depending on receiving features might break with some hosts */
 	packet = xmlnode_get_child(node, "features");
+	conn->state = BOSH_CONN_ONLINE;
 	conn->js->use_bosh = TRUE;
 	conn->receive_cb = auth_response_cb;
 	jabber_stream_features_parse(conn->js, packet);
@@ -511,6 +536,8 @@
 	                conn->js->user->domain,
 	                ++conn->rid);
 
+	purple_debug_misc("jabber", "SendBOSH Boot %s(%" G_GSIZE_FORMAT "): %s\n",
+	                  conn->ssl ? "(ssl)" : "", buf->len, buf->str);
 	conn->receive_cb = boot_response_cb;
 	http_connection_send_request(conn->connections[0], buf);
 	g_string_free(buf, TRUE);
@@ -550,7 +577,7 @@
 connection_common_established_cb(PurpleHTTPConnection *conn)
 {
 	/* Indicate we're ready and reset some variables */
-	conn->ready = TRUE;
+	conn->state = HTTP_CONN_CONNECTED;
 	conn->requests = 0;
 	if (conn->buf) {
 		g_string_free(conn->buf, TRUE);
@@ -559,9 +586,11 @@
 	conn->headers_done = FALSE;
 	conn->handled_len = conn->body_len = 0;
 
-	if (conn->bosh->ready) {
+	if (conn->bosh->needs_restart)
+		jabber_bosh_connection_stream_restart(conn->bosh);
+	else if (conn->bosh->state == BOSH_CONN_ONLINE) {
 		purple_debug_info("jabber", "BOSH session already exists. Trying to reuse it.\n");
-		if (conn->bosh->pending->bufused > 0) {
+		if (conn->bosh->requests == 0 || conn->bosh->pending->bufused > 0) {
 			/* Send the pending data */
 			jabber_bosh_connection_send(conn->bosh, PACKET_NORMAL, NULL);
 		}
@@ -585,7 +614,7 @@
 	 * Well, then. Fine! I never liked you anyway, server! I was cheating on you
 	 * with AIM!
 	 */
-	conn->ready = FALSE;
+	conn->state = HTTP_CONN_OFFLINE;
 	if (conn->psc) {
 		purple_ssl_close(conn->psc);
 		conn->psc = NULL;
@@ -620,6 +649,10 @@
 
 void jabber_bosh_connection_connect(PurpleBOSHConnection *bosh) {
 	PurpleHTTPConnection *conn = bosh->connections[0];
+
+	g_return_if_fail(bosh->state == BOSH_CONN_OFFLINE);
+	bosh->state = BOSH_CONN_BOOTING;
+
 	http_connection_connect(conn);
 }
 
@@ -679,10 +712,10 @@
 	http_received_cb(conn->buf->str + conn->handled_len, conn->body_len,
 	                 conn->bosh);
 
-	if (conn->bosh->ready &&
+	if (conn->bosh->state == BOSH_CONN_ONLINE &&
 			(conn->bosh->requests == 0 || conn->bosh->pending->bufused > 0)) {
+		purple_debug_misc("jabber", "BOSH: Sending an empty request\n");
 		jabber_bosh_connection_send(conn->bosh, PACKET_NORMAL, NULL);
-		purple_debug_misc("jabber", "BOSH: Sending an empty request\n");
 	}
 
 	g_string_free(conn->buf, TRUE);
@@ -802,6 +835,8 @@
 	PurpleConnection *gc = bosh->js->gc;
 	PurpleAccount *account = purple_connection_get_account(gc);
 
+	conn->state = HTTP_CONN_CONNECTING;
+
 	if (bosh->ssl) {
 		if (purple_ssl_is_supported()) {
 			conn->psc = purple_ssl_connect(account, bosh->host, bosh->port,
--- a/libpurple/protocols/jabber/buddy.c	Thu Jul 16 05:15:42 2009 +0000
+++ b/libpurple/protocols/jabber/buddy.c	Thu Jul 16 05:17:45 2009 +0000
@@ -702,7 +702,7 @@
 		const char *status_name = jabber_buddy_state_get_name(jbr->state);
 
 		if (jbr->status) {
-			purdy = g_markup_escape_text(jbr->status, -1);
+			purdy = purple_strreplace(jbr->status, "\n", "<br />\n");
 
 			if (purple_strequal(status_name, purdy))
 				status_name = NULL;
--- a/libpurple/protocols/jabber/jutil.c	Thu Jul 16 05:15:42 2009 +0000
+++ b/libpurple/protocols/jabber/jutil.c	Thu Jul 16 05:17:45 2009 +0000
@@ -257,29 +257,29 @@
 
 	/* normalization */
 	if(at) {
-		node = g_utf8_normalize(str, at-str, G_NORMALIZE_NFKC);
+		node = g_utf8_strdown(str, at-str);
 		if(slash) {
-			domain = g_utf8_normalize(at+1, slash-(at+1), G_NORMALIZE_NFKC);
+			domain = g_utf8_strdown(at+1, slash-(at+1));
 			jid->resource = g_utf8_normalize(slash+1, -1, G_NORMALIZE_NFKC);
 		} else {
-			domain = g_utf8_normalize(at+1, -1, G_NORMALIZE_NFKC);
+			domain = g_utf8_strdown(at+1, -1);
 		}
 	} else {
 		if(slash) {
-			domain = g_utf8_normalize(str, slash-str, G_NORMALIZE_NFKC);
+			domain = g_utf8_strdown(str, slash-str);
 			jid->resource = g_utf8_normalize(slash+1, -1, G_NORMALIZE_NFKC);
 		} else {
-			domain = g_utf8_normalize(str, -1, G_NORMALIZE_NFKC);
+			domain = g_utf8_strdown(str, -1);
 		}
 	}
 
 	if (node) {
-		jid->node = g_utf8_strdown(node, -1);
+		jid->node = g_utf8_normalize(node, -1, G_NORMALIZE_NFKC);
 		g_free(node);
 	}
 
 	if (domain) {
-		jid->domain = g_utf8_strdown(domain, -1);
+		jid->domain = g_utf8_normalize(domain, -1, G_NORMALIZE_NFKC);
 		g_free(domain);
 	}
 
--- a/libpurple/protocols/jabber/si.c	Thu Jul 16 05:15:42 2009 +0000
+++ b/libpurple/protocols/jabber/si.c	Thu Jul 16 05:17:45 2009 +0000
@@ -51,9 +51,9 @@
 	char *iq_id;
 
 	enum {
-		STREAM_METHOD_UNKNOWN = 0,
+		STREAM_METHOD_UNKNOWN     = 0,
 		STREAM_METHOD_BYTESTREAMS = 2 << 1,
-		STREAM_METHOD_IBB = 2 << 2,
+		STREAM_METHOD_IBB         = 2 << 2,
 		STREAM_METHOD_UNSUPPORTED = 2 << 31
 	} stream_method;
 
@@ -1379,6 +1379,31 @@
 
 static void jabber_si_xfer_request_denied(PurpleXfer *xfer)
 {
+	JabberSIXfer *jsx = (JabberSIXfer *) xfer->data;
+	JabberStream *js = jsx->js;
+
+	/*
+	 * TODO: It's probably an error if jsx->iq_id == NULL. g_return_if_fail
+	 * might be warranted.
+	 */
+	if (jsx->iq_id && !jsx->accepted) {
+		JabberIq *iq;
+		xmlnode *error, *child;
+		iq = jabber_iq_new(js, JABBER_IQ_ERROR);
+		xmlnode_set_attrib(iq->node, "to", xfer->who);
+		jabber_iq_set_id(iq, jsx->iq_id);
+
+		error = xmlnode_new_child(iq->node, "error");
+		xmlnode_set_attrib(error, "type", "cancel");
+		child = xmlnode_new_child(error, "forbidden");
+		xmlnode_set_namespace(child, "urn:ietf:params:xml:ns:xmpp-stanzas");
+		child = xmlnode_new_child(error, "text");
+		xmlnode_set_namespace(child, "urn:ietf:params:xml:ns:xmpp-stanzas");
+		xmlnode_insert_data(child, "Offer Declined", -1);
+
+		jabber_iq_send(iq);
+	}
+
 	jabber_si_xfer_free(xfer);
 	purple_debug(PURPLE_DEBUG_INFO, "jabber", "in jabber_si_xfer_request_denied\n");
 }
@@ -1421,6 +1446,7 @@
 		purple_notify_error(js->gc, _("File Send Failed"),
 				_("File Send Failed"), msg);
 		g_free(msg);
+		purple_xfer_cancel_local(xfer);
 	}
 }
 
@@ -1434,13 +1460,38 @@
 	JabberSIXfer *jsx = xfer->data;
 	char **who_v = g_strsplit(xfer->who, "/", 2);
 	char *who;
+	JabberBuddy *jb;
+	JabberBuddyResource *jbr = NULL;
+
+	jb = jabber_buddy_find(jsx->js, who_v[0], FALSE);
+	if (jb) {
+		jbr = jabber_buddy_find_resource(jb, resource);
+	}
 
 	who = g_strdup_printf("%s/%s", who_v[0], resource);
 	g_strfreev(who_v);
 	g_free(xfer->who);
 	xfer->who = who;
-	jabber_disco_info_do(jsx->js, who,
-			jabber_si_xfer_send_disco_cb, xfer);
+
+	if (jbr) {
+		char *msg;
+
+		if (jabber_resource_has_capability(jbr, XEP_0047_NAMESPACE))
+			jsx->stream_method |= STREAM_METHOD_IBB;
+		if (jabber_resource_has_capability(jbr, "http://jabber.org/protocol/si/profile/file-transfer")) {
+			jabber_si_xfer_send_request(xfer);
+			return;
+		}
+
+		msg = g_strdup_printf(_("Unable to send file to %s, user does not support file transfers"), who);
+		purple_notify_error(jsx->js->gc, _("File Send Failed"),
+				_("File Send Failed"), msg);
+		g_free(msg);
+		purple_xfer_cancel_local(xfer);
+	} else {
+		jabber_disco_info_do(jsx->js, who,
+				jabber_si_xfer_send_disco_cb, xfer);
+	}
 }
 
 static void resource_select_ok_cb(PurpleXfer *xfer, PurpleRequestFields *fields)
@@ -1529,6 +1580,8 @@
 		xmlnode_set_attrib(iq->node, "to", xfer->who);
 		if(jsx->iq_id)
 			jabber_iq_set_id(iq, jsx->iq_id);
+		else
+			purple_debug_error("jabber", "Sending SI result with new IQ id.\n");
 
 		jsx->accepted = TRUE;
 
--- a/libpurple/protocols/oscar/flap_connection.c	Thu Jul 16 05:15:42 2009 +0000
+++ b/libpurple/protocols/oscar/flap_connection.c	Thu Jul 16 05:17:45 2009 +0000
@@ -505,7 +505,6 @@
 
 	g_free(conn->error_message);
 	g_free(conn->cookie);
-	g_free(conn->ssl_cert_cn);
 
 	/*
 	 * Free conn->internal, if necessary
--- a/libpurple/protocols/oscar/oscar.c	Thu Jul 16 05:15:42 2009 +0000
+++ b/libpurple/protocols/oscar/oscar.c	Thu Jul 16 05:17:45 2009 +0000
@@ -1246,32 +1246,6 @@
 }
 
 static void
-ssl_proxy_conn_established_cb(gpointer data, gint source, const gchar *error_message)
-{
-	OscarData *od;
-	PurpleConnection *gc;
-	PurpleAccount *account;
-	FlapConnection *conn;
-
-	conn = data;
-	od = conn->od;
-	gc = od->gc;
-	account = purple_connection_get_account(gc);
-
-	conn->connect_data = NULL;
-
-	if (source < 0)
-	{
-		connection_common_error_cb(conn, error_message);
-		return;
-	}
-
-	conn->gsc = purple_ssl_connect_with_host_fd(account, source,
-			ssl_connection_established_cb, ssl_connection_error_cb,
-			conn->ssl_cert_cn, conn);
-}
-
-static void
 flap_connection_established_bos(OscarData *od, FlapConnection *conn)
 {
 	PurpleConnection *gc = od->gc;
@@ -1943,12 +1917,13 @@
 	if (od->use_ssl)
 	{
 		/*
-		 * This shouldn't be hardcoded except that the server isn't sending
-		 * us a name to use for comparing the certificate common name.
+		 * This shouldn't be hardcoded to "bos.oscar.aol.com" except that
+		 * the server isn't sending us a name to use for comparing the
+		 * certificate common name.
 		 */
-		newconn->ssl_cert_cn = g_strdup("bos.oscar.aol.com");
-		newconn->connect_data = purple_proxy_connect(NULL, account, host, port,
-				ssl_proxy_conn_established_cb, newconn);
+		newconn->gsc = purple_ssl_connect_with_ssl_cn(account, host, port,
+				ssl_connection_established_cb, ssl_connection_error_cb,
+				"bos.oscar.aol.com", newconn);
 	}
 	else
 	{
@@ -1957,7 +1932,7 @@
 	}
 
 	g_free(host);
-	if (newconn->connect_data == NULL)
+	if (newconn->gsc == NULL && newconn->connect_data == NULL)
 	{
 		purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, _("Unable to connect"));
 		return 0;
@@ -2114,15 +2089,9 @@
 
 	if (redir->use_ssl)
 	{
-		/*
-		 * TODO: It should be possible to specify a certificate common name
-		 * distinct from the host we're passing to purple_ssl_connect. The
-		 * way to work around that is to use purple_proxy_connect +
-		 * purple_ssl_connect_with_host_fd
-		 */
-		newconn->ssl_cert_cn = g_strdup(redir->ssl_cert_cn);
-		newconn->connect_data = purple_proxy_connect(NULL, account, host, port,
-				ssl_proxy_conn_established_cb, newconn);
+		newconn->gsc = purple_ssl_connect_with_ssl_cn(account, host, port,
+				ssl_connection_established_cb, ssl_connection_error_cb,
+				redir->ssl_cert_cn, newconn);
 	}
 	else
 	{
--- a/libpurple/protocols/oscar/oscar.h	Thu Jul 16 05:15:42 2009 +0000
+++ b/libpurple/protocols/oscar/oscar.h	Thu Jul 16 05:17:45 2009 +0000
@@ -429,7 +429,6 @@
 	guint16 cookielen;
 	guint8 *cookie;
 	gpointer new_conn_data;
-	gchar *ssl_cert_cn;
 
 	int fd;
 	PurpleSslConnection *gsc;
--- a/libpurple/sslconn.c	Thu Jul 16 05:15:42 2009 +0000
+++ b/libpurple/sslconn.c	Thu Jul 16 05:17:45 2009 +0000
@@ -100,6 +100,15 @@
 				 PurpleSslInputFunction func, PurpleSslErrorFunction error_func,
 				 void *data)
 {
+	return purple_ssl_connect_with_ssl_cn(account, host, port, func, error_func,
+	                                  NULL, data);
+}
+
+PurpleSslConnection *
+purple_ssl_connect_with_ssl_cn(PurpleAccount *account, const char *host, int port,
+				 PurpleSslInputFunction func, PurpleSslErrorFunction error_func,
+				 const char *ssl_cn, void *data)
+{
 	PurpleSslConnection *gsc;
 
 	g_return_val_if_fail(host != NULL,            NULL);
@@ -116,7 +125,7 @@
 	gsc = g_new0(PurpleSslConnection, 1);
 
 	gsc->fd              = -1;
-	gsc->host            = g_strdup(host);
+	gsc->host            = ssl_cn ? g_strdup(ssl_cn) : g_strdup(host);
 	gsc->port            = port;
 	gsc->connect_cb_data = data;
 	gsc->connect_cb      = func;
--- a/libpurple/sslconn.h	Thu Jul 16 05:15:42 2009 +0000
+++ b/libpurple/sslconn.h	Thu Jul 16 05:17:45 2009 +0000
@@ -186,6 +186,31 @@
 									PurpleSslErrorFunction error_func,
 									void *data);
 
+/**
+ * Makes a SSL connection to the specified host and port, using the separate
+ * name to verify with the certificate.  The caller should keep track of the
+ * returned value and use it to cancel the connection, if needed.
+ *
+ * @param account    The account making the connection.
+ * @param host       The destination host.
+ * @param port       The destination port.
+ * @param func       The SSL input handler function.
+ * @param error_func The SSL error handler function.  This function
+ *                   should <strong>NOT</strong> call purple_ssl_close().  In
+ *                   the event of an error the #PurpleSslConnection will be
+ *                   destroyed for you.
+ * @param ssl_host   The hostname of the other peer (to verify the CN)
+ * @param data       User-defined data.
+ *
+ * @return The SSL connection handle.
+ * @since 2.6.0
+ */
+PurpleSslConnection *purple_ssl_connect_with_ssl_cn(PurpleAccount *account, const char *host,
+									int port, PurpleSslInputFunction func,
+									PurpleSslErrorFunction error_func,
+									const char *ssl_host,
+									void *data);
+
 #if !(defined PURPLE_DISABLE_DEPRECATED) || (defined _PURPLE_SSLCONN_C_)
 /**
  * Makes a SSL connection using an already open file descriptor.
--- a/libpurple/tests/test_jabber_jutil.c	Thu Jul 16 05:15:42 2009 +0000
+++ b/libpurple/tests/test_jabber_jutil.c	Thu Jul 16 05:17:45 2009 +0000
@@ -80,6 +80,17 @@
 	jabber_id_free(jid); \
 }
 
+#define assert_jid_parts(expect_node, expect_domain, str) { \
+	JabberID *jid = jabber_id_new(str); \
+	fail_if(jid == NULL, "JID '%s' is valid but jabber_id_new() rejected it", str); \
+	fail_if(jid->node == NULL,     "JID '%s' is valid but jabber_id_new() didn't return a node", str); \
+	fail_if(jid->domain == NULL,   "JID '%s' is valid but jabber_id_new() didn't return a domain", str); \
+	fail_if(jid->resource != NULL, "JID '%s' doesn't contain a resource", str); \
+	assert_string_equal(expect_node, jid->node); \
+	assert_string_equal(expect_domain, jid->domain); \
+	jabber_id_free(jid); \
+}
+
 START_TEST(test_jabber_id_new)
 {
 	assert_valid_jid("gmail.com");
@@ -117,6 +128,12 @@
 	assert_invalid_jid("mark.doliner@gmail\\stuff.org");
 	assert_invalid_jid("paul@[::1]124");
 	assert_invalid_jid("paul@2[::1]124/as");
+
+	/* 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");
+	assert_jid_parts("ꙥ", "darkrain42.org", "Ꙥ@darkrain42.org");
+	assert_jid_parts("paul", "өarkrain42.org", "paul@Өarkrain42.org");
 }
 END_TEST
 
--- a/pidgin/gtkdialogs.c	Thu Jul 16 05:15:42 2009 +0000
+++ b/pidgin/gtkdialogs.c	Thu Jul 16 05:17:45 2009 +0000
@@ -76,7 +76,7 @@
 	{"Paul 'darkrain42' Aurich",	NULL, NULL },
 	{"John 'rekkanoryo' Bailey",	N_("bug master"), NULL},
 	{"Ethan 'Paco-Paco' Blanton",	NULL, NULL},
-	{"Hylke Bons",			N_("artist"), "h.bons@student.rug.nl"},
+	{"Hylke Bons",			N_("artist"), "hylkebons@gmail.com"},
 	{"Thomas Butter",				NULL, NULL},
 	/* feel free to not translate this */
 	{N_("Ka-Hing Cheung"),			NULL, NULL},
--- a/pidgin/gtkutils.c	Thu Jul 16 05:15:42 2009 +0000
+++ b/pidgin/gtkutils.c	Thu Jul 16 05:17:45 2009 +0000
@@ -3736,10 +3736,9 @@
 		return;
 	}
 
-	if (!g_file_set_contents(file, contents, length, &error)) {
-		purple_debug_error("gtkutils", "Unable to write contents to %s: %s\n",
-		                   file, error->message);
-		g_error_free(error);
+	if (!purple_util_write_data_to_file_absolute(file, contents, length)) {
+		purple_debug_error("gtkutils", "Unable to write contents to %s\n",
+		                   file);
 	}
 }