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
31 TODO: test, what happens, if the http server (BOSH server) doesn't support
32 keep-alive (sends connection: close).
35 #define JABBER_BOSH_SEND_DELAY 250
37 #define JABBER_BOSH_TIMEOUT 10
39 static gchar
*jabber_bosh_useragent
= NULL
;
41 struct _PurpleJabberBOSHConnection
{
43 PurpleHttpKeepalivePool
*kapool
;
44 PurpleHttpConnection
*sc_req
; /* Session Creation Request */
45 PurpleHttpConnectionSet
*payload_reqs
;
49 gboolean is_terminating
;
52 guint64 rid
; /* Must be big enough to hold 2^53 - 1 */
58 static PurpleHttpRequest
*
59 jabber_bosh_connection_http_request_new(PurpleJabberBOSHConnection
*conn
,
62 jabber_bosh_connection_session_create(PurpleJabberBOSHConnection
*conn
);
64 jabber_bosh_connection_send_now(PurpleJabberBOSHConnection
*conn
);
67 jabber_bosh_init(void)
69 GHashTable
*ui_info
= purple_core_get_ui_info();
70 const gchar
*ui_name
= NULL
;
71 const gchar
*ui_version
= NULL
;
74 ui_name
= g_hash_table_lookup(ui_info
, "name");
75 ui_version
= g_hash_table_lookup(ui_info
, "version");
79 jabber_bosh_useragent
= g_strdup_printf(
80 "%s%s%s (libpurple " VERSION
")", ui_name
,
81 ui_version
? " " : "", ui_version
? ui_version
: "");
83 jabber_bosh_useragent
= g_strdup("libpurple " VERSION
);
86 void jabber_bosh_uninit(void)
88 g_free(jabber_bosh_useragent
);
89 jabber_bosh_useragent
= NULL
;
92 PurpleJabberBOSHConnection
*
93 jabber_bosh_connection_new(JabberStream
*js
, const gchar
*url
)
95 PurpleJabberBOSHConnection
*conn
;
98 url_p
= purple_http_url_parse(url
);
100 purple_debug_error("jabber-bosh", "Unable to parse given URL.\n");
104 conn
= g_new0(PurpleJabberBOSHConnection
, 1);
105 conn
->kapool
= purple_http_keepalive_pool_new();
106 conn
->payload_reqs
= purple_http_connection_set_new();
107 purple_http_keepalive_pool_set_limit_per_host(conn
->kapool
, 2);
108 conn
->url
= g_strdup(url
);
110 conn
->is_ssl
= (g_ascii_strcasecmp("https",
111 purple_http_url_get_protocol(url_p
)) == 0);
112 conn
->send_buff
= g_string_new(NULL
);
115 * Random 64-bit integer masked off by 2^52 - 1.
117 * This should produce a random integer in the range [0, 2^52). It's
118 * unlikely we'll send enough packets in one session to overflow the
121 conn
->rid
= (((guint64
)g_random_int() << 32) | g_random_int());
122 conn
->rid
&= 0xFFFFFFFFFFFFFLL
;
124 if (purple_ip_address_is_valid(purple_http_url_get_host(url_p
)))
125 js
->serverFQDN
= g_strdup(js
->user
->domain
);
127 js
->serverFQDN
= g_strdup(purple_http_url_get_host(url_p
));
129 purple_http_url_free(url_p
);
131 jabber_bosh_connection_session_create(conn
);
137 jabber_bosh_connection_destroy(PurpleJabberBOSHConnection
*conn
)
139 if (conn
== NULL
|| conn
->is_terminating
)
141 conn
->is_terminating
= TRUE
;
143 if (conn
->sid
!= NULL
) {
144 purple_debug_info("jabber-bosh",
145 "Terminating a session for %p\n", conn
);
146 jabber_bosh_connection_send_now(conn
);
149 purple_http_connection_set_destroy(conn
->payload_reqs
);
150 conn
->payload_reqs
= NULL
;
152 if (conn
->send_timer
)
153 g_source_remove(conn
->send_timer
);
155 purple_http_conn_cancel(conn
->sc_req
);
158 purple_http_keepalive_pool_unref(conn
->kapool
);
160 g_string_free(conn
->send_buff
, TRUE
);
161 conn
->send_buff
= NULL
;
172 jabber_bosh_connection_is_ssl(const PurpleJabberBOSHConnection
*conn
)
177 static PurpleXmlNode
*
178 jabber_bosh_connection_parse(PurpleJabberBOSHConnection
*conn
,
179 PurpleHttpResponse
*response
)
186 g_return_val_if_fail(conn
!= NULL
, NULL
);
187 g_return_val_if_fail(response
!= NULL
, NULL
);
189 if (conn
->is_terminating
|| purple_account_is_disconnecting(
190 purple_connection_get_account(conn
->js
->gc
)))
195 if (!purple_http_response_is_successful(response
)) {
196 purple_connection_error(conn
->js
->gc
,
197 PURPLE_CONNECTION_ERROR_NETWORK_ERROR
,
198 _("Unable to connect"));
202 data
= purple_http_response_get_data(response
, &data_len
);
203 root
= purple_xmlnode_from_str(data
, data_len
);
205 type
= purple_xmlnode_get_attrib(root
, "type");
206 if (purple_strequal(type
, "terminate")) {
207 purple_connection_error(conn
->js
->gc
,
208 PURPLE_CONNECTION_ERROR_OTHER_ERROR
, _("The BOSH "
209 "connection manager terminated your session."));
210 purple_xmlnode_free(root
);
218 jabber_bosh_connection_recv(PurpleHttpConnection
*http_conn
,
219 PurpleHttpResponse
*response
, gpointer _bosh_conn
)
221 PurpleJabberBOSHConnection
*bosh_conn
= _bosh_conn
;
222 PurpleXmlNode
*node
, *child
;
224 if (purple_debug_is_verbose() && purple_debug_is_unsafe()) {
225 purple_debug_misc("jabber-bosh", "received: %s\n",
226 purple_http_response_get_data(response
, NULL
));
229 node
= jabber_bosh_connection_parse(bosh_conn
, response
);
234 while (child
!= NULL
) {
235 /* jabber_process_packet might free child */
236 PurpleXmlNode
*next
= child
->next
;
239 if (child
->type
!= PURPLE_XMLNODE_TYPE_TAG
) {
244 /* Workaround for non-compliant servers that don't stamp
245 * the right xmlns on these packets. See #11315.
247 xmlns
= purple_xmlnode_get_namespace(child
);
248 if ((xmlns
== NULL
|| purple_strequal(xmlns
, NS_BOSH
)) &&
249 (purple_strequal(child
->name
, "iq") ||
250 purple_strequal(child
->name
, "message") ||
251 purple_strequal(child
->name
, "presence")))
253 purple_xmlnode_set_namespace(child
, NS_XMPP_CLIENT
);
256 jabber_process_packet(bosh_conn
->js
, &child
);
261 jabber_bosh_connection_send(bosh_conn
, NULL
);
265 jabber_bosh_connection_send_now(PurpleJabberBOSHConnection
*conn
)
267 PurpleHttpRequest
*req
;
270 g_return_if_fail(conn
!= NULL
);
272 if (conn
->send_timer
!= 0) {
273 g_source_remove(conn
->send_timer
);
274 conn
->send_timer
= 0;
277 if (conn
->sid
== NULL
)
280 data
= g_string_new(NULL
);
282 /* missing parameters: route, from, ack */
283 g_string_printf(data
, "<body "
284 "rid='%" G_GUINT64_FORMAT
"' "
286 "xmlns='" NS_BOSH
"' "
287 "xmlns:xmpp='" NS_XMPP_BOSH
"' ",
288 ++conn
->rid
, conn
->sid
);
290 if (conn
->js
->reinit
&& !conn
->is_terminating
) {
291 g_string_append(data
, "xmpp:restart='true'/>");
292 conn
->js
->reinit
= FALSE
;
294 if (conn
->is_terminating
)
295 g_string_append(data
, "type='terminate' ");
296 g_string_append_c(data
, '>');
297 g_string_append_len(data
, conn
->send_buff
->str
,
298 conn
->send_buff
->len
);
299 g_string_append(data
, "</body>");
300 g_string_set_size(conn
->send_buff
, 0);
303 if (purple_debug_is_verbose() && purple_debug_is_unsafe())
304 purple_debug_misc("jabber-bosh", "sending: %s\n", data
->str
);
306 req
= jabber_bosh_connection_http_request_new(conn
, data
);
307 g_string_free(data
, TRUE
);
309 if (conn
->is_terminating
) {
310 purple_http_request(NULL
, req
, NULL
, NULL
);
314 purple_http_connection_set_add(conn
->payload_reqs
,
315 purple_http_request(conn
->js
->gc
, req
,
316 jabber_bosh_connection_recv
, conn
));
319 purple_http_request_unref(req
);
323 jabber_bosh_connection_send_delayed(gpointer _conn
)
325 PurpleJabberBOSHConnection
*conn
= _conn
;
327 conn
->send_timer
= 0;
328 jabber_bosh_connection_send_now(conn
);
334 jabber_bosh_connection_send(PurpleJabberBOSHConnection
*conn
,
337 g_return_if_fail(conn
!= NULL
);
340 g_string_append(conn
->send_buff
, data
);
342 if (conn
->send_timer
== 0) {
343 conn
->send_timer
= g_timeout_add(
344 JABBER_BOSH_SEND_DELAY
,
345 jabber_bosh_connection_send_delayed
, conn
);
350 jabber_bosh_connection_send_keepalive(PurpleJabberBOSHConnection
*conn
)
352 g_return_if_fail(conn
!= NULL
);
354 jabber_bosh_connection_send_now(conn
);
358 jabber_bosh_version_check(const gchar
*version
, int major_req
, int minor_min
)
361 int major
, minor
= 0;
366 major
= atoi(version
);
367 dot
= strchr(version
, '.');
369 minor
= atoi(dot
+ 1);
371 if (major
!= major_req
)
373 if (minor
< minor_min
)
379 jabber_bosh_connection_session_created(PurpleHttpConnection
*http_conn
,
380 PurpleHttpResponse
*response
, gpointer _bosh_conn
)
382 PurpleJabberBOSHConnection
*bosh_conn
= _bosh_conn
;
383 PurpleXmlNode
*node
, *features
;
384 const gchar
*sid
, *ver
, *inactivity_str
;
387 bosh_conn
->sc_req
= NULL
;
389 if (purple_debug_is_verbose() && purple_debug_is_unsafe()) {
390 purple_debug_misc("jabber-bosh",
391 "received (session creation): %s\n",
392 purple_http_response_get_data(response
, NULL
));
395 node
= jabber_bosh_connection_parse(bosh_conn
, response
);
399 sid
= purple_xmlnode_get_attrib(node
, "sid");
400 ver
= purple_xmlnode_get_attrib(node
, "ver");
401 inactivity_str
= purple_xmlnode_get_attrib(node
, "inactivity");
402 /* requests = purple_xmlnode_get_attrib(node, "requests"); */
405 purple_connection_error(bosh_conn
->js
->gc
,
406 PURPLE_CONNECTION_ERROR_OTHER_ERROR
,
407 _("No BOSH session ID given"));
408 purple_xmlnode_free(node
);
413 purple_debug_info("jabber-bosh", "Missing version in BOSH initiation\n");
414 } else if (!jabber_bosh_version_check(ver
, 1, 6)) {
415 purple_debug_error("jabber-bosh",
416 "Unsupported BOSH version: %s\n", ver
);
417 purple_connection_error(bosh_conn
->js
->gc
,
418 PURPLE_CONNECTION_ERROR_NETWORK_ERROR
,
419 _("Unsupported version of BOSH protocol"));
420 purple_xmlnode_free(node
);
424 purple_debug_misc("jabber-bosh", "Session created for %p\n", bosh_conn
);
426 bosh_conn
->sid
= g_strdup(sid
);
429 inactivity
= atoi(inactivity_str
);
430 if (inactivity
< 0 || inactivity
> 3600) {
431 purple_debug_warning("jabber-bosh", "Ignoring invalid "
432 "inactivity value: %s\n", inactivity_str
);
435 if (inactivity
> 0) {
436 inactivity
-= 5; /* rounding */
439 bosh_conn
->js
->max_inactivity
= inactivity
;
440 if (bosh_conn
->js
->inactivity_timer
== 0) {
441 purple_debug_misc("jabber-bosh", "Starting inactivity "
442 "timer for %d secs (compensating for "
443 "rounding)\n", inactivity
);
444 jabber_stream_restart_inactivity_timer(bosh_conn
->js
);
448 jabber_stream_set_state(bosh_conn
->js
, JABBER_STREAM_AUTHENTICATING
);
450 /* FIXME: Depending on receiving features might break with some hosts */
451 features
= purple_xmlnode_get_child(node
, "features");
452 jabber_stream_features_parse(bosh_conn
->js
, features
);
454 purple_xmlnode_free(node
);
456 jabber_bosh_connection_send(bosh_conn
, NULL
);
460 jabber_bosh_connection_session_create(PurpleJabberBOSHConnection
*conn
)
462 PurpleHttpRequest
*req
;
465 g_return_if_fail(conn
!= NULL
);
467 if (conn
->sid
|| conn
->sc_req
)
470 purple_debug_misc("jabber-bosh", "Requesting Session Create for %p\n",
473 data
= g_string_new(NULL
);
475 /* missing optional parameters: route, from, ack */
476 g_string_printf(data
, "<body content='text/xml; charset=utf-8' "
477 "rid='%" G_GUINT64_FORMAT
"' "
483 "xmlns='" NS_BOSH
"' "
484 "xmpp:version='1.0' "
485 "xmlns:xmpp='urn:xmpp:xbosh' "
487 ++conn
->rid
, conn
->js
->user
->domain
, JABBER_BOSH_TIMEOUT
);
489 req
= jabber_bosh_connection_http_request_new(conn
, data
);
490 g_string_free(data
, TRUE
);
492 conn
->sc_req
= purple_http_request(conn
->js
->gc
, req
,
493 jabber_bosh_connection_session_created
, conn
);
495 purple_http_request_unref(req
);
498 static PurpleHttpRequest
*
499 jabber_bosh_connection_http_request_new(PurpleJabberBOSHConnection
*conn
,
502 PurpleHttpRequest
*req
;
504 jabber_stream_restart_inactivity_timer(conn
->js
);
506 req
= purple_http_request_new(conn
->url
);
507 purple_http_request_set_keepalive_pool(req
, conn
->kapool
);
508 purple_http_request_set_method(req
, "POST");
509 purple_http_request_set_timeout(req
, JABBER_BOSH_TIMEOUT
+ 2);
510 purple_http_request_header_set(req
, "User-Agent",
511 jabber_bosh_useragent
);
512 purple_http_request_header_set(req
, "Content-Encoding",
513 "text/xml; charset=utf-8");
514 purple_http_request_set_contents(req
, data
->str
, data
->len
);