comparison libpurple/protocols/jabber/bosh.c @ 25988:f36a94f19db3

Restore BOSH to a more-or-less working state. Major changes: * Obey the 'requests' attribute on session creation response. Extra requests are buffered until we can send something. * Attempt to gracefully fail from a proxy that doesn't allow pipelining to multiple TCP connections. Still to do: * SSL the Pidgin<>Connection Manager connection * Pay attention to 'inactivity' and 'polling' * The HTTP handler won't work if a read() on a pipelined connection returns data from one response as well as the beginning of a second response.
author Paul Aurich <paul@darkrain42.org>
date Sun, 15 Mar 2009 04:48:47 +0000
parents c11c14dde641
children 71835e00c0fc
comparison
equal deleted inserted replaced
25987:035ba9355361 25988:f36a94f19db3
26 #include "util.h" 26 #include "util.h"
27 #include "xmlnode.h" 27 #include "xmlnode.h"
28 28
29 #include "bosh.h" 29 #include "bosh.h"
30 30
31 typedef struct _PurpleHTTPRequest PurpleHTTPRequest; 31 #define MAX_HTTP_CONNECTIONS 2
32
32 typedef struct _PurpleHTTPConnection PurpleHTTPConnection; 33 typedef struct _PurpleHTTPConnection PurpleHTTPConnection;
33 34
34 typedef void (*PurpleHTTPConnectionConnectFunction)(PurpleHTTPConnection *conn); 35 typedef void (*PurpleHTTPConnectionConnectFunction)(PurpleHTTPConnection *conn);
35 typedef void (*PurpleHTTPConnectionDisconnectFunction)(PurpleHTTPConnection *conn); 36 typedef void (*PurpleHTTPConnectionDisconnectFunction)(PurpleHTTPConnection *conn);
36 typedef void (*PurpleBOSHConnectionConnectFunction)(PurpleBOSHConnection *conn); 37 typedef void (*PurpleBOSHConnectionConnectFunction)(PurpleBOSHConnection *conn);
37 typedef void (*PurpleBOSHConnectionReceiveFunction)(PurpleBOSHConnection *conn, xmlnode *node); 38 typedef void (*PurpleBOSHConnectionReceiveFunction)(PurpleBOSHConnection *conn, xmlnode *node);
38 39
39 static char *bosh_useragent = NULL; 40 static char *bosh_useragent = NULL;
41
42 typedef enum {
43 PACKET_TERMINATE,
44 PACKET_STREAM_RESTART,
45 PACKET_NORMAL,
46 } PurpleBOSHPacketType;
40 47
41 struct _PurpleBOSHConnection { 48 struct _PurpleBOSHConnection {
42 /* decoded URL */ 49 /* decoded URL */
43 char *host; 50 char *host;
44 int port; 51 int port;
49 char *sid; 56 char *sid;
50 int wait; 57 int wait;
51 58
52 JabberStream *js; 59 JabberStream *js;
53 gboolean pipelining; 60 gboolean pipelining;
54 PurpleHTTPConnection *conn_a; 61 PurpleHTTPConnection *connections[MAX_HTTP_CONNECTIONS];
55 PurpleHTTPConnection *conn_b; 62
63 int max_inactivity;
64 int max_requests;
65 int requests;
66 GString *pending;
56 67
57 gboolean ready; 68 gboolean ready;
58 PurpleBOSHConnectionConnectFunction connect_cb; 69 PurpleBOSHConnectionConnectFunction connect_cb;
59 PurpleBOSHConnectionReceiveFunction receive_cb; 70 PurpleBOSHConnectionReceiveFunction receive_cb;
60 }; 71 };
61 72
62 struct _PurpleHTTPConnection { 73 struct _PurpleHTTPConnection {
63 int fd; 74 int fd;
75 gboolean ready;
64 char *host; 76 char *host;
65 int port; 77 int port;
66 int ie_handle; 78 int ie_handle;
67 int requests; /* number of outstanding HTTP requests */ 79 int requests; /* number of outstanding HTTP requests */
68 80
69 GString *buf; 81 GString *buf;
70 gboolean headers_done; 82 gboolean headers_done;
71 gsize handled_len; 83 gsize handled_len;
72 gsize body_len; 84 gsize body_len;
75 PurpleHTTPConnectionConnectFunction connect_cb; 87 PurpleHTTPConnectionConnectFunction connect_cb;
76 PurpleHTTPConnectionConnectFunction disconnect_cb; 88 PurpleHTTPConnectionConnectFunction disconnect_cb;
77 PurpleBOSHConnection *bosh; 89 PurpleBOSHConnection *bosh;
78 }; 90 };
79 91
80 struct _PurpleHTTPRequest {
81 const char *path;
82 char *data;
83 int data_len;
84 };
85
86 static void jabber_bosh_connection_stream_restart(PurpleBOSHConnection *conn); 92 static void jabber_bosh_connection_stream_restart(PurpleBOSHConnection *conn);
87 static gboolean jabber_bosh_connection_error_check(PurpleBOSHConnection *conn, xmlnode *node); 93 static gboolean jabber_bosh_connection_error_check(PurpleBOSHConnection *conn, xmlnode *node);
88 static void jabber_bosh_connection_received(PurpleBOSHConnection *conn, xmlnode *node); 94 static void jabber_bosh_connection_received(PurpleBOSHConnection *conn, xmlnode *node);
89 static void jabber_bosh_connection_send_native(PurpleBOSHConnection *conn, xmlnode *node); 95 static void jabber_bosh_connection_send_native(PurpleBOSHConnection *conn, PurpleBOSHPacketType, xmlnode *node);
90 96
91 static void jabber_bosh_http_connection_connect(PurpleHTTPConnection *conn); 97 static void jabber_bosh_http_connection_connect(PurpleHTTPConnection *conn);
92 static void jabber_bosh_http_connection_send_request(PurpleHTTPConnection *conn, PurpleHTTPRequest *req); 98 static void jabber_bosh_connection_connected(PurpleHTTPConnection *conn);
99 static void jabber_bosh_http_connection_disconnected(PurpleHTTPConnection *conn);
100 static void jabber_bosh_http_connection_send_request(PurpleHTTPConnection *conn, const GString *req);
93 101
94 void jabber_bosh_init(void) 102 void jabber_bosh_init(void)
95 { 103 {
96 GHashTable *ui_info = purple_core_get_ui_info(); 104 GHashTable *ui_info = purple_core_get_ui_info();
97 const char *ui_name = NULL; 105 const char *ui_name = NULL;
114 { 122 {
115 g_free(bosh_useragent); 123 g_free(bosh_useragent);
116 bosh_useragent = NULL; 124 bosh_useragent = NULL;
117 } 125 }
118 126
119 static void
120 jabber_bosh_http_request_destroy(PurpleHTTPRequest *req)
121 {
122 g_free(req->data);
123 g_free(req);
124 }
125
126 static PurpleHTTPConnection* 127 static PurpleHTTPConnection*
127 jabber_bosh_http_connection_init(const char *host, int port) 128 jabber_bosh_http_connection_init(PurpleBOSHConnection *bosh)
128 { 129 {
129 PurpleHTTPConnection *conn = g_new0(PurpleHTTPConnection, 1); 130 PurpleHTTPConnection *conn = g_new0(PurpleHTTPConnection, 1);
130 conn->host = g_strdup(host); 131 conn->bosh = bosh;
131 conn->port = port; 132 conn->host = g_strdup(bosh->host);
133 conn->port = bosh->port;
132 conn->fd = -1; 134 conn->fd = -1;
135 conn->ready = FALSE;
133 136
134 return conn; 137 return conn;
135 } 138 }
136 139
137 static void 140 static void
178 g_free(passwd); 181 g_free(passwd);
179 182
180 conn->js = js; 183 conn->js = js;
181 /* FIXME: This doesn't seem very random */ 184 /* FIXME: This doesn't seem very random */
182 conn->rid = rand() % 100000 + 1728679472; 185 conn->rid = rand() % 100000 + 1728679472;
186
187 conn->pending = g_string_new("");
188
183 conn->ready = FALSE; 189 conn->ready = FALSE;
184 conn->conn_a = jabber_bosh_http_connection_init(conn->host, conn->port); 190
185 conn->conn_a->bosh = conn; 191 conn->connections[0] = jabber_bosh_http_connection_init(conn);
186 192
187 return conn; 193 return conn;
188 } 194 }
189 195
190 void 196 void
191 jabber_bosh_connection_destroy(PurpleBOSHConnection *conn) 197 jabber_bosh_connection_destroy(PurpleBOSHConnection *conn)
192 { 198 {
199 int i;
200
193 g_free(conn->host); 201 g_free(conn->host);
194 g_free(conn->path); 202 g_free(conn->path);
195 203
196 if (conn->conn_a) 204 if (conn->pending)
197 jabber_bosh_http_connection_destroy(conn->conn_a); 205 g_string_free(conn->pending, TRUE);
198 if (conn->conn_b) 206
199 jabber_bosh_http_connection_destroy(conn->conn_b); 207 for (i = 0; i < MAX_HTTP_CONNECTIONS; ++i) {
208 if (conn->connections[i])
209 jabber_bosh_http_connection_destroy(conn->connections[i]);
210 }
200 211
201 g_free(conn); 212 g_free(conn);
202 } 213 }
203 214
204 void jabber_bosh_connection_close(PurpleBOSHConnection *conn) 215 void jabber_bosh_connection_close(PurpleBOSHConnection *conn)
205 { 216 {
206 xmlnode *packet = xmlnode_new("body"); 217 jabber_bosh_connection_send_native(conn, PACKET_TERMINATE, NULL);
207 /* XEP-0124: The rid must not exceed 16 characters */
208 char rid[17];
209 g_snprintf(rid, sizeof(rid), "%" G_GUINT64_FORMAT, ++conn->rid);
210 xmlnode_set_attrib(packet, "type", "terminate");
211 xmlnode_set_attrib(packet, "xmlns", "http://jabber.org/protocol/httpbind");
212 xmlnode_set_attrib(packet, "sid", conn->sid);
213 xmlnode_set_attrib(packet, "rid", rid);
214
215 jabber_bosh_connection_send_native(conn, packet);
216 xmlnode_free(packet);
217 } 218 }
218 219
219 static void jabber_bosh_connection_stream_restart(PurpleBOSHConnection *conn) { 220 static void jabber_bosh_connection_stream_restart(PurpleBOSHConnection *conn) {
220 xmlnode *restart = xmlnode_new("body"); 221 jabber_bosh_connection_send_native(conn, PACKET_STREAM_RESTART, NULL);
221 /* XEP-0124: The rid must not exceed 16 characters */
222 char rid[17];
223 g_snprintf(rid, sizeof(rid), "%" G_GUINT64_FORMAT, ++conn->rid);
224 xmlnode_set_attrib(restart, "rid", rid);
225 xmlnode_set_attrib(restart, "sid", conn->sid);
226 xmlnode_set_attrib(restart, "to", conn->js->user->domain);
227 xmlnode_set_attrib(restart, "xml:lang", "en");
228 xmlnode_set_attrib(restart, "xmpp:restart", "true");
229 xmlnode_set_attrib(restart, "xmlns", "http://jabber.org/protocol/httpbind");
230 xmlnode_set_attrib(restart, "xmlns:xmpp", "urn:xmpp:xbosh");
231
232 jabber_bosh_connection_send_native(conn, restart);
233 xmlnode_free(restart);
234 } 222 }
235 223
236 static gboolean jabber_bosh_connection_error_check(PurpleBOSHConnection *conn, xmlnode *node) { 224 static gboolean jabber_bosh_connection_error_check(PurpleBOSHConnection *conn, xmlnode *node) {
237 const char *type; 225 const char *type;
238 226
240 228
241 if (type != NULL && !strcmp(type, "terminate")) { 229 if (type != NULL && !strcmp(type, "terminate")) {
242 conn->ready = FALSE; 230 conn->ready = FALSE;
243 purple_connection_error_reason (conn->js->gc, 231 purple_connection_error_reason (conn->js->gc,
244 PURPLE_CONNECTION_ERROR_OTHER_ERROR, 232 PURPLE_CONNECTION_ERROR_OTHER_ERROR,
245 _("The BOSH conncetion manager suggested to terminate your session.")); 233 _("The BOSH connection manager terminated your session."));
246 return TRUE; 234 return TRUE;
247 } 235 }
248 return FALSE; 236 return FALSE;
249 } 237 }
250 238
301 } 289 }
302 } 290 }
303 291
304 static void boot_response_cb(PurpleBOSHConnection *conn, xmlnode *node) { 292 static void boot_response_cb(PurpleBOSHConnection *conn, xmlnode *node) {
305 const char *sid, *version; 293 const char *sid, *version;
294 const char *inactivity, *requests;
306 xmlnode *packet; 295 xmlnode *packet;
307 296
308 g_return_if_fail(node != NULL); 297 g_return_if_fail(node != NULL);
309 if (jabber_bosh_connection_error_check(conn, node)) 298 if (jabber_bosh_connection_error_check(conn, node))
310 return; 299 return;
311 300
312 sid = xmlnode_get_attrib(node, "sid"); 301 sid = xmlnode_get_attrib(node, "sid");
313 version = xmlnode_get_attrib(node, "ver"); 302 version = xmlnode_get_attrib(node, "ver");
303
304 inactivity = xmlnode_get_attrib(node, "inactivity");
305 requests = xmlnode_get_attrib(node, "requests");
314 306
315 if (sid) { 307 if (sid) {
316 conn->sid = g_strdup(sid); 308 conn->sid = g_strdup(sid);
317 } else { 309 } else {
318 purple_connection_error_reason(conn->js->gc, 310 purple_connection_error_reason(conn->js->gc,
336 } 328 }
337 } else { 329 } else {
338 purple_debug_info("jabber", "Missing version in BOSH initiation\n"); 330 purple_debug_info("jabber", "Missing version in BOSH initiation\n");
339 } 331 }
340 332
333 if (inactivity)
334 conn->max_inactivity = atoi(inactivity);
335
336 if (requests)
337 conn->max_requests = atoi(requests);
338
341 /* FIXME: Depending on receiving features might break with some hosts */ 339 /* FIXME: Depending on receiving features might break with some hosts */
342 packet = xmlnode_get_child(node, "features"); 340 packet = xmlnode_get_child(node, "features");
343 conn->js->use_bosh = TRUE; 341 conn->js->use_bosh = TRUE;
344 conn->receive_cb = auth_response_cb; 342 conn->receive_cb = auth_response_cb;
345 jabber_stream_features_parse(conn->js, packet); 343 jabber_stream_features_parse(conn->js, packet);
346 } 344 }
347 345
346 static PurpleHTTPConnection *
347 find_available_http_connection(PurpleBOSHConnection *conn)
348 {
349 int i;
350
351 /* Easy solution: Does everyone involved support pipelining? Hooray! Just use
352 * one TCP connection! */
353 if (conn->pipelining)
354 return conn->connections[0];
355
356 /* First loop, look for a connection that's ready */
357 for (i = 0; i < MAX_HTTP_CONNECTIONS; ++i) {
358 if (conn->connections[i] && conn->connections[i]->ready &&
359 conn->connections[i]->requests == 0)
360 return conn->connections[i];
361 }
362
363 /* Second loop, look for one that's NULL and create a new connection */
364 for (i = 0; i < MAX_HTTP_CONNECTIONS; ++i) {
365 if (!conn->connections[i]) {
366 conn->connections[i] = jabber_bosh_http_connection_init(conn);
367
368 conn->connections[i]->connect_cb = jabber_bosh_connection_connected;
369 conn->connections[i]->disconnect_cb = jabber_bosh_http_connection_disconnected;
370 jabber_bosh_http_connection_connect(conn->connections[i]);
371 return NULL;
372 }
373 }
374
375 /* None available. */
376 return NULL;
377 }
378
348 static void jabber_bosh_connection_boot(PurpleBOSHConnection *conn) { 379 static void jabber_bosh_connection_boot(PurpleBOSHConnection *conn) {
349 xmlnode *init = xmlnode_new("body"); 380 GString *buf = g_string_new("");
350 /* XEP-0124: The rid must not exceed 16 characters */ 381
351 char rid[17]; 382 g_string_printf(buf, "<body content='text/xml; charset=utf-8' "
352 g_snprintf(rid, sizeof(rid), "%" G_GUINT64_FORMAT, ++conn->rid); 383 "secure='true' "
353 xmlnode_set_attrib(init, "content", "text/xml; charset=utf-8"); 384 "to='%s' "
354 xmlnode_set_attrib(init, "secure", "true"); 385 "xml:lang='en' "
355 /* 386 "xmpp:version='1.0' "
356 xmlnode_set_attrib(init, "route", tmp = g_strdup_printf("xmpp:%s:5222", conn->js->user->domain)); 387 "ver='1.6' "
357 g_free(tmp); 388 "xmlns:xmpp='urn:xmpp:bosh' "
358 */ 389 "rid='%" G_GUINT64_FORMAT "' "
359 xmlnode_set_attrib(init, "to", conn->js->user->domain); 390 /* TODO: This should be adjusted/adjustable automatically according to
360 xmlnode_set_attrib(init, "xml:lang", "en"); 391 * realtime network behavior */
361 xmlnode_set_attrib(init, "xmpp:version", "1.0"); 392 "wait='60' "
362 xmlnode_set_attrib(init, "ver", "1.6"); 393 "hold='1' "
363 xmlnode_set_attrib(init, "xmlns:xmpp", "urn:xmpp:xbosh"); 394 "xmlns='http://jabber.org/protocol/httpbind'/>",
364 xmlnode_set_attrib(init, "rid", rid); 395 conn->js->user->domain,
365 xmlnode_set_attrib(init, "wait", "60"); /* this should be adjusted automatically according to real time network behavior */ 396 ++conn->rid);
366 xmlnode_set_attrib(init, "xmlns", "http://jabber.org/protocol/httpbind"); 397
367 xmlnode_set_attrib(init, "hold", "1");
368
369 conn->receive_cb = boot_response_cb; 398 conn->receive_cb = boot_response_cb;
370 jabber_bosh_connection_send_native(conn, init); 399 jabber_bosh_http_connection_send_request(conn->connections[0], buf);
371 xmlnode_free(init); 400 g_string_free(buf, TRUE);
372 } 401 }
373 402
374 static void 403 static void
375 http_received_cb(const char *data, int len, PurpleBOSHConnection *conn) 404 http_received_cb(const char *data, int len, PurpleBOSHConnection *conn)
376 { 405 {
389 g_return_if_reached(); 418 g_return_if_reached();
390 } 419 }
391 } 420 }
392 421
393 void jabber_bosh_connection_send(PurpleBOSHConnection *conn, xmlnode *node) { 422 void jabber_bosh_connection_send(PurpleBOSHConnection *conn, xmlnode *node) {
394 xmlnode *packet = xmlnode_new("body"); 423 jabber_bosh_connection_send_native(conn, PACKET_NORMAL, node);
395 /* XEP-0124: The rid must not exceed 16 characters */
396 char rid[17];
397 g_snprintf(rid, sizeof(rid), "%" G_GUINT64_FORMAT, ++conn->rid);
398 xmlnode_set_attrib(packet, "xmlns", "http://jabber.org/protocol/httpbind");
399 xmlnode_set_attrib(packet, "sid", conn->sid);
400 xmlnode_set_attrib(packet, "rid", rid);
401
402 if (node) {
403 xmlnode *copy = xmlnode_copy(node);
404 xmlnode_insert_child(packet, copy);
405 if (conn->ready == TRUE)
406 xmlnode_set_attrib(copy, "xmlns", "jabber:client");
407 }
408 jabber_bosh_connection_send_native(conn, packet);
409 xmlnode_free(packet);
410 } 424 }
411 425
412 void jabber_bosh_connection_send_raw(PurpleBOSHConnection *conn, 426 void jabber_bosh_connection_send_raw(PurpleBOSHConnection *conn,
413 const char *data, int len) 427 const char *data, int len)
414 { 428 {
415 xmlnode *node = xmlnode_from_str(data, len); 429 xmlnode *node = xmlnode_from_str(data, len);
416 if (node) { 430 if (node) {
417 jabber_bosh_connection_send(conn, node); 431 jabber_bosh_connection_send_native(conn, PACKET_NORMAL, node);
418 xmlnode_free(node); 432 xmlnode_free(node);
419 } else { 433 } else {
420 /* 434 /*
421 * This best emulates what a normal XMPP server would do 435 * This best emulates what a normal XMPP server would do
422 * if you send bad XML. 436 * if you send bad XML.
425 PURPLE_CONNECTION_ERROR_NETWORK_ERROR, 439 PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
426 _("Cannot send malformed XML")); 440 _("Cannot send malformed XML"));
427 } 441 }
428 } 442 }
429 443
430 static void jabber_bosh_connection_send_native(PurpleBOSHConnection *conn, xmlnode *node) { 444 static void
431 PurpleHTTPRequest *request; 445 jabber_bosh_connection_send_native(PurpleBOSHConnection *conn, PurpleBOSHPacketType type,
446 xmlnode *node)
447 {
448 PurpleHTTPConnection *chosen;
449 GString *packet = NULL;
450 char *buf = NULL;
451
452 chosen = find_available_http_connection(conn);
453
454 if (type != PACKET_NORMAL && !chosen) {
455 /*
456 * For non-ordinary traffic, we don't want to 'buffer' it, so use the first
457 * connection.
458 */
459 chosen = conn->connections[0];
432 460
433 request = g_new0(PurpleHTTPRequest, 1); 461 if (!chosen->ready)
434 request->path = conn->path; 462 purple_debug_warning("jabber", "First BOSH connection wasn't ready. Bad "
435 request->data = xmlnode_to_str(node, &(request->data_len)); 463 "things may happen.\n");
436 464 }
437 jabber_bosh_http_connection_send_request(conn->conn_a, request); 465
466 if (node)
467 buf = xmlnode_to_str(node, NULL);
468
469 if (type == PACKET_NORMAL && (!chosen ||
470 (conn->max_requests > 0 && conn->requests == conn->max_requests))) {
471 /*
472 * For normal data, send up to max_requests requests at a time or there is no
473 * connection ready (likely, we're currently opening a second connection and
474 * will send these packets when connected).
475 */
476 if (buf) {
477 conn->pending = g_string_append(conn->pending, buf);
478 g_free(buf);
479 }
480 return;
481 }
482
483 packet = g_string_new("");
484
485 g_string_printf(packet, "<body "
486 "rid='%" G_GUINT64_FORMAT "' "
487 "sid='%s' "
488 "to='%s' "
489 "xml:lang='en' "
490 "xmlns='http://jabber.org/protocol/httpbind' "
491 "xmlns:xmpp='urn:xmpp:xbosh'",
492 ++conn->rid,
493 conn->sid,
494 conn->js->user->domain);
495
496 if (type == PACKET_STREAM_RESTART)
497 packet = g_string_append(packet, " xmpp:restart='true'/>");
498 else {
499 if (type == PACKET_TERMINATE)
500 packet = g_string_append(packet, " type='terminate'");
501
502 g_string_append_printf(packet, ">%s%s</body>", conn->pending->str,
503 buf ? buf : "");
504 g_string_truncate(conn->pending, 0);
505 }
506
507 g_free(buf);
508
509 jabber_bosh_http_connection_send_request(chosen, packet);
438 } 510 }
439 511
440 static void jabber_bosh_connection_connected(PurpleHTTPConnection *conn) { 512 static void jabber_bosh_connection_connected(PurpleHTTPConnection *conn) {
441 if (conn->bosh->ready == TRUE && conn->bosh->connect_cb) { 513 conn->ready = TRUE;
514
515 if (conn->bosh->ready) {
442 purple_debug_info("jabber", "BOSH session already exists. Trying to reuse it.\n"); 516 purple_debug_info("jabber", "BOSH session already exists. Trying to reuse it.\n");
517 if (conn->bosh->pending && conn->bosh->pending->len > 0) {
518 /* Send the pending data */
519 jabber_bosh_connection_send_native(conn->bosh, PACKET_NORMAL, NULL);
520 }
521 #if 0
443 conn->bosh->receive_cb = jabber_bosh_connection_received; 522 conn->bosh->receive_cb = jabber_bosh_connection_received;
444 conn->bosh->connect_cb(conn->bosh); 523 if (conn->bosh->connect_cb)
524 conn->bosh->connect_cb(conn->bosh);
525 #endif
445 } else 526 } else
446 jabber_bosh_connection_boot(conn->bosh); 527 jabber_bosh_connection_boot(conn->bosh);
447 } 528 }
448 529
449 void jabber_bosh_connection_refresh(PurpleBOSHConnection *conn) 530 void jabber_bosh_connection_refresh(PurpleBOSHConnection *conn)
450 { 531 {
451 jabber_bosh_connection_send(conn, NULL); 532 jabber_bosh_connection_send(conn, NULL);
452 } 533 }
453 534
454 static void jabber_bosh_http_connection_disconnected(PurpleHTTPConnection *conn) { 535 static void jabber_bosh_http_connection_disconnected(PurpleHTTPConnection *conn) {
536 /*
537 * Well, then. Fine! I never liked you anyway, server! I was cheating on you
538 * with AIM!
539 */
540 conn->ready = FALSE;
541
542 if (conn->bosh->pipelining)
543 /* Hmmmm, fall back to multiple connections */
544 conn->bosh->pipelining = FALSE;
545
546 /* No! Please! Take me back. It was me, not you! I was weak! */
455 conn->connect_cb = jabber_bosh_connection_connected; 547 conn->connect_cb = jabber_bosh_connection_connected;
456 jabber_bosh_http_connection_connect(conn); 548 jabber_bosh_http_connection_connect(conn);
457 } 549 }
458 550
459 void jabber_bosh_connection_connect(PurpleBOSHConnection *conn) { 551 void jabber_bosh_connection_connect(PurpleBOSHConnection *bosh) {
460 conn->conn_a->connect_cb = jabber_bosh_connection_connected; 552 PurpleHTTPConnection *conn = bosh->connections[0];
461 jabber_bosh_http_connection_connect(conn->conn_a); 553 conn->connect_cb = jabber_bosh_connection_connected;
554 conn->disconnect_cb = jabber_bosh_http_connection_disconnected;
555 jabber_bosh_http_connection_connect(conn);
462 } 556 }
463 557
464 static void 558 static void
465 jabber_bosh_http_connection_process(PurpleHTTPConnection *conn) 559 jabber_bosh_http_connection_process(PurpleHTTPConnection *conn)
466 { 560 {
499 /* Have we read all that the Content-Length promised us? */ 593 /* Have we read all that the Content-Length promised us? */
500 if (conn->buf->len - conn->handled_len < conn->body_len) 594 if (conn->buf->len - conn->handled_len < conn->body_len)
501 return; 595 return;
502 596
503 --conn->requests; 597 --conn->requests;
598 --conn->bosh->requests;
504 599
505 #warning For a pure HTTP 1.1 stack, this would need to be handled elsewhere. 600 #warning For a pure HTTP 1.1 stack, this would need to be handled elsewhere.
506 if (conn->bosh->ready && conn->requests == 0) { 601 if (conn->bosh->ready && conn->bosh->requests == 0) {
507 jabber_bosh_connection_send(conn->bosh, NULL); 602 jabber_bosh_connection_send(conn->bosh, NULL);
508 purple_debug_misc("jabber", "BOSH: Sending an empty request\n"); 603 purple_debug_misc("jabber", "BOSH: Sending an empty request\n");
509 } 604 }
510 605
511 http_received_cb(conn->buf->str + conn->handled_len, conn->body_len, 606 http_received_cb(conn->buf->str + conn->handled_len, conn->body_len,
526 int perrno; 621 int perrno;
527 int cnt, count = 0; 622 int cnt, count = 0;
528 623
529 purple_debug_info("jabber", "jabber_bosh_http_connection_read\n"); 624 purple_debug_info("jabber", "jabber_bosh_http_connection_read\n");
530 625
531 if (conn->buf == NULL) 626 if (!conn->buf)
532 conn->buf = g_string_new(""); 627 conn->buf = g_string_new("");
533 628
534 while ((cnt = read(fd, buffer, sizeof(buffer))) > 0) { 629 while ((cnt = read(fd, buffer, sizeof(buffer))) > 0) {
535 purple_debug_info("jabber", "bosh read %d bytes\n", cnt); 630 purple_debug_info("jabber", "bosh read %d bytes\n", cnt);
536 count += cnt; 631 count += cnt;
597 } 692 }
598 } 693 }
599 694
600 static void 695 static void
601 jabber_bosh_http_connection_send_request(PurpleHTTPConnection *conn, 696 jabber_bosh_http_connection_send_request(PurpleHTTPConnection *conn,
602 PurpleHTTPRequest *req) 697 const GString *req)
603 { 698 {
604 GString *packet = g_string_new(""); 699 GString *packet = g_string_new("");
605 int ret; 700 int ret;
606 701
607 /* TODO: Should we lie about this HTTP/1.1 support? */ 702 g_string_printf(packet, "POST %s HTTP/1.1\r\n"
608 g_string_append_printf(packet, "POST %s HTTP/1.1\r\n"
609 "Host: %s\r\n" 703 "Host: %s\r\n"
610 "User-Agent: %s\r\n" 704 "User-Agent: %s\r\n"
611 "Content-Encoding: text/xml; charset=utf-8\r\n" 705 "Content-Encoding: text/xml; charset=utf-8\r\n"
612 "Content-Length: %d\r\n\r\n", 706 "Content-Length: %" G_GSIZE_FORMAT "\r\n\r\n",
613 req->path, conn->host, bosh_useragent, req->data_len); 707 conn->bosh->path, conn->host, bosh_useragent,
614 708 req->len);
615 packet = g_string_append(packet, req->data); 709
710 packet = g_string_append(packet, req->str);
616 711
617 purple_debug_misc("jabber", "BOSH out: %s\n", packet->str); 712 purple_debug_misc("jabber", "BOSH out: %s\n", packet->str);
618 /* TODO: Better error handling, circbuffer or possible integration with 713 /* TODO: Better error handling, circbuffer or possible integration with
619 * low-level code in jabber.c */ 714 * low-level code in jabber.c */
620 ret = write(conn->fd, packet->str, packet->len); 715 ret = write(conn->fd, packet->str, packet->len);
621 716
622 ++conn->requests; 717 ++conn->requests;
718 ++conn->bosh->requests;
623 g_string_free(packet, TRUE); 719 g_string_free(packet, TRUE);
624 jabber_bosh_http_request_destroy(req);
625 720
626 if (ret < 0 && errno == EAGAIN) 721 if (ret < 0 && errno == EAGAIN)
627 purple_debug_warning("jabber", "BOSH write would have blocked\n"); 722 purple_debug_warning("jabber", "BOSH write would have blocked\n");
628 723
629 if (ret <= 0) { 724 if (ret <= 0) {