source: trunk/libtransmission/http.c @ 2154

Last change on this file since 2154 was 2154, checked in by charles, 15 years ago
  • fix error checking large files reported by Gimp_
  • portability changes to pathname/filename building
  • small gratuitous changes
  • Property svn:keywords set to Date Rev Author Id
File size: 22.1 KB
Line 
1/******************************************************************************
2 * $Id: http.c 2154 2007-06-18 19:39:52Z 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 "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_strndup( 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_strndup( 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_httpIsUrl( const char * url, int len )
309{
310    if( 0 > len )
311    {
312        len = strlen( url );
313    }
314
315    /* check for protocol */
316    if( 7 > len || 0 != tr_strncasecmp( url, "http://", 7 ) )
317    {
318        return 0;
319    }
320
321    return 7;
322}
323
324int
325tr_httpParseUrl( const char * url, int len,
326                 char ** host, int * port, char ** path )
327{
328    const char * pathstart, * hostend;
329    int          ii, colon, portnum;
330    char         str[6];
331
332    if( 0 > len )
333    {
334        len = strlen( url );
335    }
336
337    ii = tr_httpIsUrl( url, len );
338    if( 0 >= ii )
339    {
340        return 1;
341    }
342    url += ii;
343    len -= ii;
344
345    /* find the hostname and port */
346    colon = -1;
347    for( ii = 0; len > ii && '/' != url[ii]; ii++ )
348    {
349        if( ':' == url[ii] )
350        {
351            colon = ii;
352        }
353    }
354    hostend = url + ( 0 > colon ? ii : colon );
355    pathstart = url + ii;
356
357    /* parse the port number */
358    portnum = HTTP_PORT;
359    if( 0 <= colon )
360    {
361        colon++;
362        memset( str, 0, sizeof( str ) );
363        memcpy( str, url + colon, MIN( (int) sizeof( str) - 1, ii - colon ) );
364        portnum = strtol( str, NULL, 10 );
365        if( 0 >= portnum || 0xffff <= portnum )
366        {
367            tr_err( "Invalid port (%i)", portnum );
368            return 1;
369        }
370    }
371
372    if( NULL != host )
373    {
374        *host = tr_strndup( url, hostend - url );
375    }
376    if( NULL != port )
377    {
378        *port = portnum;
379    }
380    if( NULL != path )
381    {
382        if( 0 < len - ( pathstart - url ) )
383        {
384            *path = tr_strndup( pathstart, len - ( pathstart - url ) );
385        }
386        else
387        {
388            *path = strdup( "/" );
389        }
390    }
391
392    return 0;
393}
394
395tr_http_t *
396tr_httpClient( int method, const char * host, int port, const char * fmt, ... )
397{
398    tr_http_t    * http;
399    va_list        ap1, ap2;
400    char         * methodstr;
401
402    http = malloc( sizeof( *http ) );
403    if( NULL == http )
404    {
405        return NULL;
406    }
407
408    memset( http, 0, sizeof( *http ) );
409    http->state      = HTTP_STATE_CONSTRUCT;
410    http->lengthtype = HTTP_LENGTH_UNKNOWN;
411    http->host       = strdup( host );
412    http->port       = port;
413    http->sock       = -1;
414
415    if( NULL == http->host || NULL == fmt )
416    {
417        goto err;
418    }
419
420    switch( method )
421    {
422        case TR_HTTP_GET:
423            methodstr = "GET";
424            break;
425        case TR_HTTP_POST:
426            methodstr = "POST";
427            break;
428        case TR_HTTP_M_POST:
429            methodstr = "M-POST";
430            break;
431        default:
432            goto err;
433    }
434    if( tr_sprintf( EXPANDBUF( http->header ), "%s ", methodstr ) )
435    {
436        goto err;
437    }
438
439    va_start( ap1, fmt );
440    va_start( ap2, fmt );
441    if( tr_vsprintf( EXPANDBUF( http->header ), fmt, ap1, ap2 ) )
442    {
443        va_end( ap2 );
444        va_end( ap1 );
445        goto err;
446    }
447    va_end( ap2 );
448    va_end( ap1 );
449
450    if( tr_sprintf( EXPANDBUF( http->header ), " HTTP/1.1" CR LF
451                    "Host: %s:%d" CR LF
452                    "User-Agent: %s/%d.%d%d" CR LF
453                    "Connection: close" CR LF,
454                    http->host, http->port, TR_NAME, VERSION_MAJOR, VERSION_MINOR, VERSION_MAINTENANCE ) )
455    {
456        goto err;
457    }
458
459    return http;
460
461  err:
462    tr_httpClose( http );
463    return NULL;
464}
465
466tr_http_t *
467tr_httpClientUrl( int method, const char * fmt, ... )
468{
469    char      * url, * host, * path;
470    int         port;
471    va_list     ap;
472    tr_http_t * ret;
473
474    va_start( ap, fmt );
475    url = NULL;
476    vasprintf( &url, fmt, ap );
477    va_end( ap );
478
479    if( tr_httpParseUrl( url, -1, &host, &port, &path ) )
480    {
481        tr_err( "Invalid HTTP URL: %s", url );
482        free( url );
483        return NULL;
484    }
485    free( url );
486
487    ret = tr_httpClient( method, host, port, "%s", path );
488    free( host );
489    free( path );
490
491    return ret;
492}
493
494void
495tr_httpAddHeader( tr_http_t * http, const char * name, const char * value )
496{
497    if( HTTP_STATE_CONSTRUCT == http->state )
498    {
499        if( tr_sprintf( EXPANDBUF( http->header ),
500                        "%s: %s" CR LF, name, value ) )
501        {
502            http->state = HTTP_STATE_ERROR;
503        }
504    }
505    else
506    {
507        assert( HTTP_STATE_ERROR == http->state );
508    }
509}
510
511void
512tr_httpAddBody( tr_http_t * http , const char * fmt , ... )
513{
514    va_list ap1, ap2;
515
516    if( HTTP_STATE_CONSTRUCT == http->state )
517    {
518        va_start( ap1, fmt );
519        va_start( ap2, fmt );
520        if( tr_vsprintf( EXPANDBUF( http->body ), fmt, ap1, ap2 ) )
521        {
522            http->state = HTTP_STATE_ERROR;
523        }
524        va_end( ap2 );
525        va_end( ap1 );
526    }
527    else
528    {
529        assert( HTTP_STATE_ERROR == http->state );
530    }
531}
532
533void
534tr_httpGetHeaders( tr_http_t * http, const char ** buf, int * len )
535{
536    *buf = http->header.buf;
537    *len = http->header.used;
538}
539
540void
541tr_httpGetBody( tr_http_t * http, const char ** buf, int * len )
542{
543    *buf = http->body.buf;
544    *len = http->body.used;
545}
546
547tr_tristate_t
548tr_httpPulse( tr_http_t * http, const char ** data, int * len )
549{
550    struct in_addr addr;
551
552    switch( http->state )
553    {
554        case HTTP_STATE_CONSTRUCT:
555            if( tr_sprintf( EXPANDBUF( http->header ), "Content-length: %i"
556                            CR LF CR LF, http->body.used ) )
557            {
558                goto err;
559            }
560            if( !tr_netResolve( http->host, &addr ) )
561            {
562                http->sock = tr_netOpenTCP( addr, htons( http->port ), 1 );
563                http->state = HTTP_STATE_CONNECT;
564                break;
565            }
566            http->resolve = tr_netResolveInit( http->host );
567            if( NULL == http->resolve )
568            {
569                goto err;
570            }
571            http->state = HTTP_STATE_RESOLVE;
572            /* fallthrough */
573
574        case HTTP_STATE_RESOLVE:
575            switch( tr_netResolvePulse( http->resolve, &addr ) )
576            {
577                case TR_NET_WAIT:
578                    return TR_NET_WAIT;
579                case TR_NET_ERROR:
580                    goto err;
581                case TR_NET_OK:
582                    tr_netResolveClose( http->resolve );
583                    http->resolve = NULL;
584                    http->sock = tr_netOpenTCP( addr, htons( http->port ), 1 );
585                    http->state = HTTP_STATE_CONNECT;
586            }
587            /* fallthrough */
588
589        case HTTP_STATE_CONNECT:
590            switch( sendrequest( http ) )
591            {
592                case TR_NET_WAIT:
593                    return TR_NET_WAIT;
594                case TR_NET_ERROR:
595                    goto err;
596                case TR_NET_OK:
597                    http->state = HTTP_STATE_RECEIVE;
598            }
599            /* fallthrough */
600
601        case HTTP_STATE_RECEIVE:
602            switch( receiveresponse( http ) )
603            {
604                case TR_NET_WAIT:
605                    return TR_NET_WAIT;
606                case TR_NET_ERROR:
607                    goto err;
608                case TR_NET_OK:
609                    goto ok;
610            }
611            break;
612
613        case HTTP_STATE_DONE:
614            goto ok;
615
616        case HTTP_STATE_ERROR:
617            goto err;
618    }
619
620    return TR_NET_WAIT;
621
622  err:
623    http->state = HTTP_STATE_ERROR;
624    return TR_NET_ERROR;
625
626  ok:
627    http->state = HTTP_STATE_DONE;
628    if( NULL != data )
629    {
630        *data = http->header.buf;
631    }
632    if( NULL != len )
633    {
634        *len = http->header.used;
635    }
636    return TR_NET_OK;
637}
638
639static tr_tristate_t
640sendrequest( tr_http_t * http )
641{
642    struct buf * buf;
643    int          ret;
644
645    if( 0 == http->date )
646    {
647        http->date = tr_date();
648    }
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      ret = tr_netSend( http->sock, (uint8_t *) buf->buf, buf->used );
659        if( ret & TR_NET_CLOSE )
660        {
661            return TR_NET_ERROR;
662        }
663        else if( ret & TR_NET_BLOCK )
664        {
665            return TR_NET_WAIT;
666        }
667        buf->used = 0;
668        buf = &http->body;
669    }
670
671    free( http->body.buf );
672    http->body.buf = NULL;
673    http->body.size = 0;
674    http->date = 0;
675
676    return TR_NET_OK;
677}
678
679static tr_tristate_t
680receiveresponse( tr_http_t * http )
681{
682    int    ret, before;
683    void * newbuf;
684
685    if( 0 == http->date )
686    {
687        http->date = tr_date();
688    }
689
690    before = http->header.used;
691    for(;;)
692    {
693        if( http->header.size - http->header.used < HTTP_BUFSIZE )
694        {
695            newbuf = realloc( http->header.buf,
696                              http->header.size + HTTP_BUFSIZE );
697            if( NULL == newbuf )
698            {
699                return TR_NET_ERROR;
700            }
701            http->header.buf = newbuf;
702            http->header.size += HTTP_BUFSIZE;
703        }
704
705        ret = tr_netRecv( http->sock,
706                          (uint8_t *) ( http->header.buf + http->header.used ),
707                          http->header.size - http->header.used );
708        if( ret & TR_NET_CLOSE )
709        {
710            checklength( http );
711            return TR_NET_OK;
712        }
713        else if( ret & TR_NET_BLOCK )
714        {
715            break;
716        }
717        else
718        {
719            http->header.used += ret;
720        }
721    }
722
723    if( before < http->header.used && checklength( http ) )
724    {
725        return TR_NET_OK;
726    }
727
728    if( tr_date() > HTTP_TIMEOUT + http->date )
729    {
730        return TR_NET_ERROR;
731    }
732
733    return TR_NET_WAIT;
734}
735
736static int
737checklength( tr_http_t * http )
738{
739    char * buf;
740    int    num, ii, len, lastnum;
741
742    switch( http->lengthtype )
743    {
744        case HTTP_LENGTH_UNKNOWN:
745            if( learnlength( http ) )
746            {
747                return checklength( http );
748            }
749            break;
750
751        case HTTP_LENGTH_EOF:
752            break;
753
754        case HTTP_LENGTH_FIXED:
755            if( http->header.used >= http->chunkoff + http->chunklen )
756            {
757                http->header.used = http->chunkoff + http->chunklen;
758                return 1;
759            }
760            break;
761
762        case HTTP_LENGTH_CHUNKED:
763            buf     = http->header.buf;
764            lastnum = -1;
765            while( http->header.used > http->chunkoff + http->chunklen )
766            {
767                num = http->chunkoff + http->chunklen;
768                if( lastnum == num )
769                {
770                    /* ugh, some trackers send Transfer-encoding: chunked
771                       and then don't encode the body */
772                    http->lengthtype = HTTP_LENGTH_EOF;
773                    return checklength( http );
774                }
775                lastnum = num;
776                while(  http->header.used > num && NL( buf[num] ) )
777                {
778                    num++;
779                }
780                ii = num;
781                while( http->header.used > ii && !NL( buf[ii] ) )
782                {
783                    ii++;
784                }
785                if( http->header.used > ii )
786                {
787                    /* strtol should stop at the newline */
788                    len = strtol( buf + num, NULL, 16 );
789                    if( 0 == len )
790                    {
791                        /* XXX should handle invalid length
792                               differently than 0 length chunk */
793                        http->header.used = http->chunkoff + http->chunklen;
794                        return 1;
795                    }
796                    if( http->header.used > ii + 1 )
797                    {
798                        ii += ( 0 == memcmp( buf + ii, CR LF, 2 ) ? 2 : 1 );
799                        if( http->header.used > ii )
800                        {
801                            memmove( buf + http->chunkoff + http->chunklen,
802                                     buf + ii, http->header.used - ii );
803                        }
804                        http->header.used -=
805                            ii - ( http->chunkoff + http->chunklen );
806                        http->chunkoff += http->chunklen;
807                        http->chunklen = len;
808                    }
809                }
810            }
811            break;
812    }
813
814    return 0;
815}
816
817static int
818learnlength( tr_http_t * http )
819{
820    tr_http_header_t hdr[] = {
821        { "Content-Length",    NULL, 0 },
822        /*
823          XXX this probably doesn't handle multiple encodings correctly
824          http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.41
825        */
826        { "Transfer-Encoding", NULL, 0 },
827        { NULL,                NULL, 0 }
828    };
829    const char * body;
830    char       * duped;
831
832    body = tr_httpParse( http->header.buf, http->header.used, hdr );
833    if( NULL != body )
834    {
835        if( 0 < hdr[1].len &&
836            0 == tr_strncasecmp( "chunked", hdr[1].data, hdr[1].len ) )
837        {
838            http->lengthtype = HTTP_LENGTH_CHUNKED;
839            http->chunkoff = body - http->header.buf;
840            http->chunklen = 0;
841        }
842        else if( 0 < hdr[0].len )
843        {
844            http->lengthtype = HTTP_LENGTH_FIXED;
845            http->chunkoff = body - http->header.buf;
846            duped = tr_strndup( hdr[0].data, hdr[0].len );
847            http->chunklen = strtol( duped, NULL, 10 );
848            free( duped );
849        }
850        else
851        {
852            http->lengthtype = HTTP_LENGTH_EOF;
853        }
854        return 1;
855    }
856
857    return 0;
858}
859
860char *
861tr_httpWhatsMyAddress( tr_http_t * http )
862{
863    struct sockaddr_in sin;
864    socklen_t          size;
865    char               buf[INET_ADDRSTRLEN];
866
867    if( 0 > http->sock )
868    {
869        return NULL;
870    }
871
872    size = sizeof( sin );
873    if( 0 > getsockname( http->sock, (struct sockaddr *) &sin, &size ) )
874    {
875        return NULL;
876    }
877
878    tr_netNtop( &sin.sin_addr, buf, sizeof( buf ) );
879
880    return strdup( buf );
881}
882
883void
884tr_httpClose( tr_http_t * http )
885{
886    if( NULL != http->resolve )
887    {
888        tr_netResolveClose( http->resolve );
889    }
890    free( http->host );
891    if( 0 <= http->sock )
892    {
893        tr_netClose( http->sock );
894    }
895    free( http->header.buf );
896    free( http->body.buf );
897    free( http );
898}
Note: See TracBrowser for help on using the repository browser.