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
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
24 #include "circbuffer.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
;
52 } PurpleBOSHPacketType
;
54 struct _PurpleBOSHConnection
{
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 */
79 guint8 failed_connections
;
89 struct _PurpleHTTPConnection
{
90 PurpleBOSHConnection
*bosh
;
91 PurpleSslConnection
*psc
;
93 PurpleCircBuffer
*write_buf
;
105 HTTP_CONN_CONNECTING
,
108 int requests
; /* number of outstanding HTTP requests */
110 gboolean headers_done
;
115 debug_dump_http_connections(PurpleBOSHConnection
*conn
)
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",
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
,
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
;
145 ui_name
= g_hash_table_lookup(ui_info
, "name");
146 ui_version
= g_hash_table_lookup(ui_info
, "version");
150 bosh_useragent
= g_strdup_printf("%s%s%s (libpurple " VERSION
")",
151 ui_name
, ui_version
? " " : "",
152 ui_version
? ui_version
: "");
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);
169 conn
->state
= HTTP_CONN_OFFLINE
;
171 conn
->write_buf
= purple_circ_buffer_new(0 /* default grow size */);
177 jabber_bosh_http_connection_destroy(PurpleHTTPConnection
*conn
)
180 g_string_free(conn
->read_buf
, TRUE
);
183 purple_circ_buffer_destroy(conn
->write_buf
);
185 purple_input_remove(conn
->readh
);
187 purple_input_remove(conn
->writeh
);
189 purple_ssl_close(conn
->psc
);
193 purple_proxy_connect_cancel_with_handle(conn
);
198 PurpleBOSHConnection
*
199 jabber_bosh_connection_init(JabberStream
*js
, const char *url
)
201 PurpleBOSHConnection
*conn
;
202 char *host
, *path
, *user
, *passwd
;
205 if (!purple_url_parse(url
, &host
, &port
, &path
, &user
, &passwd
)) {
206 purple_debug_info("jabber", "Unable to parse given URL.\n");
210 conn
= g_new0(PurpleBOSHConnection
, 1);
213 conn
->path
= g_strdup_printf("/%s", path
);
215 conn
->pipelining
= TRUE
;
217 if (purple_ip_address_is_valid(host
))
218 js
->serverFQDN
= g_strdup(js
->user
->domain
);
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 "
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
)
249 conn
->connections
[0] = jabber_bosh_http_connection_init(conn
);
255 jabber_bosh_connection_destroy(PurpleBOSHConnection
*conn
)
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
]);
275 gboolean
jabber_bosh_connection_is_ssl(PurpleBOSHConnection
*conn
)
280 static PurpleHTTPConnection
*
281 find_available_http_connection(PurpleBOSHConnection
*conn
)
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
)
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
]);
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
]);
332 purple_debug_warning("jabber", "Could not find a HTTP connection!\n");
334 /* None available. */
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
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
);
363 chosen
= find_available_http_connection(conn
);
366 if (type
== PACKET_FLUSH
)
369 * For non-ordinary traffic, we can't 'buffer' it, so use the
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
);
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
"' "
394 "xmlns='" NS_BOSH
"' "
395 "xmlns:xmpp='" NS_XMPP_BOSH
"'",
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
;
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
);
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
) {
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."));
446 send_timer_cb(gpointer data
)
448 PurpleBOSHConnection
*bosh
;
451 bosh
->send_timer
= 0;
453 jabber_bosh_connection_send(bosh
, PACKET_FLUSH
, NULL
);
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 */
469 jabber_bosh_disable_pipelining(PurpleBOSHConnection
*bosh
)
471 /* Do nothing if it's already disabled */
472 if (!bosh
->pipelining
)
475 purple_debug_info("jabber", "BOSH: Disabling pipelining on conn %p\n",
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]);
482 /* Shouldn't happen; this should be the only place pipelining
489 static void jabber_bosh_connection_received(PurpleBOSHConnection
*conn
, xmlnode
*node
) {
491 JabberStream
*js
= conn
->js
;
493 g_return_if_fail(node
!= NULL
);
494 if (jabber_bosh_connection_error_check(conn
, node
))
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
);
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
;
527 g_return_if_fail(node
!= NULL
);
528 if (jabber_bosh_connection_error_check(conn
, node
))
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");
538 conn
->sid
= g_strdup(sid
);
540 purple_connection_error_reason(js
->gc
,
541 PURPLE_CONNECTION_ERROR_NETWORK_ERROR
,
542 _("No session ID given"));
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
);
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"));
563 purple_debug_info("jabber", "Missing version in BOSH initiation\n");
567 js
->max_inactivity
= atoi(inactivity
);
568 if (js
->max_inactivity
<= 5) {
569 purple_debug_warning("jabber", "Ignoring bogusly small inactivity: %s\n",
571 /* Leave it at the default */
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",
580 jabber_stream_restart_inactivity_timer(js
);
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' "
604 "xmpp:version='1.0' "
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 */
612 "xmlns='" NS_BOSH
"'/>",
613 conn
->js
->user
->domain
,
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.
628 http_received_cb(const char *data
, int len
, PurpleBOSHConnection
*conn
)
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
);
647 conn
->receive_cb(conn
, node
);
650 purple_debug_warning("jabber", "BOSH: Received invalid XML\n");
654 void jabber_bosh_connection_send_raw(PurpleBOSHConnection
*conn
,
657 jabber_bosh_connection_send(conn
, PACKET_NORMAL
, data
);
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
);
672 if (conn
->read_buf
) {
673 g_string_free(conn
->read_buf
, TRUE
);
674 conn
->read_buf
= NULL
;
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
);
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
702 conn
->state
= HTTP_CONN_OFFLINE
;
704 purple_ssl_close(conn
->psc
);
706 } else if (conn
->fd
>= 0) {
712 purple_input_remove(conn
->readh
);
717 purple_input_remove(conn
->writeh
);
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
;
729 if (conn
->bosh
->pipelining
) {
730 /* Hmmmm, fall back to multiple connections */
731 jabber_bosh_disable_pipelining(conn
->bosh
);
735 /* If the server disconnected us without any requests, let's
736 * just wait until we have something to send before we reconnect
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"));
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.
765 jabber_bosh_http_connection_process(PurpleHTTPConnection
*conn
)
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
)) {
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.
791 len
= atoi(content_length
+ strlen("\r\nContent-Length:"));
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
)) {
800 if (strstr(connection
, "\r\n") == NULL
)
804 tmp
= connection
+ strlen("\r\nConnection:");
805 while (*tmp
&& (*tmp
== ' ' || *tmp
== '\t'))
808 if (!g_ascii_strncasecmp(tmp
, "close", strlen("close"))) {
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;
818 conn
->handled_len
= conn
->read_buf
->len
;
823 /* Have we handled everything in the buffer? */
824 if (conn
->handled_len
>= conn
->read_buf
->len
)
827 /* Have we read all that the Content-Length promised us? */
828 if (conn
->read_buf
->len
- conn
->handled_len
< conn
->body_len
)
832 --conn
->bosh
->requests
;
834 http_received_cb(conn
->read_buf
->str
+ conn
->handled_len
, conn
->body_len
,
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;
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: "
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;
868 * Common code for reading, called from http_connection_read_cb_ssl and
869 * http_connection_read_cb.
872 http_connection_read(PurpleHTTPConnection
*conn
)
878 conn
->read_buf
= g_string_new(NULL
);
882 cnt
= purple_ssl_read(conn
->psc
, buffer
, sizeof(buffer
));
884 cnt
= read(conn
->fd
, buffer
, sizeof(buffer
));
887 g_string_append_len(conn
->read_buf
, buffer
, cnt
);
891 if (cnt
== 0 || (cnt
< 0 && errno
!= EAGAIN
)) {
893 purple_debug_info("jabber", "BOSH (%p) read=%d, errno=%d, error=%s\n",
894 conn
, cnt
, errno
, g_strerror(errno
));
896 purple_debug_info("jabber", "BOSH server closed the connection (%p)\n",
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
));
914 http_connection_read_cb(gpointer data
, gint fd
, PurpleInputCondition condition
)
916 PurpleHTTPConnection
*conn
= data
;
918 http_connection_read(conn
);
922 http_connection_read_cb_ssl(gpointer data
, PurpleSslConnection
*psc
,
923 PurpleInputCondition cond
)
925 PurpleHTTPConnection
*conn
= data
;
927 http_connection_read(conn
);
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
);
941 ssl_connection_error_cb(PurpleSslConnection
*gsc
, PurpleSslErrorType error
,
944 PurpleHTTPConnection
*conn
= data
;
946 /* sslconn frees the connection on error */
949 purple_connection_ssl_error(conn
->bosh
->js
->gc
, error
);
953 connection_established_cb(gpointer data
, gint source
, const gchar
*error
)
955 PurpleHTTPConnection
*conn
= data
;
956 PurpleConnection
*gc
= conn
->bosh
->js
->gc
;
960 tmp
= g_strdup_printf(_("Unable to establish a connection with the server: %s"),
962 purple_connection_error_reason(gc
, PURPLE_CONNECTION_ERROR_NETWORK_ERROR
, tmp
);
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
;
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
,
988 purple_connection_error_reason(gc
,
989 PURPLE_CONNECTION_ERROR_NO_SSL_SUPPORT
,
990 _("Unable to establish SSL connection"));
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"));
1006 http_connection_do_send(PurpleHTTPConnection
*conn
, const char *data
, int len
)
1011 ret
= purple_ssl_write(conn
->psc
, data
, len
);
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
);
1022 http_connection_send_cb(gpointer data
, gint source
, PurpleInputCondition cond
)
1024 PurpleHTTPConnection
*conn
= data
;
1026 int writelen
= purple_circ_buffer_get_max_read(conn
->write_buf
);
1028 if (writelen
== 0) {
1029 purple_input_remove(conn
->writeh
);
1034 ret
= http_connection_do_send(conn
, conn
->write_buf
->outptr
, writelen
);
1036 if (ret
< 0 && errno
== EAGAIN
)
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"),
1046 purple_connection_error_reason(conn
->bosh
->js
->gc
,
1047 PURPLE_CONNECTION_ERROR_NETWORK_ERROR
,
1053 purple_circ_buffer_mark_read(conn
->write_buf
, ret
);
1057 http_connection_send_request(PurpleHTTPConnection
*conn
, const GString
*req
)
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"
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"
1072 conn
->bosh
->path
, conn
->bosh
->host
, bosh_useragent
,
1073 req
->len
, req
->str
);
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
);
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"),
1102 purple_connection_error_reason(conn
->bosh
->js
->gc
,
1103 PURPLE_CONNECTION_ERROR_NETWORK_ERROR
,
1107 } else if ((size_t)ret
< len
) {
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
);