Changeset ab19567


Ignore:
Timestamp:
2013-06-16T12:15:15Z (11 years ago)
Author:
Wilmer van der Gaast <wilmer@…>
Branches:
master
Children:
1a2c1c0
Parents:
41a94dd (diff), dd7b931 (diff)
Note: this is a merge changeset, the changes displayed below correspond to the merge itself.
Use the (diff) links above to see all the changes relative to each parent.
Message:

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

Files:
7 edited

Legend:

Unmodified
Added
Removed
  • lib/http_client.c

    r41a94dd rab19567  
    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. :-( 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
    349424/* Splits headers and body. Checks result code, in case of 300s it'll handle
    350425   redirects. If this returns FALSE, don't call any callbacks! */
    351426static gboolean http_handle_headers( struct http_request *req )
    352427{
    353         char *end1, *end2;
     428        char *end1, *end2, *s;
    354429        int evil_server = 0;
    355430       
     
    377452        }
    378453       
    379         *end1 = 0;
     454        *end1 = '\0';
    380455       
    381456        if( getenv( "BITLBEE_DEBUG" ) )
     
    387462                req->reply_body = end1 + 2;
    388463       
    389         req->body_size = req->reply_headers + req->bytes_read - req->reply_body;
     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 );
    390468       
    391469        if( ( end1 = strchr( req->reply_headers, ' ' ) ) != NULL )
     
    452530                           don't need this yet anyway, I won't implement it. */
    453531                       
    454                         req->status_string = g_strdup( "Can't handle recursive redirects" );
     532                        req->status_string = g_strdup( "Can't handle relative redirects" );
    455533                       
    456534                        return TRUE;
     
    460538                        /* A whole URL */
    461539                        url_t *url;
    462                         char *s;
     540                        char *s, *version, *headers;
    463541                        const char *new_method;
    464542                       
     
    488566                                return TRUE;
    489567                        }
     568                        headers = s;
    490569                       
    491570                        /* More or less HTTP/1.0 compliant, from my reading of RFC 2616.
     
    507586                                new_method = "POST";
    508587                       
     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                       
    509597                        /* 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 );
     598                        new_request = g_strdup_printf( "%s %s %s\r\nHost: %s%s",
     599                                                       new_method, url->file, version,
     600                                                       url->host, headers );
    512601                       
    513602                        new_host = g_strdup( url->host );
     
    521610                       
    522611                        g_free( url );
     612                        g_free( version );
    523613                }
    524614               
     
    557647                g_free( req->request );
    558648                g_free( req->reply_headers );
     649                g_free( req->sbuf );
    559650                req->request = new_request;
    560651                req->request_length = strlen( new_request );
    561652                req->bytes_read = req->bytes_written = req->inpa = 0;
    562653                req->reply_headers = req->reply_body = NULL;
     654                req->sbuf = req->cbuf = NULL;
     655                req->sblen = req->cblen = 0;
    563656               
    564657                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 );
    565677        }
    566678       
     
    607719        g_free( req->status_string );
    608720        g_free( req->sbuf );
     721        g_free( req->cbuf );
    609722        g_free( req );
    610723}
  • lib/http_client.h

    r41a94dd rab19567  
    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 rab19567  
    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 rab19567  
    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 rab19567  
    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()) {
  • protocols/twitter/twitter.c

    r41a94dd rab19567  
    289289        char *def_url;
    290290        char *def_tul;
     291        char *def_mentions;
    291292
    292293        if (strcmp(acc->prpl->name, "twitter") == 0) {
    293294                def_url = TWITTER_API_URL;
    294295                def_tul = "20";
     296                def_mentions = "true";
    295297        } else {                /* if( strcmp( acc->prpl->name, "identica" ) == 0 ) */
    296298                def_url = IDENTICA_API_URL;
    297299                def_tul = "0";
     300                def_mentions = "false";
    298301        }
    299302
     
    308311        s->flags |= ACC_SET_OFFLINE_ONLY;
    309312
    310         s = set_add(&acc->set, "fetch_mentions", "true", set_eval_bool, acc);
     313        s = set_add(&acc->set, "fetch_mentions", def_mentions, set_eval_bool, acc);
    311314
    312315        s = set_add(&acc->set, "message_length", "140", set_eval_int, acc);
  • protocols/twitter/twitter_http.c

    r41a94dd rab19567  
    7878       
    7979        // Make the request.
    80         g_string_printf(request, "%s %s%s%s%s HTTP/1.0\r\n"
     80        g_string_printf(request, "%s %s%s%s%s HTTP/1.1\r\n"
    8181                        "Host: %s\r\n"
    8282                        "User-Agent: BitlBee " BITLBEE_VERSION " " ARCH "/" CPU "\r\n",
Note: See TracChangeset for help on using the changeset viewer.