changeset 27600:fceac27fcad1

merge of 'ebacad33e72745d3734a3d870fee301705c7282c' and 'f276f730c0a3584d3e8c1ee6dc1119e23809b362'
author Paul Aurich <paul@darkrain42.org>
date Sat, 18 Jul 2009 00:40:40 +0000
parents 409ef6d76bf6 (diff) 4bd3f7d841e3 (current diff)
children f3e06bdac3d1
files
diffstat 7 files changed, 327 insertions(+), 143 deletions(-) [+]
line wrap: on
line diff
--- a/ChangeLog	Fri Jul 17 23:45:52 2009 +0000
+++ b/ChangeLog	Sat Jul 18 00:40:40 2009 +0000
@@ -105,6 +105,9 @@
 	  markup if they support it.
 	* Removed support for obsoleted XEP-0022 (Message Events) and XEP-0091
 	  (Legacy Entity Time).
+	* When the GNU IDN library (libidn) is available, it is used for
+	  normalization of Jabber IDs. When unavailable, internal routines are
+	  used (as in previous versions).
 
 	Yahoo!/Yahoo! JAPAN:
 	* P2P file transfers.  (Sulabh Mahajan)
--- a/configure.ac	Fri Jul 17 23:45:52 2009 +0000
+++ b/configure.ac	Sat Jul 18 00:40:40 2009 +0000
@@ -604,7 +604,7 @@
 ])
 			fi])
 	fi
-        
+
 
 else # GTK
 	enable_cap=no
@@ -808,6 +808,25 @@
 	fi
 fi
 
+AC_ARG_ENABLE(idn,
+	[AC_HELP_STRING([--disable-idn], [compile without IDN support])],
+	[enable_idn="$enableval" force_idn=$enableval], [enable_idn="yes" force_idn=no])
+if test "x$enable_idn" != "xno"; then
+	PKG_CHECK_MODULES(IDN, libidn >= 0.0.0, [
+		AC_DEFINE(USE_IDN, 1, [Use GNU Libidn for stringprep and IDN])
+		AC_SUBST(IDN_CFLAGS)
+		AC_SUBST(IDN_LIBS)
+	], [
+		AC_MSG_RESULT(no)
+		if test "x$force_deps" = "xyes" ; then
+			AC_MSG_ERROR([
+GNU Libidn development headers not found.
+Use --disable-idn if you do not need it.
+])
+		fi
+	])
+fi
+
 dnl #######################################################################
 dnl # Check for Meanwhile headers (for Sametime)
 dnl #######################################################################
@@ -1984,7 +2003,7 @@
 
 				if test "x$ac_cv_moz_nss_libs" = "xno"; then
 					nsslibs="-lssl3 -lsmime3 -lnss3 -lsoftokn3"
