Mercurial > pidgin
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; |