Mercurial > pidgin
comparison libpurple/protocols/jabber/bosh.c @ 25988:f36a94f19db3
Restore BOSH to a more-or-less working state.
Major changes:
* Obey the 'requests' attribute on session creation response. Extra requests
are buffered until we can send something.
* Attempt to gracefully fail from a proxy that doesn't allow pipelining to
multiple TCP connections.
Still to do:
* SSL the Pidgin<>Connection Manager connection
* Pay attention to 'inactivity' and 'polling'
* The HTTP handler won't work if a read() on a pipelined connection returns
data from one response as well as the beginning of a second response.
author | Paul Aurich <paul@darkrain42.org> |
---|---|
date | Sun, 15 Mar 2009 04:48:47 +0000 |
parents | c11c14dde641 |
children | 71835e00c0fc |
comparison
equal
deleted
inserted
replaced
25987:035ba9355361 | 25988:f36a94f19db3 |
---|---|
26 #include "util.h" | 26 #include "util.h" |
27 #include "xmlnode.h" | 27 #include "xmlnode.h" |
28 | 28 |
29 #include "bosh.h" | 29 #include "bosh.h" |
30 | 30 |
31 typedef struct _PurpleHTTPRequest PurpleHTTPRequest; | 31 #define MAX_HTTP_CONNECTIONS 2 |
32 | |
32 typedef struct _PurpleHTTPConnection PurpleHTTPConnection; | 33 typedef struct _PurpleHTTPConnection PurpleHTTPConnection; |
33 | 34 |
34 typedef void (*PurpleHTTPConnectionConnectFunction)(PurpleHTTPConnection *conn); | 35 typedef void (*PurpleHTTPConnectionConnectFunction)(PurpleHTTPConnection *conn); |
35 typedef void (*PurpleHTTPConnectionDisconnectFunction)(PurpleHTTPConnection *conn); | 36 typedef void (*PurpleHTTPConnectionDisconnectFunction)(PurpleHTTPConnection *conn); |
36 typedef void (*PurpleBOSHConnectionConnectFunction)(PurpleBOSHConnection *conn); | 37 typedef void (*PurpleBOSHConnectionConnectFunction)(PurpleBOSHConnection *conn); |
37 typedef void (*PurpleBOSHConnectionReceiveFunction)(PurpleBOSHConnection *conn, xmlnode *node); | 38 typedef void (*PurpleBOSHConnectionReceiveFunction)(PurpleBOSHConnection *conn, xmlnode *node); |
38 | 39 |
39 static char *bosh_useragent = NULL; | 40 static char *bosh_useragent = NULL; |
41 | |
42 typedef enum { | |
43 PACKET_TERMINATE, | |
44 PACKET_STREAM_RESTART, | |
45 PACKET_NORMAL, | |
46 } PurpleBOSHPacketType; | |
40 | 47 |
41 struct _PurpleBOSHConnection { | 48 struct _PurpleBOSHConnection { |
42 /* decoded URL */ | 49 /* decoded URL */ |
43 char *host; | 50 char *host; |
44 int port; | 51 int port; |
49 char *sid; | 56 char *sid; |
50 int wait; | 57 int wait; |
51 | 58 |
52 JabberStream *js; | 59 JabberStream *js; |
53 gboolean pipelining; | 60 gboolean pipelining; |
54 PurpleHTTPConnection *conn_a; | 61 PurpleHTTPConnection *connections[MAX_HTTP_CONNECTIONS]; |
55 PurpleHTTPConnection *conn_b; | 62 |
63 int max_inactivity; | |
64 int max_requests; | |
65 int requests; | |
66 GString *pending; | |
56 | 67 |
57 gboolean ready; | 68 gboolean ready; |
58 PurpleBOSHConnectionConnectFunction connect_cb; | 69 PurpleBOSHConnectionConnectFunction connect_cb; |
59 PurpleBOSHConnectionReceiveFunction receive_cb; | 70 PurpleBOSHConnectionReceiveFunction receive_cb; |
60 }; | 71 }; |
61 | 72 |
62 struct _PurpleHTTPConnection { | 73 struct _PurpleHTTPConnection { |
63 int fd; | 74 int fd; |
75 gboolean ready; | |
64 char *host; | 76 char *host; |
65 int port; | 77 int port; |
66 int ie_handle; | 78 int ie_handle; |
67 int requests; /* number of outstanding HTTP requests */ | 79 int requests; /* number of outstanding HTTP requests */ |
68 | 80 |
69 GString *buf; | 81 GString *buf; |
70 gboolean headers_done; | 82 gboolean headers_done; |
71 gsize handled_len; | 83 gsize handled_len; |
72 gsize body_len; | 84 gsize body_len; |
75 PurpleHTTPConnectionConnectFunction connect_cb; | 87 PurpleHTTPConnectionConnectFunction connect_cb; |
76 PurpleHTTPConnectionConnectFunction disconnect_cb; | 88 PurpleHTTPConnectionConnectFunction disconnect_cb; |
77 PurpleBOSHConnection *bosh; | 89 PurpleBOSHConnection *bosh; |
78 }; | 90 }; |
79 | 91 |
80 struct _PurpleHTTPRequest { | |
81 const char *path; | |
82 char *data; | |
83 int data_len; | |
84 }; | |
85 | |
86 static void jabber_bosh_connection_stream_restart(PurpleBOSHConnection *conn); | 92 static void jabber_bosh_connection_stream_restart(PurpleBOSHConnection *conn); |
87 static gboolean jabber_bosh_connection_error_check(PurpleBOSHConnection *conn, xmlnode *node); | 93 static gboolean jabber_bosh_connection_error_check(PurpleBOSHConnection *conn, xmlnode *node); |
88 static void jabber_bosh_connection_received(PurpleBOSHConnection *conn, xmlnode *node); | 94 static void jabber_bosh_connection_received(PurpleBOSHConnection *conn, xmlnode *node); |
89 static void jabber_bosh_connection_send_native(PurpleBOSHConnection *conn, xmlnode *node); | 95 static void jabber_bosh_connection_send_native(PurpleBOSHConnection *conn, PurpleBOSHPacketType, xmlnode *node); |
90 | 96 |
91 static void jabber_bosh_http_connection_connect(PurpleHTTPConnection *conn); | 97 static void jabber_bosh_http_connection_connect(PurpleHTTPConnection *conn); |
92 static void jabber_bosh_http_connection_send_request(PurpleHTTPConnection *conn, PurpleHTTPRequest *req); | 98 static void jabber_bosh_connection_connected(PurpleHTTPConnection *conn); |
99 static void jabber_bosh_http_connection_disconnected(PurpleHTTPConnection *conn); | |
100 static void jabber_bosh_http_connection_send_request(PurpleHTTPConnection *conn, const GString *req); | |
93 | 101 |
94 void jabber_bosh_init(void) | 102 void jabber_bosh_init(void) |
95 { | 103 { |
96 GHashTable *ui_info = purple_core_get_ui_info(); | 104 GHashTable *ui_info = purple_core_get_ui_info(); |
97 const char *ui_name = NULL; | 105 const char *ui_name = NULL; |
114 { | 122 { |
115 g_free(bosh_useragent); | 123 g_free(bosh_useragent); |
116 bosh_useragent = NULL; | 124 bosh_useragent = NULL; |
117 } | 125 } |
118 | 126 |
119 static void | |
120 jabber_bosh_http_request_destroy(PurpleHTTPRequest *req) | |
121 { | |
122 g_free(req->data); | |
123 g_free(req); | |
124 } | |
125 | |
126 static PurpleHTTPConnection* | 127 static PurpleHTTPConnection* |
127 jabber_bosh_http_connection_init(const char *host, int port) | 128 jabber_bosh_http_connection_init(PurpleBOSHConnection *bosh) |
128 { | 129 { |
129 PurpleHTTPConnection *conn = g_new0(PurpleHTTPConnection, 1); | 130 PurpleHTTPConnection *conn = g_new0(PurpleHTTPConnection, 1); |
130 conn->host = g_strdup(host); | 131 conn->bosh = bosh; |
131 conn->port = port; | 132 conn->host = g_strdup(bosh->host); |
133 conn->port = bosh->port; | |
132 conn->fd = -1; | 134 conn->fd = -1; |
135 conn->ready = FALSE; | |
133 | 136 |
134 return conn; | 137 return conn; |
135 } | 138 } |
136 | 139 |
137 static void | 140 static void |
178 g_free(passwd); | 181 g_free(passwd); |
179 | 182 |
180 conn->js = js; | 183 conn->js = js; |
181 /* FIXME: This doesn't seem very random */ | 184 /* FIXME: This doesn't seem very random */ |
182 conn->rid = rand() % 100000 + 1728679472; | 185 conn->rid = rand() % 100000 + 1728679472; |
186 | |
187 conn->pending = g_string_new(""); | |
188 | |
183 conn->ready = FALSE; | 189 conn->ready = FALSE; |
184 conn->conn_a = jabber_bosh_http_connection_init(conn->host, conn->port); | 190 |
185 conn->conn_a->bosh = conn; | 191 conn->connections[0] = jabber_bosh_http_connection_init(conn); |
186 | 192 |
187 return conn; | 193 return conn; |
188 } | 194 } |
189 | 195 |
190 void | 196 void |
191 jabber_bosh_connection_destroy(PurpleBOSHConnection *conn) | 197 jabber_bosh_connection_destroy(PurpleBOSHConnection *conn) |
192 { | 198 { |
199 int i; | |
200 | |
193 g_free(conn->host); | 201 g_free(conn->host); |
194 g_free(conn->path); | 202 g_free(conn->path); |
195 | 203 |
196 if (conn->conn_a) | 204 if (conn->pending) |
197 jabber_bosh_http_connection_destroy(conn->conn_a); | 205 g_string_free(conn->pending, TRUE); |
198 if (conn->conn_b) | 206 |
199 jabber_bosh_http_connection_destroy(conn->conn_b); | 207 for (i = 0; i < MAX_HTTP_CONNECTIONS; ++i) { |
208 if (conn->connections[i]) | |
209 jabber_bosh_http_connection_destroy(conn->connections[i]); | |
210 } | |
200 | 211 |
201 g_free(conn); | 212 g_free(conn); |
202 } | 213 } |
203 | 214 |
204 void jabber_bosh_connection_close(PurpleBOSHConnection *conn) | 215 void jabber_bosh_connection_close(PurpleBOSHConnection *conn) |
205 { | 216 { |
206 xmlnode *packet = xmlnode_new("body"); | 217 jabber_bosh_connection_send_native(conn, PACKET_TERMINATE, NULL); |
207 /* XEP-0124: The rid must not exceed 16 characters */ | |
208 char rid[17]; | |
209 g_snprintf(rid, sizeof(rid), "%" G_GUINT64_FORMAT, ++conn->rid); | |
210 xmlnode_set_attrib(packet, "type", "terminate"); | |
211 xmlnode_set_attrib(packet, "xmlns", "http://jabber.org/protocol/httpbind"); | |
212 xmlnode_set_attrib(packet, "sid", conn->sid); | |
213 xmlnode_set_attrib(packet, "rid", rid); | |
214 | |
215 jabber_bosh_connection_send_native(conn, packet); | |
216 xmlnode_free(packet); | |
217 } | 218 } |
218 | 219 |
219 static void jabber_bosh_connection_stream_restart(PurpleBOSHConnection *conn) { | 220 static void jabber_bosh_connection_stream_restart(PurpleBOSHConnection *conn) { |
220 xmlnode *restart = xmlnode_new("body"); | 221 jabber_bosh_connection_send_native(conn, PACKET_STREAM_RESTART, NULL); |
221 /* XEP-0124: The rid must not exceed 16 characters */ | |
222 char rid[17]; | |
223 g_snprintf(rid, sizeof(rid), "%" G_GUINT64_FORMAT, ++conn->rid); | |
224 xmlnode_set_attrib(restart, "rid", rid); | |
225 xmlnode_set_attrib(restart, "sid", conn->sid); | |
226 xmlnode_set_attrib(restart, "to", conn->js->user->domain); | |
227 xmlnode_set_attrib(restart, "xml:lang", "en"); | |
228 xmlnode_set_attrib(restart, "xmpp:restart", "true"); | |
229 xmlnode_set_attrib(restart, "xmlns", "http://jabber.org/protocol/httpbind"); | |
230 xmlnode_set_attrib(restart, "xmlns:xmpp", "urn:xmpp:xbosh"); | |
231 | |
232 jabber_bosh_connection_send_native(conn, restart); | |
233 xmlnode_free(restart); | |
234 } | 222 } |
235 | 223 |
236 static gboolean jabber_bosh_connection_error_check(PurpleBOSHConnection *conn, xmlnode *node) { | 224 static gboolean jabber_bosh_connection_error_check(PurpleBOSHConnection *conn, xmlnode *node) { |
237 const char *type; | 225 const char *type; |
238 | 226 |
240 | 228 |
241 if (type != NULL && !strcmp(type, "terminate")) { | 229 if (type != NULL && !strcmp(type, "terminate")) { |
242 conn->ready = FALSE; | 230 conn->ready = FALSE; |
243 purple_connection_error_reason (conn->js->gc, | 231 purple_connection_error_reason (conn->js->gc, |
244 PURPLE_CONNECTION_ERROR_OTHER_ERROR, | 232 PURPLE_CONNECTION_ERROR_OTHER_ERROR, |
245 _("The BOSH conncetion manager suggested to terminate your session.")); | 233 _("The BOSH connection manager terminated your session.")); |
246 return TRUE; | 234 return TRUE; |
247 } | 235 } |
248 return FALSE; | 236 return FALSE; |
249 } | 237 } |
250 | 238 |
301 } | 289 } |
302 } | 290 } |
303 | 291 |
304 static void boot_response_cb(PurpleBOSHConnection *conn, xmlnode *node) { | 292 static void boot_response_cb(PurpleBOSHConnection *conn, xmlnode *node) { |
305 const char *sid, *version; | 293 const char *sid, *version; |
294 const char *inactivity, *requests; | |
306 xmlnode *packet; | 295 xmlnode *packet; |
307 | 296 |
308 g_return_if_fail(node != NULL); | 297 g_return_if_fail(node != NULL); |
309 if (jabber_bosh_connection_error_check(conn, node)) | 298 if (jabber_bosh_connection_error_check(conn, node)) |
310 return; | 299 return; |
311 | 300 |
312 sid = xmlnode_get_attrib(node, "sid"); | 301 sid = xmlnode_get_attrib(node, "sid"); |
313 version = xmlnode_get_attrib(node, "ver"); | 302 version = xmlnode_get_attrib(node, "ver"); |
303 | |
304 inactivity = xmlnode_get_attrib(node, "inactivity"); | |
305 requests = xmlnode_get_attrib(node, "requests"); | |
314 | 306 |
315 if (sid) { | 307 if (sid) { |
316 conn->sid = g_strdup(sid); | 308 conn->sid = g_strdup(sid); |
317 } else { | 309 } else { |
318 purple_connection_error_reason(conn->js->gc, | 310 purple_connection_error_reason(conn->js->gc, |
336 } | 328 } |
337 } else { | 329 } else { |
338 purple_debug_info("jabber", "Missing version in BOSH initiation\n"); | 330 purple_debug_info("jabber", "Missing version in BOSH initiation\n"); |
339 } | 331 } |
340 | 332 |
333 if (inactivity) | |
334 conn->max_inactivity = atoi(inactivity); | |
335 | |
336 if (requests) | |
337 conn->max_requests = atoi(requests); | |
338 | |
341 /* FIXME: Depending on receiving features might break with some hosts */ | 339 /* FIXME: Depending on receiving features might break with some hosts */ |
342 packet = xmlnode_get_child(node, "features"); | 340 packet = xmlnode_get_child(node, "features"); |
343 conn->js->use_bosh = TRUE; | 341 conn->js->use_bosh = TRUE; |
344 conn->receive_cb = auth_response_cb; | 342 conn->receive_cb = auth_response_cb; |
345 jabber_stream_features_parse(conn->js, packet); | 343 jabber_stream_features_parse(conn->js, packet); |
346 } | 344 } |
347 | 345 |
346 static PurpleHTTPConnection * | |
347 find_available_http_connection(PurpleBOSHConnection *conn) | |
348 { | |
349 int i; | |
350 | |
351 /* Easy solution: Does everyone involved support pipelining? Hooray! Just use | |
352 * one TCP connection! */ | |
353 if (conn->pipelining) | |
354 return conn->connections[0]; | |
355 | |
356 /* First loop, look for a connection that's ready */ | |
357 for (i = 0; i < MAX_HTTP_CONNECTIONS; ++i) { | |
358 if (conn->connections[i] && conn->connections[i]->ready && | |
359 conn->connections[i]->requests == 0) | |
360 return conn->connections[i]; | |
361 } | |
362 | |
363 /* Second loop, look for one that's NULL and create a new connection */ | |
364 for (i = 0; i < MAX_HTTP_CONNECTIONS; ++i) { | |
365 if (!conn->connections[i]) { | |
366 conn->connections[i] = jabber_bosh_http_connection_init(conn); | |
367 | |
368 conn->connections[i]->connect_cb = jabber_bosh_connection_connected; | |
369 conn->connections[i]->disconnect_cb = jabber_bosh_http_connection_disconnected; | |
370 jabber_bosh_http_connection_connect(conn->connections[i]); | |
371 return NULL; | |
372 } | |
373 } | |
374 | |
375 /* None available. */ | |
376 return NULL; | |
377 } | |
378 | |
348 static void jabber_bosh_connection_boot(PurpleBOSHConnection *conn) { | 379 static void jabber_bosh_connection_boot(PurpleBOSHConnection *conn) { |
349 xmlnode *init = xmlnode_new("body"); | 380 GString *buf = g_string_new(""); |
350 /* XEP-0124: The rid must not exceed 16 characters */ | 381 |
351 char rid[17]; | 382 g_string_printf(buf, "<body content='text/xml; charset=utf-8' " |
352 g_snprintf(rid, sizeof(rid), "%" G_GUINT64_FORMAT, ++conn->rid); | 383 "secure='true' " |
353 xmlnode_set_attrib(init, "content", "text/xml; charset=utf-8"); | 384 "to='%s' " |
354 xmlnode_set_attrib(init, "secure", "true"); | 385 "xml:lang='en' " |
355 /* | 386 "xmpp:version='1.0' " |
356 xmlnode_set_attrib(init, "route", tmp = g_strdup_printf("xmpp:%s:5222", conn->js->user->domain)); | 387 "ver='1.6' " |
357 g_free(tmp); | 388 "xmlns:xmpp='urn:xmpp:bosh' " |
358 */ | 389 "rid='%" G_GUINT64_FORMAT "' " |
359 xmlnode_set_attrib(init, "to", conn->js->user->domain); | 390 /* TODO: This should be adjusted/adjustable automatically according to |
360 xmlnode_set_attrib(init, "xml:lang", "en"); | 391 * realtime network behavior */ |
361 xmlnode_set_attrib(init, "xmpp:version", "1.0"); | 392 "wait='60' " |
362 xmlnode_set_attrib(init, "ver", "1.6"); | 393 "hold='1' " |
363 xmlnode_set_attrib(init, "xmlns:xmpp", "urn:xmpp:xbosh"); | 394 "xmlns='http://jabber.org/protocol/httpbind'/>", |
364 xmlnode_set_attrib(init, "rid", rid); | 395 conn->js->user->domain, |
365 xmlnode_set_attrib(init, "wait", "60"); /* this should be adjusted automatically according to real time network behavior */ | 396 ++conn->rid); |
366 xmlnode_set_attrib(init, "xmlns", "http://jabber.org/protocol/httpbind"); | 397 |
367 xmlnode_set_attrib(init, "hold", "1"); | |
368 | |
369 conn->receive_cb = boot_response_cb; | 398 conn->receive_cb = boot_response_cb; |
370 jabber_bosh_connection_send_native(conn, init); | 399 jabber_bosh_http_connection_send_request(conn->connections[0], buf); |
371 xmlnode_free(init); | 400 g_string_free(buf, TRUE); |
372 } | 401 } |
373 | 402 |
374 static void | 403 static void |
375 http_received_cb(const char *data, int len, PurpleBOSHConnection *conn) | 404 http_received_cb(const char *data, int len, PurpleBOSHConnection *conn) |
376 { | 405 { |
389 g_return_if_reached(); | 418 g_return_if_reached(); |
390 } | 419 } |
391 } | 420 } |
392 | 421 |
393 void jabber_bosh_connection_send(PurpleBOSHConnection *conn, xmlnode *node) { | 422 void jabber_bosh_connection_send(PurpleBOSHConnection *conn, xmlnode *node) { |
394 xmlnode *packet = xmlnode_new("body"); | 423 jabber_bosh_connection_send_native(conn, PACKET_NORMAL, node); |
395 /* XEP-0124: The rid must not exceed 16 characters */ | |
396 char rid[17]; | |
397 g_snprintf(rid, sizeof(rid), "%" G_GUINT64_FORMAT, ++conn->rid); | |
398 xmlnode_set_attrib(packet, "xmlns", "http://jabber.org/protocol/httpbind"); | |
399 xmlnode_set_attrib(packet, "sid", conn->sid); | |
400 xmlnode_set_attrib(packet, "rid", rid); | |
401 | |
402 if (node) { | |
403 xmlnode *copy = xmlnode_copy(node); | |
404 xmlnode_insert_child(packet, copy); | |
405 if (conn->ready == TRUE) | |
406 xmlnode_set_attrib(copy, "xmlns", "jabber:client"); | |
407 } | |
408 jabber_bosh_connection_send_native(conn, packet); | |
409 xmlnode_free(packet); | |
410 } | 424 } |
411 | 425 |
412 void jabber_bosh_connection_send_raw(PurpleBOSHConnection *conn, | 426 void jabber_bosh_connection_send_raw(PurpleBOSHConnection *conn, |
413 const char *data, int len) | 427 const char *data, int len) |
414 { | 428 { |
415 xmlnode *node = xmlnode_from_str(data, len); | 429 xmlnode *node = xmlnode_from_str(data, len); |
416 if (node) { | 430 if (node) { |
417 jabber_bosh_connection_send(conn, node); | 431 jabber_bosh_connection_send_native(conn, PACKET_NORMAL, node); |
418 xmlnode_free(node); | 432 xmlnode_free(node); |
419 } else { | 433 } else { |
420 /* | 434 /* |
421 * This best emulates what a normal XMPP server would do | 435 * This best emulates what a normal XMPP server would do |
422 * if you send bad XML. | 436 * if you send bad XML. |
425 PURPLE_CONNECTION_ERROR_NETWORK_ERROR, | 439 PURPLE_CONNECTION_ERROR_NETWORK_ERROR, |
426 _("Cannot send malformed XML")); | 440 _("Cannot send malformed XML")); |
427 } | 441 } |
428 } | 442 } |
429 | 443 |
430 static void jabber_bosh_connection_send_native(PurpleBOSHConnection *conn, xmlnode *node) { | 444 static void |
431 PurpleHTTPRequest *request; | 445 jabber_bosh_connection_send_native(PurpleBOSHConnection *conn, PurpleBOSHPacketType type, |
446 xmlnode *node) | |
447 { | |
448 PurpleHTTPConnection *chosen; | |
449 GString *packet = NULL; | |
450 char *buf = NULL; | |
451 | |
452 chosen = find_available_http_connection(conn); | |
453 | |
454 if (type != PACKET_NORMAL && !chosen) { | |
455 /* | |
456 * For non-ordinary traffic, we don't want to 'buffer' it, so use the first | |
457 * connection. | |
458 */ | |
459 chosen = conn->connections[0]; | |
432 | 460 |
433 request = g_new0(PurpleHTTPRequest, 1); | 461 if (!chosen->ready) |
434 request->path = conn->path; | 462 purple_debug_warning("jabber", "First BOSH connection wasn't ready. Bad " |
435 request->data = xmlnode_to_str(node, &(request->data_len)); | 463 "things may happen.\n"); |
436 | 464 } |
437 jabber_bosh_http_connection_send_request(conn->conn_a, request); | 465 |
466 if (node) | |
467 buf = xmlnode_to_str(node, NULL); | |
468 | |
469 if (type == PACKET_NORMAL && (!chosen || | |
470 (conn->max_requests > 0 && conn->requests == conn->max_requests))) { | |
471 /* | |
472 * For normal data, send up to max_requests requests at a time or there is no | |
473 * connection ready (likely, we're currently opening a second connection and | |
474 * will send these packets when connected). | |
475 */ | |
476 if (buf) { | |
477 conn->pending = g_string_append(conn->pending, buf); | |
478 g_free(buf); | |
479 } | |
480 return; | |
481 } | |
482 | |
483 packet = g_string_new(""); | |
484 | |
485 g_string_printf(packet, "<body " | |
486 "rid='%" G_GUINT64_FORMAT "' " | |
487 "sid='%s' " | |
488 "to='%s' " | |
489 "xml:lang='en' " | |
490 "xmlns='http://jabber.org/protocol/httpbind' " | |
491 "xmlns:xmpp='urn:xmpp:xbosh'", | |
492 ++conn->rid, | |
493 conn->sid, | |
494 conn->js->user->domain); | |
495 | |
496 if (type == PACKET_STREAM_RESTART) | |
497 packet = g_string_append(packet, " xmpp:restart='true'/>"); | |
498 else { | |
499 if (type == PACKET_TERMINATE) | |
500 packet = g_string_append(packet, " type='terminate'"); | |
501 | |
502 g_string_append_printf(packet, ">%s%s</body>", conn->pending->str, | |
503 buf ? buf : ""); | |
504 g_string_truncate(conn->pending, 0); | |
505 } | |
506 | |
507 g_free(buf); | |
508 | |
509 jabber_bosh_http_connection_send_request(chosen, packet); | |
438 } | 510 } |
439 | 511 |
440 static void jabber_bosh_connection_connected(PurpleHTTPConnection *conn) { | 512 static void jabber_bosh_connection_connected(PurpleHTTPConnection *conn) { |
441 if (conn->bosh->ready == TRUE && conn->bosh->connect_cb) { | 513 conn->ready = TRUE; |
514 | |
515 if (conn->bosh->ready) { | |
442 purple_debug_info("jabber", "BOSH session already exists. Trying to reuse it.\n"); | 516 purple_debug_info("jabber", "BOSH session already exists. Trying to reuse it.\n"); |
517 if (conn->bosh->pending && conn->bosh->pending->len > 0) { | |
518 /* Send the pending data */ | |
519 jabber_bosh_connection_send_native(conn->bosh, PACKET_NORMAL, NULL); | |
520 } | |
521 #if 0 | |
443 conn->bosh->receive_cb = jabber_bosh_connection_received; | 522 conn->bosh->receive_cb = jabber_bosh_connection_received; |
444 conn->bosh->connect_cb(conn->bosh); | 523 if (conn->bosh->connect_cb) |
524 conn->bosh->connect_cb(conn->bosh); | |
525 #endif | |
445 } else | 526 } else |
446 jabber_bosh_connection_boot(conn->bosh); | 527 jabber_bosh_connection_boot(conn->bosh); |
447 } | 528 } |
448 | 529 |
449 void jabber_bosh_connection_refresh(PurpleBOSHConnection *conn) | 530 void jabber_bosh_connection_refresh(PurpleBOSHConnection *conn) |
450 { | 531 { |
451 jabber_bosh_connection_send(conn, NULL); | 532 jabber_bosh_connection_send(conn, NULL); |
452 } | 533 } |
453 | 534 |
454 static void jabber_bosh_http_connection_disconnected(PurpleHTTPConnection *conn) { | 535 static void jabber_bosh_http_connection_disconnected(PurpleHTTPConnection *conn) { |
536 /* | |
537 * Well, then. Fine! I never liked you anyway, server! I was cheating on you | |
538 * with AIM! | |
539 */ | |
540 conn->ready = FALSE; | |
541 | |
542 if (conn->bosh->pipelining) | |
543 /* Hmmmm, fall back to multiple connections */ | |
544 conn->bosh->pipelining = FALSE; | |
545 | |
546 /* No! Please! Take me back. It was me, not you! I was weak! */ | |
455 conn->connect_cb = jabber_bosh_connection_connected; | 547 conn->connect_cb = jabber_bosh_connection_connected; |
456 jabber_bosh_http_connection_connect(conn); | 548 jabber_bosh_http_connection_connect(conn); |
457 } | 549 } |
458 | 550 |
459 void jabber_bosh_connection_connect(PurpleBOSHConnection *conn) { | 551 void jabber_bosh_connection_connect(PurpleBOSHConnection *bosh) { |
460 conn->conn_a->connect_cb = jabber_bosh_connection_connected; | 552 PurpleHTTPConnection *conn = bosh->connections[0]; |
461 jabber_bosh_http_connection_connect(conn->conn_a); | 553 conn->connect_cb = jabber_bosh_connection_connected; |
554 conn->disconnect_cb = jabber_bosh_http_connection_disconnected; | |
555 jabber_bosh_http_connection_connect(conn); | |
462 } | 556 } |
463 | 557 |
464 static void | 558 static void |
465 jabber_bosh_http_connection_process(PurpleHTTPConnection *conn) | 559 jabber_bosh_http_connection_process(PurpleHTTPConnection *conn) |
466 { | 560 { |
499 /* Have we read all that the Content-Length promised us? */ | 593 /* Have we read all that the Content-Length promised us? */ |
500 if (conn->buf->len - conn->handled_len < conn->body_len) | 594 if (conn->buf->len - conn->handled_len < conn->body_len) |
501 return; | 595 return; |
502 | 596 |
503 --conn->requests; | 597 --conn->requests; |
598 --conn->bosh->requests; | |
504 | 599 |
505 #warning For a pure HTTP 1.1 stack, this would need to be handled elsewhere. | 600 #warning For a pure HTTP 1.1 stack, this would need to be handled elsewhere. |
506 if (conn->bosh->ready && conn->requests == 0) { | 601 if (conn->bosh->ready && conn->bosh->requests == 0) { |
507 jabber_bosh_connection_send(conn->bosh, NULL); | 602 jabber_bosh_connection_send(conn->bosh, NULL); |
508 purple_debug_misc("jabber", "BOSH: Sending an empty request\n"); | 603 purple_debug_misc("jabber", "BOSH: Sending an empty request\n"); |
509 } | 604 } |
510 | 605 |
511 http_received_cb(conn->buf->str + conn->handled_len, conn->body_len, | 606 http_received_cb(conn->buf->str + conn->handled_len, conn->body_len, |
526 int perrno; | 621 int perrno; |
527 int cnt, count = 0; | 622 int cnt, count = 0; |
528 | 623 |
529 purple_debug_info("jabber", "jabber_bosh_http_connection_read\n"); | 624 purple_debug_info("jabber", "jabber_bosh_http_connection_read\n"); |
530 | 625 |
531 if (conn->buf == NULL) | 626 if (!conn->buf) |
532 conn->buf = g_string_new(""); | 627 conn->buf = g_string_new(""); |
533 | 628 |
534 while ((cnt = read(fd, buffer, sizeof(buffer))) > 0) { | 629 while ((cnt = read(fd, buffer, sizeof(buffer))) > 0) { |
535 purple_debug_info("jabber", "bosh read %d bytes\n", cnt); | 630 purple_debug_info("jabber", "bosh read %d bytes\n", cnt); |
536 count += cnt; | 631 count += cnt; |
597 } | 692 } |
598 } | 693 } |
599 | 694 |
600 static void | 695 static void |
601 jabber_bosh_http_connection_send_request(PurpleHTTPConnection *conn, | 696 jabber_bosh_http_connection_send_request(PurpleHTTPConnection *conn, |
602 PurpleHTTPRequest *req) | 697 const GString *req) |
603 { | 698 { |
604 GString *packet = g_string_new(""); | 699 GString *packet = g_string_new(""); |
605 int ret; | 700 int ret; |
606 | 701 |
607 /* TODO: Should we lie about this HTTP/1.1 support? */ | 702 g_string_printf(packet, "POST %s HTTP/1.1\r\n" |
608 g_string_append_printf(packet, "POST %s HTTP/1.1\r\n" | |
609 "Host: %s\r\n" | 703 "Host: %s\r\n" |
610 "User-Agent: %s\r\n" | 704 "User-Agent: %s\r\n" |
611 "Content-Encoding: text/xml; charset=utf-8\r\n" | 705 "Content-Encoding: text/xml; charset=utf-8\r\n" |
612 "Content-Length: %d\r\n\r\n", | 706 "Content-Length: %" G_GSIZE_FORMAT "\r\n\r\n", |
613 req->path, conn->host, bosh_useragent, req->data_len); | 707 conn->bosh->path, conn->host, bosh_useragent, |
614 | 708 req->len); |
615 packet = g_string_append(packet, req->data); | 709 |
710 packet = g_string_append(packet, req->str); | |
616 | 711 |
617 purple_debug_misc("jabber", "BOSH out: %s\n", packet->str); | 712 purple_debug_misc("jabber", "BOSH out: %s\n", packet->str); |
618 /* TODO: Better error handling, circbuffer or possible integration with | 713 /* TODO: Better error handling, circbuffer or possible integration with |
619 * low-level code in jabber.c */ | 714 * low-level code in jabber.c */ |
620 ret = write(conn->fd, packet->str, packet->len); | 715 ret = write(conn->fd, packet->str, packet->len); |
621 | 716 |
622 ++conn->requests; | 717 ++conn->requests; |
718 ++conn->bosh->requests; | |
623 g_string_free(packet, TRUE); | 719 g_string_free(packet, TRUE); |
624 jabber_bosh_http_request_destroy(req); | |
625 | 720 |
626 if (ret < 0 && errno == EAGAIN) | 721 if (ret < 0 && errno == EAGAIN) |
627 purple_debug_warning("jabber", "BOSH write would have blocked\n"); | 722 purple_debug_warning("jabber", "BOSH write would have blocked\n"); |
628 | 723 |
629 if (ret <= 0) { | 724 if (ret <= 0) { |