source: trunk/libtransmission/web.c @ 12204

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

(trunk) #4138 "use stdbool.h instead of tr_bool" -- done.

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