Coverage Report

Created: 2025-08-28 06:24

/src/pidgin/libpurple/protocols/jabber/bosh.c
Line
Count
Source (jump to first uncovered line)
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., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
21
 *
22
 */
23
#include "internal.h"
24
#include "circbuffer.h"
25
#include "core.h"
26
#include "cipher.h"
27
#include "debug.h"
28
#include "prpl.h"
29
#include "util.h"
30
#include "xmlnode.h"
31
32
#include "bosh.h"
33
34
/* The number of HTTP connections to use. This MUST be at least 2. */
35
0
#define NUM_HTTP_CONNECTIONS      2
36
/* How many failed connection attempts before it becomes a fatal error */
37
0
#define MAX_FAILED_CONNECTIONS    3
38
/* How long in seconds to queue up outgoing messages */
39
0
#define BUFFER_SEND_IN_SECS       1
40
41
typedef struct _PurpleHTTPConnection PurpleHTTPConnection;
42
43
typedef void (*PurpleBOSHConnectionConnectFunction)(PurpleBOSHConnection *conn);
44
typedef void (*PurpleBOSHConnectionReceiveFunction)(PurpleBOSHConnection *conn, xmlnode *node);
45
46
static char *bosh_useragent = NULL;
47
48
typedef enum {
49
  PACKET_NORMAL,
50
  PACKET_TERMINATE,
51
  PACKET_FLUSH,
52
} PurpleBOSHPacketType;
53
54
struct _PurpleBOSHConnection {
55
  JabberStream *js;
56
  PurpleHTTPConnection *connections[NUM_HTTP_CONNECTIONS];
57
58
  PurpleCircBuffer *pending;
59
  PurpleBOSHConnectionConnectFunction connect_cb;
60
  PurpleBOSHConnectionReceiveFunction receive_cb;
61
62
  /* Must be big enough to hold 2^53 - 1 */
63
  char *sid;
64
  guint64 rid;
65
66
  /* decoded URL */
67
  char *host;
68
  char *path;
69
  guint16 port;
70
71
  gboolean ssl;
72
73
  enum {
74
    BOSH_CONN_OFFLINE,
75
    BOSH_CONN_BOOTING,
76
    BOSH_CONN_ONLINE
77
  } state;
78
  guint8 failed_connections;
79
80
  int wait;
81
82
  int max_requests;
83
  int requests;
84
85
  guint send_timer;
86
};
87
88
struct _PurpleHTTPConnection {
89
  PurpleBOSHConnection *bosh;
90
  PurpleSslConnection *psc;
91
92
  PurpleCircBuffer *write_buf;
93
  GString *read_buf;
94
95
  gsize handled_len;
96
  gsize body_len;
97
98
  int fd;
99
  guint readh;
100
  guint writeh;
101
102
  enum {
103
    HTTP_CONN_OFFLINE,
104
    HTTP_CONN_CONNECTING,
105
    HTTP_CONN_CONNECTED
106
  } state;
107
  int requests; /* number of outstanding HTTP requests */
108
109
  gboolean headers_done;
110
  gboolean close;
111
};
112
113
static void
114
debug_dump_http_connections(PurpleBOSHConnection *conn)
115
0
{
116
0
  int i;
117
118
0
  g_return_if_fail(conn != NULL);
119
120
0
  for (i = 0; i < NUM_HTTP_CONNECTIONS; ++i) {
121
0
    PurpleHTTPConnection *httpconn = conn->connections[i];
122
0
    if (httpconn == NULL)
123
0
      purple_debug_misc("jabber", "BOSH %p->connections[%d] = (nil)\n",
124
0
                        conn, i);
125
0
    else
126
0
      purple_debug_misc("jabber", "BOSH %p->connections[%d] = %p, state = %d"
127
0
                        ", requests = %d\n", conn, i, httpconn,
128
0
                        httpconn->state, httpconn->requests);
129
0
  }
130
0
}
131
132
static void http_connection_connect(PurpleHTTPConnection *conn);
133
static void http_connection_send_request(PurpleHTTPConnection *conn,
134
                                         const GString *req);
