2
* purple - Jabber Protocol Plugin
4
* Purple is the legal property of its developers, whose names are too numerous
5
* to list here. Please refer to the COPYRIGHT file distributed with this
8
* This program is free software; you can redistribute it and/or modify
9
* it under the terms of the GNU General Public License as published by
10
* the Free Software Foundation; either version 2 of the License, or
11
* (at your option) any later version.
13
* This program is distributed in the hope that it will be useful,
14
* but WITHOUT ANY WARRANTY; without even the implied warranty of
15
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16
* GNU General Public License for more details.
18
* You should have received a copy of the GNU General Public License
19
* along with this program; if not, write to the Free Software
20
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
26
#include "useravatar.h"
30
#define MAX_HTTP_BUDDYICON_BYTES (200 * 1024)
32
static void update_buddy_metadata(JabberStream *js, const char *from, xmlnode *items);
34
void jabber_avatar_init(void)
36
jabber_add_feature(NS_AVATAR_1_1_METADATA,
37
jabber_pep_namespace_only_when_pep_enabled_cb);
38
jabber_add_feature(NS_AVATAR_1_1_DATA,
39
jabber_pep_namespace_only_when_pep_enabled_cb);
41
jabber_pep_register_handler(NS_AVATAR_1_1_METADATA,
42
update_buddy_metadata);
46
remove_avatar_0_12_nodes(JabberStream *js)
49
/* See note below for why this is #if 0'd */
51
/* Publish an empty avatar according to the XEP-0084 v0.12 semantics */
52
xmlnode *publish, *item, *metadata;
53
/* publish the metadata */
54
publish = xmlnode_new("publish");
55
xmlnode_set_attrib(publish, "node", NS_AVATAR_0_12_METADATA);
57
item = xmlnode_new_child(publish, "item");
58
xmlnode_set_attrib(item, "id", "stop");
60
metadata = xmlnode_new_child(item, "metadata");
61
xmlnode_set_namespace(metadata, NS_AVATAR_0_12_METADATA);
63
xmlnode_new_child(metadata, "stop");
66
jabber_pep_publish(js, publish);
70
* This causes ejabberd 2.0.0 to kill the connection unceremoniously.
71
* See https://support.process-one.net/browse/EJAB-623. When adiumx.com
72
* was upgraded, the issue went away.
74
* I think it makes a lot of sense to not have an avatar at the old
75
* node instead of having something interpreted as "no avatar". When
76
* a contact with an older client logs in, in the latter situation,
77
* there's a race between interpreting the <presence/> vcard-temp:x:update
78
* avatar (non-empty) and the XEP-0084 v0.12 avatar (empty, so show no
79
* avatar for the buddy) which leads to unhappy and confused users.
81
* A deluge of frustrating "Read error" bug reports may change my mind
85
jabber_pep_delete_node(js, NS_AVATAR_0_12_METADATA);
86
jabber_pep_delete_node(js, NS_AVATAR_0_12_DATA);
89
void jabber_avatar_set(JabberStream *js, PurpleStoredImage *img)
91
xmlnode *publish, *metadata, *item;
96
/* Hmmm, not sure if this is worth the traffic, but meh */
97
remove_avatar_0_12_nodes(js);
100
publish = xmlnode_new("publish");
101
xmlnode_set_attrib(publish, "node", NS_AVATAR_1_1_METADATA);
103
item = xmlnode_new_child(publish, "item");
104
metadata = xmlnode_new_child(item, "metadata");
105
xmlnode_set_namespace(metadata, NS_AVATAR_1_1_METADATA);
108
jabber_pep_publish(js, publish);
111
* TODO: This is pretty gross. The Jabber PRPL really shouldn't
112
* do voodoo to try to determine the image type, height
115
/* A PNG header, including the IHDR, but nothing else */
117
guchar signature[8]; /* must be hex 89 50 4E 47 0D 0A 1A 0A */
119
guint32 length; /* must be 0x0d */
120
guchar type[4]; /* must be 'I' 'H' 'D' 'R' */
129
} *png = purple_imgstore_get_data(img); /* ATTN: this is in network byte order! */
131
/* check if the data is a valid png file (well, at least to some extent) */
132
if(png->signature[0] == 0x89 &&
133
png->signature[1] == 0x50 &&
134
png->signature[2] == 0x4e &&
135
png->signature[3] == 0x47 &&
136
png->signature[4] == 0x0d &&
137
png->signature[5] == 0x0a &&
138
png->signature[6] == 0x1a &&
139
png->signature[7] == 0x0a &&
140
ntohl(png->ihdr.length) == 0x0d &&
141
png->ihdr.type[0] == 'I' &&
142
png->ihdr.type[1] == 'H' &&
143
png->ihdr.type[2] == 'D' &&
144
png->ihdr.type[3] == 'R') {
145
/* parse PNG header to get the size of the image (yes, this is required) */
146
guint32 width = ntohl(png->ihdr.width);
147
guint32 height = ntohl(png->ihdr.height);
148
xmlnode *data, *info;
149
char *lengthstring, *widthstring, *heightstring;
151
/* compute the sha1 hash */
152
char *hash = jabber_calculate_data_sha1sum(purple_imgstore_get_data(img),
153
purple_imgstore_get_size(img));
154
char *base64avatar = purple_base64_encode(purple_imgstore_get_data(img),
155
purple_imgstore_get_size(img));
157
publish = xmlnode_new("publish");
158
xmlnode_set_attrib(publish, "node", NS_AVATAR_1_1_DATA);
160
item = xmlnode_new_child(publish, "item");
161
xmlnode_set_attrib(item, "id", hash);
163
data = xmlnode_new_child(item, "data");
164
xmlnode_set_namespace(data, NS_AVATAR_1_1_DATA);
166
xmlnode_insert_data(data, base64avatar, -1);
167
/* publish the avatar itself */
168
jabber_pep_publish(js, publish);
170
g_free(base64avatar);
172
lengthstring = g_strdup_printf("%" G_GSIZE_FORMAT,
173
purple_imgstore_get_size(img));
174
widthstring = g_strdup_printf("%u", width);
175
heightstring = g_strdup_printf("%u", height);
177
/* publish the metadata */
178
publish = xmlnode_new("publish");
179
xmlnode_set_attrib(publish, "node", NS_AVATAR_1_1_METADATA);
181
item = xmlnode_new_child(publish, "item");
182
xmlnode_set_attrib(item, "id", hash);
184
metadata = xmlnode_new_child(item, "metadata");
185
xmlnode_set_namespace(metadata, NS_AVATAR_1_1_METADATA);
187
info = xmlnode_new_child(metadata, "info");
188
xmlnode_set_attrib(info, "id", hash);
189
xmlnode_set_attrib(info, "type", "image/png");
190
xmlnode_set_attrib(info, "bytes", lengthstring);
191
xmlnode_set_attrib(info, "width", widthstring);
192
xmlnode_set_attrib(info, "height", heightstring);
194
jabber_pep_publish(js, publish);
196
g_free(lengthstring);
198
g_free(heightstring);
201
purple_debug_error("jabber", "Cannot set PEP avatar to non-PNG data\n");
207
do_got_own_avatar_0_12_cb(JabberStream *js, const char *from, xmlnode *items)
210
/* It wasn't an error (i.e. 'item-not-found') */
211
remove_avatar_0_12_nodes(js);
215
do_got_own_avatar_cb(JabberStream *js, const char *from, xmlnode *items)
217
xmlnode *item = NULL, *metadata = NULL, *info = NULL;
218
PurpleAccount *account = purple_connection_get_account(js->gc);
219
const char *server_hash = NULL;
221
if (items && (item = xmlnode_get_child(items, "item")) &&
222
(metadata = xmlnode_get_child(item, "metadata")) &&
223
(info = xmlnode_get_child(metadata, "info"))) {
224
server_hash = xmlnode_get_attrib(info, "id");
228
* If we have an avatar and the server returned an error/malformed data,
229
* push our avatar. If the server avatar doesn't match the local one, push
232
if (((!items || !metadata) && js->initial_avatar_hash) ||
233
!purple_strequal(server_hash, js->initial_avatar_hash)) {
234
PurpleStoredImage *img = purple_buddy_icons_find_account_icon(account);
235
jabber_avatar_set(js, img);
236
purple_imgstore_unref(img);
240
void jabber_avatar_fetch_mine(JabberStream *js)
242
char *jid = g_strdup_printf("%s@%s", js->user->node, js->user->domain);
243
jabber_pep_request_item(js, jid, NS_AVATAR_0_12_METADATA, NULL,
244
do_got_own_avatar_0_12_cb);
245
jabber_pep_request_item(js, jid, NS_AVATAR_1_1_METADATA, NULL,
246
do_got_own_avatar_cb);
250
typedef struct _JabberBuddyAvatarUpdateURLInfo {
254
} JabberBuddyAvatarUpdateURLInfo;
257
do_buddy_avatar_update_fromurl(PurpleUtilFetchUrlData *url_data,
258
gpointer user_data, const gchar *url_text,
259
gsize len, const gchar *error_message)
261
JabberBuddyAvatarUpdateURLInfo *info = user_data;
265
purple_debug(PURPLE_DEBUG_ERROR, "jabber",
266
"do_buddy_avatar_update_fromurl got error \"%s\"",
271
icon_data = g_memdup(url_text, len);
272
purple_buddy_icons_set_for_user(purple_connection_get_account(info->js->gc), info->from, icon_data, len, info->id);
281
do_buddy_avatar_update_data(JabberStream *js, const char *from, xmlnode *items)
283
xmlnode *item, *data;
284
const char *checksum;
291
item = xmlnode_get_child(items, "item");
295
data = xmlnode_get_child(item, "data");
299
checksum = xmlnode_get_attrib(item,"id");
303
b64data = xmlnode_get_data(data);
307
img = purple_base64_decode(b64data, &size);
313
purple_buddy_icons_set_for_user(purple_connection_get_account(js->gc), from, img, size, checksum);
318
update_buddy_metadata(JabberStream *js, const char *from, xmlnode *items)
320
PurpleBuddy *buddy = purple_find_buddy(purple_connection_get_account(js->gc), from);
321
const char *checksum;
322
xmlnode *item, *metadata;
329
item = xmlnode_get_child(items,"item");
333
metadata = xmlnode_get_child(item, "metadata");
337
checksum = purple_buddy_icons_get_checksum_for_user(buddy);
339
/* <stop/> was the pre-v1.1 method of publishing an empty avatar */
340
if(xmlnode_get_child(metadata, "stop")) {
341
purple_buddy_icons_set_for_user(purple_connection_get_account(js->gc), from, NULL, 0, NULL);
343
xmlnode *info, *goodinfo = NULL;
344
gboolean has_children = FALSE;
346
/* iterate over all info nodes to get one we can use */
347
for(info = metadata->child; info; info = info->next) {
348
if(info->type == XMLNODE_TYPE_TAG)
350
if(info->type == XMLNODE_TYPE_TAG && !strcmp(info->name,"info")) {
351
const char *type = xmlnode_get_attrib(info,"type");
352
const char *id = xmlnode_get_attrib(info,"id");
354
if(checksum && id && !strcmp(id, checksum)) {
355
/* we already have that avatar, so we don't have to do anything */
359
/* We'll only pick the png one for now. It's a very nice image format anyways. */
360
if(type && id && !goodinfo && !strcmp(type, "image/png"))
364
if(has_children == FALSE) {
365
purple_buddy_icons_set_for_user(purple_connection_get_account(js->gc), from, NULL, 0, NULL);
366
} else if(goodinfo) {
367
const char *url = xmlnode_get_attrib(goodinfo, "url");
368
const char *id = xmlnode_get_attrib(goodinfo,"id");
370
/* the avatar might either be stored in a pep node, or on a HTTP(S) URL */
372
jabber_pep_request_item(js, from, NS_AVATAR_1_1_DATA, id,
373
do_buddy_avatar_update_data);
375
PurpleUtilFetchUrlData *url_data;
376
JabberBuddyAvatarUpdateURLInfo *info = g_new0(JabberBuddyAvatarUpdateURLInfo, 1);
379
url_data = purple_util_fetch_url_len(url, TRUE, NULL, TRUE,
380
MAX_HTTP_BUDDYICON_BYTES,
381
do_buddy_avatar_update_fromurl, info);
383
info->from = g_strdup(from);
384
info->id = g_strdup(id);
385
js->url_datas = g_slist_prepend(js->url_datas, url_data);