source: trunk/libtransmission/http.c @ 2334

Last change on this file since 2334 was 2334, checked in by charles, 15 years ago

second draft of a fix for the tracker communication bug reported by TMT

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