source: trunk/libtransmission/web.c @ 11599

Last change on this file since 11599 was 11599, checked in by charles, 11 years ago

(trunk) Join the 21st century and use only 1 space at the end sentences. This commit is nearly as important as the semi-annual ones that remove trailing spaces from the ends of lines of code... :)

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