source: trunk/libtransmission/web.c @ 7557

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

(trunk libT) this diff has held #1631 at bay for 18 hours now... is it finally fixed? :)

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