source: trunk/libtransmission/web.c @ 10033

Last change on this file since 10033 was 10033, checked in by charles, 12 years ago

(trunk libT) fix DNS timing issue reported by gn0s1s in irc

  • Property svn:keywords set to Date Rev Author Id
File size: 23.3 KB
Line 
1/*
2 * This file Copyright (C) 2008-2010 Mnemosyne LLC
3 *
4 * This file is licensed by the GPL version 2.  Works owned by the
5 * Transmission project are granted a special exemption to clause 2(b)
6 * so that the bulk of its code can remain under the MIT license.
7 * This exemption does not extend to derived works not owned by
8 * the Transmission project.
9 *
10 * $Id: web.c 10033 2010-01-28 18:25:18Z charles $
11 */
12
13#include <assert.h>
14
15#include <curl/curl.h>
16#include <event.h>
17#include <evdns.h>
18
19#include "transmission.h"
20#include "net.h"
21#include "ptrarray.h"
22#include "session.h"
23#include "trevent.h"
24#include "utils.h"
25#include "version.h"
26#include "web.h"
27
28enum
29{
30    TR_MEMORY_TRASH = 0xCC,
31
32    DEFAULT_TIMER_MSEC = 1500, /* arbitrary */
33
34    MIN_DNS_CACHE_TIME = 60 * 60 * 24
35};
36
37#if 0
38#define dbgmsg(...) \
39    do { \
40        fprintf( stderr, __VA_ARGS__ ); \
41        fprintf( stderr, "\n" ); \
42    } while( 0 )
43#else
44#define dbgmsg( ... ) \
45    do { \
46        if( tr_deepLoggingIsActive( ) ) \
47            tr_deepLog( __FILE__, __LINE__, "web", __VA_ARGS__ ); \
48    } while( 0 )
49#endif
50
51/***
52****
53***/
54
55struct tr_web
56{
57    tr_bool closing;
58    tr_bool haveAddr;
59    int taskCount;
60    long timer_msec;
61    CURLM * multi;
62    tr_session * session;
63    tr_address addr;
64    tr_ptrArray dns_cache;
65    struct event timer_event;
66};
67
68struct dns_cache_item;
69static void dns_cache_item_free( struct dns_cache_item * );
70
71static void
72web_free( tr_web * g )
73{
74    evdns_shutdown( TRUE );
75    curl_multi_cleanup( g->multi );
76    evtimer_del( &g->timer_event );
77    tr_ptrArrayDestruct( &g->dns_cache, (PtrArrayForeachFunc)dns_cache_item_free );
78    memset( g, TR_MEMORY_TRASH, sizeof( struct tr_web ) );
79    tr_free( g );
80}
81
82/***
83****
84***/
85
86struct tr_web_task
87{
88    int port;
89    unsigned long tag;
90    struct curl_slist * slist;
91    struct evbuffer * response;
92    char * url;
93    char * host;
94    const char * resolved_host;
95    char * range;
96    tr_session * session;
97    tr_web_done_func * done_func;
98    void * done_func_user_data;
99    struct event timer_event;
100    CURL * easy;
101    CURLM * multi;
102    tr_bool timer_event_isSet;
103};
104
105static void
106task_free( struct tr_web_task * task )
107{
108    if( task->slist != NULL )
109        curl_slist_free_all( task->slist );
110    if( task->timer_event_isSet )
111        evtimer_del( &task->timer_event );
112    evbuffer_free( task->response );
113    tr_free( task->host );
114    tr_free( task->range );
115    tr_free( task->url );
116    memset( task, TR_MEMORY_TRASH, sizeof( struct tr_web_task ) );
117    tr_free( task );
118}
119
120/***
121****
122***/
123
124struct dns_cache_item
125{
126    char * host;
127    char * resolved_host;
128    time_t expiration;
129    tr_bool success;
130};
131
132static void
133dns_cache_item_free( struct dns_cache_item * item )
134{
135    if( item != NULL )
136    {
137        tr_free( item->host );
138        tr_free( item->resolved_host );
139        memset( item, TR_MEMORY_TRASH, sizeof( struct dns_cache_item ) );
140        tr_free( item );
141    }
142}
143
144static int
145dns_cache_compare( const void * va, const void * vb )
146{
147    const struct dns_cache_item * a = va;
148    const struct dns_cache_item * b = vb;
149    return strcmp( a->host, b->host );
150}
151
152typedef enum
153{
154    TR_DNS_OK,
155    TR_DNS_FAIL,
156    TR_DNS_UNTESTED
157}
158tr_dns_result;
159
160static void
161dns_cache_clear_entry( struct tr_ptrArray * cache, const char * host )
162{
163    struct dns_cache_item key;
164    key.host = (char*) host;
165    dns_cache_item_free( tr_ptrArrayRemoveSorted( cache, &key, dns_cache_compare ) );
166}
167
168static tr_dns_result
169dns_cache_lookup( struct tr_web_task * task, const char * host, const char ** resolved )
170{
171    tr_dns_result result = TR_DNS_UNTESTED;
172
173    if( task->session->web != NULL )
174    {
175        struct dns_cache_item key;
176        struct dns_cache_item * item;
177        tr_ptrArray * cache = &task->session->web->dns_cache;
178
179        key.host = (char*) host;
180        item = tr_ptrArrayFindSorted( cache, &key, dns_cache_compare );
181
182        /* has the ttl expired? */
183        if( ( item != NULL ) && ( item->expiration <= tr_time( ) ) )
184        {
185            dns_cache_clear_entry( cache, host );
186            item = NULL;
187        }
188
189        if( item != NULL )
190        {
191            result = item->success ? TR_DNS_OK : TR_DNS_FAIL;
192
193            if( result == TR_DNS_OK )
194            {
195                *resolved = item->resolved_host;
196           
197                dbgmsg( "found cached dns entry for \"%s\": %s", host, *resolved );
198            }
199        }
200    }
201
202    return result;
203}
204
205static void
206dns_cache_set_fail( struct tr_web_task * task, const char * host )
207{
208    if( task->session->web != NULL )
209    {
210        struct dns_cache_item * item;
211        tr_ptrArray * cache = &task->session->web->dns_cache;
212
213        dns_cache_clear_entry( cache, host );
214
215        item = tr_new( struct dns_cache_item, 1 );
216        item->host = tr_strdup( host );
217        item->resolved_host = NULL;
218        item->expiration = tr_time( ) + MIN_DNS_CACHE_TIME;
219        item->success = FALSE;
220        tr_ptrArrayInsertSorted( cache, item, dns_cache_compare );
221    }
222}
223
224static const char*
225dns_cache_set_name( struct tr_web_task * task, const char * host,
226                   const char * resolved, int ttl )
227{
228    char * ret = NULL;
229
230    ttl = MAX( MIN_DNS_CACHE_TIME, ttl );
231
232    if( task->session->web != NULL )
233    {
234        struct dns_cache_item * item;
235        tr_ptrArray * cache = &task->session->web->dns_cache;
236
237        dns_cache_clear_entry( cache, host );
238
239        item = tr_new( struct dns_cache_item, 1 );
240        item->host = tr_strdup( host );
241        item->resolved_host = tr_strdup( resolved );
242        item->expiration = tr_time( ) + ttl;
243        item->success = TRUE;
244        tr_ptrArrayInsertSorted( cache, item, dns_cache_compare );
245        ret = item->resolved_host;
246        dbgmsg( "adding dns cache entry for \"%s\": %s", host, resolved );
247    }
248    return ret;
249}
250
251/***
252****
253***/
254
255static size_t
256writeFunc( void * ptr, size_t size, size_t nmemb, void * vtask )
257{
258    const size_t byteCount = size * nmemb;
259    struct tr_web_task * task = vtask;
260    evbuffer_add( task->response, ptr, byteCount );
261    dbgmsg( "wrote %zu bytes to task %p's buffer", byteCount, task );
262    return byteCount;
263}
264
265static int
266sockoptfunction( void * vtask, curl_socket_t fd, curlsocktype purpose UNUSED )
267{
268    struct tr_web_task * task = vtask;
269    const tr_bool isScrape = strstr( task->url, "scrape" ) != NULL;
270    const tr_bool isAnnounce = strstr( task->url, "announce" ) != NULL;
271
272    /* announce and scrape requests have tiny payloads. */
273    if( isScrape || isAnnounce )
274    {
275        const int sndbuf = 1024;
276        const int rcvbuf = isScrape ? 2048 : 3072;
277        setsockopt( fd, SOL_SOCKET, SO_SNDBUF, &sndbuf, sizeof(sndbuf) );
278        setsockopt( fd, SOL_SOCKET, SO_RCVBUF, &rcvbuf, sizeof(rcvbuf) );
279    }
280
281    /* return nonzero if this function encountered an error */
282    return 0;
283}
284
285static int
286getCurlProxyType( tr_proxy_type t )
287{
288    if( t == TR_PROXY_SOCKS4 ) return CURLPROXY_SOCKS4;
289    if( t == TR_PROXY_SOCKS5 ) return CURLPROXY_SOCKS5;
290    return CURLPROXY_HTTP;
291}
292
293static int
294getTimeoutFromURL( const char * url )
295{
296    if( strstr( url, "scrape" ) != NULL ) return 20;
297    if( strstr( url, "announce" ) != NULL ) return 45;
298    return 240;
299}
300
301static void task_timeout_cb( int fd UNUSED, short what UNUSED, void * task );
302static void task_finish( struct tr_web_task * task, long response_code );
303
304static void
305addTask( void * vtask )
306{
307    struct tr_web_task * task = vtask;
308    const tr_session * session = task->session;
309
310    if( ( session == NULL ) || ( session->web == NULL ) )
311        return;
312
313    if( !task->resolved_host )
314    {
315        dbgmsg( "couldn't resolve host for \"%s\"... task failed", task->url );
316        task_finish( task, 0 );
317    }
318    else
319    {
320        CURL * e = curl_easy_init( );
321        struct tr_web * web = session->web;
322        const int timeout = getTimeoutFromURL( task->url );
323        const long verbose = getenv( "TR_CURL_VERBOSE" ) != NULL;
324        const char * user_agent = TR_NAME "/" LONG_VERSION_STRING;
325        char * url = NULL;
326
327        /* insert the resolved host into the URL s.t. curl's DNS won't block
328         * even if -- like on most OSes -- it wasn't built with C-Ares :(
329         * "http://www.craptrackular.org/announce?key=val&key2=..." becomes
330         * "http://127.0.0.1/announce?key=val&key2=..." */
331        {
332            char * host;
333            struct evbuffer * buf = evbuffer_new( );
334            char * pch = strstr( task->url, task->host );
335            char * tail = pch + strlen( task->host );
336            evbuffer_add( buf, task->url, pch - task->url );
337            evbuffer_add_printf( buf, "%s", task->resolved_host );
338            evbuffer_add_printf( buf, "%s", tail );
339            url = tr_strndup( EVBUFFER_DATA( buf ), EVBUFFER_LENGTH( buf ) );
340            dbgmsg( "old url: \"%s\" -- new url: \"%s\"", task->url, url );
341            evbuffer_free( buf );
342
343            /* Manually add a Host: argument that refers to the true URL */
344            if( ( ( task->port <= 0 ) ) ||
345                ( ( task->port == 80 ) && !strncmp( task->url, "http://", 7 ) ) ||
346                ( ( task->port == 443 ) && !strncmp( task->url, "https://", 8 ) ) )
347                host = tr_strdup_printf( "Host: %s", task->host );
348            else
349                host = tr_strdup_printf( "Host: %s:%d", task->host, task->port );
350
351            task->slist = curl_slist_append( NULL, host );
352            curl_easy_setopt( e, CURLOPT_HTTPHEADER, task->slist );
353            tr_free( host );
354        }
355
356        dbgmsg( "adding task #%lu [%s]", task->tag, url ? url : task->url );
357
358        if( !task->range && session->isProxyEnabled ) {
359            curl_easy_setopt( e, CURLOPT_PROXY, session->proxy );
360            curl_easy_setopt( e, CURLOPT_PROXYAUTH, CURLAUTH_ANY );
361            curl_easy_setopt( e, CURLOPT_PROXYPORT, session->proxyPort );
362            curl_easy_setopt( e, CURLOPT_PROXYTYPE,
363                                      getCurlProxyType( session->proxyType ) );
364        }
365        if( !task->range && session->isProxyAuthEnabled ) {
366            char * str = tr_strdup_printf( "%s:%s", session->proxyUsername,
367                                                    session->proxyPassword );
368            curl_easy_setopt( e, CURLOPT_PROXYUSERPWD, str );
369            tr_free( str );
370        }
371
372        task->easy = e;
373        task->multi = web->multi;
374
375        /* use our own timeout instead of CURLOPT_TIMEOUT because the latter
376         * doesn't play nicely with curl_multi.  See curl bug #2501457 */
377        task->timer_event_isSet = TRUE;
378        evtimer_set( &task->timer_event, task_timeout_cb, task );
379        tr_timerAdd( &task->timer_event, timeout, 0 );
380
381        curl_easy_setopt( e, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4 );
382        curl_easy_setopt( e, CURLOPT_SOCKOPTFUNCTION, sockoptfunction );
383        curl_easy_setopt( e, CURLOPT_SOCKOPTDATA, task );
384        curl_easy_setopt( e, CURLOPT_WRITEDATA, task );
385        curl_easy_setopt( e, CURLOPT_WRITEFUNCTION, writeFunc );
386        curl_easy_setopt( e, CURLOPT_DNS_CACHE_TIMEOUT, MIN_DNS_CACHE_TIME );
387        curl_easy_setopt( e, CURLOPT_FOLLOWLOCATION, 1L );
388        curl_easy_setopt( e, CURLOPT_AUTOREFERER, 1L );
389        curl_easy_setopt( e, CURLOPT_FORBID_REUSE, 1L );
390        curl_easy_setopt( e, CURLOPT_MAXREDIRS, -1L );
391        curl_easy_setopt( e, CURLOPT_NOSIGNAL, 1L );
392        curl_easy_setopt( e, CURLOPT_PRIVATE, task );
393        curl_easy_setopt( e, CURLOPT_SSL_VERIFYHOST, 0L );
394        curl_easy_setopt( e, CURLOPT_SSL_VERIFYPEER, 0L );
395        curl_easy_setopt( e, CURLOPT_URL, url ? url : task->url );
396        curl_easy_setopt( e, CURLOPT_USERAGENT, user_agent );
397        curl_easy_setopt( e, CURLOPT_VERBOSE, verbose );
398        if( web->haveAddr )
399            curl_easy_setopt( e, CURLOPT_INTERFACE, tr_ntop_non_ts( &web->addr ) );
400        if( task->range )
401            curl_easy_setopt( e, CURLOPT_RANGE, task->range );
402
403        if( curl_multi_add_handle( web->multi, e ) == CURLM_OK )
404            ++web->taskCount;
405
406        tr_free( url );
407    }
408}
409
410static void
411dns_ipv6_done_cb( int err, char type, int count, int ttl, void * addresses, void * vtask )
412{
413    struct tr_web_task * task = vtask;
414
415    if( !err && task->host && ( count>0 ) && ( ttl>=0 ) && ( type==DNS_IPv6_AAAA ) )
416    {
417        int i;
418        char buf[INET6_ADDRSTRLEN+1];
419        struct in6_addr *in6_addrs = addresses;
420
421        for( i=0; i<count; ++i ) {
422            const char * b = inet_ntop(AF_INET6, &in6_addrs[i], buf,sizeof(buf));
423            if( b != NULL ) {
424                /* FIXME: is there a better way to tell which one to use if count > 1? */
425                task->resolved_host = dns_cache_set_name( task, task->host, b, ttl );
426                break;
427            }
428        }
429    }
430
431    if( task->resolved_host == NULL )
432        dns_cache_set_fail( task, task->host );
433
434    addTask( task );
435}
436
437static void
438dns_ipv4_done_cb( int err, char type, int count, int ttl, void * addresses, void * vtask )
439{
440    struct tr_web_task * task = vtask;
441
442    if( !err && task->host && ( count>0 ) && ( ttl>=0 ) && ( type==DNS_IPv4_A ) )
443    {
444        struct in_addr * in_addrs = addresses;
445        const char * resolved = inet_ntoa( in_addrs[0] );
446        task->resolved_host = dns_cache_set_name( task, task->host, resolved, ttl );
447        /* FIXME: if count > 1, is there a way to decide which is best to use? */
448    }
449
450    if( ( task->resolved_host != NULL )
451            || ( task->host == NULL )
452            || evdns_resolve_ipv6( task->host, 0, dns_ipv6_done_cb, task ) )
453        dns_ipv6_done_cb( DNS_ERR_UNKNOWN, DNS_IPv6_AAAA, 0, 0, NULL, task );
454}
455
456static void
457doDNS( void * vtask )
458{
459    tr_address addr;
460    int port = -1;
461    char * host = NULL;
462    struct tr_web_task * task = vtask;
463    tr_dns_result lookup_result = TR_DNS_UNTESTED;
464
465    assert( task->resolved_host == NULL );
466
467    if( !tr_httpParseURL( task->url, -1, &host, &port, NULL ) )
468    {
469        task->port = port;
470        task->host = host;
471
472        /* If 'host' is an IPv4 or IPv6 address in text form, use it as-is.
473         * Otherwise, see if its resolved name is in our DNS cache */
474        if( tr_pton( task->host, &addr ) != NULL )
475        {
476            task->resolved_host = task->host;
477            lookup_result = TR_DNS_OK;
478        }
479        else
480        {
481            lookup_result = dns_cache_lookup( task, host, &task->resolved_host );
482        }
483    }
484
485    if( lookup_result != TR_DNS_UNTESTED )
486    {
487        addTask( task );
488    }
489    else if( !host || evdns_resolve_ipv4( host, 0, dns_ipv4_done_cb, task ) )
490    {
491        dns_ipv4_done_cb( DNS_ERR_UNKNOWN, DNS_IPv4_A, 0, 0, NULL, task );
492    }
493}
494
495/***
496****
497***/
498
499static void
500task_finish( struct tr_web_task * task, long response_code )
501{
502    dbgmsg( "finished web task %lu; got %ld", task->tag, response_code );
503
504    if( task->done_func != NULL )
505        task->done_func( task->session,
506                         response_code,
507                         EVBUFFER_DATA( task->response ),
508                         EVBUFFER_LENGTH( task->response ),
509                         task->done_func_user_data );
510    task_free( task );
511}
512
513static void
514remove_task( struct tr_web_task * task )
515{
516    long code;
517
518    curl_easy_getinfo( task->easy, CURLINFO_RESPONSE_CODE, &code );
519    curl_multi_remove_handle( task->multi, task->easy );
520    curl_easy_cleanup( task->easy );
521    task_finish( task, code );
522}
523
524static void
525task_timeout_cb( int fd UNUSED, short what UNUSED, void * task )
526{
527    remove_task( task );
528}
529
530static void
531remove_finished_tasks( tr_web * g )
532{
533    CURLMsg * msg;
534    int msgs_left;
535
536    while(( msg = curl_multi_info_read( g->multi, &msgs_left ))) {
537        if(( msg->msg == CURLMSG_DONE ) && ( msg->easy_handle != NULL )) {
538            struct tr_web_task * task;
539            CURL * e = msg->easy_handle;
540            curl_easy_getinfo( e, CURLINFO_PRIVATE, (void*)&task );
541            assert( e == task->easy );
542            remove_task( task );
543        }
544    }
545}
546
547static void
548restart_timer( tr_web * g )
549{
550    dbgmsg( "adding a timeout for %.1f seconds from now", g->timer_msec/1000.0 );
551    evtimer_del( &g->timer_event );
552    tr_timerAddMsec( &g->timer_event, g->timer_msec );
553}
554
555static void
556tr_multi_perform( tr_web * g, int fd, int curl_what )
557{
558    CURLMcode m;
559
560    dbgmsg( "check_run_count: %d taskCount", g->taskCount );
561
562    /* invoke libcurl's processing */
563    do
564        m = curl_multi_socket_action( g->multi, fd, curl_what, &g->taskCount );
565    while( m == CURLM_CALL_MULTI_SOCKET );
566
567    remove_finished_tasks( g );
568
569    if( g->closing && !g->taskCount )
570        web_free( g );
571    else
572        restart_timer( g );
573}
574
575/* libevent says that sock is ready to be processed, so wake up libcurl */
576static void
577event_cb( int fd, short ev_what, void * g )
578{
579    int curl_what = 0;
580    if( ev_what & EV_READ ) curl_what |= CURL_POLL_IN;
581    if( ev_what & EV_WRITE ) curl_what |= CURL_POLL_OUT;
582    tr_multi_perform( g, fd, curl_what );
583}
584
585/* CURLMOPT_SOCKETFUNCTION */
586static int
587sock_cb( CURL * e UNUSED, curl_socket_t fd, int curl_what,
588         void * vweb, void * vevent )
589{
590    /*static int num_events = 0;*/
591    struct tr_web * web = vweb;
592    struct event * io_event = vevent;
593    dbgmsg( "sock_cb: curl_what %d, fd %d, io_event %p",
594            curl_what, (int)fd, io_event );
595
596    if( io_event != NULL )
597        event_del( io_event );
598
599    if( curl_what & ( CURL_POLL_IN | CURL_POLL_OUT ) )
600    {
601        const short ev_what = EV_PERSIST
602                           | (( curl_what & CURL_POLL_IN ) ? EV_READ : 0 )
603                           | (( curl_what & CURL_POLL_OUT ) ? EV_WRITE : 0 );
604
605        if( io_event == NULL ) {
606            io_event = tr_new0( struct event, 1 );
607            curl_multi_assign( web->multi, fd, io_event );
608            /*fprintf( stderr, "+1 io_events to %d\n", ++num_events );*/
609        }
610
611        dbgmsg( "enabling (libevent %hd, libcurl %d) on io_event %p, fd %d",
612                ev_what, curl_what, io_event, fd );
613        event_set( io_event, fd, ev_what, event_cb, web );
614        assert( io_event->ev_base != NULL );
615        event_add( io_event, NULL );
616    }
617
618    if( ( io_event != NULL ) && ( curl_what & CURL_POLL_REMOVE ) )
619    {
620        memset( io_event, TR_MEMORY_TRASH, sizeof( struct event ) );
621        tr_free( io_event );
622        /*fprintf( stderr, "-1 io_events to %d\n", --num_events );*/
623    }
624
625    return 0; /* libcurl documentation: "The callback MUST return 0." */
626}
627
628/* libevent says that timer_msec have passed, so wake up libcurl */
629static void
630libevent_timer_cb( int fd UNUSED, short what UNUSED, void * g )
631{
632    dbgmsg( "libevent timer is done" );
633    tr_multi_perform( g, CURL_SOCKET_TIMEOUT, 0 );
634}
635
636/* libcurl documentation: "If 0, it means you should proceed immediately
637 * without waiting for anything. If it returns -1, there's no timeout at all
638 * set ... (but) you must not wait too long (more than a few seconds perhaps)
639 * before you call curl_multi_perform() again."  */
640static void
641multi_timer_cb( CURLM * multi UNUSED, long timer_msec, void * vg )
642{
643    tr_web * g = vg;
644
645    g->timer_msec = timer_msec > 0 ? timer_msec : DEFAULT_TIMER_MSEC;
646
647    if( timer_msec < 1 )
648        tr_multi_perform( g, CURL_SOCKET_TIMEOUT, 0 );
649    else
650        restart_timer( g );
651}
652
653/****
654*****
655****/
656
657void
658tr_webRun( tr_session         * session,
659           const char         * url,
660           const char         * range,
661           tr_web_done_func     done_func,
662           void               * done_func_user_data )
663{
664    if( session->web != NULL )
665    {
666        static unsigned long tag = 0;
667        struct tr_web_task * task = tr_new0( struct tr_web_task, 1 );
668        task->session = session;
669        task->url = tr_strdup( url );
670        task->range = tr_strdup( range );
671        task->done_func = done_func;
672        task->done_func_user_data = done_func_user_data;
673        task->tag = ++tag;
674        task->response = evbuffer_new( );
675        tr_runInEventThread( session, doDNS, task );
676    }
677}
678
679void
680tr_webSetInterface( tr_web * web, const tr_address * addr )
681{
682    if(( web->haveAddr = ( addr != NULL )))
683        web->addr = *addr;
684}
685
686tr_web*
687tr_webInit( tr_session * session )
688{
689    tr_web * web;
690
691    /* try to enable ssl for https support; but if that fails,
692     * try a plain vanilla init */
693    if( curl_global_init( CURL_GLOBAL_SSL ) )
694        curl_global_init( 0 );
695
696    web = tr_new0( struct tr_web, 1 );
697    web->dns_cache = TR_PTR_ARRAY_INIT;
698    web->session = session;
699    web->timer_msec = DEFAULT_TIMER_MSEC; /* overwritten by multi_timer_cb() */
700    evtimer_set( &web->timer_event, libevent_timer_cb, web );
701
702    web->multi = curl_multi_init( );
703
704    evdns_init( );
705
706    curl_multi_setopt( web->multi, CURLMOPT_SOCKETDATA, web );
707    curl_multi_setopt( web->multi, CURLMOPT_SOCKETFUNCTION, sock_cb );
708    curl_multi_setopt( web->multi, CURLMOPT_TIMERDATA, web );
709    curl_multi_setopt( web->multi, CURLMOPT_TIMERFUNCTION, multi_timer_cb );
710
711    return web;
712}
713
714void
715tr_webClose( tr_web ** web_in )
716{
717    tr_web * web = *web_in;
718    *web_in = NULL;
719    if( web->taskCount < 1 )
720        web_free( web );
721    else
722        web->closing = 1;
723}
724
725/*****
726******
727******
728*****/
729
730const char *
731tr_webGetResponseStr( long code )
732{
733    switch( code )
734    {
735        case   0: return "No Response";
736        case 101: return "Switching Protocols";
737        case 200: return "OK";
738        case 201: return "Created";
739        case 202: return "Accepted";
740        case 203: return "Non-Authoritative Information";
741        case 204: return "No Content";
742        case 205: return "Reset Content";
743        case 206: return "Partial Content";
744        case 300: return "Multiple Choices";
745        case 301: return "Moved Permanently";
746        case 302: return "Found";
747        case 303: return "See Other";
748        case 304: return "Not Modified";
749        case 305: return "Use Proxy";
750        case 306: return "(Unused)";
751        case 307: return "Temporary Redirect";
752        case 400: return "Bad Request";
753        case 401: return "Unauthorized";
754        case 402: return "Payment Required";
755        case 403: return "Forbidden";
756        case 404: return "Not Found";
757        case 405: return "Method Not Allowed";
758        case 406: return "Not Acceptable";
759        case 407: return "Proxy Authentication Required";
760        case 408: return "Request Timeout";
761        case 409: return "Conflict";
762        case 410: return "Gone";
763        case 411: return "Length Required";
764        case 412: return "Precondition Failed";
765        case 413: return "Request Entity Too Large";
766        case 414: return "Request-URI Too Long";
767        case 415: return "Unsupported Media Type";
768        case 416: return "Requested Range Not Satisfiable";
769        case 417: return "Expectation Failed";
770        case 500: return "Internal Server Error";
771        case 501: return "Not Implemented";
772        case 502: return "Bad Gateway";
773        case 503: return "Service Unavailable";
774        case 504: return "Gateway Timeout";
775        case 505: return "HTTP Version Not Supported";
776        default:  return "Unknown Error";
777    }
778}
779
780void
781tr_http_escape( struct evbuffer  * out,
782                const char * str, int len, tr_bool escape_slashes )
783{
784    int i;
785
786    if( ( len < 0 ) && ( str != NULL ) )
787        len = strlen( str );
788
789    for( i = 0; i < len; i++ ) {
790        if( str[i] == ',' || str[i] == '-' || str[i] == '.'
791            || ( '0' <= str[i] && str[i] <= '9' )
792            || ( 'A' <= str[i] && str[i] <= 'Z' )
793            || ( 'a' <= str[i] && str[i] <= 'z' )
794            || ( str[i] == '/' && !escape_slashes ) )
795            evbuffer_add( out, &str[i], 1 );
796        else
797            evbuffer_add_printf( out, "%%%02X", (unsigned)(str[i]&0xFF) );
798    }
799}
800
801char *
802tr_http_unescape( const char * str, int len )
803{
804    char * tmp = curl_unescape( str, len );
805    char * ret = tr_strdup( tmp );
806    curl_free( tmp );
807    return ret;
808}
Note: See TracBrowser for help on using the repository browser.