comparison libpurple/protocols/jabber/useravatar.c @ 26735:3912f55a1633

propagate from branch 'im.pidgin.pidgin' (head fbb4fe5da444943eecc76bdcd6c8ba967790b6c8) to branch 'im.pidgin.cpw.darkrain42.xmpp.bosh' (head 601bc627c9430320848361f0ed81c6c4c6ee53e0)
author Paul Aurich <paul@darkrain42.org>
date Tue, 28 Apr 2009 18:43:57 +0000
parents 80437c891f92
children a0e48796defb
comparison
equal deleted inserted replaced
26661:de9816c970fe 26735:3912f55a1633
1 /*
2 * purple - Jabber Protocol Plugin
3 *
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
6 * source distribution.
7 *
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.
12 *
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.
17 *
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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
21 *
22 */
23
24 #include "internal.h"
25
26 #include "useravatar.h"
27 #include "pep.h"
28 #include "debug.h"
29
30 #define MAX_HTTP_BUDDYICON_BYTES (200 * 1024)
31
32 static void update_buddy_metadata(JabberStream *js, const char *from, xmlnode *items);
33
34 void jabber_avatar_init(void)
35 {
36 jabber_pep_register_handler("avatar", NS_AVATAR_0_12_METADATA,
37 update_buddy_metadata);
38
39 jabber_add_feature("urn_avatarmeta", NS_AVATAR_1_1_METADATA,
40 jabber_pep_namespace_only_when_pep_enabled_cb);
41 jabber_add_feature("urn_avatardata", NS_AVATAR_1_1_DATA,
42 jabber_pep_namespace_only_when_pep_enabled_cb);
43
44 jabber_pep_register_handler("urn_avatar", NS_AVATAR_1_1_METADATA,
45 update_buddy_metadata);
46 }
47
48 static void
49 remove_avatar_0_12_nodes(JabberStream *js)
50 {
51 jabber_pep_delete_node(js, NS_AVATAR_0_12_METADATA);
52 jabber_pep_delete_node(js, NS_AVATAR_0_12_DATA);
53 }
54
55 void jabber_avatar_set(JabberStream *js, PurpleStoredImage *img)
56 {
57 xmlnode *publish, *metadata, *item;
58
59 if (!js->pep)
60 return;
61
62 remove_avatar_0_12_nodes(js);
63
64 if (!img) {
65 publish = xmlnode_new("publish");
66 xmlnode_set_attrib(publish, "node", NS_AVATAR_1_1_METADATA);
67
68 item = xmlnode_new_child(publish, "item");
69 metadata = xmlnode_new_child(item, "metadata");
70 xmlnode_set_namespace(metadata, NS_AVATAR_1_1_METADATA);
71
72 /* publish */
73 jabber_pep_publish(js, publish);
74 } else {
75 /*
76 * TODO: This is pretty gross. The Jabber PRPL really shouldn't
77 * do voodoo to try to determine the image type, height
78 * and width.
79 */
80 /* A PNG header, including the IHDR, but nothing else */
81 const struct {
82 guchar signature[8]; /* must be hex 89 50 4E 47 0D 0A 1A 0A */
83 struct {
84 guint32 length; /* must be 0x0d */
85 guchar type[4]; /* must be 'I' 'H' 'D' 'R' */
86 guint32 width;
87 guint32 height;
88 guchar bitdepth;
89 guchar colortype;
90 guchar compression;
91 guchar filter;
92 guchar interlace;
93 } ihdr;
94 } *png = purple_imgstore_get_data(img); /* ATTN: this is in network byte order! */
95
96 /* check if the data is a valid png file (well, at least to some extent) */
97 if(png->signature[0] == 0x89 &&
98 png->signature[1] == 0x50 &&
99 png->signature[2] == 0x4e &&
100 png->signature[3] == 0x47 &&
101 png->signature[4] == 0x0d &&
102 png->signature[5] == 0x0a &&
103 png->signature[6] == 0x1a &&
104 png->signature[7] == 0x0a &&
105 ntohl(png->ihdr.length) == 0x0d &&
106 png->ihdr.type[0] == 'I' &&
107 png->ihdr.type[1] == 'H' &&
108 png->ihdr.type[2] == 'D' &&
109 png->ihdr.type[3] == 'R') {
110 /* parse PNG header to get the size of the image (yes, this is required) */
111 guint32 width = ntohl(png->ihdr.width);
112 guint32 height = ntohl(png->ihdr.height);
113 xmlnode *data, *info;
114 char *lengthstring, *widthstring, *heightstring;
115
116 /* compute the sha1 hash */
117 char *hash = jabber_calculate_data_sha1sum(purple_imgstore_get_data(img),
118 purple_imgstore_get_size(img));
119 char *base64avatar = purple_base64_encode(purple_imgstore_get_data(img),
120 purple_imgstore_get_size(img));
121
122 publish = xmlnode_new("publish");
123 xmlnode_set_attrib(publish, "node", NS_AVATAR_1_1_DATA);
124
125 item = xmlnode_new_child(publish, "item");
126 xmlnode_set_attrib(item, "id", hash);
127
128 data = xmlnode_new_child(item, "data");
129 xmlnode_set_namespace(data, NS_AVATAR_1_1_DATA);
130
131 xmlnode_insert_data(data, base64avatar, -1);
132 /* publish the avatar itself */
133 jabber_pep_publish(js, publish);
134
135 g_free(base64avatar);
136
137 lengthstring = g_strdup_printf("%" G_GSIZE_FORMAT,
138 purple_imgstore_get_size(img));
139 widthstring = g_strdup_printf("%u", width);
140 heightstring = g_strdup_printf("%u", height);
141
142 /* publish the metadata */
143 publish = xmlnode_new("publish");
144 xmlnode_set_attrib(publish, "node", NS_AVATAR_1_1_METADATA);
145
146 item = xmlnode_new_child(publish, "item");
147 xmlnode_set_attrib(item, "id", hash);
148
149 metadata = xmlnode_new_child(item, "metadata");
150 xmlnode_set_namespace(metadata, NS_AVATAR_1_1_METADATA);
151
152 info = xmlnode_new_child(metadata, "info");
153 xmlnode_set_attrib(info, "id", hash);
154 xmlnode_set_attrib(info, "type", "image/png");
155 xmlnode_set_attrib(info, "bytes", lengthstring);
156 xmlnode_set_attrib(info, "width", widthstring);
157 xmlnode_set_attrib(info, "height", heightstring);
158
159 jabber_pep_publish(js, publish);
160
161 g_free(lengthstring);
162 g_free(widthstring);
163 g_free(heightstring);
164 g_free(hash);
165 } else {
166 purple_debug_error("jabber", "Cannot set PEP avatar to non-PNG data\n");
167 }
168 }
169 }
170
171 static void
172 do_got_own_avatar_cb(JabberStream *js, const char *from, xmlnode *items)
173 {
174 xmlnode *item = NULL, *metadata = NULL, *info = NULL;
175 PurpleAccount *account = purple_connection_get_account(js->gc);
176 const char *server_hash = NULL;
177 const char *ns;
178
179 if ((item = xmlnode_get_child(items, "item")) &&
180 (metadata = xmlnode_get_child(item, "metadata")) &&
181 (info = xmlnode_get_child(metadata, "info"))) {
182 server_hash = xmlnode_get_attrib(info, "id");
183 }
184
185 if (!metadata)
186 return;
187
188 ns = xmlnode_get_namespace(metadata);
189 if (!ns)
190 return;
191
192 /*
193 * We no longer publish avatars to the older namespace. If there is one
194 * there, delete it.
195 */
196 if (g_str_equal(ns, NS_AVATAR_0_12_METADATA) && server_hash) {
197 remove_avatar_0_12_nodes(js);
198 return;
199 }
200
201 /* Publish ours if it's different than the server's */
202 if (!purple_strequal(server_hash, js->initial_avatar_hash)) {
203 PurpleStoredImage *img = purple_buddy_icons_find_account_icon(account);
204 jabber_avatar_set(js, img);
205 purple_imgstore_unref(img);
206 }
207 }
208
209 void jabber_avatar_fetch_mine(JabberStream *js)
210 {
211 char *jid = g_strdup_printf("%s@%s", js->user->node, js->user->domain);
212 jabber_pep_request_item(js, jid, NS_AVATAR_0_12_METADATA, NULL,
213 do_got_own_avatar_cb);
214 jabber_pep_request_item(js, jid, NS_AVATAR_1_1_METADATA, NULL,
215 do_got_own_avatar_cb);
216 g_free(jid);
217 }
218
219 typedef struct _JabberBuddyAvatarUpdateURLInfo {
220 JabberStream *js;
221 char *from;
222 char *id;
223 } JabberBuddyAvatarUpdateURLInfo;
224
225 static void
226 do_buddy_avatar_update_fromurl(PurpleUtilFetchUrlData *url_data,
227 gpointer user_data, const gchar *url_text,
228 gsize len, const gchar *error_message)
229 {
230 JabberBuddyAvatarUpdateURLInfo *info = user_data;
231 if(!url_text) {
232 purple_debug(PURPLE_DEBUG_ERROR, "jabber",
233 "do_buddy_avatar_update_fromurl got error \"%s\"",
234 error_message);
235 goto out;
236 }
237
238 purple_buddy_icons_set_for_user(purple_connection_get_account(info->js->gc), info->from, (void*)url_text, len, info->id);
239
240 out:
241 g_free(info->from);
242 g_free(info->id);
243 g_free(info);
244 }
245
246 static void
247 do_buddy_avatar_update_data(JabberStream *js, const char *from, xmlnode *items)
248 {
249 xmlnode *item, *data;
250 const char *checksum, *ns;
251 char *b64data;
252 void *img;
253 size_t size;
254 if(!items)
255 return;
256
257 item = xmlnode_get_child(items, "item");
258 if(!item)
259 return;
260
261 data = xmlnode_get_child(item, "data");
262 if(!data)
263 return;
264
265 ns = xmlnode_get_namespace(data);
266 /* Make sure the namespace is one of the two valid possibilities */
267 if (!ns || (!g_str_equal(ns, NS_AVATAR_0_12_DATA) &&
268 !g_str_equal(ns, NS_AVATAR_1_1_DATA)))
269 return;
270
271 checksum = xmlnode_get_attrib(item,"id");
272 if(!checksum)
273 return;
274
275 b64data = xmlnode_get_data(data);
276 if(!b64data)
277 return;
278
279 img = purple_base64_decode(b64data, &size);
280 if(!img) {
281 g_free(b64data);
282 return;
283 }
284
285 purple_buddy_icons_set_for_user(purple_connection_get_account(js->gc), from, img, size, checksum);
286 g_free(b64data);
287 }
288
289 static void
290 update_buddy_metadata(JabberStream *js, const char *from, xmlnode *items)
291 {
292 PurpleBuddy *buddy = purple_find_buddy(purple_connection_get_account(js->gc), from);
293 const char *checksum, *ns;
294 xmlnode *item, *metadata;
295 if(!buddy)
296 return;
297
298 if (!items)
299 return;
300
301 item = xmlnode_get_child(items,"item");
302 if (!item)
303 return;
304
305 metadata = xmlnode_get_child(item, "metadata");
306 if(!metadata)
307 return;
308
309 ns = xmlnode_get_namespace(metadata);
310 /* Make sure the namespace is one of the two valid possibilities */
311 if (!ns || (!g_str_equal(ns, NS_AVATAR_0_12_METADATA) &&
312 !g_str_equal(ns, NS_AVATAR_1_1_METADATA)))
313 return;
314
315 checksum = purple_buddy_icons_get_checksum_for_user(buddy);
316
317 /* check if we have received a stop */
318 if(xmlnode_get_child(metadata, "stop")) {
319 purple_buddy_icons_set_for_user(purple_connection_get_account(js->gc), from, NULL, 0, NULL);
320 } else {
321 xmlnode *info, *goodinfo = NULL;
322 gboolean has_children = FALSE;
323
324 /* iterate over all info nodes to get one we can use */
325 for(info = metadata->child; info; info = info->next) {
326 if(info->type == XMLNODE_TYPE_TAG)
327 has_children = TRUE;
328 if(info->type == XMLNODE_TYPE_TAG && !strcmp(info->name,"info")) {
329 const char *type = xmlnode_get_attrib(info,"type");
330 const char *id = xmlnode_get_attrib(info,"id");
331
332 if(checksum && id && !strcmp(id, checksum)) {
333 /* we already have that avatar, so we don't have to do anything */
334 goodinfo = NULL;
335 break;
336 }
337 /* We'll only pick the png one for now. It's a very nice image format anyways. */
338 if(type && id && !goodinfo && !strcmp(type, "image/png"))
339 goodinfo = info;
340 }
341 }
342 if(has_children == FALSE) {
343 purple_buddy_icons_set_for_user(purple_connection_get_account(js->gc), from, NULL, 0, NULL);
344 } else if(goodinfo) {
345 const char *url = xmlnode_get_attrib(goodinfo, "url");
346 const char *id = xmlnode_get_attrib(goodinfo,"id");
347
348 /* the avatar might either be stored in a pep node, or on a HTTP(S) URL */
349 if(!url) {
350 const char *data_ns;
351 data_ns = (g_str_equal(ns, NS_AVATAR_0_12_METADATA) ?
352 NS_AVATAR_0_12_DATA : NS_AVATAR_1_1_DATA);
353 jabber_pep_request_item(js, from, data_ns, id,
354 do_buddy_avatar_update_data);
355 } else {
356 PurpleUtilFetchUrlData *url_data;
357 JabberBuddyAvatarUpdateURLInfo *info = g_new0(JabberBuddyAvatarUpdateURLInfo, 1);
358 info->js = js;
359
360 url_data = purple_util_fetch_url_len(url, TRUE, NULL, TRUE,
361 MAX_HTTP_BUDDYICON_BYTES,
362 do_buddy_avatar_update_fromurl, info);
363 if (url_data) {
364 info->from = g_strdup(from);
365 info->id = g_strdup(id);
366 js->url_datas = g_slist_prepend(js->url_datas, url_data);
367 } else
368 g_free(info);
369
370 }
371 }
372 }
373 }