Ignore:
File:
1 edited

Legend:

Unmodified
Added
Removed
  • lib/http_client.c

    rc1d9c95 rdd7b931  
    22  * BitlBee -- An IRC to other IM-networks gateway                     *
    33  *                                                                    *
    4   * Copyright 2002-2012 Wilmer van der Gaast and others                *
     4  * Copyright 2002-2013 Wilmer van der Gaast and others                *
    55  \********************************************************************/
    66
     
    6969        req->request_length = strlen( request );
    7070        req->redir_ttl = 3;
     71        req->content_length = -1;
    7172       
    7273        if( getenv( "BITLBEE_DEBUG" ) )
     
    9697        request = g_strdup_printf( "GET %s HTTP/1.0\r\n"
    9798                                   "Host: %s\r\n"
    98                                    "Connection: close\r\n"
    9999                                   "User-Agent: BitlBee " BITLBEE_VERSION " " ARCH "/" CPU "\r\n"
    100100                                   "\r\n", url->file, url->host );
     
    193193}
    194194
     195typedef enum {
     196        CR_OK,
     197        CR_EOF,
     198        CR_ERROR,
     199        CR_ABORT,
     200} http_ret_t;
     201
    195202static 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 );
    196205
    197206static gboolean http_incoming_data( gpointer data, int source, b_input_condition cond )
     
    199208        struct http_request *req = data;
    200209        char buffer[4096];
    201         char *s;
    202         size_t content_length;
    203210        int st;
    204211       
     
    244251        }
    245252       
    246         if( st > 0 && !req->sbuf )
    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 );
    250                 req->bytes_read += st;
    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;
    284                         req->sbuf[req->sblen] = '\0';
    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 );
    291         }
     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;
    292271       
    293272        if( ssl_pending( req->ssl ) )
     
    311290                goto cleanup;
    312291        }
    313        
    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         }
    321292
    322293cleanup:
     
    326297                closesocket( req->fd );
    327298       
    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 );
     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        }
    339305       
    340306        if( getenv( "BITLBEE_DEBUG" ) && req )
     
    347313}
    348314
     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. :-( */
     332        chunk = req->cbuf;
     333        eos = req->cbuf + req->cblen;
     334        while( TRUE )
     335        {
     336                int clen = 0;
     337               
     338                /* Might be a \r\n from the last chunk. */
     339                s = chunk;
     340                while( isspace( *s ) )
     341                        s ++;
     342                /* Chunk length. Might be incomplete. */
     343                if( s < eos && sscanf( s, "%x", &clen ) != 1 )
     344                        return CR_ERROR;
     345                while( isxdigit( *s ) )
     346                        s ++;
     347               
     348                /* If we read anything here, it *must* be \r\n. */
     349                if( strncmp( s, "\r\n", MIN( 2, eos - s ) ) != 0 )
     350                        return CR_ERROR;
     351                s += 2;
     352               
     353                if( s >= eos )
     354                        break;
     355               
     356                /* 0-length chunk means end of response. */     
     357                if( clen == 0 )
     358                        return CR_EOF;
     359               
     360                if( s + clen > eos )
     361                        break;
     362                if( http_process_data( req, s, clen ) != CR_OK )
     363                        return CR_ABORT;
     364               
     365                chunk = s + clen;
     366        }
     367       
     368        if( chunk != req->cbuf )
     369        {
     370                req->cblen = eos - chunk;
     371                s = g_memdup( chunk, req->cblen + 1 );
     372                g_free( req->cbuf );
     373                req->cbuf = s;
     374        }
     375       
     376        return CR_OK;
     377}
     378
     379static http_ret_t http_process_data( struct http_request *req, const char *buffer, int len )
     380{
     381        if( len <= 0 )
     382                return CR_OK;
     383       
     384        if( !req->reply_body )
     385        {
     386                req->reply_headers = g_realloc( req->reply_headers, req->bytes_read + len + 1 );
     387                memcpy( req->reply_headers + req->bytes_read, buffer, len );
     388                req->bytes_read += len;
     389                req->reply_headers[req->bytes_read] = '\0';
     390               
     391                if( strstr( req->reply_headers, "\r\n\r\n" ) ||
     392                    strstr( req->reply_headers, "\n\n" ) )
     393                {
     394                        /* We've now received all headers. Look for something
     395                           interesting. */
     396                        if( !http_handle_headers( req ) )
     397                                return CR_ABORT;
     398                       
     399                        /* Start parsing the body as chunked if required. */
     400                        if( req->flags & HTTPC_CHUNKED )
     401                                return http_process_chunked_data( req, NULL, 0 );
     402                }
     403        }
     404        else
     405        {
     406                int pos = req->reply_body - req->sbuf;
     407                req->sbuf = g_realloc( req->sbuf, req->sblen + len + 1 );
     408                memcpy( req->sbuf + req->sblen, buffer, len );
     409                req->bytes_read += len;
     410                req->sblen += len;
     411                req->sbuf[req->sblen] = '\0';
     412                req->reply_body = req->sbuf + pos;
     413                req->body_size = req->sblen - pos;
     414        }
     415       
     416        if( ( req->flags & HTTPC_STREAMING ) && req->reply_body )
     417                req->func( req );
     418       
     419        return CR_OK;
     420}
     421
    349422/* Splits headers and body. Checks result code, in case of 300s it'll handle
    350423   redirects. If this returns FALSE, don't call any callbacks! */
    351424static gboolean http_handle_headers( struct http_request *req )
    352425{
    353         char *end1, *end2;
     426        char *end1, *end2, *s;
    354427        int evil_server = 0;
    355428       
     
    377450        }
    378451       
    379         *end1 = 0;
     452        *end1 = '\0';
    380453       
    381454        if( getenv( "BITLBEE_DEBUG" ) )
     
    387460                req->reply_body = end1 + 2;
    388461       
    389         req->body_size = req->reply_headers + req->bytes_read - req->reply_body;
     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 );
    390466       
    391467        if( ( end1 = strchr( req->reply_headers, ' ' ) ) != NULL )
     
    452528                           don't need this yet anyway, I won't implement it. */
    453529                       
    454                         req->status_string = g_strdup( "Can't handle recursive redirects" );
     530                        req->status_string = g_strdup( "Can't handle relative redirects" );
    455531                       
    456532                        return TRUE;
     
    460536                        /* A whole URL */
    461537                        url_t *url;
    462                         char *s;
     538                        char *s, *version, *headers;
    463539                        const char *new_method;
    464540                       
     
    488564                                return TRUE;
    489565                        }
     566                        headers = s;
    490567                       
    491568                        /* More or less HTTP/1.0 compliant, from my reading of RFC 2616.
     
    507584                                new_method = "POST";
    508585                       
     586                        if( ( version = strstr( req->request, " HTTP/" ) ) &&
     587                            ( s = strstr( version, "\r\n" ) ) )
     588                        {
     589                                version ++;
     590                                version = g_strndup( version, s - version );
     591                        }
     592                        else
     593                                version = g_strdup( "HTTP/1.0" );
     594                       
    509595                        /* 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 );
     596                        new_request = g_strdup_printf( "%s %s %s\r\nHost: %s%s",
     597                                                       new_method, url->file, version,
     598                                                       url->host, headers );
    512599                       
    513600                        new_host = g_strdup( url->host );
     
    521608                       
    522609                        g_free( url );
     610                        g_free( version );
    523611                }
    524612               
     
    564652                return FALSE;
    565653        }
     654
     655        if( ( s = get_rfc822_header( req->reply_headers, "Content-Length", 0 ) ) &&
     656            sscanf( s, "%d", &req->content_length ) != 1 )
     657                req->content_length = -1;
     658        g_free( s );
     659       
     660        if( ( s = get_rfc822_header( req->reply_headers, "Transfer-Encoding", 0 ) ) )
     661        {
     662                if( strcasestr( s, "chunked" ) )
     663                {
     664                        req->flags |= HTTPC_CHUNKED;
     665                        req->cbuf = req->sbuf;
     666                        req->cblen = req->sblen;
     667                       
     668                        req->reply_body = req->sbuf = g_strdup( "" );
     669                        req->body_size = req->sblen = 0;
     670                }
     671                g_free( s );
     672        }
    566673       
    567674        return TRUE;
     
    607714        g_free( req->status_string );
    608715        g_free( req->sbuf );
     716        g_free( req->cbuf );
    609717        g_free( req );
    610718}
Note: See TracChangeset for help on using the changeset viewer.