Coverage Report

Created: 2026-04-01 06:37

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/pidgin/libpurple/protocols/jabber/ibb.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
#include "ibb.h"
26
#include "debug.h"
27
#include "xmlnode.h"
28
29
0
#define JABBER_IBB_SESSION_DEFAULT_BLOCK_SIZE 4096
30
31
static GHashTable *jabber_ibb_sessions = NULL;
32
static GList *open_handlers = NULL;
33
34
JabberIBBSession *
35
jabber_ibb_session_create(JabberStream *js, const gchar *sid, const gchar *who,
36
  gpointer user_data)
37
0
{
38
0
  JabberIBBSession *sess = g_new0(JabberIBBSession, 1);
39
0
  sess->js = js;
40
0
  if (sid) {
41
0
    sess->sid = g_strdup(sid);
42
0
  } else {
43
0
    sess->sid = jabber_get_next_id(js);
44
0
  }
45
0
  sess->who = g_strdup(who);
46
0
  sess->block_size = JABBER_IBB_SESSION_DEFAULT_BLOCK_SIZE;
47
0
  sess->state = JABBER_IBB_SESSION_NOT_OPENED;
48
0
  sess->user_data = user_data;
49
50
0
  g_hash_table_insert(jabber_ibb_sessions, sess->sid, sess);
51
52
0
  return sess;
53
0
}
54
55
JabberIBBSession *
56
jabber_ibb_session_create_from_xmlnode(JabberStream *js, const char *from,
57
  const char *id, xmlnode *open, gpointer user_data)
58
0
{
59
0
  JabberIBBSession *sess = NULL;
60
0
  const gchar *sid = xmlnode_get_attrib(open, "sid");
61
0
  const gchar *block_size = xmlnode_get_attrib(open, "block-size");
62
63
0
  if (!open) {
64
0
    return NULL;
65
0
  }
66
67
0
  if (!sid || !block_size) {
68
0
    purple_debug_error("jabber",
69
0
      "IBB session open tag requires sid and block-size attributes\n");
70
0
    g_free(sess);
71
0
    return NULL;
72
0
  }
73
74
0
  sess = jabber_ibb_session_create(js, sid, from, user_data);
75
0
  sess->id = g_strdup(id);
76
0
  sess->block_size = atoi(block_size);
77
  /* if we create a session from an incoming <open/> request, it means the
78
    session is immediatly open... */
79
0
  sess->state = JABBER_IBB_SESSION_OPENED;
80
81
0
  return sess;
82
0
}
83
84
void
85
jabber_ibb_session_destroy(JabberIBBSession *sess)
86
0
{
87
0
  purple_debug_info("jabber", "IBB: destroying session %p %s\n", sess,
88
0
    sess->sid);
89
90
0
  if (jabber_ibb_session_get_state(sess) == JABBER_IBB_SESSION_OPENED) {
91
0
    jabber_ibb_session_close(sess);
92
0
  }
93
94
0
  if (sess->last_iq_id) {
95
0
    purple_debug_info("jabber", "IBB: removing callback for <iq/> %s\n",
96
0
      sess->last_iq_id);
97
0
    jabber_iq_remove_callback_by_id(jabber_ibb_session_get_js(sess),
98
0
      sess->last_iq_id);
99
0
    g_free(sess->last_iq_id);
100
0
    sess->last_iq_id = NULL;
101
0
  }
102
103
0
  g_hash_table_remove(jabber_ibb_sessions, sess->sid);
104
0
  g_free(sess->id);
105
0
  g_free(sess->sid);
106
0
  g_free(sess->who);
107
0
  g_free(sess);
108
0
}
109
110
const gchar *
111
jabber_ibb_session_get_sid(const JabberIBBSession *sess)
112
0
{
113
0
  return sess->sid;
114
0
}
115
116
JabberStream *
117
jabber_ibb_session_get_js(JabberIBBSession *sess)
118
0
{
119
0
  return sess->js;
120
0
}
121
122
const gchar *
123
jabber_ibb_session_get_who(const JabberIBBSession *sess)
124
0
{
125
0
  return sess->who;
126
0
}
127
128
guint16
129
jabber_ibb_session_get_send_seq(const JabberIBBSession *sess)
130
0
{
131
0
  return sess->send_seq;
132
0
}
133
134
guint16
135
jabber_ibb_session_get_recv_seq(const JabberIBBSession *sess)
136
0
{
137
0
  return sess->recv_seq;
138
0
}
139
140
JabberIBBSessionState
141
jabber_ibb_session_get_state(const JabberIBBSession *sess)
142
0
{
143
0
  return sess->state;
144
0
}
145
146
gsize
147
jabber_ibb_session_get_block_size(const JabberIBBSession *sess)
148
0
{
149
0
  return sess->block_size;
150
0
}
151
152
void
153
jabber_ibb_session_set_block_size(JabberIBBSession *sess, gsize size)
154
0
{
155
0
  if (jabber_ibb_session_get_state(sess) == JABBER_IBB_SESSION_NOT_OPENED) {
156
0
    sess->block_size = size;
157
0
  } else {
158
0
    purple_debug_error("jabber",
159
0
      "Can't set block size on an open IBB session\n");
160
0
  }
161
0
}
162
163
gsize
164
jabber_ibb_session_get_max_data_size(const JabberIBBSession *sess)
165
0
{
166
0
  return (gsize) floor((sess->block_size - 2) * (float) 3 / 4);
167
0
}
168
169
gpointer
170
jabber_ibb_session_get_user_data(JabberIBBSession *sess)
171
0
{
172
0
  return sess->user_data;
173
0
}
174
175
void
176
jabber_ibb_session_set_opened_callback(JabberIBBSession *sess,
177
  JabberIBBOpenedCallback *cb)
