source: lib/http_client.c @ 129e282

Last change on this file since 129e282 was 0db6618, checked in by dequis <dx@…>, at 2015-10-26T08:28:10Z

Use proxy_disconnect() in http, ssl, jabber, oscar

Twitter and MSN are all HTTP/SSL, so they don't need it either.

The out of tree facebook and steam plugins are also covered by the
HTTP/SSL changes.

Yahoo is written in a weird way and doesn't seem to need it (it seems it
doesn't immediately stop connections when you tell it to logout)

  • Property mode set to 100644
File size: 17.5 KB
Line 
1/********************************************************************\
2  * BitlBee -- An IRC to other IM-networks gateway                     *
3  *                                                                    *
4  * Copyright 2002-2013 Wilmer van der Gaast and others                *
5  \********************************************************************/
6
7/* HTTP(S) module                                                       */
8
9/*
10  This program is free software; you can redistribute it and/or modify
11  it under the terms of the GNU General Public License as published by
12  the Free Software Foundation; either version 2 of the License, or
13  (at your option) any later version.
14
15  This program is distributed in the hope that it will be useful,
16  but WITHOUT ANY WARRANTY; without even the implied warranty of
17  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  GNU General Public License for more details.
19
20  You should have received a copy of the GNU General Public License with
21  the Debian GNU/Linux distribution in /usr/share/common-licenses/GPL;
22  if not, write to the Free Software Foundation, Inc., 51 Franklin St.,
23  Fifth Floor, Boston, MA  02110-1301  USA
24*/
25
26#include <string.h>
27#include <stdio.h>
28
29#include "http_client.h"
30#include "url.h"
31#include "sock.h"
32
33
34static gboolean http_connected(gpointer data, int source, b_input_condition cond);
35static gboolean http_ssl_connected(gpointer data, int returncode, void *source, b_input_condition cond);
36static gboolean http_incoming_data(gpointer data, int source, b_input_condition cond);
37static void http_free(struct http_request *req);
38
39
40struct http_request *http_dorequest(char *host, int port, int ssl, char *request, http_input_function func,
41                                    gpointer data)
42{
43        struct http_request *req;
44        int error = 0;
45
46        req = g_new0(struct http_request, 1);
47
48        if (ssl) {
49                req->ssl = ssl_connect(host, port, TRUE, http_ssl_connected, req);
50                if (req->ssl == NULL) {
51                        error = 1;
52                }
53        } else {
54                req->fd = proxy_connect(host, port, http_connected, req);
55                if (req->fd < 0) {
56                        error = 1;
57                }
58        }
59
60        if (error) {
61                http_free(req);
62                return NULL;
63        }
64
65        req->func = func;
66        req->data = data;
67        req->request = g_strdup(request);
68        req->request_length = strlen(request);
69        req->redir_ttl = 3;
70        req->content_length = -1;
71
72        if (getenv("BITLBEE_DEBUG")) {
73                printf("About to send HTTP request:\n%s\n", req->request);
74        }
75
76        return req;
77}
78
79struct http_request *http_dorequest_url(char *url_string, http_input_function func, gpointer data)
80{
81        url_t *url = g_new0(url_t, 1);
82        char *request;
83        void *ret;
84
85        if (!url_set(url, url_string)) {
86                g_free(url);
87                return NULL;
88        }
89
90        if (url->proto != PROTO_HTTP && url->proto != PROTO_HTTPS) {
91                g_free(url);
92                return NULL;
93        }
94
95        request = g_strdup_printf("GET %s HTTP/1.0\r\n"
96                                  "Host: %s\r\n"
97                                  "User-Agent: BitlBee " BITLBEE_VERSION " " ARCH "/" CPU "\r\n"
98                                  "\r\n", url->file, url->host);
99
100        ret = http_dorequest(url->host, url->port,
101                             url->proto == PROTO_HTTPS, request, func, data);
102
103        g_free(url);
104        g_free(request);
105        return ret;
106}
107
108/* This one is actually pretty simple... Might get more calls if we can't write
109   the whole request at once. */
110static gboolean http_connected(gpointer data, int source, b_input_condition cond)
111{
112        struct http_request *req = data;
113        int st;
114
115        if (source < 0) {
116                goto error;
117        }
118
119        if (req->inpa > 0) {
120                b_event_remove(req->inpa);
121        }
122
123        sock_make_nonblocking(req->fd);
124
125        if (req->ssl) {
126                st = ssl_write(req->ssl, req->request + req->bytes_written,
127                               req->request_length - req->bytes_written);
128                if (st < 0) {
129                        if (ssl_errno != SSL_AGAIN) {
130                                ssl_disconnect(req->ssl);
131                                goto error;
132                        }
133                }
134        } else {
135                st = write(source, req->request + req->bytes_written,
136                           req->request_length - req->bytes_written);
137                if (st < 0) {
138                        if (!sockerr_again()) {
139                                closesocket(req->fd);
140                                goto error;
141                        }
142                }
143        }
144
145        if (st > 0) {
146                req->bytes_written += st;
147        }
148
149        if (req->bytes_written < req->request_length) {
150                req->inpa = b_input_add(source,
151                                        req->ssl ? ssl_getdirection(req->ssl) : B_EV_IO_WRITE,
152                                        http_connected, req);
153        } else {
154                req->inpa = b_input_add(source, B_EV_IO_READ, http_incoming_data, req);
155        }
156
157        return FALSE;
158
159error:
160        if (req->status_string == NULL) {
161                req->status_string = g_strdup("Error while writing HTTP request");
162        }
163
164        if (req->func != NULL) {
165                req->func(req);
166        }
167        http_free(req);
168        return FALSE;
169}
170
171static gboolean http_ssl_connected(gpointer data, int returncode, void *source, b_input_condition cond)
172{
173        struct http_request *req = data;
174
175        if (source == NULL) {
176                if (returncode != 0) {
177                        char *err = ssl_verify_strerror(returncode);
178                        req->status_string = g_strdup_printf(
179                                "Certificate verification problem 0x%x: %s",
180                                returncode, err ? err : "Unknown");
181                        g_free(err);
182                }
183                return http_connected(data, -1, cond);
184        }
185
186        req->fd = ssl_getfd(source);
187
188        return http_connected(data, req->fd, cond);
189}
190
191typedef enum {
192        CR_OK,
193        CR_EOF,
194        CR_ERROR,
195        CR_ABORT,
196} http_ret_t;
197
198static gboolean http_handle_headers(struct http_request *req);
199static http_ret_t http_process_chunked_data(struct http_request *req, const char *buffer, int len);
200static http_ret_t http_process_data(struct http_request *req, const char *buffer, int len);
201
202static gboolean http_incoming_data(gpointer data, int source, b_input_condition cond)
203{
204        struct http_request *req = data;
205        char buffer[4096];
206        int st;
207
208        if (req->inpa > 0) {
209                b_event_remove(req->inpa);
210                req->inpa = 0;
211        }
212
213        if (req->ssl) {
214                st = ssl_read(req->ssl, buffer, sizeof(buffer));
215                if (st < 0) {
216                        if (ssl_errno != SSL_AGAIN) {
217                                /* goto cleanup; */
218
219                                /* YAY! We have to deal with crappy Microsoft
220                                   servers that LOVE to send invalid TLS
221                                   packets that abort connections! \o/ */
222
223                                goto eof;
224                        }
225                } else if (st == 0) {
226                        goto eof;
227                }
228        } else {
229                st = read(req->fd, buffer, sizeof(buffer));
230                if (st < 0) {
231                        if (!sockerr_again()) {
232                                req->status_string = g_strdup(strerror(errno));
233                                goto cleanup;
234                        }
235                } else if (st == 0) {
236                        goto eof;
237                }
238        }
239
240        if (st > 0) {
241                http_ret_t c;
242
243                if (req->flags & HTTPC_CHUNKED) {
244                        c = http_process_chunked_data(req, buffer, st);
245                } else {
246                        c = http_process_data(req, buffer, st);
247                }
248
249                if (c == CR_EOF) {
250                        goto eof;
251                } else if (c == CR_ERROR || c == CR_ABORT) {
252                        return FALSE;
253                }
254        }
255
256        if (req->content_length != -1 &&
257            req->body_size >= req->content_length) {
258                goto eof;
259        }
260
261        if (ssl_pending(req->ssl)) {
262                return http_incoming_data(data, source, cond);
263        }
264
265        /* There will be more! */
266        req->inpa = b_input_add(req->fd,
267                                req->ssl ? ssl_getdirection(req->ssl) : B_EV_IO_READ,
268                                http_incoming_data, req);
269
270        return FALSE;
271
272eof:
273        req->flags |= HTTPC_EOF;
274
275        /* Maybe if the webserver is overloaded, or when there's bad SSL
276           support... */
277        if (req->bytes_read == 0) {
278                req->status_string = g_strdup("Empty HTTP reply");
279                goto cleanup;
280        }
281
282cleanup:
283        /* Avoid g_source_remove warnings */
284        req->inpa = 0;
285
286        if (req->ssl) {
287                ssl_disconnect(req->ssl);
288        } else {
289                closesocket(req->fd);
290        }
291
292        if (req->body_size < req->content_length) {
293                req->status_code = -1;
294                g_free(req->status_string);
295                req->status_string = g_strdup("Response truncated");
296        }
297
298        if (getenv("BITLBEE_DEBUG") && req) {
299                printf("Finishing HTTP request with status: %s\n",
300                       req->status_string ? req->status_string : "NULL");
301        }
302
303        if (req->func != NULL) {
304                req->func(req);
305        }
306        http_free(req);
307        return FALSE;
308}
309
310static http_ret_t http_process_chunked_data(struct http_request *req, const char *buffer, int len)
311{
312        char *chunk, *eos, *s;
313
314        if (len < 0) {
315                return TRUE;
316        }
317
318        if (len > 0) {
319                req->cbuf = g_realloc(req->cbuf, req->cblen + len + 1);
320                memcpy(req->cbuf + req->cblen, buffer, len);
321                req->cblen += len;
322                req->cbuf[req->cblen] = '\0';
323        }
324
325        /* Turns out writing a proper chunked-encoding state machine is not
326           that simple. :-( I've tested this one feeding it byte by byte so
327           I hope it's solid now. */
328        chunk = req->cbuf;
329        eos = req->cbuf + req->cblen;
330        while (TRUE) {
331                int clen = 0;
332
333                /* Might be a \r\n from the last chunk. */
334                s = chunk;
335                while (g_ascii_isspace(*s)) {
336                        s++;
337                }
338                /* Chunk length. Might be incomplete. */
339                if (s < eos && sscanf(s, "%x", &clen) != 1) {
340                        return CR_ERROR;
341                }
342                while (g_ascii_isxdigit(*s)) {
343                        s++;
344                }
345
346                /* If we read anything here, it *must* be \r\n. */
347                if (strncmp(s, "\r\n", MIN(2, eos - s)) != 0) {
348                        return CR_ERROR;
349                }
350                s += 2;
351
352                if (s >= eos) {
353                        break;
354                }
355
356                /* 0-length chunk means end of response. */
357                if (clen == 0) {
358                        return CR_EOF;
359                }
360
361                /* Wait for the whole chunk to arrive. */
362                if (s + clen > eos) {
363                        break;
364                }
365                if (http_process_data(req, s, clen) != CR_OK) {
366                        return CR_ABORT;
367                }
368
369                chunk = s + clen;
370        }
371
372        if (chunk != req->cbuf) {
373                req->cblen = eos - chunk;
374                s = g_memdup(chunk, req->cblen + 1);
375                g_free(req->cbuf);
376                req->cbuf = s;
377        }
378
379        return CR_OK;
380}
381
382static http_ret_t http_process_data(struct http_request *req, const char *buffer, int len)
383{
384        if (len <= 0) {
385                return CR_OK;
386        }
387
388        if (!req->reply_body) {
389                req->reply_headers = g_realloc(req->reply_headers, req->bytes_read + len + 1);
390                memcpy(req->reply_headers + req->bytes_read, buffer, len);
391                req->bytes_read += len;
392                req->reply_headers[req->bytes_read] = '\0';
393
394                if (strstr(req->reply_headers, "\r\n\r\n") ||
395                    strstr(req->reply_headers, "\n\n")) {
396                        /* We've now received all headers. Look for something
397                           interesting. */
398                        if (!http_handle_headers(req)) {
399                                return CR_ABORT;
400                        }
401
402                        /* Start parsing the body as chunked if required. */
403                        if (req->flags & HTTPC_CHUNKED) {
404                                return http_process_chunked_data(req, NULL, 0);
405                        }
406                }
407        } else {
408                int pos = req->reply_body - req->sbuf;
409                req->sbuf = g_realloc(req->sbuf, req->sblen + len + 1);
410                memcpy(req->sbuf + req->sblen, buffer, len);
411                req->bytes_read += len;
412                req->sblen += len;
413                req->sbuf[req->sblen] = '\0';
414                req->reply_body = req->sbuf + pos;
415                req->body_size = req->sblen - pos;
416        }
417
418        if ((req->flags & HTTPC_STREAMING) && req->reply_body && req->func != NULL) {
419                req->func(req);
420        }
421
422        return CR_OK;
423}
424
425/* Splits headers and body. Checks result code, in case of 300s it'll handle
426   redirects. If this returns FALSE, don't call any callbacks! */
427static gboolean http_handle_headers(struct http_request *req)
428{
429        char *end1, *end2, *s;
430        int evil_server = 0;
431
432        /* Zero termination is very convenient. */
433        req->reply_headers[req->bytes_read] = '\0';
434
435        /* Find the separation between headers and body, and keep stupid
436           webservers in mind. */
437        end1 = strstr(req->reply_headers, "\r\n\r\n");
438        end2 = strstr(req->reply_headers, "\n\n");
439
440        if (end2 && end2 < end1) {
441                end1 = end2 + 1;
442                evil_server = 1;
443        } else if (end1) {
444                end1 += 2;
445        } else {
446                req->status_string = g_strdup("Malformed HTTP reply");
447                return TRUE;
448        }
449
450        *end1 = '\0';
451
452        if (getenv("BITLBEE_DEBUG")) {
453                printf("HTTP response headers:\n%s\n", req->reply_headers);
454        }
455
456        if (evil_server) {
457                req->reply_body = end1 + 1;
458        } else {
459                req->reply_body = end1 + 2;
460        }
461
462        /* Separately allocated space for headers and body. */
463        req->sblen = req->body_size = req->reply_headers + req->bytes_read - req->reply_body;
464        req->sbuf = req->reply_body = g_memdup(req->reply_body, req->body_size + 1);
465        req->reply_headers = g_realloc(req->reply_headers, end1 - req->reply_headers + 1);
466
467        if ((end1 = strchr(req->reply_headers, ' ')) != NULL) {
468                if (sscanf(end1 + 1, "%hd", &req->status_code) != 1) {
469                        req->status_string = g_strdup("Can't parse status code");
470                        req->status_code = -1;
471                } else {
472                        char *eol;
473
474                        if (evil_server) {
475                                eol = strchr(end1, '\n');
476                        } else {
477                                eol = strchr(end1, '\r');
478                        }
479
480                        req->status_string = g_strndup(end1 + 1, eol - end1 - 1);
481
482                        /* Just to be sure... */
483                        if ((eol = strchr(req->status_string, '\r'))) {
484                                *eol = 0;
485                        }
486                        if ((eol = strchr(req->status_string, '\n'))) {
487                                *eol = 0;
488                        }
489                }
490        } else {
491                req->status_string = g_strdup("Can't locate status code");
492                req->status_code = -1;
493        }
494
495        if (((req->status_code >= 301 && req->status_code <= 303) ||
496             req->status_code == 307) && req->redir_ttl-- > 0) {
497                char *loc, *new_request, *new_host;
498                int error = 0, new_port, new_proto;
499
500                /* We might fill it again, so let's not leak any memory. */
501                g_free(req->status_string);
502                req->status_string = NULL;
503
504                loc = strstr(req->reply_headers, "\nLocation: ");
505                if (loc == NULL) { /* We can't handle this redirect... */
506                        req->status_string = g_strdup("Can't locate Location: header");
507                        return TRUE;
508                }
509
510                loc += 11;
511                while (*loc == ' ') {
512                        loc++;
513                }
514
515                /* TODO/FIXME: Possibly have to handle relative redirections,
516                   and rewrite Host: headers. Not necessary for now, it's
517                   enough for passport authentication like this. */
518
519                if (*loc == '/') {
520                        /* Just a different pathname... */
521
522                        /* Since we don't cache the servername, and since we
523                           don't need this yet anyway, I won't implement it. */
524
525                        req->status_string = g_strdup("Can't handle relative redirects");
526
527                        return TRUE;
528                } else {
529                        /* A whole URL */
530                        url_t *url;
531                        char *s, *version, *headers;
532                        const char *new_method;
533
534                        s = strstr(loc, "\r\n");
535                        if (s == NULL) {
536                                return TRUE;
537                        }
538
539                        url = g_new0(url_t, 1);
540                        *s = 0;
541
542                        if (!url_set(url, loc)) {
543                                req->status_string = g_strdup("Malformed redirect URL");
544                                g_free(url);
545                                return TRUE;
546                        }
547
548                        /* Find all headers and, if necessary, the POST request contents.
549                           Skip the old Host: header though. This crappy code here means
550                           anything using this http_client MUST put the Host: header at
551                           the top. */
552                        if (!((s = strstr(req->request, "\r\nHost: ")) &&
553                              (s = strstr(s + strlen("\r\nHost: "), "\r\n")))) {
554                                req->status_string = g_strdup("Error while rebuilding request string");
555                                g_free(url);
556                                return TRUE;
557                        }
558                        headers = s;
559
560                        /* More or less HTTP/1.0 compliant, from my reading of RFC 2616.
561                           Always perform a GET request unless we received a 301. 303 was
562                           meant for this but it's HTTP/1.1-only and we're specifically
563                           speaking HTTP/1.0. ...
564
565                           Well except someone at identi.ca's didn't bother reading any
566                           RFCs and just return HTTP/1.1-specific status codes to HTTP/1.0
567                           requests. Fuckers. So here we are, handle 301..303,307. */
568                        if (strncmp(req->request, "GET", 3) == 0) {
569                                /* GETs never become POSTs. */
570                                new_method = "GET";
571                        } else if (req->status_code == 302 || req->status_code == 303) {
572                                /* 302 de-facto becomes GET, 303 as specified by RFC 2616#10.3.3 */
573                                new_method = "GET";
574                        } else {
575                                /* 301 de-facto should stay POST, 307 specifally RFC 2616#10.3.8 */
576                                new_method = "POST";
577                        }
578
579                        if ((version = strstr(req->request, " HTTP/")) &&
580                            (s = strstr(version, "\r\n"))) {
581                                version++;
582                                version = g_strndup(version, s - version);
583                        } else {
584                                version = g_strdup("HTTP/1.0");
585                        }
586
587                        /* Okay, this isn't fun! We have to rebuild the request... :-( */
588                        new_request = g_strdup_printf("%s %s %s\r\nHost: %s%s",
589                                                      new_method, url->file, version,
590                                                      url->host, headers);
591
592                        new_host = g_strdup(url->host);
593                        new_port = url->port;
594                        new_proto = url->proto;
595
596                        /* If we went from POST to GET, truncate the request content. */
597                        if (new_request[0] != req->request[0] && new_request[0] == 'G' &&
598                            (s = strstr(new_request, "\r\n\r\n"))) {
599                                s[4] = '\0';
600                        }
601
602                        g_free(url);
603                        g_free(version);
604                }
605
606                if (req->ssl) {
607                        ssl_disconnect(req->ssl);
608                } else {
609                        closesocket(req->fd);
610                }
611
612                req->fd = -1;
613                req->ssl = NULL;
614
615                if (getenv("BITLBEE_DEBUG")) {
616                        printf("New headers for redirected HTTP request:\n%s\n", new_request);
617                }
618
619                if (new_proto == PROTO_HTTPS) {
620                        req->ssl = ssl_connect(new_host, new_port, TRUE, http_ssl_connected, req);
621                        if (req->ssl == NULL) {
622                                error = 1;
623                        }
624                } else {
625                        req->fd = proxy_connect(new_host, new_port, http_connected, req);
626                        if (req->fd < 0) {
627                                error = 1;
628                        }
629                }
630                g_free(new_host);
631
632                if (error) {
633                        req->status_string = g_strdup("Connection problem during redirect");
634                        g_free(new_request);
635                        return TRUE;
636                }
637
638                g_free(req->request);
639                g_free(req->reply_headers);
640                g_free(req->sbuf);
641                req->request = new_request;
642                req->request_length = strlen(new_request);
643                req->bytes_read = req->bytes_written = req->inpa = 0;
644                req->reply_headers = req->reply_body = NULL;
645                req->sbuf = req->cbuf = NULL;
646                req->sblen = req->cblen = 0;
647
648                return FALSE;
649        }
650
651        if ((s = get_rfc822_header(req->reply_headers, "Content-Length", 0)) &&
652            sscanf(s, "%d", &req->content_length) != 1) {
653                req->content_length = -1;
654        }
655        g_free(s);
656
657        if ((s = get_rfc822_header(req->reply_headers, "Transfer-Encoding", 0))) {
658                if (strcasestr(s, "chunked")) {
659                        req->flags |= HTTPC_CHUNKED;
660                        req->cbuf = req->sbuf;
661                        req->cblen = req->sblen;
662
663                        req->reply_body = req->sbuf = g_strdup("");
664                        req->body_size = req->sblen = 0;
665                }
666                g_free(s);
667        }
668
669        return TRUE;
670}
671
672void http_flush_bytes(struct http_request *req, size_t len)
673{
674        if (len <= 0 || len > req->body_size || !(req->flags & HTTPC_STREAMING)) {
675                return;
676        }
677
678        req->reply_body += len;
679        req->body_size -= len;
680
681        if (req->reply_body - req->sbuf >= 512) {
682                char *new = g_memdup(req->reply_body, req->body_size + 1);
683                g_free(req->sbuf);
684                req->reply_body = req->sbuf = new;
685                req->sblen = req->body_size;
686        }
687}
688
689void http_close(struct http_request *req)
690{
691        if (!req) {
692                return;
693        }
694
695        if (req->inpa > 0) {
696                b_event_remove(req->inpa);
697        }
698
699        if (req->ssl) {
700                ssl_disconnect(req->ssl);
701        } else {
702                proxy_disconnect(req->fd);
703        }
704
705        http_free(req);
706}
707
708static void http_free(struct http_request *req)
709{
710        g_free(req->request);
711        g_free(req->reply_headers);
712        g_free(req->status_string);
713        g_free(req->sbuf);
714        g_free(req->cbuf);
715        g_free(req);
716}
Note: See TracBrowser for help on using the repository browser.