135
static gboolean send_timer_cb(gpointer data);
136
137
void jabber_bosh_init(void)
138
0
{
139
0
  GHashTable *ui_info = purple_core_get_ui_info();
140
0
  const char *ui_name = NULL;
141
0
  const char *ui_version = NULL;
142
143
0
  if (ui_info) {
144
0
    ui_name = g_hash_table_lookup(ui_info, "name");
145
0
    ui_version = g_hash_table_lookup(ui_info, "version");
146
0
  }
147
148
0
  if (ui_name)
149
0
    bosh_useragent = g_strdup_printf("%s%s%s (libpurple " VERSION ")",
150
0
                                     ui_name, ui_version ? " " : "",
151
0
                                     ui_version ? ui_version : "");
152
0
  else
153
0
    bosh_useragent = g_strdup("libpurple " VERSION);
154
0
}
155
156
void jabber_bosh_uninit(void)
157
0
{
158
0
  g_free(bosh_useragent);
159
0
  bosh_useragent = NULL;
160
0
}
161
162
static PurpleHTTPConnection*
163
jabber_bosh_http_connection_init(PurpleBOSHConnection *bosh)
164
0
{
165
0
  PurpleHTTPConnection *conn = g_new0(PurpleHTTPConnection, 1);
166
0
  conn->bosh = bosh;
167
0
  conn->fd = -1;
168
0
  conn->state = HTTP_CONN_OFFLINE;
169
170
0
  conn->write_buf = purple_circ_buffer_new(0 /* default grow size */);
171
172
0
  return conn;
173
0
}
174
175
static void
176
jabber_bosh_http_connection_destroy(PurpleHTTPConnection *conn)
177
0
{
178
0
  if (conn->read_buf)
179
0
    g_string_free(conn->read_buf, TRUE);
180
181
0
  if (conn->write_buf)
182
0
    purple_circ_buffer_destroy(conn->write_buf);
183
0
  if (conn->readh)
184
0
    purple_input_remove(conn->readh);
185
0
  if (conn->writeh)
186
0
    purple_input_remove(conn->writeh);
187
0
  if (conn->psc)
188
0
    purple_ssl_close(conn->psc);
189
0
  if (conn->fd >= 0)
190
0
    close(conn->fd);
191
192
0
  purple_proxy_connect_cancel_with_handle(conn);
193
194
0
  g_free(conn);
195
0
}
196
197
PurpleBOSHConnection*
198
jabber_bosh_connection_init(JabberStream *js, const char *url)
199
0
{
200
0
  PurpleBOSHConnection *conn;
201
0
  char *host, *path, *user, *passwd;
202
0
  int port;
203
204
0
  if (!purple_url_parse(url, &host, &port, &path, &user, &passwd)) {
205
0
    purple_debug_info("jabber", "Unable to parse given URL.\n");
206
0
    return NULL;
207
0
  }
208
209
0
  conn = g_new0(PurpleBOSHConnection, 1);
210
0
  conn->host = host;
211
0
  conn->port = port;
212
0
  conn->path = g_strdup_printf("/%s", path);
213
0
  g_free(path);
214
215
0
  if (purple_ip_address_is_valid(host))
216
0
    js->serverFQDN = g_strdup(js->user->domain);
217
0
  else
218
0
    js->serverFQDN = g_strdup(host);
219
220
0
  if ((user && user[0] != '\0') || (passwd && passwd[0] != '\0')) {
221
0
    purple_debug_info("jabber", "Ignoring unexpected username and password "
222
0
                                "in BOSH URL.\n");
223
0
  }
224
225
0
  g_free(user);
226
0
  g_free(passwd);
227
228
0
  conn->js = js;
229
230
  /*
231
   * Random 64-bit integer masked off by 2^52 - 1.
232
   *
233
   * This should produce a random integer in the range [0, 2^52). It's
234
   * unlikely we'll send enough packets in one session to overflow the rid.
235
   */
236
0
  conn->rid = ((guint64)g_random_int() << 32) | g_random_int();
237
0
  conn->rid &= 0xFFFFFFFFFFFFFLL;
238
239
0
  conn->pending = purple_circ_buffer_new(0 /* default grow size */);
240
241
0
  conn->state = BOSH_CONN_OFFLINE;
242
0
  if (purple_strcasestr(url, "https://") != NULL)
243
0
    conn->ssl = TRUE;
244
0
  else
245
0
    conn->ssl = FALSE;
246
247
0
  conn->connections[0] = jabber_bosh_http_connection_init(conn);
248
249
0
  return conn;
250
0
}
251
252
void
253
jabber_bosh_connection_destroy(PurpleBOSHConnection *conn)
254
0
{
255
0
  int i;
256
257
0
  g_free(conn->host);
258
0
  g_free(conn->path);
259
260
0
  if (conn->send_timer)
261
0
    purple_timeout_remove(conn->send_timer);
262
263
0
  purple_circ_buffer_destroy(conn->pending);
264
265
0
  for (i = 0; i < NUM_HTTP_CONNECTIONS; ++i) {
266
0
    if (conn->connections[i])
267
0
      jabber_bosh_http_connection_destroy(conn->connections[i]);
268
0
  }
269
270
0
  g_free(conn);
271
0
}
272
273
gboolean jabber_bosh_connection_is_ssl(PurpleBOSHConnection *conn)
274
0
{
275
0
  return conn->ssl;
276
0
}
277
278
static PurpleHTTPConnection *
279
find_available_http_connection(PurpleBOSHConnection *conn)
280
0
{
281
0
  int i;
282
283
0
  if (purple_debug_is_verbose())
284
0
    debug_dump_http_connections(conn);
285
286
  /* First loop, look for a connection that's ready */
287
0
  for (i = 0; i < NUM_HTTP_CONNECTIONS; ++i) {
288
0
    if (conn->connections[i] &&
289
0
        conn->connections[i]->state == HTTP_CONN_CONNECTED &&
290
0
        conn->connections[i]->requests == 0)
291
0
      return conn->connections[i];
292
0
  }
293
294
  /* Second loop, is something currently connecting? If so, just queue up. */
295
0
  for (i = 0; i < NUM_HTTP_CONNECTIONS; ++i) {
296
0
    if (conn->connections[i] &&
297
0
        conn->connections[i]->state == HTTP_CONN_CONNECTING)
298
0
      return NULL;
299
0
  }
300
301
  /* Third loop, is something offline that we can connect? */
302
0
  for (i = 0; i < NUM_HTTP_CONNECTIONS; ++i) {
303
0
    if (conn->connections[i] &&
304
0
        conn->connections[i]->state == HTTP_CONN_OFFLINE) {
305
0
      purple_debug_info("jabber", "bosh: Reconnecting httpconn "
306
0
                                  "(%i, %p)\n", i, conn->connections[i]);
307
0
      http_connection_connect(conn->connections[i]);
308
0
      return NULL;
309
0
    }
310
0
  }
311
312
  /* Fourth loop, look for one that's NULL and create a new connection */
313
0
  for (i = 0; i < NUM_HTTP_CONNECTIONS; ++i) {
314
0
    if (!conn->connections[i]) {
315
0
      conn->connections[i] = jabber_bosh_http_connection_init(conn);
316
0
      purple_debug_info("jabber", "bosh: Creating and connecting new httpconn "
317
0
                                  "(%i, %p)\n", i, conn->connections[i]);
318
319
0
      http_connection_connect(conn->connections[i]);
320
0
      return NULL;
321
0
    }
322
0
  }
323
324
0
  purple_debug_warning("jabber", "Could not find a HTTP connection!\n");
325
326
  /* None available. */
327
0
  return NULL;
328
0
}
329
330
static void
331
jabber_bosh_connection_send(PurpleBOSHConnection *conn,
332
                            const PurpleBOSHPacketType type, const char *data)