178
0
{
179
0
  sess->opened_cb = cb;
180
0
}
181
182
void
183
jabber_ibb_session_set_data_sent_callback(JabberIBBSession *sess,
184
  JabberIBBSentCallback *cb)
185
0
{
186
0
  sess->data_sent_cb = cb;
187
0
}
188
189
void
190
jabber_ibb_session_set_closed_callback(JabberIBBSession *sess,
191
  JabberIBBClosedCallback *cb)
192
0
{
193
0
  sess->closed_cb = cb;
194
0
}
195
196
void
197
jabber_ibb_session_set_data_received_callback(JabberIBBSession *sess,
198
  JabberIBBDataCallback *cb)
199
0
{
200
0
  sess->data_received_cb = cb;
201
0
}
202
203
void
204
jabber_ibb_session_set_error_callback(JabberIBBSession *sess,
205
  JabberIBBErrorCallback *cb)
206
0
{
207
0
  sess->error_cb = cb;
208
0
}
209
210
static void
211
jabber_ibb_session_opened_cb(JabberStream *js, const char *from,
212
                             JabberIqType type, const char *id,
213
                             xmlnode *packet, gpointer data)
214
0
{
215
0
  JabberIBBSession *sess = (JabberIBBSession *) data;
216
217
0
  if (type == JABBER_IQ_ERROR) {
218
0
    sess->state = JABBER_IBB_SESSION_ERROR;
219
0
  } else {
220
0
    sess->state = JABBER_IBB_SESSION_OPENED;
221
0
  }
222
223
0
  if (sess->opened_cb) {
224
0
    sess->opened_cb(sess);
225
0
  }
226
0
}
227
228
void
229
jabber_ibb_session_open(JabberIBBSession *sess)
230
0
{
231
0
  if (jabber_ibb_session_get_state(sess) != JABBER_IBB_SESSION_NOT_OPENED) {
232
0
    purple_debug_error("jabber",
233
0
      "jabber_ibb_session called on an already open stream\n");
234
0
  } else {
235
0
    JabberIq *set = jabber_iq_new(sess->js, JABBER_IQ_SET);
236
0
    xmlnode *open = xmlnode_new("open");
237
0
    gchar block_size[10];
238
239
0
    xmlnode_set_attrib(set->node, "to", jabber_ibb_session_get_who(sess));
240
0
    xmlnode_set_namespace(open, NS_IBB);
241
0
    xmlnode_set_attrib(open, "sid", jabber_ibb_session_get_sid(sess));
242
0
    g_snprintf(block_size, sizeof(block_size), "%" G_GSIZE_FORMAT,
243
0
      jabber_ibb_session_get_block_size(sess));
244
0
    xmlnode_set_attrib(open, "block-size", block_size);
245
0
    xmlnode_insert_child(set->node, open);
246
247
0
    jabber_iq_set_callback(set, jabber_ibb_session_opened_cb, sess);
248
249
0
    jabber_iq_send(set);
250
0
  }
251
0
}
252
253
void
254
jabber_ibb_session_close(JabberIBBSession *sess)
255
0
{
256
0
  JabberIBBSessionState state = jabber_ibb_session_get_state(sess);
257
258
0
  if (state != JABBER_IBB_SESSION_OPENED && state != JABBER_IBB_SESSION_ERROR) {
259
0
    purple_debug_error("jabber",
260
0
      "jabber_ibb_session_close called on a session that has not been"
261
0
      "opened\n");
262
0
  } else {
263
0
    JabberIq *set = jabber_iq_new(jabber_ibb_session_get_js(sess),
264
0
      JABBER_IQ_SET);
265
0
    xmlnode *close = xmlnode_new("close");
266
267
0
    xmlnode_set_attrib(set->node, "to", jabber_ibb_session_get_who(sess));
268
0
    xmlnode_set_namespace(close, NS_IBB);
269
0
    xmlnode_set_attrib(close, "sid", jabber_ibb_session_get_sid(sess));
270
0
    xmlnode_insert_child(set->node, close);
271
0
    jabber_iq_send(set);
272
0
    sess->state = JABBER_IBB_SESSION_CLOSED;
273
0
  }
274
0
}
275
276
void
277
jabber_ibb_session_accept(JabberIBBSession *sess)
278
0
{
279
0
  JabberIq *result = jabber_iq_new(jabber_ibb_session_get_js(sess),
280
0
    JABBER_IQ_RESULT);
281
282
0
  xmlnode_set_attrib(result->node, "to", jabber_ibb_session_get_who(sess));
283
0
  jabber_iq_set_id(result, sess->id);
284
0
  jabber_iq_send(result);
285
0
  sess->state = JABBER_IBB_SESSION_OPENED;
286
0
}
287
288
static void
289
jabber_ibb_session_send_acknowledge_cb(JabberStream *js, const char *from,
290
                                       JabberIqType type, const char *id,
291
                                       xmlnode *packet, gpointer data)
