Coverage Report

Created: 2026-10-13 06:48

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/pidgin/libpurple/protocols/jabber/data.c
Line
Count
Source
1
/*
2
 * purple - Jabber Service Discovery
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., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
21
 *
22
 */
23
24
#include "internal.h"
25
26
#include <stdlib.h>
27
#include <glib.h>
28
#include <string.h>
29
30
#include "data.h"
31
#include "debug.h"
32
#include "glibcompat.h"
33
#include "xmlnode.h"
34
#include "util.h"
35
#include "iq.h"
36
37
static GHashTable *local_data_by_alt = NULL;
38
static GHashTable *local_data_by_cid = NULL;
39
static GHashTable *remote_data_by_cid = NULL;
40
41
JabberData *
42
jabber_data_create_from_data(gconstpointer rawdata, gsize size, const char *type,
43
  gboolean ephemeral, JabberStream *js)
44
0
{
45
0
  JabberData *data;
46
0
  gchar *checksum;
47
0
  gchar cid[256]; /* "Big enough" for a SHA1 hex hash value */
48
49
0
  g_return_val_if_fail(rawdata != NULL, NULL);
50
0
  g_return_val_if_fail(size > 0, NULL);
51
0
  g_return_val_if_fail(type != NULL, NULL);
52
53
0
  data = g_new0(JabberData, 1);
54
0
  checksum = jabber_calculate_data_hash(rawdata, size, "sha1");
55
56
0
  g_snprintf(cid, sizeof(cid), "sha1+%s@bob.xmpp.org", checksum);
57
0
  g_free(checksum);
58
59
0
  data->cid = g_strdup(cid);
60
0
  data->type = g_strdup(type);
61
0
  data->size = size;
62
0
  data->ephemeral = ephemeral;
63
64
0
  data->data = g_memdup2(rawdata, size);
65
66
0
  return data;
67
0
}
68
69
static void
70
jabber_data_delete(gpointer cbdata)
71
0
{
72
0
  JabberData *data = cbdata;
73
74
0
  g_free(data->cid);
75
0
  g_free(data->type);
76
0
  g_free(data->data);
77
0
  g_free(data);
78
0
}
79
80
81
JabberData *
82
jabber_data_create_from_xml(xmlnode *tag)
83
0
{
84
0
  JabberData *data;
85
0
  gchar *raw_data = NULL;
86
0
  const gchar *cid, *type;
87
88
0
  g_return_val_if_fail(tag != NULL, NULL);
89
90
  /* check if this is a "data" tag */
91
0
  if (!purple_strequal(tag->name, "data")) {
92
0
    purple_debug_error("jabber", "Invalid data element\n");
93
0
    return NULL;
94
0
  }
95
96
0
  cid = xmlnode_get_attrib(tag, "cid");
97
0
  type = xmlnode_get_attrib(tag, "type");
98
99
0
  if (!cid || !type) {
100
0
    purple_debug_error("jabber", "cid or type missing\n");
101
0
    return NULL;
102
0
  }
103
104
0
  raw_data = xmlnode_get_data(tag);
105
106
0
  if (raw_data == NULL || *raw_data == '\0') {
107
0
    purple_debug_error("jabber", "data element was empty");
108
0
    g_free(raw_data);
109
0
    return NULL;
110
0
  }
111
112
0
  data = g_new0(JabberData, 1);
113
0
  data->data = purple_base64_decode(raw_data, &data->size);
114
0
  g_free(raw_data);
115
116
0
  if (data->data == NULL) {
117
0
    purple_debug_error("jabber", "Malformed base64 data\n");
118
0
    g_free(data);
119
0
    return NULL;
120
0
  }
121
122
0
  data->cid = g_strdup(cid);
123
0
  data->type = g_strdup(type);
124
125
0
  return data;
126
0
}
127
128
void
129
jabber_data_destroy(JabberData *data)
130
0
{
131
0
  g_return_if_fail(data != NULL);
132
133
0
  jabber_data_delete(data);
134
0
}
135
136
const char *
137
jabber_data_get_cid(const JabberData *data)
138
0
{
139
0
  g_return_val_if_fail(data != NULL, NULL);
140
141
0
  return data->cid;
142
0
}
143
144
145
const char *
146
jabber_data_get_type(const JabberData *data)
147
0
{
148
0
  g_return_val_if_fail(data != NULL, NULL);
149
150
0
  return data->type;
151
0
}
152
153
gsize
154
jabber_data_get_size(const JabberData *data)
155
0
{
156
0
  g_return_val_if_fail(data != NULL, 0);
157
158
0
  return data->size;
159
0
}
160
161
gpointer
162
jabber_data_get_data(const JabberData *data)
163
0
{
164
0
  g_return_val_if_fail(data != NULL, NULL);
165
166
0
  return data->data;
167
0
}
168
169
xmlnode *
170
jabber_data_get_xml_definition(const JabberData *data)
171
0
{
172
0
  xmlnode *tag;
173
0
  char *base64data;
174
175
0
  g_return_val_if_fail(data != NULL, NULL);
176
177
0
  tag = xmlnode_new("data");
178
0
  base64data = purple_base64_encode(data->data, data->size);
179
180
0
  xmlnode_set_namespace(tag, NS_BOB);
181
0
  xmlnode_set_attrib(tag, "cid", data->cid);
182
0
  xmlnode_set_attrib(tag, "type", data->type);
183
184
0
  xmlnode_insert_data(tag, base64data, -1);
185
186
0
  g_free(base64data);
187
188
0
  return tag;
189
0
}
190
191
xmlnode *
192
jabber_data_get_xhtml_im(const JabberData *data, const gchar *alt)
193
0
{
194
0
  xmlnode *img;
195
0
  char *src;
196
197
0
  g_return_val_if_fail(data != NULL, NULL);
198
0
  g_return_val_if_fail(alt != NULL, NULL);
199
200
0
  img = xmlnode_new("img");
201
0
  xmlnode_set_attrib(img, "alt", alt);
202
203
0
  src = g_strconcat("cid:", data->cid, NULL);
204
0
  xmlnode_set_attrib(img, "src", src);
205
0
  g_free(src);
206
207
0
  return img;
208
0
}
209
210
static xmlnode *
211
jabber_data_get_xml_request(const gchar *cid)
212
0
{
213
0
  xmlnode *tag = xmlnode_new("data");
214
215
0
  xmlnode_set_namespace(tag, NS_BOB);
216
0
  xmlnode_set_attrib(tag, "cid", cid);
217
218
0
  return tag;
219
0
}
220
221
static gboolean
222
jabber_data_has_valid_hash(const JabberData *data)
223
0
{
224
0
  const gchar *cid = jabber_data_get_cid(data);
225
0
  gchar **cid_parts = g_strsplit(cid, "@", -1);
226
0
  guint num_cid_parts = 0;
227
0
  gboolean ret = FALSE;
228
229
0
  if (cid_parts)
230
0
    num_cid_parts = g_strv_length(cid_parts);
231
232
0
  if (num_cid_parts == 2 && purple_strequal(cid_parts[1], "bob.xmpp.org")) {
233
0
    gchar **sub_parts = g_strsplit(cid_parts[0], "+", -1);
234
0
    guint num_sub_parts = 0;
235
236
0
    if (sub_parts)
237
0
      num_sub_parts = g_strv_length(sub_parts);
238
239
0
    if (num_sub_parts == 2) {
240
0
      const gchar *hash_algo = sub_parts[0];
241
0
      const gchar *hash_value = sub_parts[1];
242
0
      gchar *digest =
243
0
        jabber_calculate_data_hash(jabber_data_get_data(data),
244
0
            jabber_data_get_size(data), hash_algo);
245
246
0
      if (digest) {
247
0
        ret = purple_strequal(digest, hash_value);
248
249
0
        if (!ret)
250
0
          purple_debug_warning("jabber", "Unable to validate BoB "
251
0
                               "hash; expecting %s, got %s\n",
252
0
                               cid, digest);
253
254
0
        g_free(digest);
255
0
      } else {
256
0
        purple_debug_warning("jabber", "Unable to validate BoB hash; "
257
0
                             "unknown hash algorithm %s\n", hash_algo);
258
0
      }
259
0
    } else {
260
0
      purple_debug_warning("jabber", "Malformed BoB CID\n");
261
0
    }
262
263
0
    g_strfreev(sub_parts);
264
0
  }
265
266
0
  g_strfreev(cid_parts);
267
0
  return ret;
268
0
}
269
270
271
typedef struct {
272
  gpointer userdata;
273
  gchar *alt;
274
  gboolean ephemeral;
275
  JabberDataRequestCallback *cb;
276
} JabberDataRequestData;
277
278
static void
279
jabber_data_request_cb(JabberStream *js, const char *from,
280
  JabberIqType type, const char *id, xmlnode *packet, gpointer data)
