rename accountopt.[ch] to purpleaccountoption.[ch]
[pidgin-git.git] / libpurple / protocols / jabber / bosh.c
blob6157ce2d368e812e536926bc3665dd94e2269747
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 "core.h"
25 #include "debug.h"
26 #include "http.h"
28 #include "bosh.h"
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 {
42 JabberStream *js;
43 PurpleHttpKeepalivePool *kapool;
44 PurpleHttpConnection *sc_req; /* Session Creation Request */
45 PurpleHttpConnectionSet *payload_reqs;
47 gchar *url;
48 gboolean is_ssl;
49 gboolean is_terminating;
51 gchar *sid;
52 guint64 rid; /* Must be big enough to hold 2^53 - 1 */
54 GString *send_buff;
55 guint send_timer;
58 static PurpleHttpRequest *
59 jabber_bosh_connection_http_request_new(PurpleJabberBOSHConnection *conn,
60 const GString *data);
61 static void
62 jabber_bosh_connection_session_create(PurpleJabberBOSHConnection *conn);
63 static void
64 jabber_bosh_connection_send_now(PurpleJabberBOSHConnection *conn);
66 void
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;
73 if (ui_info) {
74 ui_name = g_hash_table_lookup(ui_info, "name");
75 ui_version = g_hash_table_lookup(ui_info, "version");
78 if (ui_name) {
79 jabber_bosh_useragent = g_strdup_printf(
80 "%s%s%s (libpurple " VERSION ")", ui_name,
81 ui_version ? " " : "", ui_version ? ui_version : "");
82 } else
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;
96 PurpleHttpURL *url_p;
98 url_p = purple_http_url_parse(url);
99 if (!url_p) {
100 purple_debug_error("jabber-bosh", "Unable to parse given URL.\n");
101 return NULL;
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);
109 conn->js = js;
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
119 * rid.
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);
126 else
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);
133 return conn;
136 void
137 jabber_bosh_connection_destroy(PurpleJabberBOSHConnection *conn)
139 if (conn == NULL || conn->is_terminating)
140 return;
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);
156 conn->sc_req = NULL;
158 purple_http_keepalive_pool_unref(conn->kapool);
159 conn->kapool = NULL;
160 g_string_free(conn->send_buff, TRUE);
161 conn->send_buff = NULL;
163 g_free(conn->sid);
164 conn->sid = NULL;
165 g_free(conn->url);
166 conn->url = NULL;
168 g_free(conn);
171 gboolean
172 jabber_bosh_connection_is_ssl(const PurpleJabberBOSHConnection *conn)
174 return conn->is_ssl;
177 static PurpleXmlNode *
178 jabber_bosh_connection_parse(PurpleJabberBOSHConnection *conn,
179 PurpleHttpResponse *response)
181 PurpleXmlNode *root;
182 const gchar *data;
183 size_t data_len;
184 const gchar *type;
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)))
192 return NULL;
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"));
199 return NULL;
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);
211 return NULL;
214 return root;
217 static void
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);
230 if (node == NULL)
231 return;
233 child = node->child;
234 while (child != NULL) {
235 /* jabber_process_packet might free child */
236 PurpleXmlNode *next = child->next;
237 const gchar *xmlns;
239 if (child->type != PURPLE_XMLNODE_TYPE_TAG) {
240 child = next;
241 continue;
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);
258 child = next;
261 jabber_bosh_connection_send(bosh_conn, NULL);
264 static void
265 jabber_bosh_connection_send_now(PurpleJabberBOSHConnection *conn)
267 PurpleHttpRequest *req;
268 GString *data;
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)
278 return;
280 data = g_string_new(NULL);
282 /* missing parameters: route, from, ack */
283 g_string_printf(data, "<body "
284 "rid='%" G_GUINT64_FORMAT "' "
285 "sid='%s' "
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;
293 } else {
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);
311 g_free(conn->sid);
312 conn->sid = NULL;
313 } else {
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);
322 static gboolean
323 jabber_bosh_connection_send_delayed(gpointer _conn)
325 PurpleJabberBOSHConnection *conn = _conn;
327 conn->send_timer = 0;
328 jabber_bosh_connection_send_now(conn);
330 return FALSE;
333 void
334 jabber_bosh_connection_send(PurpleJabberBOSHConnection *conn,
335 const gchar *data)
337 g_return_if_fail(conn != NULL);
339 if (data)
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);
349 void
350 jabber_bosh_connection_send_keepalive(PurpleJabberBOSHConnection *conn)
352 g_return_if_fail(conn != NULL);
354 jabber_bosh_connection_send_now(conn);
357 static gboolean
358 jabber_bosh_version_check(const gchar *version, int major_req, int minor_min)
360 const gchar *dot;
361 int major, minor = 0;
363 if (version == NULL)
364 return FALSE;
366 major = atoi(version);
367 dot = strchr(version, '.');
368 if (dot)
369 minor = atoi(dot + 1);
371 if (major != major_req)
372 return FALSE;
373 if (minor < minor_min)
374 return FALSE;
375 return TRUE;
378 static void
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;
385 int inactivity = 0;
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);
396 if (node == NULL)
397 return;
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"); */
404 if (!sid) {
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);
409 return;
412 if (ver == NULL) {
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);
421 return;
424 purple_debug_misc("jabber-bosh", "Session created for %p\n", bosh_conn);
426 bosh_conn->sid = g_strdup(sid);
428 if (inactivity_str)
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);
433 inactivity = 0;
435 if (inactivity > 0) {
436 inactivity -= 5; /* rounding */
437 if (inactivity <= 0)
438 inactivity = 1;
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);
459 static void
460 jabber_bosh_connection_session_create(PurpleJabberBOSHConnection *conn)
462 PurpleHttpRequest *req;
463 GString *data;
465 g_return_if_fail(conn != NULL);
467 if (conn->sid || conn->sc_req)
468 return;
470 purple_debug_misc("jabber-bosh", "Requesting Session Create for %p\n",
471 conn);
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 "' "
478 "to='%s' "
479 "xml:lang='en' "
480 "ver='1.10' "
481 "wait='%d' "
482 "hold='1' "
483 "xmlns='" NS_BOSH "' "
484 "xmpp:version='1.0' "
485 "xmlns:xmpp='urn:xmpp:xbosh' "
486 "/>",
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,
500 const GString *data)
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);
516 return req;