333
0
{
334
0
  PurpleHTTPConnection *chosen;
335
0
  GString *packet = NULL;
336
337
0
  if (type != PACKET_FLUSH && type != PACKET_TERMINATE) {
338
    /*
339
     * Unless this is a flush (or session terminate, which needs to be
340
     * sent immediately), queue up the data and start a timer to flush
341
     * the buffer.
342
     */
343
0
    if (data)
344
0
      purple_circ_buffer_append(conn->pending, data, strlen(data));
345
346
0
    if (purple_debug_is_verbose())
347
0
      purple_debug_misc("jabber", "bosh: %p has %" G_GSIZE_FORMAT " bytes in "
348
0
                        "the buffer.\n", conn, conn->pending->bufused);
349
0
    if (conn->send_timer == 0)
350
0
      conn->send_timer = purple_timeout_add_seconds(BUFFER_SEND_IN_SECS,
351
0
          send_timer_cb, conn);
352
0
    return;
353
0
  }
354
355
0
  chosen = find_available_http_connection(conn);
356
357
0
  if (!chosen) {
358
0
    if (type == PACKET_FLUSH)
359
0
      return;
360
    /*
361
     * For non-ordinary traffic, we can't 'buffer' it, so use the
362
     * first connection.
363
     */
364
0
    chosen = conn->connections[0];
365
366
0
    if (chosen->state != HTTP_CONN_CONNECTED) {
367
0
      purple_debug_warning("jabber", "Unable to find a ready BOSH "
368
0
          "connection. Ignoring send of type 0x%02x.\n", type);
369
0
      return;
370
0
    }
371
0
  }
372
373
  /* We're flushing the send buffer, so remove the send timer */
374
0
  if (conn->send_timer != 0) {
375
0
    purple_timeout_remove(conn->send_timer);
376
0
    conn->send_timer = 0;
377
0
  }
378
379
0
  packet = g_string_new(NULL);
380
381
0
  g_string_printf(packet, "<body "
382
0
                  "rid='%" G_GUINT64_FORMAT "' "
383
0
                  "sid='%s' "
384
0
                  "to='%s' "
385
0
                  "xml:lang='en' "
386
0
                  "xmlns='" NS_BOSH "' "
387
0
                  "xmlns:xmpp='" NS_XMPP_BOSH "'",
388
0
                  ++conn->rid,
389
0
                  conn->sid,
390
0
                  conn->js->user->domain);
391
392
0
  if (conn->js->reinit) {
393
0
    packet = g_string_append(packet, " xmpp:restart='true'/>");
394
    /* TODO: Do we need to wait for a response? */
395
0
    conn->js->reinit = FALSE;
396
0
  } else {
397
0
    gsize read_amt;
398
0
    if (type == PACKET_TERMINATE)
399
0
      packet = g_string_append(packet, " type='terminate'");
400
401
0
    packet = g_string_append_c(packet, '>');
402
403
0
    while ((read_amt = purple_circ_buffer_get_max_read(conn->pending)) > 0) {
404
0
      packet = g_string_append_len(packet, conn->pending->outptr, read_amt);
405
0
      purple_circ_buffer_mark_read(conn->pending, read_amt);
406
0
    }
407
408
0
    if (data)
409
0
      packet = g_string_append(packet, data);
410
0
    packet = g_string_append(packet, "</body>");
411
0
  }
412
413
0
  http_connection_send_request(chosen, packet);
414
0
}
415
416
void jabber_bosh_connection_close(PurpleBOSHConnection *conn)
417
0
{
418
0
  if (conn->state == BOSH_CONN_ONLINE)
419
0
    jabber_bosh_connection_send(conn, PACKET_TERMINATE, NULL);
420
0
}
421
422
0
static gboolean jabber_bosh_connection_error_check(PurpleBOSHConnection *conn, xmlnode *node) {
423
0
  const char *type;
424
425
0
  type = xmlnode_get_attrib(node, "type");
426
427
0
  if (purple_strequal(type, "terminate")) {
428
0
    conn->state = BOSH_CONN_OFFLINE;
429
0
    purple_connection_error_reason(conn->js->gc,
430
0
      PURPLE_CONNECTION_ERROR_OTHER_ERROR,
431
0
      _("The BOSH connection manager terminated your session."));
432
0
    return TRUE;
433
0
  }
434
0
  return FALSE;
435
0
}
436
437
static gboolean
438
send_timer_cb(gpointer data)
439
0
{
440
0
  PurpleBOSHConnection *bosh;
441
442
0
  bosh = data;
443
0
  bosh->send_timer = 0;
444
445
0
  jabber_bosh_connection_send(bosh, PACKET_FLUSH, NULL);
446
447
0
  return FALSE;
448
0
}
449
450
void
451
jabber_bosh_connection_send_keepalive(PurpleBOSHConnection *bosh)
452
0
{
453
0
  if (bosh->send_timer != 0)
454
0
    purple_timeout_remove(bosh->send_timer);
455
456
  /* clears bosh->send_timer */
457
0
  send_timer_cb(bosh);
458
0
}
459
460
0
static void jabber_bosh_connection_received(PurpleBOSHConnection *conn, xmlnode *node) {
461
0
  xmlnode *child;
462
0
  JabberStream *js = conn->js;
463
464
0
  g_return_if_fail(node != NULL);
465
0
  if (jabber_bosh_connection_error_check(conn, node))
466
0
    return;
467
468
0
  child = node->child;
469
0
  while (child != NULL) {
470
    /* jabber_process_packet might free child */
471
0
    xmlnode *next = child->next;
472
0
    if (child->type == XMLNODE_TYPE_TAG) {
473
0
      const char *xmlns = xmlnode_get_namespace(child);
474
      /*
475
       * Workaround for non-compliant servers that don't stamp
476
       * the right xmlns on these packets.  See #11315.
477
       */
478
0
      if ((xmlns == NULL /* shouldn't happen, but is equally wrong */ ||
479
0
          purple_strequal(xmlns, NS_BOSH)) &&
480
0
        (purple_strequal(child->name, "iq") ||
481
0
         purple_strequal(child->name, "message") ||
482
0
         purple_strequal(child->name, "presence"))) {
483
0
        xmlnode_set_namespace(child, NS_XMPP_CLIENT);
484
0
      }
485
0
      jabber_process_packet(js, &child);
486
0
    }
487
488
0
    child = next;
489
0
  }
490
0
}
491
492
0
static void boot_response_cb(PurpleBOSHConnection *conn, xmlnode *node) {
493
0
  JabberStream *js = conn->js;
494
0
  const char *sid, *version;
495
0
  const char *inactivity, *requests;
496
0
  xmlnode *packet;
497
498
0
  g_return_if_fail(node != NULL);
499
0
  if (jabber_bosh_connection_error_check(conn, node))
500
0
    return;
501
502
0
  sid = xmlnode_get_attrib(node, "sid");
503
0
  version = xmlnode_get_attrib(node, "ver");
504
505
0
  inactivity = xmlnode_get_attrib(node, "inactivity");
506
0
  requests = xmlnode_get_attrib(node, "requests");
507
508
0
  if (sid) {
509
0
    conn->sid = g_strdup(sid);
510
0
  } else {
511
0
    purple_connection_error_reason(js->gc,
512
0
            PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
513
0
            _("No session ID given"));
514
0
    return;
515
0
  }
516
517
0
  if (version) {
518
0
    const char *dot = strchr(version, '.');
519
0
    int major, minor = 0;
520
521
0
    purple_debug_info("jabber", "BOSH connection manager version %s\n", version);
522
523
0
    major = atoi(version);
524
0
    if (dot)
525
0
      minor = atoi(dot + 1);
526
527
0
    if (major != 1 || minor < 6) {
528
0
      purple_connection_error_reason(js->gc,
529
0
              PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
530
0
              _("Unsupported version of BOSH protocol"));
531
0
      return;
532
0
    }
533
0
  } else {
534
0
    purple_debug_info("jabber", "Missing version in BOSH initiation\n");
535
0
  }
536
537
0
  if (inactivity) {
538
0
    js->max_inactivity = atoi(inactivity);
539
0
    if (js->max_inactivity <= 5) {
540
0
      purple_debug_warning("jabber", "Ignoring bogusly small inactivity: %s\n",
541
0
                           inactivity);
542
      /* Leave it at the default */
543
0
    } else {
544
      /* TODO: Can this check fail? It shouldn't */
545
0
      js->max_inactivity -= 5; /* rounding */
546
547
0
      if (js->inactivity_timer == 0) {
548
0
        purple_debug_misc("jabber", "Starting BOSH inactivity timer "
549
0
            "for %d secs (compensating for rounding)\n",
550
0
            js->max_inactivity);
551
0
        jabber_stream_restart_inactivity_timer(js);
552
0
      }
553
0
    }
554
0
  }
555
556
0
  if (requests)
557
0
    conn->max_requests = atoi(requests);
558
559
0
  jabber_stream_set_state(js, JABBER_STREAM_AUTHENTICATING);
560
561
  /* FIXME: Depending on receiving features might break with some hosts */
562
0
  packet = xmlnode_get_child(node, "features");
563
0
  conn->state = BOSH_CONN_ONLINE;
564
0
  conn->receive_cb = jabber_bosh_connection_received;
565
0
  jabber_stream_features_parse(js, packet);
566
0
}
567
568
0
static void jabber_bosh_connection_boot(PurpleBOSHConnection *conn) {
569
0
  GString *buf = g_string_new(NULL);
570
571
0
  g_string_printf(buf, "<body content='text/xml; charset=utf-8' "
572
0
                  "secure='true' "
573
0
                  "to='%s' "
574
0
                  "xml:lang='en' "
575
0
                  "xmpp:version='1.0' "
576
0
                  "ver='1.6' "
577
0
                  "xmlns:xmpp='" NS_XMPP_BOSH "' "
578
0
                  "rid='%" G_GUINT64_FORMAT "' "
579
/* TODO: This should be adjusted/adjustable automatically according to
580
 * realtime network behavior */
581
0
                  "wait='60' "
582
0
                  "hold='1' "
583
0
                  "xmlns='" NS_BOSH "'/>",
584
0
                  conn->js->user->domain,
585
0
                  ++conn->rid);
586
587
0
  purple_debug_misc("jabber", "SendBOSH Boot %s(%" G_GSIZE_FORMAT "): %s\n",
588
0
                    conn->ssl ? "(ssl)" : "", buf->len, buf->str);
589
0
  conn->receive_cb = boot_response_cb;
590
0
  http_connection_send_request(conn->connections[0], buf);
591
0
  g_string_free(buf, TRUE);
592
0
}
593
594
/**
595
 * Handle one complete BOSH response. This is a <body> node containing
596
 * any number of XMPP stanzas.
597
 */
