Remove redundant NULL checks
[pidgin-git.git] / libpurple / protocols / jabber / bosh.c
blobaf178c64bc537925f70c6aaccf904cd83c21ddae
1 /*
2 * purple - Jabber Protocol Plugin
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.
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.
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.
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
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"
32 #include "bosh.h"
34 /* The number of HTTP connections to use. This MUST be at least 2. */
35 #define NUM_HTTP_CONNECTIONS 2
36 /* How many failed connection attempts before it becomes a fatal error */
37 #define MAX_FAILED_CONNECTIONS 3
38 /* How long in seconds to queue up outgoing messages */
39 #define BUFFER_SEND_IN_SECS 1
41 typedef struct _PurpleHTTPConnection PurpleHTTPConnection;
43 typedef void (*PurpleBOSHConnectionConnectFunction)(PurpleBOSHConnection *conn);
44 typedef void (*PurpleBOSHConnectionReceiveFunction)(PurpleBOSHConnection *conn, xmlnode *node);
46 static char *bosh_useragent = NULL;
48 typedef enum {
49 PACKET_NORMAL,
50 PACKET_TERMINATE,
51 PACKET_FLUSH,
52 } PurpleBOSHPacketType;
54 struct _PurpleBOSHConnection {
55 JabberStream *js;
56 PurpleHTTPConnection *connections[NUM_HTTP_CONNECTIONS];
58 PurpleCircBuffer *pending;
59 PurpleBOSHConnectionConnectFunction connect_cb;
60 PurpleBOSHConnectionReceiveFunction receive_cb;
62 /* Must be big enough to hold 2^53 - 1 */
63 char *sid;
64 guint64 rid;
66 /* decoded URL */
67 char *host;
68 char *path;
69 guint16 port;
71 gboolean pipelining;
72 gboolean ssl;
74 enum {
75 BOSH_CONN_OFFLINE,
76 BOSH_CONN_BOOTING,
77 BOSH_CONN_ONLINE
78 } state;
79 guint8 failed_connections;
81 int wait;
83 int max_requests;
84 int requests;
86 guint send_timer;
89 struct _PurpleHTTPConnection {
90 PurpleBOSHConnection *bosh;
91 PurpleSslConnection *psc;
93 PurpleCircBuffer *write_buf;
94 GString *read_buf;
96 gsize handled_len;
97 gsize body_len;
99 int fd;
100 guint readh;
101 guint writeh;
103 enum {
104 HTTP_CONN_OFFLINE,
105 HTTP_CONN_CONNECTING,
106 HTTP_CONN_CONNECTED
107 } state;
108 int requests; /* number of outstanding HTTP requests */
110 gboolean headers_done;
111 gboolean close;
114 static void
115 debug_dump_http_connections(PurpleBOSHConnection *conn)
117 int i;
119 g_return_if_fail(conn != NULL);
121 for (i = 0; i < NUM_HTTP_CONNECTIONS; ++i) {
122 PurpleHTTPConnection *httpconn = conn->connections[i];
123 if (httpconn == NULL)
124 purple_debug_misc("jabber", "BOSH %p->connections[%d] = (nil)\n",
125 conn, i);
126 else
127 purple_debug_misc("jabber", "BOSH %p->connections[%d] = %p, state = %d"
128 ", requests = %d\n", conn, i, httpconn,
129 httpconn->state, httpconn->requests);
133 static void http_connection_connect(PurpleHTTPConnection *conn);
134 static void http_connection_send_request(PurpleHTTPConnection *conn,
135 const GString *req);
136 static gboolean send_timer_cb(gpointer data);
138 void jabber_bosh_init(void)
140 GHashTable *ui_info = purple_core_get_ui_info();
141 const char *ui_name = NULL;
142 const char *ui_version = NULL;
144 if (ui_info) {
145 ui_name = g_hash_table_lookup(ui_info, "name");
146 ui_version = g_hash_table_lookup(ui_info, "version");
149 if (ui_name)
150 bosh_useragent = g_strdup_printf("%s%s%s (libpurple " VERSION ")",
151 ui_name, ui_version ? " " : "",
152 ui_version ? ui_version : "");
153 else
154 bosh_useragent = g_strdup("libpurple " VERSION);
157 void jabber_bosh_uninit(void)
159 g_free(bosh_useragent);
160 bosh_useragent = NULL;
163 static PurpleHTTPConnection*
164 jabber_bosh_http_connection_init(PurpleBOSHConnection *bosh)
166 PurpleHTTPConnection *conn = g_new0(PurpleHTTPConnection, 1);
167 conn->bosh = bosh;
168 conn->fd = -1;
169 conn->state = HTTP_CONN_OFFLINE;
171 conn->write_buf = purple_circ_buffer_new(0 /* default grow size */);
173 return conn;
176 static void
177 jabber_bosh_http_connection_destroy(PurpleHTTPConnection *conn)
179 if (conn->read_buf)
180 g_string_free(conn->read_buf, TRUE);
182 if (conn->write_buf)
183 purple_circ_buffer_destroy(conn->write_buf);
184 if (conn->readh)
185 purple_input_remove(conn->readh);
186 if (conn->writeh)
187 purple_input_remove(conn->writeh);
188 if (conn->psc)
189 purple_ssl_close(conn->psc);
190 if (conn->fd >= 0)
191 close(conn->fd);
193 purple_proxy_connect_cancel_with_handle(conn);
195 g_free(conn);
198 PurpleBOSHConnection*
199 jabber_bosh_connection_init(JabberStream *js, const char *url)
201 PurpleBOSHConnection *conn;
202 char *host, *path, *user, *passwd;
203 int port;
205 if (!purple_url_parse(url, &host, &port, &path, &user, &passwd)) {
206 purple_debug_info("jabber", "Unable to parse given URL.\n");
207 return NULL;
210 conn = g_new0(PurpleBOSHConnection, 1);
211 conn->host = host;
212 conn->port = port;
213 conn->path = g_strdup_printf("/%s", path);
214 g_free(path);
215 conn->pipelining = TRUE;
217 if (purple_ip_address_is_valid(host))
218 js->serverFQDN = g_strdup(js->user->domain);
219 else
220 js->serverFQDN = g_strdup(host);
222 if ((user && user[0] != '\0') || (passwd && passwd[0] != '\0')) {
223 purple_debug_info("jabber", "Ignoring unexpected username and password "
224 "in BOSH URL.\n");
227 g_free(user);
228 g_free(passwd);
230 conn->js = js;
233 * Random 64-bit integer masked off by 2^52 - 1.
235 * This should produce a random integer in the range [0, 2^52). It's
236 * unlikely we'll send enough packets in one session to overflow the rid.
238 conn->rid = ((guint64)g_random_int() << 32) | g_random_int();
239 conn->rid &= 0xFFFFFFFFFFFFFLL;
241 conn->pending = purple_circ_buffer_new(0 /* default grow size */);
243 conn->state = BOSH_CONN_OFFLINE;
244 if (purple_strcasestr(url, "https://") != NULL)
245 conn->ssl = TRUE;
246 else
247 conn->ssl = FALSE;
249 conn->connections[0] = jabber_bosh_http_connection_init(conn);
251 return conn;
254 void
255 jabber_bosh_connection_destroy(PurpleBOSHConnection *conn)
257 int i;
259 g_free(conn->host);
260 g_free(conn->path);
262 if (conn->send_timer)
263 purple_timeout_remove(conn->send_timer);
265 purple_circ_buffer_destroy(conn->pending);
267 for (i = 0; i < NUM_HTTP_CONNECTIONS; ++i) {
268 if (conn->connections[i])
269 jabber_bosh_http_connection_destroy(conn->connections[i]);
272 g_free(conn);
275 gboolean jabber_bosh_connection_is_ssl(PurpleBOSHConnection *conn)
277 return conn->ssl;
280 static PurpleHTTPConnection *
281 find_available_http_connection(PurpleBOSHConnection *conn)
283 int i;
285 if (purple_debug_is_verbose())
286 debug_dump_http_connections(conn);
288 /* Easy solution: Does everyone involved support pipelining? Hooray! Just use
289 * one TCP connection! */
290 if (conn->pipelining)
291 return conn->connections[0]->state == HTTP_CONN_CONNECTED ?
292 conn->connections[0] : NULL;
294 /* First loop, look for a connection that's ready */
295 for (i = 0; i < NUM_HTTP_CONNECTIONS; ++i) {
296 if (conn->connections[i] &&
297 conn->connections[i]->state == HTTP_CONN_CONNECTED &&
298 conn->connections[i]->requests == 0)
299 return conn->connections[i];
302 /* Second loop, is something currently connecting? If so, just queue up. */
303 for (i = 0; i < NUM_HTTP_CONNECTIONS; ++i) {
304 if (conn->connections[i] &&
305 conn->connections[i]->state == HTTP_CONN_CONNECTING)
306 return NULL;
309 /* Third loop, is something offline that we can connect? */
310 for (i = 0; i < NUM_HTTP_CONNECTIONS; ++i) {
311 if (conn->connections[i] &&
312 conn->connections[i]->state == HTTP_CONN_OFFLINE) {
313 purple_debug_info("jabber", "bosh: Reconnecting httpconn "
314 "(%i, %p)\n", i, conn->connections[i]);
315 http_connection_connect(conn->connections[i]);
316 return NULL;
320 /* Fourth loop, look for one that's NULL and create a new connection */
321 for (i = 0; i < NUM_HTTP_CONNECTIONS; ++i) {
322 if (!conn->connections[i]) {
323 conn->connections[i] = jabber_bosh_http_connection_init(conn);
324 purple_debug_info("jabber", "bosh: Creating and connecting new httpconn "
325 "(%i, %p)\n", i, conn->connections[i]);
327 http_connection_connect(conn->connections[i]);
328 return NULL;
332 purple_debug_warning("jabber", "Could not find a HTTP connection!\n");
334 /* None available. */
335 return NULL;
338 static void
339 jabber_bosh_connection_send(PurpleBOSHConnection *conn,
340 const PurpleBOSHPacketType type, const char *data)
342 PurpleHTTPConnection *chosen;
343 GString *packet = NULL;
345 if (type != PACKET_FLUSH && type != PACKET_TERMINATE) {
347 * Unless this is a flush (or session terminate, which needs to be
348 * sent immediately), queue up the data and start a timer to flush
349 * the buffer.
351 if (data)
352 purple_circ_buffer_append(conn->pending, data, strlen(data));
354 if (purple_debug_is_verbose())
355 purple_debug_misc("jabber", "bosh: %p has %" G_GSIZE_FORMAT " bytes in "
356 "the buffer.\n", conn, conn->pending->bufused);
357 if (conn->send_timer == 0)
358 conn->send_timer = purple_timeout_add_seconds(BUFFER_SEND_IN_SECS,
359 send_timer_cb, conn);
360 return;
363 chosen = find_available_http_connection(conn);
365 if (!chosen) {
366 if (type == PACKET_FLUSH)
367 return;
369 * For non-ordinary traffic, we can't 'buffer' it, so use the
370 * first connection.
372 chosen = conn->connections[0];
374 if (chosen->state != HTTP_CONN_CONNECTED) {
375 purple_debug_warning("jabber", "Unable to find a ready BOSH "
376 "connection. Ignoring send of type 0x%02x.\n", type);
377 return;
381 /* We're flushing the send buffer, so remove the send timer */
382 if (conn->send_timer != 0) {
383 purple_timeout_remove(conn->send_timer);
384 conn->send_timer = 0;
387 packet = g_string_new(NULL);
389 g_string_printf(packet, "<body "
390 "rid='%" G_GUINT64_FORMAT "' "
391 "sid='%s' "
392 "to='%s' "
393 "xml:lang='en' "
394 "xmlns='" NS_BOSH "' "
395 "xmlns:xmpp='" NS_XMPP_BOSH "'",
396 ++conn->rid,
397 conn->sid,
398 conn->js->user->domain);
400 if (conn->js->reinit) {
401 packet = g_string_append(packet, " xmpp:restart='true'/>");
402 /* TODO: Do we need to wait for a response? */
403 conn->js->reinit = FALSE;
404 } else {
405 gsize read_amt;
406 if (type == PACKET_TERMINATE)
407 packet = g_string_append(packet, " type='terminate'");
409 packet = g_string_append_c(packet, '>');
411 while ((read_amt = purple_circ_buffer_get_max_read(conn->pending)) > 0) {
412 packet = g_string_append_len(packet, conn->pending->outptr, read_amt);
413 purple_circ_buffer_mark_read(conn->pending, read_amt);
416 if (data)
417 packet = g_string_append(packet, data);
418 packet = g_string_append(packet, "</body>");
421 http_connection_send_request(chosen, packet);
424 void jabber_bosh_connection_close(PurpleBOSHConnection *conn)
426 if (conn->state == BOSH_CONN_ONLINE)
427 jabber_bosh_connection_send(conn, PACKET_TERMINATE, NULL);
430 static gboolean jabber_bosh_connection_error_check(PurpleBOSHConnection *conn, xmlnode *node) {
431 const char *type;
433 type = xmlnode_get_attrib(node, "type");
435 if (purple_strequal(type, "terminate")) {
436 conn->state = BOSH_CONN_OFFLINE;
437 purple_connection_error_reason(conn->js->gc,
438 PURPLE_CONNECTION_ERROR_OTHER_ERROR,
439 _("The BOSH connection manager terminated your session."));
440 return TRUE;
442 return FALSE;
445 static gboolean
446 send_timer_cb(gpointer data)
448 PurpleBOSHConnection *bosh;
450 bosh = data;
451 bosh->send_timer = 0;
453 jabber_bosh_connection_send(bosh, PACKET_FLUSH, NULL);
455 return FALSE;
458 void
459 jabber_bosh_connection_send_keepalive(PurpleBOSHConnection *bosh)
461 if (bosh->send_timer != 0)
462 purple_timeout_remove(bosh->send_timer);
464 /* clears bosh->send_timer */
465 send_timer_cb(bosh);
468 static void
469 jabber_bosh_disable_pipelining(PurpleBOSHConnection *bosh)
471 /* Do nothing if it's already disabled */
472 if (!bosh->pipelining)
473 return;
475 purple_debug_info("jabber", "BOSH: Disabling pipelining on conn %p\n",
476 bosh);
477 bosh->pipelining = FALSE;
478 if (bosh->connections[1] == NULL) {
479 bosh->connections[1] = jabber_bosh_http_connection_init(bosh);
480 http_connection_connect(bosh->connections[1]);
481 } else {
482 /* Shouldn't happen; this should be the only place pipelining
483 * is turned off.
485 g_warn_if_reached();
489 static void jabber_bosh_connection_received(PurpleBOSHConnection *conn, xmlnode *node) {
490 xmlnode *child;
491 JabberStream *js = conn->js;
493 g_return_if_fail(node != NULL);
494 if (jabber_bosh_connection_error_check(conn, node))
495 return;
497 child = node->child;
498 while (child != NULL) {
499 /* jabber_process_packet might free child */
500 xmlnode *next = child->next;
501 if (child->type == XMLNODE_TYPE_TAG) {
502 const char *xmlns = xmlnode_get_namespace(child);
504 * Workaround for non-compliant servers that don't stamp
505 * the right xmlns on these packets. See #11315.
507 if ((xmlns == NULL /* shouldn't happen, but is equally wrong */ ||
508 purple_strequal(xmlns, NS_BOSH)) &&
509 (purple_strequal(child->name, "iq") ||
510 purple_strequal(child->name, "message") ||
511 purple_strequal(child->name, "presence"))) {
512 xmlnode_set_namespace(child, NS_XMPP_CLIENT);
514 jabber_process_packet(js, &child);
517 child = next;
521 static void boot_response_cb(PurpleBOSHConnection *conn, xmlnode *node) {
522 JabberStream *js = conn->js;
523 const char *sid, *version;
524 const char *inactivity, *requests;
525 xmlnode *packet;
527 g_return_if_fail(node != NULL);
528 if (jabber_bosh_connection_error_check(conn, node))
529 return;
531 sid = xmlnode_get_attrib(node, "sid");
532 version = xmlnode_get_attrib(node, "ver");
534 inactivity = xmlnode_get_attrib(node, "inactivity");
535 requests = xmlnode_get_attrib(node, "requests");
537 if (sid) {
538 conn->sid = g_strdup(sid);
539 } else {
540 purple_connection_error_reason(js->gc,
541 PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
542 _("No session ID given"));
543 return;
546 if (version) {
547 const char *dot = strchr(version, '.');
548 int major, minor = 0;
550 purple_debug_info("jabber", "BOSH connection manager version %s\n", version);
552 major = atoi(version);
553 if (dot)
554 minor = atoi(dot + 1);
556 if (major != 1 || minor < 6) {
557 purple_connection_error_reason(js->gc,
558 PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
559 _("Unsupported version of BOSH protocol"));
560 return;
562 } else {
563 purple_debug_info("jabber", "Missing version in BOSH initiation\n");
566 if (inactivity) {
567 js->max_inactivity = atoi(inactivity);
568 if (js->max_inactivity <= 5) {
569 purple_debug_warning("jabber", "Ignoring bogusly small inactivity: %s\n",
570 inactivity);
571 /* Leave it at the default */
572 } else {
573 /* TODO: Can this check fail? It shouldn't */
574 js->max_inactivity -= 5; /* rounding */
576 if (js->inactivity_timer == 0) {
577 purple_debug_misc("jabber", "Starting BOSH inactivity timer "
578 "for %d secs (compensating for rounding)\n",
579 js->max_inactivity);
580 jabber_stream_restart_inactivity_timer(js);
585 if (requests)
586 conn->max_requests = atoi(requests);
588 jabber_stream_set_state(js, JABBER_STREAM_AUTHENTICATING);
590 /* FIXME: Depending on receiving features might break with some hosts */
591 packet = xmlnode_get_child(node, "features");
592 conn->state = BOSH_CONN_ONLINE;
593 conn->receive_cb = jabber_bosh_connection_received;
594 jabber_stream_features_parse(js, packet);
597 static void jabber_bosh_connection_boot(PurpleBOSHConnection *conn) {
598 GString *buf = g_string_new(NULL);
600 g_string_printf(buf, "<body content='text/xml; charset=utf-8' "
601 "secure='true' "
602 "to='%s' "
603 "xml:lang='en' "
604 "xmpp:version='1.0' "
605 "ver='1.6' "
606 "xmlns:xmpp='" NS_XMPP_BOSH "' "
607 "rid='%" G_GUINT64_FORMAT "' "
608 /* TODO: This should be adjusted/adjustable automatically according to
609 * realtime network behavior */
610 "wait='60' "
611 "hold='1' "
612 "xmlns='" NS_BOSH "'/>",
613 conn->js->user->domain,
614 ++conn->rid);
616 purple_debug_misc("jabber", "SendBOSH Boot %s(%" G_GSIZE_FORMAT "): %s\n",
617 conn->ssl ? "(ssl)" : "", buf->len, buf->str);
618 conn->receive_cb = boot_response_cb;
619 http_connection_send_request(conn->connections[0], buf);
620 g_string_free(buf, TRUE);
624 * Handle one complete BOSH response. This is a <body> node containing
625 * any number of XMPP stanzas.
627 static void
628 http_received_cb(const char *data, int len, PurpleBOSHConnection *conn)
630 xmlnode *node;
631 gchar *message;
633 if (conn->failed_connections)
634 /* We've got some data, so reset the number of failed connections */
635 conn->failed_connections = 0;
637 g_return_if_fail(conn->receive_cb);
639 node = xmlnode_from_str(data, len);
641 message = g_strndup(data, len);
642 purple_debug_info("jabber", "RecvBOSH %s(%d): %s\n",
643 conn->ssl ? "(ssl)" : "", len, message);
644 g_free(message);
646 if (node) {
647 conn->receive_cb(conn, node);
648 xmlnode_free(node);
649 } else {
650 purple_debug_warning("jabber", "BOSH: Received invalid XML\n");
654 void jabber_bosh_connection_send_raw(PurpleBOSHConnection *conn,
655 const char *data)
657 jabber_bosh_connection_send(conn, PACKET_NORMAL, data);
660 static void
661 connection_common_established_cb(PurpleHTTPConnection *conn)
663 purple_debug_misc("jabber", "bosh: httpconn %p re-connected\n", conn);
665 /* Indicate we're ready and reset some variables */
666 conn->state = HTTP_CONN_CONNECTED;
667 if (conn->requests != 0)
668 purple_debug_error("jabber", "bosh: httpconn %p has %d requests, != 0\n",
669 conn, conn->requests);
671 conn->requests = 0;
672 if (conn->read_buf) {
673 g_string_free(conn->read_buf, TRUE);
674 conn->read_buf = NULL;
676 conn->close = FALSE;
677 conn->headers_done = FALSE;
678 conn->handled_len = conn->body_len = 0;
680 if (purple_debug_is_verbose())
681 debug_dump_http_connections(conn->bosh);
683 if (conn->bosh->js->reinit)
684 jabber_bosh_connection_send(conn->bosh, PACKET_NORMAL, NULL);
685 else if (conn->bosh->state == BOSH_CONN_ONLINE) {
686 purple_debug_info("jabber", "BOSH session already exists. Trying to reuse it.\n");
687 if (conn->bosh->requests == 0 || conn->bosh->pending->bufused > 0) {
688 /* Send the pending data */
689 jabber_bosh_connection_send(conn->bosh, PACKET_FLUSH, NULL);
691 } else
692 jabber_bosh_connection_boot(conn->bosh);
695 static void http_connection_disconnected(PurpleHTTPConnection *conn)
697 gboolean had_requests = FALSE;
699 * Well, then. Fine! I never liked you anyway, server! I was cheating on you
700 * with AIM!
702 conn->state = HTTP_CONN_OFFLINE;
703 if (conn->psc) {
704 purple_ssl_close(conn->psc);
705 conn->psc = NULL;
706 } else if (conn->fd >= 0) {
707 close(conn->fd);
708 conn->fd = -1;
711 if (conn->readh) {
712 purple_input_remove(conn->readh);
713 conn->readh = 0;
716 if (conn->writeh) {
717 purple_input_remove(conn->writeh);
718 conn->writeh = 0;
721 had_requests = (conn->requests > 0);
722 if (had_requests && conn->read_buf->len == 0) {
723 purple_debug_error("jabber", "bosh: Adjusting BOSHconn requests (%d) to %d\n",
724 conn->bosh->requests, conn->bosh->requests - conn->requests);
725 conn->bosh->requests -= conn->requests;
726 conn->requests = 0;
729 if (conn->bosh->pipelining) {
730 /* Hmmmm, fall back to multiple connections */
731 jabber_bosh_disable_pipelining(conn->bosh);
734 if (!had_requests)
735 /* If the server disconnected us without any requests, let's
736 * just wait until we have something to send before we reconnect
738 return;
740 if (++conn->bosh->failed_connections == MAX_FAILED_CONNECTIONS) {
741 purple_connection_error_reason(conn->bosh->js->gc,
742 PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
743 _("Unable to establish a connection with the server"));
744 } else {
745 /* No! Please! Take me back. It was me, not you! I was weak! */
746 http_connection_connect(conn);
750 void jabber_bosh_connection_connect(PurpleBOSHConnection *bosh) {
751 PurpleHTTPConnection *conn = bosh->connections[0];
753 g_return_if_fail(bosh->state == BOSH_CONN_OFFLINE);
754 bosh->state = BOSH_CONN_BOOTING;
756 http_connection_connect(conn);
760 * @return TRUE if we want to be called again immediately. This happens when
761 * we parse an HTTP response AND there is more data in read_buf. FALSE
762 * if we should not be called again unless more data has been read.
764 static gboolean
765 jabber_bosh_http_connection_process(PurpleHTTPConnection *conn)
767 const char *cursor;
769 cursor = conn->read_buf->str + conn->handled_len;
771 if (purple_debug_is_verbose())
772 purple_debug_misc("jabber", "BOSH server sent: %s\n", cursor);
774 /* TODO: Chunked encoding and check response version :/ */
775 if (!conn->headers_done) {
776 const char *content_length = purple_strcasestr(cursor, "\r\nContent-Length:");
777 const char *connection = purple_strcasestr(cursor, "\r\nConnection:");
778 const char *end_of_headers = strstr(cursor, "\r\n\r\n");
780 /* Make sure Content-Length is in headers, not body */
781 if (content_length && (!end_of_headers || content_length < end_of_headers)) {
782 int len;
784 if (strstr(content_length, "\r\n") == NULL)
786 * The packet ends in the middle of the Content-Length line.
787 * We'll try again later when we have more.
789 return FALSE;
791 len = atoi(content_length + strlen("\r\nContent-Length:"));
792 if (len == 0)
793 purple_debug_warning("jabber", "Found mangled Content-Length header, or server returned 0-length response.\n");
795 conn->body_len = len;
798 if (connection && (!end_of_headers || connection < end_of_headers)) {
799 const char *tmp;
800 if (strstr(connection, "\r\n") == NULL)
801 return FALSE;
804 tmp = connection + strlen("\r\nConnection:");
805 while (*tmp && (*tmp == ' ' || *tmp == '\t'))
806 ++tmp;
808 if (!g_ascii_strncasecmp(tmp, "close", strlen("close"))) {
809 conn->close = TRUE;
810 jabber_bosh_disable_pipelining(conn->bosh);
814 if (end_of_headers) {
815 conn->headers_done = TRUE;
816 conn->handled_len = end_of_headers - conn->read_buf->str + 4;
817 } else {
818 conn->handled_len = conn->read_buf->len;
819 return FALSE;
823 /* Have we handled everything in the buffer? */
824 if (conn->handled_len >= conn->read_buf->len)
825 return FALSE;
827 /* Have we read all that the Content-Length promised us? */
828 if (conn->read_buf->len - conn->handled_len < conn->body_len)
829 return FALSE;
831 --conn->requests;
832 --conn->bosh->requests;
834 http_received_cb(conn->read_buf->str + conn->handled_len, conn->body_len,
835 conn->bosh);
837 /* Is there another response in the buffer ? */
838 if (conn->read_buf->len > conn->body_len + conn->handled_len) {
839 g_string_erase(conn->read_buf, 0, conn->handled_len + conn->body_len);
840 conn->headers_done = FALSE;
841 conn->handled_len = conn->body_len = 0;
842 return TRUE;
845 /* Connection: Close? */
846 if (conn->close && conn->state == HTTP_CONN_CONNECTED) {
847 if (purple_debug_is_verbose())
848 purple_debug_misc("jabber", "bosh (%p), server sent Connection: "
849 "close\n", conn);
850 http_connection_disconnected(conn);
853 if (conn->bosh->state == BOSH_CONN_ONLINE &&
854 (conn->bosh->requests == 0 || conn->bosh->pending->bufused > 0)) {
855 purple_debug_misc("jabber", "BOSH: Sending an empty request\n");
856 jabber_bosh_connection_send(conn->bosh, PACKET_NORMAL, NULL);
859 g_string_free(conn->read_buf, TRUE);
860 conn->read_buf = NULL;
861 conn->headers_done = FALSE;
862 conn->handled_len = conn->body_len = 0;
864 return FALSE;
868 * Common code for reading, called from http_connection_read_cb_ssl and
869 * http_connection_read_cb.
871 static void
872 http_connection_read(PurpleHTTPConnection *conn)
874 char buffer[1025];
875 int cnt;
877 if (!conn->read_buf)
878 conn->read_buf = g_string_new(NULL);
880 do {
881 if (conn->psc)
882 cnt = purple_ssl_read(conn->psc, buffer, sizeof(buffer));
883 else
884 cnt = read(conn->fd, buffer, sizeof(buffer));
886 if (cnt > 0) {
887 g_string_append_len(conn->read_buf, buffer, cnt);
889 } while (cnt > 0);
891 if (cnt == 0 || (cnt < 0 && errno != EAGAIN)) {
892 if (cnt < 0)
893 purple_debug_info("jabber", "BOSH (%p) read=%d, errno=%d, error=%s\n",
894 conn, cnt, errno, g_strerror(errno));
895 else
896 purple_debug_info("jabber", "BOSH server closed the connection (%p)\n",
897 conn);
900 * If the socket is closed, the processing really needs to know about
901 * it. Handle that now.
903 http_connection_disconnected(conn);
905 /* Process what we do have */
908 if (conn->read_buf->len > 0) {
909 while (jabber_bosh_http_connection_process(conn));
913 static void
914 http_connection_read_cb(gpointer data, gint fd, PurpleInputCondition condition)
916 PurpleHTTPConnection *conn = data;
918 http_connection_read(conn);
921 static void
922 http_connection_read_cb_ssl(gpointer data, PurpleSslConnection *psc,
923 PurpleInputCondition cond)
925 PurpleHTTPConnection *conn = data;
927 http_connection_read(conn);
930 static void
931 ssl_connection_established_cb(gpointer data, PurpleSslConnection *psc,
932 PurpleInputCondition cond)
934 PurpleHTTPConnection *conn = data;
936 purple_ssl_input_add(psc, http_connection_read_cb_ssl, conn);
937 connection_common_established_cb(conn);
940 static void
941 ssl_connection_error_cb(PurpleSslConnection *gsc, PurpleSslErrorType error,
942 gpointer data)
944 PurpleHTTPConnection *conn = data;
946 /* sslconn frees the connection on error */
947 conn->psc = NULL;
949 purple_connection_ssl_error(conn->bosh->js->gc, error);
952 static void
953 connection_established_cb(gpointer data, gint source, const gchar *error)
955 PurpleHTTPConnection *conn = data;
956 PurpleConnection *gc = conn->bosh->js->gc;
958 if (source < 0) {
959 gchar *tmp;
960 tmp = g_strdup_printf(_("Unable to establish a connection with the server: %s"),
961 error);
962 purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, tmp);
963 g_free(tmp);
964 return;
967 conn->fd = source;
968 conn->readh = purple_input_add(conn->fd, PURPLE_INPUT_READ,
969 http_connection_read_cb, conn);
970 connection_common_established_cb(conn);
973 static void http_connection_connect(PurpleHTTPConnection *conn)
975 PurpleBOSHConnection *bosh = conn->bosh;
976 PurpleConnection *gc = bosh->js->gc;
977 PurpleAccount *account = purple_connection_get_account(gc);
979 conn->state = HTTP_CONN_CONNECTING;
981 if (bosh->ssl) {
982 if (purple_ssl_is_supported()) {
983 conn->psc = purple_ssl_connect(account, bosh->host, bosh->port,
984 ssl_connection_established_cb,
985 ssl_connection_error_cb,
986 conn);
987 if (!conn->psc) {
988 purple_connection_error_reason(gc,
989 PURPLE_CONNECTION_ERROR_NO_SSL_SUPPORT,
990 _("Unable to establish SSL connection"));
992 } else {
993 purple_connection_error_reason(gc,
994 PURPLE_CONNECTION_ERROR_NO_SSL_SUPPORT,
995 _("SSL support unavailable"));
997 } else if (purple_proxy_connect(conn, account, bosh->host, bosh->port,
998 connection_established_cb, conn) == NULL) {
999 purple_connection_error_reason(gc,
1000 PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
1001 _("Unable to connect"));
1005 static int
1006 http_connection_do_send(PurpleHTTPConnection *conn, const char *data, int len)
1008 int ret;
1010 if (conn->psc)
1011 ret = purple_ssl_write(conn->psc, data, len);
1012 else
1013 ret = write(conn->fd, data, len);
1015 if (purple_debug_is_verbose())
1016 purple_debug_misc("jabber", "BOSH (%p): wrote %d bytes\n", conn, ret);
1018 return ret;
1021 static void
1022 http_connection_send_cb(gpointer data, gint source, PurpleInputCondition cond)
1024 PurpleHTTPConnection *conn = data;
1025 int ret;
1026 int writelen = purple_circ_buffer_get_max_read(conn->write_buf);
1028 if (writelen == 0) {
1029 purple_input_remove(conn->writeh);
1030 conn->writeh = 0;
1031 return;
1034 ret = http_connection_do_send(conn, conn->write_buf->outptr, writelen);
1036 if (ret < 0 && errno == EAGAIN)
1037 return;
1038 else if (ret <= 0) {
1040 * TODO: Handle this better. Probably requires a PurpleBOSHConnection
1041 * buffer that stores what is "being sent" until the
1042 * PurpleHTTPConnection reports it is fully sent.
1044 gchar *tmp = g_strdup_printf(_("Lost connection with server: %s"),
1045 g_strerror(errno));
1046 purple_connection_error_reason(conn->bosh->js->gc,
1047 PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
1048 tmp);
1049 g_free(tmp);
1050 return;
1053 purple_circ_buffer_mark_read(conn->write_buf, ret);
1056 static void
1057 http_connection_send_request(PurpleHTTPConnection *conn, const GString *req)
1059 char *data;
1060 int ret;
1061 size_t len;
1063 /* Sending something to the server, restart the inactivity timer */
1064 jabber_stream_restart_inactivity_timer(conn->bosh->js);
1066 data = g_strdup_printf("POST %s HTTP/1.1\r\n"
1067 "Host: %s\r\n"
1068 "User-Agent: %s\r\n"
1069 "Content-Encoding: text/xml; charset=utf-8\r\n"
1070 "Content-Length: %" G_GSIZE_FORMAT "\r\n\r\n"
1071 "%s",
1072 conn->bosh->path, conn->bosh->host, bosh_useragent,
1073 req->len, req->str);
1075 len = strlen(data);
1077 ++conn->requests;
1078 ++conn->bosh->requests;
1080 if (purple_debug_is_unsafe() && purple_debug_is_verbose())
1081 /* Will contain passwords for SASL PLAIN and is verbose */
1082 purple_debug_misc("jabber", "BOSH (%p): Sending %s\n", conn, data);
1083 else if (purple_debug_is_verbose())
1084 purple_debug_misc("jabber", "BOSH (%p): Sending request of "
1085 "%" G_GSIZE_FORMAT " bytes.\n", conn, len);
1087 if (conn->writeh == 0)
1088 ret = http_connection_do_send(conn, data, len);
1089 else {
1090 ret = -1;
1091 errno = EAGAIN;
1094 if (ret < 0 && errno != EAGAIN) {
1096 * TODO: Handle this better. Probably requires a PurpleBOSHConnection
1097 * buffer that stores what is "being sent" until the
1098 * PurpleHTTPConnection reports it is fully sent.
1100 gchar *tmp = g_strdup_printf(_("Lost connection with server: %s"),
1101 g_strerror(errno));
1102 purple_connection_error_reason(conn->bosh->js->gc,
1103 PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
1104 tmp);
1105 g_free(tmp);
1106 return;
1107 } else if ((size_t)ret < len) {
1108 if (ret < 0)
1109 ret = 0;
1110 if (conn->writeh == 0)
1111 conn->writeh = purple_input_add(conn->psc ? conn->psc->fd : conn->fd,
1112 PURPLE_INPUT_WRITE, http_connection_send_cb, conn);
1113 purple_circ_buffer_append(conn->write_buf, data + ret, len - ret);