changeset 27467:2dff07ddcc83

merge of '90da8da01d8b8fa432adae5fd697ef4bad3582b5' and 'f5644cc4891cdcc46fafcec5127d00e48aa48e22'
author Elliott Sales de Andrade <qulogic@pidgin.im>
date Sat, 11 Jul 2009 07:02:33 +0000
parents 302934aea9fb (diff) ff18653ef9f4 (current diff)
children ef48fb87d8d2
files
diffstat 26 files changed, 469 insertions(+), 87 deletions(-) [+]
line wrap: on
line diff
--- a/finch/gntconv.c	Sat Jul 11 07:01:00 2009 +0000
+++ b/finch/gntconv.c	Sat Jul 11 07:02:33 2009 +0000
@@ -191,10 +191,7 @@
 	}
 	else
 	{
-		char *escape = g_markup_escape_text((*text == '/' ? text + 1 : text), -1);
-		char *apos = purple_strreplace(escape, "&apos;", "'");
-		g_free(escape);
-		escape = apos;
+		char *escape = purple_markup_escape_text((*text == '/' ? text + 1 : text), -1);
 		switch (purple_conversation_get_type(ggconv->active_conv))
 		{
 			case PURPLE_CONV_TYPE_IM:
--- a/libpurple/internal.h	Sat Jul 11 07:01:00 2009 +0000
+++ b/libpurple/internal.h	Sat Jul 11 07:02:33 2009 +0000
@@ -230,6 +230,16 @@
 #	endif
 #endif
 
+#ifdef HAVE_CONFIG_H
+#if SIZEOF_TIME_T == 4
+#	define PURPLE_TIME_T_MODIFIER "lu"
+#elif SIZEOF_TIME_T == 8
+#	define PURPLE_TIME_T_MODIFIER "zu"
+#else
+#error Unknown size of time_t
+#endif
+#endif
+
 #include <glib-object.h>
 
 #ifndef G_DEFINE_TYPE
--- a/libpurple/protocols/jabber/buddy.c	Sat Jul 11 07:01:00 2009 +0000
+++ b/libpurple/protocols/jabber/buddy.c	Sat Jul 11 07:02:33 2009 +0000
@@ -2226,7 +2226,7 @@
 	const JabberCapsNodeExts *exts;
 
 	if (!jbr->caps.info) {
-		purple_debug_error("jabber",
+		purple_debug_info("jabber",
 			"Unable to find caps: nothing known about buddy\n");
 		return FALSE;
 	}
--- a/libpurple/protocols/jabber/google.c	Sat Jul 11 07:01:00 2009 +0000
+++ b/libpurple/protocols/jabber/google.c	Sat Jul 11 07:02:33 2009 +0000
@@ -1062,7 +1062,7 @@
 
 	js = (JabberStream*)(gc->proto_data);
 
-	if (!js || !js->server_caps & JABBER_CAP_GOOGLE_ROSTER)
+	if (!js || !(js->server_caps & JABBER_CAP_GOOGLE_ROSTER))
 		return;
 
 	jb = jabber_buddy_find(js, who, TRUE);
@@ -1132,7 +1132,7 @@
 
 	js = (JabberStream*)(gc->proto_data);
 
-	if (!js || !js->server_caps & JABBER_CAP_GOOGLE_ROSTER)
+	if (!js || !(js->server_caps & JABBER_CAP_GOOGLE_ROSTER))
 		return;
 
 	buddies = purple_find_buddies(purple_connection_get_account(js->gc), who);
--- a/libpurple/protocols/jabber/jabber.c	Sat Jul 11 07:01:00 2009 +0000
+++ b/libpurple/protocols/jabber/jabber.c	Sat Jul 11 07:02:33 2009 +0000
@@ -3371,6 +3371,7 @@
 					  PURPLE_CMD_FLAG_ALLOW_WRONG_ARGS,
 					  "prpl-jabber", jabber_cmd_buzz,
 					  _("buzz: Buzz a user to get their attention"), NULL);
+	jabber_cmds = g_slist_prepend(jabber_cmds, GUINT_TO_POINTER(id));
 }
 
 void jabber_unregister_commands(void)
--- a/libpurple/protocols/jabber/jutil.c	Sat Jul 11 07:01:00 2009 +0000
+++ b/libpurple/protocols/jabber/jutil.c	Sat Jul 11 07:02:33 2009 +0000
@@ -64,16 +64,24 @@
 	if(strlen(str) > 1023)
 		return FALSE;
 
+	/*
+	 * TODO: An IPv6 address of the form [2001:470:1f05:d58::2] is also
+	 * a valid XMPP domain portion.
+	 */
 	c = str;
 	while(c && *c) {
 		gunichar ch = g_utf8_get_char(c);
-		if(!g_unichar_isgraph(ch))
+		/* The list of characters allowed in domain names is pretty small */
+		if ((ch <= 0x7F && !( (ch >= 'a' && ch <= 'z')
+				|| (ch >= '0' && ch <= '9')
+				|| (ch >= 'A' && ch <= 'Z')
+				|| ch == '.'
+				|| ch == '-' )) || (ch >= 0x80 && !g_unichar_isgraph(ch)))
 			return FALSE;
 
 		c = g_utf8_next_char(c);
 	}
 
-
 	return TRUE;
 }
 
