source: trunk/libtransmission/web.c @ 9798

Last change on this file since 9798 was 9798, checked in by charles, 13 years ago

(trunk libT) it certainly smells like we're using freed memory in the libcurl + libevent code in web.c... let's trash the structures right before free()ing them

  • Property svn:keywords set to Date Rev Author Id
File size: 15.3 KB
Line 
1/*
2 * This file Copyright (C) 2008-2009 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 9798 2009-12-18 17:32:16Z charles $
11 */
12
13#include <curl/curl.h>
14#include <event.h>
15
16#include "transmission.h"
17#include "net.h"
18#include "session.h"
19#include "trevent.h"
20#include "utils.h"
21#include "version.h"
22#include "web.h"
23
24enum
25{
26    TR_MEMORY_TRASH = 0xCC,
27
28    DEFAULT_TIMER_MSEC = 1500 /* arbitrary */
29};
30
31#if 0
32#define dbgmsg(...) \
33    do { \
34        fprintf( stderr, __VA_ARGS__ ); \
35        fprintf( stderr, "\n" ); \
36    } while( 0 )
37#else
38#define dbgmsg( ... ) \
39    do { \
40        if( tr_deepLoggingIsActive( ) ) \
41            tr_deepLog( __FILE__, __LINE__, "web", __VA_ARGS__ ); \
42    } while( 0 )
43#endif
44
45struct tr_web
46{
47    tr_bool closing;
48    tr_bool haveAddr;
49    int taskCount;
50    long timer_msec;
51    CURLM * multi;
52    tr_session * session;
53    tr_address addr;
54    struct event timer_event;
55};
56
57static void
58web_free( tr_web * g )
59{
60    curl_multi_cleanup( g->multi );
61    evtimer_del( &g->timer_event );
62    memset( g, TR_MEMORY_TRASH, sizeof( struct tr_web ) );
63    tr_free( g );
64}
65
66/***
67****
68***/
69
70struct tr_web_task
71{
72    unsigned long tag;
73    struct evbuffer * response;
74    char * url;
75    char * range;
76    tr_session * session;
77    tr_web_done_func * done_func;
78    void * done_func_user_data;
79};
80
81static void
82task_free( struct tr_web_task * task )
83{
84    evbuffer_free( task->response );
85    tr_free( task->range );
86    tr_free( task->url );
87    memset( task, TR_MEMORY_TRASH, sizeof( struct tr_web_task ) );
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
105static void
106sockoptfunction( void * vtask, curl_socket_t fd, curlsocktype purpose UNUSED )
107{
108    struct tr_web_task * task = vtask;
109    const tr_bool isScrape = strstr( task->url, "scrape" ) != NULL;
110    const tr_bool isAnnounce = strstr( task->url, "announce" ) != NULL;
111
112    /* announce and scrape requests have tiny payloads...
113     * which have very small 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
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 int
132getTimeoutFromURL( const char * url )
133{
134    if( strstr( url, "scrape" ) != NULL ) return 20;
135    if( strstr( url, "announce" ) != NULL ) return 30;
136    return 240;
137}
138
139static void
140addTask( void * vtask )
141{
142    struct tr_web_task * task = vtask;
143    const tr_session * session = task->session;
144
145    if( session && session->web )
146    {
147        CURL * e = curl_easy_init( );
148        struct tr_web * web = session->web;
149        const long timeout = getTimeoutFromURL( task->url );
150        const long verbose = getenv( "TR_CURL_VERBOSE" ) != NULL;
151        const char * user_agent = TR_NAME "/" LONG_VERSION_STRING;
152
153        dbgmsg( "adding task #%lu [%s]", task->tag, task->url );
154
155        if( !task->range && session->isProxyEnabled ) {
156            curl_easy_setopt( e, CURLOPT_PROXY, session->proxy );
157            curl_easy_setopt( e, CURLOPT_PROXYAUTH, CURLAUTH_ANY );
158            curl_easy_setopt( e, CURLOPT_PROXYPORT, session->proxyPort );
159            curl_easy_setopt( e, CURLOPT_PROXYTYPE,
160                                      getCurlProxyType( session->proxyType ) );
161        }
162        if( !task->range && session->isProxyAuthEnabled ) {
163            char * str = tr_strdup_printf( "%s:%s", session->proxyUsername,
164                                                    session->proxyPassword );
165            curl_easy_setopt( e, CURLOPT_PROXYUSERPWD, str );
166            tr_free( str );
167        }
168
169        curl_easy_setopt( e, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4 );
170        curl_easy_setopt( e, CURLOPT_TIMEOUT, timeout );
171        curl_easy_setopt( e, CURLOPT_CONNECTTIMEOUT, timeout-5 );
172        curl_easy_setopt( e, CURLOPT_SOCKOPTFUNCTION, sockoptfunction );
173        curl_easy_setopt( e, CURLOPT_SOCKOPTDATA, task );
174        curl_easy_setopt( e, CURLOPT_WRITEDATA, task );
175        curl_easy_setopt( e, CURLOPT_WRITEFUNCTION, writeFunc );
176        curl_easy_setopt( e, CURLOPT_DNS_CACHE_TIMEOUT, 1800L );
177        curl_easy_setopt( e, CURLOPT_FOLLOWLOCATION, 1L );
178        curl_easy_setopt( e, CURLOPT_AUTOREFERER, 1L );
179        curl_easy_setopt( e, CURLOPT_FORBID_REUSE, 1L );
180        curl_easy_setopt( e, CURLOPT_MAXREDIRS, -1L );
181        curl_easy_setopt( e, CURLOPT_NOSIGNAL, 1L );
182        curl_easy_setopt( e, CURLOPT_PRIVATE, task );
183        curl_easy_setopt( e, CURLOPT_SSL_VERIFYHOST, 0L );
184        curl_easy_setopt( e, CURLOPT_SSL_VERIFYPEER, 0L );
185        curl_easy_setopt( e, CURLOPT_URL, task->url );
186        curl_easy_setopt( e, CURLOPT_USERAGENT, user_agent );
187        curl_easy_setopt( e, CURLOPT_VERBOSE, verbose );
188        if( web->haveAddr )
189            curl_easy_setopt( e, CURLOPT_INTERFACE, tr_ntop_non_ts( &web->addr ) );
190        if( task->range )
191            curl_easy_setopt( e, CURLOPT_RANGE, task->range );
192
193        if( curl_multi_add_handle( web->multi, e ) == CURLM_OK )
194            ++web->taskCount;
195    }
196}
197
198/***
199****
200***/
201
202static void
203task_finish( struct tr_web_task * task, long response_code )
204{
205    dbgmsg( "finished web task %lu; got %ld", task->tag, response_code );
206
207    if( task->done_func != NULL )
208        task->done_func( task->session,
209                         response_code,
210                         EVBUFFER_DATA( task->response ),
211                         EVBUFFER_LENGTH( task->response ),
212                         task->done_func_user_data );
213    task_free( task );
214}
215
216static void
217remove_finished_tasks( tr_web * g )
218{
219    CURLMsg * msg;
220    int msgs_left;
221
222    while(( msg = curl_multi_info_read( g->multi, &msgs_left ))) {
223        if(( msg->msg == CURLMSG_DONE ) && ( msg->easy_handle != NULL )) {
224            long code;
225            struct tr_web_task * task;
226            CURL * e = msg->easy_handle;
227            curl_easy_getinfo( e, CURLINFO_PRIVATE, (void*)&task );
228            curl_easy_getinfo( e, CURLINFO_RESPONSE_CODE, &code );
229            curl_multi_remove_handle( g->multi, e );
230            curl_easy_cleanup( e );
231            task_finish( task, code );
232        }
233    }
234}
235
236static void
237restart_timer( tr_web * g )
238{
239    dbgmsg( "adding a timeout for %.1f seconds from now", g->timer_msec/1000.0 );
240    evtimer_del( &g->timer_event );
241    tr_timerAddMsec( &g->timer_event, g->timer_msec );
242}
243
244static void
245tr_multi_perform( tr_web * g, int fd, int curl_what )
246{
247    CURLMcode mcode;
248
249    dbgmsg( "check_run_count: %d taskCount", g->taskCount );
250
251    /* invoke libcurl's processing */
252    do
253        mcode = curl_multi_socket_action( g->multi, fd, curl_what, &g->taskCount );
254    while( mcode == CURLM_CALL_MULTI_SOCKET );
255
256    remove_finished_tasks( g );
257
258    if( g->closing && !g->taskCount )
259        web_free( g );
260    else
261        restart_timer( g );
262}
263
264/* libevent says that sock is ready to be processed, so wake up libcurl */
265static void
266event_cb( int fd, short ev_what, void * g )
267{
268    int curl_what = 0;
269    if( ev_what & EV_READ ) curl_what |= CURL_POLL_IN;
270    if( ev_what & EV_WRITE ) curl_what |= CURL_POLL_OUT;
271    tr_multi_perform( g, fd, curl_what );
272}
273
274/* CURLMOPT_SOCKETFUNCTION */
275static int
276sock_cb( CURL * e UNUSED, curl_socket_t fd, int action,
277         void * vweb, void * vevent )
278{
279    /*static int num_events = 0;*/
280    struct tr_web * web = vweb;
281    struct event * io_event = vevent;
282    dbgmsg( "sock_cb: action %d, fd %d, io_event %p", action, (int)fd, io_event );
283
284    if( ( action == CURL_POLL_NONE ) || ( action & CURL_POLL_REMOVE ) )
285    {
286        if( io_event != NULL )
287        {
288            event_del( io_event );
289            memset( io_event, TR_MEMORY_TRASH, sizeof( struct event ) );
290            tr_free( io_event );
291            curl_multi_assign( web->multi, fd, NULL );
292            /*fprintf( stderr, "-1 io_events to %d\n", --num_events );*/
293        }
294    }
295    else if( action & ( CURL_POLL_IN | CURL_POLL_OUT ) )
296    {
297        const short events = EV_PERSIST
298                           | (( action & CURL_POLL_IN ) ? EV_READ : 0 )
299                           | (( action & CURL_POLL_OUT ) ? EV_WRITE : 0 );
300
301        if( io_event != NULL )
302            event_del( io_event );
303        else {
304            io_event = tr_new0( struct event, 1 );
305            curl_multi_assign( web->multi, fd, io_event );
306            /*fprintf( stderr, "+1 io_events to %d\n", ++num_events );*/
307        }
308
309        dbgmsg( "enabling (libevent %hd, libcurl %d) polling on io_event %p, fd %d",
310                events, action, io_event, fd );
311        event_set( io_event, fd, events, event_cb, web );
312        event_add( io_event, NULL );
313    }
314    else tr_assert( 0, "unhandled action: %d", action );
315
316    return 0; /* libcurl documentation: "The callback MUST return 0." */
317}
318
319/* libevent says that timer_msec have passed, so wake up libcurl */
320static void
321libevent_timer_cb( int fd UNUSED, short what UNUSED, void * g )
322{
323    dbgmsg( "libevent timer is done" );
324    tr_multi_perform( g, CURL_SOCKET_TIMEOUT, 0 );
325}
326
327/* libcurl documentation: "If 0, it means you should proceed immediately
328 * without waiting for anything. If it returns -1, there's no timeout at all
329 * set ... (but) you must not wait too long (more than a few seconds perhaps)
330 * before you call curl_multi_perform() again."  */
331static void
332multi_timer_cb( CURLM * multi UNUSED, long timer_msec, void * vg )
333{
334    tr_web * g = vg;
335
336    g->timer_msec = timer_msec > 0 ? timer_msec : DEFAULT_TIMER_MSEC;
337
338    if( timer_msec < 1 )
339        tr_multi_perform( g, CURL_SOCKET_TIMEOUT, 0 );
340}
341
342/****
343*****
344****/
345
346void
347tr_webRun( tr_session         * session,
348           const char         * url,
349           const char         * range,
350           tr_web_done_func     done_func,
351           void               * done_func_user_data )
352{
353    if( session->web != NULL )
354    {
355        static unsigned long tag = 0;
356        struct tr_web_task * task = tr_new0( struct tr_web_task, 1 );
357        task->session = session;
358        task->url = tr_strdup( url );
359        task->range = tr_strdup( range );
360        task->done_func = done_func;
361        task->done_func_user_data = done_func_user_data;
362        task->tag = ++tag;
363        task->response = evbuffer_new( );
364        tr_runInEventThread( session, addTask, task );
365    }
366}
367
368void
369tr_webSetInterface( tr_web * web, const tr_address * addr )
370{
371    if(( web->haveAddr = ( addr != NULL )))
372        web->addr = *addr;
373}
374
375tr_web*
376tr_webInit( tr_session * session )
377{
378    tr_web * web;
379
380    /* try to enable ssl for https support; but if that fails,
381     * try a plain vanilla init */
382    if( curl_global_init( CURL_GLOBAL_SSL ) )
383        curl_global_init( 0 );
384
385    web = tr_new0( struct tr_web, 1 );
386    web->session = session;
387    web->timer_msec = DEFAULT_TIMER_MSEC; /* overwritten by multi_timer_cb() */
388    evtimer_set( &web->timer_event, libevent_timer_cb, web );
389
390    web->multi = curl_multi_init( );
391    curl_multi_setopt( web->multi, CURLMOPT_SOCKETDATA, web );
392    curl_multi_setopt( web->multi, CURLMOPT_SOCKETFUNCTION, sock_cb );
393    curl_multi_setopt( web->multi, CURLMOPT_TIMERDATA, web );
394    curl_multi_setopt( web->multi, CURLMOPT_TIMERFUNCTION, multi_timer_cb );
395
396    return web;
397}
398
399void
400tr_webClose( tr_web ** web_in )
401{
402    tr_web * web = *web_in;
403    *web_in = NULL;
404    if( web->taskCount < 1 )
405        web_free( web );
406    else
407        web->closing = 1;
408}
409
410/*****
411******
412******
413*****/
414
415const char *
416tr_webGetResponseStr( long code )
417{
418    switch( code )
419    {
420        case   0: return "No Response";
421        case 101: return "Switching Protocols";
422        case 200: return "OK";
423        case 201: return "Created";
424        case 202: return "Accepted";
425        case 203: return "Non-Authoritative Information";
426        case 204: return "No Content";
427        case 205: return "Reset Content";
428        case 206: return "Partial Content";
429        case 300: return "Multiple Choices";
430        case 301: return "Moved Permanently";
431        case 302: return "Found";
432        case 303: return "See Other";
433        case 304: return "Not Modified";
434        case 305: return "Use Proxy";
435        case 306: return "(Unused)";
436        case 307: return "Temporary Redirect";
437        case 400: return "Bad Request";
438        case 401: return "Unauthorized";
439        case 402: return "Payment Required";
440        case 403: return "Forbidden";
441        case 404: return "Not Found";
442        case 405: return "Method Not Allowed";
443        case 406: return "Not Acceptable";
444        case 407: return "Proxy Authentication Required";
445        case 408: return "Request Timeout";
446        case 409: return "Conflict";
447        case 410: return "Gone";
448        case 411: return "Length Required";
449        case 412: return "Precondition Failed";
450        case 413: return "Request Entity Too Large";
451        case 414: return "Request-URI Too Long";
452        case 415: return "Unsupported Media Type";
453        case 416: return "Requested Range Not Satisfiable";
454        case 417: return "Expectation Failed";
455        case 500: return "Internal Server Error";
456        case 501: return "Not Implemented";
457        case 502: return "Bad Gateway";
458        case 503: return "Service Unavailable";
459        case 504: return "Gateway Timeout";
460        case 505: return "HTTP Version Not Supported";
461        default:  return "Unknown Error";
462    }
463}
464
465void
466tr_http_escape( struct evbuffer  * out,
467                const char * str, int len, tr_bool escape_slashes )
468{
469    int i;
470
471    if( ( len < 0 ) && ( str != NULL ) )
472        len = strlen( str );
473
474    for( i = 0; i < len; i++ ) {
475        switch( str[i] ) {
476        case ',': case '-': case '.':
477        case '0': case '1': case '2': case '3': case '4':
478        case '5': case '6': case '7': case '8': case '9':
479        case 'a': case 'b': case 'c': case 'd': case 'e':
480        case 'f': case 'g': case 'h': case 'i': case 'j':
481        case 'k': case 'l': case 'm': case 'n': case 'o':
482        case 'p': case 'q': case 'r': case 's': case 't':
483        case 'u': case 'v': case 'w': case 'x': case 'y': case 'z':
484        case 'A': case 'B': case 'C': case 'D': case 'E':
485        case 'F': case 'G': case 'H': case 'I': case 'J':
486        case 'K': case 'L': case 'M': case 'N': case 'O':
487        case 'P': case 'Q': case 'R': case 'S': case 'T':
488        case 'U': case 'V': case 'W': case 'X': case 'Y': case 'Z':
489            evbuffer_add( out, &str[i], 1 );
490            break;
491        case '/':
492            if(!escape_slashes) {
493                evbuffer_add( out, &str[i], 1 );
494                break;
495            }
496            /* Fall through. */
497        default:
498            evbuffer_add_printf( out, "%%%02X", (unsigned)(str[i]&0xFF) );
499            break;
500        }
501    }
502}
503
504char *
505tr_http_unescape( const char * str, int len )
506{
507    char * tmp = curl_unescape( str, len );
508    char * ret = tr_strdup( tmp );
509    curl_free( tmp );
510    return ret;
511}
Note: See TracBrowser for help on using the repository browser.