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

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

(trunk libT) backport latest changes to 1.5x -- (1) all of libT (2) more stats in daemon (3) compiler warning in gtk

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