# HG changeset patch # User Paul Aurich # Date 1240812865 0 # Node ID aa28018bfa177138567645e438aabfc3d4b75aea # Parent 6ab56b3dd83647b414e611b8d596e043e483774d# Parent 80437c891f924c1f5c5cefe5ff4e5c2cf8571f10 propagate from branch 'im.pidgin.pidgin' (head 0a225b2db14919b2e455f6938d28c885d6ca74e4) to branch 'im.pidgin.cpw.darkrain42.xmpp.avatars' (head 640f790180ffe11e0cca24d096f4cc182f0e0d53) diff -r 6ab56b3dd836 -r aa28018bfa17 libpurple/protocols/jabber/Makefile.am --- a/libpurple/protocols/jabber/Makefile.am Mon Apr 27 05:43:18 2009 +0000 +++ b/libpurple/protocols/jabber/Makefile.am Mon Apr 27 06:14:25 2009 +0000 @@ -61,6 +61,8 @@ adhoccommands.h \ pep.c \ pep.h \ + useravatar.c \ + useravatar.h \ usermood.c \ usermood.h \ usernick.c \ diff -r 6ab56b3dd836 -r aa28018bfa17 libpurple/protocols/jabber/buddy.c --- a/libpurple/protocols/jabber/buddy.c Mon Apr 27 05:43:18 2009 +0000 +++ b/libpurple/protocols/jabber/buddy.c Mon Apr 27 06:14:25 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; diff -r 6ab56b3dd836 -r aa28018bfa17 libpurple/protocols/jabber/buddy.h --- a/libpurple/protocols/jabber/buddy.h Mon Apr 27 05:43:18 2009 +0000 +++ b/libpurple/protocols/jabber/buddy.h Mon Apr 27 06:14:25 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); diff -r 6ab56b3dd836 -r aa28018bfa17 libpurple/protocols/jabber/disco.c --- a/libpurple/protocols/jabber/disco.c Mon Apr 27 05:43:18 2009 +0000 +++ b/libpurple/protocols/jabber/disco.c Mon Apr 27 06:14:25 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); diff -r 6ab56b3dd836 -r aa28018bfa17 libpurple/protocols/jabber/jabber.c --- a/libpurple/protocols/jabber/jabber.c Mon Apr 27 05:43:18 2009 +0000 +++ b/libpurple/protocols/jabber/jabber.c Mon Apr 27 06:14:25 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 | @@ -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; @@ -1416,6 +1428,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 +1547,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 +1888,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); } diff -r 6ab56b3dd836 -r aa28018bfa17 libpurple/protocols/jabber/jabber.h --- a/libpurple/protocols/jabber/jabber.h Mon Apr 27 05:43:18 2009 +0000 +++ b/libpurple/protocols/jabber/jabber.h Mon Apr 27 06:14:25 2009 +0000 @@ -166,6 +166,7 @@ gboolean registration; + char *initial_avatar_hash; char *avatar_hash; GSList *pending_avatar_requests; diff -r 6ab56b3dd836 -r aa28018bfa17 libpurple/protocols/jabber/libxmpp.c --- a/libpurple/protocols/jabber/libxmpp.c Mon Apr 27 05:43:18 2009 +0000 +++ b/libpurple/protocols/jabber/libxmpp.c Mon Apr 27 06:14:25 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 diff -r 6ab56b3dd836 -r aa28018bfa17 libpurple/protocols/jabber/pep.c --- a/libpurple/protocols/jabber/pep.c Mon Apr 27 05:43:18 2009 +0000 +++ b/libpurple/protocols/jabber/pep.c Mon Apr 27 06:14:25 2009 +0000 @@ -24,6 +24,7 @@ #include "pep.h" #include "iq.h" #include +#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; diff -r 6ab56b3dd836 -r aa28018bfa17 libpurple/protocols/jabber/pep.h --- a/libpurple/protocols/jabber/pep.h Mon Apr 27 05:43:18 2009 +0000 +++ b/libpurple/protocols/jabber/pep.h Mon Apr 27 06:14:25 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) * diff -r 6ab56b3dd836 -r aa28018bfa17 libpurple/protocols/jabber/presence.c --- a/libpurple/protocols/jabber/presence.c Mon Apr 27 05:43:18 2009 +0000 +++ b/libpurple/protocols/jabber/presence.c Mon Apr 27 06:14:25 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 = 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 ; 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); } } diff -r 6ab56b3dd836 -r aa28018bfa17 libpurple/protocols/jabber/useravatar.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/jabber/useravatar.c Mon Apr 27 06:14:25 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); + + } + } + } +} diff -r 6ab56b3dd836 -r aa28018bfa17 libpurple/protocols/jabber/useravatar.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/jabber/useravatar.h Mon Apr 27 06:14:25 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_ */