source: trunk/libtransmission/web.c @ 11200

Last change on this file since 11200 was 11200, checked in by charles, 12 years ago

(trunk libT) #3077 "add support for cookies files"

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