/src/pidgin/libpurple/protocols/jabber/bosh.c
Line | Count | Source (jump to first uncovered line) |
1 | | /* |
2 | | * purple - Jabber Protocol Plugin |
3 | | * |
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. |
7 | | * |
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. |
12 | | * |
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. |
17 | | * |
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 |
21 | | * |
22 | | */ |
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" |
31 | | |
32 | | #include "bosh.h" |
33 | | |
34 | | /* The number of HTTP connections to use. This MUST be at least 2. */ |
35 | 0 | #define NUM_HTTP_CONNECTIONS 2 |
36 | | /* How many failed connection attempts before it becomes a fatal error */ |
37 | 0 | #define MAX_FAILED_CONNECTIONS 3 |
38 | | /* How long in seconds to queue up outgoing messages */ |
39 | 0 | #define BUFFER_SEND_IN_SECS 1 |
40 | | |
41 | | typedef struct _PurpleHTTPConnection PurpleHTTPConnection; |
42 | | |
43 | | typedef void (*PurpleBOSHConnectionConnectFunction)(PurpleBOSHConnection *conn); |
44 | | typedef void (*PurpleBOSHConnectionReceiveFunction)(PurpleBOSHConnection *conn, xmlnode *node); |
45 | | |
46 | | static char *bosh_useragent = NULL; |
47 | | |
48 | | typedef enum { |
49 | | PACKET_NORMAL, |
50 | | PACKET_TERMINATE, |
51 | | PACKET_FLUSH, |
52 | | } PurpleBOSHPacketType; |
53 | | |
54 | | struct _PurpleBOSHConnection { |
55 | | JabberStream *js; |
56 | | PurpleHTTPConnection *connections[NUM_HTTP_CONNECTIONS]; |
57 | | |
58 | | PurpleCircBuffer *pending; |
59 | | PurpleBOSHConnectionConnectFunction connect_cb; |
60 | | PurpleBOSHConnectionReceiveFunction receive_cb; |
61 | | |
62 | | /* Must be big enough to hold 2^53 - 1 */ |
63 | | char *sid; |
64 | | guint64 rid; |
65 | | |
66 | | /* decoded URL */ |
67 | | char *host; |
68 | | char *path; |
69 | | guint16 port; |
70 | | |
71 | | gboolean ssl; |
72 | | |
73 | | enum { |
74 | | BOSH_CONN_OFFLINE, |
75 | | BOSH_CONN_BOOTING, |
76 | | BOSH_CONN_ONLINE |
77 | | } state; |
78 | | guint8 failed_connections; |
79 | | |
80 | | int wait; |
81 | | |
82 | | int max_requests; |
83 | | int requests; |
84 | | |
85 | | guint send_timer; |
86 | | }; |
87 | | |
88 | | struct _PurpleHTTPConnection { |
89 | | PurpleBOSHConnection *bosh; |
90 | | PurpleSslConnection *psc; |
91 | | |
92 | | PurpleCircBuffer *write_buf; |
93 | | GString *read_buf; |
94 | | |
95 | | gsize handled_len; |
96 | | gsize body_len; |
97 | | |
98 | | int fd; |
99 | | guint readh; |
100 | | guint writeh; |
101 | | |
102 | | enum { |
103 | | HTTP_CONN_OFFLINE, |
104 | | HTTP_CONN_CONNECTING, |
105 | | HTTP_CONN_CONNECTED |
106 | | } state; |
107 | | int requests; /* number of outstanding HTTP requests */ |
108 | | |
109 | | gboolean headers_done; |
110 | | gboolean close; |
111 | | }; |
112 | | |
113 | | static void |
114 | | debug_dump_http_connections(PurpleBOSHConnection *conn) |
115 | 0 | { |
116 | 0 | int i; |
117 | |
|
118 | 0 | g_return_if_fail(conn != NULL); |
119 | | |
120 | 0 | for (i = 0; i < NUM_HTTP_CONNECTIONS; ++i) { |
121 | 0 | PurpleHTTPConnection *httpconn = conn->connections[i]; |
122 | 0 | if (httpconn == NULL) |
123 | 0 | purple_debug_misc("jabber", "BOSH %p->connections[%d] = (nil)\n", |
124 | 0 | conn, i); |
125 | 0 | else |
126 | 0 | purple_debug_misc("jabber", "BOSH %p->connections[%d] = %p, state = %d" |
127 | 0 | ", requests = %d\n", conn, i, httpconn, |
128 | 0 | httpconn->state, httpconn->requests); |
129 | 0 | } |
130 | 0 | } |
131 | | |
132 | | static void http_connection_connect(PurpleHTTPConnection *conn); |
133 | | static void http_connection_send_request(PurpleHTTPConnection *conn, |
134 | | const GString *req); |
135 | | static gboolean send_timer_cb(gpointer data); |
136 | | |
137 | | void jabber_bosh_init(void) |
138 | 0 | { |
139 | 0 | GHashTable *ui_info = purple_core_get_ui_info(); |
140 | 0 | const char *ui_name = NULL; |
141 | 0 | const char *ui_version = NULL; |
142 | |
|
143 | 0 | if (ui_info) { |
144 | 0 | ui_name = g_hash_table_lookup(ui_info, "name"); |
145 | 0 | ui_version = g_hash_table_lookup(ui_info, "version"); |
146 | 0 | } |
147 | |
|
148 | 0 | if (ui_name) |
149 | 0 | bosh_useragent = g_strdup_printf("%s%s%s (libpurple " VERSION ")", |
150 | 0 | ui_name, ui_version ? " " : "", |
151 | 0 | ui_version ? ui_version : ""); |
152 | 0 | else |
153 | 0 | bosh_useragent = g_strdup("libpurple " VERSION); |
154 | 0 | } |
155 | | |
156 | | void jabber_bosh_uninit(void) |
157 | 0 | { |
158 | 0 | g_free(bosh_useragent); |
159 | 0 | bosh_useragent = NULL; |
160 | 0 | } |
161 | | |
162 | | static PurpleHTTPConnection* |
163 | | jabber_bosh_http_connection_init(PurpleBOSHConnection *bosh) |
164 | 0 | { |
165 | 0 | PurpleHTTPConnection *conn = g_new0(PurpleHTTPConnection, 1); |
166 | 0 | conn->bosh = bosh; |
167 | 0 | conn->fd = -1; |
168 | 0 | conn->state = HTTP_CONN_OFFLINE; |
169 | |
|
170 | 0 | conn->write_buf = purple_circ_buffer_new(0 /* default grow size */); |
171 | |
|
172 | 0 | return conn; |
173 | 0 | } |
174 | | |
175 | | static void |
176 | | jabber_bosh_http_connection_destroy(PurpleHTTPConnection *conn) |
177 | 0 | { |
178 | 0 | if (conn->read_buf) |
179 | 0 | g_string_free(conn->read_buf, TRUE); |
180 | |
|
181 | 0 | if (conn->write_buf) |
182 | 0 | purple_circ_buffer_destroy(conn->write_buf); |
183 | 0 | if (conn->readh) |
184 | 0 | purple_input_remove(conn->readh); |
185 | 0 | if (conn->writeh) |
186 | 0 | purple_input_remove(conn->writeh); |
187 | 0 | if (conn->psc) |
188 | 0 | purple_ssl_close(conn->psc); |
189 | 0 | if (conn->fd >= 0) |
190 | 0 | close(conn->fd); |
191 | |
|
192 | 0 | purple_proxy_connect_cancel_with_handle(conn); |
193 | |
|
194 | 0 | g_free(conn); |
195 | 0 | } |
196 | | |
197 | | PurpleBOSHConnection* |
198 | | jabber_bosh_connection_init(JabberStream *js, const char *url) |
199 | 0 | { |
200 | 0 | PurpleBOSHConnection *conn; |
201 | 0 | char *host, *path, *user, *passwd; |
202 | 0 | int port; |
203 | |
|
204 | 0 | if (!purple_url_parse(url, &host, &port, &path, &user, &passwd)) { |
205 | 0 | purple_debug_info("jabber", "Unable to parse given URL.\n"); |
206 | 0 | return NULL; |
207 | 0 | } |
208 | | |
209 | 0 | conn = g_new0(PurpleBOSHConnection, 1); |
210 | 0 | conn->host = host; |
211 | 0 | conn->port = port; |
212 | 0 | conn->path = g_strdup_printf("/%s", path); |
213 | 0 | g_free(path); |
214 | |
|
215 | 0 | if (purple_ip_address_is_valid(host)) |
216 | 0 | js->serverFQDN = g_strdup(js->user->domain); |
217 | 0 | else |
218 | 0 | js->serverFQDN = g_strdup(host); |
219 | |
|
220 | 0 | if ((user && user[0] != '\0') || (passwd && passwd[0] != '\0')) { |
221 | 0 | purple_debug_info("jabber", "Ignoring unexpected username and password " |
222 | 0 | "in BOSH URL.\n"); |
223 | 0 | } |
224 | |
|
225 | 0 | g_free(user); |
226 | 0 | g_free(passwd); |
227 | |
|
228 | 0 | conn->js = js; |
229 | | |
230 | | /* |
231 | | * Random 64-bit integer masked off by 2^52 - 1. |
232 | | * |
233 | | * This should produce a random integer in the range [0, 2^52). It's |
234 | | * unlikely we'll send enough packets in one session to overflow the rid. |
235 | | */ |
236 | 0 | conn->rid = ((guint64)g_random_int() << 32) | g_random_int(); |
237 | 0 | conn->rid &= 0xFFFFFFFFFFFFFLL; |
238 | |
|
239 | 0 | conn->pending = purple_circ_buffer_new(0 /* default grow size */); |
240 | |
|
241 | 0 | conn->state = BOSH_CONN_OFFLINE; |
242 | 0 | if (purple_strcasestr(url, "https://") != NULL) |
243 | 0 | conn->ssl = TRUE; |
244 | 0 | else |
245 | 0 | conn->ssl = FALSE; |
246 | |
|
247 | 0 | conn->connections[0] = jabber_bosh_http_connection_init(conn); |
248 | |
|
249 | 0 | return conn; |
250 | 0 | } |
251 | | |
252 | | void |
253 | | jabber_bosh_connection_destroy(PurpleBOSHConnection *conn) |
254 | 0 | { |
255 | 0 | int i; |
256 | |
|
257 | 0 | g_free(conn->host); |
258 | 0 | g_free(conn->path); |
259 | |
|
260 | 0 | if (conn->send_timer) |
261 | 0 | purple_timeout_remove(conn->send_timer); |
262 | |
|
263 | 0 | purple_circ_buffer_destroy(conn->pending); |
264 | |
|
265 | 0 | for (i = 0; i < NUM_HTTP_CONNECTIONS; ++i) { |
266 | 0 | if (conn->connections[i]) |
267 | 0 | jabber_bosh_http_connection_destroy(conn->connections[i]); |
268 | 0 | } |
269 | |
|
270 | 0 | g_free(conn); |
271 | 0 | } |
272 | | |
273 | | gboolean jabber_bosh_connection_is_ssl(PurpleBOSHConnection *conn) |
274 | 0 | { |
275 | 0 | return conn->ssl; |
276 | 0 | } |
277 | | |
278 | | static PurpleHTTPConnection * |
279 | | find_available_http_connection(PurpleBOSHConnection *conn) |
280 | 0 | { |
281 | 0 | int i; |
282 | |
|
283 | 0 | if (purple_debug_is_verbose()) |
284 | 0 | debug_dump_http_connections(conn); |
285 | | |
286 | | /* First loop, look for a connection that's ready */ |
287 | 0 | for (i = 0; i < NUM_HTTP_CONNECTIONS; ++i) { |
288 | 0 | if (conn->connections[i] && |
289 | 0 | conn->connections[i]->state == HTTP_CONN_CONNECTED && |
290 | 0 | conn->connections[i]->requests == 0) |
291 | 0 | return conn->connections[i]; |
292 | 0 | } |
293 | | |
294 | | /* Second loop, is something currently connecting? If so, just queue up. */ |
295 | 0 | for (i = 0; i < NUM_HTTP_CONNECTIONS; ++i) { |
296 | 0 | if (conn->connections[i] && |
297 | 0 | conn->connections[i]->state == HTTP_CONN_CONNECTING) |
298 | 0 | return NULL; |
299 | 0 | } |
300 | | |
301 | | /* Third loop, is something offline that we can connect? */ |
302 | 0 | for (i = 0; i < NUM_HTTP_CONNECTIONS; ++i) { |
303 | 0 | if (conn->connections[i] && |
304 | 0 | conn->connections[i]->state == HTTP_CONN_OFFLINE) { |
305 | 0 | purple_debug_info("jabber", "bosh: Reconnecting httpconn " |
306 | 0 | "(%i, %p)\n", i, conn->connections[i]); |
307 | 0 | http_connection_connect(conn->connections[i]); |
308 | 0 | return NULL; |
309 | 0 | } |
310 | 0 | } |
311 | | |
312 | | /* Fourth loop, look for one that's NULL and create a new connection */ |
313 | 0 | for (i = 0; i < NUM_HTTP_CONNECTIONS; ++i) { |
314 | 0 | if (!conn->connections[i]) { |
315 | 0 | conn->connections[i] = jabber_bosh_http_connection_init(conn); |
316 | 0 | purple_debug_info("jabber", "bosh: Creating and connecting new httpconn " |
317 | 0 | "(%i, %p)\n", i, conn->connections[i]); |
318 | |
|
319 | 0 | http_connection_connect(conn->connections[i]); |
320 | 0 | return NULL; |
321 | 0 | } |
322 | 0 | } |
323 | | |
324 | 0 | purple_debug_warning("jabber", "Could not find a HTTP connection!\n"); |
325 | | |
326 | | /* None available. */ |
327 | 0 | return NULL; |
328 | 0 | } |
329 | | |
330 | | static void |
331 | | jabber_bosh_connection_send(PurpleBOSHConnection *conn, |
332 | | const PurpleBOSHPacketType type, const char *data) |
333 | 0 | { |
334 | 0 | PurpleHTTPConnection *chosen; |
335 | 0 | GString *packet = NULL; |
336 | |
|
337 | 0 | if (type != PACKET_FLUSH && type != PACKET_TERMINATE) { |
338 | | /* |
339 | | * Unless this is a flush (or session terminate, which needs to be |
340 | | * sent immediately), queue up the data and start a timer to flush |
341 | | * the buffer. |
342 | | */ |
343 | 0 | if (data) |
344 | 0 | purple_circ_buffer_append(conn->pending, data, strlen(data)); |
345 | |
|
346 | 0 | if (purple_debug_is_verbose()) |
347 | 0 | purple_debug_misc("jabber", "bosh: %p has %" G_GSIZE_FORMAT " bytes in " |
348 | 0 | "the buffer.\n", conn, conn->pending->bufused); |
349 | 0 | if (conn->send_timer == 0) |
350 | 0 | conn->send_timer = purple_timeout_add_seconds(BUFFER_SEND_IN_SECS, |
351 | 0 | send_timer_cb, conn); |
352 | 0 | return; |
353 | 0 | } |
354 | | |
355 | 0 | chosen = find_available_http_connection(conn); |
356 | |
|
357 | 0 | if (!chosen) { |
358 | 0 | if (type == PACKET_FLUSH) |
359 | 0 | return; |
360 | | /* |
361 | | * For non-ordinary traffic, we can't 'buffer' it, so use the |
362 | | * first connection. |
363 | | */ |
364 | 0 | chosen = conn->connections[0]; |
365 | |
|
366 | 0 | if (chosen->state != HTTP_CONN_CONNECTED) { |
367 | 0 | purple_debug_warning("jabber", "Unable to find a ready BOSH " |
368 | 0 | "connection. Ignoring send of type 0x%02x.\n", type); |
369 | 0 | return; |
370 | 0 | } |
371 | 0 | } |
372 | | |
373 | | /* We're flushing the send buffer, so remove the send timer */ |
374 | 0 | if (conn->send_timer != 0) { |
375 | 0 | purple_timeout_remove(conn->send_timer); |
376 | 0 | conn->send_timer = 0; |
377 | 0 | } |
378 | |
|
379 | 0 | packet = g_string_new(NULL); |
380 | |
|
381 | 0 | g_string_printf(packet, "<body " |
382 | 0 | "rid='%" G_GUINT64_FORMAT "' " |
383 | 0 | "sid='%s' " |
384 | 0 | "to='%s' " |
385 | 0 | "xml:lang='en' " |
386 | 0 | "xmlns='" NS_BOSH "' " |
387 | 0 | "xmlns:xmpp='" NS_XMPP_BOSH "'", |
388 | 0 | ++conn->rid, |
389 | 0 | conn->sid, |
390 | 0 | conn->js->user->domain); |
391 | |
|
392 | 0 | if (conn->js->reinit) { |
393 | 0 | packet = g_string_append(packet, " xmpp:restart='true'/>"); |
394 | | /* TODO: Do we need to wait for a response? */ |
395 | 0 | conn->js->reinit = FALSE; |
396 | 0 | } else { |
397 | 0 | gsize read_amt; |
398 | 0 | if (type == PACKET_TERMINATE) |
399 | 0 | packet = g_string_append(packet, " type='terminate'"); |
400 | |
|
401 | 0 | packet = g_string_append_c(packet, '>'); |
402 | |
|
403 | 0 | while ((read_amt = purple_circ_buffer_get_max_read(conn->pending)) > 0) { |
404 | 0 | packet = g_string_append_len(packet, conn->pending->outptr, read_amt); |
405 | 0 | purple_circ_buffer_mark_read(conn->pending, read_amt); |
406 | 0 | } |
407 | |
|
408 | 0 | if (data) |
409 | 0 | packet = g_string_append(packet, data); |
410 | 0 | packet = g_string_append(packet, "</body>"); |
411 | 0 | } |
412 | |
|
413 | 0 | http_connection_send_request(chosen, packet); |
414 | 0 | } |
415 | | |
416 | | void jabber_bosh_connection_close(PurpleBOSHConnection *conn) |
417 | 0 | { |
418 | 0 | if (conn->state == BOSH_CONN_ONLINE) |
419 | 0 | jabber_bosh_connection_send(conn, PACKET_TERMINATE, NULL); |
420 | 0 | } |
421 | | |
422 | 0 | static gboolean jabber_bosh_connection_error_check(PurpleBOSHConnection *conn, xmlnode *node) { |
423 | 0 | const char *type; |
424 | |
|
425 | 0 | type = xmlnode_get_attrib(node, "type"); |
426 | |
|
427 | 0 | if (purple_strequal(type, "terminate")) { |
428 | 0 | conn->state = BOSH_CONN_OFFLINE; |
429 | 0 | purple_connection_error_reason(conn->js->gc, |
430 | 0 | PURPLE_CONNECTION_ERROR_OTHER_ERROR, |
431 | 0 | _("The BOSH connection manager terminated your session.")); |
432 | 0 | return TRUE; |
433 | 0 | } |
434 | 0 | return FALSE; |
435 | 0 | } |
436 | | |
437 | | static gboolean |
438 | | send_timer_cb(gpointer data) |
439 | 0 | { |
440 | 0 | PurpleBOSHConnection *bosh; |
441 | |
|
442 | 0 | bosh = data; |
443 | 0 | bosh->send_timer = 0; |
444 | |
|
445 | 0 | jabber_bosh_connection_send(bosh, PACKET_FLUSH, NULL); |
446 | |
|
447 | 0 | return FALSE; |
448 | 0 | } |
449 | | |
450 | | void |
451 | | jabber_bosh_connection_send_keepalive(PurpleBOSHConnection *bosh) |
452 | 0 | { |
453 | 0 | if (bosh->send_timer != 0) |
454 | 0 | purple_timeout_remove(bosh->send_timer); |
455 | | |
456 | | /* clears bosh->send_timer */ |
457 | 0 | send_timer_cb(bosh); |
458 | 0 | } |
459 | | |
460 | 0 | static void jabber_bosh_connection_received(PurpleBOSHConnection *conn, xmlnode *node) { |
461 | 0 | xmlnode *child; |
462 | 0 | JabberStream *js = conn->js; |
463 | |
|
464 | 0 | g_return_if_fail(node != NULL); |
465 | 0 | if (jabber_bosh_connection_error_check(conn, node)) |
466 | 0 | return; |
467 | | |
468 | 0 | child = node->child; |
469 | 0 | while (child != NULL) { |
470 | | /* jabber_process_packet might free child */ |
471 | 0 | xmlnode *next = child->next; |
472 | 0 | if (child->type == XMLNODE_TYPE_TAG) { |
473 | 0 | const char *xmlns = xmlnode_get_namespace(child); |
474 | | /* |
475 | | * Workaround for non-compliant servers that don't stamp |
476 | | * the right xmlns on these packets. See #11315. |
477 | | */ |
478 | 0 | if ((xmlns == NULL /* shouldn't happen, but is equally wrong */ || |
479 | 0 | purple_strequal(xmlns, NS_BOSH)) && |
480 | 0 | (purple_strequal(child->name, "iq") || |
481 | 0 | purple_strequal(child->name, "message") || |
482 | 0 | purple_strequal(child->name, "presence"))) { |
483 | 0 | xmlnode_set_namespace(child, NS_XMPP_CLIENT); |
484 | 0 | } |
485 | 0 | jabber_process_packet(js, &child); |
486 | 0 | } |
487 | |
|
488 | 0 | child = next; |
489 | 0 | } |
490 | 0 | } |
491 | | |
492 | 0 | static void boot_response_cb(PurpleBOSHConnection *conn, xmlnode *node) { |
493 | 0 | JabberStream *js = conn->js; |
494 | 0 | const char *sid, *version; |
495 | 0 | const char *inactivity, *requests; |
496 | 0 | xmlnode *packet; |
497 | |
|
498 | 0 | g_return_if_fail(node != NULL); |
499 | 0 | if (jabber_bosh_connection_error_check(conn, node)) |
500 | 0 | return; |
501 | | |
502 | 0 | sid = xmlnode_get_attrib(node, "sid"); |
503 | 0 | version = xmlnode_get_attrib(node, "ver"); |
504 | |
|
505 | 0 | inactivity = xmlnode_get_attrib(node, "inactivity"); |
506 | 0 | requests = xmlnode_get_attrib(node, "requests"); |
507 | |
|
508 | 0 | if (sid) { |
509 | 0 | conn->sid = g_strdup(sid); |
510 | 0 | } else { |
511 | 0 | purple_connection_error_reason(js->gc, |
512 | 0 | PURPLE_CONNECTION_ERROR_NETWORK_ERROR, |
513 | 0 | _("No session ID given")); |
514 | 0 | return; |
515 | 0 | } |
516 | | |
517 | 0 | if (version) { |
518 | 0 | const char *dot = strchr(version, '.'); |
519 | 0 | int major, minor = 0; |
520 | |
|
521 | 0 | purple_debug_info("jabber", "BOSH connection manager version %s\n", version); |
522 | |
|
523 | 0 | major = atoi(version); |
524 | 0 | if (dot) |
525 | 0 | minor = atoi(dot + 1); |
526 | |
|
527 | 0 | if (major != 1 || minor < 6) { |
528 | 0 | purple_connection_error_reason(js->gc, |
529 | 0 | PURPLE_CONNECTION_ERROR_NETWORK_ERROR, |
530 | 0 | _("Unsupported version of BOSH protocol")); |
531 | 0 | return; |
532 | 0 | } |
533 | 0 | } else { |
534 | 0 | purple_debug_info("jabber", "Missing version in BOSH initiation\n"); |
535 | 0 | } |
536 | | |
537 | 0 | if (inactivity) { |
538 | 0 | js->max_inactivity = atoi(inactivity); |
539 | 0 | if (js->max_inactivity <= 5) { |
540 | 0 | purple_debug_warning("jabber", "Ignoring bogusly small inactivity: %s\n", |
541 | 0 | inactivity); |
542 | | /* Leave it at the default */ |
543 | 0 | } else { |
544 | | /* TODO: Can this check fail? It shouldn't */ |
545 | 0 | js->max_inactivity -= 5; /* rounding */ |
546 | |
|
547 | 0 | if (js->inactivity_timer == 0) { |
548 | 0 | purple_debug_misc("jabber", "Starting BOSH inactivity timer " |
549 | 0 | "for %d secs (compensating for rounding)\n", |
550 | 0 | js->max_inactivity); |
551 | 0 | jabber_stream_restart_inactivity_timer(js); |
552 | 0 | } |
553 | 0 | } |
554 | 0 | } |
555 | |
|
556 | 0 | if (requests) |
557 | 0 | conn->max_requests = atoi(requests); |
558 | |
|
559 | 0 | jabber_stream_set_state(js, JABBER_STREAM_AUTHENTICATING); |
560 | | |
561 | | /* FIXME: Depending on receiving features might break with some hosts */ |
562 | 0 | packet = xmlnode_get_child(node, "features"); |
563 | 0 | conn->state = BOSH_CONN_ONLINE; |
564 | 0 | conn->receive_cb = jabber_bosh_connection_received; |
565 | 0 | jabber_stream_features_parse(js, packet); |
566 | 0 | } |
567 | | |
568 | 0 | static void jabber_bosh_connection_boot(PurpleBOSHConnection *conn) { |
569 | 0 | GString *buf = g_string_new(NULL); |
570 | |
|
571 | 0 | g_string_printf(buf, "<body content='text/xml; charset=utf-8' " |
572 | 0 | "secure='true' " |
573 | 0 | "to='%s' " |
574 | 0 | "xml:lang='en' " |
575 | 0 | "xmpp:version='1.0' " |
576 | 0 | "ver='1.6' " |
577 | 0 | "xmlns:xmpp='" NS_XMPP_BOSH "' " |
578 | 0 | "rid='%" G_GUINT64_FORMAT "' " |
579 | | /* TODO: This should be adjusted/adjustable automatically according to |
580 | | * realtime network behavior */ |
581 | 0 | "wait='60' " |
582 | 0 | "hold='1' " |
583 | 0 | "xmlns='" NS_BOSH "'/>", |
584 | 0 | conn->js->user->domain, |
585 | 0 | ++conn->rid); |
586 | |
|
587 | 0 | purple_debug_misc("jabber", "SendBOSH Boot %s(%" G_GSIZE_FORMAT "): %s\n", |
588 | 0 | conn->ssl ? "(ssl)" : "", buf->len, buf->str); |
589 | 0 | conn->receive_cb = boot_response_cb; |
590 | 0 | http_connection_send_request(conn->connections[0], buf); |
591 | 0 | g_string_free(buf, TRUE); |
592 | 0 | } |
593 | | |
594 | | /** |
595 | | * Handle one complete BOSH response. This is a <body> node containing |
596 | | * any number of XMPP stanzas. |
597 | | */ |
598 | | static void |
599 | | http_received_cb(const char *data, int len, PurpleBOSHConnection *conn) |
600 | 0 | { |
601 | 0 | xmlnode *node; |
602 | 0 | gchar *message; |
603 | |
|
604 | 0 | if (conn->failed_connections) |
605 | | /* We've got some data, so reset the number of failed connections */ |
606 | 0 | conn->failed_connections = 0; |
607 | |
|
608 | 0 | g_return_if_fail(conn->receive_cb); |
609 | | |
610 | 0 | node = xmlnode_from_str(data, len); |
611 | |
|
612 | 0 | message = g_strndup(data, len); |
613 | 0 | purple_debug_info("jabber", "RecvBOSH %s(%d): %s\n", |
614 | 0 | conn->ssl ? "(ssl)" : "", len, message); |
615 | 0 | g_free(message); |
616 | |
|
617 | 0 | if (node) { |
618 | 0 | conn->receive_cb(conn, node); |
619 | 0 | xmlnode_free(node); |
620 | 0 | } else { |
621 | 0 | purple_debug_warning("jabber", "BOSH: Received invalid XML\n"); |
622 | 0 | } |
623 | 0 | } |
624 | | |
625 | | void jabber_bosh_connection_send_raw(PurpleBOSHConnection *conn, |
626 | | const char *data) |
627 | 0 | { |
628 | 0 | jabber_bosh_connection_send(conn, PACKET_NORMAL, data); |
629 | 0 | } |
630 | | |
631 | | static void |
632 | | connection_common_established_cb(PurpleHTTPConnection *conn) |
633 | 0 | { |
634 | 0 | purple_debug_misc("jabber", "bosh: httpconn %p re-connected\n", conn); |
635 | | |
636 | | /* Indicate we're ready and reset some variables */ |
637 | 0 | conn->state = HTTP_CONN_CONNECTED; |
638 | 0 | if (conn->requests != 0) |
639 | 0 | purple_debug_error("jabber", "bosh: httpconn %p has %d requests, != 0\n", |
640 | 0 | conn, conn->requests); |
641 | |
|
642 | 0 | conn->requests = 0; |
643 | 0 | if (conn->read_buf) { |
644 | 0 | g_string_free(conn->read_buf, TRUE); |
645 | 0 | conn->read_buf = NULL; |
646 | 0 | } |
647 | 0 | conn->close = FALSE; |
648 | 0 | conn->headers_done = FALSE; |
649 | 0 | conn->handled_len = conn->body_len = 0; |
650 | |
|
651 | 0 | if (purple_debug_is_verbose()) |
652 | 0 | debug_dump_http_connections(conn->bosh); |
653 | |
|
654 | 0 | if (conn->bosh->js->reinit) |
655 | 0 | jabber_bosh_connection_send(conn->bosh, PACKET_NORMAL, NULL); |
656 | 0 | else if (conn->bosh->state == BOSH_CONN_ONLINE) { |
657 | 0 | purple_debug_info("jabber", "BOSH session already exists. Trying to reuse it.\n"); |
658 | 0 | if (conn->bosh->requests == 0 || conn->bosh->pending->bufused > 0) { |
659 | | /* Send the pending data */ |
660 | 0 | jabber_bosh_connection_send(conn->bosh, PACKET_FLUSH, NULL); |
661 | 0 | } |
662 | 0 | } else |
663 | 0 | jabber_bosh_connection_boot(conn->bosh); |
664 | 0 | } |
665 | | |
666 | | static void http_connection_disconnected(PurpleHTTPConnection *conn) |
667 | 0 | { |
668 | 0 | gboolean had_requests = FALSE; |
669 | | /* |
670 | | * Well, then. Fine! I never liked you anyway, server! I was cheating on you |
671 | | * with AIM! |
672 | | */ |
673 | 0 | conn->state = HTTP_CONN_OFFLINE; |
674 | 0 | if (conn->psc) { |
675 | 0 | purple_ssl_close(conn->psc); |
676 | 0 | conn->psc = NULL; |
677 | 0 | } else if (conn->fd >= 0) { |
678 | 0 | close(conn->fd); |
679 | 0 | conn->fd = -1; |
680 | 0 | } |
681 | |
|
682 | 0 | if (conn->readh) { |
683 | 0 | purple_input_remove(conn->readh); |
684 | 0 | conn->readh = 0; |
685 | 0 | } |
686 | |
|
687 | 0 | if (conn->writeh) { |
688 | 0 | purple_input_remove(conn->writeh); |
689 | 0 | conn->writeh = 0; |
690 | 0 | } |
691 | |
|
692 | 0 | had_requests = (conn->requests > 0); |
693 | 0 | if (had_requests && conn->read_buf->len == 0) { |
694 | 0 | purple_debug_error("jabber", "bosh: Adjusting BOSHconn requests (%d) to %d\n", |
695 | 0 | conn->bosh->requests, conn->bosh->requests - conn->requests); |
696 | 0 | conn->bosh->requests -= conn->requests; |
697 | 0 | conn->requests = 0; |
698 | 0 | } |
699 | |
|
700 | 0 | if (!had_requests) |
701 | | /* If the server disconnected us without any requests, let's |
702 | | * just wait until we have something to send before we reconnect |
703 | | */ |
704 | 0 | return; |
705 | | |
706 | 0 | if (++conn->bosh->failed_connections == MAX_FAILED_CONNECTIONS) { |
707 | 0 | purple_connection_error_reason(conn->bosh->js->gc, |
708 | 0 | PURPLE_CONNECTION_ERROR_NETWORK_ERROR, |
709 | 0 | _("Unable to establish a connection with the server")); |
710 | 0 | } else { |
711 | | /* No! Please! Take me back. It was me, not you! I was weak! */ |
712 | 0 | http_connection_connect(conn); |
713 | 0 | } |
714 | 0 | } |
715 | | |
716 | 0 | void jabber_bosh_connection_connect(PurpleBOSHConnection *bosh) { |
717 | 0 | PurpleHTTPConnection *conn = bosh->connections[0]; |
718 | |
|
719 | 0 | g_return_if_fail(bosh->state == BOSH_CONN_OFFLINE); |
720 | 0 | bosh->state = BOSH_CONN_BOOTING; |
721 | |
|
722 | 0 | http_connection_connect(conn); |
723 | 0 | } |
724 | | |
725 | | /** |
726 | | * @return TRUE if we want to be called again immediately. This happens when |
727 | | * we parse an HTTP response AND there is more data in read_buf. FALSE |
728 | | * if we should not be called again unless more data has been read. |
729 | | */ |
730 | | static gboolean |
731 | | jabber_bosh_http_connection_process(PurpleHTTPConnection *conn) |
732 | 0 | { |
733 | 0 | const char *cursor; |
734 | |
|
735 | 0 | cursor = conn->read_buf->str + conn->handled_len; |
736 | |
|
737 | 0 | if (purple_debug_is_verbose()) |
738 | 0 | purple_debug_misc("jabber", "BOSH server sent: %s\n", cursor); |
739 | | |
740 | | /* TODO: Chunked encoding and check response version :/ */ |
741 | 0 | if (!conn->headers_done) { |
742 | 0 | const char *content_length = purple_strcasestr(cursor, "\r\nContent-Length:"); |
743 | 0 | const char *connection = purple_strcasestr(cursor, "\r\nConnection:"); |
744 | 0 | const char *end_of_headers = strstr(cursor, "\r\n\r\n"); |
745 | | |
746 | | /* Make sure Content-Length is in headers, not body */ |
747 | 0 | if (content_length && (!end_of_headers || content_length < end_of_headers)) { |
748 | 0 | int len; |
749 | |
|
750 | 0 | if (strstr(content_length, "\r\n") == NULL) |
751 | | /* |
752 | | * The packet ends in the middle of the Content-Length line. |
753 | | * We'll try again later when we have more. |
754 | | */ |
755 | 0 | return FALSE; |
756 | | |
757 | 0 | len = atoi(content_length + strlen("\r\nContent-Length:")); |
758 | 0 | if (len == 0) |
759 | 0 | purple_debug_warning("jabber", "Found mangled Content-Length header, or server returned 0-length response.\n"); |
760 | |
|
761 | 0 | conn->body_len = len; |
762 | 0 | } |
763 | | |
764 | 0 | if (connection && (!end_of_headers || connection < end_of_headers)) { |
765 | 0 | const char *tmp; |
766 | 0 | if (strstr(connection, "\r\n") == NULL) |
767 | 0 | return FALSE; |
768 | | |
769 | | |
770 | 0 | tmp = connection + strlen("\r\nConnection:"); |
771 | 0 | while (*tmp && (*tmp == ' ' || *tmp == '\t')) |
772 | 0 | ++tmp; |
773 | |
|
774 | 0 | if (!g_ascii_strncasecmp(tmp, "close", strlen("close"))) { |
775 | 0 | conn->close = TRUE; |
776 | 0 | } |
777 | 0 | } |
778 | | |
779 | 0 | if (end_of_headers) { |
780 | 0 | conn->headers_done = TRUE; |
781 | 0 | conn->handled_len = end_of_headers - conn->read_buf->str + 4; |
782 | 0 | } else { |
783 | 0 | conn->handled_len = conn->read_buf->len; |
784 | 0 | return FALSE; |
785 | 0 | } |
786 | 0 | } |
787 | | |
788 | | /* Have we handled everything in the buffer? */ |
789 | 0 | if (conn->handled_len >= conn->read_buf->len) |
790 | 0 | return FALSE; |
791 | | |
792 | | /* Have we read all that the Content-Length promised us? */ |
793 | 0 | if (conn->read_buf->len - conn->handled_len < conn->body_len) |
794 | 0 | return FALSE; |
795 | | |
796 | 0 | --conn->requests; |
797 | 0 | --conn->bosh->requests; |
798 | |
|
799 | 0 | http_received_cb(conn->read_buf->str + conn->handled_len, conn->body_len, |
800 | 0 | conn->bosh); |
801 | | |
802 | | /* Is there another response in the buffer ? */ |
803 | 0 | if (conn->read_buf->len > conn->body_len + conn->handled_len) { |
804 | 0 | g_string_erase(conn->read_buf, 0, conn->handled_len + conn->body_len); |
805 | 0 | conn->headers_done = FALSE; |
806 | 0 | conn->handled_len = conn->body_len = 0; |
807 | 0 | return TRUE; |
808 | 0 | } |
809 | | |
810 | | /* Connection: Close? */ |
811 | 0 | if (conn->close && conn->state == HTTP_CONN_CONNECTED) { |
812 | 0 | if (purple_debug_is_verbose()) |
813 | 0 | purple_debug_misc("jabber", "bosh (%p), server sent Connection: " |
814 | 0 | "close\n", conn); |
815 | 0 | http_connection_disconnected(conn); |
816 | 0 | } |
817 | |
|
818 | 0 | if (conn->bosh->state == BOSH_CONN_ONLINE && |
819 | 0 | (conn->bosh->requests == 0 || conn->bosh->pending->bufused > 0)) { |
820 | 0 | purple_debug_misc("jabber", "BOSH: Sending an empty request\n"); |
821 | 0 | jabber_bosh_connection_send(conn->bosh, PACKET_NORMAL, NULL); |
822 | 0 | } |
823 | |
|
824 | 0 | g_string_free(conn->read_buf, TRUE); |
825 | 0 | conn->read_buf = NULL; |
826 | 0 | conn->headers_done = FALSE; |
827 | 0 | conn->handled_len = conn->body_len = 0; |
828 | |
|
829 | 0 | return FALSE; |
830 | 0 | } |
831 | | |
832 | | /* |
833 | | * Common code for reading, called from http_connection_read_cb_ssl and |
834 | | * http_connection_read_cb. |
835 | | */ |
836 | | static void |
837 | | http_connection_read(PurpleHTTPConnection *conn) |
838 | 0 | { |
839 | 0 | char buffer[1025]; |
840 | 0 | int cnt; |
841 | |
|
842 | 0 | if (!conn->read_buf) |
843 | 0 | conn->read_buf = g_string_new(NULL); |
844 | |
|
845 | 0 | do { |
846 | 0 | if (conn->psc) |
847 | 0 | cnt = purple_ssl_read(conn->psc, buffer, sizeof(buffer)); |
848 | 0 | else |
849 | 0 | cnt = read(conn->fd, buffer, sizeof(buffer)); |
850 | |
|
851 | 0 | if (cnt > 0) { |
852 | 0 | g_string_append_len(conn->read_buf, buffer, cnt); |
853 | 0 | } |
854 | 0 | } while (cnt > 0); |
855 | |
|
856 | 0 | if (cnt == 0 || (cnt < 0 && errno != EAGAIN)) { |
857 | 0 | if (cnt < 0) |
858 | 0 | purple_debug_info("jabber", "BOSH (%p) read=%d, errno=%d, error=%s\n", |
859 | 0 | conn, cnt, errno, g_strerror(errno)); |
860 | 0 | else |
861 | 0 | purple_debug_info("jabber", "BOSH server closed the connection (%p)\n", |
862 | 0 | conn); |
863 | | |
864 | | /* |
865 | | * If the socket is closed, the processing really needs to know about |
866 | | * it. Handle that now. |
867 | | */ |
868 | 0 | http_connection_disconnected(conn); |
869 | | |
870 | | /* Process what we do have */ |
871 | 0 | } |
872 | |
|
873 | 0 | if (conn->read_buf->len > 0) { |
874 | 0 | while (jabber_bosh_http_connection_process(conn)); |
875 | 0 | } |
876 | 0 | } |
877 | | |
878 | | static void |
879 | | http_connection_read_cb(gpointer data, gint fd, PurpleInputCondition condition) |
880 | 0 | { |
881 | 0 | PurpleHTTPConnection *conn = data; |
882 | |
|
883 | 0 | http_connection_read(conn); |
884 | 0 | } |
885 | | |
886 | | static void |
887 | | http_connection_read_cb_ssl(gpointer data, PurpleSslConnection *psc, |
888 | | PurpleInputCondition cond) |
889 | 0 | { |
890 | 0 | PurpleHTTPConnection *conn = data; |
891 | |
|
892 | 0 | http_connection_read(conn); |
893 | 0 | } |
894 | | |
895 | | static void |
896 | | ssl_connection_established_cb(gpointer data, PurpleSslConnection *psc, |
897 | | PurpleInputCondition cond) |
898 | 0 | { |
899 | 0 | PurpleHTTPConnection *conn = data; |
900 | |
|
901 | 0 | purple_ssl_input_add(psc, http_connection_read_cb_ssl, conn); |
902 | 0 | connection_common_established_cb(conn); |
903 | 0 | } |
904 | | |
905 | | static void |
906 | | ssl_connection_error_cb(PurpleSslConnection *gsc, PurpleSslErrorType error, |
907 | | gpointer data) |
908 | 0 | { |
909 | 0 | PurpleHTTPConnection *conn = data; |
910 | | |
911 | | /* sslconn frees the connection on error */ |
912 | 0 | conn->psc = NULL; |
913 | |
|
914 | 0 | purple_connection_ssl_error(conn->bosh->js->gc, error); |
915 | 0 | } |
916 | | |
917 | | static void |
918 | | connection_established_cb(gpointer data, gint source, const gchar *error) |
919 | 0 | { |
920 | 0 | PurpleHTTPConnection *conn = data; |
921 | 0 | PurpleConnection *gc = conn->bosh->js->gc; |
922 | |
|
923 | 0 | if (source < 0) { |
924 | 0 | gchar *tmp; |
925 | 0 | tmp = g_strdup_printf(_("Unable to establish a connection with the server: %s"), |
926 | 0 | error); |
927 | 0 | purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, tmp); |
928 | 0 | g_free(tmp); |
929 | 0 | return; |
930 | 0 | } |
931 | | |
932 | 0 | conn->fd = source; |
933 | 0 | conn->readh = purple_input_add(conn->fd, PURPLE_INPUT_READ, |
934 | 0 | http_connection_read_cb, conn); |
935 | 0 | connection_common_established_cb(conn); |
936 | 0 | } |
937 | | |
938 | | static void http_connection_connect(PurpleHTTPConnection *conn) |
939 | 0 | { |
940 | 0 | PurpleBOSHConnection *bosh = conn->bosh; |
941 | 0 | PurpleConnection *gc = bosh->js->gc; |
942 | 0 | PurpleAccount *account = purple_connection_get_account(gc); |
943 | |
|
944 | 0 | conn->state = HTTP_CONN_CONNECTING; |
945 | |
|
946 | 0 | if (bosh->ssl) { |
947 | 0 | if (purple_ssl_is_supported()) { |
948 | 0 | conn->psc = purple_ssl_connect(account, bosh->host, bosh->port, |
949 | 0 | ssl_connection_established_cb, |
950 | 0 | ssl_connection_error_cb, |
951 | 0 | conn); |
952 | 0 | if (!conn->psc) { |
953 | 0 | purple_connection_error_reason(gc, |
954 | 0 | PURPLE_CONNECTION_ERROR_NO_SSL_SUPPORT, |
955 | 0 | _("Unable to establish SSL connection")); |
956 | 0 | } |
957 | 0 | } else { |
958 | 0 | purple_connection_error_reason(gc, |
959 | 0 | PURPLE_CONNECTION_ERROR_NO_SSL_SUPPORT, |
960 | 0 | _("SSL support unavailable")); |
961 | 0 | } |
962 | 0 | } else if (purple_proxy_connect(conn, account, bosh->host, bosh->port, |
963 | 0 | connection_established_cb, conn) == NULL) { |
964 | 0 | purple_connection_error_reason(gc, |
965 | 0 | PURPLE_CONNECTION_ERROR_NETWORK_ERROR, |
966 | 0 | _("Unable to connect")); |
967 | 0 | } |
968 | 0 | } |
969 | | |
970 | | static int |
971 | | http_connection_do_send(PurpleHTTPConnection *conn, const char *data, int len) |
972 | 0 | { |
973 | 0 | int ret; |
974 | |
|
975 | 0 | if (conn->psc) |
976 | 0 | ret = purple_ssl_write(conn->psc, data, len); |
977 | 0 | else |
978 | 0 | ret = write(conn->fd, data, len); |
979 | |
|
980 | 0 | if (purple_debug_is_verbose()) |
981 | 0 | purple_debug_misc("jabber", "BOSH (%p): wrote %d bytes\n", conn, ret); |
982 | |
|
983 | 0 | return ret; |
984 | 0 | } |
985 | | |
986 | | static void |
987 | | http_connection_send_cb(gpointer data, gint source, PurpleInputCondition cond) |
988 | 0 | { |
989 | 0 | PurpleHTTPConnection *conn = data; |
990 | 0 | int ret; |
991 | 0 | int writelen = purple_circ_buffer_get_max_read(conn->write_buf); |
992 | |
|
993 | 0 | if (writelen == 0) { |
994 | 0 | purple_input_remove(conn->writeh); |
995 | 0 | conn->writeh = 0; |
996 | 0 | return; |
997 | 0 | } |
998 | | |
999 | 0 | ret = http_connection_do_send(conn, conn->write_buf->outptr, writelen); |
1000 | |
|
1001 | 0 | if (ret < 0 && errno == EAGAIN) |
1002 | 0 | return; |
1003 | 0 | else if (ret <= 0) { |
1004 | | /* |
1005 | | * TODO: Handle this better. Probably requires a PurpleBOSHConnection |
1006 | | * buffer that stores what is "being sent" until the |
1007 | | * PurpleHTTPConnection reports it is fully sent. |
1008 | | */ |
1009 | 0 | gchar *tmp = g_strdup_printf(_("Lost connection with server: %s"), |
1010 | 0 | g_strerror(errno)); |
1011 | 0 | purple_connection_error_reason(conn->bosh->js->gc, |
1012 | 0 | PURPLE_CONNECTION_ERROR_NETWORK_ERROR, |
1013 | 0 | tmp); |
1014 | 0 | g_free(tmp); |
1015 | 0 | return; |
1016 | 0 | } |
1017 | | |
1018 | 0 | purple_circ_buffer_mark_read(conn->write_buf, ret); |
1019 | 0 | } |
1020 | | |
1021 | | static void |
1022 | | http_connection_send_request(PurpleHTTPConnection *conn, const GString *req) |
1023 | 0 | { |
1024 | 0 | char *data; |
1025 | 0 | int ret; |
1026 | 0 | size_t len; |
1027 | | |
1028 | | /* Sending something to the server, restart the inactivity timer */ |
1029 | 0 | jabber_stream_restart_inactivity_timer(conn->bosh->js); |
1030 | |
|
1031 | 0 | data = g_strdup_printf("POST %s HTTP/1.1\r\n" |
1032 | 0 | "Host: %s\r\n" |
1033 | 0 | "User-Agent: %s\r\n" |
1034 | 0 | "Content-Encoding: text/xml; charset=utf-8\r\n" |
1035 | 0 | "Content-Length: %" G_GSIZE_FORMAT "\r\n\r\n" |
1036 | 0 | "%s", |
1037 | 0 | conn->bosh->path, conn->bosh->host, bosh_useragent, |
1038 | 0 | req->len, req->str); |
1039 | |
|
1040 | 0 | len = strlen(data); |
1041 | |
|
1042 | 0 | ++conn->requests; |
1043 | 0 | ++conn->bosh->requests; |
1044 | |
|
1045 | 0 | if (purple_debug_is_unsafe() && purple_debug_is_verbose()) |
1046 | | /* Will contain passwords for SASL PLAIN and is verbose */ |
1047 | 0 | purple_debug_misc("jabber", "BOSH (%p): Sending %s\n", conn, data); |
1048 | 0 | else if (purple_debug_is_verbose()) |
1049 | 0 | purple_debug_misc("jabber", "BOSH (%p): Sending request of " |
1050 | 0 | "%" G_GSIZE_FORMAT " bytes.\n", conn, len); |
1051 | |
|
1052 | 0 | if (conn->writeh == 0) |
1053 | 0 | ret = http_connection_do_send(conn, data, len); |
1054 | 0 | else { |
1055 | 0 | ret = -1; |
1056 | 0 | errno = EAGAIN; |
1057 | 0 | } |
1058 | |
|
1059 | 0 | if (ret < 0 && errno != EAGAIN) { |
1060 | | /* |
1061 | | * TODO: Handle this better. Probably requires a PurpleBOSHConnection |
1062 | | * buffer that stores what is "being sent" until the |
1063 | | * PurpleHTTPConnection reports it is fully sent. |
1064 | | */ |
1065 | 0 | gchar *tmp = g_strdup_printf(_("Lost connection with server: %s"), |
1066 | 0 | g_strerror(errno)); |
1067 | 0 | purple_connection_error_reason(conn->bosh->js->gc, |
1068 | 0 | PURPLE_CONNECTION_ERROR_NETWORK_ERROR, |
1069 | 0 | tmp); |
1070 | 0 | g_free(tmp); |
1071 | 0 | return; |
1072 | 0 | } else if ((size_t)ret < len) { |
1073 | 0 | if (ret < 0) |
1074 | 0 | ret = 0; |
1075 | 0 | if (conn->writeh == 0) |
1076 | 0 | conn->writeh = purple_input_add(conn->psc ? conn->psc->fd : conn->fd, |
1077 | 0 | PURPLE_INPUT_WRITE, http_connection_send_cb, conn); |
1078 | 0 | purple_circ_buffer_append(conn->write_buf, data + ret, len - ret); |
1079 | 0 | } |
1080 | 0 | } |
1081 | | |