Changeset ca8037e


Ignore:
Timestamp:
2013-06-09T21:17:45Z (11 years ago)
Author:
Wilmer van der Gaast <wilmer@…>
Branches:
master
Children:
777461b
Parents:
41a94dd
Message:

Add better handling of HTTP/1.1 and/or keepalive connections. This should
let me close #641, and more importantly, prepares the Twitter module for
an upcoming API change. https://dev.twitter.com/blog/deprecating-http-1.0-streaming-api

Location:
lib
Files:
5 edited

Legend:

Unmodified
Added
Removed
  • lib/http_client.c

    r41a94dd rca8037e  
    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;
     
    508584                       
    509585                        /* 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",
     586                        new_request = g_strdup_printf( "%s %s HTTP/1.1\r\nHost: %s%s",
    511587                                                       new_method, url->file, url->host, s );
    512588                       
     
    564640                return FALSE;
    565641        }
     642
     643        if( ( s = get_rfc822_header( req->reply_headers, "Content-Length", 0 ) ) &&
     644            sscanf( s, "%d", &req->content_length ) != 1 )
     645                req->content_length = -1;
     646        g_free( s );
     647       
     648        if( ( s = get_rfc822_header( req->reply_headers, "Transfer-Encoding", 0 ) ) )
     649        {
     650                if( strcasestr( s, "chunked" ) )
     651                {
     652                        req->flags |= HTTPC_CHUNKED;
     653                        req->cbuf = req->sbuf;
     654                        req->cblen = req->sblen;
     655                       
     656                        req->reply_body = req->sbuf = g_strdup( "" );
     657                        req->body_size = req->sblen = 0;
     658                }
     659                g_free( s );
     660        }
    566661       
    567662        return TRUE;
     
    607702        g_free( req->status_string );
    608703        g_free( req->sbuf );
     704        g_free( req->cbuf );
    609705        g_free( req );
    610706}
  • lib/http_client.h

    r41a94dd rca8037e  
    4242        HTTPC_STREAMING = 1,
    4343        HTTPC_EOF = 2,
     44        HTTPC_CHUNKED = 4,
    4445       
    4546        /* Let's reserve 0x1000000+ for lib users. */
     
    7778        int bytes_written;
    7879        int bytes_read;
     80        int content_length;     /* "Content-Length:" header or -1 */
    7981       
    8082        /* Used in streaming mode. Caller should read from reply_body. */
    8183        char *sbuf;
    8284        size_t sblen;
     85       
     86        /* Chunked encoding only. Raw chunked stream is decoded from here. */
     87        char *cbuf;
     88        size_t cblen;
    8389};
    8490
  • lib/oauth.c

    r41a94dd rca8037e  
    262262                             "Content-Type: application/x-www-form-urlencoded\r\n"
    263263                             "Content-Length: %zd\r\n"
    264                              "Connection: close\r\n"
    265264                             "\r\n"
    266265                             "%s", url_p.file, url_p.host, strlen( post ), post );
  • lib/oauth2.c

    r41a94dd rca8037e  
    9696                             "Content-Type: application/x-www-form-urlencoded\r\n"
    9797                             "Content-Length: %zd\r\n"
    98                              "Connection: close\r\n"
    9998                             "\r\n"
    10099                             "%s", url_p.file, url_p.host, strlen( args_s ), args_s );
  • lib/proxy.c

    r41a94dd rca8037e  
    158158                }
    159159
    160                 event_debug("proxy_connect_none( \"%s\", %d ) = %d\n", host, port, fd);
     160                event_debug("proxy_connect_none( \"%s\", %d ) = %d\n", host, port_, fd);
    161161       
    162162                if (connect(fd, phb->gai_cur->ai_addr, phb->gai_cur->ai_addrlen) < 0 && !sockerr_again()) {
Note: See TracChangeset for help on using the changeset viewer.