changeset 26731:0e67e1b7581e

merge of '2833fd1d337477c5b6efc4247a9e6127580414ea' and '4831edb99cb3b9420b5fc9cee492184c163b291b'
author Paul Aurich <paul@darkrain42.org>
date Mon, 27 Apr 2009 17:01:11 +0000
parents 911a325116d5 (current diff) baa3867f2281 (diff)
children d1a0c36cc4ea
files
diffstat 13 files changed, 542 insertions(+), 295 deletions(-) [+]
line wrap: on
line diff
--- a/ChangeLog	Mon Apr 27 16:25:57 2009 +0000
+++ b/ChangeLog	Mon Apr 27 17:01:11 2009 +0000
@@ -19,7 +19,7 @@
 	  and voice support with GTalk and GMail. (Mike "Maiku" Ruprecht)
 	* Add support for in-band bytestreams for file transfers (XEP-0047).
 	* Add support for sending and receiving attentions (equivalent to "buzz"
-	  and "nudge") using the command /buzz (XEP-0224).
+	  and "nudge") using the command /buzz. (XEP-0224)
 	* A buddy's local time is displayed in the Get Info dialog if the remote
 	  client supports it.
 	* The set_chat_topic function can unset the chat topic.
@@ -28,7 +28,8 @@
 	  type 'headline'.
 	* The Ad-Hoc commands associated with our server are now always shown at
 	  login.
-	* Support showing and reporting idle times (XEP-0256)
+	* Support showing and reporting idle times in the buddy list. (XEP-0256)
+	* Support most recent version of User Avatar. (XEP-0084 v1.1)
 
 	IRC:
 	* Correctly handle WHOIS for users who are joined to a large number of
--- a/libpurple/protocols/jabber/Makefile.am	Mon Apr 27 16:25:57 2009 +0000
+++ b/libpurple/protocols/jabber/Makefile.am	Mon Apr 27 17:01:11 2009 +0000
@@ -61,6 +61,8 @@
 			  adhoccommands.h \
 			  pep.c \
 			  pep.h \
+			  useravatar.c \
+			  useravatar.h \
 			  usermood.c \
 			  usermood.h \
 			  usernick.c \
--- a/libpurple/protocols/jabber/buddy.c	Mon Apr 27 16:25:57 2009 +0000
+++ b/libpurple/protocols/jabber/buddy.c	Mon Apr 27 17:01:11 2009 +0000
@@ -32,12 +32,11 @@
 #include "jabber.h"
 #include "iq.h"
 #include "presence.h"
+#include "useravatar.h"
 #include "xdata.h"
 #include "pep.h"
 #include "adhoccommands.h"
 
-#define MAX_HTTP_BUDDYICON_BYTES (200 * 1024)
-
 typedef struct {
 	long idle_seconds;
 } JabberBuddyInfoResource;