281
0
{
282
0
  JabberDataRequestData *request_data = (JabberDataRequestData *) data;
283
0
  gpointer userdata = request_data->userdata;
284
0
  gchar *alt = request_data->alt;
285
0
  gboolean ephemeral = request_data->ephemeral;
286
0
  JabberDataRequestCallback *cb = request_data->cb;
287
288
0
  xmlnode *data_element = xmlnode_get_child(packet, "data");
289
0
  xmlnode *item_not_found = xmlnode_get_child(packet, "item-not-found");
290
291
  /* did we get a data element as result? */
292
0
  if (data_element && type == JABBER_IQ_RESULT) {
293
0
    JabberData *data = jabber_data_create_from_xml(data_element);
294
295
0
    if (data && !ephemeral) {
296
0
      jabber_data_associate_remote(js, from, data);
297
0
    }
298
0
    cb(data, alt, userdata);
299
0
  } else if (item_not_found) {
300
0
    purple_debug_info("jabber",
301
0
      "Responder didn't recognize requested data\n");
302
0
    cb(NULL, alt, userdata);
303
0
  } else {
304
0
    purple_debug_warning("jabber", "Unknown response to data request\n");
305
0
    cb(NULL, alt, userdata);
306
0
  }
307
308
0
  g_free(request_data);
309
0
}
310
311
void
312
jabber_data_request(JabberStream *js, const gchar *cid, const gchar *who,
313
    gchar *alt, gboolean ephemeral, JabberDataRequestCallback cb,
314
    gpointer userdata)
