source: trunk/libtransmission/http.c @ 1579

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

Merge PEX branch, I hope this works.

  • Property svn:keywords set to Date Rev Author Id
File size: 21.8 KB
Line 
1/******************************************************************************
2 * $Id: http.c 1579 2007-03-23 08:28:01Z joshe $
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_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_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_dupstr( 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_dupstr( 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" CR LF
452                    "User-Agent: %s/%d.%d" CR LF
453                    "Connection: close" CR LF,
454                    http->host, TR_NAME, VERSION_MAJOR, VERSION_MINOR ) )
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
533tr_tristate_t
534tr_httpPulse( tr_http_t * http, const char ** data, int * len )
535{
536    struct in_addr addr;
537
538    switch( http->state )
539    {
540        case HTTP_STATE_CONSTRUCT:
541            if( tr_sprintf( EXPANDBUF( http->header ), "Content-length: %i"
542                            CR LF CR LF, http->body.used ) )
543            {
544                goto err;
545            }
546            if( !tr_netResolve( http->host, &addr ) )
547            {
548                http->sock = tr_netOpenTCP( addr, htons( http->port ), 1 );
549                http->state = HTTP_STATE_CONNECT;
550                break;
551            }
552            http->resolve = tr_netResolveInit( http->host );
553            if( NULL == http->resolve )
554            {
555                goto err;
556            }
557            http->state = HTTP_STATE_RESOLVE;
558            /* fallthrough */
559
560        case HTTP_STATE_RESOLVE:
561            switch( tr_netResolvePulse( http->resolve, &addr ) )
562            {
563                case TR_NET_WAIT:
564                    return TR_NET_WAIT;
565                case TR_NET_ERROR:
566                    goto err;
567                case TR_NET_OK:
568                    tr_netResolveClose( http->resolve );
569                    http->resolve = NULL;
570                    http->sock = tr_netOpenTCP( addr, htons( http->port ), 1 );
571                    http->state = HTTP_STATE_CONNECT;
572            }
573            /* fallthrough */
574
575        case HTTP_STATE_CONNECT:
576            switch( sendrequest( http ) )
577            {
578                case TR_NET_WAIT:
579                    return TR_NET_WAIT;
580                case TR_NET_ERROR:
581                    goto err;
582                case TR_NET_OK:
583                    http->state = HTTP_STATE_RECEIVE;
584            }
585            /* fallthrough */
586
587        case HTTP_STATE_RECEIVE:
588            switch( receiveresponse( http ) )
589            {
590                case TR_NET_WAIT:
591                    return TR_NET_WAIT;
592                case TR_NET_ERROR:
593                    goto err;
594                case TR_NET_OK:
595                    goto ok;
596            }
597            break;
598
599        case HTTP_STATE_DONE:
600            goto ok;
601
602        case HTTP_STATE_ERROR:
603            goto err;
604    }
605
606    return TR_NET_WAIT;
607
608  err:
609    http->state = HTTP_STATE_ERROR;
610    return TR_NET_ERROR;
611
612  ok:
613    http->state = HTTP_STATE_DONE;
614    if( NULL != data )
615    {
616        *data = http->header.buf;
617    }
618    if( NULL != len )
619    {
620        *len = http->header.used;
621    }
622    return TR_NET_OK;
623}
624
625static tr_tristate_t
626sendrequest( tr_http_t * http )
627{
628    struct buf * buf;
629    int          ret;
630
631    if( 0 == http->date )
632    {
633        http->date = tr_date();
634    }
635
636    if( 0 > http->sock || tr_date() > http->date + HTTP_TIMEOUT )
637    {
638        return TR_NET_ERROR;
639    }
640
641    buf = ( 0 < http->header.used ? &http->header : &http->body );
642    while( 0 < buf->used )
643    {
644      ret = tr_netSend( http->sock, (uint8_t *) buf->buf, buf->used );
645        if( ret & TR_NET_CLOSE )
646        {
647            return TR_NET_ERROR;
648        }
649        else if( ret & TR_NET_BLOCK )
650        {
651            return TR_NET_WAIT;
652        }
653        buf->used = 0;
654        buf = &http->body;
655    }
656
657    free( http->body.buf );
658    http->body.buf = NULL;
659    http->body.size = 0;
660    http->date = 0;
661
662    return TR_NET_OK;
663}
664
665static tr_tristate_t
666receiveresponse( tr_http_t * http )
667{
668    int    ret, before;
669    void * newbuf;
670
671    if( 0 == http->date )
672    {
673        http->date = tr_date();
674    }
675
676    before = http->header.used;
677    for(;;)
678    {
679        if( http->header.size - http->header.used < HTTP_BUFSIZE )
680        {
681            newbuf = realloc( http->header.buf,
682                              http->header.size + HTTP_BUFSIZE );
683            if( NULL == newbuf )
684            {
685                return TR_NET_ERROR;
686            }
687            http->header.buf = newbuf;
688            http->header.size += HTTP_BUFSIZE;
689        }
690
691        ret = tr_netRecv( http->sock,
692                          (uint8_t *) ( http->header.buf + http->header.used ),
693                          http->header.size - http->header.used );
694        if( ret & TR_NET_CLOSE )
695        {
696            checklength( http );
697            return TR_NET_OK;
698        }
699        else if( ret & TR_NET_BLOCK )
700        {
701            break;
702        }
703        else
704        {
705            http->header.used += ret;
706        }
707    }
708
709    if( before < http->header.used && checklength( http ) )
710    {
711        return TR_NET_OK;
712    }
713
714    if( tr_date() > HTTP_TIMEOUT + http->date )
715    {
716        return TR_NET_ERROR;
717    }
718
719    return TR_NET_WAIT;
720}
721
722static int
723checklength( tr_http_t * http )
724{
725    char * buf;
726    int    num, ii, len, lastnum;
727
728    switch( http->lengthtype )
729    {
730        case HTTP_LENGTH_UNKNOWN:
731            if( learnlength( http ) )
732            {
733                return checklength( http );
734            }
735            break;
736
737        case HTTP_LENGTH_EOF:
738            break;
739
740        case HTTP_LENGTH_FIXED:
741            if( http->header.used >= http->chunkoff + http->chunklen )
742            {
743                http->header.used = http->chunkoff + http->chunklen;
744                return 1;
745            }
746            break;
747
748        case HTTP_LENGTH_CHUNKED:
749            buf     = http->header.buf;
750            lastnum = -1;
751            while( http->header.used > http->chunkoff + http->chunklen )
752            {
753                num = http->chunkoff + http->chunklen;
754                if( lastnum == num )
755                {
756                    /* ugh, some trackers send Transfer-encoding: chunked
757                       and then don't encode the body */
758                    http->lengthtype = HTTP_LENGTH_EOF;
759                    return checklength( http );
760                }
761                lastnum = num;
762                while(  http->header.used > num && NL( buf[num] ) )
763                {
764                    num++;
765                }
766                ii = num;
767                while( http->header.used > ii && !NL( buf[ii] ) )
768                {
769                    ii++;
770                }
771                if( http->header.used > ii )
772                {
773                    /* strtol should stop at the newline */
774                    len = strtol( buf + num, NULL, 16 );
775                    if( 0 == len )
776                    {
777                        /* XXX should handle invalid length
778                               differently than 0 length chunk */
779                        http->header.used = http->chunkoff + http->chunklen;
780                        return 1;
781                    }
782                    if( http->header.used > ii + 1 )
783                    {
784                        ii += ( 0 == memcmp( buf + ii, CR LF, 2 ) ? 2 : 1 );
785                        if( http->header.used > ii )
786                        {
787                            memmove( buf + http->chunkoff + http->chunklen,
788                                     buf + ii, http->header.used - ii );
789                        }
790                        http->header.used -=
791                            ii - ( http->chunkoff + http->chunklen );
792                        http->chunkoff += http->chunklen;
793                        http->chunklen = len;
794                    }
795                }
796            }
797            break;
798    }
799
800    return 0;
801}
802
803static int
804learnlength( tr_http_t * http )
805{
806    tr_http_header_t hdr[] = {
807        { "Content-Length",    NULL, 0 },
808        /*
809          XXX this probably doesn't handle multiple encodings correctly
810          http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.41
811        */
812        { "Transfer-Encoding", NULL, 0 },
813        { NULL,                NULL, 0 }
814    };
815    const char * body;
816    char       * duped;
817
818    body = tr_httpParse( http->header.buf, http->header.used, hdr );
819    if( NULL != body )
820    {
821        if( 0 < hdr[1].len &&
822            0 == tr_strncasecmp( "chunked", hdr[1].data, hdr[1].len ) )
823        {
824            http->lengthtype = HTTP_LENGTH_CHUNKED;
825            http->chunkoff = body - http->header.buf;
826            http->chunklen = 0;
827        }
828        else if( 0 < hdr[0].len )
829        {
830            http->lengthtype = HTTP_LENGTH_FIXED;
831            http->chunkoff = body - http->header.buf;
832            duped = tr_dupstr( hdr[0].data, hdr[0].len );
833            http->chunklen = strtol( duped, NULL, 10 );
834            free( duped );
835        }
836        else
837        {
838            http->lengthtype = HTTP_LENGTH_EOF;
839        }
840        return 1;
841    }
842
843    return 0;
844}
845
846char *
847tr_httpWhatsMyAddress( tr_http_t * http )
848{
849    struct sockaddr_in sin;
850    socklen_t          size;
851    char               buf[INET_ADDRSTRLEN];
852
853    if( 0 > http->sock )
854    {
855        return NULL;
856    }
857
858    size = sizeof( sin );
859    if( 0 > getsockname( http->sock, (struct sockaddr *) &sin, &size ) )
860    {
861        return NULL;
862    }
863
864    tr_netNtop( &sin.sin_addr, buf, sizeof( buf ) );
865
866    return strdup( buf );
867}
868
869void
870tr_httpClose( tr_http_t * http )
871{
872    if( NULL != http->resolve )
873    {
874        tr_netResolveClose( http->resolve );
875    }
876    free( http->host );
877    if( 0 <= http->sock )
878    {
879        tr_netClose( http->sock );
880    }
881    free( http->header.buf );
882    free( http->body.buf );
883    free( http );
884}
Note: See TracBrowser for help on using the repository browser.