summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaul Aurich <darkrain42@pidgin.im>2009-04-26 20:51:21 +0000
committerPaul Aurich <darkrain42@pidgin.im>2009-04-26 20:51:21 +0000
commitce898405ada43000d535738fc5280553190eed4c (patch)
tree4eefa3ebf6b401d00058de2696ea92261a7d78e9
parent5d657b5ec07f105b842df3d6e1f4e8d0b241e489 (diff)
parent1ebc8f1e3331a84054c73891ca3cc47478e0502d (diff)
downloadpidgin-ce898405ada43000d535738fc5280553190eed4c.tar.gz
propagate from branch 'im.pidgin.pidgin' (head ff10d01acbb3cefe4b9ff7d849b9af40a0c4f983)
to branch 'im.pidgin.cpw.darkrain42.xmpp.avatars' (head 32a53606656bc3a235703431475f50226af62ab5)
-rw-r--r--libpurple/protocols/jabber/Makefile.am2
-rw-r--r--libpurple/protocols/jabber/buddy.c286
-rw-r--r--libpurple/protocols/jabber/buddy.h4
-rw-r--r--libpurple/protocols/jabber/disco.c16
-rw-r--r--libpurple/protocols/jabber/jabber.c14
-rw-r--r--libpurple/protocols/jabber/jabber.h1
-rw-r--r--libpurple/protocols/jabber/libxmpp.c6
-rw-r--r--libpurple/protocols/jabber/pep.c2
-rw-r--r--libpurple/protocols/jabber/presence.c16
-rw-r--r--libpurple/protocols/jabber/useravatar.c418
-rw-r--r--libpurple/protocols/jabber/useravatar.h47
11 files changed, 548 insertions, 264 deletions
diff --git a/libpurple/protocols/jabber/Makefile.am b/libpurple/protocols/jabber/Makefile.am
index bf498b4aa8..956a32f791 100644
--- a/libpurple/protocols/jabber/Makefile.am
+++ b/libpurple/protocols/jabber/Makefile.am
@@ -61,6 +61,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 0ea8372002..752dd4e54a 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;
@@ -467,137 +466,30 @@ 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*)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);
-
- 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 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);
- }
- }
-
+ PurpleAccount *account = purple_connection_get_account(gc);
+ 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));
+ jabber_set_info(gc, purple_account_get_user_info(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). */
}
/*
@@ -1172,9 +1064,8 @@ static void jabber_vcard_save_mine(JabberStream *js, const char *from,
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");
@@ -1194,10 +1085,30 @@ static void jabber_vcard_save_mine(JabberStream *js, const char *from,
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 ((!vcard_hash && js->initial_avatar_hash) ||
+ (vcard_hash && (!js->initial_avatar_hash || strcmp(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)
@@ -1445,127 +1356,6 @@ static void jabber_vcard_parse(JabberStream *js, const char *from,
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 cf0e576e17..42680de2c9 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;
@@ -103,7 +100,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 702f8b6a7d..0dacbb944a 100644
--- a/libpurple/protocols/jabber/disco.c
+++ b/libpurple/protocols/jabber/disco.c
@@ -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 @@ jabber_disco_finish_server_info_result_cb(JabberStream *js)
{
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);
diff --git a/libpurple/protocols/jabber/jabber.c b/libpurple/protocols/jabber/jabber.c
index 912a9fb2e0..b7e1a88d9c 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"
@@ -707,6 +708,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 |
@@ -747,6 +749,17 @@ jabber_login(PurpleAccount *account)
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;
@@ -1406,6 +1419,7 @@ void jabber_close(PurpleConnection *gc)
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);
diff --git a/libpurple/protocols/jabber/jabber.h b/libpurple/protocols/jabber/jabber.h
index 841c78119d..edf469bbdf 100644
--- a/libpurple/protocols/jabber/jabber.h
+++ b/libpurple/protocols/jabber/jabber.h
@@ -165,6 +165,7 @@ struct _JabberStream
gboolean registration;
+ char *initial_avatar_hash;
char *avatar_hash;
GSList *pending_avatar_requests;
diff --git a/libpurple/protocols/jabber/libxmpp.c b/libpurple/protocols/jabber/libxmpp.c
index 105ce898f4..6d30fd5fd9 100644
--- a/libpurple/protocols/jabber/libxmpp.c
+++ b/libpurple/protocols/jabber/libxmpp.c
@@ -117,6 +117,7 @@ static PurplePluginProtocolInfo prpl_info =
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 @@ init_plugin(PurplePlugin *plugin)
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
diff --git a/libpurple/protocols/jabber/pep.c b/libpurple/protocols/jabber/pep.c
index 514c1f86d3..eb89079b47 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/presence.c b/libpurple/protocols/jabber/presence.c
index 141a0d2100..0dece1bd0a 100644
--- a/libpurple/protocols/jabber/presence.c
+++ b/libpurple/protocols/jabber/presence.c
@@ -155,11 +155,19 @@ void jabber_presence_send(PurpleAccount *account, PurpleStatus *status)
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);
diff --git a/libpurple/protocols/jabber/useravatar.c b/libpurple/protocols/jabber/useravatar.c
new file mode 100644
index 0000000000..65cc523fcf
--- /dev/null
+++ b/libpurple/protocols/jabber/useravatar.c
@@ -0,0 +1,418 @@
+/*
+ * 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 *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;
+
+ /* Publish ours if it's different than the server's */
+ if ((!server_hash && js->initial_avatar_hash) ||
+ (server_hash && (!js->initial_avatar_hash || strcmp(server_hash, js->initial_avatar_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;
+
+ 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 || (strcmp(ns, NS_AVATAR_0_12_METADATA) &&
+ strcmp(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 = (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_ */