598
static void
599
http_received_cb(const char *data, int len, PurpleBOSHConnection *conn)
600
0
{
601
0
  xmlnode *node;
602
0
  gchar *message;
603
604
0
  if (conn->failed_connections)
605
    /* We've got some data, so reset the number of failed connections */
606
0
    conn->failed_connections = 0;
607
608
0
  g_return_if_fail(conn->receive_cb);
609
610
0
  node = xmlnode_from_str(data, len);
611
612
0
  message = g_strndup(data, len);
613
0
  purple_debug_info("jabber", "RecvBOSH %s(%d): %s\n",
614
0
                    conn->ssl ? "(ssl)" : "", len, message);
615
0
  g_free(message);
616
617
0
  if (node) {
618
0
    conn->receive_cb(conn, node);
619
0
    xmlnode_free(node);
620
0
  } else {
621
0
    purple_debug_warning("jabber", "BOSH: Received invalid XML\n");
622
0
  }
623
0
}
624
625
void jabber_bosh_connection_send_raw(PurpleBOSHConnection *conn,
626
                                     const char *data)
627
0
{
628
0
  jabber_bosh_connection_send(conn, PACKET_NORMAL, data);
629
0
}
630
631
static void
632
connection_common_established_cb(PurpleHTTPConnection *conn)
633
0
{
634
0
  purple_debug_misc("jabber", "bosh: httpconn %p re-connected\n", conn);
635
636
  /* Indicate we're ready and reset some variables */
637
0
  conn->state = HTTP_CONN_CONNECTED;
638
0
  if (conn->requests != 0)
639
0
    purple_debug_error("jabber", "bosh: httpconn %p has %d requests, != 0\n",
640
0
                       conn, conn->requests);
641
642
0
  conn->requests = 0;
643
0
  if (conn->read_buf) {
644
0
    g_string_free(conn->read_buf, TRUE);
645
0
    conn->read_buf = NULL;
646
0
  }
647
0
  conn->close = FALSE;
648
0
  conn->headers_done = FALSE;
649
0
  conn->handled_len = conn->body_len = 0;
650
651
0
  if (purple_debug_is_verbose())
652
0
    debug_dump_http_connections(conn->bosh);
653
654
0
  if (conn->bosh->js->reinit)
655
0
    jabber_bosh_connection_send(conn->bosh, PACKET_NORMAL, NULL);
656
0
  else if (conn->bosh->state == BOSH_CONN_ONLINE) {
657
0
    purple_debug_info("jabber", "BOSH session already exists. Trying to reuse it.\n");
658
0
    if (conn->bosh->requests == 0 || conn->bosh->pending->bufused > 0) {
659
      /* Send the pending data */
660
0
      jabber_bosh_connection_send(conn->bosh, PACKET_FLUSH, NULL);
661
0
    }
662
0
  } else
663
0
    jabber_bosh_connection_boot(conn->bosh);
664
0
}
665
666
static void http_connection_disconnected(PurpleHTTPConnection *conn)
667
0
{
668
0
  gboolean had_requests = FALSE;
669
  /*
670
   * Well, then. Fine! I never liked you anyway, server! I was cheating on you
671
   * with AIM!
672
   */
673
0
  conn->state = HTTP_CONN_OFFLINE;
674
0
  if (conn->psc) {
675
0
    purple_ssl_close(conn->psc);
676
0
    conn->psc = NULL;
677
0
  } else if (conn->fd >= 0) {
678
0
    close(conn->fd);
679
0
    conn->fd = -1;
680
0
  }
681
682
0
  if (conn->readh) {
683
0
    purple_input_remove(conn->readh);
684
0
    conn->readh = 0;
685
0
  }
686
687
0
  if (conn->writeh) {
688
0
    purple_input_remove(conn->writeh);
689
0
    conn->writeh = 0;
690
0
  }
691
692
0
  had_requests = (conn->requests > 0);
693
0
  if (had_requests && conn->read_buf->len == 0) {
694
0
    purple_debug_error("jabber", "bosh: Adjusting BOSHconn requests (%d) to %d\n",
695
0
                       conn->bosh->requests, conn->bosh->requests - conn->requests);
696
0
    conn->bosh->requests -= conn->requests;
697
0
    conn->requests = 0;
698
0
  }
699
700
0
  if (!had_requests)
701
    /* If the server disconnected us without any requests, let's
702
     * just wait until we have something to send before we reconnect
703
     */
704
0
    return;
705
706
0
  if (++conn->bosh->failed_connections == MAX_FAILED_CONNECTIONS) {
707
0
    purple_connection_error_reason(conn->bosh->js->gc,
708
0
        PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
709
0
        _("Unable to establish a connection with the server"));
710
0
  } else {
711
    /* No! Please! Take me back. It was me, not you! I was weak! */
712
0
    http_connection_connect(conn);
713
0
  }
714
0
}
715
716
0
void jabber_bosh_connection_connect(PurpleBOSHConnection *bosh) {
717
0
  PurpleHTTPConnection *conn = bosh->connections[0];
718
719
0
  g_return_if_fail(bosh->state == BOSH_CONN_OFFLINE);
720
0
  bosh->state = BOSH_CONN_BOOTING;
721
722
0
  http_connection_connect(conn);
723
0
}
724
725
/**
726
 * @return TRUE if we want to be called again immediately. This happens when
727
 *         we parse an HTTP response AND there is more data in read_buf. FALSE
728
 *         if we should not be called again unless more data has been read.
729
 */
