Mercurial > pidgin
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 } |