source: trunk/libtransmission/web.c @ 10845

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

(trunk libt) #3311 "MingW build of Transmission" -- possible win32 fix for the curl thread's select() call. probably broken... :)

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