Migrate certificates, icons, logs to XDG dirs
[pidgin-git.git] / libpurple / protocols / jabber / data.c
blob6c3878c48a01400a3e5d91a011d7d9143435b0e5
1 /*
2 * purple - Handling of XEP-0231: Bits of Binary.
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.
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
24 #include "internal.h"
26 #include <stdlib.h>
27 #include <glib.h>
28 #include <string.h>
30 #include "data.h"
31 #include "debug.h"
32 #include "xmlnode.h"
33 #include "util.h"
34 #include "iq.h"
36 static GHashTable *local_data_by_alt = NULL;
37 static GHashTable *local_data_by_cid = NULL;
38 static GHashTable *remote_data_by_cid = NULL;
40 JabberData *
41 jabber_data_create_from_data(gconstpointer rawdata, gsize size, const char *type,
42 gboolean ephemeral, JabberStream *js)
44 JabberData *data;
45 gchar *checksum;
46 gchar cid[256]; /* "Big enough" for a SHA1 hex hash value */
48 g_return_val_if_fail(rawdata != NULL, NULL);
49 g_return_val_if_fail(size > 0, NULL);
50 g_return_val_if_fail(type != NULL, NULL);
52 data = g_new0(JabberData, 1);
53 checksum = jabber_calculate_data_hash(rawdata, size, "sha1");
55 g_snprintf(cid, sizeof(cid), "sha1+%s@bob.xmpp.org", checksum);
56 g_free(checksum);
58 data->cid = g_strdup(cid);
59 data->type = g_strdup(type);
60 data->size = size;
61 data->ephemeral = ephemeral;
63 data->data = g_memdup(rawdata, size);
65 return data;
68 static void
69 jabber_data_delete(gpointer cbdata)
71 JabberData *data = cbdata;
73 g_free(data->cid);
74 g_free(data->type);
75 g_free(data->data);
76 g_free(data);
80 JabberData *
81 jabber_data_create_from_xml(PurpleXmlNode *tag)
83 JabberData *data;
84 gchar *raw_data = NULL;
85 const gchar *cid, *type;
87 g_return_val_if_fail(tag != NULL, NULL);
89 /* check if this is a "data" tag */
90 if (strcmp(tag->name, "data") != 0) {
91 purple_debug_error("jabber", "Invalid data element\n");
92 return NULL;
95 cid = purple_xmlnode_get_attrib(tag, "cid");
96 type = purple_xmlnode_get_attrib(tag, "type");
98 if (!cid || !type) {
99 purple_debug_error("jabber", "cid or type missing\n");
100 return NULL;
103 raw_data = purple_xmlnode_get_data(tag);
105 if (raw_data == NULL || *raw_data == '\0') {
106 purple_debug_error("jabber", "data element was empty");
107 g_free(raw_data);
108 return NULL;
111 data = g_new0(JabberData, 1);
112 data->data = purple_base64_decode(raw_data, &data->size);
113 g_free(raw_data);
115 if (data->data == NULL) {
116 purple_debug_error("jabber", "Malformed base64 data\n");
117 g_free(data);
118 return NULL;
121 data->cid = g_strdup(cid);
122 data->type = g_strdup(type);
124 return data;
127 void
128 jabber_data_destroy(JabberData *data)
130 g_return_if_fail(data != NULL);
132 jabber_data_delete(data);
135 const char *
136 jabber_data_get_cid(const JabberData *data)
138 g_return_val_if_fail(data != NULL, NULL);
140 return data->cid;
144 const char *
145 jabber_data_get_type(const JabberData *data)
147 g_return_val_if_fail(data != NULL, NULL);
149 return data->type;
152 gsize
153 jabber_data_get_size(const JabberData *data)
155 g_return_val_if_fail(data != NULL, 0);
157 return data->size;
160 gpointer
161 jabber_data_get_data(const JabberData *data)
163 g_return_val_if_fail(data != NULL, NULL);
165 return data->data;
168 PurpleXmlNode *
169 jabber_data_get_xml_definition(const JabberData *data)
171 PurpleXmlNode *tag;
172 char *base64data;
174 g_return_val_if_fail(data != NULL, NULL);
176 tag = purple_xmlnode_new("data");
177 base64data = purple_base64_encode(data->data, data->size);
179 purple_xmlnode_set_namespace(tag, NS_BOB);
180 purple_xmlnode_set_attrib(tag, "cid", data->cid);
181 purple_xmlnode_set_attrib(tag, "type", data->type);
183 purple_xmlnode_insert_data(tag, base64data, -1);
185 g_free(base64data);
187 return tag;
190 PurpleXmlNode *
191 jabber_data_get_xhtml_im(const JabberData *data, const gchar *alt)
193 PurpleXmlNode *img;
194 char *src;
196 g_return_val_if_fail(data != NULL, NULL);
197 g_return_val_if_fail(alt != NULL, NULL);
199 img = purple_xmlnode_new("img");
200 purple_xmlnode_set_attrib(img, "alt", alt);
202 src = g_strconcat("cid:", data->cid, NULL);
203 purple_xmlnode_set_attrib(img, "src", src);
204 g_free(src);
206 return img;
209 static PurpleXmlNode *
210 jabber_data_get_xml_request(const gchar *cid)
212 PurpleXmlNode *tag = purple_xmlnode_new("data");
214 purple_xmlnode_set_namespace(tag, NS_BOB);
215 purple_xmlnode_set_attrib(tag, "cid", cid);
217 return tag;
220 static gboolean
221 jabber_data_has_valid_hash(const JabberData *data)
223 const gchar *cid = jabber_data_get_cid(data);
224 gchar **cid_parts = g_strsplit(cid, "@", -1);
225 guint num_cid_parts = 0;
226 gboolean ret = FALSE;
228 if (cid_parts)
229 num_cid_parts = g_strv_length(cid_parts);
231 if (num_cid_parts == 2 && purple_strequal(cid_parts[1], "bob.xmpp.org")) {
232 gchar **sub_parts = g_strsplit(cid_parts[0], "+", -1);
233 guint num_sub_parts = 0;
235 if (sub_parts)
236 num_sub_parts = g_strv_length(sub_parts);
238 if (num_sub_parts == 2) {
239 const gchar *hash_algo = sub_parts[0];
240 const gchar *hash_value = sub_parts[1];
241 gchar *digest =
242 jabber_calculate_data_hash(jabber_data_get_data(data),
243 jabber_data_get_size(data), hash_algo);
245 if (digest) {
246 ret = purple_strequal(digest, hash_value);
248 if (!ret)
249 purple_debug_warning("jabber", "Unable to validate BoB "
250 "hash; expecting %s, got %s\n",
251 cid, digest);
253 g_free(digest);
254 } else {
255 purple_debug_warning("jabber", "Unable to validate BoB hash; "
256 "unknown hash algorithm %s\n", hash_algo);
258 } else {
259 purple_debug_warning("jabber", "Malformed BoB CID\n");
262 g_strfreev(sub_parts);
265 g_strfreev(cid_parts);
266 return ret;
270 typedef struct {
271 gpointer userdata;
272 gchar *alt;
273 gboolean ephemeral;
274 JabberDataRequestCallback *cb;
275 } JabberDataRequestData;
277 static void
278 jabber_data_request_cb(JabberStream *js, const char *from,
279 JabberIqType type, const char *id, PurpleXmlNode *packet, gpointer data)
281 JabberDataRequestData *request_data = (JabberDataRequestData *) data;
282 gpointer userdata = request_data->userdata;
283 gchar *alt = request_data->alt;
284 gboolean ephemeral = request_data->ephemeral;
285 JabberDataRequestCallback *cb = request_data->cb;
287 PurpleXmlNode *data_element = purple_xmlnode_get_child(packet, "data");
288 PurpleXmlNode *item_not_found = purple_xmlnode_get_child(packet, "item-not-found");
290 /* did we get a data element as result? */
291 if (data_element && type == JABBER_IQ_RESULT) {
292 JabberData *data = jabber_data_create_from_xml(data_element);
294 if (data && !ephemeral) {
295 jabber_data_associate_remote(js, from, data);
297 cb(data, alt, userdata);
298 } else if (item_not_found) {
299 purple_debug_info("jabber",
300 "Responder didn't recognize requested data\n");
301 cb(NULL, alt, userdata);
302 } else {
303 purple_debug_warning("jabber", "Unknown response to data request\n");
304 cb(NULL, alt, userdata);
307 g_free(request_data);
310 void
311 jabber_data_request(JabberStream *js, const gchar *cid, const gchar *who,
312 gchar *alt, gboolean ephemeral, JabberDataRequestCallback cb,
313 gpointer userdata)
315 JabberIq *request;
316 PurpleXmlNode *data_request;
317 JabberDataRequestData *data;
319 g_return_if_fail(cid != NULL);
320 g_return_if_fail(who != NULL);
321 g_return_if_fail(alt != NULL);
323 request = jabber_iq_new(js, JABBER_IQ_GET);
324 data_request = jabber_data_get_xml_request(cid);
325 data = g_new0(JabberDataRequestData, 1);
327 data->userdata = userdata;
328 data->alt = alt;
329 data->ephemeral = ephemeral;
330 data->cb = cb;
332 purple_xmlnode_set_attrib(request->node, "to", who);
333 jabber_iq_set_callback(request, jabber_data_request_cb, data);
334 purple_xmlnode_insert_child(request->node, data_request);
335 jabber_iq_send(request);
338 const JabberData *
339 jabber_data_find_local_by_alt(const gchar *alt)
341 purple_debug_info("jabber", "looking up local data object with alt = %s\n", alt);
342 return g_hash_table_lookup(local_data_by_alt, alt);
345 const JabberData *
346 jabber_data_find_local_by_cid(const gchar *cid)
348 purple_debug_info("jabber", "lookup local data object with cid = %s\n", cid);
349 return g_hash_table_lookup(local_data_by_cid, cid);
352 const JabberData *
353 jabber_data_find_remote_by_cid(JabberStream *js, const gchar *who,
354 const gchar *cid)
356 const JabberData *data = g_hash_table_lookup(remote_data_by_cid, cid);
357 purple_debug_info("jabber", "lookup remote data object with cid = %s\n", cid);
359 if (data == NULL) {
360 gchar *jid_cid =
361 g_strdup_printf("%s@%s/%s%s%s", js->user->node, js->user->domain,
362 js->user->resource, who, cid);
363 purple_debug_info("jabber",
364 "didn't find BoB object by pure CID, try including JIDs: %s\n",
365 jid_cid);
366 data = g_hash_table_lookup(remote_data_by_cid, jid_cid);
367 g_free(jid_cid);
369 return data;
372 void
373 jabber_data_associate_local(JabberData *data, const gchar *alt)
375 g_return_if_fail(data != NULL);
377 purple_debug_info("jabber", "associating local data object\n alt = %s, cid = %s\n",
378 alt , jabber_data_get_cid(data));
379 if (alt)
380 g_hash_table_insert(local_data_by_alt, g_strdup(alt), data);
381 g_hash_table_insert(local_data_by_cid, g_strdup(jabber_data_get_cid(data)),
382 data);
385 void
386 jabber_data_associate_remote(JabberStream *js, const gchar *who, JabberData *data)
388 gchar *cid;
390 g_return_if_fail(data != NULL);
392 if (jabber_data_has_valid_hash(data)) {
393 cid = g_strdup(jabber_data_get_cid(data));
394 } else {
395 cid = g_strdup_printf("%s@%s/%s%s%s", js->user->node, js->user->domain,
396 js->user->resource, who, jabber_data_get_cid(data));
399 purple_debug_info("jabber", "associating remote BoB object with cid = %s\n",
400 cid);
402 g_hash_table_insert(remote_data_by_cid, cid, data);
405 void
406 jabber_data_parse(JabberStream *js, const char *who, JabberIqType type,
407 const char *id, PurpleXmlNode *data_node)
409 JabberIq *result = NULL;
410 const char *cid = purple_xmlnode_get_attrib(data_node, "cid");
411 const JabberData *data = cid ? jabber_data_find_local_by_cid(cid) : NULL;
413 if (!data) {
414 PurpleXmlNode *item_not_found = purple_xmlnode_new("item-not-found");
416 result = jabber_iq_new(js, JABBER_IQ_ERROR);
417 if (who)
418 purple_xmlnode_set_attrib(result->node, "to", who);
419 purple_xmlnode_set_attrib(result->node, "id", id);
420 purple_xmlnode_insert_child(result->node, item_not_found);
421 } else {
422 result = jabber_iq_new(js, JABBER_IQ_RESULT);
423 if (who)
424 purple_xmlnode_set_attrib(result->node, "to", who);
425 purple_xmlnode_set_attrib(result->node, "id", id);
426 purple_xmlnode_insert_child(result->node,
427 jabber_data_get_xml_definition(data));
428 /* if the data object is temporary, destroy it and remove the references
429 to it */
430 if (data->ephemeral) {
431 g_hash_table_remove(local_data_by_cid, cid);
434 jabber_iq_send(result);
437 void
438 jabber_data_init(void)
440 if (purple_debug_is_verbose())
441 purple_debug_misc("jabber", "creating hash tables for data objects");
442 local_data_by_alt = g_hash_table_new_full(g_str_hash, g_str_equal,
443 g_free, NULL);
444 local_data_by_cid = g_hash_table_new_full(g_str_hash, g_str_equal,
445 g_free, jabber_data_delete);
446 remote_data_by_cid = g_hash_table_new_full(g_str_hash, g_str_equal,
447 g_free, jabber_data_delete);
449 jabber_iq_register_handler("data", NS_BOB, jabber_data_parse);
452 void
453 jabber_data_uninit(void)
455 if (purple_debug_is_verbose())
456 purple_debug_info("jabber", "destroying hash tables for data objects");
457 g_hash_table_destroy(local_data_by_alt);
458 g_hash_table_destroy(local_data_by_cid);
459 g_hash_table_destroy(remote_data_by_cid);
460 local_data_by_alt = local_data_by_cid = remote_data_by_cid = NULL;