@@ -103,20 +111,137 @@
 JabberID*
 jabber_id_new(const char *str)
 {
-	char *at;
-	char *slash;
+	const char *at = NULL;
+	const char *slash = NULL;
+	const char *c;
+	gboolean needs_validation = FALSE;
+#if 0
+	gboolean node_is_required = FALSE;
+#endif
 	char *node = NULL;
 	char *domain;
 	JabberID *jid;
 
-	if(!str || !g_utf8_validate(str, -1, NULL))
+	if (!str)
+		return NULL;
+
+	for (c = str; *c != '\0'; c++)
+	{
+		switch (*c) {
+			case '@':
+				if (!slash) {
+					if (at) {
+						/* Multiple @'s in the node/domain portion, not a valid JID! */
+						return NULL;
+					}
+					if (c == str) {
+						/* JIDs cannot start with @ */
+						return NULL;
+					}
+					if (c[1] == '\0') {
+						/* JIDs cannot end with @ */
+						return NULL;
+					}
+					at = c;
+				}
+				break;
+
+			case '/':
+				if (!slash) {
+					if (c == str) {
+						/* JIDs cannot start with / */
+						return NULL;
+					}
+					if (c[1] == '\0') {
+						/* JIDs cannot end with / */
+						return NULL;
+					}
+					slash = c;
+				}
+				break;
+
+			default:
+				/* characters allowed everywhere */
+				if ((*c >= 'a' && *c <= 'z')
+						|| (*c >= '0' && *c <= '9')
+						|| (*c >= 'A' && *c <= 'Z')
+						|| *c == '.' || *c == '-')
+					/* We're good */
+					break;
+
+#if 0
+				if (slash != NULL) {
+					/* characters allowed only in the resource */
+					if (implement_me)
+						/* We're good */
+						break;
+				}
+
+				/* characters allowed only in the node */
+				if (implement_me) {
+					/*
+					 * Ok, this character is valid, but only if it's a part
+					 * of the node and not the domain.  But we don't know
+					 * if "c" is a part of the node or the domain until after
+					 * we've found the @.  So set a flag for now and check
+					 * that we found an @ later.
+					 */
+					node_is_required = TRUE;
+					break;
+				}
+#endif
+
+				/*
+				 * Hmm, this character is a bit more exotic.  Better fall
+				 * back to using the more expensive UTF-8 compliant
+				 * stringprep functions.
+				 */
+				needs_validation = TRUE;
+				break;
+		}
+	}
+
+#if 0
+	if (node_is_required && at == NULL)
+		/* Found invalid characters in the domain */
+		return NULL;
+#endif
+
+	if (!needs_validation) {
+		/* JID is made of only ASCII characters--just lowercase and return */
+		jid = g_new0(JabberID, 1);
+
+		if (at) {
+			jid->node = g_ascii_strdown(str, at - str);
+			if (slash) {
+				jid->domain = g_ascii_strdown(at + 1, slash - (at + 1));
+				jid->resource = g_strdup(slash + 1);
+			} else {
+				jid->domain = g_ascii_strdown(at + 1, -1);
+			}
+		} else {
+			if (slash) {
+				jid->domain = g_ascii_strdown(str, slash - str);
+				jid->resource = g_strdup(slash + 1);
+			} else {
+				jid->domain = g_ascii_strdown(str, -1);
+			}
+		}
+		return jid;
+	}
+
+	/*
+	 * If we get here, there are some non-ASCII chars in the string, so
+	 * we'll need to validate it, normalize, and finally do a full jabber
+	 * nodeprep on the jid.
+	 */
+
+	if (!g_utf8_validate(str, -1, NULL))
 		return NULL;
 
 	jid = g_new0(JabberID, 1);
 
-	at = g_utf8_strchr(str, -1, '@');
-	slash = g_utf8_strchr(str, -1, '/');
-
+	/* normalization */
 	if(at) {
 		node = g_utf8_normalize(str, at-str, G_NORMALIZE_NFKC);
 		if(slash) {
@@ -144,6 +269,7 @@
 		g_free(domain);
 	}
 
+	/* and finally the jabber nodeprep */
 	if(!jabber_nodeprep_validate(jid->node) ||
 			!jabber_nameprep_validate(jid->domain) ||
 			!jabber_resourceprep_validate(jid->resource)) {
--- a/libpurple/protocols/jabber/jutil.h	Sat Jul 11 07:01:00 2009 +0000
+++ b/libpurple/protocols/jabber/jutil.h	Sat Jul 11 07:02:33 2009 +0000
@@ -45,6 +45,7 @@
 gboolean jabber_is_own_account(JabberStream *js, const char *jid);
 
 gboolean jabber_nodeprep_validate(const char *);
+/* TODO: This needs to be named jabber_domain_validate and handle IPv6/IDNA. */
 gboolean jabber_nameprep_validate(const char *);
 gboolean jabber_resourceprep_validate(const char *);
 
--- a/libpurple/protocols/jabber/presence.c	Sat Jul 11 07:01:00 2009 +0000
+++ b/libpurple/protocols/jabber/presence.c	Sat Jul 11 07:02:33 2009 +0000
@@ -762,6 +762,7 @@
 				purple_conv_chat_user_set_flags(PURPLE_CONV_CHAT(chat->conv), jid->resource,
 						flags);
 		} else if (g_str_equal(type, "unavailable")) {
+			xmlnode *x;
 			gboolean nick_change = FALSE;
 			gboolean kick = FALSE;
 			gboolean is_our_resource = FALSE; /* Is the presence about us? */
@@ -784,28 +785,31 @@
 			is_our_resource = (0 == g_utf8_collate(jid->resource, chat->handle));
 
 			jabber_buddy_remove_resource(jb, jid->resource);
-			if(chat->muc) {
-				xmlnode *x;
-				for(x = xmlnode_get_child(packet, "x"); x; x = xmlnode_get_next_twin(x)) {
-					const char *xmlns, *nick, *code;
-					xmlnode *stat, *item;
-					if(!(xmlns = xmlnode_get_namespace(x)) ||
-							strcmp(xmlns, "http://jabber.org/protocol/muc#user"))
-						continue;
-					if(!(stat = xmlnode_get_child(x, "status")))
-						continue;
-					if(!(code = xmlnode_get_attrib(stat, "code")))
-						continue;
+
+			x = xmlnode_get_child_with_namespace(packet, "x",
+					"http://jabber.org/protocol/muc#user");
+			if (chat->muc && x) {
+				const char *nick;
+				const char *code = NULL;
+				const char *item_jid = NULL;
+				xmlnode *stat;
+				xmlnode *item;
 
-					item = xmlnode_get_child(x, "item");
+				item = xmlnode_get_child(x, "item");
+				if (item)
+					item_jid = xmlnode_get_attrib(item, "jid");
+
 
+				stat = xmlnode_get_child(x, "status");
+
+				if (stat)
+					code = xmlnode_get_attrib(stat, "code");
+
+				if (code) {
 					if(!strcmp(code, "301")) {
 						/* XXX: we got banned */
-					} else if(!strcmp(code, "303")) {
-						if (!item)
-							continue;
-						if(!(nick = xmlnode_get_attrib(item, "nick")))
-							continue;
+					} else if(!strcmp(code, "303") && item &&
+							(nick = xmlnode_get_attrib(item, "nick"))) {
 						nick_change = TRUE;
 						if(!strcmp(jid->resource, chat->handle)) {
 							g_free(chat->handle);
@@ -813,7 +817,8 @@
 						}
 						purple_conv_chat_rename_user(PURPLE_CONV_CHAT(chat->conv), jid->resource, nick);
 						jabber_chat_remove_handle(chat, jid->resource);
-						break;
+						/* TODO: Enable this when this is in a for-loop...
+						break; */
 					} else if(!strcmp(code, "307")) {
 						/* Someone was kicked from the room */
 						xmlnode *reason = NULL, *actor = NULL;
@@ -863,6 +868,21 @@
 						/* XXX: removed due to system shutdown */
 					}
 				}
+
+				/*
+				 * Possibly another connected resource of our JID (see XEP-0045
+				 * v1.24 section 7.1.10) being disconnected. Should be
+				 * distinguished by the item_jid.
+				 * Also possibly works around bits of an Openfire bug. See
+				 * #8319.
+				 */
+				if (is_our_resource && !purple_strequal(from, item_jid)) {
+					/* TODO: When the above is a loop, this needs to still act
+					 * sanely for all cases (this code is a little fragile). */
+					if (!kick && !nick_change)
+						/* Presumably, kicks and nick changes also affect us. */
+						is_our_resource = FALSE;
+				}
 			}
 			if(!nick_change) {
 				if (is_our_resource) {
--- a/libpurple/protocols/msnp9/command.c	Sat Jul 11 07:01:00 2009 +0000
+++ b/libpurple/protocols/msnp9/command.c	Sat Jul 11 07:02:33 2009 +0000
@@ -59,7 +59,6 @@
 
 	if (cmd->params != NULL)
 	{
-		char *param;
 		int c;
 
 		for (c = 0; cmd->params[c]; c++);
--- a/libpurple/protocols/oscar/clientlogin.c	Sat Jul 11 07:01:00 2009 +0000
+++ b/libpurple/protocols/oscar/clientlogin.c	Sat Jul 11 07:02:33 2009 +0000
@@ -288,7 +288,7 @@
 	query_string = g_strdup_printf("a=%s"
 			"&f=xml"
 			"&k=%s"
-			"&ts=%zu"
+			"&ts=%" PURPLE_TIME_T_MODIFIER
 			"&useTLS=0",
 			oscar_auth_url_encode(token), get_client_key(od), hosttime);
 	signature = generate_signature("GET", URL_START_OSCAR_SESSION,
--- a/libpurple/protocols/qq/buddy_opt.c	Sat Jul 11 07:01:00 2009 +0000
+++ b/libpurple/protocols/qq/buddy_opt.c	Sat Jul 11 07:02:33 2009 +0000
@@ -1012,7 +1012,6 @@
 	guint16 flag1, flag2;
 
 	g_return_if_fail(data != NULL && data_len >= 5);
-	g_return_if_fail(uid != 0);
 
 	qd = (qq_data *) gc->proto_data;
 
--- a/libpurple/protocols/yahoo/libymsg.c	Sat Jul 11 07:01:00 2009 +0000
+++ b/libpurple/protocols/yahoo/libymsg.c	Sat Jul 11 07:02:33 2009 +0000
@@ -1163,7 +1163,10 @@
 	struct yahoo_data *yd = add_req->gc->proto_data;
 	struct yahoo_packet *pkt;
 	char *encoded_msg = NULL;
-	PurpleAccount *account = purple_connection_get_account(add_req->gc);
+	const char *who = add_req->who;
+
+	if (add_req->protocol == 2)
+		who += 4; /* Skip 'msn/' */
 
 	if (msg && *msg)
 		encoded_msg = yahoo_string_encode(add_req->gc, msg, NULL);
@@ -1171,9 +1174,10 @@
 	pkt = yahoo_packet_new(YAHOO_SERVICE_AUTH_REQ_15,
 			YAHOO_STATUS_AVAILABLE, 0);
 
-	yahoo_packet_hash(pkt, "ssiiis",
-			1, purple_normalize(account, purple_account_get_username(account)),
-			5, add_req->who,
+	yahoo_packet_hash(pkt, "ssiiiis",
+			1, add_req->id,
+			5, who,
+			241, add_req->protocol,
 			13, 2,
 			334, 0,
 			97, 1,
@@ -1291,7 +1295,6 @@
 			switch (pair->key) {
 			case 4:
 				temp = pair->value;
-				add_req->who = g_strdup(pair->value);
 				break;
 			case 5:
 				add_req->id = g_strdup(pair->value);
@@ -3535,6 +3538,8 @@
 	g_free(yd->pending_chat_goto);
 	g_strfreev(yd->profiles);
 
+	yahoo_personal_details_reset(&yd->ypd);
+
 	g_free(yd->current_list15_grp);
 
 	g_free(yd);
@@ -4009,9 +4014,13 @@
 				   "Unable to request mail login token; forwarding to login screen.");
 		purple_notify_uri(gc, yahoo_mail_url);
 	}
-
 }
 
+static void
+yahoo_set_userinfo_fn(PurplePluginAction *action)
+{
+	yahoo_set_userinfo(action->context);
+}
 
 static void yahoo_show_act_id(PurplePluginAction *action)
 {
@@ -4058,6 +4067,10 @@
 	GList *m = NULL;
 	PurplePluginAction *act;
 
+	act = purple_plugin_action_new(_("Set User Info..."),
+			yahoo_set_userinfo_fn);
+	m = g_list_append(m, act);
+
 	act = purple_plugin_action_new(_("Activate ID..."),
 			yahoo_show_act_id);
 	m = g_list_append(m, act);
--- a/libpurple/protocols/yahoo/libymsg.h	Sat Jul 11 07:01:00 2009 +0000
+++ b/libpurple/protocols/yahoo/libymsg.h	Sat Jul 11 07:02:33 2009 +0000
@@ -65,6 +65,9 @@
 
 #define YAHOO_SMS_CARRIER_URL "http://lookup.msg.vip.mud.yahoo.com"
 
+#define YAHOO_USERINFO_URL "http://address.yahoo.com/yab/us?v=XM&sync=1&tags=short&useutf8=1&noclear=1&legenc=codepage-1252"
+#define YAHOOJP_USERINFO_URL "http://address.yahoo.co.jp/yab/jp?v=XM&sync=1&tags=short&useutf8=1&noclear=1&legenc=codepage-1252"
+
 #define YAHOO_PICURL_SETTING "picture_url"
 #define YAHOO_PICCKSUM_SETTING "picture_checksum"
 #define YAHOO_PICEXPIRE_SETTING "picture_expire"
@@ -147,6 +150,23 @@
 
 struct _YchtConn;
 
+typedef struct _YahooPersonalDetails {
+	char *id;
+
+	struct {
+		char *first;
+		char *last;
+		char *middle;
+		char *nick;
+	} names;
+
+	struct {
+		char *work;
+		char *home;
+		char *mobile;
+	} phone;
+} YahooPersonalDetails;
+
 struct yahoo_data {
 	PurpleConnection *gc;
 	int fd;
@@ -157,6 +177,7 @@
 	GHashTable *friends;
 
 	char **profiles;  /* Multiple profiles can be associated with an account */
+	YahooPersonalDetails ypd;
 
 	/**
 	 * This is used to keep track of the IMVironment chosen
--- a/libpurple/protocols/yahoo/yahoo_aliases.c	Sat Jul 11 07:01:00 2009 +0000
+++ b/libpurple/protocols/yahoo/yahoo_aliases.c	Sat Jul 11 07:02:33 2009 +0000
@@ -29,6 +29,7 @@
 #include "blist.h"
 #include "debug.h"
 #include "util.h"
+#include "request.h"
 #include "version.h"
 #include "libymsg.h"
 #include "yahoo_aliases.h"
@@ -52,6 +53,17 @@
 	gchar *who;
 };
 
+void yahoo_personal_details_reset(YahooPersonalDetails *ypd)
+{
+	g_free(ypd->id);
+	g_free(ypd->names.first);
+	g_free(ypd->names.last);
+	g_free(ypd->names.middle);
+	g_free(ypd->names.nick);
+	g_free(ypd->phone.work);
+	g_free(ypd->phone.home);
+	g_free(ypd->phone.mobile);
+}
 
 /**************************************************************************
  * Alias Fetch Functions
@@ -60,8 +72,7 @@
 static void
 yahoo_fetch_aliases_cb(PurpleUtilFetchUrlData *url_data, gpointer user_data, const gchar *url_text, size_t len, const gchar *error_message)
 {
-	struct callback_data *cb = user_data;
-	PurpleConnection *gc = cb->gc;
+	PurpleConnection *gc = user_data;
 	struct yahoo_data *yd = gc->proto_data;
 
 	yd->url_datas = g_slist_remove(yd->url_datas, url_data);
@@ -71,19 +82,19 @@
 						  error_message ? " Error:" : "", error_message ? error_message : "");
 	} else {
 		gchar *full_name, *nick_name;
-		const char *yid, *id, *fn, *ln, *nn, *alias;
+		const char *yid, *id, *fn, *ln, *nn, *alias, *mn;
+		const char *hp, *wp, *mo;
 		YahooFriend *f;
 		PurpleBuddy *b;
 		xmlnode *item, *contacts;
+		PurpleAccount *account;
 
+		account = purple_connection_get_account(gc);
 		/* Put our web response into a xmlnode for easy management */
 		contacts = xmlnode_from_str(url_text, -1);
 
 		if (contacts == NULL) {
 			purple_debug_error("yahoo", "Badly formed Alias XML\n");
-			g_free(cb->who);
-			g_free(cb->id);
-			g_free(cb);
 			return;
 		}
 		purple_debug_info("yahoo", "Fetched %" G_GSIZE_FORMAT
@@ -97,8 +108,13 @@
 				fn = xmlnode_get_attrib(item, "fn");
 				ln = xmlnode_get_attrib(item, "ln");
 				nn = xmlnode_get_attrib(item, "nn");
+				mn = xmlnode_get_attrib(item, "mn");
 				id = xmlnode_get_attrib(item, "id");
 
+				hp = xmlnode_get_attrib(item, "hp");
+				wp = xmlnode_get_attrib(item, "wp");
+				mo = xmlnode_get_attrib(item, "mo");
+
 				full_name = nick_name = NULL;
 				alias = NULL;
 
@@ -115,8 +131,8 @@
 					alias = full_name;  /* If no Yahoo nickname, we can use the full_name created above */
 
 				/*  Find the local buddy that matches */
-				f = yahoo_friend_find(cb->gc, yid);
-				b = purple_find_buddy(cb->gc->account, yid);
+				f = yahoo_friend_find(gc, yid);
+				b = purple_find_buddy(account, yid);
 
 				/*  If we don't find a matching buddy, ignore the alias !!  */
 				if (f != NULL && b != NULL) {
@@ -125,13 +141,30 @@
 
 					/* Finally, if we received an alias, we better update the buddy list */
 					if (alias != NULL) {
-						serv_got_alias(cb->gc, yid, alias);
+						serv_got_alias(gc, yid, alias);
 						purple_debug_info("yahoo", "Fetched alias '%s' (%s)\n", alias, id);
 					} else if (buddy_alias != NULL && strcmp(buddy_alias, "") != 0) {
 					/* Or if we have an alias that Yahoo doesn't, send it up */
-						yahoo_update_alias(cb->gc, yid, buddy_alias);
+						yahoo_update_alias(gc, yid, buddy_alias);
 						purple_debug_info("yahoo", "Sent updated alias '%s'\n", buddy_alias);
 					}
+				} else {
+					/* May be the alias is for the account? */
+					const char *yidn = purple_normalize(account, yid);
+					if (purple_strequal(yidn, purple_connection_get_display_name(gc))) {
+						yahoo_personal_details_reset(&yd->ypd);
+
+						yd->ypd.id = g_strdup(id);
+
+						yd->ypd.names.first = g_strdup(fn);
+						yd->ypd.names.middle = g_strdup(mn);
+						yd->ypd.names.last = g_strdup(ln);
+						yd->ypd.names.nick = g_strdup(nn);
+
+						yd->ypd.phone.work = g_strdup(wp);
+						yd->ypd.phone.home = g_strdup(hp);
+						yd->ypd.phone.mobile = g_strdup(mo);
+					}
 				}
 
 				g_free(full_name);
@@ -140,17 +173,12 @@
 		}
 		xmlnode_free(contacts);
 	}
-
-	g_free(cb->who);
-	g_free(cb->id);
-	g_free(cb);
 }
 
 void
 yahoo_fetch_aliases(PurpleConnection *gc)
 {
 	struct yahoo_data *yd = gc->proto_data;
-	struct callback_data *cb;
 	const char *url;
 	gchar *request, *webpage, *webaddress;
 	PurpleUtilFetchUrlData *url_data;
@@ -158,10 +186,6 @@
 	/* use whole URL if using HTTP Proxy */
 	gboolean use_whole_url = yahoo_account_use_http_proxy(gc);
 
-	/* Using callback_data so I have access to gc in the callback function */
-	cb = g_new0(struct callback_data, 1);
-	cb->gc = gc;
-
 	/*  Build all the info to make the web request */
 	url = yd->jp ? YAHOOJP_ALIAS_FETCH_URL : YAHOO_ALIAS_FETCH_URL;
 	purple_url_parse(url, &webaddress, NULL, &webpage, NULL, NULL);
@@ -177,7 +201,7 @@
 	/* We have a URL and some header information, let's connect and get some aliases  */
 	url_data = purple_util_fetch_url_request_len_with_account(purple_connection_get_account(gc),
 				url, use_whole_url, NULL, TRUE, request, FALSE, -1,
-				yahoo_fetch_aliases_cb, cb);
+				yahoo_fetch_aliases_cb, gc);
 	if (url_data != NULL)
 		yd->url_datas = g_slist_prepend(yd->url_datas, url_data);
 
@@ -359,3 +383,104 @@
 	g_free(request);
 }
 
+
+/**************************************************************************
+ * User Info Update Functions
+ **************************************************************************/
+
+static void
+yahoo_set_userinfo_cb(PurpleConnection *gc, PurpleRequestFields *fields)
+{
+	xmlnode *node = xmlnode_new("ab");
+	xmlnode *ct = xmlnode_new_child(node, "ct");
+	struct yahoo_data *yd = purple_connection_get_protocol_data(gc);
+	PurpleAccount *account;
+	PurpleUtilFetchUrlData *url_data;
+	char *webaddress, *webpage;
+	char *request, *content;
+	int len;
+	int i;
+	char * yfields[] = { "fn", "ln", "nn", "mn", "hp", "wp", "mo", NULL };
+
+	account = purple_connection_get_account(gc);
+
+	xmlnode_set_attrib(node, "k", purple_connection_get_display_name(gc));
+	xmlnode_set_attrib(node, "cc", "1");		/* XXX: ? */
+
+	xmlnode_set_attrib(ct, "e", "1");
+	xmlnode_set_attrib(ct, "yi", purple_connection_get_display_name(gc));
+	xmlnode_set_attrib(ct, "id", yd->ypd.id);
+	xmlnode_set_attrib(ct, "pr", "0");
+
+	for (i = 0; yfields[i]; i++) {
+		const char *v = purple_request_fields_get_string(fields, yfields[i]);
+		xmlnode_set_attrib(ct, yfields[i], v ? v : "");
+	}
+
+	content = xmlnode_to_formatted_str(node, &len);
+	purple_url_parse(yd->jp ? YAHOOJP_USERINFO_URL : YAHOO_USERINFO_URL, &webaddress, NULL, &webpage, NULL, NULL);
+
+	request = g_strdup_printf("POST %s HTTP/1.1\r\n"
+				  "User-Agent: " YAHOO_CLIENT_USERAGENT "\r\n"
+				  "Cookie: T=%s; path=/; domain=.yahoo.com; Y=%s;\r\n"
+				  "Host: %s\r\n"
+				  "Content-Length: %" G_GSIZE_FORMAT "\r\n"
+				  "Cache-Control: no-cache\r\n\r\n"
+				  "%s\r\n\r\n",
+				  webpage,
+				  yd->cookie_t, yd->cookie_y,
+				  webaddress,
+				  len + 4,
+				  content);
+
+	url_data = purple_util_fetch_url_request_len_with_account(account, webaddress, FALSE,
+			YAHOO_CLIENT_USERAGENT, TRUE, request, FALSE, -1,
+			yahoo_fetch_aliases_cb, gc);
+	if (url_data != NULL)
+		yd->url_datas = g_slist_prepend(yd->url_datas, url_data);
+
+	g_free(webaddress);
+	g_free(webpage);
+	g_free(content);
+	g_free(request);
+	xmlnode_free(node);
+}
+
+void yahoo_set_userinfo(PurpleConnection *gc)
+{
+	PurpleRequestFields *fields;
+	PurpleRequestFieldGroup *group;
+	PurpleRequestField *field;
+	struct yahoo_data *yd = purple_connection_get_protocol_data(gc);
+	int i;
+	struct {
+		char *id;
+		char *text;
+		char *value;
+	} yfields[] = {
+		{"fn", N_("First Name"), yd->ypd.names.first},
+		{"ln", N_("Last Name"), yd->ypd.names.last},
+		{"nn", N_("Nickname"), yd->ypd.names.nick},
+		{"mn", N_("Middle Name"), yd->ypd.names.middle},
+		{"hp", N_("Home Phone Number"), yd->ypd.phone.home},
+		{"wp", N_("Work Phone Number"), yd->ypd.phone.work},
+		{"mo", N_("Mobile Phone Number"), yd->ypd.phone.mobile},
+		{NULL, NULL, NULL}
+	};
+
+	fields = purple_request_fields_new();
+	group = purple_request_field_group_new(NULL);
+	purple_request_fields_add_group(fields, group);
+
+	for (i = 0; yfields[i].id; i++) {
+		field = purple_request_field_string_new(yfields[i].id, _(yfields[i].text),
+				yfields[i].value, FALSE);
+		purple_request_field_group_add_field(group, field);
+	}
+
+	purple_request_fields(gc, NULL, _("Set User Info"), NULL, fields,
+			_("OK"), G_CALLBACK(yahoo_set_userinfo_cb),
+			_("Cancel"), NULL,
+			purple_connection_get_account(gc), NULL, NULL, gc);
+}
+
--- a/libpurple/protocols/yahoo/yahoo_aliases.h	Sat Jul 11 07:01:00 2009 +0000
+++ b/libpurple/protocols/yahoo/yahoo_aliases.h	Sat Jul 11 07:02:33 2009 +0000
@@ -35,4 +35,6 @@
 
 void yahoo_update_alias(PurpleConnection *gc, const char *who, const char *alias);
 void yahoo_fetch_aliases(PurpleConnection *gc);
+void yahoo_set_userinfo(PurpleConnection *gc);
+void yahoo_personal_details_reset(YahooPersonalDetails *ypd);
 
--- a/libpurple/protocols/yahoo/yahoo_friend.h	Sat Jul 11 07:01:00 2009 +0000
+++ b/libpurple/protocols/yahoo/yahoo_friend.h	Sat Jul 11 07:02:33 2009 +0000
@@ -56,7 +56,7 @@
 	YahooPresenceVisibility presence;
 	int protocol; /* 1=LCS, 2=MSN*/
 	long int version_id;
-	gchar *alias_id;
+	gchar *alias_id;		/* XXX: stick in a YahooPersonalDetails instead? */
 	YahooP2PStatus p2p_status;
 	gboolean p2p_packet_sent;	/* 0:not sent, 1=sent */
 	gint session_id;	/* session id of friend */
--- a/libpurple/protocols/yahoo/yahoo_packet.c	Sat Jul 11 07:01:00 2009 +0000
+++ b/libpurple/protocols/yahoo/yahoo_packet.c	Sat Jul 11 07:02:33 2009 +0000
@@ -187,7 +187,7 @@
 			pos = x;
 			pkt->hash = g_slist_prepend(pkt->hash, pair);
 
-			if (purple_debug_is_verbose()) {
+			if (purple_debug_is_verbose() || g_getenv("PURPLE_YAHOO_DEBUG")) {
 				char *esc;
 				esc = g_strescape(pair->value, NULL);
 				purple_debug_misc("yahoo", "Key: %d  \tValue: %s\n", pair->key, esc);
--- a/libpurple/tests/test_jabber_jutil.c	Sat Jul 11 07:01:00 2009 +0000
+++ b/libpurple/tests/test_jabber_jutil.c	Sat Jul 11 07:02:33 2009 +0000
@@ -68,6 +68,58 @@
 }
 END_TEST
 
+#define assert_valid_jid(str) { \
+	JabberID *jid = jabber_id_new(str); \
+	fail_if(jid == NULL, "JID '%s' is valid but jabber_id_new() rejected it", str); \
+	jabber_id_free(jid); \
+}
+
+#define assert_invalid_jid(str) { \
+	JabberID *jid = jabber_id_new(str); \
+	fail_if(jid != NULL, "JID '%s' is invalid but jabber_id_new() allowed it", str); \
+	jabber_id_free(jid); \
+}
+
+START_TEST(test_jabber_id_new)
+{
+	assert_valid_jid("gmail.com");
+	assert_valid_jid("gmail.com/Test");
+	assert_valid_jid("gmail.com/Test@");
+	assert_valid_jid("gmail.com/@");
+	assert_valid_jid("gmail.com/Test@alkjaweflkj");
+	assert_valid_jid("mark.doliner@gmail.com");
+	assert_valid_jid("mark.doliner@gmail.com/Test12345");
+	assert_valid_jid("mark.doliner@gmail.com/Test@12345");
+	assert_valid_jid("mark.doliner@gmail.com/Te/st@12@//345");
+	assert_valid_jid("わいど@conference.jabber.org");
+	assert_valid_jid("まりるーむ@conference.jabber.org");
+	assert_valid_jid("mark.doliner@gmail.com/まりるーむ");
+	assert_valid_jid("mark.doliner@gmail/stuff.org");
+	assert_valid_jid("stuart@nödåtXäYZ.se");
+	assert_valid_jid("stuart@nödåtXäYZ.se/まりるーむ");
+	assert_valid_jid("mark.doliner@わいど.org");
+	assert_valid_jid("nick@まつ.おおかみ.net");
+	assert_valid_jid("paul@10.0.42.230/s");
+#if 0
+/* Uncomment these when jabber_domain_validate supports IPv6 addresses */
+	assert_valid_jid("paul@[::1]"); /* IPv6 */
+	assert_valid_jid("paul@[2001:470:1f05:d58::2]");
+#endif
+
+	assert_invalid_jid("@gmail.com");
+	assert_invalid_jid("@@gmail.com");
+	assert_invalid_jid("mark.doliner@@gmail.com/Test12345");
+	assert_invalid_jid("mark@doliner@gmail.com/Test12345");
+	assert_invalid_jid("@gmail.com/Test@12345");
+	assert_invalid_jid("/Test@12345");
+	assert_invalid_jid("mark.doliner@");
+	assert_invalid_jid("mark.doliner/");
+	assert_invalid_jid("mark.doliner@gmail_stuff.org");
+	assert_invalid_jid("mark.doliner@gmail[stuff.org");
+	assert_invalid_jid("mark.doliner@gmail\\stuff.org");
+}
+END_TEST
+
 Suite *
 jabber_jutil_suite(void)
 {
@@ -82,10 +134,11 @@
 	tcase_add_test(tc, test_get_bare_jid);
 	suite_add_tcase(s, tc);
 
-	tc = tcase_create("Nodeprep validate");
+	tc = tcase_create("JID validate");
 	tcase_add_test(tc, test_nodeprep_validate);
 	tcase_add_test(tc, test_nodeprep_validate_illegal_chars);
 	tcase_add_test(tc, test_nodeprep_validate_too_long);
+	tcase_add_test(tc, test_jabber_id_new);
 	suite_add_tcase(s, tc);
 
 	return s;
--- a/libpurple/tests/test_util.c	Sat Jul 11 07:01:00 2009 +0000
+++ b/libpurple/tests/test_util.c	Sat Jul 11 07:02:33 2009 +0000
@@ -14,8 +14,7 @@
 	gsize sz = 0;
 	guchar *out = purple_base16_decode("21646c726f77202c6f6c6c656800", &sz);
 	fail_unless(sz == 14, NULL);
-	assert_string_equal("!dlrow ,olleh", (const char *)out);
-	g_free(out);
+	assert_string_equal_free("!dlrow ,olleh", (char *)out);
 }
 END_TEST
 
@@ -30,8 +29,7 @@
 	gsize sz;
 	guchar *out = purple_base64_decode("b3d0LXl0cm9mAA==", &sz);
 	fail_unless(sz == 10, NULL);
-	assert_string_equal("owt-ytrof", (const char *)out);
-	g_free(out);
+	assert_string_equal_free("owt-ytrof", (char *)out);
 }
 END_TEST
 
@@ -94,18 +92,15 @@
 	gchar *xhtml = NULL;
 	gchar *plaintext = NULL;
 	purple_markup_html_to_xhtml("<a>", &xhtml, &plaintext);
-	assert_string_equal("<a href=\"\"></a>", xhtml);
-	g_free(xhtml);
-	assert_string_equal("", plaintext);
-	g_free(plaintext);
+	assert_string_equal_free("<a href=\"\"></a>", xhtml);
+	assert_string_equal_free("", plaintext);
 }
 END_TEST
 
 START_TEST(test_mime_decode_field)
 {
 	gchar *result = purple_mime_decode_field("=?ISO-8859-1?Q?Keld_J=F8rn_Simonsen?=");
-	assert_string_equal("Keld Jørn Simonsen", result);
-	g_free(result);
+	assert_string_equal_free("Keld Jørn Simonsen", result);
 }
 END_TEST
 
--- a/libpurple/util.c	Sat Jul 11 07:01:00 2009 +0000
+++ b/libpurple/util.c	Sat Jul 11 07:02:33 2009 +0000
@@ -65,7 +65,7 @@
 	gboolean got_headers;
 	gboolean has_explicit_data_len;
 	char *webdata;
-	unsigned long len;
+	gsize len;
 	unsigned long data_len;
 	gssize max_len;
 	gboolean chunked;
@@ -3099,13 +3099,15 @@
 {
 	struct sockaddr addr;
 	socklen_t namelen = sizeof(addr);
+	struct in_addr in;
 
 	g_return_val_if_fail(fd != 0, NULL);
 
 	if (getsockname(fd, &addr, &namelen))
 		return NULL;
 
-	return g_strdup(inet_ntoa(((struct sockaddr_in *)&addr)->sin_addr));
+	in = ((struct sockaddr_in *)&addr)->sin_addr;
+	return g_strdup(inet_ntoa(in));
 }
 
 
--- a/pidgin/plugins/gevolution/add_buddy_dialog.c	Sat Jul 11 07:01:00 2009 +0000
+++ b/pidgin/plugins/gevolution/add_buddy_dialog.c	Sat Jul 11 07:02:33 2009 +0000
@@ -234,6 +234,7 @@
 	EBook *book;
 	gboolean status;
 	GList *cards, *c;
+	GError *err = NULL;
 
 	if (dialog->book != NULL)
 	{
@@ -250,10 +251,11 @@
 
 	gtk_list_store_clear(dialog->model);
 
-	if (!gevo_load_addressbook(uri, &book, NULL))
+	if (!gevo_load_addressbook(uri, &book, &err))
 	{
 		purple_debug_error("evolution",
-						 "Error retrieving default addressbook\n");
+						 "Error retrieving default addressbook: %s\n", err->message);
+		g_error_free(err);
 
 		return;
 	}
--- a/pidgin/plugins/gevolution/assoc-buddy.c	Sat Jul 11 07:01:00 2009 +0000
+++ b/pidgin/plugins/gevolution/assoc-buddy.c	Sat Jul 11 07:02:33 2009 +0000
@@ -138,6 +138,7 @@
 	const char *prpl_id;
 	gboolean status;
 	GList *cards, *c;
+	GError *err = NULL;
 
 	if (dialog->book != NULL)
 	{
@@ -154,10 +155,11 @@
 
 	gtk_list_store_clear(dialog->model);
 
-	if (!gevo_load_addressbook(uri, &book, NULL))
+	if (!gevo_load_addressbook(uri, &book, &err))
 	{
 		purple_debug_error("evolution",
-						 "Error retrieving addressbook\n");
+						 "Error retrieving addressbook: %s\n", err->message);
+		g_error_free(err);
 
 		return;
 	}
--- a/pidgin/plugins/gevolution/eds-utils.c	Sat Jul 11 07:01:00 2009 +0000
+++ b/pidgin/plugins/gevolution/eds-utils.c	Sat Jul 11 07:02:33 2009 +0000
@@ -119,11 +119,13 @@
 	EBook *book;
 	gboolean status;
 	GList *cards;
+	GError *err = NULL;
 
-	if (!gevo_load_addressbook(uri, &book, NULL))
+	if (!gevo_load_addressbook(uri, &book, &err))
 	{
 		purple_debug_error("evolution",
-						 "Error retrieving addressbook\n");
+						 "Error retrieving addressbook: %s\n", err->message);
+		g_error_free(err);
 		return NULL;
 	}
 
--- a/pidgin/plugins/gevolution/gevo-util.c	Sat Jul 11 07:01:00 2009 +0000
+++ b/pidgin/plugins/gevolution/gevo-util.c	Sat Jul 11 07:02:33 2009 +0000
@@ -143,11 +143,16 @@
 	g_return_val_if_fail(book != NULL, FALSE);
 
 	if (uri == NULL)
-		*book = e_book_new_system_addressbook(NULL);
+		*book = e_book_new_system_addressbook(error);
 	else
 		*book = e_book_new_from_uri(uri, error);
 
-	result = e_book_open(*book, FALSE, NULL);
+	if (*book == NULL)
+		return FALSE;
+
+	*error = NULL;
+
+	result = e_book_open(*book, FALSE, error);
 
 	if (!result && *book != NULL)
 	{
--- a/pidgin/plugins/gevolution/gevolution.c	Sat Jul 11 07:01:00 2009 +0000
+++ b/pidgin/plugins/gevolution/gevolution.c	Sat Jul 11 07:02:33 2009 +0000
@@ -298,12 +298,18 @@
 {
 	PurplePlugin *plugin = (PurplePlugin *)data;
 	EBookQuery *query;
+	GError *err = NULL;
 
 	timer = 0;
 
 	/* Maybe this is it? */
-	if (!gevo_load_addressbook(NULL, &book, NULL))
+	if (!gevo_load_addressbook(NULL, &book, &err))
+	{
+		purple_debug_error("evolution",
+						 "Error retrieving addressbook: %s\n", err->message);
+		g_error_free(err);
 		return FALSE;
+	}
 
 	query = e_book_query_any_field_contains("");
 
--- a/po/POTFILES.in	Sat Jul 11 07:01:00 2009 +0000
+++ b/po/POTFILES.in	Sat Jul 11 07:02:33 2009 +0000
@@ -176,6 +176,7 @@
 libpurple/protocols/yahoo/libyahoo.c
 libpurple/protocols/yahoo/libyahoojp.c
 libpurple/protocols/yahoo/libymsg.c
+libpurple/protocols/yahoo/yahoo_aliases.c
 libpurple/protocols/yahoo/yahoo_doodle.c
 libpurple/protocols/yahoo/yahoo_filexfer.c
 libpurple/protocols/yahoo/yahoo_packet.c