730
static gboolean
731
jabber_bosh_http_connection_process(PurpleHTTPConnection *conn)
732
0
{
733
0
  const char *cursor;
734
735
0
  cursor = conn->read_buf->str + conn->handled_len;
736
737
0
  if (purple_debug_is_verbose())
738
0
    purple_debug_misc("jabber", "BOSH server sent: %s\n", cursor);
739
740
  /* TODO: Chunked encoding and check response version :/ */
741
0
  if (!conn->headers_done) {
742
0
    const char *content_length = purple_strcasestr(cursor, "\r\nContent-Length:");
743
0
    const char *connection = purple_strcasestr(cursor, "\r\nConnection:");
744
0
    const char *end_of_headers = strstr(cursor, "\r\n\r\n");
745
746
    /* Make sure Content-Length is in headers, not body */
747
0
    if (content_length && (!end_of_headers || content_length < end_of_headers)) {
748
0
      int len;
749
750
0
      if (strstr(content_length, "\r\n") == NULL)
751
        /*
752
         * The packet ends in the middle of the Content-Length line.
753
         * We'll try again later when we have more.
754
         */
755
0
        return FALSE;
756
757
0
      len = atoi(content_length + strlen("\r\nContent-Length:"));
758
0
      if (len == 0)
759
0
        purple_debug_warning("jabber", "Found mangled Content-Length header, or server returned 0-length response.\n");
760
761
0
      conn->body_len = len;
762
0
    }
763
764
0
    if (connection && (!end_of_headers || connection < end_of_headers)) {
765
0
      const char *tmp;
766
0
      if (strstr(connection, "\r\n") == NULL)
767
0
        return FALSE;
768
769
770
0
      tmp = connection + strlen("\r\nConnection:");
771
0
      while (*tmp && (*tmp == ' ' || *tmp == '\t'))
772
0
        ++tmp;
773
774
0
      if (!g_ascii_strncasecmp(tmp, "close", strlen("close"))) {
775
0
        conn->close = TRUE;
776
0
      }
777
0
    }
778
779
0
    if (end_of_headers) {
780
0
      conn->headers_done = TRUE;
781
0
      conn->handled_len = end_of_headers - conn->read_buf->str + 4;
782
0
    } else {
783
0
      conn->handled_len = conn->read_buf->len;
784
0
      return FALSE;
785
0
    }
786
0
  }
787
788
  /* Have we handled everything in the buffer? */
789
0
  if (conn->handled_len >= conn->read_buf->len)
790
0
    return FALSE;
791
792
  /* Have we read all that the Content-Length promised us? */
793
0
  if (conn->read_buf->len - conn->handled_len < conn->body_len)
794
0
    return FALSE;
795
796
0
  --conn->requests;
797
0
  --conn->bosh->requests;
798
799
0
  http_received_cb(conn->read_buf->str + conn->handled_len, conn->body_len,
800
0
                   conn->bosh);
801
802
  /* Is there another response in the buffer ? */
803
0
  if (conn->read_buf->len > conn->body_len + conn->handled_len) {
804
0
    g_string_erase(conn->read_buf, 0, conn->handled_len + conn->body_len);
805
0
    conn->headers_done = FALSE;
806
0
    conn->handled_len = conn->body_len = 0;
807
0
    return TRUE;
808
0
  }
809
810
  /* Connection: Close? */
811
0
  if (conn->close && conn->state == HTTP_CONN_CONNECTED) {
812
0
    if (purple_debug_is_verbose())
813
0
      purple_debug_misc("jabber", "bosh (%p), server sent Connection: "
814
0
                                  "close\n", conn);
815
0
    http_connection_disconnected(conn);
816
0
  }
817
818
0
  if (conn->bosh->state == BOSH_CONN_ONLINE &&
819
0
      (conn->bosh->requests == 0 || conn->bosh->pending->bufused > 0)) {
820
0
    purple_debug_misc("jabber", "BOSH: Sending an empty request\n");
821
0
    jabber_bosh_connection_send(conn->bosh, PACKET_NORMAL, NULL);
822
0
  }
823
824
0
  g_string_free(conn->read_buf, TRUE);
825
0
  conn->read_buf = NULL;
826
0
  conn->headers_done = FALSE;
827
0
  conn->handled_len = conn->body_len = 0;
828
829
0
  return FALSE;
830
0
}
831
832
/*
833
 * Common code for reading, called from http_connection_read_cb_ssl and
834
 * http_connection_read_cb.
835
 */