@@ -472,137 +471,30 @@
 	}
 
 	if (vc_node != NULL) {
+		PurpleAccount *account = purple_connection_get_account(js->gc);
+
 		iq = jabber_iq_new(js, JABBER_IQ_SET);
 		xmlnode_insert_child(iq->node, vc_node);
 		jabber_iq_send(iq);
+
+		/* Send presence to update vcard-temp:x:update */
+		jabber_presence_send(account, purple_account_get_active_status(account));
 	}
 }
 
 void jabber_set_buddy_icon(PurpleConnection *gc, PurpleStoredImage *img)
 {
-	PurplePresence *gpresence;
-	PurpleStatus *status;
-
-	if(((JabberStream*)purple_connection_get_protocol_data(gc))->pep) {
-		/* XEP-0084: User Avatars */
-		if(img) {
-			/*
-			 * TODO: This is pretty gross.  The Jabber PRPL really shouldn't
-			 *       do voodoo to try to determine the image type, height
-			 *       and width.
-			 */
-			/* A PNG header, including the IHDR, but nothing else */
-			const struct {
-				guchar signature[8]; /* must be hex 89 50 4E 47 0D 0A 1A 0A */
-				struct {
-					guint32 length; /* must be 0x0d */
-					guchar type[4]; /* must be 'I' 'H' 'D' 'R' */
-					guint32 width;
-					guint32 height;
-					guchar bitdepth;
-					guchar colortype;
-					guchar compression;
-					guchar filter;
-					guchar interlace;
-				} ihdr;
-			} *png = purple_imgstore_get_data(img); /* ATTN: this is in network byte order! */
-
-			/* check if the data is a valid png file (well, at least to some extend) */
-			if(png->signature[0] == 0x89 &&
-			   png->signature[1] == 0x50 &&
-			   png->signature[2] == 0x4e &&
-			   png->signature[3] == 0x47 &&
-			   png->signature[4] == 0x0d &&
-			   png->signature[5] == 0x0a &&
-			   png->signature[6] == 0x1a &&
-			   png->signature[7] == 0x0a &&
-			   ntohl(png->ihdr.length) == 0x0d &&
-			   png->ihdr.type[0] == 'I' &&
-			   png->ihdr.type[1] == 'H' &&
-			   png->ihdr.type[2] == 'D' &&
-			   png->ihdr.type[3] == 'R') {
-				/* parse PNG header to get the size of the image (yes, this is required) */
-				guint32 width = ntohl(png->ihdr.width);
-				guint32 height = ntohl(png->ihdr.height);
-				xmlnode *publish, *item, *data, *metadata, *info;
-				char *lengthstring, *widthstring, *heightstring;
-
-				/* compute the sha1 hash */
-				char *hash = jabber_calculate_data_sha1sum(purple_imgstore_get_data(img), purple_imgstore_get_size(img));
-				char *base64avatar;
-
-				publish = xmlnode_new("publish");
-				xmlnode_set_attrib(publish,"node",AVATARNAMESPACEDATA);
-
-				item = xmlnode_new_child(publish, "item");
-				xmlnode_set_attrib(item, "id", hash);
-
-				data = xmlnode_new_child(item, "data");
-				xmlnode_set_namespace(data,AVATARNAMESPACEDATA);
+	PurpleAccount *account = purple_connection_get_account(gc);
 
-				base64avatar = purple_base64_encode(purple_imgstore_get_data(img), purple_imgstore_get_size(img));
-				xmlnode_insert_data(data,base64avatar,-1);
-				g_free(base64avatar);
-
-				/* publish the avatar itself */
-				jabber_pep_publish((JabberStream*)purple_connection_get_protocol_data(gc), publish);
-
-				/* next step: publish the metadata */
-				publish = xmlnode_new("publish");
-				xmlnode_set_attrib(publish,"node",AVATARNAMESPACEMETA);
-
-				item = xmlnode_new_child(publish, "item");
-				xmlnode_set_attrib(item, "id", hash);
-
-				metadata = xmlnode_new_child(item, "metadata");
-				xmlnode_set_namespace(metadata,AVATARNAMESPACEMETA);
-
-				info = xmlnode_new_child(metadata, "info");
-				xmlnode_set_attrib(info, "id", hash);
-				xmlnode_set_attrib(info, "type", "image/png");
-				lengthstring = g_strdup_printf("%u", (unsigned)purple_imgstore_get_size(img));
-				xmlnode_set_attrib(info, "bytes", lengthstring);
-				g_free(lengthstring);
-				widthstring = g_strdup_printf("%u", width);
-				xmlnode_set_attrib(info, "width", widthstring);
-				g_free(widthstring);
-				heightstring = g_strdup_printf("%u", height);
-				xmlnode_set_attrib(info, "height", heightstring);
-				g_free(heightstring);
+	/* Publish the avatar as specified in XEP-0084 */
+	jabber_avatar_set(gc->proto_data, img);
+	/* Set the image in our vCard */
+	jabber_set_info(gc, purple_account_get_user_info(account));
 
-				/* publish the metadata */
-				jabber_pep_publish((JabberStream*)purple_connection_get_protocol_data(gc), publish);
-
-				g_free(hash);
-			} else {
-				purple_debug_error("jabber", "jabber_set_buddy_icon received non-png data");
-			}
-		} else {
-			/* remove the metadata */
-			xmlnode *metadata, *item;
-			xmlnode *publish = xmlnode_new("publish");
-			xmlnode_set_attrib(publish,"node",AVATARNAMESPACEMETA);
-
-			item = xmlnode_new_child(publish, "item");
-
-			metadata = xmlnode_new_child(item, "metadata");
-			xmlnode_set_namespace(metadata,AVATARNAMESPACEMETA);
-
-			xmlnode_new_child(metadata, "stop");
-
-			/* publish the metadata */
-			jabber_pep_publish((JabberStream*)gc->proto_data, publish);
-		}
-	}
-
-	/* vCard avatars do not have an image type requirement so update our
-	 * vCard avatar regardless of image type for those poor older clients
-	 */
-	jabber_set_info(gc, purple_account_get_user_info(gc->account));
-
-	gpresence = purple_account_get_presence(gc->account);
-	status = purple_presence_get_active_status(gpresence);
-	jabber_presence_send(gc->account, status);
+	/* TODO: Fake image to ourselves, since a number of servers do not echo
+	 * back our presence to us. To do this without uselessly copying the data
+	 * of the image, we need purple_buddy_icons_set_for_user_image (i.e. takes
+	 * an existing icon/stored image). */
 }
 
 /*
@@ -1177,9 +1069,8 @@
                                    JabberIqType type, const char *id,
                                    xmlnode *packet, gpointer data)
 {
-	xmlnode *vcard;
-	char *txt;
-	PurpleStoredImage *img;
+	xmlnode *vcard, *photo, *binval;
+	char *txt, *vcard_hash = NULL;
 
 	if (type == JABBER_IQ_ERROR) {
 		purple_debug_warning("jabber", "Server returned error while retrieving vCard");
@@ -1199,10 +1090,29 @@
 
 	js->vcard_fetched = TRUE;
 
-	if(NULL != (img = purple_buddy_icons_find_account_icon(js->gc->account))) {
-		jabber_set_buddy_icon(js->gc, img);
-		purple_imgstore_unref(img);
+	if (vcard && (photo = xmlnode_get_child(vcard, "PHOTO")) &&
+	             (binval = xmlnode_get_child(photo, "BINVAL"))) {
+		gsize size;
+		char *bintext = xmlnode_get_data(binval);
+		guchar *data = purple_base64_decode(bintext, &size);
+		g_free(bintext);
+
+		if (data) {
+			vcard_hash = jabber_calculate_data_sha1sum(data, size);
+			g_free(data);
+		}
 	}
+
+	/* Republish our vcard if the photo is different than the server's */
+	if (!purple_strequal(vcard_hash, js->initial_avatar_hash)) {
+		PurpleAccount *account = purple_connection_get_account(js->gc);
+		jabber_set_info(js->gc, purple_account_get_user_info(account));
+	} else if (js->initial_avatar_hash) {
+		/* Our photo is in the vcard, so advertise vcard-temp updates */
+		js->avatar_hash = g_strdup(js->initial_avatar_hash);
+	}
+
+	g_free(vcard_hash);
 }
 
 void jabber_vcard_fetch_mine(JabberStream *js)