315
0
{
316
0
  JabberIq *request;
317
0
  xmlnode *data_request;
318
0
  JabberDataRequestData *data;
319
320
0
  g_return_if_fail(cid != NULL);
321
0
  g_return_if_fail(who != NULL);
322
0
  g_return_if_fail(alt != NULL);
323
324
0
  request = jabber_iq_new(js, JABBER_IQ_GET);
325
0
  data_request = jabber_data_get_xml_request(cid);
326
0
  data = g_new0(JabberDataRequestData, 1);
327
328
0
  data->userdata = userdata;
329
0
  data->alt = alt;
330
0
  data->ephemeral = ephemeral;
331
0
  data->cb = cb;
332
333
0
  xmlnode_set_attrib(request->node, "to", who);
334
0
  jabber_iq_set_callback(request, jabber_data_request_cb, data);
335
0
  xmlnode_insert_child(request->node, data_request);
336
0
  jabber_iq_send(request);
337
0
}
338
339
const JabberData *
340
jabber_data_find_local_by_alt(const gchar *alt)
341
0
{
342
0
  purple_debug_info("jabber", "looking up local data object with alt = %s\n", alt);
343
0
  return g_hash_table_lookup(local_data_by_alt, alt);
344
0
}
345
346
const JabberData *
347
jabber_data_find_local_by_cid(const gchar *cid)
348
0
{
349
0
  purple_debug_info("jabber", "lookup local data object with cid = %s\n", cid);
350
0
  return g_hash_table_lookup(local_data_by_cid, cid);
351
0
}
352
353
const JabberData *
354
jabber_data_find_remote_by_cid(JabberStream *js, const gchar *who,
355
    const gchar *cid)
