source: trunk/libtransmission/web.c @ 7831

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

(trunk libT) more hoops for Biiaru

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