comparison libpurple/protocols/jabber/bosh.c @ 25193:e7f20c859519

Jabber BOSH: Model the parsing on the SOAP stuff This is way less strict; we only parse the headers so we can read Content-Length. There are still a few problems (this is mostly untested since my BOSH daemon doesn't xmlns:ga) like crashes and some corner cases.
author Paul Aurich <paul@darkrain42.org>
date Sun, 18 Jan 2009 04:02:31 +0000
parents 592c2cf00fea
children b78c8ab5de2b
comparison
equal deleted inserted replaced
25192:592c2cf00fea 25193:e7f20c859519
67 int port; 67 int port;
68 int ie_handle; 68 int ie_handle;
69 GQueue *requests; /* Queue of PurpleHTTPRequestCallbacks */ 69 GQueue *requests; /* Queue of PurpleHTTPRequestCallbacks */
70 70
71 PurpleHTTPResponse *current_response; 71 PurpleHTTPResponse *current_response;
72 char *current_data; 72 GString *buf;
73 int current_len; 73 gboolean headers_done;
74 74 gsize handled_len;
75 gsize body_len;
76
75 int pih; /* what? */ 77 int pih; /* what? */
76 PurpleHTTPConnectionConnectFunction connect_cb; 78 PurpleHTTPConnectionConnectFunction connect_cb;
77 PurpleHTTPConnectionConnectFunction disconnect_cb; 79 PurpleHTTPConnectionConnectFunction disconnect_cb;
78 void *userdata; 80 void *userdata;
79 }; 81 };
85 int data_len; 87 int data_len;
86 void *userdata; 88 void *userdata;
87 }; 89 };
88 90
89 struct _PurpleHTTPResponse { 91 struct _PurpleHTTPResponse {
90 int status;
91 GHashTable *header;
92 char *data; 92 char *data;
93 int data_len; 93 int data_len;
94 }; 94 };
95 95
96 static void jabber_bosh_connection_stream_restart(PurpleBOSHConnection *conn); 96 static void jabber_bosh_connection_stream_restart(PurpleBOSHConnection *conn);
127 { 127 {
128 g_free(req->data); 128 g_free(req->data);
129 g_free(req); 129 g_free(req);
130 } 130 }
131 131
132 static PurpleHTTPResponse*
133 jabber_bosh_http_response_init(void)
134 {
135 PurpleHTTPResponse *res = g_new0(PurpleHTTPResponse, 1);
136 res->header = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
137 return res;
138 }
139
140 static void 132 static void
141 jabber_bosh_http_response_destroy(PurpleHTTPResponse *res) 133 jabber_bosh_http_response_destroy(PurpleHTTPResponse *res)
142 { 134 {
143 g_hash_table_destroy(res->header);
144 g_free(res->data); 135 g_free(res->data);
145 g_free(res); 136 g_free(res);
146 } 137 }
147 138
148 static PurpleHTTPConnection* 139 static PurpleHTTPConnection*
158 } 149 }
159 150
160 static void 151 static void
161 jabber_bosh_http_connection_destroy(PurpleHTTPConnection *conn) 152 jabber_bosh_http_connection_destroy(PurpleHTTPConnection *conn)
162 { 153 {
163 g_free(conn->current_data);
164 g_free(conn->host); 154 g_free(conn->host);
155
156 if (conn->buf)
157 g_string_free(conn->buf, TRUE);
165 158
166 if (conn->requests) 159 if (conn->requests)
167 g_queue_free(conn->requests); 160 g_queue_free(conn->requests);
168 161
169 if (conn->current_response) 162 if (conn->current_response)
480 void jabber_bosh_connection_connect(PurpleBOSHConnection *conn) { 473 void jabber_bosh_connection_connect(PurpleBOSHConnection *conn) {
481 conn->conn_a->connect_cb = jabber_bosh_connection_connected; 474 conn->conn_a->connect_cb = jabber_bosh_connection_connected;
482 jabber_bosh_http_connection_connect(conn->conn_a); 475 jabber_bosh_http_connection_connect(conn->conn_a);
483 } 476 }
484 477
485 static void jabber_bosh_http_connection_receive_parse_header(PurpleHTTPResponse *response, char **data, int *len) { 478 static void
486 GHashTable *header = response->header; 479 jabber_bosh_http_connection_process(PurpleHTTPConnection *conn)
487 char *beginning = *data; 480 {
488 char *found = g_strstr_len(*data, *len, "\r\n\r\n"); 481 PurpleBOSHConnection *bosh_conn = conn->userdata;
489 char *field = NULL; 482 PurpleHTTPRequestCallback cb;
490 char *value = NULL; 483 const char *cursor;
491 char *old_data = *data; 484
485 if (!conn->current_response)
486 conn->current_response = g_new0(PurpleHTTPResponse, 1);
487
488 cursor = conn->buf->str + conn->handled_len;
489
490 if (!conn->headers_done) {
491 const char *content_length = purple_strcasestr(cursor, "\r\nContent-Length");
492 const char *end_of_headers = purple_strcasestr(cursor, "\r\n\r\n");
493
494 /* Make sure Content-Length is in headers, not body */
495 if (content_length && content_length < end_of_headers) {
496 char *sep = strstr(content_length, ": ");
497 int len = atoi(sep + 2);
498 if (len == 0)
499 purple_debug_warning("jabber", "Found mangled Content-Length header.\n");
500
501 conn->body_len = len;
502 }
503
504 if (end_of_headers) {
505 conn->headers_done = TRUE;
506 conn->handled_len = end_of_headers - conn->buf->str + 4;
507 cursor = end_of_headers + 4;
508 } else {
509 conn->handled_len = conn->buf->len;
510 return;
511 }
512 }
513
514 /* Have we handled everything in the buffer? */
515 if (conn->handled_len >= conn->buf->len)
516 return;
517
518 /* Have we read all that the Content-Length promised us? */
519 if (conn->buf->len - conn->handled_len < conn->body_len)
520 return;
521
522 cb = g_queue_pop_head(conn->requests);
523
524 #warning For a pure HTTP 1.1 stack, this would need to be handled elsewhere.
525 if (bosh_conn->ready && g_queue_is_empty(conn->requests)) {
526 jabber_bosh_connection_send(bosh_conn, NULL);
527 printf("\n SEND AN EMPTY REQUEST \n");
528 }
529
530 if (cb) {
531 conn->current_response->data_len = conn->body_len;
532 conn->current_response->data = g_memdup(conn->buf->str + conn->handled_len, conn->body_len);
533
534 cb(conn->current_response, conn->userdata);
535 } else {
536 purple_debug_warning("jabber", "Received HTTP response before POST\n");
537 }
538
539 g_string_free(conn->buf, TRUE);
540 conn->buf = NULL;
541 jabber_bosh_http_response_destroy(conn->current_response);
542 conn->current_response = NULL;
543 }
544
545 static void
546 jabber_bosh_http_connection_read(gpointer data, gint fd,
547 PurpleInputCondition condition)
548 {
549 PurpleHTTPConnection *conn = data;
550 char buffer[1025];
551 int perrno;
552 int cnt, count = 0;
553
554 purple_debug_info("jabber", "jabber_bosh_http_connection_read\n");
555
556 if (conn->buf == NULL)
557 conn->buf = g_string_new("");
558
559 while ((cnt = read(fd, buffer, sizeof(buffer))) > 0) {
560 purple_debug_info("jabber", "bosh read %d bytes\n", cnt);
561 count += cnt;
562 g_string_append_len(conn->buf, buffer, cnt);
563 }
564
565 perrno = errno;
566 if (cnt == 0 && count) {
567 /* TODO: process should know this response ended with a closed socket
568 * and throw an error if it's not a complete response. */
569 jabber_bosh_http_connection_process(conn);
570 }
571
572 if (cnt == 0 || (cnt < 0 && perrno != EAGAIN)) {
573 if (cnt < 0)
574 purple_debug_info("jabber", "bosh read: %d\n", cnt);
575 else
576 purple_debug_info("jabber", "bosh socket closed\n");
492 577
493 while (*beginning != 'H') ++beginning; 578 purple_input_remove(conn->ie_handle);
494 beginning[12] = 0; 579 conn->ie_handle = 0;
495 response->status = atoi(&beginning[9]); 580
496 beginning = &beginning[13];
497 do {
498 ++beginning;
499 } while (*beginning != '\n');
500 ++beginning;
501 /* parse HTTP response header */
502 for (;beginning != found; ++beginning) {
503 if (!field) field = beginning;
504 if (*beginning == ':') {
505 *beginning = 0;
506 value = beginning + 1;
507 } else if (*beginning == '\r') {
508 *beginning = 0;
509 g_hash_table_replace(header, g_ascii_strdown(field, -1), g_strdup(value));
510 value = field = 0;
511 ++beginning;
512 }
513 }
514 ++beginning; ++beginning; ++beginning; ++beginning;
515 /* remove the header from data buffer */
516 *data = g_strdup(beginning);
517 *len = *len - (beginning - old_data);
518 g_free(old_data);
519 }
520
521 static void jabber_bosh_http_connection_receive(gpointer data, gint source, PurpleInputCondition condition) {
522 char buffer[1025];
523 int len;
524 PurpleHTTPConnection *conn = data;
525 PurpleBOSHConnection *bosh_conn = conn->userdata;
526 PurpleHTTPResponse *response = conn->current_response;
527
528 purple_debug_info("jabber", "jabber_bosh_http_connection_receive\n");
529
530 len = read(source, buffer, 1024);
531 if (len > 0) {
532 buffer[len] = 0;
533 if (conn->current_data == NULL) {
534 conn->current_len = len;
535 conn->current_data = g_strdup_printf("%s", buffer);
536 } else {
537 char *old_data = conn->current_data;
538 conn->current_len += len;
539 conn->current_data = g_strdup_printf("%s%s", conn->current_data, buffer);
540 g_free(old_data);
541 }
542
543 if (!response) {
544 /* check for header footer */
545 char *found = g_strstr_len(conn->current_data, conn->current_len, "\r\n\r\n");
546 if (found) {
547 /* New response */
548 response = conn->current_response = jabber_bosh_http_response_init();
549 jabber_bosh_http_connection_receive_parse_header(response, &(conn->current_data), &(conn->current_len));
550 /* XXX: Crash if there is no Content-Length header */
551 response->data_len = atoi(g_hash_table_lookup(response->header, "content-length"));
552 } else {
553 printf("\nDid not receive HTTP header\n");
554 }
555 }
556
557 if (response) {
558 if (conn->current_len >= response->data_len) {
559 PurpleHTTPRequestCallback cb = g_queue_pop_head(conn->requests);
560
561 #warning For a pure HTTP 1.1 stack, this would need to be handled elsewhere.
562 if (bosh_conn->ready == TRUE && g_queue_is_empty(conn->requests) == TRUE) {
563 jabber_bosh_connection_send(bosh_conn, NULL);
564 printf("\n SEND AN EMPTY REQUEST \n");
565 }
566
567 if (cb) {
568 char *old_data = conn->current_data;
569 response->data = g_memdup(conn->current_data, response->data_len);
570 conn->current_data = g_strdup(&conn->current_data[response->data_len]);
571 conn->current_len -= response->data_len;
572 g_free(old_data);
573
574 cb(response, conn->userdata);
575 jabber_bosh_http_response_destroy(response);
576 conn->current_response = NULL;
577 } else {
578 purple_debug_info("jabber", "received HTTP response but haven't requested anything yet.\n");
579 }
580 }
581 }
582 } else if (len == 0) {
583 if (conn->ie_handle) {
584 purple_input_remove(conn->ie_handle);
585 conn->ie_handle = 0;
586 }
587 if (conn->disconnect_cb) 581 if (conn->disconnect_cb)
588 conn->disconnect_cb(conn); 582 conn->disconnect_cb(conn);
589 } else { 583
590 purple_debug_info("jabber", "jabber_bosh_http_connection_receive: problem receiving data (%d)\n", len); 584 return;
591 } 585 }
586
587 jabber_bosh_http_connection_process(conn);
592 } 588 }
593 589
594 static void jabber_bosh_http_connection_callback(gpointer data, gint source, const gchar *error) 590 static void jabber_bosh_http_connection_callback(gpointer data, gint source, const gchar *error)
595 { 591 {
596 PurpleHTTPConnection *conn = data; 592 PurpleHTTPConnection *conn = data;
610 606
611 if (conn->connect_cb) 607 if (conn->connect_cb)
612 conn->connect_cb(conn); 608 conn->connect_cb(conn);
613 609
614 conn->ie_handle = purple_input_add(conn->fd, PURPLE_INPUT_READ, 610 conn->ie_handle = purple_input_add(conn->fd, PURPLE_INPUT_READ,
615 jabber_bosh_http_connection_receive, conn); 611 jabber_bosh_http_connection_read, conn);
616 } 612 }
617 613
618 static void jabber_bosh_http_connection_connect(PurpleHTTPConnection *conn) 614 static void jabber_bosh_http_connection_connect(PurpleHTTPConnection *conn)
619 { 615 {
620 PurpleBOSHConnection *bosh_conn = conn->userdata; 616 PurpleBOSHConnection *bosh_conn = conn->userdata;