source: trunk/libtransmission/web.c @ 9717

Last change on this file since 9717 was 9717, checked in by charles, 13 years ago

(trunk libT) #2416 "crash in event_queue_insert()"

  • Property svn:keywords set to Date Rev Author Id
File size: 17.8 KB
Line 
1/*
2 * This file Copyright (C) 2008-2009 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 9717 2009-12-11 15:41:34Z charles $
11 */
12
13#include <assert.h>
14#include <stdlib.h> /* bsearch */
15
16#include <event.h>
17
18#define CURL_DISABLE_TYPECHECK /* otherwise -Wunreachable-code goes insane */
19#include <curl/curl.h>
20
21#include "transmission.h"
22#include "net.h" /* socklen_t */
23#include "session.h"
24#include "trevent.h" /* tr_runInEventThread() */
25#include "utils.h"
26#include "version.h"
27#include "web.h"
28
29enum
30{
31    /* arbitrary number */
32    DEFAULT_TIMER_MSEC = 1500
33};
34
35static void
36tr_multi_perform( tr_web * g, int fd );
37
38#if 0
39#define dbgmsg(...) \
40    do { \
41        fprintf( stderr, __VA_ARGS__ ); \
42        fprintf( stderr, "\n" ); \
43    } while( 0 )
44#else
45#define dbgmsg( ... ) \
46    do { \
47        if( tr_deepLoggingIsActive( ) ) \
48            tr_deepLog( __FILE__, __LINE__, "web", __VA_ARGS__ ); \
49    } while( 0 )
50#endif
51
52struct tr_web
53{
54    tr_bool closing;
55    int prev_running;
56    int still_running;
57    long timer_msec;
58    CURLM * multi;
59    tr_session * session;
60    tr_bool haveAddr;
61    tr_address addr;
62    struct event timer_event;
63};
64
65/***
66****
67***/
68
69struct tr_web_task
70{
71    unsigned long tag;
72    struct evbuffer * response;
73    char * url;
74    char * range;
75    tr_session * session;
76    tr_web_done_func * done_func;
77    void * done_func_user_data;
78};
79
80static size_t
81writeFunc( void * ptr, size_t size, size_t nmemb, void * vtask )
82{
83    const size_t byteCount = size * nmemb;
84    struct tr_web_task * task = vtask;
85    evbuffer_add( task->response, ptr, byteCount );
86    dbgmsg( "wrote %zu bytes to task %p's buffer", byteCount, task );
87    return byteCount;
88}
89
90static int
91getCurlProxyType( tr_proxy_type t )
92{
93    switch( t )
94    {
95        case TR_PROXY_SOCKS4: return CURLPROXY_SOCKS4;
96        case TR_PROXY_SOCKS5: return CURLPROXY_SOCKS5;
97        default:              return CURLPROXY_HTTP;
98    }
99}
100
101static void
102sockoptfunction( void * vtask, curl_socket_t fd, curlsocktype purpose UNUSED )
103{
104    struct tr_web_task * task = vtask;
105    const tr_bool isScrape = strstr( task->url, "scrape" ) != NULL;
106    const tr_bool isAnnounce = strstr( task->url, "announce" ) != NULL;
107
108    /* announce and scrape requests have tiny payloads...
109     * which have very small payloads */
110    if( isScrape || isAnnounce )
111    {
112        int sndbuf = 1024;
113        int rcvbuf = isScrape ? 2048 : 3072;
114
115        if( setsockopt( fd, SOL_SOCKET, SO_SNDBUF, &sndbuf, sizeof(sndbuf) ) )
116            tr_inf( "Unable to set SO_SNDBUF on socket %d: %s", fd, tr_strerror( sockerrno ) );
117
118        if( setsockopt( fd, SOL_SOCKET, SO_RCVBUF, &rcvbuf, sizeof(rcvbuf) ) )
119            tr_inf( "Unable to set SO_RCVBUF on socket %d: %s", fd, tr_strerror( sockerrno ) );
120    }
121}
122
123static void
124addTask( void * vtask )
125{
126    struct tr_web_task * task = vtask;
127    const tr_session * session = task->session;
128
129    if( session && session->web )
130    {
131        struct tr_web * web = session->web;
132        CURL * easy;
133        long timeout;
134
135        dbgmsg( "adding task #%lu [%s]", task->tag, task->url );
136
137        easy = curl_easy_init( );
138
139        if( !task->range && session->isProxyEnabled ) {
140            curl_easy_setopt( easy, CURLOPT_PROXY, session->proxy );
141            curl_easy_setopt( easy, CURLOPT_PROXYAUTH, CURLAUTH_ANY );
142            curl_easy_setopt( easy, CURLOPT_PROXYPORT, session->proxyPort );
143            curl_easy_setopt( easy, CURLOPT_PROXYTYPE,
144                                      getCurlProxyType( session->proxyType ) );
145        }
146        if( !task->range && session->isProxyAuthEnabled ) {
147            char * str = tr_strdup_printf( "%s:%s", session->proxyUsername,
148                                                    session->proxyPassword );
149            curl_easy_setopt( easy, CURLOPT_PROXYUSERPWD, str );
150            tr_free( str );
151        }
152
153        curl_easy_setopt( easy, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4 );
154
155        /* set a time limit for announces & scrapes */
156        if( strstr( task->url, "scrape" ) != NULL )
157            timeout = 20L;
158        else if( strstr( task->url, "announce" ) != NULL )
159            timeout = 30L;
160        else
161            timeout = 240L;
162        curl_easy_setopt( easy, CURLOPT_TIMEOUT, timeout );
163        curl_easy_setopt( easy, CURLOPT_CONNECTTIMEOUT, timeout-5 );
164        dbgmsg( "new task's timeout is %ld\n", timeout );
165
166        curl_easy_setopt( easy, CURLOPT_SOCKOPTFUNCTION, sockoptfunction );
167        curl_easy_setopt( easy, CURLOPT_SOCKOPTDATA, task );
168        curl_easy_setopt( easy, CURLOPT_DNS_CACHE_TIMEOUT, 1800L );
169        curl_easy_setopt( easy, CURLOPT_FOLLOWLOCATION, 1L );
170        curl_easy_setopt( easy, CURLOPT_AUTOREFERER, 1L );
171        curl_easy_setopt( easy, CURLOPT_FORBID_REUSE, 1L );
172        curl_easy_setopt( easy, CURLOPT_MAXREDIRS, -1L );
173        curl_easy_setopt( easy, CURLOPT_NOSIGNAL, 1L );
174        curl_easy_setopt( easy, CURLOPT_PRIVATE, task );
175        curl_easy_setopt( easy, CURLOPT_SSL_VERIFYHOST, 0L );
176        curl_easy_setopt( easy, CURLOPT_SSL_VERIFYPEER, 0L );
177        curl_easy_setopt( easy, CURLOPT_URL, task->url );
178        curl_easy_setopt( easy, CURLOPT_USERAGENT,
179                                           TR_NAME "/" LONG_VERSION_STRING );
180        curl_easy_setopt( easy, CURLOPT_VERBOSE,
181                                       getenv( "TR_CURL_VERBOSE" ) != NULL );
182        if( web->haveAddr )
183            curl_easy_setopt( easy, CURLOPT_INTERFACE, tr_ntop_non_ts( &web->addr ) );
184        curl_easy_setopt( easy, CURLOPT_WRITEDATA, task );
185        curl_easy_setopt( easy, CURLOPT_WRITEFUNCTION, writeFunc );
186        if( task->range )
187            curl_easy_setopt( easy, CURLOPT_RANGE, task->range );
188        else /* don't set encoding on webseeds; it messes up binary data */
189            curl_easy_setopt( easy, CURLOPT_ENCODING, "" );
190
191        {
192            const CURLMcode mcode = curl_multi_add_handle( web->multi, easy );
193            tr_assert( mcode == CURLM_OK, "curl_multi_add_handle() failed: %d (%s)", mcode, curl_multi_strerror( mcode ) );
194            if( mcode == CURLM_OK )
195                ++web->still_running;
196            else
197                tr_err( "%s", curl_multi_strerror( mcode ) );
198
199            tr_multi_perform( web, CURL_SOCKET_TIMEOUT );
200        }
201    }
202}
203
204/***
205****
206***/
207
208static void
209task_free( struct tr_web_task * task )
210{
211    evbuffer_free( task->response );
212    tr_free( task->range );
213    tr_free( task->url );
214    tr_free( task );
215}
216
217static void
218task_finish( struct tr_web_task * task, long response_code )
219{
220    dbgmsg( "finished a web task... response code is %ld", response_code );
221    dbgmsg( "===================================================" );
222
223    if( task->done_func != NULL )
224        task->done_func( task->session,
225                         response_code,
226                         EVBUFFER_DATA( task->response ),
227                         EVBUFFER_LENGTH( task->response ),
228                         task->done_func_user_data );
229    task_free( task );
230}
231
232static void
233remove_finished_tasks( tr_web * g )
234{
235    CURL * easy;
236
237    do
238    {
239        CURLMsg * msg;
240        int msgs_left;
241
242        easy = NULL;
243        while(( msg = curl_multi_info_read( g->multi, &msgs_left ))) {
244            if( msg->msg == CURLMSG_DONE ) {
245                easy = msg->easy_handle;
246                break;
247            }
248        }
249
250        if( easy )
251        {
252            long code;
253            struct tr_web_task * task;
254            curl_easy_getinfo( easy, CURLINFO_PRIVATE, (void*)&task );
255            curl_easy_getinfo( easy, CURLINFO_RESPONSE_CODE, &code );
256            curl_multi_remove_handle( g->multi, easy );
257            curl_easy_cleanup( easy );
258            task_finish( task, code );
259        }
260    }
261    while ( easy );
262
263    g->prev_running = g->still_running;
264}
265
266static void
267stop_timer( tr_web* g )
268{
269    dbgmsg( "deleting the pending global timer" );
270    evtimer_del( &g->timer_event );
271}
272
273static void
274restart_timer( tr_web * g )
275{
276    assert( tr_amInEventThread( g->session ) );
277    assert( g->session != NULL );
278    assert( g->session->events != NULL );
279
280    stop_timer( g );
281    dbgmsg( "adding a timeout for %.1f seconds from now", g->timer_msec/1000.0 );
282    tr_timerAddMsec( &g->timer_event, g->timer_msec );
283}
284
285static void
286web_close( tr_web * g )
287{
288    CURLMcode mcode;
289
290    stop_timer( g );
291
292    mcode = curl_multi_cleanup( g->multi );
293    tr_assert( mcode == CURLM_OK, "curl_multi_cleanup() failed: %d (%s)", mcode, curl_multi_strerror( mcode ) );
294    if( mcode != CURLM_OK )
295        tr_err( "%s", curl_multi_strerror( mcode ) );
296
297    tr_free( g );
298}
299
300/* note: this function can free the tr_web if its 'closing' flag is set
301   and no tasks remain.  callers must not reference their g pointer
302   after calling this function */
303static void
304tr_multi_perform( tr_web * g, int fd )
305{
306    int closed = FALSE;
307    CURLMcode mcode;
308
309    dbgmsg( "check_run_count: prev_running %d, still_running %d",
310            g->prev_running, g->still_running );
311
312    /* invoke libcurl's processing */
313    do {
314        dbgmsg( "calling curl_multi_socket_action..." );
315        mcode = curl_multi_socket_action( g->multi, fd, 0, &g->still_running );
316        fd = CURL_SOCKET_TIMEOUT;
317        dbgmsg( "done calling curl_multi_socket_action..." );
318    } while( mcode == CURLM_CALL_MULTI_SOCKET );
319    tr_assert( mcode == CURLM_OK, "curl_multi_perform() failed: %d (%s)", mcode, curl_multi_strerror( mcode ) );
320    if( mcode != CURLM_OK )
321        tr_err( "%s", curl_multi_strerror( mcode ) );
322
323    remove_finished_tasks( g );
324
325    if( !g->still_running ) {
326        stop_timer( g );
327        if( g->closing ) {
328            web_close( g );
329            closed = TRUE;
330        }
331    }
332
333    if( !closed )
334        restart_timer( g );
335}
336
337/* libevent says that sock is ready to be processed, so wake up libcurl */
338static void
339event_cb( int fd, short kind UNUSED, void * g )
340{
341    tr_multi_perform( g, fd );
342}
343
344/* CURLMOPT_SOCKETFUNCTION */
345static int
346sock_cb( CURL            * easy UNUSED,
347         curl_socket_t     fd,
348         int               action,
349         void            * vweb,
350         void            * vevent )
351{
352    /*static int num_events = 0;*/
353    struct tr_web * web = vweb;
354    struct event * io_event = vevent;
355    dbgmsg( "sock_cb: action is %d, fd is %d, io_event is %p", action, (int)fd, io_event );
356
357    if( action == CURL_POLL_REMOVE )
358    {
359        if( io_event != NULL )
360        {
361            event_del( io_event );
362            tr_free( io_event );
363            curl_multi_assign( web->multi, fd, NULL ); /* does libcurl do this automatically? */
364            /*fprintf( stderr, "-1 io_events to %d\n", --num_events );*/
365        }
366    }
367    else
368    {
369        const short events = EV_PERSIST
370                           | (( action & CURL_POLL_IN ) ? EV_READ : 0 )
371                           | (( action & CURL_POLL_OUT ) ? EV_WRITE : 0 );
372
373        if( io_event != NULL )
374            event_del( io_event );
375        else {
376            io_event = tr_new0( struct event, 1 );
377            curl_multi_assign( web->multi, fd, io_event );
378            /*fprintf( stderr, "+1 io_events to %d\n", ++num_events );*/
379        }
380
381        dbgmsg( "enabling (libevent %hd, libcurl %d) polling on io_event %p, fd %d", events, action, io_event, fd );
382        event_set( io_event, fd, events, event_cb, web );
383        event_add( io_event, NULL );
384    }
385
386    return 0; /* libcurl doc sez: "The callback MUST return 0." */
387}
388
389/* libevent says that timer_msec have passed, so wake up libcurl */
390static void
391libevent_timer_cb( int fd UNUSED, short what UNUSED, void * g )
392{
393    dbgmsg( "libevent timer is done" );
394    tr_multi_perform( g, CURL_SOCKET_TIMEOUT );
395}
396
397/* libcurl documentation: "If 0, it means you should proceed immediately
398 * without waiting for anything. If it returns -1, there's no timeout at all
399 * set ... (but) you must not wait too long (more than a few seconds perhaps)
400 * before you call curl_multi_perform() again."  */
401static void
402multi_timer_cb( CURLM * multi UNUSED, long timer_msec, void * vg )
403{
404    tr_web * g = vg;
405
406    if( timer_msec < 1 ) {
407        if( timer_msec == 0 ) /* call it immediately */
408            libevent_timer_cb( 0, 0, g );
409        timer_msec = DEFAULT_TIMER_MSEC;
410    }
411
412    g->timer_msec = timer_msec;
413    restart_timer( g );
414}
415
416/****
417*****
418****/
419
420void
421tr_webRun( tr_session         * session,
422           const char         * url,
423           const char         * range,
424           tr_web_done_func     done_func,
425           void               * done_func_user_data )
426{
427    if( session->web )
428    {
429        struct tr_web_task * task;
430        static unsigned long tag = 0;
431
432        task = tr_new0( struct tr_web_task, 1 );
433        task->session = session;
434        task->url = tr_strdup( url );
435        task->range = tr_strdup( range );
436        task->done_func = done_func;
437        task->done_func_user_data = done_func_user_data;
438        task->tag = ++tag;
439        task->response = evbuffer_new( );
440
441        tr_runInEventThread( session, addTask, task );
442    }
443}
444
445void
446tr_webSetInterface( tr_web * web, const tr_address * addr )
447{
448    if(( web->haveAddr = ( addr != NULL )))
449        web->addr = *addr;
450}
451
452tr_web*
453tr_webInit( tr_session * session )
454{
455    tr_web * web;
456    static int curlInited = FALSE;
457
458    /* call curl_global_init if we haven't done it already.
459     * try to enable ssl for https support; but if that fails,
460     * try a plain vanilla init */
461    if( curlInited == FALSE ) {
462        curlInited = TRUE;
463        if( curl_global_init( CURL_GLOBAL_SSL ) )
464            curl_global_init( 0 );
465    }
466
467    web = tr_new0( struct tr_web, 1 );
468    web->session = session;
469
470    web->timer_msec = DEFAULT_TIMER_MSEC; /* overwritten by multi_timer_cb() */
471    evtimer_set( &web->timer_event, libevent_timer_cb, web );
472
473    web->multi = curl_multi_init( );
474    curl_multi_setopt( web->multi, CURLMOPT_SOCKETDATA, web );
475    curl_multi_setopt( web->multi, CURLMOPT_SOCKETFUNCTION, sock_cb );
476    curl_multi_setopt( web->multi, CURLMOPT_TIMERDATA, web );
477    curl_multi_setopt( web->multi, CURLMOPT_TIMERFUNCTION, multi_timer_cb );
478
479    return web;
480}
481
482void
483tr_webClose( tr_web ** web_in )
484{
485    tr_web * web = *web_in;
486    *web_in = NULL;
487    if( web->still_running < 1 )
488        web_close( web );
489    else
490        web->closing = 1;
491}
492
493/*****
494******
495******
496*****/
497
498static struct http_msg {
499    long code;
500    const char * text;
501} http_msg[] = {
502    {   0, "No Response" },
503    { 101, "Switching Protocols" },
504    { 200, "OK" },
505    { 201, "Created" },
506    { 202, "Accepted" },
507    { 203, "Non-Authoritative Information" },
508    { 204, "No Content" },
509    { 205, "Reset Content" },
510    { 206, "Partial Content" },
511    { 300, "Multiple Choices" },
512    { 301, "Moved Permanently" },
513    { 302, "Found" },
514    { 303, "See Other" },
515    { 304, "Not Modified" },
516    { 305, "Use Proxy" },
517    { 306, "(Unused)" },
518    { 307, "Temporary Redirect" },
519    { 400, "Bad Request" },
520    { 401, "Unauthorized" },
521    { 402, "Payment Required" },
522    { 403, "Forbidden" },
523    { 404, "Not Found" },
524    { 405, "Method Not Allowed" },
525    { 406, "Not Acceptable" },
526    { 407, "Proxy Authentication Required" },
527    { 408, "Request Timeout" },
528    { 409, "Conflict" },
529    { 410, "Gone" },
530    { 411, "Length Required" },
531    { 412, "Precondition Failed" },
532    { 413, "Request Entity Too Large" },
533    { 414, "Request-URI Too Long" },
534    { 415, "Unsupported Media Type" },
535    { 416, "Requested Range Not Satisfiable" },
536    { 417, "Expectation Failed" },
537    { 500, "Internal Server Error" },
538    { 501, "Not Implemented" },
539    { 502, "Bad Gateway" },
540    { 503, "Service Unavailable" },
541    { 504, "Gateway Timeout" },
542    { 505, "HTTP Version Not Supported" }
543};
544
545static int
546compareResponseCodes( const void * va, const void * vb )
547{
548    const long a = *(const long*) va;
549    const struct http_msg * b = vb;
550    return a - b->code;
551}
552
553const char *
554tr_webGetResponseStr( long code )
555{
556    struct http_msg * msg = bsearch( &code,
557                                     http_msg,
558                                     sizeof( http_msg ) / sizeof( http_msg[0] ),
559                                     sizeof( http_msg[0] ),
560                                     compareResponseCodes );
561    return msg ? msg->text : "Unknown Error";
562}
563
564/* escapes a string to be URI-legal as per RFC 2396.
565   like curl_escape() but can optionally avoid escaping slashes. */
566void
567tr_http_escape( struct evbuffer  * out,
568                const char       * str,
569                int                len,
570                tr_bool            escape_slashes )
571{
572    int i;
573
574    if( ( len < 0 ) && ( str != NULL ) )
575        len = strlen( str );
576
577    for( i = 0; i < len; i++ ) {
578        switch( str[i] ) {
579        case ',': case '-': case '.':
580        case '0': case '1': case '2': case '3': case '4':
581        case '5': case '6': case '7': case '8': case '9':
582        case 'a': case 'b': case 'c': case 'd': case 'e':
583        case 'f': case 'g': case 'h': case 'i': case 'j':
584        case 'k': case 'l': case 'm': case 'n': case 'o':
585        case 'p': case 'q': case 'r': case 's': case 't':
586        case 'u': case 'v': case 'w': case 'x': case 'y': case 'z':
587        case 'A': case 'B': case 'C': case 'D': case 'E':
588        case 'F': case 'G': case 'H': case 'I': case 'J':
589        case 'K': case 'L': case 'M': case 'N': case 'O':
590        case 'P': case 'Q': case 'R': case 'S': case 'T':
591        case 'U': case 'V': case 'W': case 'X': case 'Y': case 'Z':
592            evbuffer_add( out, &str[i], 1 );
593            break;
594        case '/':
595            if(!escape_slashes) {
596                evbuffer_add( out, &str[i], 1 );
597                break;
598            }
599            /* Fall through. */
600        default:
601            evbuffer_add_printf( out, "%%%02X", (unsigned)(str[i]&0xFF) );
602            break;
603        }
604    }
605}
606
607char*
608tr_http_unescape( const char * str, int len )
609{
610    char * tmp = curl_unescape( str, len );
611    char * ret = tr_strdup( tmp );
612    curl_free( tmp );
613    return ret;
614}
Note: See TracBrowser for help on using the repository browser.