@@ -1450,127 +1360,6 @@
 	jabber_buddy_info_show_if_ready(jbi);
 }
 
-typedef struct _JabberBuddyAvatarUpdateURLInfo {
-	JabberStream *js;
-	char *from;
-	char *id;
-} JabberBuddyAvatarUpdateURLInfo;
-
-static void do_buddy_avatar_update_fromurl(PurpleUtilFetchUrlData *url_data, gpointer user_data, const gchar *url_text, gsize len, const gchar *error_message) {
-	JabberBuddyAvatarUpdateURLInfo *info = user_data;
-	if(!url_text) {
-		purple_debug(PURPLE_DEBUG_ERROR, "jabber",
-					 "do_buddy_avatar_update_fromurl got error \"%s\"", error_message);
-		return;
-	}
-
-	purple_buddy_icons_set_for_user(purple_connection_get_account(info->js->gc), info->from, (void*)url_text, len, info->id);
-	g_free(info->from);
-	g_free(info->id);
-	g_free(info);
-}
-
-static void do_buddy_avatar_update_data(JabberStream *js, const char *from, xmlnode *items) {
-	xmlnode *item, *data;
-	const char *checksum;
-	char *b64data;
-	void *img;
-	size_t size;
-	if(!items)
-		return;
-
-	item = xmlnode_get_child(items, "item");
-	if(!item)
-		return;
-
-	data = xmlnode_get_child_with_namespace(item,"data",AVATARNAMESPACEDATA);
-	if(!data)
-		return;
-
-	checksum = xmlnode_get_attrib(item,"id");
-	if(!checksum)
-		return;
-
-	b64data = xmlnode_get_data(data);
-	if(!b64data)
-		return;
-
-	img = purple_base64_decode(b64data, &size);
-	if(!img) {
-		g_free(b64data);
-		return;
-	}
-
-	purple_buddy_icons_set_for_user(purple_connection_get_account(js->gc), from, img, size, checksum);
-	g_free(b64data);
-}
-
-void jabber_buddy_avatar_update_metadata(JabberStream *js, const char *from, xmlnode *items) {
-	PurpleBuddy *buddy = purple_find_buddy(purple_connection_get_account(js->gc), from);
-	const char *checksum;
-	xmlnode *item, *metadata;
-	if(!buddy)
-		return;
-
-	checksum = purple_buddy_icons_get_checksum_for_user(buddy);
-	item = xmlnode_get_child(items,"item");
-	metadata = xmlnode_get_child_with_namespace(item, "metadata", AVATARNAMESPACEMETA);
-	if(!metadata)
-		return;
-	/* check if we have received a stop */
-	if(xmlnode_get_child(metadata, "stop")) {
-		purple_buddy_icons_set_for_user(purple_connection_get_account(js->gc), from, NULL, 0, NULL);
-	} else {
-		xmlnode *info, *goodinfo = NULL;
-		gboolean has_children = FALSE;
-
-		/* iterate over all info nodes to get one we can use */
-		for(info = metadata->child; info; info = info->next) {
-			if(info->type == XMLNODE_TYPE_TAG)
-				has_children = TRUE;
-			if(info->type == XMLNODE_TYPE_TAG && !strcmp(info->name,"info")) {
-				const char *type = xmlnode_get_attrib(info,"type");
-				const char *id = xmlnode_get_attrib(info,"id");
-
-				if(checksum && id && !strcmp(id, checksum)) {
-					/* we already have that avatar, so we don't have to do anything */
-					goodinfo = NULL;
-					break;
-				}
-				/* We'll only pick the png one for now. It's a very nice image format anyways. */
-				if(type && id && !goodinfo && !strcmp(type, "image/png"))
-					goodinfo = info;
-			}
-		}
-		if(has_children == FALSE) {
-			purple_buddy_icons_set_for_user(purple_connection_get_account(js->gc), from, NULL, 0, NULL);
-		} else if(goodinfo) {
-			const char *url = xmlnode_get_attrib(goodinfo, "url");
-			const char *id = xmlnode_get_attrib(goodinfo,"id");
-
-			/* the avatar might either be stored in a pep node, or on a HTTP/HTTPS URL */
-			if(!url)
-				jabber_pep_request_item(js, from, AVATARNAMESPACEDATA, id, do_buddy_avatar_update_data);
-			else {
-				PurpleUtilFetchUrlData *url_data;
-				JabberBuddyAvatarUpdateURLInfo *info = g_new0(JabberBuddyAvatarUpdateURLInfo, 1);
-				info->js = js;
-
-				url_data = purple_util_fetch_url_len(url, TRUE, NULL, TRUE,
-										  MAX_HTTP_BUDDYICON_BYTES,
-										  do_buddy_avatar_update_fromurl, info);
-				if (url_data) {
-					info->from = g_strdup(from);
-					info->id = g_strdup(id);
-					js->url_datas = g_slist_prepend(js->url_datas, url_data);
-				} else
-					g_free(info);
-
-			}
-		}
-	}
-}
-
 static void jabber_buddy_info_resource_free(gpointer data)
 {
 	JabberBuddyInfoResource *jbri = data;
--- a/libpurple/protocols/jabber/buddy.h	Mon Apr 27 16:25:57 2009 +0000
+++ b/libpurple/protocols/jabber/buddy.h	Mon Apr 27 17:01:11 2009 +0000
@@ -36,9 +36,6 @@
 #include "jabber.h"
 #include "caps.h"
 
-#define AVATARNAMESPACEDATA "http://www.xmpp.org/extensions/xep-0084.html#ns-data"
-#define AVATARNAMESPACEMETA "http://www.xmpp.org/extensions/xep-0084.html#ns-metadata"
-
 typedef struct _JabberBuddy {
 	GList *resources;
 	char *error_msg;
@@ -104,7 +101,6 @@
 void jabber_set_info(PurpleConnection *gc, const char *info);
 void jabber_setup_set_info(PurplePluginAction *action);
 void jabber_set_buddy_icon(PurpleConnection *gc, PurpleStoredImage *img);
-void jabber_buddy_avatar_update_metadata(JabberStream *js, const char *from, xmlnode *items);
 
 const char *jabber_buddy_state_get_name(JabberBuddyState state);
 const char *jabber_buddy_state_get_status_id(JabberBuddyState state);
--- a/libpurple/protocols/jabber/disco.c	Mon Apr 27 16:25:57 2009 +0000
+++ b/libpurple/protocols/jabber/disco.c	Mon Apr 27 17:01:11 2009 +0000
@@ -23,17 +23,17 @@
 #include "prefs.h"
 #include "debug.h"
 
+#include "adhoccommands.h"
 #include "buddy.h"
+#include "disco.h"
 #include "google.h"
 #include "iq.h"
-#include "disco.h"
 #include "jabber.h"
 #include "jingle/jingle.h"
+#include "pep.h"
 #include "presence.h"
 #include "roster.h"
-#include "pep.h"
-#include "adhoccommands.h"
-
+#include "useravatar.h"
 
 struct _jabber_disco_info_cb_data {
 	gpointer data;
@@ -341,8 +341,16 @@
 {
 	const char *ft_proxies;
 
+	/*
+	 * This *should* happen only if the server supports vcard-temp, but there
+	 * are apparently some servers that don't advertise it even though they
+	 * support it.
+	 */
 	jabber_vcard_fetch_mine(js);
 
+	if (js->pep)
+		jabber_avatar_fetch_mine(js);
+
 	if (!(js->server_caps & JABBER_CAP_GOOGLE_ROSTER)) {
 		/* If the server supports JABBER_CAP_GOOGLE_ROSTER; we will have already requested it */
 		jabber_roster_request(js);
--- a/libpurple/protocols/jabber/jabber.c	Mon Apr 27 16:25:57 2009 +0000
+++ b/libpurple/protocols/jabber/jabber.c	Mon Apr 27 17:01:11 2009 +0000
@@ -28,6 +28,7 @@
 #include "conversation.h"
 #include "debug.h"
 #include "dnssrv.h"
+#include "imgstore.h"
 #include "message.h"
 #include "notify.h"
 #include "pluginpref.h"
@@ -709,6 +710,7 @@
 			"connect_server", "");
 	JabberStream *js;
 	PurplePresence *presence;
+	PurpleStoredImage *image;
 	JabberBuddy *my_jb = NULL;
 
 	gc->flags |= PURPLE_CONNECTION_HTML |
@@ -727,7 +729,7 @@
 	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;
+	js->old_length = -1;
 	js->keepalive_timeout = -1;
 	js->certificate_CN = g_strdup(connect_server[0] ? connect_server : js->user ? js->user->domain : NULL);
 	js->sessions = NULL;
@@ -735,7 +737,6 @@
 	js->stun_port = 0;
 	js->stun_query = NULL;
 
-
 	/* if we are idle, set idle-ness on the stream (this could happen if we get
 		disconnected and the reconnects while being idle. I don't think it makes
 		sense to do this when registering a new account... */
@@ -757,6 +758,17 @@
 		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;
 
@@ -1338,6 +1350,11 @@
 	jabber_unregister_account_cb(js);
 }
 
+/* TODO: As Will pointed out in IRC, after being notified by the core to
+ * shutdown, we should async. wait for the server to send us the stream
+ * termination before destorying everything. That seems like it would require
+ * changing the semantics of prpl->close(), so it's a good idea for 3.0.0.
+ */
 void jabber_close(PurpleConnection *gc)
 {
 	JabberStream *js = gc->proto_data;
@@ -1352,16 +1369,6 @@
 	if (!gc->disconnect_timeout)
 		jabber_send_raw(js, "</stream:stream>", -1);
 
-	if (!purple_account_get_current_error(purple_connection_get_account(gc))) {
-		/*
-		 * The common case is user-triggered, so we never receive a
-		 * </stream:stream> from the server when disconnecting, so silence the
-		 * parser's warnings. On errors, though, the server terminated the
-		 * connection, so we should have received a real </stream:stream>.
-		 */
-		jabber_parser_close_stream(js);
-	}
-
 	if (js->srv_query_data)
 		purple_srv_cancel(js->srv_query_data);
 
@@ -1416,6 +1423,7 @@
 	g_free(js->stream_id);
 	if(js->user)
 		jabber_id_free(js->user);
+	g_free(js->initial_avatar_hash);
 	g_free(js->avatar_hash);
 
 	purple_circ_buffer_destroy(js->write_buffer);
@@ -1534,9 +1542,9 @@
 	JabberStream *js = gc->proto_data;
 	PurpleAccount *account = purple_connection_get_account(gc);
 	PurpleStatus *status = purple_account_get_active_status(account);
-	
+
 	js->idle = idle ? time(NULL) - idle : idle;
-	
+
 	/* send out an updated prescence */
 	purple_debug_info("jabber", "sending updated presence for idle\n");
 	jabber_presence_send(account, status);
@@ -1875,8 +1883,8 @@
 
 			purple_notify_user_info_add_pair(user_info, _("Subscription"), sub);
 
-		}	
-		
+		}
+
 		if(!PURPLE_BUDDY_IS_ONLINE(b) && jb->error_msg) {
 			purple_notify_user_info_add_pair(user_info, _("Error"), jb->error_msg);
 		}
--- a/libpurple/protocols/jabber/jabber.h	Mon Apr 27 16:25:57 2009 +0000
+++ b/libpurple/protocols/jabber/jabber.h	Mon Apr 27 17:01:11 2009 +0000
@@ -166,6 +166,7 @@
 
 	gboolean registration;
 
+	char *initial_avatar_hash;
 	char *avatar_hash;
 	GSList *pending_avatar_requests;
 
--- a/libpurple/protocols/jabber/libxmpp.c	Mon Apr 27 16:25:57 2009 +0000
+++ b/libpurple/protocols/jabber/libxmpp.c	Mon Apr 27 17:01:11 2009 +0000
@@ -117,6 +117,7 @@
 	jabber_unregister_account,		/* unregister_user */
 	jabber_send_attention,			/* send_attention */
 	jabber_attention_types,			/* attention_types */
+
 	sizeof(PurplePluginProtocolInfo),       /* struct_size */
 	NULL, /* get_account_text_table */
 	jabber_initiate_media,          /* initiate_media */
@@ -289,19 +290,16 @@
 	jabber_ibb_init();
 	jabber_si_init();
 
-	jabber_add_feature("avatarmeta", AVATARNAMESPACEMETA, jabber_pep_namespace_only_when_pep_enabled_cb);
-	jabber_add_feature("avatardata", AVATARNAMESPACEDATA, jabber_pep_namespace_only_when_pep_enabled_cb);
 	jabber_add_feature("buzz", XEP_0224_NAMESPACE,
 					   jabber_buzz_isenabled);
 	jabber_add_feature("bob", XEP_0231_NAMESPACE,
 					   jabber_custom_smileys_isenabled);
 	jabber_add_feature("ibb", XEP_0047_NAMESPACE, NULL);
 
-	jabber_pep_register_handler("avatar", AVATARNAMESPACEMETA, jabber_buddy_avatar_update_metadata);
 #ifdef USE_VV
 	jabber_add_feature("voice-v1", "http://www.xmpp.org/extensions/xep-0167.html#ns", NULL);
 #endif
 }
 
 
-PURPLE_INIT_PLUGIN(jabber, init_plugin, info);
+PURPLE_INIT_PLUGIN(jabber, init_plugin, info);
\ No newline at end of file
--- a/libpurple/protocols/jabber/pep.c	Mon Apr 27 16:25:57 2009 +0000
+++ b/libpurple/protocols/jabber/pep.c	Mon Apr 27 17:01:11 2009 +0000
@@ -24,6 +24,7 @@
 #include "pep.h"
 #include "iq.h"
 #include <string.h>
+#include "useravatar.h"
 #include "usermood.h"
 #include "usernick.h"
 
@@ -34,6 +35,7 @@
 		pep_handlers = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
 
 		/* register PEP handlers */
+		jabber_avatar_init();
 		jabber_mood_init();
 		jabber_nick_init();
 	}
