source: trunk/libtransmission/web.c @ 10306

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

(trunk libT) poke at the newfound 100% cpu bug

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