source: trunk/libtransmission/web.c @ 7786

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

(trunk libT) silence curl_easy_setopt -Wunreachable-code warnings

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