changeset 17581:5ab3c6bb95b4

Implemented receiving other people's avatars via XEP-0084. Note that this code now includes a workaround for a non-spec incompatibility for the current ejabberd PEP implementation, and doesn't use the correct namespace due to Psi using the wrong one (outdated?). Works fine though, and the vcard-based approach is retained.
author Andreas Monitzer <pidgin@monitzer.com>
date Sun, 17 Jun 2007 03:51:19 +0000
parents 7754d39d70c5
children 0f0e98978d66
files libpurple/protocols/jabber/buddy.c libpurple/protocols/jabber/buddy.h libpurple/protocols/jabber/jabber.c libpurple/protocols/jabber/libxmpp.c libpurple/protocols/jabber/pep.c libpurple/protocols/jabber/pep.h
diffstat 6 files changed, 170 insertions(+), 10 deletions(-) [+]
line wrap: on
line diff
--- a/libpurple/protocols/jabber/buddy.c	Sun Jun 17 01:16:55 2007 +0000
+++ b/libpurple/protocols/jabber/buddy.c	Sun Jun 17 03:51:19 2007 +0000
@@ -502,13 +502,13 @@
 				hash = g_strdup_printf("%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x",digest[0],digest[1],digest[2],digest[3],digest[4],digest[5],digest[6],digest[7],digest[8],digest[9],digest[10],digest[11],digest[12],digest[13],digest[14],digest[15],digest[16],digest[17],digest[18],digest[19]);
 				
 				publish = xmlnode_new("publish");
-				xmlnode_set_attrib(publish,"node","http://www.xmpp.org/extensions/xep-0084.html#ns-data");
+				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,"http://www.xmpp.org/extensions/xep-0084.html#ns-data");
+				xmlnode_set_namespace(data,AVATARNAMESPACEDATA);
 				
 				base64avatar = purple_base64_encode(purple_imgstore_get_data(img), purple_imgstore_get_size(img));
 				xmlnode_insert_data(data,base64avatar,-1);
@@ -519,13 +519,13 @@
 				
 				/* next step: publish the metadata */
 				publish = xmlnode_new("publish");
-				xmlnode_set_attrib(publish,"node","http://www.xmpp.org/extensions/xep-0084.html#ns-metadata");
+				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,"http://www.xmpp.org/extensions/xep-0084.html#ns-metadata");
+				xmlnode_set_namespace(metadata,AVATARNAMESPACEMETA);
 				
 				info = xmlnode_new_child(metadata, "info");
 				xmlnode_set_attrib(info, "id", hash);
@@ -548,12 +548,12 @@
 				/* remove the metadata */
 				xmlnode *metadata, *item;
 				xmlnode *publish = xmlnode_new("publish");
-				xmlnode_set_attrib(publish,"node","http://www.xmpp.org/extensions/xep-0084.html#ns-metadata");
+				xmlnode_set_attrib(publish,"node",AVATARNAMESPACEMETA);
 				
 				item = xmlnode_new_child(publish, "item");
 				
 				metadata = xmlnode_new_child(item, "metadata");
-				xmlnode_set_namespace(metadata,"http://www.xmpp.org/extensions/xep-0084.html#ns-metadata");
+				xmlnode_set_namespace(metadata,AVATARNAMESPACEMETA);
 				
 				xmlnode_new_child(metadata, "stop");
 				
@@ -1109,8 +1109,104 @@
 	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 *item) {
+	xmlnode *data;
+	const char *checksum;
+	char *b64data;
+	void *img;
+	size_t size;
+	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)
+		return;
+	
+	purple_buddy_icons_set_for_user(purple_connection_get_account(js->gc), from, img, size, checksum);
+}
+
 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;
