source: lib/http_client.c @ 96dd574

Last change on this file since 96dd574 was 898c08e, checked in by Wilmer van der Gaast <wilmer@…>, at 2012-11-25T11:47:23Z

Clean up streaming HTTP event handler, which should fix crash-bugs with GLib.
Forgot to do this so far because my libevent code let me get away with it.

  • Property mode set to 100644
File size: 15.1 KB
RevLine 
[8a9afe4]1  /********************************************************************\
2  * BitlBee -- An IRC to other IM-networks gateway                     *
3  *                                                                    *
[03a8f8e]4  * Copyright 2002-2011 Wilmer van der Gaast and others                *
[8a9afe4]5  \********************************************************************/
6
[52b3a99]7/* HTTP(S) module                                                       */
[8a9afe4]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>
[52b3a99]27#include <stdio.h>
[8a9afe4]28
29#include "http_client.h"
[52b3a99]30#include "url.h"
[e4d6271]31#include "sock.h"
[8a9afe4]32
33
[ba9edaa]34static gboolean http_connected( gpointer data, int source, b_input_condition cond );
[486ddb5]35static gboolean http_ssl_connected( gpointer data, int returncode, void *source, b_input_condition cond );
[ba9edaa]36static gboolean http_incoming_data( gpointer data, int source, b_input_condition cond );
[516a9c6]37static void http_free( struct http_request *req );
[8a9afe4]38
39
[516a9c6]40struct http_request *http_dorequest( char *host, int port, int ssl, char *request, http_input_function func, gpointer data )
[8a9afe4]41{
42        struct http_request *req;
43        int error = 0;
44       
45        req = g_new0( struct http_request, 1 );
46       
47        if( ssl )
48        {
[a72dc2b]49                req->ssl = ssl_connect( host, port, TRUE, http_ssl_connected, req );
[8a9afe4]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        {
[fb98634]62                http_free( req );
63                return NULL;
[8a9afe4]64        }
65       
[52b3a99]66        req->func = func;
67        req->data = data;
[8a9afe4]68        req->request = g_strdup( request );
69        req->request_length = strlen( request );
[7885d0f]70        req->redir_ttl = 3;
[8a9afe4]71       
[3f808ca]72        if( getenv( "BITLBEE_DEBUG" ) )
73                printf( "About to send HTTP request:\n%s\n", req->request );
74       
[11ec078]75        return req;
[8a9afe4]76}
77
[516a9c6]78struct http_request *http_dorequest_url( char *url_string, http_input_function func, gpointer data )
[0790644]79{
80        url_t *url = g_new0( url_t, 1 );
81        char *request;
82        void *ret;
83       
84        if( !url_set( url, url_string ) )
85        {
86                g_free( url );
87                return NULL;
88        }
89       
90        if( url->proto != PROTO_HTTP && url->proto != PROTO_HTTPS )
91        {
92                g_free( url );
93                return NULL;
94        }
95       
96        request = g_strdup_printf( "GET %s HTTP/1.0\r\n"
97                                   "Host: %s\r\n"
[2423c93]98                                   "Connection: close\r\n"
[1b5ab36]99                                   "User-Agent: BitlBee " BITLBEE_VERSION " " ARCH "/" CPU "\r\n"
[0790644]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 );
[266fe2f]107        return ret;
[0790644]108}
109
[8a9afe4]110/* This one is actually pretty simple... Might get more calls if we can't write
111   the whole request at once. */
[ba9edaa]112static gboolean http_connected( gpointer data, int source, b_input_condition cond )
[8a9afe4]113{
114        struct http_request *req = data;
115        int st;
116       
117        if( source < 0 )
118                goto error;
119       
120        if( req->inpa > 0 )
[ba9edaa]121                b_event_remove( req->inpa );
[8a9afe4]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                        {
[52b3a99]146                                closesocket( req->fd );
[8a9afe4]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 )
[ba9edaa]156                req->inpa = b_input_add( source,
[e046390]157                                         req->ssl ? ssl_getdirection( req->ssl ) : B_EV_IO_WRITE,
[ba9edaa]158                                         http_connected, req );
[8a9afe4]159        else
[e046390]160                req->inpa = b_input_add( source, B_EV_IO_READ, http_incoming_data, req );
[8a9afe4]161       
[ba9edaa]162        return FALSE;
[8a9afe4]163       
164error:
[a72dc2b]165        if( req->status_string == NULL )
166                req->status_string = g_strdup( "Error while writing HTTP request" );
[7deb447]167       
[8a9afe4]168        req->func( req );
[fb98634]169        http_free( req );
[ba9edaa]170        return FALSE;
[8a9afe4]171}
172
[486ddb5]173static gboolean http_ssl_connected( gpointer data, int returncode, void *source, b_input_condition cond )
[8a9afe4]174{
175        struct http_request *req = data;
176       
177        if( source == NULL )
[a72dc2b]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                }
[8a9afe4]187                return http_connected( data, -1, cond );
[a72dc2b]188        }
[8a9afe4]189       
190        req->fd = ssl_getfd( source );
191       
192        return http_connected( data, req->fd, cond );
193}
194
[8bd866f]195static gboolean http_handle_headers( struct http_request *req );
196
[ba9edaa]197static gboolean http_incoming_data( gpointer data, int source, b_input_condition cond )
[8a9afe4]198{
199        struct http_request *req = data;
[5f2f728]200        char buffer[4096];
[8bd866f]201        char *s;
[55ccc9a0]202        size_t content_length;
[8a9afe4]203        int st;
204       
205        if( req->inpa > 0 )
[ba9edaa]206                b_event_remove( req->inpa );
[8a9afe4]207       
208        if( req->ssl )
209        {
210                st = ssl_read( req->ssl, buffer, sizeof( buffer ) );
211                if( st < 0 )
212                {
213                        if( ssl_errno != SSL_AGAIN )
214                        {
[ad8b8a3]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                               
[8bd866f]221                                goto eof;
[8a9afe4]222                        }
223                }
224                else if( st == 0 )
225                {
[8bd866f]226                        goto eof;
[8a9afe4]227                }
228        }
229        else
230        {
231                st = read( req->fd, buffer, sizeof( buffer ) );
232                if( st < 0 )
233                {
234                        if( !sockerr_again() )
235                        {
[7deb447]236                                req->status_string = g_strdup( strerror( errno ) );
[8a9afe4]237                                goto cleanup;
238                        }
239                }
240                else if( st == 0 )
241                {
[8bd866f]242                        goto eof;
[8a9afe4]243                }
244        }
245       
[8bd866f]246        if( st > 0 && !req->sbuf )
[8a9afe4]247        {
248                req->reply_headers = g_realloc( req->reply_headers, req->bytes_read + st + 1 );
249                memcpy( req->reply_headers + req->bytes_read, buffer, st );
[52b3a99]250                req->bytes_read += st;
[8bd866f]251               
252                st = 0;
253        }
254       
255        if( st >= 0 && ( req->flags & HTTPC_STREAMING ) )
256        {
257                if( !req->reply_body &&
258                    ( strstr( req->reply_headers, "\r\n\r\n" ) ||
259                      strstr( req->reply_headers, "\n\n" ) ) )
260                {
261                        size_t hlen;
262                       
263                        /* We've now received all headers, so process them once
264                           before we start feeding back data. */
265                        if( !http_handle_headers( req ) )
266                                return FALSE;
267                       
268                        hlen = req->reply_body - req->reply_headers;
269                       
270                        req->sblen = req->bytes_read - hlen;
271                        req->sbuf = g_memdup( req->reply_body, req->sblen + 1 );
272                        req->reply_headers = g_realloc( req->reply_headers, hlen + 1 );
273                       
274                        req->reply_body = req->sbuf;
275                }
276               
277                if( st > 0 )
278                {
279                        int pos = req->reply_body - req->sbuf;
280                        req->sbuf = g_realloc( req->sbuf, req->sblen + st + 1 );
281                        memcpy( req->sbuf + req->sblen, buffer, st );
282                        req->bytes_read += st;
283                        req->sblen += st;
[ddc2de5]284                        req->sbuf[req->sblen] = '\0';
[8bd866f]285                        req->reply_body = req->sbuf + pos;
286                        req->body_size = req->sblen - pos;
287                }
288               
289                if( req->reply_body )
290                        req->func( req );
[8a9afe4]291        }
292       
[8bd866f]293        if( ssl_pending( req->ssl ) )
294                return http_incoming_data( data, source, cond );
295       
[8a9afe4]296        /* There will be more! */
[ba9edaa]297        req->inpa = b_input_add( req->fd,
[e046390]298                                 req->ssl ? ssl_getdirection( req->ssl ) : B_EV_IO_READ,
[ba9edaa]299                                 http_incoming_data, req );
[8a9afe4]300       
[8bd866f]301        return FALSE;
[8a9afe4]302
[8bd866f]303eof:
[dd672e2]304        req->flags |= HTTPC_EOF;
305       
[0602496]306        /* Maybe if the webserver is overloaded, or when there's bad SSL
307           support... */
308        if( req->bytes_read == 0 )
[7deb447]309        {
310                req->status_string = g_strdup( "Empty HTTP reply" );
[0602496]311                goto cleanup;
[7deb447]312        }
[0602496]313       
[8bd866f]314        if( !( req->flags & HTTPC_STREAMING ) )
315        {
316                /* Returns FALSE if we were redirected, in which case we should abort
317                   and not run any callback yet. */
318                if( !http_handle_headers( req ) )
319                        return FALSE;
320        }
321
322cleanup:
323        if( req->ssl )
324                ssl_disconnect( req->ssl );
325        else
326                closesocket( req->fd );
327       
328        if( ( s = get_rfc822_header( req->reply_headers, "Content-Length", 0 ) ) &&
329            sscanf( s, "%zd", &content_length ) == 1 )
330        {
331                if( content_length < req->body_size )
332                {
333                        req->status_code = -1;
334                        g_free( req->status_string );
335                        req->status_string = g_strdup( "Response truncated" );
336                }
337        }
338        g_free( s );
339       
340        if( getenv( "BITLBEE_DEBUG" ) && req )
341                printf( "Finishing HTTP request with status: %s\n",
342                        req->status_string ? req->status_string : "NULL" );
343       
344        req->func( req );
345        http_free( req );
346        return FALSE;
347}
348
349/* Splits headers and body. Checks result code, in case of 300s it'll handle
350   redirects. If this returns FALSE, don't call any callbacks! */
351static gboolean http_handle_headers( struct http_request *req )
352{
353        char *end1, *end2;
354        int evil_server = 0;
355       
[8a9afe4]356        /* Zero termination is very convenient. */
[8bd866f]357        req->reply_headers[req->bytes_read] = '\0';
[8a9afe4]358       
359        /* Find the separation between headers and body, and keep stupid
360           webservers in mind. */
361        end1 = strstr( req->reply_headers, "\r\n\r\n" );
362        end2 = strstr( req->reply_headers, "\n\n" );
363       
364        if( end2 && end2 < end1 )
365        {
[52b3a99]366                end1 = end2 + 1;
[8a9afe4]367                evil_server = 1;
368        }
[0eec386]369        else if( end1 )
[52b3a99]370        {
371                end1 += 2;
372        }
[0eec386]373        else
[8a9afe4]374        {
[7deb447]375                req->status_string = g_strdup( "Malformed HTTP reply" );
[8bd866f]376                return TRUE;
[52b3a99]377        }
378       
[0eec386]379        *end1 = 0;
380       
[bd31661]381        if( getenv( "BITLBEE_DEBUG" ) )
[3f808ca]382                printf( "HTTP response headers:\n%s\n", req->reply_headers );
[bd31661]383       
[0eec386]384        if( evil_server )
385                req->reply_body = end1 + 1;
386        else
387                req->reply_body = end1 + 2;
388       
[41e5202]389        req->body_size = req->reply_headers + req->bytes_read - req->reply_body;
[0eec386]390       
[52b3a99]391        if( ( end1 = strchr( req->reply_headers, ' ' ) ) != NULL )
392        {
[8bd866f]393                if( sscanf( end1 + 1, "%hd", &req->status_code ) != 1 )
[7deb447]394                {
395                        req->status_string = g_strdup( "Can't parse status code" );
[52b3a99]396                        req->status_code = -1;
[7deb447]397                }
398                else
399                {
400                        char *eol;
401                       
402                        if( evil_server )
403                                eol = strchr( end1, '\n' );
404                        else
405                                eol = strchr( end1, '\r' );
406                       
407                        req->status_string = g_strndup( end1 + 1, eol - end1 - 1 );
408                       
409                        /* Just to be sure... */
410                        if( ( eol = strchr( req->status_string, '\r' ) ) )
411                                *eol = 0;
412                        if( ( eol = strchr( req->status_string, '\n' ) ) )
413                                *eol = 0;
414                }
[52b3a99]415        }
416        else
417        {
[7deb447]418                req->status_string = g_strdup( "Can't locate status code" );
[52b3a99]419                req->status_code = -1;
420        }
421       
[3f808ca]422        if( ( ( req->status_code >= 301 && req->status_code <= 303 ) ||
423              req->status_code == 307 ) && req->redir_ttl-- > 0 )
[52b3a99]424        {
425                char *loc, *new_request, *new_host;
426                int error = 0, new_port, new_proto;
427               
[7deb447]428                /* We might fill it again, so let's not leak any memory. */
429                g_free( req->status_string );
430                req->status_string = NULL;
431               
[52b3a99]432                loc = strstr( req->reply_headers, "\nLocation: " );
433                if( loc == NULL ) /* We can't handle this redirect... */
[7deb447]434                {
435                        req->status_string = g_strdup( "Can't locate Location: header" );
[8bd866f]436                        return TRUE;
[7deb447]437                }
[52b3a99]438               
439                loc += 11;
440                while( *loc == ' ' )
441                        loc ++;
442               
443                /* TODO/FIXME: Possibly have to handle relative redirections,
444                   and rewrite Host: headers. Not necessary for now, it's
445                   enough for passport authentication like this. */
446               
447                if( *loc == '/' )
448                {
449                        /* Just a different pathname... */
450                       
451                        /* Since we don't cache the servername, and since we
452                           don't need this yet anyway, I won't implement it. */
453                       
[7deb447]454                        req->status_string = g_strdup( "Can't handle recursive redirects" );
455                       
[8bd866f]456                        return TRUE;
[52b3a99]457                }
[8a9afe4]458                else
[52b3a99]459                {
460                        /* A whole URL */
461                        url_t *url;
462                        char *s;
[03a8f8e]463                        const char *new_method;
[52b3a99]464                       
465                        s = strstr( loc, "\r\n" );
466                        if( s == NULL )
[8bd866f]467                                return TRUE;
[52b3a99]468                       
469                        url = g_new0( url_t, 1 );
470                        *s = 0;
471                       
472                        if( !url_set( url, loc ) )
473                        {
[7deb447]474                                req->status_string = g_strdup( "Malformed redirect URL" );
[52b3a99]475                                g_free( url );
[8bd866f]476                                return TRUE;
[52b3a99]477                        }
478                       
[03a8f8e]479                        /* Find all headers and, if necessary, the POST request contents.
480                           Skip the old Host: header though. This crappy code here means
481                           anything using this http_client MUST put the Host: header at
482                           the top. */
[dff732d]483                        if( !( ( s = strstr( req->request, "\r\nHost: " ) ) &&
484                               ( s = strstr( s + strlen( "\r\nHost: " ), "\r\n" ) ) ) )
[52b3a99]485                        {
[7deb447]486                                req->status_string = g_strdup( "Error while rebuilding request string" );
[52b3a99]487                                g_free( url );
[8bd866f]488                                return TRUE;
[52b3a99]489                        }
490                       
[03a8f8e]491                        /* More or less HTTP/1.0 compliant, from my reading of RFC 2616.
492                           Always perform a GET request unless we received a 301. 303 was
493                           meant for this but it's HTTP/1.1-only and we're specifically
[3f808ca]494                           speaking HTTP/1.0. ...
495                           
496                           Well except someone at identi.ca's didn't bother reading any
497                           RFCs and just return HTTP/1.1-specific status codes to HTTP/1.0
498                           requests. Fuckers. So here we are, handle 301..303,307. */
499                        if( strncmp( req->request, "GET", 3 ) == 0 )
500                                /* GETs never become POSTs. */
501                                new_method = "GET";
502                        else if( req->status_code == 302 || req->status_code == 303 )
503                                /* 302 de-facto becomes GET, 303 as specified by RFC 2616#10.3.3 */
504                                new_method = "GET";
505                        else
506                                /* 301 de-facto should stay POST, 307 specifally RFC 2616#10.3.8 */
507                                new_method = "POST";
[03a8f8e]508                       
509                        /* Okay, this isn't fun! We have to rebuild the request... :-( */
510                        new_request = g_strdup_printf( "%s %s HTTP/1.0\r\nHost: %s%s",
511                                                       new_method, url->file, url->host, s );
512                       
[52b3a99]513                        new_host = g_strdup( url->host );
514                        new_port = url->port;
515                        new_proto = url->proto;
516                       
[03a8f8e]517                        /* If we went from POST to GET, truncate the request content. */
518                        if( new_request[0] != req->request[0] && new_request[0] == 'G' &&
519                            ( s = strstr( new_request, "\r\n\r\n" ) ) )
520                                s[4] = '\0';
521                       
[52b3a99]522                        g_free( url );
523                }
524               
525                if( req->ssl )
526                        ssl_disconnect( req->ssl );
527                else
528                        closesocket( req->fd );
529               
530                req->fd = -1;
[7deb447]531                req->ssl = NULL;
[52b3a99]532               
[bd31661]533                if( getenv( "BITLBEE_DEBUG" ) )
[3f808ca]534                        printf( "New headers for redirected HTTP request:\n%s\n", new_request );
[bd31661]535       
[52b3a99]536                if( new_proto == PROTO_HTTPS )
537                {
[a72dc2b]538                        req->ssl = ssl_connect( new_host, new_port, TRUE, http_ssl_connected, req );
[52b3a99]539                        if( req->ssl == NULL )
540                                error = 1;
541                }
542                else
543                {
544                        req->fd = proxy_connect( new_host, new_port, http_connected, req );
545                        if( req->fd < 0 )
546                                error = 1;
547                }
[2db811a]548                g_free( new_host );
[52b3a99]549               
550                if( error )
551                {
[7deb447]552                        req->status_string = g_strdup( "Connection problem during redirect" );
[52b3a99]553                        g_free( new_request );
[8bd866f]554                        return TRUE;
[52b3a99]555                }
556               
557                g_free( req->request );
558                g_free( req->reply_headers );
559                req->request = new_request;
560                req->request_length = strlen( new_request );
561                req->bytes_read = req->bytes_written = req->inpa = 0;
562                req->reply_headers = req->reply_body = NULL;
563               
[ba9edaa]564                return FALSE;
[8a9afe4]565        }
566       
[8bd866f]567        return TRUE;
568}
[8a9afe4]569
[8bd866f]570void http_flush_bytes( struct http_request *req, size_t len )
571{
[1388d30]572        if( len <= 0 || len > req->body_size || !( req->flags & HTTPC_STREAMING ) )
573                return;
574       
575        req->reply_body += len;
576        req->body_size -= len;
577       
578        if( req->reply_body - req->sbuf >= 512 )
[55ccc9a0]579        {
[1388d30]580                printf( "Wasting %ld bytes, cleaning up stream buffer\n", req->reply_body - req->sbuf );
581                char *new = g_memdup( req->reply_body, req->body_size + 1 );
582                g_free( req->sbuf );
583                req->reply_body = req->sbuf = new;
584                req->sblen = req->body_size;
[55ccc9a0]585        }
[fb98634]586}
587
[1388d30]588void http_close( struct http_request *req )
589{
590        if( !req )
591                return;
592       
[898c08e]593        if( req->inpa > 0 )
594                b_event_remove( req->inpa );
595       
[1388d30]596        if( req->ssl )
597                ssl_disconnect( req->ssl );
598        else
599                closesocket( req->fd );
600       
601        http_free( req );
602}
603
[516a9c6]604static void http_free( struct http_request *req )
[fb98634]605{
[8a9afe4]606        g_free( req->request );
607        g_free( req->reply_headers );
[7deb447]608        g_free( req->status_string );
[8bd866f]609        g_free( req->sbuf );
[8a9afe4]610        g_free( req );
611}
Note: See TracBrowser for help on using the repository browser.