source: lib/http_client.c @ b7cd22d

Last change on this file since b7cd22d was ab19567, checked in by Wilmer van der Gaast <wilmer@…>, at 2013-06-16T12:15:15Z

Merging HTTP/1.1 branch. This implements HTTP/1.1 support in http_client.
Little benefit as I'm not burning my fingers on keepalive connecitons for
now, but eventually the Twitter streaming API is going to drop 1.0 support:
https://dev.twitter.com/blog/deprecating-http-1.0-streaming-api

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