source: trunk/libtransmission/http.c @ 920

Last change on this file since 920 was 920, checked in by joshe, 15 years ago

Merge nat-traversal branch to trunk.

  • Property svn:keywords set to Date Rev Author Id
File size: 20.5 KB
Line 
1/******************************************************************************
2 * $Id: http.c 920 2006-09-25 18:37:45Z joshe $
3 *
4 * Copyright (c) 2006 Transmission authors and contributors
5 *
6 * Permission is hereby granted, free of charge, to any person obtaining a
7 * copy of this software and associated documentation files (the "Software"),
8 * to deal in the Software without restriction, including without limitation
9 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
10 * and/or sell copies of the Software, and to permit persons to whom the
11 * Software is furnished to do so, subject to the following conditions:
12 *
13 * The above copyright notice and this permission notice shall be included in
14 * all copies or substantial portions of the Software.
15 *
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
22 * DEALINGS IN THE SOFTWARE.
23 *****************************************************************************/
24
25#include "transmission.h"
26
27#define HTTP_PORT               80      /* default http port 80 */
28#define HTTP_TIMEOUT            60000   /* one minute http timeout */
29#define HTTP_BUFSIZE            1500    /* 1.5K buffer size increment */
30#define LF                      "\012"
31#define CR                      "\015"
32#define SP( cc )                ( ' ' == (cc)  || '\t' == (cc) )
33#define NL( cc )                ( '\015' == (cc) || '\012' == (cc) )
34#define NUM( cc )               ( '0' <= (cc) && '9' >= (cc) )
35#define ALEN( aa )              ( (int)(sizeof( (aa) ) / sizeof( (aa)[0] ) ) )
36#define SKIP( off, len, done ) \
37    while( (off) < (len) && (done) ) { (off)++; };
38
39static const char *
40slice( const char * data, int * len, const char * delim );
41static tr_tristate_t
42sendrequest( tr_http_t * http );
43static tr_tristate_t
44receiveresponse( tr_http_t * http );
45static int
46checklength( tr_http_t * http );
47static int
48learnlength( tr_http_t * http );
49
50#define EXPANDBUF( bs )         &(bs).buf, &(bs).used, &(bs).size
51struct buf {
52    char         * buf;
53    int            size;
54    int            used;
55};
56
57struct tr_http_s {
58#define HTTP_STATE_CONSTRUCT    1
59#define HTTP_STATE_RESOLVE      2
60#define HTTP_STATE_CONNECT      3
61#define HTTP_STATE_RECEIVE      4
62#define HTTP_STATE_DONE         5
63#define HTTP_STATE_ERROR        6
64    char           state;
65#define HTTP_LENGTH_UNKNOWN     1
66#define HTTP_LENGTH_EOF         2
67#define HTTP_LENGTH_FIXED       3
68#define HTTP_LENGTH_CHUNKED     4
69    char           lengthtype;
70    tr_resolve_t * resolve;
71    char         * host;
72    int            port;
73    int            sock;
74    struct buf     header;
75    struct buf     body;
76    uint64_t       date;
77    /*
78      eof: unused
79      fixed: lenptr is the start of the body, lenint is the body length
80      chunked: lenptr is start of chunk (after length), lenint is chunk size
81    */
82    int            chunkoff;
83    int            chunklen;
84};
85
86int
87tr_httpRequestType( const char * data, int len, char ** method, char ** uri )
88{
89    const char * words[6];
90    int          ii, ret;
91    const char * end;
92
93    /* find the end of the line */
94    for( end = data; data + len > end; end++ )
95    {
96        if( NL( *data) )
97        {
98            break;
99        }
100    }
101
102    /* find the first three "words" in the line */
103    for( ii = 0; ALEN( words ) > ii && data < end; ii++ )
104    {
105        /* find the next space or non-space */
106        while( data < end && ( ii % 2 ? !SP( *data ) : SP( *data ) ) )
107        {
108            data++;
109        }
110
111        /* save the beginning of the word */
112        words[ii] = data;
113    }
114
115    /* check for missing words */
116    if( ALEN( words) > ii )
117    {
118        return -1;
119    }
120
121    /* parse HTTP version */
122    ret = -1;
123    if( 8 <= words[5] - words[4] )
124    {
125        if( 0 == tr_strncasecmp( words[4], "HTTP/1.1", 8 ) )
126        {
127            ret = 11;
128        }
129        else if( 0 == tr_strncasecmp( words[4], "HTTP/1.0", 8 ) )
130        {
131            ret = 10;
132        }
133    }
134
135    /* copy the method */
136    if( 0 <= ret && NULL != method )
137    {
138        *method = tr_dupstr( words[0], words[1] - words[0] );
139        if( NULL == *method )
140        {
141            ret = -1;
142        }
143    }
144    /* copy uri */
145    if( 0 <= ret && NULL != uri )
146    {
147        *uri = tr_dupstr( words[2], words[3] - words[2] );
148        if( NULL == *uri )
149        {
150            free( *method );
151            ret = -1;
152        }
153    }
154
155    return ret;
156}
157
158int
159tr_httpResponseCode( const char * data, int len )
160{
161    char code[4];
162    int ret;
163
164    /* check for the minimum legal length */
165    if( 12 > len ||
166    /* check for valid http version */
167        0 != tr_strncasecmp( data, "HTTP/1.", 7 ) ||
168        ( '1' != data[7] && '0' != data[7] ) ||
169    /* there should be at least one space after the version */
170        !SP( data[8] ) )
171    {
172        return -1;
173    }
174
175    /* skip any extra spaces */
176    data += 9;
177    len -= 9;
178    while( 0 < len && SP( *data ) )
179    {
180        data++;
181        len--;
182    }
183
184    /* check for a valid three-digit code */
185    if( 3 > len || !NUM( data[0] ) || !NUM( data[1] ) || !NUM( data[2] ) ||
186        ( 3 < len && NUM( data[3] ) ) )
187    {
188        return -1;
189    }
190
191    /* parse and return the code */
192    memcpy( code, data, 3 );
193    code[3] = '\0';
194    ret = strtol( code, NULL, 10 );
195    if( 100 > ret )
196    {
197        ret = -1;
198    }
199
200    return ret;
201}
202
203char *
204tr_httpParse( const char * data, int len, tr_http_header_t *headers )
205{
206    const char * body, * begin;
207    int          ii, jj, full;
208
209    /* find the end of the http headers */
210    body = slice( data, &len, CR LF CR LF );
211    if( NULL == body )
212    {
213        body = slice( data, &len, LF LF );
214        if( NULL == body )
215        {
216            body = slice( data, &len, CR CR );
217            if( NULL == body )
218            {
219                return NULL;
220            }
221        }
222    }
223
224    /* return if no headers were requested */
225    if( NULL == headers || NULL == headers[0].name )
226    {
227        return (char*) body;
228    }
229
230    /* NULL out all the header's data pointers */
231    for( ii = 0; NULL != headers[ii].name; ii++ )
232    {
233        headers[ii].data = NULL;
234        headers[ii].len = 0;
235    }
236
237    /* skip the http request or response line */
238    ii = 0;
239    SKIP( ii, len, !NL( data[ii] ) );
240    SKIP( ii, len, NL( data[ii] ) );
241
242    /* find the requested headers */
243    while(ii < len )
244    {
245        /* skip leading spaces and find the header name */
246        SKIP( ii, len, SP( data[ii] ) );
247        begin = data + ii;
248        SKIP( ii, len, ':' != data[ii] && !NL( data[ii] ) );
249
250        if( ':' == data[ii] )
251        {
252            full = 1;
253
254            /* try to match the found header with one of the requested */
255            for( jj = 0; NULL != headers[jj].name; jj++ )
256            {
257                if( NULL == headers[jj].data )
258                {
259                    full = 0;
260                    if( 0 == tr_strncasecmp( headers[jj].name, begin,
261                                             ( data + ii ) - begin ) )
262                    {
263                        ii++;
264                        /* skip leading whitespace and save the header value */
265                        SKIP( ii, len, SP( data[ii] ) );
266                        headers[jj].data = data + ii;
267                        SKIP( ii, len, !NL( data[ii] ) );
268                        headers[jj].len = ( data + ii ) - headers[jj].data;
269                        break;
270                    }
271                }
272            }
273            if( full )
274            {
275                break;
276            }
277
278            /* skip to the end of the header */
279            SKIP( ii, len, !NL( data[ii] ) );
280        }
281
282        /* skip past the newline */
283        SKIP( ii, len, NL( data[ii] ) );
284    }
285
286    return (char*)body;
287}
288
289static const char *
290slice( const char * data, int * len, const char * delim )
291{
292    const char *body;
293    int dlen;
294
295    dlen = strlen( delim );
296    body = tr_memmem( data, *len, delim, dlen );
297
298    if( NULL != body )
299    {
300        *len = body - data;
301        body += dlen;
302    }
303
304    return body;
305}
306
307int
308tr_httpParseUrl( const char * url, int len,
309                 char ** host, int * port, char ** path )
310{
311    const char * pathstart, * hostend;
312    int          ii, colon, portnum;
313    char         str[6];
314
315    if( 0 > len )
316    {
317        len = strlen( url );
318    }
319
320    /* check for protocol */
321    if( 7 > len || 0 != tr_strncasecmp( url, "http://", 7 ) )
322    {
323        tr_err( "Invalid HTTP URL" );
324        return 1;
325    }
326    url += 7;
327    len -= 7;
328
329    /* find the hostname and port */
330    colon = -1;
331    for( ii = 0; len > ii && '/' != url[ii]; ii++ )
332    {
333        if( ':' == url[ii] )
334        {
335            colon = ii;
336        }
337    }
338    hostend = url + ( 0 > colon ? ii : colon );
339    pathstart = url + ii;
340
341    /* parse the port number */
342    portnum = HTTP_PORT;
343    if( 0 <= colon )
344    {
345        colon++;
346        memset( str, 0, sizeof( str ) );
347        memcpy( str, url + colon, MIN( (int) sizeof( str) - 1, ii - colon ) );
348        portnum = strtol( str, NULL, 10 );
349        if( 0 >= portnum || 0xffff <= portnum )
350        {
351            tr_err( "Invalid port (%i)", portnum );
352            return 1;
353        }
354    }
355
356    if( NULL != host )
357    {
358        *host = tr_dupstr( url, hostend - url );
359    }
360    if( NULL != port )
361    {
362        *port = portnum;
363    }
364    if( NULL != path )
365    {
366        if( 0 < len - ( pathstart - url ) )
367        {
368            *path = tr_dupstr( pathstart, len - ( pathstart - url ) );
369        }
370        else
371        {
372            *path = strdup( "/" );
373        }
374    }
375
376    return 0;
377}
378
379tr_http_t *
380tr_httpClient( int method, const char * host, int port, const char * fmt, ... )
381{
382    tr_http_t    * http;
383    va_list        ap1, ap2;
384    char         * methodstr;
385
386    http = malloc( sizeof( *http ) );
387    if( NULL == http )
388    {
389        return NULL;
390    }
391
392    memset( http, 0, sizeof( *http ) );
393    http->state      = HTTP_STATE_CONSTRUCT;
394    http->lengthtype = HTTP_LENGTH_UNKNOWN;
395    http->host       = strdup( host );
396    http->port       = port;
397    http->sock       = -1;
398
399    if( NULL == http->host || NULL == fmt )
400    {
401        goto err;
402    }
403
404    switch( method )
405    {
406        case TR_HTTP_GET:
407            methodstr = "GET";
408            break;
409        case TR_HTTP_POST:
410            methodstr = "POST";
411            break;
412        case TR_HTTP_M_POST:
413            methodstr = "M-POST";
414            break;
415        default:
416            goto err;
417    }
418    if( tr_sprintf( EXPANDBUF( http->header ), "%s ", methodstr ) )
419    {
420        goto err;
421    }
422
423    va_start( ap1, fmt );
424    va_start( ap2, fmt );
425    if( tr_vsprintf( EXPANDBUF( http->header ), fmt, ap1, ap2 ) )
426    {
427        va_end( ap2 );
428        va_end( ap1 );
429        goto err;
430    }
431    va_end( ap2 );
432    va_end( ap1 );
433
434    if( tr_sprintf( EXPANDBUF( http->header ), " HTTP/1.1" CR LF
435                    "Host: %s" CR LF
436                    "User-Agent: Transmission/%d.%d" CR LF
437                    "Connection: close" CR LF,
438                    http->host, VERSION_MAJOR, VERSION_MINOR ) )
439    {
440        goto err;
441    }
442
443    return http;
444
445  err:
446    tr_httpClose( http );
447    return NULL;
448}
449
450void
451tr_httpAddHeader( tr_http_t * http, const char * name, const char * value )
452{
453    if( HTTP_STATE_CONSTRUCT == http->state )
454    {
455        if( tr_sprintf( EXPANDBUF( http->header ),
456                        "%s: %s" CR LF, name, value ) )
457        {
458            http->state = HTTP_STATE_ERROR;
459        }
460    }
461    else
462    {
463        assert( HTTP_STATE_ERROR == http->state );
464    }
465}
466
467void
468tr_httpAddBody( tr_http_t * http , const char * fmt , ... )
469{
470    va_list ap1, ap2;
471
472    if( HTTP_STATE_CONSTRUCT == http->state )
473    {
474        va_start( ap1, fmt );
475        va_start( ap2, fmt );
476        if( tr_vsprintf( EXPANDBUF( http->body ), fmt, ap1, ap2 ) )
477        {
478            http->state = HTTP_STATE_ERROR;
479        }
480        va_end( ap2 );
481        va_end( ap1 );
482    }
483    else
484    {
485        assert( HTTP_STATE_ERROR == http->state );
486    }
487}
488
489tr_tristate_t
490tr_httpPulse( tr_http_t * http, const char ** data, int * len )
491{
492    struct in_addr addr;
493
494    switch( http->state )
495    {
496        case HTTP_STATE_CONSTRUCT:
497            if( tr_sprintf( EXPANDBUF( http->header ), "Content-length: %i"
498                            CR LF CR LF, http->body.used ) )
499            {
500                goto err;
501            }
502            if( !tr_netResolve( http->host, &addr ) )
503            {
504                http->sock = tr_netOpenTCP( addr, htons( http->port ) );
505                http->state = HTTP_STATE_CONNECT;
506                break;
507            }
508            http->resolve = tr_netResolveInit( http->host );
509            if( NULL == http->resolve )
510            {
511                goto err;
512            }
513            http->state = HTTP_STATE_RESOLVE;
514            /* fallthrough */
515
516        case HTTP_STATE_RESOLVE:
517            switch( tr_netResolvePulse( http->resolve, &addr ) )
518            {
519                case TR_WAIT:
520                    return TR_WAIT;
521                case TR_ERROR:
522                    goto err;
523                case TR_OK:
524                    tr_netResolveClose( http->resolve );
525                    http->resolve = NULL;
526                    http->sock = tr_netOpenTCP( addr, htons( http->port ) );
527                    http->state = HTTP_STATE_CONNECT;
528            }
529            /* fallthrough */
530
531        case HTTP_STATE_CONNECT:
532            switch( sendrequest( http ) )
533            {
534                case TR_WAIT:
535                    return TR_WAIT;
536                case TR_ERROR:
537                    goto err;
538                case TR_OK:
539                    http->state = HTTP_STATE_RECEIVE;
540            }
541            /* fallthrough */
542
543        case HTTP_STATE_RECEIVE:
544            switch( receiveresponse( http ) )
545            {
546                case TR_WAIT:
547                    return TR_WAIT;
548                case TR_ERROR:
549                    goto err;
550                case TR_OK:
551                    goto ok;
552            }
553            break;
554
555        case HTTP_STATE_DONE:
556            goto ok;
557
558        case HTTP_STATE_ERROR:
559            goto err;
560    }
561
562    return TR_WAIT;
563
564  err:
565    http->state = HTTP_STATE_ERROR;
566    return TR_ERROR;
567
568  ok:
569    http->state = HTTP_STATE_DONE;
570    if( NULL != data )
571    {
572        *data = http->header.buf;
573    }
574    if( NULL != len )
575    {
576        *len = http->header.used;
577    }
578    return TR_OK;
579}
580
581static tr_tristate_t
582sendrequest( tr_http_t * http )
583{
584    struct buf * buf;
585    int          ret;
586
587    if( 0 == http->date )
588    {
589        http->date = tr_date();
590    }
591
592    if( 0 > http->sock || tr_date() > http->date + HTTP_TIMEOUT )
593    {
594        return TR_ERROR;
595    }
596
597    buf = ( 0 < http->header.used ? &http->header : &http->body );
598    while( 0 < buf->used )
599    {
600      ret = tr_netSend( http->sock, (uint8_t *) buf->buf, buf->used );
601        if( ret & TR_NET_CLOSE )
602        {
603            return TR_ERROR;
604        }
605        else if( ret & TR_NET_BLOCK )
606        {
607            return TR_WAIT;
608        }
609        buf->used = 0;
610        buf = &http->body;
611    }
612
613    free( http->body.buf );
614    http->body.buf = NULL;
615    http->body.size = 0;
616    http->date = 0;
617
618    return TR_OK;
619}
620
621static tr_tristate_t
622receiveresponse( tr_http_t * http )
623{
624    int    ret, before;
625    void * newbuf;
626
627    if( 0 == http->date )
628    {
629        http->date = tr_date();
630    }
631
632    before = http->header.used;
633    for(;;)
634    {
635        if( http->header.size - http->header.used < HTTP_BUFSIZE )
636        {
637            newbuf = realloc( http->header.buf,
638                              http->header.size + HTTP_BUFSIZE );
639            if( NULL == newbuf )
640            {
641                return TR_ERROR;
642            }
643            http->header.buf = newbuf;
644            http->header.size += HTTP_BUFSIZE;
645        }
646
647        ret = tr_netRecv( http->sock,
648                          (uint8_t *) ( http->header.buf + http->header.used ),
649                          http->header.size - http->header.used );
650        if( ret & TR_NET_CLOSE )
651        {
652            checklength( http );
653            return TR_OK;
654        }
655        else if( ret & TR_NET_BLOCK )
656        {
657            break;
658        }
659        else
660        {
661            http->header.used += ret;
662        }
663    }
664
665    if( before < http->header.used && checklength( http ) )
666    {
667        return TR_OK;
668    }
669
670    if( tr_date() > HTTP_TIMEOUT + http->date )
671    {
672        return TR_ERROR;
673    }
674
675    return TR_WAIT;
676}
677
678static int
679checklength( tr_http_t * http )
680{
681    char * buf;
682    int    num, ii, len;
683
684    switch( http->lengthtype )
685    {
686        case HTTP_LENGTH_UNKNOWN:
687            if( learnlength( http ) )
688            {
689                return checklength( http );
690            }
691            break;
692
693        case HTTP_LENGTH_EOF:
694            break;
695
696        case HTTP_LENGTH_FIXED:
697            if( http->header.used >= http->chunkoff + http->chunklen )
698            {
699                http->header.used = http->chunkoff + http->chunklen;
700                return 1;
701            }
702            break;
703
704        case HTTP_LENGTH_CHUNKED:
705            buf = http->header.buf;
706            while( http->header.used > http->chunkoff + http->chunklen )
707            {
708                num = http->chunkoff + http->chunklen;
709                while(  http->header.used > num && NL( buf[num] ) )
710                {
711                    num++;
712                }
713                ii = num;
714                while( http->header.used > ii && !NL( buf[ii] ) )
715                {
716                    ii++;
717                }
718                if( http->header.used > ii )
719                {
720                    /* strtol should stop at the newline */
721                    len = strtol( buf + num, NULL, 16 );
722                    if( 0 == len )
723                    {
724                        http->header.used = http->chunkoff + http->chunklen;
725                        return 1;
726                    }
727                    if( http->header.used > ii + 1 )
728                    {
729                        ii += ( 0 == memcmp( buf + ii, CR LF, 2 ) ? 2 : 1 );
730                        if( http->header.used > ii )
731                        {
732                            memmove( buf + http->chunkoff + http->chunklen,
733                                     buf + ii, http->header.used - ii );
734                        }
735                        http->header.used -=
736                            ii - ( http->chunkoff + http->chunklen );
737                        http->chunkoff += http->chunklen;
738                        http->chunklen = len;
739                    }
740                }
741            }
742            break;
743    }
744
745    return 0;
746}
747
748static int
749learnlength( tr_http_t * http )
750{
751    tr_http_header_t hdr[] = {
752        { "Content-Length",    NULL, 0 },
753        /*
754          XXX this probably doesn't handle multiple encodings correctly
755          http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.41
756        */
757        { "Transfer-Encoding", NULL, 0 },
758        { NULL,                NULL, 0 }
759    };
760    const char * body;
761    char       * duped;
762
763    body = tr_httpParse( http->header.buf, http->header.used, hdr );
764    if( NULL != body )
765    {
766        if( 0 < hdr[1].len &&
767            0 == tr_strncasecmp( "chunked", hdr[1].data, hdr[1].len ) )
768        {
769            http->lengthtype = HTTP_LENGTH_CHUNKED;
770            http->chunkoff = body - http->header.buf;
771            http->chunklen = 0;
772        }
773        else if( 0 < hdr[0].len )
774        {
775            http->lengthtype = HTTP_LENGTH_FIXED;
776            http->chunkoff = body - http->header.buf;
777            duped = tr_dupstr( hdr[0].data, hdr[0].len );
778            http->chunklen = strtol( duped, NULL, 10 );
779            free( duped );
780        }
781        else
782        {
783            http->lengthtype = HTTP_LENGTH_EOF;
784        }
785        return 1;
786    }
787
788    return 0;
789}
790
791char *
792tr_httpWhatsMyAddress( tr_http_t * http )
793{
794    struct sockaddr_in sin;
795    socklen_t          size;
796    char               buf[INET_ADDRSTRLEN];
797
798    if( 0 > http->sock )
799    {
800        return NULL;
801    }
802
803    size = sizeof( sin );
804    if( 0 > getsockname( http->sock, (struct sockaddr *) &sin, &size ) )
805    {
806        return NULL;
807    }
808
809    tr_netNtop( &sin.sin_addr, buf, sizeof( buf ) );
810
811    return strdup( buf );
812}
813
814void
815tr_httpClose( tr_http_t * http )
816{
817    if( NULL != http->resolve )
818    {
819        tr_netResolveClose( http->resolve );
820    }
821    free( http->host );
822    if( 0 <= http->sock )
823    {
824        tr_netClose( http->sock );
825    }
826    free( http->header.buf );
827    free( http->body.buf );
828    free( http );
829}
Note: See TracBrowser for help on using the repository browser.