-					LDFLAGS="$LDFLAGS -L$with_nspr_libs -L$with_nss_libs" 
+					LDFLAGS="$LDFLAGS -L$with_nspr_libs -L$with_nss_libs"
 					LIBS="$LIBS $nsslibs"
 					AC_TRY_LINK_FUNC(NSS_Init,
 						[ac_cv_moz_nss_libs="yes"],
@@ -2026,7 +2045,7 @@
 	AC_SUBST(NSS_CFLAGS)
 	AC_SUBST(NSS_LIBS)
 fi
- 
+
 if test "x$enable_nss" = "xyes"; then
 	AC_MSG_CHECKING(for NSS_SetAlgorithmPolicy)
 	LIBS_save="$LIBS"
@@ -2546,6 +2565,7 @@
 if test "x$enable_dbus" = "xyes" ; then
 	eval eval echo D-Bus services directory...... : $DBUS_SERVICES_DIR
 fi
+echo Build with GNU Libidn......... : $enable_idn
 echo Build with NetworkManager..... : $enable_nm
 echo SSL Library/Libraries......... : $msg_ssl
 if test "x$SSL_CERTIFICATES_DIR" != "x" ; then
--- a/libpurple/protocols/jabber/Makefile.am	Fri Jul 17 23:45:52 2009 +0000
+++ b/libpurple/protocols/jabber/Makefile.am	Sat Jul 18 00:40:40 2009 +0000
@@ -88,7 +88,7 @@
 st =
 pkg_LTLIBRARIES      = libjabber.la libxmpp.la
 libjabber_la_SOURCES = $(JABBERSOURCES)
-libjabber_la_LIBADD  = $(GLIB_LIBS) $(SASL_LIBS) $(LIBXML_LIBS)
+libjabber_la_LIBADD  = $(GLIB_LIBS) $(SASL_LIBS) $(LIBXML_LIBS) $(IDN_LIBS)
 
 libxmpp_la_SOURCES = libxmpp.c
 libxmpp_la_LIBADD = libjabber.la
@@ -100,4 +100,5 @@
 	-I$(top_builddir)/libpurple \
 	$(DEBUG_CFLAGS) \
 	$(GLIB_CFLAGS) \
+	$(IDN_CFLAGS) \
 	$(LIBXML_CFLAGS)
--- a/libpurple/protocols/jabber/jabber.c	Fri Jul 17 23:45:52 2009 +0000
+++ b/libpurple/protocols/jabber/jabber.c	Sat Jul 18 00:40:40 2009 +0000
@@ -759,31 +759,54 @@
 	}
 }
 