@@ -110,6 +112,25 @@
 	g_free(jid);
 }
 
+void jabber_pep_delete_node(JabberStream *js, const gchar *node)
+{
+	JabberIq *iq;
+	xmlnode *pubsub, *del;
+
+	g_return_if_fail(node != NULL);
+	g_return_if_fail(js->pep);
+
+	iq = jabber_iq_new(js, JABBER_IQ_SET);
+
+	pubsub = xmlnode_new_child(iq->node, "pubsub");
+	xmlnode_set_namespace(pubsub, "http://jabber.org/protocol/pubsub#owner");
+
+	del = xmlnode_new_child(pubsub, "delete");
+	xmlnode_set_attrib(del, "node", node);
+
+	jabber_iq_send(iq);
+}
+
 void jabber_pep_publish(JabberStream *js, xmlnode *publish) {
 	JabberIq *iq;
 	xmlnode *pubsub;
--- a/libpurple/protocols/jabber/pep.h	Mon Apr 27 16:25:57 2009 +0000
+++ b/libpurple/protocols/jabber/pep.h	Mon Apr 27 17:01:11 2009 +0000
@@ -74,6 +74,11 @@
 
 void jabber_handle_event(JabberMessage *jm);
 
+/**
+ * Delete the specified PEP node.
+ */
+void jabber_pep_delete_node(JabberStream *js, const gchar *node);
+
 /*
  * Publishes PEP item(s)
  *
--- a/libpurple/protocols/jabber/presence.c	Mon Apr 27 16:25:57 2009 +0000
+++ b/libpurple/protocols/jabber/presence.c	Mon Apr 27 17:01:11 2009 +0000
@@ -157,11 +157,19 @@
 
 		presence = jabber_presence_create_js(js, state, stripped, priority);
 
-		if(js->avatar_hash) {
-			x = xmlnode_new_child(presence, "x");
-			xmlnode_set_namespace(x, "vcard-temp:x:update");
+		/* Per XEP-0153 4.1, we must always send the <x> */
+		x = xmlnode_new_child(presence, "x");
+		xmlnode_set_namespace(x, "vcard-temp:x:update");
+		/*
+		 * FIXME: Per XEP-0153 4.3.2 bullet 2, we must not publish our
+		 * image hash if another resource has logged in and updated the
+		 * vcard avatar. Requires changes in jabber_presence_parse.
+		 */
+		if (js->vcard_fetched) {
+			/* Always publish a <photo>; it's empty if we have no image. */
 			photo = xmlnode_new_child(x, "photo");
-			xmlnode_insert_data(photo, js->avatar_hash, -1);
+			if (js->avatar_hash)
+				xmlnode_insert_data(photo, js->avatar_hash, -1);
 		}
 
 		jabber_send(js, presence);
@@ -351,8 +359,6 @@
 	JabberBuddy *jb = NULL;
 	xmlnode *vcard, *photo, *binval;
 	char *text;
-	guchar *data;
-	gsize size;
 
 	if(!from)
 		return;
@@ -367,19 +373,15 @@
 				(( (binval = xmlnode_get_child(photo, "BINVAL")) &&
 				(text = xmlnode_get_data(binval))) ||
 				(text = xmlnode_get_data(photo)))) {
-			unsigned char hashval[20];
-			char hash[41], *p;
-			int i;
+			guchar *data;
+			gchar *hash;
+			gsize size;
 
 			data = purple_base64_decode(text, &size);
-
-			purple_cipher_digest_region("sha1", data, size,
-					sizeof(hashval), hashval, NULL);
-			p = hash;
-			for(i=0; i<20; i++, p+=2)
-				snprintf(p, 3, "%02x", hashval[i]);
+			hash = jabber_calculate_data_sha1sum(data, size);
 
 			purple_buddy_icons_set_for_user(js->gc->account, from, data, size, hash);
+			g_free(hash);
 			g_free(text);
 		}
 	}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/jabber/useravatar.c	Mon Apr 27 17:01:11 2009 +0000