836
static void
837
http_connection_read(PurpleHTTPConnection *conn)
838
0
{
839
0
  char buffer[1025];
840
0
  int cnt;
841
842
0
  if (!conn->read_buf)
843
0
    conn->read_buf = g_string_new(NULL);
844
845
0
  do {
846
0
    if (conn->psc)
847
0
      cnt = purple_ssl_read(conn->psc, buffer, sizeof(buffer));
848
0
    else
849
0
      cnt = read(conn->fd, buffer, sizeof(buffer));
850
851
0
    if (cnt > 0) {
852
0
      g_string_append_len(conn->read_buf, buffer, cnt);
853
0
    }
854
0
  } while (cnt > 0);
855
856
0
  if (cnt == 0 || (cnt < 0 && errno != EAGAIN)) {
857
0
    if (cnt < 0)
858
0
      purple_debug_info("jabber", "BOSH (%p) read=%d, errno=%d, error=%s\n",
859
0
                        conn, cnt, errno, g_strerror(errno));
860
0
    else
861
0
      purple_debug_info("jabber", "BOSH server closed the connection (%p)\n",
862
0
                        conn);
863
864
    /*
865
     * If the socket is closed, the processing really needs to know about
866
     * it. Handle that now.
867
     */
868
0
    http_connection_disconnected(conn);
869
870
    /* Process what we do have */
871
0
  }
872
873
0
  if (conn->read_buf->len > 0) {
874
0
    while (jabber_bosh_http_connection_process(conn));
875
0
  }
876
0
}
877
878
static void
879
http_connection_read_cb(gpointer data, gint fd, PurpleInputCondition condition)
880
0
{
881
0
  PurpleHTTPConnection *conn = data;
882
883
0
  http_connection_read(conn);
884
0
}
885
886
static void
887
http_connection_read_cb_ssl(gpointer data, PurpleSslConnection *psc,
888
                            PurpleInputCondition cond)