292
0
{
293
0
  JabberIBBSession *sess = (JabberIBBSession *) data;
294
295
0
  if (sess) {
296
    /* reset callback */
297
0
    if (sess->last_iq_id) {
298
0
      g_free(sess->last_iq_id);
299
0
      sess->last_iq_id = NULL;
300
0
    }
301
302
0
    if (type == JABBER_IQ_ERROR) {
303
0
      jabber_ibb_session_close(sess);
304
0
      sess->state = JABBER_IBB_SESSION_ERROR;
305
306
0
      if (sess->error_cb) {
307
0
        sess->error_cb(sess);
308
0
      }
309
0
    } else {
310
0
      if (sess->data_sent_cb) {
311
0
        sess->data_sent_cb(sess);
312
0
      }
313
0
    }
314
0
  } else {
315
    /* the session has gone away, it was probably cancelled */
316
0
    purple_debug_info("jabber",
317
0
      "got response from send data, but IBB session is no longer active\n");
318
0
  }
319
0
}
320
321
void
322
jabber_ibb_session_send_data(JabberIBBSession *sess, gconstpointer data,
323
                             gsize size)
324
0
{
325
0
  JabberIBBSessionState state = jabber_ibb_session_get_state(sess);
326
327
0
  purple_debug_info("jabber", "sending data block of %" G_GSIZE_FORMAT " bytes on IBB stream\n",
328
0
    size);
329
330
0
  if (state != JABBER_IBB_SESSION_OPENED) {
331
0
    purple_debug_error("jabber",
332
0
      "trying to send data on a non-open IBB session\n");
333
0
  } else if (size > jabber_ibb_session_get_max_data_size(sess)) {
334
0
    purple_debug_error("jabber",
335
0
      "trying to send a too large packet in the IBB session\n");
336
0
  } else {
337
0
    JabberIq *set = jabber_iq_new(jabber_ibb_session_get_js(sess),
338
0
      JABBER_IQ_SET);
339
0
    xmlnode *data_element = xmlnode_new("data");
340
0
    char *base64 = purple_base64_encode(data, size);
341
0
    char seq[10];
342
0
    g_snprintf(seq, sizeof(seq), "%u", jabber_ibb_session_get_send_seq(sess));
343
344
0
    xmlnode_set_attrib(set->node, "to", jabber_ibb_session_get_who(sess));
345
0
    xmlnode_set_namespace(data_element, NS_IBB);
346
0
    xmlnode_set_attrib(data_element, "sid", jabber_ibb_session_get_sid(sess));
347
0
    xmlnode_set_attrib(data_element, "seq", seq);
348
0
    xmlnode_insert_data(data_element, base64, -1);
349
350
0
    xmlnode_insert_child(set->node, data_element);
351
352
0
    purple_debug_info("jabber",
353
0
      "IBB: setting send <iq/> callback for session %p %s\n", sess,
354
0
      sess->sid);
355
0
    jabber_iq_set_callback(set, jabber_ibb_session_send_acknowledge_cb, sess);
356
0
    sess->last_iq_id = g_strdup(xmlnode_get_attrib(set->node, "id"));
357
0
    purple_debug_info("jabber", "IBB: set sess->last_iq_id: %s\n",
358
0
      sess->last_iq_id);
359
0
    jabber_iq_send(set);
360
361
0
    g_free(base64);
362
0
    (sess->send_seq)++;
363
0
  }
364
0
}
365
366
static void
367
jabber_ibb_send_error_response(JabberStream *js, const char *to, const char *id)
368
0
{
369
0
  JabberIq *result = jabber_iq_new(js, JABBER_IQ_ERROR);
370
0
  xmlnode *error = xmlnode_new("error");
371
0
  xmlnode *item_not_found = xmlnode_new("item-not-found");
372
373
0
  xmlnode_set_namespace(item_not_found, NS_XMPP_STANZAS);
374
0
  xmlnode_set_attrib(error, "code", "440");
375
0
  xmlnode_set_attrib(error, "type", "cancel");
376
0
  jabber_iq_set_id(result, id);
377
0
  xmlnode_set_attrib(result->node, "to", to);
378
0
  xmlnode_insert_child(error, item_not_found);
379
0
  xmlnode_insert_child(result->node, error);
380
381
0
  jabber_iq_send(result);
382
0
}
383
384
void
385
jabber_ibb_parse(JabberStream *js, const char *who, JabberIqType type,
386
                 const char *id, xmlnode *child)
