source: branches/1.5x/libtransmission/web.c @ 7855

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

(1.5x libT) backport #1671, #1798

  • Property svn:keywords set to Date Rev Author Id
File size: 16.8 KB
Line 
1/*
2 * This file Copyright (C) 2008-2009 Charles Kerr <charles@transmissionbt.com>
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 7855 2009-02-09 17:34:43Z 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 "session.h"
23#include "list.h"
24#include "net.h" /* socklen_t */
25#include "trevent.h"
26#include "utils.h"
27#include "web.h"
28
29enum
30{
31    /* arbitrary number */
32    MAX_CONCURRENT_TASKS = 100,
33
34    /* arbitrary number */
35    DEFAULT_TIMER_MSEC = 2500
36};
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_sockinfo
53{
54    int fd;
55    tr_bool evset;
56    struct event ev;
57};
58
59struct tr_web
60{
61    tr_bool closing;
62    int prev_running;
63    int still_running;
64    long timer_ms;
65    CURLM * multi;
66    tr_session * session;
67    struct event timer_event;
68    tr_list * fds;
69};
70
71/***
72****
73***/
74
75static struct tr_web_sockinfo *
76getSockinfo( tr_web * web, int fd, tr_bool createIfMissing )
77{
78    tr_list * l = web->fds;
79
80    for( l=web->fds; l!=NULL; l=l->next ) {
81        struct tr_web_sockinfo * s =  l->data;
82        if( s->fd == fd ) {
83            dbgmsg( "looked up sockinfo %p for fd %d", s, fd );
84            return s;
85        }
86    }
87
88    if( createIfMissing ) {
89        struct tr_web_sockinfo * s =  tr_new0( struct tr_web_sockinfo, 1 );
90        s->fd = fd;
91        tr_list_prepend( &web->fds, s );
92        dbgmsg( "created sockinfo %p for fd %d... we now have %d sockinfos", s, fd, tr_list_size(web->fds) );
93        return s;
94    }
95
96    return NULL;
97}
98
99static void
100clearSockinfoEvent( struct tr_web_sockinfo * s )
101{
102    if( s && s->evset )
103    {
104        dbgmsg( "clearing libevent polling for sockinfo %p, fd %d", s, s->fd );
105        event_del( &s->ev );
106        s->evset = FALSE;
107    }
108}
109
110static void
111purgeSockinfo( tr_web * web, int fd )
112{
113    struct tr_web_sockinfo * s = getSockinfo( web, fd, FALSE );
114
115    if( s != NULL )
116    {
117        tr_list_remove_data( &web->fds, s );
118        clearSockinfoEvent( s );
119        dbgmsg( "freeing sockinfo %p, fd %d", s, s->fd );
120        tr_free( s );
121    }
122}
123
124/***
125****
126***/
127
128struct tr_web_task
129{
130    unsigned long tag;
131    struct evbuffer * response;
132    char * url;
133    char * range;
134    tr_session * session;
135    tr_web_done_func * done_func;
136    void * done_func_user_data;
137};
138
139static size_t
140writeFunc( void * ptr, size_t size, size_t nmemb, void * task )
141{
142    const size_t byteCount = size * nmemb;
143    evbuffer_add( ((struct tr_web_task*)task)->response, ptr, byteCount );
144    dbgmsg( "wrote %zu bytes to task %p's buffer", byteCount, task );
145    return byteCount;
146}
147
148static int
149getCurlProxyType( tr_proxy_type t )
150{
151    switch( t )
152    {
153        case TR_PROXY_SOCKS4: return CURLPROXY_SOCKS4;
154        case TR_PROXY_SOCKS5: return CURLPROXY_SOCKS5;
155        default:              return CURLPROXY_HTTP;
156    }
157}
158
159static void
160addTask( void * vtask )
161{
162    struct tr_web_task * task = vtask;
163    const tr_session * session = task->session;
164
165    if( session && session->web )
166    {
167        struct tr_web * web = session->web;
168        CURL * easy;
169
170        dbgmsg( "adding task #%lu [%s]", task->tag, task->url );
171
172        easy = curl_easy_init( );
173
174        if( !task->range && session->isProxyEnabled ) {
175            curl_easy_setopt( easy, CURLOPT_PROXY, session->proxy );
176            curl_easy_setopt( easy, CURLOPT_PROXYAUTH, CURLAUTH_ANY );
177            curl_easy_setopt( easy, CURLOPT_PROXYPORT, session->proxyPort );
178            curl_easy_setopt( easy, CURLOPT_PROXYTYPE,
179                                      getCurlProxyType( session->proxyType ) );
180        }
181        if( !task->range && session->isProxyAuthEnabled ) {
182            char * str = tr_strdup_printf( "%s:%s", session->proxyUsername,
183                                                    session->proxyPassword );
184            curl_easy_setopt( easy, CURLOPT_PROXYUSERPWD, str );
185            tr_free( str );
186        }
187
188        curl_easy_setopt( easy, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4 );
189        curl_easy_setopt( easy, CURLOPT_DNS_CACHE_TIMEOUT, 360L );
190        curl_easy_setopt( easy, CURLOPT_CONNECTTIMEOUT, 60L );
191        curl_easy_setopt( easy, CURLOPT_FOLLOWLOCATION, 1L );
192        curl_easy_setopt( easy, CURLOPT_FORBID_REUSE, 1L );
193        curl_easy_setopt( easy, CURLOPT_MAXREDIRS, 16L );
194        curl_easy_setopt( easy, CURLOPT_NOSIGNAL, 1L );
195        curl_easy_setopt( easy, CURLOPT_PRIVATE, task );
196        curl_easy_setopt( easy, CURLOPT_SSL_VERIFYHOST, 0L );
197        curl_easy_setopt( easy, CURLOPT_SSL_VERIFYPEER, 0L );
198        curl_easy_setopt( easy, CURLOPT_URL, task->url );
199        curl_easy_setopt( easy, CURLOPT_USERAGENT,
200                                           TR_NAME "/" LONG_VERSION_STRING );
201        curl_easy_setopt( easy, CURLOPT_VERBOSE,
202                                       getenv( "TR_CURL_VERBOSE" ) != NULL );
203        curl_easy_setopt( easy, CURLOPT_WRITEDATA, task );
204        curl_easy_setopt( easy, CURLOPT_WRITEFUNCTION, writeFunc );
205        if( task->range )
206            curl_easy_setopt( easy, CURLOPT_RANGE, task->range );
207        else /* don't set encoding on webseeds; it messes up binary data */
208            curl_easy_setopt( easy, CURLOPT_ENCODING, "" );
209
210        {
211            const CURLMcode mcode = curl_multi_add_handle( web->multi, easy );
212            tr_assert( mcode == CURLM_OK, "curl_multi_add_handle() failed: %d (%s)", mcode, curl_multi_strerror( mcode ) );
213            if( mcode == CURLM_OK )
214                ++web->still_running;
215            else
216                tr_err( "%s", curl_multi_strerror( mcode ) );
217        }
218    }
219}
220
221/***
222****
223***/
224
225static void
226task_free( struct tr_web_task * task )
227{
228    evbuffer_free( task->response );
229    tr_free( task->range );
230    tr_free( task->url );
231    tr_free( task );
232}
233
234static void
235task_finish( struct tr_web_task * task, long response_code )
236{
237    dbgmsg( "finished a web task... response code is %ld", response_code );
238    dbgmsg( "===================================================" );
239    task->done_func( task->session,
240                     response_code,
241                     EVBUFFER_DATA( task->response ),
242                     EVBUFFER_LENGTH( task->response ),
243                     task->done_func_user_data );
244    task_free( task );
245}
246
247static void
248remove_finished_tasks( tr_web * g )
249{
250    CURL * easy;
251
252    do
253    {
254        CURLMsg * msg;
255        int msgs_left;
256
257        easy = NULL;
258        while(( msg = curl_multi_info_read( g->multi, &msgs_left ))) {
259            if( msg->msg == CURLMSG_DONE ) {
260                easy = msg->easy_handle;
261                break;
262            }
263        }
264
265        if( easy ) {
266            long code;
267            long fd;
268            struct tr_web_task * task;
269            CURLcode ecode;
270            CURLMcode mcode;
271
272            ecode = curl_easy_getinfo( easy, CURLINFO_PRIVATE, (void*)&task );
273            tr_assert( ecode == CURLE_OK, "curl_easy_getinfo() failed: %d (%s)", ecode, curl_easy_strerror( ecode ) );
274
275            ecode = curl_easy_getinfo( easy, CURLINFO_RESPONSE_CODE, &code );
276            tr_assert( ecode == CURLE_OK, "curl_easy_getinfo() failed: %d (%s)", ecode, curl_easy_strerror( ecode ) );
277
278            ecode = curl_easy_getinfo( easy, CURLINFO_LASTSOCKET, &fd );
279            tr_assert( ecode == CURLE_OK, "curl_easy_getinfo() failed: %d (%s)", ecode, curl_easy_strerror( ecode ) );
280            if( fd != -1L )
281                purgeSockinfo( g, fd );
282
283            mcode = curl_multi_remove_handle( g->multi, easy );
284            tr_assert( mcode == CURLM_OK, "curl_multi_socket_action() failed: %d (%s)", mcode, curl_multi_strerror( mcode ) );
285
286            curl_easy_cleanup( easy );
287            task_finish( task, code );
288        }
289    }
290    while ( easy );
291
292    g->prev_running = g->still_running;
293}
294
295static void
296stop_timer( tr_web* g )
297{
298    if( evtimer_pending( &g->timer_event, NULL ) )
299    {
300        dbgmsg( "deleting the pending global timer" );
301        evtimer_del( &g->timer_event );
302    }
303}
304
305static void
306restart_timer( tr_web * g )
307{
308    struct timeval interval;
309
310    assert( tr_amInEventThread( g->session ) );
311    assert( g->session != NULL );
312    assert( g->session->events != NULL );
313
314    stop_timer( g );
315    dbgmsg( "adding a timeout for %ld seconds from now", g->timer_ms/1000L );
316    tr_timevalMsec( g->timer_ms, &interval );
317    evtimer_add( &g->timer_event, &interval );
318}
319
320static void
321web_close( tr_web * g )
322{
323    CURLMcode mcode;
324
325    stop_timer( g );
326
327    mcode = curl_multi_cleanup( g->multi );
328    tr_assert( mcode == CURLM_OK, "curl_multi_cleanup() failed: %d (%s)", mcode, curl_multi_strerror( mcode ) );
329    if( mcode != CURLM_OK )
330        tr_err( "%s", curl_multi_strerror( mcode ) );
331
332    tr_free( g );
333}
334
335/* note: this function can free the tr_web if its 'closing' flag is set
336   and no tasks remain.  callers must not reference their g pointer
337   after calling this function */
338static void
339tr_multi_socket_action( tr_web * g, int fd )
340{
341    int closed = FALSE;
342    CURLMcode mcode;
343
344    dbgmsg( "check_run_count: prev_running %d, still_running %d",
345            g->prev_running, g->still_running );
346
347    /* invoke libcurl's processing */
348    do {
349        mcode = curl_multi_socket_action( g->multi, fd, 0, &g->still_running );
350        dbgmsg( "event_cb(): fd %d, still_running is %d", fd, g->still_running );
351    } while( mcode == CURLM_CALL_MULTI_PERFORM );
352    if( ( mcode == CURLM_BAD_SOCKET ) && ( fd != CURL_SOCKET_TIMEOUT ) )
353        purgeSockinfo( g, fd );
354    else {
355        tr_assert( mcode == CURLM_OK, "curl_multi_socket_action() failed on fd %d: %d (%s)", fd, mcode, curl_multi_strerror( mcode ) );
356        if( mcode != CURLM_OK )
357            tr_err( "%s", curl_multi_strerror( mcode ) );
358    }
359
360    remove_finished_tasks( g );
361
362    if( !g->still_running ) {
363        assert( tr_list_size( g->fds ) == 0 );
364        stop_timer( g );
365        if( g->closing ) {
366            web_close( g );
367            closed = TRUE;
368        }
369    }
370
371    if( !closed )
372        restart_timer( g );
373}
374
375/* libevent says that sock is ready to be processed, so wake up libcurl */
376static void
377event_cb( int fd, short kind UNUSED, void * g )
378{
379    tr_multi_socket_action( g, fd );
380}
381
382/* libevent says that timer_ms have passed, so wake up libcurl */
383static void
384timer_cb( int socket UNUSED, short action UNUSED, void * g )
385{
386    dbgmsg( "libevent timer is done" );
387    tr_multi_socket_action( g, CURL_SOCKET_TIMEOUT );
388}
389
390/* CURLMOPT_SOCKETFUNCTION */
391static int
392sock_cb( CURL            * e UNUSED,
393         curl_socket_t     fd,
394         int               action,
395         void            * vweb,
396         void            * unused UNUSED)
397{
398    struct tr_web * web = vweb;
399    dbgmsg( "sock_cb: action is %d, fd is %d", action, (int)fd );
400
401    if( action == CURL_POLL_REMOVE )
402    {
403        purgeSockinfo( web, fd );
404    }
405    else
406    {
407        struct tr_web_sockinfo * sockinfo = getSockinfo( web, fd, TRUE );
408        const int kind = EV_PERSIST
409                       | (( action & CURL_POLL_IN ) ? EV_READ : 0 )
410                       | (( action & CURL_POLL_OUT ) ? EV_WRITE : 0 );
411        dbgmsg( "setsock: fd is %d, curl action is %d, libevent action is %d", fd, action, kind );
412        assert( tr_amInEventThread( web->session ) );
413        assert( kind != EV_PERSIST );
414
415        /* clear any old polling on this fd */
416        clearSockinfoEvent( sockinfo );
417
418        /* set the new polling on this fd */
419        dbgmsg( "enabling (libevent %d, libcurl %d) polling on sockinfo %p, fd %d", action, kind, sockinfo, fd );
420        event_set( &sockinfo->ev, fd, kind, event_cb, web );
421        event_add( &sockinfo->ev, NULL );
422        sockinfo->evset = TRUE;
423    }
424
425    return 0;
426}
427
428
429/* libcurl documentation: "If 0, it means you should proceed immediately
430 * without waiting for anything. If it returns -1, there's no timeout at all
431 * set ... (but) you must not wait too long (more than a few seconds perhaps)
432 * before you call curl_multi_perform() again."  */
433static void
434multi_timer_cb( CURLM *multi UNUSED, long timer_ms, void * vg )
435{
436    tr_web * g = vg;
437
438    if( timer_ms < 1 ) {
439        if( timer_ms == 0 ) /* call it immediately */
440            timer_cb( 0, 0, g );
441        timer_ms = DEFAULT_TIMER_MSEC;
442    }
443
444    g->timer_ms = timer_ms;
445    restart_timer( g );
446}
447
448/****
449*****
450****/
451
452void
453tr_webRun( tr_session         * session,
454           const char         * url,
455           const char         * range,
456           tr_web_done_func     done_func,
457           void               * done_func_user_data )
458{
459    if( session->web )
460    {
461        static unsigned long tag = 0;
462        struct tr_web_task * task;
463
464        task = tr_new0( struct tr_web_task, 1 );
465        task->session = session;
466        task->url = tr_strdup( url );
467        task->range = tr_strdup( range );
468        task->done_func = done_func;
469        task->done_func_user_data = done_func_user_data;
470        task->tag = ++tag;
471        task->response = evbuffer_new( );
472
473        tr_runInEventThread( session, addTask, task );
474    }
475}
476
477tr_web*
478tr_webInit( tr_session * session )
479{
480    CURLMcode mcode;
481    static int curlInited = FALSE;
482    tr_web * web;
483
484    /* call curl_global_init if we haven't done it already.
485     * try to enable ssl for https support; but if that fails,
486     * try a plain vanilla init */ 
487    if( curlInited == FALSE ) {
488        curlInited = TRUE;
489        if( curl_global_init( CURL_GLOBAL_SSL ) )
490            curl_global_init( 0 );
491    }
492   
493    web = tr_new0( struct tr_web, 1 );
494    web->multi = curl_multi_init( );
495    web->session = session;
496    web->timer_ms = DEFAULT_TIMER_MSEC; /* overwritten by multi_timer_cb() */
497
498    evtimer_set( &web->timer_event, timer_cb, web );
499    mcode = curl_multi_setopt( web->multi, CURLMOPT_SOCKETDATA, web );
500    tr_assert( mcode == CURLM_OK, "curl_mutli_setopt() failed: %d (%s)", mcode, curl_multi_strerror( mcode ) );
501    mcode = curl_multi_setopt( web->multi, CURLMOPT_SOCKETFUNCTION, sock_cb );
502    tr_assert( mcode == CURLM_OK, "curl_mutli_setopt() failed: %d (%s)", mcode, curl_multi_strerror( mcode ) );
503    mcode = curl_multi_setopt( web->multi, CURLMOPT_TIMERDATA, web );
504    tr_assert( mcode == CURLM_OK, "curl_mutli_setopt() failed: %d (%s)", mcode, curl_multi_strerror( mcode ) );
505    mcode = curl_multi_setopt( web->multi, CURLMOPT_TIMERFUNCTION, multi_timer_cb );
506    tr_assert( mcode == CURLM_OK, "curl_mutli_setopt() failed: %d (%s)", mcode, curl_multi_strerror( mcode ) );
507
508    return web;
509}
510
511void
512tr_webClose( tr_web ** web_in )
513{
514    tr_web * web = *web_in;
515    *web_in = NULL;
516    if( web->still_running < 1 )
517        web_close( web );
518    else
519        web->closing = 1;
520}
521
522/*****
523******
524******
525*****/
526
527static struct http_msg {
528    long code;
529    const char * text;
530} http_msg[] = {
531    {   0, "No Response" },
532    { 101, "Switching Protocols" },
533    { 200, "OK" },
534    { 201, "Created" },
535    { 202, "Accepted" },
536    { 203, "Non-Authoritative Information" },
537    { 204, "No Content" },
538    { 205, "Reset Content" },
539    { 206, "Partial Content" },
540    { 300, "Multiple Choices" },
541    { 301, "Moved Permanently" },
542    { 302, "Found" },
543    { 303, "See Other" },
544    { 304, "Not Modified" },
545    { 305, "Use Proxy" },
546    { 306, "(Unused)" },
547    { 307, "Temporary Redirect" },
548    { 400, "Bad Request" },
549    { 401, "Unauthorized" },
550    { 402, "Payment Required" },
551    { 403, "Forbidden" },
552    { 404, "Not Found" },
553    { 405, "Method Not Allowed" },
554    { 406, "Not Acceptable" },
555    { 407, "Proxy Authentication Required" },
556    { 408, "Request Timeout" },
557    { 409, "Conflict" },
558    { 410, "Gone" },
559    { 411, "Length Required" },
560    { 412, "Precondition Failed" },
561    { 413, "Request Entity Too Large" },
562    { 414, "Request-URI Too Long" },
563    { 415, "Unsupported Media Type" },
564    { 416, "Requested Range Not Satisfiable" },
565    { 417, "Expectation Failed" },
566    { 500, "Internal Server Error" },
567    { 501, "Not Implemented" },
568    { 502, "Bad Gateway" },
569    { 503, "Service Unavailable" },
570    { 504, "Gateway Timeout" },
571    { 505, "HTTP Version Not Supported" }
572};
573
574static int
575compareResponseCodes( const void * va, const void * vb )
576{
577    const long a = *(const long*) va;
578    const struct http_msg * b = vb;
579    return a - b->code;
580}
581
582const char *
583tr_webGetResponseStr( long code )
584{
585    struct http_msg * msg = bsearch( &code,
586                                     http_msg, 
587                                     sizeof( http_msg ) / sizeof( http_msg[0] ),
588                                     sizeof( http_msg[0] ),
589                                     compareResponseCodes );
590    return msg ? msg->text : "Unknown Error";
591}
Note: See TracBrowser for help on using the repository browser.