889
0
{
890
0
  PurpleHTTPConnection *conn = data;
891
892
0
  http_connection_read(conn);
893
0
}
894
895
static void
896
ssl_connection_established_cb(gpointer data, PurpleSslConnection *psc,
897
                              PurpleInputCondition cond)
898
0
{
899
0
  PurpleHTTPConnection *conn = data;
900
901
0
  purple_ssl_input_add(psc, http_connection_read_cb_ssl, conn);
902
0
  connection_common_established_cb(conn);
903
0
}
904
905
static void
906
ssl_connection_error_cb(PurpleSslConnection *gsc, PurpleSslErrorType error,
907
                        gpointer data)
908
0
{
909
0
  PurpleHTTPConnection *conn = data;
910
911
  /* sslconn frees the connection on error */
912
0
  conn->psc = NULL;
913
914
0
  purple_connection_ssl_error(conn->bosh->js->gc, error);
915
0
}
916
917
static void
918
connection_established_cb(gpointer data, gint source, const gchar *error)
919
0
{
920
0
  PurpleHTTPConnection *conn = data;
921
0
  PurpleConnection *gc = conn->bosh->js->gc;
922
923
0
  if (source < 0) {
924
0
    gchar *tmp;
925
0
    tmp = g_strdup_printf(_("Unable to establish a connection with the server: %s"),
926
0
            error);
927
0
    purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, tmp);
928
0
    g_free(tmp);
929
0
    return;
930
0
  }
931
932
0
  conn->fd = source;
933
0
  conn->readh = purple_input_add(conn->fd, PURPLE_INPUT_READ,
934
0
          http_connection_read_cb, conn);
935
0
  connection_common_established_cb(conn);