387
0
{
388
0
  const char *name = child->name;
389
0
  gboolean data  = purple_strequal(name, "data");
390
0
  gboolean close = purple_strequal(name, "close");
391
0
  gboolean open  = purple_strequal(name, "open");
392
0
  const gchar *sid = (data || close) ?
393
0
    xmlnode_get_attrib(child, "sid") : NULL;
394
0
  JabberIBBSession *sess =
395
0
    sid ? g_hash_table_lookup(jabber_ibb_sessions, sid) : NULL;
396
397
0
  if (sess) {
398
399
0
    if (!purple_strequal(who, jabber_ibb_session_get_who(sess))) {
400
      /* the iq comes from a different JID than the remote JID of the
401
        session, ignore it */
402
0
      purple_debug_error("jabber",
403
0
        "Got IBB iq from wrong JID, ignoring\n");
404
0
    } else if (data) {
405
0
      const gchar *seq_attr = xmlnode_get_attrib(child, "seq");
406
0
      guint16 seq = (seq_attr ? atoi(seq_attr) : 0);
407
408
      /* reject the data, and set the session in error if we get an
409
        out-of-order packet */
410
0
      if (seq_attr && seq == jabber_ibb_session_get_recv_seq(sess)) {
411
        /* sequence # is the expected... */
412
0
        JabberIq *result = jabber_iq_new(js, JABBER_IQ_RESULT);
413
414
0
        jabber_iq_set_id(result, id);
415
0
        xmlnode_set_attrib(result->node, "to", who);
416
417
0
        if (sess->data_received_cb) {
418
0
          gchar *base64 = xmlnode_get_data(child);
419
0
          gsize size;
420
0
          gpointer rawdata = purple_base64_decode(base64, &size);
421
422
0
          g_free(base64);
423
424
0
          if (rawdata) {
425
0
            purple_debug_info("jabber",
426
0
              "got %" G_GSIZE_FORMAT " bytes of data on IBB stream\n",
427
0
              size);
428
            /* we accept other clients to send up to block-size
429
             of _unencoded_ data, since there's been some confusions
430
             regarding the interpretation of this attribute
431
             (including previous versions of libpurple) */
432
0
            if (size > jabber_ibb_session_get_block_size(sess)) {
433
0
              purple_debug_error("jabber",
434
0
                "IBB: received a too large packet\n");
435
0
              if (sess->error_cb)
436
0
                sess->error_cb(sess);
437
0
              g_free(rawdata);
438
0
              return;
439
0
            } else {
440
0
              purple_debug_info("jabber",
441
0
                "calling IBB callback for received data\n");
442
0
              sess->data_received_cb(sess, rawdata, size);
443
0
            }
444
0
            g_free(rawdata);
445
0
          } else {
446
0
            purple_debug_error("jabber",
447
0
              "IBB: invalid BASE64 data received\n");
448
0
            if (sess->error_cb)
449
0
              sess->error_cb(sess);
450
0
            return;
451
452
0
          }
453
0
        }
454
455
0
        (sess->recv_seq)++;
456
0
        jabber_iq_send(result);
457
458
0
      } else {
459
0
        purple_debug_error("jabber",
460
0
          "Received an out-of-order/invalid IBB packet\n");
461
0
        sess->state = JABBER_IBB_SESSION_ERROR;
462
463
0
        if (sess->error_cb) {
464
0
          sess->error_cb(sess);
465
0
        }
466
0
      }
467
0
    } else if (close) {
468
0
      sess->state = JABBER_IBB_SESSION_CLOSED;
469
0
      purple_debug_info("jabber", "IBB: received close\n");
470
471
0
      if (sess->closed_cb) {
472
0
        purple_debug_info("jabber", "IBB: calling closed handler\n");
473
0
        sess->closed_cb(sess);
474
0
      }
475
0
    }
476
0
  } else if (open) {
477
0
    JabberIq *result;
478
0
    const GList *iterator;
479
480
    /* run all open handlers registered until one returns true */
481
0
    for (iterator = open_handlers ; iterator ;
482
0
       iterator = g_list_next(iterator)) {
483
0
      JabberIBBOpenHandler *handler = iterator->data;
484
485
0
      if (handler(js, who, id, child)) {
486
0
        result = jabber_iq_new(js, JABBER_IQ_RESULT);
487
0
        xmlnode_set_attrib(result->node, "to", who);
488
0
        jabber_iq_set_id(result, id);
489
0
        jabber_iq_send(result);
490
0
        return;
491
0
      }
492
0
    }
493
    /* no open callback returned success, reject */
494
0
    jabber_ibb_send_error_response(js, who, id);
495
0
  } else {
496
    /* send error reply */
497
0
    jabber_ibb_send_error_response(js, who, id);
498
0
  }
499
0
}
500
501
void
502
jabber_ibb_register_open_handler(JabberIBBOpenHandler *cb)
503
0
{
504
0
  open_handlers = g_list_append(open_handlers, cb);
505
0
}
506
507
void
508
jabber_ibb_unregister_open_handler(JabberIBBOpenHandler *cb)
509
0
{
510
0
  open_handlers = g_list_remove(open_handlers, cb);
511
0
}
512
513
void
514
jabber_ibb_init(void)
515
0
{
516
0
  jabber_ibb_sessions = g_hash_table_new(g_str_hash, g_str_equal);
517
518
0
  jabber_add_feature(NS_IBB, NULL);
519
520
0
  jabber_iq_register_handler("close", NS_IBB, jabber_ibb_parse);
521
0
  jabber_iq_register_handler("data", NS_IBB, jabber_ibb_parse);
522
0
  jabber_iq_register_handler("open", NS_IBB, jabber_ibb_parse);
523
0
}
524
525
void
526
jabber_ibb_uninit(void)
527
0
{
528
0
  g_hash_table_destroy(jabber_ibb_sessions);
529
0
  g_list_free(open_handlers);
530
0
  jabber_ibb_sessions = NULL;
531
  open_handlers = NULL;
532
0
}
533