@@ -0,0 +1,373 @@
+/*
+ * purple - Jabber Protocol Plugin
+ *
+ * Purple is the legal property of its developers, whose names are too numerous
+ * to list here.  Please refer to the COPYRIGHT file distributed with this
+ * source distribution.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.	 See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA	 02111-1307	 USA
+ *
+ */
+
+#include "internal.h"
+
+#include "useravatar.h"
+#include "pep.h"
+#include "debug.h"
+
+#define MAX_HTTP_BUDDYICON_BYTES (200 * 1024)
+
+static void update_buddy_metadata(JabberStream *js, const char *from, xmlnode *items);
+
+void jabber_avatar_init(void)
+{
+	jabber_pep_register_handler("avatar", NS_AVATAR_0_12_METADATA,
+	                            update_buddy_metadata);
+
+	jabber_add_feature("urn_avatarmeta", NS_AVATAR_1_1_METADATA,
+	                   jabber_pep_namespace_only_when_pep_enabled_cb);
+	jabber_add_feature("urn_avatardata", NS_AVATAR_1_1_DATA,
+	                   jabber_pep_namespace_only_when_pep_enabled_cb);
+
+	jabber_pep_register_handler("urn_avatar", NS_AVATAR_1_1_METADATA,
+	                            update_buddy_metadata);
+}
+
+static void
+remove_avatar_0_12_nodes(JabberStream *js)
+{
+	jabber_pep_delete_node(js, NS_AVATAR_0_12_METADATA);
+	jabber_pep_delete_node(js, NS_AVATAR_0_12_DATA);
+}
+
+void jabber_avatar_set(JabberStream *js, PurpleStoredImage *img)
+{
+	xmlnode *publish, *metadata, *item;
+
+	if (!js->pep)
+		return;
+
+	remove_avatar_0_12_nodes(js);
+
+	if (!img) {
+		publish = xmlnode_new("publish");
+		xmlnode_set_attrib(publish, "node", NS_AVATAR_1_1_METADATA);
+
+		item = xmlnode_new_child(publish, "item");
+		metadata = xmlnode_new_child(item, "metadata");
+		xmlnode_set_namespace(metadata, NS_AVATAR_1_1_METADATA);
+
+		/* publish */
+		jabber_pep_publish(js, publish);
+	} else {
+		/*
+		 * TODO: This is pretty gross.  The Jabber PRPL really shouldn't
+		 *       do voodoo to try to determine the image type, height
+		 *       and width.
+		 */
+		/* A PNG header, including the IHDR, but nothing else */
+		const struct {
+			guchar signature[8]; /* must be hex 89 50 4E 47 0D 0A 1A 0A */
+			struct {
+				guint32 length; /* must be 0x0d */
+				guchar type[4]; /* must be 'I' 'H' 'D' 'R' */
+				guint32 width;
+				guint32 height;
+				guchar bitdepth;
+				guchar colortype;
+				guchar compression;
+				guchar filter;
+				guchar interlace;
+			} ihdr;
+		} *png = purple_imgstore_get_data(img); /* ATTN: this is in network byte order! */
+
+		/* check if the data is a valid png file (well, at least to some extent) */
+		if(png->signature[0] == 0x89 &&
+		   png->signature[1] == 0x50 &&
+		   png->signature[2] == 0x4e &&
+		   png->signature[3] == 0x47 &&
+		   png->signature[4] == 0x0d &&
+		   png->signature[5] == 0x0a &&
+		   png->signature[6] == 0x1a &&
+		   png->signature[7] == 0x0a &&
+		   ntohl(png->ihdr.length) == 0x0d &&
+		   png->ihdr.type[0] == 'I' &&
+		   png->ihdr.type[1] == 'H' &&
+		   png->ihdr.type[2] == 'D' &&
+		   png->ihdr.type[3] == 'R') {
+			/* parse PNG header to get the size of the image (yes, this is required) */
+			guint32 width = ntohl(png->ihdr.width);
+			guint32 height = ntohl(png->ihdr.height);
+			xmlnode *data, *info;
+			char *lengthstring, *widthstring, *heightstring;
+
+			/* compute the sha1 hash */
+			char *hash = jabber_calculate_data_sha1sum(purple_imgstore_get_data(img),
+			                                           purple_imgstore_get_size(img));
+			char *base64avatar = purple_base64_encode(purple_imgstore_get_data(img),
+			                                          purple_imgstore_get_size(img));
+
+			publish = xmlnode_new("publish");
+			xmlnode_set_attrib(publish, "node", NS_AVATAR_1_1_DATA);
+
+			item = xmlnode_new_child(publish, "item");
+			xmlnode_set_attrib(item, "id", hash);
+
+			data = xmlnode_new_child(item, "data");
+			xmlnode_set_namespace(data, NS_AVATAR_1_1_DATA);
+
+			xmlnode_insert_data(data, base64avatar, -1);
+			/* publish the avatar itself */
+			jabber_pep_publish(js, publish);
+
+			g_free(base64avatar);
+
+			lengthstring = g_strdup_printf("%" G_GSIZE_FORMAT,
+			                               purple_imgstore_get_size(img));
+			widthstring = g_strdup_printf("%u", width);
+			heightstring = g_strdup_printf("%u", height);
+
+			/* publish the metadata */
+			publish = xmlnode_new("publish");
+			xmlnode_set_attrib(publish, "node", NS_AVATAR_1_1_METADATA);
+
+			item = xmlnode_new_child(publish, "item");
+			xmlnode_set_attrib(item, "id", hash);
+
+			metadata = xmlnode_new_child(item, "metadata");
+			xmlnode_set_namespace(metadata, NS_AVATAR_1_1_METADATA);
+
+			info = xmlnode_new_child(metadata, "info");
+			xmlnode_set_attrib(info, "id", hash);
+			xmlnode_set_attrib(info, "type", "image/png");
+			xmlnode_set_attrib(info, "bytes", lengthstring);
+			xmlnode_set_attrib(info, "width", widthstring);
+			xmlnode_set_attrib(info, "height", heightstring);
+
+			jabber_pep_publish(js, publish);
+
+			g_free(lengthstring);
+			g_free(widthstring);
+			g_free(heightstring);
+			g_free(hash);
+		} else {
+			purple_debug_error("jabber", "Cannot set PEP avatar to non-PNG data\n");
+		}
+	}
+}
+
+static void
+do_got_own_avatar_cb(JabberStream *js, const char *from, xmlnode *items)
+{
+	xmlnode *item = NULL, *metadata = NULL, *info = NULL;
+	PurpleAccount *account = purple_connection_get_account(js->gc);
+	const char *server_hash = NULL;
+	const char *ns;
+
+	if ((item = xmlnode_get_child(items, "item")) &&
+	     (metadata = xmlnode_get_child(item, "metadata")) &&
+	     (info = xmlnode_get_child(metadata, "info"))) {
+		server_hash = xmlnode_get_attrib(info, "id");
+	}
+
+	if (!metadata)
+		return;
+
+	ns = xmlnode_get_namespace(metadata);
+	if (!ns)
+		return;
+
+	/*
+	 * We no longer publish avatars to the older namespace. If there is one
+	 * there, delete it.
+	 */
+	if (g_str_equal(ns, NS_AVATAR_0_12_METADATA) && server_hash) {
+		remove_avatar_0_12_nodes(js);
+		return;
+	}
+
+	/* Publish ours if it's different than the server's */
+	if (!purple_strequal(server_hash, js->initial_avatar_hash)) {
+		PurpleStoredImage *img = purple_buddy_icons_find_account_icon(account);
+		jabber_avatar_set(js, img);
+		purple_imgstore_unref(img);
+	}
+}
+
+void jabber_avatar_fetch_mine(JabberStream *js)
+{
+	char *jid = g_strdup_printf("%s@%s", js->user->node, js->user->domain);
+	jabber_pep_request_item(js, jid, NS_AVATAR_0_12_METADATA, NULL,
+	                        do_got_own_avatar_cb);
+	jabber_pep_request_item(js, jid, NS_AVATAR_1_1_METADATA, NULL,
+	                        do_got_own_avatar_cb);
+	g_free(jid);
+}
+
+typedef struct _JabberBuddyAvatarUpdateURLInfo {
+	JabberStream *js;
+	char *from;
+	char *id;
+} JabberBuddyAvatarUpdateURLInfo;
+
+static void
+do_buddy_avatar_update_fromurl(PurpleUtilFetchUrlData *url_data,
+                               gpointer user_data, const gchar *url_text,
+                               gsize len, const gchar *error_message)
+{
+	JabberBuddyAvatarUpdateURLInfo *info = user_data;
+	if(!url_text) {
+		purple_debug(PURPLE_DEBUG_ERROR, "jabber",
+		             "do_buddy_avatar_update_fromurl got error \"%s\"",
+		             error_message);
+		goto out;
+	}
+
+	purple_buddy_icons_set_for_user(purple_connection_get_account(info->js->gc), info->from, (void*)url_text, len, info->id);
+
+out:
+	g_free(info->from);
+	g_free(info->id);
+	g_free(info);
+}
+
+static void
+do_buddy_avatar_update_data(JabberStream *js, const char *from, xmlnode *items)
+{
+	xmlnode *item, *data;
+	const char *checksum, *ns;
+	char *b64data;
+	void *img;
+	size_t size;
+	if(!items)
+		return;
+
+	item = xmlnode_get_child(items, "item");
+	if(!item)
+		return;
+
+	data = xmlnode_get_child(item, "data");
+	if(!data)
+		return;
+
+	ns = xmlnode_get_namespace(data);
+	/* Make sure the namespace is one of the two valid possibilities */
+	if (!ns || (!g_str_equal(ns, NS_AVATAR_0_12_DATA) &&
+	            !g_str_equal(ns, NS_AVATAR_1_1_DATA)))
+		return;
+
+	checksum = xmlnode_get_attrib(item,"id");
+	if(!checksum)
+		return;
+
+	b64data = xmlnode_get_data(data);
+	if(!b64data)
+		return;
+
+	img = purple_base64_decode(b64data, &size);
+	if(!img) {
+		g_free(b64data);
+		return;
+	}
+
+	purple_buddy_icons_set_for_user(purple_connection_get_account(js->gc), from, img, size, checksum);
+	g_free(b64data);
+}
+
+static void
+update_buddy_metadata(JabberStream *js, const char *from, xmlnode *items)
+{
+	PurpleBuddy *buddy = purple_find_buddy(purple_connection_get_account(js->gc), from);
+	const char *checksum, *ns;
+	xmlnode *item, *metadata;
+	if(!buddy)
+		return;
+
+	if (!items)
+		return;
+
+	item = xmlnode_get_child(items,"item");
+	if (!item)
+		return;
+
+	metadata = xmlnode_get_child(item, "metadata");
+	if(!metadata)
+		return;
+
+	ns = xmlnode_get_namespace(metadata);
+	/* Make sure the namespace is one of the two valid possibilities */
+	if (!ns || (!g_str_equal(ns, NS_AVATAR_0_12_METADATA) &&
+	            !g_str_equal(ns, NS_AVATAR_1_1_METADATA)))
+		return;
+
+	checksum = purple_buddy_icons_get_checksum_for_user(buddy);
+
+	/* check if we have received a stop */
+	if(xmlnode_get_child(metadata, "stop")) {
+		purple_buddy_icons_set_for_user(purple_connection_get_account(js->gc), from, NULL, 0, NULL);
+	} else {
+		xmlnode *info, *goodinfo = NULL;
+		gboolean has_children = FALSE;
+
+		/* iterate over all info nodes to get one we can use */
+		for(info = metadata->child; info; info = info->next) {
+			if(info->type == XMLNODE_TYPE_TAG)
+				has_children = TRUE;
+			if(info->type == XMLNODE_TYPE_TAG && !strcmp(info->name,"info")) {
+				const char *type = xmlnode_get_attrib(info,"type");
+				const char *id = xmlnode_get_attrib(info,"id");
+
+				if(checksum && id && !strcmp(id, checksum)) {
+					/* we already have that avatar, so we don't have to do anything */
+					goodinfo = NULL;
+					break;
+				}
+				/* We'll only pick the png one for now. It's a very nice image format anyways. */
+				if(type && id && !goodinfo && !strcmp(type, "image/png"))
+					goodinfo = info;
+			}
+		}
+		if(has_children == FALSE) {
+			purple_buddy_icons_set_for_user(purple_connection_get_account(js->gc), from, NULL, 0, NULL);
+		} else if(goodinfo) {
+			const char *url = xmlnode_get_attrib(goodinfo, "url");
+			const char *id = xmlnode_get_attrib(goodinfo,"id");
+
+			/* the avatar might either be stored in a pep node, or on a HTTP(S) URL */
+			if(!url) {
+				const char *data_ns;
+				data_ns = (g_str_equal(ns, NS_AVATAR_0_12_METADATA) ?
+				               NS_AVATAR_0_12_DATA : NS_AVATAR_1_1_DATA);
+				jabber_pep_request_item(js, from, data_ns, id,
+				                        do_buddy_avatar_update_data);
+			} else {
+				PurpleUtilFetchUrlData *url_data;
+				JabberBuddyAvatarUpdateURLInfo *info = g_new0(JabberBuddyAvatarUpdateURLInfo, 1);
+				info->js = js;
+
+				url_data = purple_util_fetch_url_len(url, TRUE, NULL, TRUE,
+										  MAX_HTTP_BUDDYICON_BYTES,
+										  do_buddy_avatar_update_fromurl, info);
+				if (url_data) {
+					info->from = g_strdup(from);
+					info->id = g_strdup(id);
+					js->url_datas = g_slist_prepend(js->url_datas, url_data);
+				} else
+					g_free(info);
+
+			}
+		}
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/jabber/useravatar.h	Mon Apr 27 17:01:11 2009 +0000
@@ -0,0 +1,43 @@
+/*
+ * purple - Jabber Protocol Plugin
+ *
+ * Purple is the legal property of its developers, whose names are too numerous
+ * to list here.  Please refer to the COPYRIGHT file distributed with this
+ * source distribution.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.	 See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA	 02111-1307	 USA
+ *
+ */
+
+#ifndef _PURPLE_JABBER_USERAVATAR_H_
+#define _PURPLE_JABBER_USERAVATAR_H_
+
+#include "jabber.h"
+#include "imgstore.h"
+
+/* Implementation of XEP-0084 */
+
+#define NS_AVATAR_0_12_DATA     "http://www.xmpp.org/extensions/xep-0084.html#ns-data"
+#define NS_AVATAR_0_12_METADATA "http://www.xmpp.org/extensions/xep-0084.html#ns-metadata"
+
+#define NS_AVATAR_1_1_DATA      "urn:xmpp:avatar:data"
+#define NS_AVATAR_1_1_METADATA  "urn:xmpp:avatar:metadata"
+
+void jabber_avatar_init(void);
+void jabber_avatar_set(JabberStream *js, PurpleStoredImage *img);
+
+void jabber_avatar_fetch_mine(JabberStream *js);
+
+#endif /* _PURPLE_JABBER_USERAVATAR_H_ */