+		
+		/* 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 && !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(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 {
+				JabberBuddyAvatarUpdateURLInfo *info = g_new0(JabberBuddyAvatarUpdateURLInfo, 1);
+				info->js = js;
+				info->from = g_strdup(from);
+				info->id = g_strdup(id);
+				purple_util_fetch_url(url, TRUE, NULL, TRUE, do_buddy_avatar_update_fromurl, info);
+			}
+		}
+	}
 }
 
 static void jabber_buddy_info_resource_free(gpointer data)
--- a/libpurple/protocols/jabber/buddy.h	Sun Jun 17 01:16:55 2007 +0000
+++ b/libpurple/protocols/jabber/buddy.h	Sun Jun 17 03:51:19 2007 +0000
@@ -25,6 +25,15 @@
 #include "jabber.h"
 #include "usermood.h"
 
+/* for some reason, Psi uses the first one instead of the second one as defined in the XEP */
+#ifndef USE_XEP_0084
+#define AVATARNAMESPACEDATA "http://jabber.org/protocol/avatar#data"
+#define AVATARNAMESPACEMETA "http://jabber.org/protocol/avatar#metadata"
+#else
+#define AVATARNAMESPACEDATA "http://www.xmpp.org/extensions/xep-0084.html#ns-data"
+#define AVATARNAMESPACEMETA "http://www.xmpp.org/extensions/xep-0084.html#ns-metadata"
+#endif
+
 typedef enum {
 	JABBER_BUDDY_STATE_UNKNOWN = -2,
 	JABBER_BUDDY_STATE_ERROR = -1,
--- a/libpurple/protocols/jabber/jabber.c	Sun Jun 17 01:16:55 2007 +0000
+++ b/libpurple/protocols/jabber/jabber.c	Sun Jun 17 03:51:19 2007 +0000
@@ -1966,8 +1966,4 @@
 jabber_init_plugin(PurplePlugin *plugin)
 {
 	my_protocol = plugin;
-	jabber_add_feature("avatarmeta", "http://www.xmpp.org/extensions/xep-0084.html#ns-metadata", jabber_pep_namespace_only_when_pep_enabled_cb);
-	jabber_add_feature("avatardata", "http://www.xmpp.org/extensions/xep-0084.html#ns-data", jabber_pep_namespace_only_when_pep_enabled_cb);
-	
-	jabber_pep_register_handler("avatar", "http://www.xmpp.org/extensions/xep-0084.html#ns-data", jabber_buddy_avatar_update_metadata);
 }
--- a/libpurple/protocols/jabber/libxmpp.c	Sun Jun 17 01:16:55 2007 +0000
+++ b/libpurple/protocols/jabber/libxmpp.c	Sun Jun 17 03:51:19 2007 +0000
@@ -231,6 +231,11 @@
 
         jabber_iq_init();
         jabber_pep_init();
+
+		jabber_add_feature("avatarnonxepmeta", AVATARNAMESPACEMETA, jabber_pep_namespace_only_when_pep_enabled_cb);
+		jabber_add_feature("avatarnonxepdata", AVATARNAMESPACEDATA, jabber_pep_namespace_only_when_pep_enabled_cb);
+		
+		jabber_pep_register_handler("avatarnonxep", AVATARNAMESPACEMETA, jabber_buddy_avatar_update_metadata);
 }
 
 
--- a/libpurple/protocols/jabber/pep.c	Sun Jun 17 01:16:55 2007 +0000
+++ b/libpurple/protocols/jabber/pep.c	Sun Jun 17 03:51:19 2007 +0000
@@ -46,6 +46,46 @@
 	g_hash_table_replace(pep_handlers, g_strdup(xmlns), handlerfunc);
 }
 
+static void do_pep_iq_request_item_callback(JabberStream *js, xmlnode *packet, gpointer data) {
+	const char *from = xmlnode_get_attrib(packet,"from");
+	xmlnode *pubsub = xmlnode_get_child_with_namespace(packet,"pubsub","http://jabber.org/protocol/pubsub#event");
+	xmlnode *item = NULL;
+	JabberPEPHandler *cb = data;
+	
+	if(pubsub) {
+		item = xmlnode_get_child(pubsub, "item");
+		if(!item) {
+			/* does not follow the spec, but the ejabberd PEP implementation behaves that way */
+			xmlnode *items = xmlnode_get_child(pubsub, "items");
+			if(items)
+				item = xmlnode_get_child(items, "item");
+		}
+	}
+	
+	cb(js, from, item);
+}
+
+void jabber_pep_request_item(JabberStream *js, const char *to, const char *node, const char *id, JabberPEPHandler cb) {
+	JabberIq *iq = jabber_iq_new(js, JABBER_IQ_GET);
+	xmlnode *pubsub, *items, *item;
+	
+	xmlnode_set_attrib(iq->node,"to",to);
+	pubsub = xmlnode_new_child(iq->node,"pubsub");
+	
+	xmlnode_set_namespace(pubsub,"http://jabber.org/protocol/pubsub");
+	
+	items = xmlnode_new_child(pubsub, "items");
+	xmlnode_set_attrib(items,"node",node);
+	
+	item = xmlnode_new_child(items, "item");
+	if(id)
+		xmlnode_set_attrib(item, "id", id);
+	
+	jabber_iq_set_callback(iq,do_pep_iq_request_item_callback,(gpointer)cb);
+	
+	jabber_iq_send(iq);
+}
+
 gboolean jabber_pep_namespace_only_when_pep_enabled_cb(JabberStream *js, const gchar *shortname, const gchar *namespace) {
 	return js->pep;
 }
--- a/libpurple/protocols/jabber/pep.h	Sun Jun 17 01:16:55 2007 +0000
+++ b/libpurple/protocols/jabber/pep.h	Sun Jun 17 03:51:19 2007 +0000
@@ -49,6 +49,20 @@
 void jabber_pep_register_handler(const char *shortname, const char *xmlns, JabberPEPHandler handlerfunc);
 
 /*
+ * Request a specific item from another PEP node.
+ *
+ * @parameter js	The JabberStream that should be used
+ * @parameter to	The target PEP node
+ * @parameter node	The node name of the item that is requested
+ * @parameter id	The item id of the requested item (may be NULL)
+ * @parameter cb	The callback to be used when this item is received
+ *
+ * ATTN: The items parameter of the callback will only point to the single <item> element here!
+ * Additionally, the items element passed will be NULL if any error occured (like a permission error, node doesn't exist etc.)
+ */
+void jabber_pep_request_item(JabberStream *js, const char *to, const char *node, const char *id, JabberPEPHandler cb);
+
+/*
  * Default callback that can be used for namespaces which should only be enabled when PEP is supported
  *
  * @parameter js	The JabberStream struct for this connection