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

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

(1.5x libT) various backports for 1.52:
(1) recognize Aria2 as a client
(2) remove jhujhiti's tr_suspectAddress(), since he removed it from trunka
(3) on Mac, better detection of where the Web UI files are located
(4) reintroduce the web task queue
(5) various minor formatting changes to reduce the diffs between 1.52 and trunk

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