summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaul Aurich <darkrain42@pidgin.im>2009-02-05 01:00:17 +0000
committerPaul Aurich <darkrain42@pidgin.im>2009-02-05 01:00:17 +0000
commit2b076108d5e0ad784d0e700ca3a369eef2ff2839 (patch)
treef982abd20e0840fc758ed9cbb9feaf128c36b03a
parent71ad3f45a71fc7ba782f9e6dce44be2a153a0d51 (diff)
parent9c3663761590a539b06ffe7aa6a5c8400e6628a7 (diff)
downloadpidgin-2b076108d5e0ad784d0e700ca3a369eef2ff2839.tar.gz
propagate from branch 'im.pidgin.pidgin' (head 9cec90116395a424243dc04f682da4a41cb0dbdd)
to branch 'im.pidgin.cpw.darkrain42.xmpp.avatars' (head 48141976258f119175f90b57be6eee23247d1e11)
-rw-r--r--libpurple/protocols/jabber/Makefile.am2
-rw-r--r--libpurple/protocols/jabber/buddy.c285
-rw-r--r--libpurple/protocols/jabber/buddy.h4
-rw-r--r--libpurple/protocols/jabber/disco.c10
-rw-r--r--libpurple/protocols/jabber/jabber.c16
-rw-r--r--libpurple/protocols/jabber/libxmpp.c4
-rw-r--r--libpurple/protocols/jabber/pep.c2
-rw-r--r--libpurple/protocols/jabber/useravatar.c409
-rw-r--r--libpurple/protocols/jabber/useravatar.h47
9 files changed, 517 insertions, 262 deletions
diff --git a/libpurple/protocols/jabber/Makefile.am b/libpurple/protocols/jabber/Makefile.am
index 5f46e374a9..645be7e52b 100644
--- a/libpurple/protocols/jabber/Makefile.am
+++ b/libpurple/protocols/jabber/Makefile.am
@@ -45,6 +45,8 @@ JABBERSOURCES = auth.c \
adhoccommands.h \
pep.c \
pep.h \
+ useravatar.c \
+ useravatar.h \
usermood.c \
usermood.h \
usernick.c \
diff --git a/libpurple/protocols/jabber/buddy.c b/libpurple/protocols/jabber/buddy.c
index c91f27c8f0..39554e04e7 100644
--- a/libpurple/protocols/jabber/buddy.c
+++ b/libpurple/protocols/jabber/buddy.c
@@ -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;
@@ -473,7 +472,6 @@ void jabber_set_info(PurpleConnection *gc, const char *info)
enc = purple_base64_encode(avatar_data, avatar_len);
js->avatar_hash = jabber_calculate_data_sha1sum(avatar_data, avatar_len);
-
xmlnode_insert_data(binval, enc, -1);
g_free(enc);
} else if (vc_node) {
@@ -485,137 +483,24 @@ void jabber_set_info(PurpleConnection *gc, const char *info)
}
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*)gc->proto_data)->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);
-
- 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*)gc->proto_data, 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 metadata */
- jabber_pep_publish((JabberStream*)gc->proto_data, 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);
- }
- }
-
+ jabber_avatar_set(gc->proto_data, img, NULL);
/* 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);
}
/*
@@ -1157,9 +1042,9 @@ static void jabber_buddy_info_remove_id(JabberBuddyInfo *jbi, const char *id)
static void jabber_vcard_save_mine(JabberStream *js, xmlnode *packet, gpointer data)
{
- xmlnode *vcard;
- char *txt;
- PurpleStoredImage *img;
+ xmlnode *vcard, *photo, *binval;
+ char *txt, *vcard_hash = NULL;
+ const char *current_hash;
if((vcard = xmlnode_get_child(packet, "vCard")) ||
(vcard = xmlnode_get_child_with_namespace(packet, "query", "vcard-temp")))
@@ -1174,10 +1059,33 @@ static void jabber_vcard_save_mine(JabberStream *js, xmlnode *packet, gpointer d
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);
+ }
+ }
+
+ current_hash = purple_account_get_string(js->gc->account,
+ "prpl-jabber_icon_checksum", "");
+
+ /* Republish our vcard if the photo is different than the server's */
+ if ((!vcard_hash && current_hash[0] != '\0') ||
+ (vcard_hash && strcmp(vcard_hash, current_hash))) {
+ PurpleAccount *account = purple_connection_get_account(js->gc);
+ jabber_set_info(js->gc, purple_account_get_user_info(account));
+ } else if (current_hash != '\0') {
+ /* Our photo is in the vcard, so advertise vcard-temp updates */
+ js->avatar_hash = g_strdup(current_hash);
}
+
+ g_free(vcard_hash);
}
void jabber_vcard_fetch_mine(JabberStream *js)
@@ -1427,127 +1335,6 @@ static void jabber_vcard_parse(JabberStream *js, xmlnode *packet, gpointer data)
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;
diff --git a/libpurple/protocols/jabber/buddy.h b/libpurple/protocols/jabber/buddy.h
index 0eb49b7c50..3c74366127 100644
--- a/libpurple/protocols/jabber/buddy.h
+++ b/libpurple/protocols/jabber/buddy.h
@@ -36,9 +36,6 @@ typedef enum {
#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;
@@ -102,7 +99,6 @@ GList *jabber_blist_node_menu(PurpleBlistNode *node);
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);
diff --git a/libpurple/protocols/jabber/disco.c b/libpurple/protocols/jabber/disco.c
index 71f87cffa6..ace22c9ced 100644
--- a/libpurple/protocols/jabber/disco.c
+++ b/libpurple/protocols/jabber/disco.c
@@ -23,16 +23,16 @@
#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 "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,6 +341,8 @@ jabber_disco_finish_server_info_result_cb(JabberStream *js)
const char *ft_proxies;
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 */
diff --git a/libpurple/protocols/jabber/jabber.c b/libpurple/protocols/jabber/jabber.c
index a105b93a00..479d0dddba 100644
--- a/libpurple/protocols/jabber/jabber.c
+++ b/libpurple/protocols/jabber/jabber.c
@@ -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"
@@ -701,6 +702,7 @@ jabber_login(PurpleAccount *account)
const char *connect_server = purple_account_get_string(account,
"connect_server", "");
JabberStream *js;
+ PurpleStoredImage *image;
JabberBuddy *my_jb = NULL;
gc->flags |= PURPLE_CONNECTION_HTML |
@@ -736,7 +738,19 @@ jabber_login(PurpleAccount *account)
_("Invalid XMPP ID. Domain must be set."));
return;
}
-
+
+ /* This account setting is used to determine if we should re-sync our avatar to the
+ * server at login. */
+ if ((image = purple_buddy_icons_find_account_icon(account))) {
+ char *checksum = jabber_calculate_data_sha1sum(purple_imgstore_get_data(image),
+ purple_imgstore_get_size(image));
+ purple_account_set_string(account, "prpl-jabber_icon_checksum", checksum);
+ g_free(checksum);
+ purple_imgstore_unref(image);
+ } else {
+ purple_account_set_string(account, "prpl-jabber_icon_checksum", "");
+ }
+
if((my_jb = jabber_buddy_find(js, purple_account_get_username(account), TRUE)))
my_jb->subscription |= JABBER_SUB_BOTH;
diff --git a/libpurple/protocols/jabber/libxmpp.c b/libpurple/protocols/jabber/libxmpp.c
index cc8c408320..7b170580cf 100644
--- a/libpurple/protocols/jabber/libxmpp.c
+++ b/libpurple/protocols/jabber/libxmpp.c
@@ -281,14 +281,10 @@ init_plugin(PurplePlugin *plugin)
jabber_data_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", "http://www.xmpp.org/extensions/xep-0224.html#ns",
jabber_buzz_isenabled);
jabber_add_feature("bob", XEP_0231_NAMESPACE,
jabber_custom_smileys_isenabled);
-
- jabber_pep_register_handler("avatar", AVATARNAMESPACEMETA, jabber_buddy_avatar_update_metadata);
}
diff --git a/libpurple/protocols/jabber/pep.c b/libpurple/protocols/jabber/pep.c
index fae2223b63..d2d35255ec 100644
--- a/libpurple/protocols/jabber/pep.c
+++ b/libpurple/protocols/jabber/pep.c
@@ -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 @@ void jabber_pep_init(void) {
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();
}
diff --git a/libpurple/protocols/jabber/useravatar.c b/libpurple/protocols/jabber/useravatar.c
new file mode 100644
index 0000000000..ad8b6992be
--- /dev/null
+++ b/libpurple/protocols/jabber/useravatar.c
@@ -0,0 +1,409 @@
+/*
+ * 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_add_feature("avatarmeta", NS_AVATAR_0_12_METADATA,
+ jabber_pep_namespace_only_when_pep_enabled_cb);
+ jabber_add_feature("avatardata", NS_AVATAR_0_12_DATA,
+ jabber_pep_namespace_only_when_pep_enabled_cb);
+ 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);
+}
+
+void jabber_avatar_set(JabberStream *js, PurpleStoredImage *img, const char *ns)
+{
+ xmlnode *publish, *metadata, *item;
+
+ if (!js->pep)
+ return;
+
+ if (!img) {
+ if (ns == NULL || !strcmp(ns, NS_AVATAR_0_12_METADATA)) {
+ /* remove the metadata */
+ publish = xmlnode_new("publish");
+ xmlnode_set_attrib(publish, "node", NS_AVATAR_0_12_METADATA);
+
+ item = xmlnode_new_child(publish, "item");
+ metadata = xmlnode_new_child(item, "metadata");
+ xmlnode_set_namespace(metadata, NS_AVATAR_0_12_METADATA);
+
+ xmlnode_new_child(metadata, "stop");
+ /* publish */
+ jabber_pep_publish(js, publish);
+ }
+
+ if (ns == NULL || !strcmp(ns, NS_AVATAR_1_1_METADATA)) {
+ /* Now for the XEP-0084 v1.1 namespace, where we publish an empty
+ * metadata node instead of a <stop/> element */
+ 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 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 *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));
+
+ if (ns == NULL || !strcmp(ns, NS_AVATAR_0_12_METADATA)) {
+ publish = xmlnode_new("publish");
+ xmlnode_set_attrib(publish, "node", NS_AVATAR_0_12_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_0_12_DATA);
+
+ xmlnode_insert_data(data, base64avatar, -1);
+ /* publish the avatar itself */
+ jabber_pep_publish(js, publish);
+ }
+
+ if (ns == NULL || !strcmp(ns, NS_AVATAR_1_1_METADATA)) {
+ 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);
+
+ /* next step: publish the metadata to the old namespace */
+ if (ns == NULL || !strcmp(ns, NS_AVATAR_0_12_METADATA)) {
+ publish = xmlnode_new("publish");
+ xmlnode_set_attrib(publish, "node", NS_AVATAR_0_12_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_0_12_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);
+ /* publish the metadata */
+ jabber_pep_publish(js, publish);
+ }
+
+ if (ns == NULL || !strcmp(ns, NS_AVATAR_1_1_METADATA)) {
+ /* publish the metadata to the new namespace */
+ 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 *current_hash = purple_account_get_string(account, "prpl-jabber_icon_checksum", "");
+ 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");
+ }
+
+ ns = xmlnode_get_namespace(metadata);
+ if (!ns)
+ return;
+
+ /* Publish ours if it's different than the server's */
+ if ((!server_hash && current_hash[0] != '\0') ||
+ (server_hash && strcmp(server_hash, current_hash))) {
+ PurpleStoredImage *img = purple_buddy_icons_find_account_icon(account);
+ jabber_avatar_set(js, img, ns);
+ 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 || (strcmp(ns, NS_AVATAR_0_12_DATA) &&
+ strcmp(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;
+
+ checksum = purple_buddy_icons_get_checksum_for_user(buddy);
+ item = xmlnode_get_child(items,"item");
+ 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 || (strcmp(ns, NS_AVATAR_0_12_METADATA) &&
+ strcmp(ns, NS_AVATAR_1_1_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(S) URL */
+ if(!url) {
+ const char *data_ns;
+ data_ns = (strcmp(ns, NS_AVATAR_0_12_METADATA) == 0 ?
+ 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);
+
+ }
+ }
+ }
+}
diff --git a/libpurple/protocols/jabber/useravatar.h b/libpurple/protocols/jabber/useravatar.h
new file mode 100644
index 0000000000..0865ae2dd7
--- /dev/null
+++ b/libpurple/protocols/jabber/useravatar.h
@@ -0,0 +1,47 @@
+/*
+ * 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);
+/**
+ * @param ns The metadata namespace for which to set the avatar or NULL to set the
+ * avatar for both namespaces.
+ */
+void jabber_avatar_set(JabberStream *js, PurpleStoredImage *img, const char *ns);
+
+void jabber_avatar_fetch_mine(JabberStream *js);
+
+#endif /* _PURPLE_JABBER_USERAVATAR_H_ */