-void
-jabber_login(PurpleAccount *account)
+static JabberStream *
+jabber_stream_new(PurpleAccount *account)
 {
 	PurpleConnection *gc = purple_account_get_connection(account);
-	const char *connect_server = purple_account_get_string(account,
-			"connect_server", "");
-	const char *bosh_url = purple_account_get_string(account,
-			"bosh_url", "");
 	JabberStream *js;
+	JabberBuddy *my_jb;
 	PurplePresence *presence;
-	PurpleStoredImage *image;
-	JabberBuddy *my_jb = NULL;
-
-	gc->flags |= PURPLE_CONNECTION_HTML |
-		PURPLE_CONNECTION_ALLOW_CUSTOM_SMILEY;
+
 	js = gc->proto_data = g_new0(JabberStream, 1);
 	js->gc = gc;
 	js->fd = -1;
-	js->iq_callbacks = g_hash_table_new_full(g_str_hash, g_str_equal,
-			g_free, g_free);
+	js->user = jabber_id_new(purple_account_get_username(account));
+
+	if (!js->user) {
+		purple_connection_error_reason(gc,
+			PURPLE_CONNECTION_ERROR_INVALID_SETTINGS,
+			_("Invalid XMPP ID"));
+		/* Destroying the connection will free the JabberStream */
+		return NULL;
+	}
+
+	if (!js->user->domain || *(js->user->domain) == '\0') {
+		purple_connection_error_reason(gc,
+			PURPLE_CONNECTION_ERROR_INVALID_SETTINGS,
+			_("Invalid XMPP ID. Domain must be set."));
+		/* Destroying the connection will free the JabberStream */
+		return NULL;
+	}
+
 	js->buddies = g_hash_table_new_full(g_str_hash, g_str_equal,
 			g_free, (GDestroyNotify)jabber_buddy_free);
+
+	my_jb = jabber_buddy_find(js, purple_account_get_username(account), TRUE);
+	if (!my_jb) {
+		/* This basically *can't* fail, but for good measure... */
+		purple_connection_error_reason(gc,
+			PURPLE_CONNECTION_ERROR_INVALID_SETTINGS,
+			_("Invalid XMPP ID"));
+		/* Destroying the connection will free the JabberStream */
+		g_return_val_if_reached(NULL);
+	}
+
+	my_jb->subscription |= JABBER_SUB_BOTH;
+
+	js->iq_callbacks = g_hash_table_new_full(g_str_hash, g_str_equal,
+			g_free, g_free);
 	js->chats = g_hash_table_new_full(g_str_hash, g_str_equal,
 			g_free, (GDestroyNotify)jabber_chat_free);
-	js->user = jabber_id_new(purple_account_get_username(account));
 	js->next_id = g_random_int();
 	js->write_buffer = purple_circ_buffer_new(512);
 	js->old_length = 0;
@@ -802,37 +825,21 @@
 	if (purple_presence_is_idle(presence))
 		js->idle = purple_presence_get_idle_time(presence);
 
-	if(!js->user) {
-		purple_connection_error_reason(gc,
-			PURPLE_CONNECTION_ERROR_INVALID_SETTINGS,
-			_("Invalid XMPP ID"));
-		return;
-	}
-
-	if (!js->user->domain || *(js->user->domain) == '\0') {
-		purple_connection_error_reason(gc,
-			PURPLE_CONNECTION_ERROR_INVALID_SETTINGS,
-			_("Invalid XMPP ID. Domain must be set."));
-		return;
-	}
-
-	/*
-	 * Calculate the avatar hash for our current image so we know (when we
-	 * fetch our vCard and PEP avatar) if we should send our avatar to the
-	 * server.
-	 */
-	if ((image = purple_buddy_icons_find_account_icon(account))) {
-		js->initial_avatar_hash = jabber_calculate_data_sha1sum(purple_imgstore_get_data(image),
-					purple_imgstore_get_size(image));
-		purple_imgstore_unref(image);
-	}
-
-	if((my_jb = jabber_buddy_find(js, purple_account_get_username(account), TRUE)))
-		my_jb->subscription |= JABBER_SUB_BOTH;
+	return js;
+}
+
+static void
+jabber_stream_connect(JabberStream *js)
+{
+	PurpleConnection *gc = js->gc;
+	PurpleAccount *account = purple_connection_get_account(gc);
+	const char *connect_server = purple_account_get_string(account,
+			"connect_server", "");
+	const char *bosh_url = purple_account_get_string(account,
+			"bosh_url", "");
 
 	jabber_stream_set_state(js, JABBER_STREAM_CONNECTING);
 
-	/* TODO: Just use purple_url_parse? */
 	/* If both BOSH and a Connect Server are specified, we prefer BOSH. I'm not
 	 * attached to that choice, though.
 	 */
@@ -842,7 +849,7 @@
 		if (js->bosh)
 			jabber_bosh_connection_connect(js->bosh);
 		else {
-			purple_connection_error_reason(js->gc,
+			purple_connection_error_reason(gc,
 				PURPLE_CONNECTION_ERROR_INVALID_SETTINGS,
 				_("Malformed BOSH URL"));
 		}
@@ -853,19 +860,18 @@
 	js->certificate_CN = g_strdup(connect_server[0] ? connect_server : js->user->domain);
 
 	/* if they've got old-ssl mode going, we probably want to ignore SRV lookups */
-	if(purple_account_get_bool(js->gc->account, "old_ssl", FALSE)) {
+	if(purple_account_get_bool(account, "old_ssl", FALSE)) {
 		if(purple_ssl_is_supported()) {
-			js->gsc = purple_ssl_connect(js->gc->account,
-					js->certificate_CN,
-					purple_account_get_int(account, "port", 5223), jabber_login_callback_ssl,
-					jabber_ssl_connect_failure, js->gc);
+			js->gsc = purple_ssl_connect(account, js->certificate_CN,
+					purple_account_get_int(account, "port", 5223),
+					jabber_login_callback_ssl, jabber_ssl_connect_failure, gc);
 			if (!js->gsc) {
-				purple_connection_error_reason(js->gc,
+				purple_connection_error_reason(gc,
 					PURPLE_CONNECTION_ERROR_NO_SSL_SUPPORT,
 					_("Unable to establish SSL connection"));
 			}
 		} else {
-			purple_connection_error_reason(js->gc,
+			purple_connection_error_reason(gc,
 				PURPLE_CONNECTION_ERROR_NO_SSL_SUPPORT,
 				_("SSL support unavailable"));
 		}
@@ -883,6 +889,35 @@
 	}
 }
 
+void
+jabber_login(PurpleAccount *account)
+{
+	PurpleConnection *gc = purple_account_get_connection(account);
+	JabberStream *js;
+	PurpleStoredImage *image;
+
+	gc->flags |= PURPLE_CONNECTION_HTML |
+		PURPLE_CONNECTION_ALLOW_CUSTOM_SMILEY;
+	js = jabber_stream_new(account);
+	if (js == NULL)
+		return;
+
+	/*
+	 * Calculate the avatar hash for our current image so we know (when we
+	 * fetch our vCard and PEP avatar) if we should send our avatar to the
+	 * server.
+	 */
+	image = purple_buddy_icons_find_account_icon(account);
+	if (image != NULL) {
+		js->initial_avatar_hash =
+			jabber_calculate_data_sha1sum(purple_imgstore_get_data(image),
+					purple_imgstore_get_size(image));
+		purple_imgstore_unref(image);
+	}
+
+	jabber_stream_connect(js);
+}
+
 
 static gboolean
 conn_close_cb(gpointer data)
@@ -1293,90 +1328,14 @@
 
 void jabber_register_account(PurpleAccount *account)
 {
-	PurpleConnection *gc = purple_account_get_connection(account);
 	JabberStream *js;
-	JabberBuddy *my_jb = NULL;
-	const char *connect_server = purple_account_get_string(account,
-			"connect_server", "");
-	const char *server;
-
-	js = gc->proto_data = g_new0(JabberStream, 1);
-	js->gc = gc;
-	js->registration = TRUE;
-	js->iq_callbacks = g_hash_table_new_full(g_str_hash, g_str_equal,
-			g_free, g_free);
-	js->user = jabber_id_new(purple_account_get_username(account));
-	js->next_id = g_random_int();
-	js->old_length = 0;
-	js->keepalive_timeout = 0;
-
-	if(!js->user) {
-		purple_connection_error_reason(gc,
-			PURPLE_CONNECTION_ERROR_INVALID_SETTINGS,
-			_("Invalid XMPP ID"));
+
+	js = jabber_stream_new(account);
+	if (js == NULL)
 		return;
-	}
-
-	js->write_buffer = purple_circ_buffer_new(512);
-
-	if((my_jb = jabber_buddy_find(js, purple_account_get_username(account), TRUE)))
-		my_jb->subscription |= JABBER_SUB_BOTH;
-
-	server = connect_server[0] ? connect_server : js->user->domain;
-	js->certificate_CN = g_strdup(server);
-
-	js->stun_ip = NULL;
-	js->stun_port = 0;
-	js->stun_query = NULL;
-
-	jabber_stream_set_state(js, JABBER_STREAM_CONNECTING);
-
-	/* TODO: Just use purple_url_parse? */
-	if (!g_ascii_strncasecmp(connect_server, "http://", 7) || !g_ascii_strncasecmp(connect_server, "https://", 8)) {
-		js->use_bosh = TRUE;
-		js->bosh = jabber_bosh_connection_init(js, connect_server);
-		if (!js->bosh) {
-			purple_connection_error_reason(js->gc,
-				PURPLE_CONNECTION_ERROR_INVALID_SETTINGS,
-				_("Malformed BOSH Connect Server"));
-			return;
-		}
-		jabber_bosh_connection_connect(js->bosh);
-		return;
-	} else {
-		js->certificate_CN = g_strdup(connect_server[0] ? connect_server : js->user->domain);
-	}
-
-	if(purple_account_get_bool(account, "old_ssl", FALSE)) {
-		if(purple_ssl_is_supported()) {
-			js->gsc = purple_ssl_connect(account, server,
-					purple_account_get_int(account, "port", 5222),
-					jabber_login_callback_ssl, jabber_ssl_connect_failure, gc);
-			if (!js->gsc) {
-				purple_connection_error_reason(js->gc,
-					PURPLE_CONNECTION_ERROR_NO_SSL_SUPPORT,
-					_("Unable to establish SSL connection"));
-			}
-		} else {
-			purple_connection_error_reason(gc,
-				PURPLE_CONNECTION_ERROR_NO_SSL_SUPPORT,
-				_("SSL support unavailable"));
-		}
-
-		return;
-	}
-
-	if (connect_server[0]) {
-		jabber_login_connect(js, js->user->domain, server,
-		                     purple_account_get_int(account,
-		                                            "port", 5222), TRUE);
-	} else {
-		js->srv_query_data = purple_srv_resolve("xmpp-client",
-		                                        "tcp",
-		                                        js->user->domain,
-		                                        srv_resolved_cb,
-		                                        js);
-	}
+
+	js->registration = TRUE;
+	jabber_stream_connect(js);
 }
 
 static void
--- a/libpurple/protocols/jabber/jutil.c	Fri Jul 17 23:45:52 2009 +0000
+++ b/libpurple/protocols/jabber/jutil.c	Sat Jul 18 00:40:40 2009 +0000
@@ -31,9 +31,165 @@
 #include "presence.h"
 #include "jutil.h"
 
+#ifdef USE_IDN
+#include <idna.h>
+#include <stringprep.h>
+static char idn_buffer[1024];
+#endif
+
+gchar *jabber_try_idna_to_ascii(const char *input)
+{
+#ifndef USE_IDN
+	return g_strdup(input);
+#else
+	gchar *out;
+	char *tmp;
+
+	g_return_val_if_fail(input != NULL, NULL);
+	g_return_val_if_fail(*input != '\0', NULL);
+
+	if (idna_to_ascii_8z(input, &tmp, IDNA_USE_STD3_ASCII_RULES) != IDNA_SUCCESS) {
+		return NULL;
+	}
+
+	out = g_strdup(tmp);
+	/* This *MUST* be freed with free, not g_free */
+	free(tmp);
+	return out;
+#endif
+}
+
+#ifdef USE_IDN
+static gboolean jabber_nodeprep(char *str, size_t buflen)
+{
+	return stringprep_xmpp_nodeprep(str, buflen) == STRINGPREP_OK;
+}
+
+static gboolean jabber_resourceprep(char *str, size_t buflen)
+{
+	return stringprep_xmpp_resourceprep(str, buflen) == STRINGPREP_OK;
+}
+
+static JabberID*
+jabber_idn_validate(const char *str, const char *at, const char *slash,
+                    const char *null)
+{
+	const char *node = NULL;
+	const char *domain = NULL;
+	const char *resource = NULL;
+	int node_len = 0;
+	int domain_len = 0;
+	int resource_len = 0;
+	char *out;
+	JabberID *jid;
+
+	/* Ensure no parts are > 1023 bytes */
+	if (at) {
+		node = str;
+		node_len = at - str;
+
+		domain = at + 1;
+		if (slash) {
+			domain_len = slash - (at + 1);
+			resource = slash + 1;
+			resource_len = null - (slash + 1);
+		} else {
+			domain_len = null - (at + 1);
+		}
+	} else {
+		domain = str;
+
+		if (slash) {
+			domain_len = slash - str;
+			resource = slash;
+			resource_len = null - (slash + 1);
+		} else {
+			domain_len = null - (str + 1);
+		}
+	}
+
+	if (node && node_len > 1023)
+		return NULL;
+	if (domain_len > 1023)
+		return NULL;
+	if (resource && resource_len > 1023)
+		return NULL;
+
+	jid = g_new0(JabberID, 1);
+
+	if (node) {
+		strncpy(idn_buffer, node, node_len);
+		idn_buffer[node_len] = '\0';
+
+		if (!jabber_nodeprep(idn_buffer, sizeof(idn_buffer))) {
+			jabber_id_free(jid);
+			jid = NULL;
+			goto out;
+		}
+
+		jid->node = g_strdup(idn_buffer);
+	}
+
+	/* domain *must* be here */
+	strncpy(idn_buffer, domain, domain_len);
+	idn_buffer[domain_len] = '\0';
+	if (domain[0] == '[') { /* IPv6 address */
+		gboolean valid = FALSE;
+
+		if (idn_buffer[domain_len - 1] == ']') {
+			idn_buffer[domain_len - 1] = '\0';
+			valid = purple_ipv6_address_is_valid(idn_buffer + 1);
+		}
+
+		if (!valid) {
+			jabber_id_free(jid);
+			jid = NULL;
+			goto out;
+		}
+	} else {
+		/* Apply nameprep */
+		if (stringprep_nameprep(idn_buffer, sizeof(idn_buffer)) != STRINGPREP_OK) {
+			jabber_id_free(jid);
+			jid = NULL;
+			goto out;
+		}
+
+		/* And now ToASCII */
+		if (idna_to_ascii_8z(idn_buffer, &out, IDNA_USE_STD3_ASCII_RULES) != IDNA_SUCCESS) {
+			jabber_id_free(jid);
+			jid = NULL;
+			goto out;
+		}
+
+		/* This *MUST* be freed using 'free', not 'g_free' */
+		free(out);
+		jid->domain = g_strdup(idn_buffer);
+	}
+
+	if (resource) {
+		strncpy(idn_buffer, resource, resource_len);
+		idn_buffer[resource_len] = '\0';
+
+		if (!jabber_resourceprep(idn_buffer, sizeof(idn_buffer))) {
+			jabber_id_free(jid);
+			jid = NULL;
+			/* goto out; */
+		}
+	}
+
+out:
+	return jid;
+}
+
+#endif /* USE_IDN */
+
 gboolean jabber_nodeprep_validate(const char *str)
 {
+#ifdef USE_IDN
+	gboolean result;
+#else
 	const char *c;
+#endif
 
 	if(!str)
 		return TRUE;
@@ -41,6 +197,12 @@
 	if(strlen(str) > 1023)
 		return FALSE;
 
+#ifdef USE_IDN
+	strncpy(idn_buffer, str, sizeof(idn_buffer) - 1);
+	idn_buffer[sizeof(idn_buffer) - 1] = '\0';
+	result = jabber_nodeprep(idn_buffer, sizeof(idn_buffer));
+	return result;
+#else /* USE_IDN */
 	c = str;
 	while(c && *c) {
 		gunichar ch = g_utf8_get_char(c);
@@ -52,6 +214,7 @@
 	}
 
 	return TRUE;
+#endif /* USE_IDN */
 }
 
 gboolean jabber_domain_validate(const char *str)
@@ -101,7 +264,11 @@
 
 gboolean jabber_resourceprep_validate(const char *str)
 {
+#ifdef USE_IDN
+	gboolean result;
+#else
 	const char *c;
+#endif
 
 	if(!str)
 		return TRUE;
@@ -109,6 +276,12 @@
 	if(strlen(str) > 1023)
 		return FALSE;
 
+#ifdef USE_IDN
+	strncpy(idn_buffer, str, sizeof(idn_buffer) - 1);
+	idn_buffer[sizeof(idn_buffer) - 1] = '\0';
+	result = jabber_resourceprep(idn_buffer, sizeof(idn_buffer));
+	return result;
+#else /* USE_IDN */
 	c = str;
 	while(c && *c) {
 		gunichar ch = g_utf8_get_char(c);
@@ -119,9 +292,9 @@
 	}
 
 	return TRUE;
+#endif /* USE_IDN */
 }
 
-
 JabberID*
 jabber_id_new(const char *str)
 {
@@ -132,8 +305,10 @@
 #if 0
 	gboolean node_is_required = FALSE;
 #endif
+#ifndef USE_IDN
 	char *node = NULL;
 	char *domain;
+#endif
 	JabberID *jid;
 
 	if (!str)
@@ -253,23 +428,27 @@
 	if (!g_utf8_validate(str, -1, NULL))
 		return NULL;
 
+#ifdef USE_IDN
+	return jabber_idn_validate(str, at, slash, c /* points to the null */);
+#else /* USE_IDN */
+
 	jid = g_new0(JabberID, 1);
 
 	/* normalization */
 	if(at) {
-		node = g_utf8_strdown(str, at-str);
+		node = g_utf8_casefold(str, at-str);
 		if(slash) {
-			domain = g_utf8_strdown(at+1, slash-(at+1));
+			domain = g_utf8_casefold(at+1, slash-(at+1));
 			jid->resource = g_utf8_normalize(slash+1, -1, G_NORMALIZE_NFKC);
 		} else {
-			domain = g_utf8_strdown(at+1, -1);
+			domain = g_utf8_casefold(at+1, -1);
 		}
 	} else {
 		if(slash) {
-			domain = g_utf8_strdown(str, slash-str);
+			domain = g_utf8_casefold(str, slash-str);
 			jid->resource = g_utf8_normalize(slash+1, -1, G_NORMALIZE_NFKC);
 		} else {
-			domain = g_utf8_strdown(str, -1);
+			domain = g_utf8_casefold(str, -1);
 		}
 	}
 
@@ -292,6 +471,7 @@
 	}
 
 	return jid;
+#endif /* USE_IDN */
 }
 
 void
--- a/libpurple/protocols/jabber/jutil.h	Fri Jul 17 23:45:52 2009 +0000
+++ b/libpurple/protocols/jabber/jutil.h	Sat Jul 18 00:40:40 2009 +0000
@@ -44,6 +44,11 @@
 /* Returns true if JID is the bare JID of our account. */
 gboolean jabber_is_own_account(JabberStream *js, const char *jid);
 
+/* Try to convert an IDNA domain name to something we can pass to a DNS lookup.
+ * If IDN support is not available, returns a copy of the input string.
+ */
+gchar *jabber_try_idna_to_ascii(const gchar *input);
+
 gboolean jabber_nodeprep_validate(const char *);
 gboolean jabber_domain_validate(const char *);
 gboolean jabber_resourceprep_validate(const char *);
--- a/libpurple/tests/test_jabber_jutil.c	Fri Jul 17 23:45:52 2009 +0000
+++ b/libpurple/tests/test_jabber_jutil.c	Sat Jul 18 00:40:40 2009 +0000
@@ -44,6 +44,10 @@
 	longnode = g_strnfill(1023, 'a');
 	fail_unless(jabber_nodeprep_validate(longnode));
 	g_free(longnode);
+
+	longnode = g_strnfill(1024, 'a');
+	fail_if(jabber_nodeprep_validate(longnode));
+	g_free(longnode);
 }
 END_TEST
 
@@ -132,7 +136,19 @@
 	/* 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");
+
+	/* These case-mapping tests culled from examining RFC3454 B.2 */
+
+	/* Cyrillic capital EF (U+0424) maps to lowercase EF (U+0444) */
+	assert_jid_parts("ф", "darkrain42.org", "Ф@darkrain42.org");
+	/*
+	 * These character (U+A664 and U+A665) are not mapped to anything in
+	 * RFC3454 B.2. This first test *fails* when not using IDN because glib's
+	 * case-folding/utf8_strdown improperly lowercases the character.
+	 */
+	assert_jid_parts("Ꙥ", "darkrain42.org", "Ꙥ@darkrain42.org");
+	assert_jid_parts("ꙥ", "darkrain42.org", "ꙥ@darkrain42.org");
+	/* U+04E9 to U+04E9 */
 	assert_jid_parts("paul", "өarkrain42.org", "paul@Өarkrain42.org");
 }
 END_TEST