source: lib/http_client.c @ a6b00fc

Last change on this file since a6b00fc was 5ebff60, checked in by dequis <dx@…>, at 2015-02-20T22:50:54Z

Reindent everything to K&R style with tabs

Used uncrustify, with the configuration file in ./doc/uncrustify.cfg

Commit author set to "Indent <please@…>" so that it's easier to
skip while doing git blame.

  • Property mode set to 100644
File size: 17.4 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        req->func(req);
165        http_free(req);
166        return FALSE;
167}
168
169static gboolean http_ssl_connected(gpointer data, int returncode, void *source, b_input_condition cond)
170{
171        struct http_request *req = data;
172
173        if (source == NULL) {
174                if (returncode != 0) {
175                        char *err = ssl_verify_strerror(returncode);
176                        req->status_string = g_strdup_printf(
177                                "Certificate verification problem 0x%x: %s",
178                                returncode, err ? err : "Unknown");
179                        g_free(err);
180                }
181                return http_connected(data, -1, cond);
182        }
183
184        req->fd = ssl_getfd(source);
185
186        return http_connected(data, req->fd, cond);
187}
188
189typedef enum {
190        CR_OK,
191        CR_EOF,
192        CR_ERROR,
193        CR_ABORT,
194} http_ret_t;
195
196static gboolean http_handle_headers(struct http_request *req);
197static http_ret_t http_process_chunked_data(struct http_request *req, const char *buffer, int len);
198static http_ret_t http_process_data(struct http_request *req, const char *buffer, int len);
199
200static gboolean http_incoming_data(gpointer data, int source, b_input_condition cond)
201{
202        struct http_request *req = data;
203        char buffer[4096];
204        int st;
205
206        if (req->inpa > 0) {
207                b_event_remove(req->inpa);
208                req->inpa = 0;
209        }
210
211        if (req->ssl) {
212                st = ssl_read(req->ssl, buffer, sizeof(buffer));
213                if (st < 0) {
214                        if (ssl_errno != SSL_AGAIN) {
215                                /* goto cleanup; */
216
217                                /* YAY! We have to deal with crappy Microsoft
218                                   servers that LOVE to send invalid TLS
219                                   packets that abort connections! \o/ */
220
221                                goto eof;
222                        }
223                } else if (st == 0) {
224                        goto eof;
225                }
226        } else {
227                st = read(req->fd, buffer, sizeof(buffer));
228                if (st < 0) {
229                        if (!sockerr_again()) {
230                                req->status_string = g_strdup(strerror(errno));
231                                goto cleanup;
232                        }
233                } else if (st == 0) {
234                        goto eof;
235                }
236        }
237
238        if (st > 0) {
239                http_ret_t c;
240
241                if (req->flags & HTTPC_CHUNKED) {
242                        c = http_process_chunked_data(req, buffer, st);
243                } else {
244                        c = http_process_data(req, buffer, st);
245                }
246
247                if (c == CR_EOF) {
248                        goto eof;
249                } else if (c == CR_ERROR || c == CR_ABORT) {
250                        return FALSE;
251                }
252        }
253
254        if (req->content_length != -1 &&
255            req->body_size >= req->content_length) {
256                goto eof;
257        }
258
259        if (ssl_pending(req->ssl)) {
260                return http_incoming_data(data, source, cond);
261        }
262
263        /* There will be more! */
264        req->inpa = b_input_add(req->fd,
265                                req->ssl ? ssl_getdirection(req->ssl) : B_EV_IO_READ,
266                                http_incoming_data, req);
267
268        return FALSE;
269
270eof:
271        req->flags |= HTTPC_EOF;
272
273        /* Maybe if the webserver is overloaded, or when there's bad SSL
274           support... */
275        if (req->bytes_read == 0) {
276                req->status_string = g_strdup("Empty HTTP reply");
277                goto cleanup;
278        }
279
280cleanup:
281        /* Avoid g_source_remove warnings */
282        req->inpa = 0;
283
284        if (req->ssl) {
285                ssl_disconnect(req->ssl);
286        } else {
287                closesocket(req->fd);
288        }
289
290        if (req->body_size < req->content_length) {
291                req->status_code = -1;
292                g_free(req->status_string);
293                req->status_string = g_strdup("Response truncated");
294        }
295
296        if (getenv("BITLBEE_DEBUG") && req) {
297                printf("Finishing HTTP request with status: %s\n",
298                       req->status_string ? req->status_string : "NULL");
299        }
300
301        req->func(req);
302        http_free(req);
303        return FALSE;
304}
305
306static http_ret_t http_process_chunked_data(struct http_request *req, const char *buffer, int len)
307{
308        char *chunk, *eos, *s;
309
310        if (len < 0) {
311                return TRUE;
312        }
313
314        if (len > 0) {
315                req->cbuf = g_realloc(req->cbuf, req->cblen + len + 1);
316                memcpy(req->cbuf + req->cblen, buffer, len);
317                req->cblen += len;
318                req->cbuf[req->cblen] = '\0';
319        }
320
321        /* Turns out writing a proper chunked-encoding state machine is not
322           that simple. :-( I've tested this one feeding it byte by byte so
323           I hope it's solid now. */
324        chunk = req->cbuf;
325        eos = req->cbuf + req->cblen;
326        while (TRUE) {
327                int clen = 0;
328
329                /* Might be a \r\n from the last chunk. */
330                s = chunk;
331                while (g_ascii_isspace(*s)) {
332                        s++;
333                }
334                /* Chunk length. Might be incomplete. */
335                if (s < eos && sscanf(s, "%x", &clen) != 1) {
336                        return CR_ERROR;
337                }
338                while (g_ascii_isxdigit(*s)) {
339                        s++;
340                }
341
342                /* If we read anything here, it *must* be \r\n. */
343                if (strncmp(s, "\r\n", MIN(2, eos - s)) != 0) {
344                        return CR_ERROR;
345                }
346                s += 2;
347
348                if (s >= eos) {
349                        break;
350                }
351
352                /* 0-length chunk means end of response. */
353                if (clen == 0) {
354                        return CR_EOF;
355                }
356
357                /* Wait for the whole chunk to arrive. */
358                if (s + clen > eos) {
359                        break;
360                }
361                if (http_process_data(req, s, clen) != CR_OK) {
362                        return CR_ABORT;
363                }
364
365                chunk = s + clen;
366        }
367
368        if (chunk != req->cbuf) {
369                req->cblen = eos - chunk;
370                s = g_memdup(chunk, req->cblen + 1);
371                g_free(req->cbuf);
372                req->cbuf = s;
373        }
374
375        return CR_OK;
376}
377
378static http_ret_t http_process_data(struct http_request *req, const char *buffer, int len)
379{
380        if (len <= 0) {
381                return CR_OK;
382        }
383
384        if (!req->reply_body) {
385                req->reply_headers = g_realloc(req->reply_headers, req->bytes_read + len + 1);
386                memcpy(req->reply_headers + req->bytes_read, buffer, len);
387                req->bytes_read += len;
388                req->reply_headers[req->bytes_read] = '\0';
389
390                if (strstr(req->reply_headers, "\r\n\r\n") ||
391                    strstr(req->reply_headers, "\n\n")) {
392                        /* We've now received all headers. Look for something
393                           interesting. */
394                        if (!http_handle_headers(req)) {
395                                return CR_ABORT;
396                        }
397
398                        /* Start parsing the body as chunked if required. */
399                        if (req->flags & HTTPC_CHUNKED) {
400                                return http_process_chunked_data(req, NULL, 0);
401                        }
402                }
403        } else {
404                int pos = req->reply_body - req->sbuf;
405                req->sbuf = g_realloc(req->sbuf, req->sblen + len + 1);
406                memcpy(req->sbuf + req->sblen, buffer, len);
407                req->bytes_read += len;
408                req->sblen += len;
409                req->sbuf[req->sblen] = '\0';
410                req->reply_body = req->sbuf + pos;
411                req->body_size = req->sblen - pos;
412        }
413
414        if ((req->flags & HTTPC_STREAMING) && req->reply_body) {
415                req->func(req);
416        }
417
418        return CR_OK;
419}
420
421/* Splits headers and body. Checks result code, in case of 300s it'll handle
422   redirects. If this returns FALSE, don't call any callbacks! */
423static gboolean http_handle_headers(struct http_request *req)
424{
425        char *end1, *end2, *s;
426        int evil_server = 0;
427
428        /* Zero termination is very convenient. */
429        req->reply_headers[req->bytes_read] = '\0';
430
431        /* Find the separation between headers and body, and keep stupid
432           webservers in mind. */
433        end1 = strstr(req->reply_headers, "\r\n\r\n");
434        end2 = strstr(req->reply_headers, "\n\n");
435
436        if (end2 && end2 < end1) {
437                end1 = end2 + 1;
438                evil_server = 1;
439        } else if (end1) {
440                end1 += 2;
441        } else {
442                req->status_string = g_strdup("Malformed HTTP reply");
443                return TRUE;
444        }
445
446        *end1 = '\0';
447
448        if (getenv("BITLBEE_DEBUG")) {
449                printf("HTTP response headers:\n%s\n", req->reply_headers);
450        }
451
452        if (evil_server) {
453                req->reply_body = end1 + 1;
454        } else {
455                req->reply_body = end1 + 2;
456        }
457
458        /* Separately allocated space for headers and body. */
459        req->sblen = req->body_size = req->reply_headers + req->bytes_read - req->reply_body;
460        req->sbuf = req->reply_body = g_memdup(req->reply_body, req->body_size + 1);
461        req->reply_headers = g_realloc(req->reply_headers, end1 - req->reply_headers + 1);
462
463        if ((end1 = strchr(req->reply_headers, ' ')) != NULL) {
464                if (sscanf(end1 + 1, "%hd", &req->status_code) != 1) {
465                        req->status_string = g_strdup("Can't parse status code");
466                        req->status_code = -1;
467                } else {
468                        char *eol;
469
470                        if (evil_server) {
471                                eol = strchr(end1, '\n');
472                        } else {
473                                eol = strchr(end1, '\r');
474                        }
475
476                        req->status_string = g_strndup(end1 + 1, eol - end1 - 1);
477
478                        /* Just to be sure... */
479                        if ((eol = strchr(req->status_string, '\r'))) {
480                                *eol = 0;
481                        }
482                        if ((eol = strchr(req->status_string, '\n'))) {
483                                *eol = 0;
484                        }
485                }
486        } else {
487                req->status_string = g_strdup("Can't locate status code");
488                req->status_code = -1;
489        }
490
491        if (((req->status_code >= 301 && req->status_code <= 303) ||
492             req->status_code == 307) && req->redir_ttl-- > 0) {
493                char *loc, *new_request, *new_host;
494                int error = 0, new_port, new_proto;
495
496                /* We might fill it again, so let's not leak any memory. */
497                g_free(req->status_string);
498                req->status_string = NULL;
499
500                loc = strstr(req->reply_headers, "\nLocation: ");
501                if (loc == NULL) { /* We can't handle this redirect... */
502                        req->status_string = g_strdup("Can't locate Location: header");
503                        return TRUE;
504                }
505
506                loc += 11;
507                while (*loc == ' ') {
508                        loc++;
509                }
510
511                /* TODO/FIXME: Possibly have to handle relative redirections,
512                   and rewrite Host: headers. Not necessary for now, it's
513                   enough for passport authentication like this. */
514
515                if (*loc == '/') {
516                        /* Just a different pathname... */
517
518                        /* Since we don't cache the servername, and since we
519                           don't need this yet anyway, I won't implement it. */
520
521                        req->status_string = g_strdup("Can't handle relative redirects");
522
523                        return TRUE;
524                } else {
525                        /* A whole URL */
526                        url_t *url;
527                        char *s, *version, *headers;
528                        const char *new_method;
529
530                        s = strstr(loc, "\r\n");
531                        if (s == NULL) {
532                                return TRUE;
533                        }
534
535                        url = g_new0(url_t, 1);
536                        *s = 0;
537
538                        if (!url_set(url, loc)) {
539                                req->status_string = g_strdup("Malformed redirect URL");
540                                g_free(url);
541                                return TRUE;
542                        }
543
544                        /* Find all headers and, if necessary, the POST request contents.
545                           Skip the old Host: header though. This crappy code here means
546                           anything using this http_client MUST put the Host: header at
547                           the top. */
548                        if (!((s = strstr(req->request, "\r\nHost: ")) &&
549                              (s = strstr(s + strlen("\r\nHost: "), "\r\n")))) {
550                                req->status_string = g_strdup("Error while rebuilding request string");
551                                g_free(url);
552                                return TRUE;
553                        }
554                        headers = s;
555
556                        /* More or less HTTP/1.0 compliant, from my reading of RFC 2616.
557                           Always perform a GET request unless we received a 301. 303 was
558                           meant for this but it's HTTP/1.1-only and we're specifically
559                           speaking HTTP/1.0. ...
560
561                           Well except someone at identi.ca's didn't bother reading any
562                           RFCs and just return HTTP/1.1-specific status codes to HTTP/1.0
563                           requests. Fuckers. So here we are, handle 301..303,307. */
564                        if (strncmp(req->request, "GET", 3) == 0) {
565                                /* GETs never become POSTs. */
566                                new_method = "GET";
567                        } else if (req->status_code == 302 || req->status_code == 303) {
568                                /* 302 de-facto becomes GET, 303 as specified by RFC 2616#10.3.3 */
569                                new_method = "GET";
570                        } else {
571                                /* 301 de-facto should stay POST, 307 specifally RFC 2616#10.3.8 */
572                                new_method = "POST";
573                        }
574
575                        if ((version = strstr(req->request, " HTTP/")) &&
576                            (s = strstr(version, "\r\n"))) {
577                                version++;
578                                version = g_strndup(version, s - version);
579                        } else {
580                                version = g_strdup("HTTP/1.0");
581                        }
582
583                        /* Okay, this isn't fun! We have to rebuild the request... :-( */
584                        new_request = g_strdup_printf("%s %s %s\r\nHost: %s%s",
585                                                      new_method, url->file, version,
586                                                      url->host, headers);
587
588                        new_host = g_strdup(url->host);
589                        new_port = url->port;
590                        new_proto = url->proto;
591
592                        /* If we went from POST to GET, truncate the request content. */
593                        if (new_request[0] != req->request[0] && new_request[0] == 'G' &&
594                            (s = strstr(new_request, "\r\n\r\n"))) {
595                                s[4] = '\0';
596                        }
597
598                        g_free(url);
599                        g_free(version);
600                }
601
602                if (req->ssl) {
603                        ssl_disconnect(req->ssl);
604                } else {
605                        closesocket(req->fd);
606                }
607
608                req->fd = -1;
609                req->ssl = NULL;
610
611                if (getenv("BITLBEE_DEBUG")) {
612                        printf("New headers for redirected HTTP request:\n%s\n", new_request);
613                }
614
615                if (new_proto == PROTO_HTTPS) {
616                        req->ssl = ssl_connect(new_host, new_port, TRUE, http_ssl_connected, req);
617                        if (req->ssl == NULL) {
618                                error = 1;
619                        }
620                } else {
621                        req->fd = proxy_connect(new_host, new_port, http_connected, req);
622                        if (req->fd < 0) {
623                                error = 1;
624                        }
625                }
626                g_free(new_host);
627
628                if (error) {
629                        req->status_string = g_strdup("Connection problem during redirect");
630                        g_free(new_request);
631                        return TRUE;
632                }
633
634                g_free(req->request);
635                g_free(req->reply_headers);
636                g_free(req->sbuf);
637                req->request = new_request;
638                req->request_length = strlen(new_request);
639                req->bytes_read = req->bytes_written = req->inpa = 0;
640                req->reply_headers = req->reply_body = NULL;
641                req->sbuf = req->cbuf = NULL;
642                req->sblen = req->cblen = 0;
643
644                return FALSE;
645        }
646
647        if ((s = get_rfc822_header(req->reply_headers, "Content-Length", 0)) &&
648            sscanf(s, "%d", &req->content_length) != 1) {
649                req->content_length = -1;
650        }
651        g_free(s);
652
653        if ((s = get_rfc822_header(req->reply_headers, "Transfer-Encoding", 0))) {
654                if (strcasestr(s, "chunked")) {
655                        req->flags |= HTTPC_CHUNKED;
656                        req->cbuf = req->sbuf;
657                        req->cblen = req->sblen;
658
659                        req->reply_body = req->sbuf = g_strdup("");
660                        req->body_size = req->sblen = 0;
661                }
662                g_free(s);
663        }
664
665        return TRUE;
666}
667
668void http_flush_bytes(struct http_request *req, size_t len)
669{
670        if (len <= 0 || len > req->body_size || !(req->flags & HTTPC_STREAMING)) {
671                return;
672        }
673
674        req->reply_body += len;
675        req->body_size -= len;
676
677        if (req->reply_body - req->sbuf >= 512) {
678                char *new = g_memdup(req->reply_body, req->body_size + 1);
679                g_free(req->sbuf);
680                req->reply_body = req->sbuf = new;
681                req->sblen = req->body_size;
682        }
683}
684
685void http_close(struct http_request *req)
686{
687        if (!req) {
688                return;
689        }
690
691        if (req->inpa > 0) {
692                b_event_remove(req->inpa);
693        }
694
695        if (req->ssl) {
696                ssl_disconnect(req->ssl);
697        } else {
698                closesocket(req->fd);
699        }
700
701        http_free(req);
702}
703
704static void http_free(struct http_request *req)
705{
706        g_free(req->request);
707        g_free(req->reply_headers);
708        g_free(req->status_string);
709        g_free(req->sbuf);
710        g_free(req->cbuf);
711        g_free(req);
712}
Note: See TracBrowser for help on using the repository browser.