Mercurial > pidgin
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