source: trunk/libtransmission/web.c @ 12417

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

(trunk libT) experimental fix for the unterminated announce URLs reported by blacklion in the forums

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