936
0
}
937
938
static void http_connection_connect(PurpleHTTPConnection *conn)
939
0
{
940
0
  PurpleBOSHConnection *bosh = conn->bosh;
941
0
  PurpleConnection *gc = bosh->js->gc;
942
0
  PurpleAccount *account = purple_connection_get_account(gc);
943
944
0
  conn->state = HTTP_CONN_CONNECTING;
945
946
0
  if (bosh->ssl) {
947
0
    if (purple_ssl_is_supported()) {
948
0
      conn->psc = purple_ssl_connect(account, bosh->host, bosh->port,
949
0
                                     ssl_connection_established_cb,
950
0
                                     ssl_connection_error_cb,
951
0
                                     conn);
952
0
      if (!conn->psc) {
953
0
        purple_connection_error_reason(gc,
954
0
          PURPLE_CONNECTION_ERROR_NO_SSL_SUPPORT,
955
0
          _("Unable to establish SSL connection"));
956
0
      }
957
0
    } else {
958
0
      purple_connection_error_reason(gc,
959
0
          PURPLE_CONNECTION_ERROR_NO_SSL_SUPPORT,
960
0
          _("SSL support unavailable"));
961
0
    }
962
0
  } else if (purple_proxy_connect(conn, account, bosh->host, bosh->port,
963
0
                                   connection_established_cb, conn) == NULL) {
964
0
    purple_connection_error_reason(gc,
965
0
        PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
966
0
        _("Unable to connect"));
967
0
  }
968
0
}
969
970
static int
971
http_connection_do_send(PurpleHTTPConnection *conn, const char *data, int len)
972
0
{
973
0
  int ret;
974
975
0
  if (conn->psc)
976
0
    ret = purple_ssl_write(conn->psc, data, len);
977
0
  else
978
0
    ret = write(conn->fd, data, len);
979
980
0
  if (purple_debug_is_verbose())
981
0
    purple_debug_misc("jabber", "BOSH (%p): wrote %d bytes\n", conn, ret);
982
983
0
  return ret;
984
0
}
985
986
static void
987
http_connection_send_cb(gpointer data, gint source, PurpleInputCondition cond)
988
0
{
989
0
  PurpleHTTPConnection *conn = data;
990
0
  int ret;
991
0
  int writelen = purple_circ_buffer_get_max_read(conn->write_buf);
992
993
0
  if (writelen == 0) {
994
0
    purple_input_remove(conn->writeh);
995
0
    conn->writeh = 0;
996
0
    return;
997
0
  }
998
999
0
  ret = http_connection_do_send(conn, conn->write_buf->outptr, writelen);
1000
1001
0
  if (ret < 0 && errno == EAGAIN)
1002
0
    return;
1003
0
  else if (ret <= 0) {
1004
    /*
1005
     * TODO: Handle this better. Probably requires a PurpleBOSHConnection
1006
     * buffer that stores what is "being sent" until the
1007
     * PurpleHTTPConnection reports it is fully sent.
1008
     */
1009
0
    gchar *tmp = g_strdup_printf(_("Lost connection with server: %s"),
1010
0
        g_strerror(errno));
1011
0
    purple_connection_error_reason(conn->bosh->js->gc,
1012
0
        PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
1013
0
        tmp);
1014
0
    g_free(tmp);
1015
0
    return;
1016
0
  }
1017
1018
0
  purple_circ_buffer_mark_read(conn->write_buf, ret);
1019
0
}
1020
1021
static void
1022
http_connection_send_request(PurpleHTTPConnection *conn, const GString *req)
1023
0
{
1024
0
  char *data;
1025
0
  int ret;
1026
0
  size_t len;
1027
1028
  /* Sending something to the server, restart the inactivity timer */
1029
0
  jabber_stream_restart_inactivity_timer(conn->bosh->js);
1030
1031
0
  data = g_strdup_printf("POST %s HTTP/1.1\r\n"
1032
0
                         "Host: %s\r\n"
1033
0
                         "User-Agent: %s\r\n"
1034
0
                         "Content-Encoding: text/xml; charset=utf-8\r\n"
1035
0
                         "Content-Length: %" G_GSIZE_FORMAT "\r\n\r\n"
1036
0
                         "%s",
1037
0
                         conn->bosh->path, conn->bosh->host, bosh_useragent,
1038
0
                         req->len, req->str);
1039
1040
0
  len = strlen(data);
1041
1042
0
  ++conn->requests;
1043
0
  ++conn->bosh->requests;
1044
1045
0
  if (purple_debug_is_unsafe() && purple_debug_is_verbose())
1046
    /* Will contain passwords for SASL PLAIN and is verbose */
1047
0
    purple_debug_misc("jabber", "BOSH (%p): Sending %s\n", conn, data);
1048
0
  else if (purple_debug_is_verbose())
1049
0
    purple_debug_misc("jabber", "BOSH (%p): Sending request of "
1050
0
                                "%" G_GSIZE_FORMAT " bytes.\n", conn, len);
1051
1052
0
  if (conn->writeh == 0)
1053
0
    ret = http_connection_do_send(conn, data, len);
1054
0
  else {
1055
0
    ret = -1;
1056
0
    errno = EAGAIN;
1057
0
  }
1058
1059
0
  if (ret < 0 && errno != EAGAIN) {
1060
    /*
1061
     * TODO: Handle this better. Probably requires a PurpleBOSHConnection
1062
     * buffer that stores what is "being sent" until the
1063
     * PurpleHTTPConnection reports it is fully sent.
1064
     */
1065
0
    gchar *tmp = g_strdup_printf(_("Lost connection with server: %s"),
1066
0
        g_strerror(errno));
1067
0
    purple_connection_error_reason(conn->bosh->js->gc,
1068
0
        PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
1069
0
        tmp);
1070
0
    g_free(tmp);
1071
0
    return;
1072
0
  } else if ((size_t)ret < len) {
1073
0
    if (ret < 0)
1074
0
      ret = 0;
1075
0
    if (conn->writeh == 0)
1076
0
      conn->writeh = purple_input_add(conn->psc ? conn->psc->fd : conn->fd,
1077
0
          PURPLE_INPUT_WRITE, http_connection_send_cb, conn);
1078
0
    purple_circ_buffer_append(conn->write_buf, data + ret, len - ret);
1079
0
  }
1080
0
}
1081