Mercurial > pidgin
comparison libpurple/protocols/jabber/bosh.c @ 27546:70dcaa0b6fea
Various fixes to get BOSH working with Prosody 0.5.
Prosody closes the TCP socket immediately after every reply, so is an
extremely good test of that corner case.
author | Paul Aurich <paul@darkrain42.org> |
---|---|
date | Wed, 15 Jul 2009 05:49:30 +0000 |
parents | f541583e31bd |
children | 14e5eadff540 |
comparison
equal
deleted
inserted
replaced
27545:5c17c400153d | 27546:70dcaa0b6fea |
---|---|
49 JabberStream *js; | 49 JabberStream *js; |
50 gboolean pipelining; | 50 gboolean pipelining; |
51 PurpleHTTPConnection *connections[MAX_HTTP_CONNECTIONS]; | 51 PurpleHTTPConnection *connections[MAX_HTTP_CONNECTIONS]; |
52 unsigned short failed_connections; | 52 unsigned short failed_connections; |
53 | 53 |
54 gboolean ready; | 54 enum { |
55 BOSH_CONN_OFFLINE, | |
56 BOSH_CONN_BOOTING, | |
57 BOSH_CONN_ONLINE | |
58 } state; | |
55 gboolean ssl; | 59 gboolean ssl; |
60 gboolean needs_restart; | |
56 | 61 |
57 /* decoded URL */ | 62 /* decoded URL */ |
58 char *host; | 63 char *host; |
59 int port; | 64 int port; |
60 char *path; | 65 char *path; |
82 guint readh; | 87 guint readh; |
83 guint writeh; | 88 guint writeh; |
84 | 89 |
85 PurpleCircBuffer *write_buffer; | 90 PurpleCircBuffer *write_buffer; |
86 | 91 |
87 gboolean ready; | 92 enum { |
93 HTTP_CONN_OFFLINE, | |
94 HTTP_CONN_CONNECTING, | |
95 HTTP_CONN_CONNECTED | |
96 } state; | |
88 int requests; /* number of outstanding HTTP requests */ | 97 int requests; /* number of outstanding HTTP requests */ |
89 | 98 |
90 GString *buf; | 99 GString *buf; |
91 gboolean headers_done; | 100 gboolean headers_done; |
92 gsize handled_len; | 101 gsize handled_len; |
127 jabber_bosh_http_connection_init(PurpleBOSHConnection *bosh) | 136 jabber_bosh_http_connection_init(PurpleBOSHConnection *bosh) |
128 { | 137 { |
129 PurpleHTTPConnection *conn = g_new0(PurpleHTTPConnection, 1); | 138 PurpleHTTPConnection *conn = g_new0(PurpleHTTPConnection, 1); |
130 conn->bosh = bosh; | 139 conn->bosh = bosh; |
131 conn->fd = -1; | 140 conn->fd = -1; |
132 conn->ready = FALSE; | 141 conn->state = HTTP_CONN_OFFLINE; |
133 | 142 |
134 conn->write_buffer = purple_circ_buffer_new(0 /* default grow size */); | 143 conn->write_buffer = purple_circ_buffer_new(0 /* default grow size */); |
135 | 144 |
136 return conn; | 145 return conn; |
137 } | 146 } |
174 conn->host = host; | 183 conn->host = host; |
175 conn->port = port; | 184 conn->port = port; |
176 conn->path = g_strdup_printf("/%s", path); | 185 conn->path = g_strdup_printf("/%s", path); |
177 g_free(path); | 186 g_free(path); |
178 conn->pipelining = TRUE; | 187 conn->pipelining = TRUE; |
188 conn->needs_restart = FALSE; | |
179 | 189 |
180 if ((user && user[0] != '\0') || (passwd && passwd[0] != '\0')) { | 190 if ((user && user[0] != '\0') || (passwd && passwd[0] != '\0')) { |
181 purple_debug_info("jabber", "Ignoring unexpected username and password " | 191 purple_debug_info("jabber", "Ignoring unexpected username and password " |
182 "in BOSH URL.\n"); | 192 "in BOSH URL.\n"); |
183 } | 193 } |
196 conn->rid = ((guint64)g_random_int() << 32) | g_random_int(); | 206 conn->rid = ((guint64)g_random_int() << 32) | g_random_int(); |
197 conn->rid &= 0xFFFFFFFFFFFFFLL; | 207 conn->rid &= 0xFFFFFFFFFFFFFLL; |
198 | 208 |
199 conn->pending = purple_circ_buffer_new(0 /* default grow size */); | 209 conn->pending = purple_circ_buffer_new(0 /* default grow size */); |
200 | 210 |
201 conn->ready = FALSE; | 211 conn->state = BOSH_CONN_OFFLINE; |
202 if (purple_strcasestr(url, "https://") != NULL) | 212 if (purple_strcasestr(url, "https://") != NULL) |
203 conn->ssl = TRUE; | 213 conn->ssl = TRUE; |
204 else | 214 else |
205 conn->ssl = FALSE; | 215 conn->ssl = FALSE; |
206 | 216 |
241 int i; | 251 int i; |
242 | 252 |
243 /* Easy solution: Does everyone involved support pipelining? Hooray! Just use | 253 /* Easy solution: Does everyone involved support pipelining? Hooray! Just use |
244 * one TCP connection! */ | 254 * one TCP connection! */ |
245 if (conn->pipelining) | 255 if (conn->pipelining) |
246 return conn->connections[0]->ready ? conn->connections[0] : NULL; | 256 return conn->connections[0]->state == HTTP_CONN_CONNECTED ? |
257 conn->connections[0] : NULL; | |
247 | 258 |
248 /* First loop, look for a connection that's ready */ | 259 /* First loop, look for a connection that's ready */ |
249 for (i = 0; i < MAX_HTTP_CONNECTIONS; ++i) { | 260 for (i = 0; i < MAX_HTTP_CONNECTIONS; ++i) { |
250 if (conn->connections[i] && conn->connections[i]->ready && | 261 if (conn->connections[i] && |
251 conn->connections[i]->requests == 0) | 262 conn->connections[i]->state == HTTP_CONN_CONNECTED && |
263 conn->connections[i]->requests == 0) | |
252 return conn->connections[i]; | 264 return conn->connections[i]; |
253 } | 265 } |
254 | 266 |
255 /* Second loop, look for one that's NULL and create a new connection */ | 267 /* Second loop, is something currently connecting? If so, just queue up. */ |
268 for (i = 0; i < MAX_HTTP_CONNECTIONS; ++i) { | |
269 if (conn->connections[i] && | |
270 conn->connections[i]->state == HTTP_CONN_CONNECTING) | |
271 return NULL; | |
272 } | |
273 | |
274 /* Third loop, look for one that's NULL and create a new connection */ | |
256 for (i = 0; i < MAX_HTTP_CONNECTIONS; ++i) { | 275 for (i = 0; i < MAX_HTTP_CONNECTIONS; ++i) { |
257 if (!conn->connections[i]) { | 276 if (!conn->connections[i]) { |
277 purple_debug_info("jabber", "bosh: Creating and connecting new httpconn\n"); | |
258 conn->connections[i] = jabber_bosh_http_connection_init(conn); | 278 conn->connections[i] = jabber_bosh_http_connection_init(conn); |
259 | 279 |
260 http_connection_connect(conn->connections[i]); | 280 http_connection_connect(conn->connections[i]); |
261 return NULL; | 281 return NULL; |
262 } | 282 } |
266 return NULL; | 286 return NULL; |
267 } | 287 } |
268 | 288 |
269 static void | 289 static void |
270 jabber_bosh_connection_send(PurpleBOSHConnection *conn, | 290 jabber_bosh_connection_send(PurpleBOSHConnection *conn, |
271 PurpleBOSHPacketType type, const char *data) | 291 const PurpleBOSHPacketType type, const char *data) |
272 { | 292 { |
273 PurpleHTTPConnection *chosen; | 293 PurpleHTTPConnection *chosen; |
274 GString *packet = NULL; | 294 GString *packet = NULL; |
275 | 295 |
276 chosen = find_available_http_connection(conn); | 296 chosen = find_available_http_connection(conn); |
277 | 297 |
278 if (type != PACKET_NORMAL && !chosen) { | 298 if (type != PACKET_NORMAL && !chosen) { |
279 /* | 299 /* |
280 * For non-ordinary traffic, we don't want to 'buffer' it, so use the | 300 * For non-ordinary traffic, we can't 'buffer' it, so use the |
281 * first connection. | 301 * first connection. |
282 */ | 302 */ |
283 chosen = conn->connections[0]; | 303 chosen = conn->connections[0]; |
284 | 304 |
285 if (!chosen->ready) { | 305 if (chosen->state != HTTP_CONN_CONNECTED) { |
286 purple_debug_info("jabber", "Unable to find a ready BOSH " | 306 purple_debug_info("jabber", "Unable to find a ready BOSH " |
287 "connection. Ignoring send of type 0x%02x.\n", type); | 307 "connection. Ignoring send of type 0x%02x.\n", type); |
288 return; | 308 return; |
289 } | 309 } |
290 } | 310 } |
298 */ | 318 */ |
299 if (data) { | 319 if (data) { |
300 int len = data ? strlen(data) : 0; | 320 int len = data ? strlen(data) : 0; |
301 purple_circ_buffer_append(conn->pending, data, len); | 321 purple_circ_buffer_append(conn->pending, data, len); |
302 } | 322 } |
323 | |
303 return; | 324 return; |
304 } | 325 } |
305 | 326 |
306 packet = g_string_new(NULL); | 327 packet = g_string_new(NULL); |
307 | 328 |
314 "xmlns:xmpp='urn:xmpp:xbosh'", | 335 "xmlns:xmpp='urn:xmpp:xbosh'", |
315 ++conn->rid, | 336 ++conn->rid, |
316 conn->sid, | 337 conn->sid, |
317 conn->js->user->domain); | 338 conn->js->user->domain); |
318 | 339 |
319 if (type == PACKET_STREAM_RESTART) | 340 if (type == PACKET_STREAM_RESTART) { |
320 packet = g_string_append(packet, " xmpp:restart='true'/>"); | 341 packet = g_string_append(packet, " xmpp:restart='true'/>"); |
321 else { | 342 /* TODO: Do we need to wait for a response? */ |
343 conn->needs_restart = FALSE; | |
344 } else { | |
322 gsize read_amt; | 345 gsize read_amt; |
323 if (type == PACKET_TERMINATE) | 346 if (type == PACKET_TERMINATE) |
324 packet = g_string_append(packet, " type='terminate'"); | 347 packet = g_string_append(packet, " type='terminate'"); |
325 | 348 |
326 packet = g_string_append_c(packet, '>'); | 349 packet = g_string_append_c(packet, '>'); |
341 void jabber_bosh_connection_close(PurpleBOSHConnection *conn) | 364 void jabber_bosh_connection_close(PurpleBOSHConnection *conn) |
342 { | 365 { |
343 jabber_bosh_connection_send(conn, PACKET_TERMINATE, NULL); | 366 jabber_bosh_connection_send(conn, PACKET_TERMINATE, NULL); |
344 } | 367 } |
345 | 368 |
346 static void jabber_bosh_connection_stream_restart(PurpleBOSHConnection *conn) { | 369 static void jabber_bosh_connection_stream_restart(PurpleBOSHConnection *conn) |
370 { | |
371 conn->needs_restart = TRUE; | |
347 jabber_bosh_connection_send(conn, PACKET_STREAM_RESTART, NULL); | 372 jabber_bosh_connection_send(conn, PACKET_STREAM_RESTART, NULL); |
348 } | 373 } |
349 | 374 |
350 static gboolean jabber_bosh_connection_error_check(PurpleBOSHConnection *conn, xmlnode *node) { | 375 static gboolean jabber_bosh_connection_error_check(PurpleBOSHConnection *conn, xmlnode *node) { |
351 const char *type; | 376 const char *type; |
352 | 377 |
353 type = xmlnode_get_attrib(node, "type"); | 378 type = xmlnode_get_attrib(node, "type"); |
354 | 379 |
355 if (type != NULL && !strcmp(type, "terminate")) { | 380 if (type != NULL && !strcmp(type, "terminate")) { |
356 conn->ready = FALSE; | 381 conn->state = BOSH_CONN_OFFLINE; |
357 purple_connection_error_reason(conn->js->gc, | 382 purple_connection_error_reason(conn->js->gc, |
358 PURPLE_CONNECTION_ERROR_OTHER_ERROR, | 383 PURPLE_CONNECTION_ERROR_OTHER_ERROR, |
359 _("The BOSH connection manager terminated your session.")); | 384 _("The BOSH connection manager terminated your session.")); |
360 return TRUE; | 385 return TRUE; |
361 } | 386 } |
382 child = node->child; | 407 child = node->child; |
383 while (child != NULL) { | 408 while (child != NULL) { |
384 /* jabber_process_packet might free child */ | 409 /* jabber_process_packet might free child */ |
385 xmlnode *next = child->next; | 410 xmlnode *next = child->next; |
386 if (child->type == XMLNODE_TYPE_TAG) { | 411 if (child->type == XMLNODE_TYPE_TAG) { |
387 if (!strcmp(child->name, "iq")) { | |
388 if (xmlnode_get_child(child, "session")) | |
389 conn->ready = TRUE; | |
390 } | |
391 | |
392 jabber_process_packet(js, &child); | 412 jabber_process_packet(js, &child); |
393 } | 413 } |
394 | 414 |
395 child = next; | 415 child = next; |
396 } | 416 } |
474 purple_debug_warning("jabber", "Ignoring bogusly small inactivity: %s\n", | 494 purple_debug_warning("jabber", "Ignoring bogusly small inactivity: %s\n", |
475 inactivity); | 495 inactivity); |
476 conn->max_inactivity = 0; | 496 conn->max_inactivity = 0; |
477 } else { | 497 } else { |
478 /* TODO: Integrate this with jabber.c keepalive checks... */ | 498 /* TODO: Integrate this with jabber.c keepalive checks... */ |
479 conn->inactivity_timer = purple_timeout_add_seconds( | 499 if (conn->inactivity_timer == 0) { |
480 conn->max_inactivity - 2 /* rounding */, bosh_inactivity_cb, | 500 purple_debug_misc("jabber", "Starting BOSH inactivity timer for %d secs (compensating for rounding)\n", |
481 conn); | 501 conn->max_inactivity - 5); |
502 conn->inactivity_timer = purple_timeout_add_seconds( | |
503 conn->max_inactivity - 5 /* rounding */, | |
504 bosh_inactivity_cb, conn); | |
505 } | |
482 } | 506 } |
483 } | 507 } |
484 | 508 |
485 if (requests) | 509 if (requests) |
486 conn->max_requests = atoi(requests); | 510 conn->max_requests = atoi(requests); |
487 | 511 |
488 /* FIXME: Depending on receiving features might break with some hosts */ | 512 /* FIXME: Depending on receiving features might break with some hosts */ |
489 packet = xmlnode_get_child(node, "features"); | 513 packet = xmlnode_get_child(node, "features"); |
514 conn->state = BOSH_CONN_ONLINE; | |
490 conn->js->use_bosh = TRUE; | 515 conn->js->use_bosh = TRUE; |
491 conn->receive_cb = auth_response_cb; | 516 conn->receive_cb = auth_response_cb; |
492 jabber_stream_features_parse(conn->js, packet); | 517 jabber_stream_features_parse(conn->js, packet); |
493 } | 518 } |
494 | 519 |
509 "hold='1' " | 534 "hold='1' " |
510 "xmlns='http://jabber.org/protocol/httpbind'/>", | 535 "xmlns='http://jabber.org/protocol/httpbind'/>", |
511 conn->js->user->domain, | 536 conn->js->user->domain, |
512 ++conn->rid); | 537 ++conn->rid); |
513 | 538 |
539 purple_debug_misc("jabber", "SendBOSH Boot %s(%" G_GSIZE_FORMAT "): %s\n", | |
540 conn->ssl ? "(ssl)" : "", buf->len, buf->str); | |
514 conn->receive_cb = boot_response_cb; | 541 conn->receive_cb = boot_response_cb; |
515 http_connection_send_request(conn->connections[0], buf); | 542 http_connection_send_request(conn->connections[0], buf); |
516 g_string_free(buf, TRUE); | 543 g_string_free(buf, TRUE); |
517 } | 544 } |
518 | 545 |
548 | 575 |
549 static void | 576 static void |
550 connection_common_established_cb(PurpleHTTPConnection *conn) | 577 connection_common_established_cb(PurpleHTTPConnection *conn) |
551 { | 578 { |
552 /* Indicate we're ready and reset some variables */ | 579 /* Indicate we're ready and reset some variables */ |
553 conn->ready = TRUE; | 580 conn->state = HTTP_CONN_CONNECTED; |
554 conn->requests = 0; | 581 conn->requests = 0; |
555 if (conn->buf) { | 582 if (conn->buf) { |
556 g_string_free(conn->buf, TRUE); | 583 g_string_free(conn->buf, TRUE); |
557 conn->buf = NULL; | 584 conn->buf = NULL; |
558 } | 585 } |
559 conn->headers_done = FALSE; | 586 conn->headers_done = FALSE; |
560 conn->handled_len = conn->body_len = 0; | 587 conn->handled_len = conn->body_len = 0; |
561 | 588 |
562 if (conn->bosh->ready) { | 589 if (conn->bosh->needs_restart) |
590 jabber_bosh_connection_stream_restart(conn->bosh); | |
591 else if (conn->bosh->state == BOSH_CONN_ONLINE) { | |
563 purple_debug_info("jabber", "BOSH session already exists. Trying to reuse it.\n"); | 592 purple_debug_info("jabber", "BOSH session already exists. Trying to reuse it.\n"); |
564 if (conn->bosh->pending->bufused > 0) { | 593 if (conn->bosh->requests == 0 || conn->bosh->pending->bufused > 0) { |
565 /* Send the pending data */ | 594 /* Send the pending data */ |
566 jabber_bosh_connection_send(conn->bosh, PACKET_NORMAL, NULL); | 595 jabber_bosh_connection_send(conn->bosh, PACKET_NORMAL, NULL); |
567 } | 596 } |
568 #if 0 | 597 #if 0 |
569 conn->bosh->receive_cb = jabber_bosh_connection_received; | 598 conn->bosh->receive_cb = jabber_bosh_connection_received; |
583 { | 612 { |
584 /* | 613 /* |
585 * Well, then. Fine! I never liked you anyway, server! I was cheating on you | 614 * Well, then. Fine! I never liked you anyway, server! I was cheating on you |
586 * with AIM! | 615 * with AIM! |
587 */ | 616 */ |
588 conn->ready = FALSE; | 617 conn->state = HTTP_CONN_OFFLINE; |
589 if (conn->psc) { | 618 if (conn->psc) { |
590 purple_ssl_close(conn->psc); | 619 purple_ssl_close(conn->psc); |
591 conn->psc = NULL; | 620 conn->psc = NULL; |
592 } else if (conn->fd >= 0) { | 621 } else if (conn->fd >= 0) { |
593 close(conn->fd); | 622 close(conn->fd); |
618 } | 647 } |
619 } | 648 } |
620 | 649 |
621 void jabber_bosh_connection_connect(PurpleBOSHConnection *bosh) { | 650 void jabber_bosh_connection_connect(PurpleBOSHConnection *bosh) { |
622 PurpleHTTPConnection *conn = bosh->connections[0]; | 651 PurpleHTTPConnection *conn = bosh->connections[0]; |
652 | |
653 g_return_if_fail(bosh->state == BOSH_CONN_OFFLINE); | |
654 bosh->state = BOSH_CONN_BOOTING; | |
655 | |
623 http_connection_connect(conn); | 656 http_connection_connect(conn); |
624 } | 657 } |
625 | 658 |
626 static void | 659 static void |
627 jabber_bosh_http_connection_process(PurpleHTTPConnection *conn) | 660 jabber_bosh_http_connection_process(PurpleHTTPConnection *conn) |
677 --conn->bosh->requests; | 710 --conn->bosh->requests; |
678 | 711 |
679 http_received_cb(conn->buf->str + conn->handled_len, conn->body_len, | 712 http_received_cb(conn->buf->str + conn->handled_len, conn->body_len, |
680 conn->bosh); | 713 conn->bosh); |
681 | 714 |
682 if (conn->bosh->ready && | 715 if (conn->bosh->state == BOSH_CONN_ONLINE && |
683 (conn->bosh->requests == 0 || conn->bosh->pending->bufused > 0)) { | 716 (conn->bosh->requests == 0 || conn->bosh->pending->bufused > 0)) { |
717 purple_debug_misc("jabber", "BOSH: Sending an empty request\n"); | |
684 jabber_bosh_connection_send(conn->bosh, PACKET_NORMAL, NULL); | 718 jabber_bosh_connection_send(conn->bosh, PACKET_NORMAL, NULL); |
685 purple_debug_misc("jabber", "BOSH: Sending an empty request\n"); | |
686 } | 719 } |
687 | 720 |
688 g_string_free(conn->buf, TRUE); | 721 g_string_free(conn->buf, TRUE); |
689 conn->buf = NULL; | 722 conn->buf = NULL; |
690 conn->headers_done = FALSE; | 723 conn->headers_done = FALSE; |
799 static void http_connection_connect(PurpleHTTPConnection *conn) | 832 static void http_connection_connect(PurpleHTTPConnection *conn) |
800 { | 833 { |
801 PurpleBOSHConnection *bosh = conn->bosh; | 834 PurpleBOSHConnection *bosh = conn->bosh; |
802 PurpleConnection *gc = bosh->js->gc; | 835 PurpleConnection *gc = bosh->js->gc; |
803 PurpleAccount *account = purple_connection_get_account(gc); | 836 PurpleAccount *account = purple_connection_get_account(gc); |
837 | |
838 conn->state = HTTP_CONN_CONNECTING; | |
804 | 839 |
805 if (bosh->ssl) { | 840 if (bosh->ssl) { |
806 if (purple_ssl_is_supported()) { | 841 if (purple_ssl_is_supported()) { |
807 conn->psc = purple_ssl_connect(account, bosh->host, bosh->port, | 842 conn->psc = purple_ssl_connect(account, bosh->host, bosh->port, |
808 ssl_connection_established_cb, | 843 ssl_connection_established_cb, |