source: trunk/libtransmission/web.c @ 12168

Last change on this file since 12168 was 12168, checked in by jordan, 11 years ago

(trunk) #4081 "Add 'cookieString' argument 'torrent-add' method in RPC" -- done.

  • Property svn:keywords set to Date Rev Author Id
File size: 15.9 KB
Line 
1/*
2 * This file Copyright (C) Mnemosyne LLC
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 12168 2011-03-15 04:22:47Z jordan $
11 */
12
13#ifdef WIN32
14  #include <ws2tcpip.h>
15#else
16  #include <sys/select.h>
17#endif
18
19#include <curl/curl.h>
20
21#include <event2/buffer.h>
22
23#include "transmission.h"
24#include "list.h"
25#include "net.h" /* tr_address */
26#include "platform.h" /* mutex */
27#include "session.h"
28#include "trevent.h" /* tr_runInEventThread() */
29#include "utils.h"
30#include "version.h" /* User-Agent */
31#include "web.h"
32
33#if LIBCURL_VERSION_NUM >= 0x070F06 /* CURLOPT_SOCKOPT* was added in 7.15.6 */
34 #define USE_LIBCURL_SOCKOPT
35#endif
36
37enum
38{
39    THREADFUNC_MAX_SLEEP_MSEC = 1000,
40};
41
42#if 0
43#define dbgmsg(...) \
44    do { \
45        fprintf( stderr, __VA_ARGS__ ); \
46        fprintf( stderr, "\n" ); \
47    } while( 0 )
48#else
49#define dbgmsg( ... ) \
50    do { \
51        if( tr_deepLoggingIsActive( ) ) \
52            tr_deepLog( __FILE__, __LINE__, "web", __VA_ARGS__ ); \
53    } while( 0 )
54#endif
55
56/***
57****
58***/
59
60struct tr_web
61{
62    int close_mode;
63    tr_list * tasks;
64    tr_lock * taskLock;
65};
66
67
68/***
69****
70***/
71
72struct tr_web_task
73{
74    long code;
75    long timeout_secs;
76    tr_bool did_connect;
77    tr_bool did_timeout;
78    struct evbuffer * response;
79    struct evbuffer * freebuf;
80    char * url;
81    char * range;
82    char * cookies;
83    tr_session * session;
84    tr_web_done_func * done_func;
85    void * done_func_user_data;
86};
87
88static void
89task_free( struct tr_web_task * task )
90{
91    if( task->freebuf )
92        evbuffer_free( task->freebuf );
93    tr_free( task->cookies );
94    tr_free( task->range );
95    tr_free( task->url );
96    tr_free( task );
97}
98
99/***
100****
101***/
102
103static size_t
104writeFunc( void * ptr, size_t size, size_t nmemb, void * vtask )
105{
106    const size_t byteCount = size * nmemb;
107    struct tr_web_task * task = vtask;
108    evbuffer_add( task->response, ptr, byteCount );
109    dbgmsg( "wrote %zu bytes to task %p's buffer", byteCount, task );
110    return byteCount;
111}
112
113#ifdef USE_LIBCURL_SOCKOPT
114static int
115sockoptfunction( void * vtask, curl_socket_t fd, curlsocktype purpose UNUSED )
116{
117    struct tr_web_task * task = vtask;
118    const tr_bool isScrape = strstr( task->url, "scrape" ) != NULL;
119    const tr_bool isAnnounce = strstr( task->url, "announce" ) != NULL;
120
121    /* announce and scrape requests have tiny payloads. */
122    if( isScrape || isAnnounce )
123    {
124        const int sndbuf = 1024;
125        const int rcvbuf = isScrape ? 2048 : 3072;
126        setsockopt( fd, SOL_SOCKET, SO_SNDBUF, &sndbuf, sizeof(sndbuf) );
127        setsockopt( fd, SOL_SOCKET, SO_RCVBUF, &rcvbuf, sizeof(rcvbuf) );
128    }
129
130    /* return nonzero if this function encountered an error */
131    return 0;
132}
133#endif
134
135static long
136getTimeoutFromURL( const struct tr_web_task * task )
137{
138    long timeout;
139    const tr_session * session = task->session;
140
141    if( !session || session->isClosed ) timeout = 20L;
142    else if( strstr( task->url, "scrape" ) != NULL ) timeout = 30L;
143    else if( strstr( task->url, "announce" ) != NULL ) timeout = 90L;
144    else timeout = 240L;
145
146    return timeout;
147}
148
149static CURL *
150createEasy( tr_session * s, struct tr_web_task * task )
151{
152    const tr_address * addr;
153    tr_bool is_default_value;
154    CURL * e = curl_easy_init( );
155    const long verbose = getenv( "TR_CURL_VERBOSE" ) != NULL;
156    char * cookie_filename = tr_buildPath( s->configDir, "cookies.txt", NULL );
157
158    task->timeout_secs = getTimeoutFromURL( task );
159
160    curl_easy_setopt( e, CURLOPT_AUTOREFERER, 1L );
161    curl_easy_setopt( e, CURLOPT_COOKIEFILE, cookie_filename );
162    curl_easy_setopt( e, CURLOPT_ENCODING, "gzip;q=1.0, deflate, identity" );
163    curl_easy_setopt( e, CURLOPT_FOLLOWLOCATION, 1L );
164    curl_easy_setopt( e, CURLOPT_MAXREDIRS, -1L );
165    curl_easy_setopt( e, CURLOPT_NOSIGNAL, 1L );
166    curl_easy_setopt( e, CURLOPT_PRIVATE, task );
167#ifdef USE_LIBCURL_SOCKOPT
168    curl_easy_setopt( e, CURLOPT_SOCKOPTFUNCTION, sockoptfunction );
169    curl_easy_setopt( e, CURLOPT_SOCKOPTDATA, task );
170#endif
171    curl_easy_setopt( e, CURLOPT_SSL_VERIFYHOST, 0L );
172    curl_easy_setopt( e, CURLOPT_SSL_VERIFYPEER, 0L );
173    curl_easy_setopt( e, CURLOPT_TIMEOUT, task->timeout_secs );
174    curl_easy_setopt( e, CURLOPT_URL, task->url );
175    curl_easy_setopt( e, CURLOPT_USERAGENT, TR_NAME "/" SHORT_VERSION_STRING );
176    curl_easy_setopt( e, CURLOPT_VERBOSE, verbose );
177    curl_easy_setopt( e, CURLOPT_WRITEDATA, task );
178    curl_easy_setopt( e, CURLOPT_WRITEFUNCTION, writeFunc );
179
180    if((( addr = tr_sessionGetPublicAddress( s, TR_AF_INET, &is_default_value ))) && !is_default_value )
181        curl_easy_setopt( e, CURLOPT_INTERFACE, tr_ntop_non_ts( addr ) );
182    else if ((( addr = tr_sessionGetPublicAddress( s, TR_AF_INET6, &is_default_value ))) && !is_default_value )
183        curl_easy_setopt( e, CURLOPT_INTERFACE, tr_ntop_non_ts( addr ) );
184
185    if( task->cookies != NULL )
186        curl_easy_setopt( e, CURLOPT_COOKIE, task->cookies );
187
188    if( task->range )
189        curl_easy_setopt( e, CURLOPT_RANGE, task->range );
190
191    if( s->curl_easy_config_func != NULL )
192        s->curl_easy_config_func( s, e, task->url );
193
194    tr_free( cookie_filename );
195    return e;
196}
197
198/***
199****
200***/
201
202static void
203task_finish_func( void * vtask )
204{
205    struct tr_web_task * task = vtask;
206    dbgmsg( "finished web task %p; got %ld", task, task->code );
207
208    if( task->done_func != NULL )
209        task->done_func( task->session,
210                         task->did_connect,
211                         task->did_timeout,
212                         task->code,
213                         evbuffer_pullup( task->response, -1 ),
214                         evbuffer_get_length( task->response ),
215                         task->done_func_user_data );
216
217    task_free( task );
218}
219
220/****
221*****
222****/
223
224void
225tr_webRun( tr_session         * session,
226           const char         * url,
227           const char         * range,
228           const char         * cookies,
229           tr_web_done_func     done_func,
230           void               * done_func_user_data )
231{
232    tr_webRunWithBuffer( session, url, range, cookies,
233                         done_func, done_func_user_data,
234                         NULL );
235}
236
237void
238tr_webRunWithBuffer( tr_session         * session,
239                     const char         * url,
240                     const char         * range,
241                     const char         * cookies,
242                     tr_web_done_func     done_func,
243                     void               * done_func_user_data,
244                     struct evbuffer    * buffer )
245{
246    struct tr_web * web = session->web;
247
248    if( web != NULL )
249    {
250        struct tr_web_task * task = tr_new0( struct tr_web_task, 1 );
251
252        task->session = session;
253        task->url = tr_strdup( url );
254        task->range = tr_strdup( range );
255        task->cookies = tr_strdup( cookies);
256        task->done_func = done_func;
257        task->done_func_user_data = done_func_user_data;
258        task->response = buffer ? buffer : evbuffer_new( );
259        task->freebuf = buffer ? NULL : task->response;
260
261        tr_lockLock( web->taskLock );
262        tr_list_append( &web->tasks, task );
263        tr_lockUnlock( web->taskLock );
264    }
265}
266
267/**
268 * Portability wrapper for select().
269 *
270 * http://msdn.microsoft.com/en-us/library/ms740141%28VS.85%29.aspx
271 * On win32, any two of the parameters, readfds, writefds, or exceptfds,
272 * can be given as null. At least one must be non-null, and any non-null
273 * descriptor set must contain at least one handle to a socket.
274 */
275static void
276tr_select( int nfds,
277           fd_set * r_fd_set, fd_set * w_fd_set, fd_set * c_fd_set,
278           struct timeval  * t )
279{
280#ifdef WIN32
281    if( !r_fd_set->fd_count && !w_fd_set->fd_count && !c_fd_set->fd_count )
282    {
283        const long int msec = t->tv_sec*1000 + t->tv_usec/1000;
284        tr_wait_msec( msec );
285    }
286    else if( select( 0, r_fd_set->fd_count ? r_fd_set : NULL,
287                        w_fd_set->fd_count ? w_fd_set : NULL,
288                        c_fd_set->fd_count ? c_fd_set : NULL, t ) < 0 )
289    {
290        char errstr[512];
291        const int e = EVUTIL_SOCKET_ERROR( );
292        tr_net_strerror( errstr, sizeof( errstr ), e );
293        dbgmsg( "Error: select (%d) %s", e, errstr );
294    }
295#else
296    select( nfds, r_fd_set, w_fd_set, c_fd_set, t );
297#endif
298}
299
300static void
301tr_webThreadFunc( void * vsession )
302{
303    CURLM * multi;
304    struct tr_web * web;
305    int taskCount = 0;
306    struct tr_web_task * task;
307    tr_session * session = vsession;
308
309    /* try to enable ssl for https support; but if that fails,
310     * try a plain vanilla init */
311    if( curl_global_init( CURL_GLOBAL_SSL ) )
312        curl_global_init( 0 );
313
314    web = tr_new0( struct tr_web, 1 );
315    web->close_mode = ~0;
316    web->taskLock = tr_lockNew( );
317    web->tasks = NULL;
318    multi = curl_multi_init( );
319    session->web = web;
320
321    for( ;; )
322    {
323        long msec;
324        int unused;
325        CURLMsg * msg;
326        CURLMcode mcode;
327
328        if( web->close_mode == TR_WEB_CLOSE_NOW )
329            break;
330        if( ( web->close_mode == TR_WEB_CLOSE_WHEN_IDLE ) && !taskCount )
331            break;
332
333        /* add tasks from the queue */
334        tr_lockLock( web->taskLock );
335        while(( task = tr_list_pop_front( &web->tasks )))
336        {
337            dbgmsg( "adding task to curl: [%s]", task->url );
338            curl_multi_add_handle( multi, createEasy( session, task ));
339            /*fprintf( stderr, "adding a task.. taskCount is now %d\n", taskCount );*/
340            ++taskCount;
341        }
342        tr_lockUnlock( web->taskLock );
343
344        /* maybe wait a little while before calling curl_multi_perform() */
345        msec = 0;
346        curl_multi_timeout( multi, &msec );
347        if( msec < 0 )
348            msec = THREADFUNC_MAX_SLEEP_MSEC;
349        if( msec > 0 )
350        {
351            int usec;
352            int max_fd;
353            struct timeval t;
354            fd_set r_fd_set, w_fd_set, c_fd_set;
355
356            max_fd = 0;
357            FD_ZERO( &r_fd_set );
358            FD_ZERO( &w_fd_set );
359            FD_ZERO( &c_fd_set );
360            curl_multi_fdset( multi, &r_fd_set, &w_fd_set, &c_fd_set, &max_fd );
361
362            if( msec > THREADFUNC_MAX_SLEEP_MSEC )
363                msec = THREADFUNC_MAX_SLEEP_MSEC;
364
365            usec = msec * 1000;
366            t.tv_sec =  usec / 1000000;
367            t.tv_usec = usec % 1000000;
368
369            tr_select( max_fd+1, &r_fd_set, &w_fd_set, &c_fd_set, &t );
370        }
371
372        /* call curl_multi_perform() */
373        do {
374            mcode = curl_multi_perform( multi, &unused );
375        } while( mcode == CURLM_CALL_MULTI_PERFORM );
376
377        /* pump completed tasks from the multi */
378        while(( msg = curl_multi_info_read( multi, &unused )))
379        {
380            if(( msg->msg == CURLMSG_DONE ) && ( msg->easy_handle != NULL ))
381            {
382                double total_time;
383                struct tr_web_task * task;
384                long req_bytes_sent;
385                CURL * e = msg->easy_handle;
386                curl_easy_getinfo( e, CURLINFO_PRIVATE, (void*)&task );
387                curl_easy_getinfo( e, CURLINFO_RESPONSE_CODE, &task->code );
388                curl_easy_getinfo( e, CURLINFO_REQUEST_SIZE, &req_bytes_sent );
389                curl_easy_getinfo( e, CURLINFO_TOTAL_TIME, &total_time );
390                task->did_connect = task->code>0 || req_bytes_sent>0;
391                task->did_timeout = !task->code && ( total_time >= task->timeout_secs );
392                curl_multi_remove_handle( multi, e );
393                curl_easy_cleanup( e );
394/*fprintf( stderr, "removing a completed task.. taskCount is now %d (response code: %d, response len: %d)\n", taskCount, (int)task->code, (int)evbuffer_get_length(task->response) );*/
395                tr_runInEventThread( task->session, task_finish_func, task );
396                --taskCount;
397            }
398        }
399    }
400
401    /* Discard any remaining tasks.
402     * This is rare, but can happen on shutdown with unresponsive trackers. */
403    while(( task = tr_list_pop_front( &web->tasks ))) {
404        dbgmsg( "Discarding task \"%s\"", task->url );
405        task_free( task );
406    }
407
408    /* cleanup */
409    curl_multi_cleanup( multi );
410    tr_lockFree( web->taskLock );
411    tr_free( web );
412    session->web = NULL;
413}
414
415void
416tr_webInit( tr_session * session )
417{
418    tr_threadNew( tr_webThreadFunc, session );
419}
420
421void
422tr_webClose( tr_session * session, tr_web_close_mode close_mode )
423{
424    if( session->web != NULL )
425    {
426        session->web->close_mode = close_mode;
427
428        if( close_mode == TR_WEB_CLOSE_NOW )
429            while( session->web != NULL )
430                tr_wait_msec( 100 );
431    }
432}
433
434/*****
435******
436******
437*****/
438
439const char *
440tr_webGetResponseStr( long code )
441{
442    switch( code )
443    {
444        case   0: return "No Response";
445        case 101: return "Switching Protocols";
446        case 200: return "OK";
447        case 201: return "Created";
448        case 202: return "Accepted";
449        case 203: return "Non-Authoritative Information";
450        case 204: return "No Content";
451        case 205: return "Reset Content";
452        case 206: return "Partial Content";
453        case 300: return "Multiple Choices";
454        case 301: return "Moved Permanently";
455        case 302: return "Found";
456        case 303: return "See Other";
457        case 304: return "Not Modified";
458        case 305: return "Use Proxy";
459        case 306: return "(Unused)";
460        case 307: return "Temporary Redirect";
461        case 400: return "Bad Request";
462        case 401: return "Unauthorized";
463        case 402: return "Payment Required";
464        case 403: return "Forbidden";
465        case 404: return "Not Found";
466        case 405: return "Method Not Allowed";
467        case 406: return "Not Acceptable";
468        case 407: return "Proxy Authentication Required";
469        case 408: return "Request Timeout";
470        case 409: return "Conflict";
471        case 410: return "Gone";
472        case 411: return "Length Required";
473        case 412: return "Precondition Failed";
474        case 413: return "Request Entity Too Large";
475        case 414: return "Request-URI Too Long";
476        case 415: return "Unsupported Media Type";
477        case 416: return "Requested Range Not Satisfiable";
478        case 417: return "Expectation Failed";
479        case 500: return "Internal Server Error";
480        case 501: return "Not Implemented";
481        case 502: return "Bad Gateway";
482        case 503: return "Service Unavailable";
483        case 504: return "Gateway Timeout";
484        case 505: return "HTTP Version Not Supported";
485        default:  return "Unknown Error";
486    }
487}
488
489void
490tr_http_escape( struct evbuffer  * out,
491                const char * str, int len, tr_bool escape_slashes )
492{
493    const char * end;
494
495    if( ( len < 0 ) && ( str != NULL ) )
496        len = strlen( str );
497
498    for( end=str+len; str && str!=end; ++str ) {
499        if(    ( *str == ',' )
500            || ( *str == '-' )
501            || ( *str == '.' )
502            || ( ( '0' <= *str ) && ( *str <= '9' ) )
503            || ( ( 'A' <= *str ) && ( *str <= 'Z' ) )
504            || ( ( 'a' <= *str ) && ( *str <= 'z' ) )
505            || ( ( *str == '/' ) && ( !escape_slashes ) ) )
506            evbuffer_add( out, str, 1 );
507        else
508            evbuffer_add_printf( out, "%%%02X", (unsigned)(*str&0xFF) );
509    }
510}
511
512char *
513tr_http_unescape( const char * str, int len )
514{
515    char * tmp = curl_unescape( str, len );
516    char * ret = tr_strdup( tmp );
517    curl_free( tmp );
518    return ret;
519}
520
521static int
522is_rfc2396_alnum( uint8_t ch )
523{
524    return ( '0' <= ch && ch <= '9' )
525        || ( 'A' <= ch && ch <= 'Z' )
526        || ( 'a' <= ch && ch <= 'z' )
527        || ch == '.'
528        || ch == '-'
529        || ch == '_'
530        || ch == '~';
531}
532
533void
534tr_http_escape_sha1( char * out, const uint8_t * sha1_digest )
535{
536    const uint8_t * in = sha1_digest;
537    const uint8_t * end = in + SHA_DIGEST_LENGTH;
538
539    while( in != end )
540        if( is_rfc2396_alnum( *in ) )
541            *out++ = (char) *in++;
542        else
543            out += tr_snprintf( out, 4, "%%%02x", (unsigned int)*in++ );
544
545    *out = '\0';
546}
Note: See TracBrowser for help on using the repository browser.