356
0
{
357
0
  const JabberData *data = g_hash_table_lookup(remote_data_by_cid, cid);
358
0
  purple_debug_info("jabber", "lookup remote data object with cid = %s\n", cid);
359
360
0
  if (data == NULL) {
361
0
    gchar *jid_cid =
362
0
      g_strdup_printf("%s@%s/%s%s%s", js->user->node, js->user->domain,
363
0
          js->user->resource, who, cid);
364
0
    purple_debug_info("jabber",
365
0
        "didn't find BoB object by pure CID, try including JIDs: %s\n",
366
0
        jid_cid);
367
0
    data = g_hash_table_lookup(remote_data_by_cid, jid_cid);
368
0
    g_free(jid_cid);
369
0
  }
370
0
  return data;
371
0
}
372
373
void
374
jabber_data_associate_local(JabberData *data, const gchar *alt)
375
0
{
376
0
  g_return_if_fail(data != NULL);
377
378
0
  purple_debug_info("jabber", "associating local data object\n alt = %s, cid = %s\n",
379
0
    alt , jabber_data_get_cid(data));
380
0
  if (alt)
381
0
    g_hash_table_insert(local_data_by_alt, g_strdup(alt), data);
382
0
  g_hash_table_insert(local_data_by_cid, g_strdup(jabber_data_get_cid(data)),
383
0
    data);
384
0
}
385
386
void
387
jabber_data_associate_remote(JabberStream *js, const gchar *who, JabberData *data)
388
0
{
389
0
  gchar *cid;
390
391
0
  g_return_if_fail(data != NULL);
392
393
0
  if (jabber_data_has_valid_hash(data)) {
394
0
    cid = g_strdup(jabber_data_get_cid(data));
395
0
  } else {
396
0
    cid = g_strdup_printf("%s@%s/%s%s%s", js->user->node, js->user->domain,
397
0
        js->user->resource, who, jabber_data_get_cid(data));
398
0
  }
399
400
0
  purple_debug_info("jabber", "associating remote BoB object with cid = %s\n",
401
0
    cid);
402
403
0
  g_hash_table_insert(remote_data_by_cid, cid, data);
404
0
}
405
406
void
407
jabber_data_parse(JabberStream *js, const char *who, JabberIqType type,
408
                  const char *id, xmlnode *data_node)
409
0
{
410
0
  JabberIq *result = NULL;
411
0
  const char *cid = xmlnode_get_attrib(data_node, "cid");
412
0
  const JabberData *data = cid ? jabber_data_find_local_by_cid(cid) : NULL;
413
414
0
  if (!data) {
415
0
    xmlnode *item_not_found = xmlnode_new("item-not-found");
416
417
0
    result = jabber_iq_new(js, JABBER_IQ_ERROR);
418
0
    if (who)
419
0
      xmlnode_set_attrib(result->node, "to", who);
420
0
    xmlnode_set_attrib(result->node, "id", id);
421
0
    xmlnode_insert_child(result->node, item_not_found);
422
0
  } else {
423
0
    result = jabber_iq_new(js, JABBER_IQ_RESULT);
424
0
    if (who)
425
0
      xmlnode_set_attrib(result->node, "to", who);
426
0
    xmlnode_set_attrib(result->node, "id", id);
427
0
    xmlnode_insert_child(result->node,
428
0
               jabber_data_get_xml_definition(data));
429
    /* if the data object is temporary, destroy it and remove the references
430
     to it */
431
0
    if (data->ephemeral) {
432
0
      g_hash_table_remove(local_data_by_cid, cid);
433
0
    }
434
0
  }
435
0
  jabber_iq_send(result);
436
0
}
437
438
void
439
jabber_data_init(void)
440
0
{
441
0
  purple_debug_info("jabber", "creating hash tables for data objects\n");
442
0
  local_data_by_alt = g_hash_table_new_full(g_str_hash, g_str_equal,
443
0
    g_free, NULL);
444
0
  local_data_by_cid = g_hash_table_new_full(g_str_hash, g_str_equal,
445
0
    g_free, jabber_data_delete);
446
0
  remote_data_by_cid = g_hash_table_new_full(g_str_hash, g_str_equal,
447
0
    g_free, jabber_data_delete);
448
449
0
  jabber_iq_register_handler("data", NS_BOB, jabber_data_parse);
450
0
}
451
452
void
453
jabber_data_uninit(void)
454
0
{
455
0
  purple_debug_info("jabber", "destroying hash tables for data objects\n");
456
0
  g_hash_table_destroy(local_data_by_alt);
457
0
  g_hash_table_destroy(local_data_by_cid);
458
0
  g_hash_table_destroy(remote_data_by_cid);
459
  local_data_by_alt = local_data_by_cid = remote_data_by_cid = NULL;
460
0
}