source: trunk/libtransmission/web.c @ 6447

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

possible fix for #1134: revert libtransmission/web.c back to r6354

  • Property svn:keywords set to Date Rev Author Id
File size: 9.8 KB
Line 
1/*
2 * This file Copyright (C) 2008 Charles Kerr <charles@rebelbase.com>
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 6447 2008-08-06 23:33:29Z charles $
11 */
12
13#include <assert.h>
14#include <stdlib.h> /* bsearch */
15
16#include <event.h>
17#include <curl/curl.h>
18
19#include "transmission.h"
20#include "trevent.h"
21#include "utils.h"
22#include "web.h"
23
24#define CURL_CHECK_VERSION(major,minor,micro)    \
25    (LIBCURL_VERSION_MAJOR > (major) || \
26     (LIBCURL_VERSION_MAJOR == (major) && LIBCURL_VERSION_MINOR > (minor)) || \
27     (LIBCURL_VERSION_MAJOR == (major) && LIBCURL_VERSION_MINOR == (minor) && \
28      LIBCURL_VERSION_PATCH >= (micro)))
29
30#define PULSE_MSEC 500
31
32#define dbgmsg(fmt...) tr_deepLog( __FILE__, __LINE__, "web", ##fmt )
33
34struct tr_web
35{
36    unsigned int dying     : 1;
37    unsigned int running   : 1;
38    int remain;
39    CURLM * cm;
40    tr_session * session;
41    struct event timer;
42};
43
44struct tr_web_task
45{
46    unsigned long tag;
47    struct evbuffer * response;
48    char * url;
49    char * range;
50    tr_session * session;
51    tr_web_done_func * done_func;
52    void * done_func_user_data;
53};
54
55static void
56processCompletedTasks( tr_web * web )
57{
58    CURL * easy;
59    CURLMsg * msg;
60
61    do {
62        /* this convoluted loop is from the "hiperinfo.c" sample which
63         * hints that removing an easy handle in curl_multi_info_read's
64         * loop may be unsafe */
65        int more;
66        easy = NULL;
67        while(( msg = curl_multi_info_read( web->cm, &more ))) {
68            if( msg->msg == CURLMSG_DONE ) {
69                easy = msg->easy_handle;
70                break;
71            }
72        }
73        if( easy ) {
74            struct tr_web_task * task;
75            long response_code;
76            curl_easy_getinfo( easy, CURLINFO_PRIVATE, &task );
77            curl_easy_getinfo( easy, CURLINFO_RESPONSE_CODE, &response_code );
78            --web->remain;
79            dbgmsg( "task #%lu done (%d remain)", task->tag, web->remain );
80            task->done_func( web->session,
81                             response_code,
82                             EVBUFFER_DATA(task->response),
83                             EVBUFFER_LENGTH(task->response),
84                             task->done_func_user_data );
85            curl_multi_remove_handle( web->cm, easy );
86            curl_easy_cleanup( easy );
87            evbuffer_free( task->response );
88            tr_free( task->range );
89            tr_free( task->url );
90            tr_free( task );
91        }
92    } while( easy );
93}
94
95static void
96pump( tr_web * web )
97{
98    int unused;
99    CURLMcode rc;
100    do {
101        rc = curl_multi_perform( web->cm, &unused );
102    } while( rc == CURLM_CALL_MULTI_PERFORM );
103    if ( rc == CURLM_OK  )
104        processCompletedTasks( web );
105    else
106        tr_err( "%s", curl_multi_strerror(rc) );
107}
108
109static size_t
110writeFunc( void * ptr, size_t size, size_t nmemb, void * task )
111{
112    const size_t byteCount = size * nmemb;
113    evbuffer_add( ((struct tr_web_task*)task)->response, ptr, byteCount );
114    return byteCount;
115}
116
117static void
118ensureTimerIsRunning( tr_web * web )
119{
120    if( !web->running )
121    {
122        struct timeval tv = tr_timevalMsec( PULSE_MSEC );
123        dbgmsg( "starting web timer" );
124        web->running = 1;
125        evtimer_add( &web->timer, &tv );
126    }
127}
128
129static int
130getCurlProxyType( tr_proxy_type t )
131{
132    switch( t )
133    {
134        case TR_PROXY_SOCKS4: return CURLPROXY_SOCKS4;
135        case TR_PROXY_SOCKS5: return CURLPROXY_SOCKS5;
136        default:              return CURLPROXY_HTTP;
137    }
138}
139
140
141
142static void
143addTask( void * vtask )
144{
145    struct tr_web_task * task = vtask;
146    const tr_handle * session = task->session;
147
148    if( session && session->web )
149    {
150        struct tr_web * web = session->web;
151        CURL * ch;
152
153        ensureTimerIsRunning( web );
154
155        ++web->remain;
156        dbgmsg( "adding task #%lu [%s] (%d remain)", task->tag, task->url, web->remain );
157
158        ch = curl_easy_init( );
159
160        if( !task->range && session->isProxyEnabled ) {
161            curl_easy_setopt( ch, CURLOPT_PROXY, session->proxy );
162            curl_easy_setopt( ch, CURLOPT_PROXYPORT, session->proxyPort );
163            curl_easy_setopt( ch, CURLOPT_PROXYTYPE, getCurlProxyType( session->proxyType ) );
164            curl_easy_setopt( ch, CURLOPT_PROXYAUTH, CURLAUTH_ANY );
165        }
166        if( !task->range && session->isProxyAuthEnabled ) {
167            char * str = tr_strdup_printf( "%s:%s", session->proxyUsername, session->proxyPassword );
168            curl_easy_setopt( ch, CURLOPT_PROXYUSERPWD, str );
169            tr_free( str );
170        }
171
172        curl_easy_setopt( ch, CURLOPT_PRIVATE, task );
173        curl_easy_setopt( ch, CURLOPT_URL, task->url );
174        curl_easy_setopt( ch, CURLOPT_WRITEFUNCTION, writeFunc );
175        curl_easy_setopt( ch, CURLOPT_WRITEDATA, task );
176        curl_easy_setopt( ch, CURLOPT_USERAGENT, TR_NAME "/" LONG_VERSION_STRING );
177        curl_easy_setopt( ch, CURLOPT_SSL_VERIFYHOST, 0 );
178        curl_easy_setopt( ch, CURLOPT_SSL_VERIFYPEER, 0 );
179        curl_easy_setopt( ch, CURLOPT_FORBID_REUSE, 1 );
180        curl_easy_setopt( ch, CURLOPT_NOSIGNAL, 1 );
181        curl_easy_setopt( ch, CURLOPT_FOLLOWLOCATION, 1 );
182        curl_easy_setopt( ch, CURLOPT_MAXREDIRS, 5 );
183        curl_easy_setopt( ch, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4 );
184        if( task->range )
185            curl_easy_setopt( ch, CURLOPT_RANGE, task->range );
186        else /* don't set encoding if range is sent; it messes up binary data */
187            curl_easy_setopt( ch, CURLOPT_ENCODING, "" );
188        curl_multi_add_handle( web->cm, ch );
189    }
190}
191
192void
193tr_webRun( tr_session         * session,
194           const char         * url,
195           const char         * range,
196           tr_web_done_func   * done_func,
197           void               * done_func_user_data )
198{
199    if( session->web )
200    {
201        static unsigned long tag = 0;
202        struct tr_web_task * task;
203
204        task = tr_new0( struct tr_web_task, 1 );
205        task->session = session;
206        task->url = tr_strdup( url );
207        task->range = tr_strdup( range );
208        task->done_func = done_func;
209        task->done_func_user_data = done_func_user_data;
210        task->tag = ++tag;
211        task->response = evbuffer_new( );
212
213        tr_runInEventThread( session, addTask, task );
214    }
215}
216
217static void
218webDestroy( tr_web * web )
219{
220    dbgmsg( "deleting web timer" );
221    assert( !web->running );
222    evtimer_del( &web->timer );
223    curl_multi_cleanup( web->cm );
224    tr_free( web );
225}
226
227static void
228pulse( int socket UNUSED, short action UNUSED, void * vweb )
229{
230    tr_web * web = vweb;
231    assert( web->running );
232
233    pump( web );
234
235    evtimer_del( &web->timer );
236
237    web->running = web->remain > 0;
238
239    if( web->running ) {
240        struct timeval tv = tr_timevalMsec( PULSE_MSEC );
241        evtimer_add( &web->timer, &tv );
242    } else if( web->dying ) {
243        webDestroy( web );
244    } else {
245        dbgmsg( "stopping web timer" );
246    }
247}
248
249tr_web*
250tr_webInit( tr_session * session )
251{
252    static int curlInited = FALSE;
253    tr_web * web;
254
255    /* call curl_global_init if we haven't done it already.
256     * try to enable ssl for https support; but if that fails,
257     * try a plain vanilla init */ 
258    if( curlInited == FALSE ) {
259        curlInited = TRUE;
260        if( curl_global_init( CURL_GLOBAL_SSL ) )
261            curl_global_init( 0 );
262    }
263   
264    web = tr_new0( struct tr_web, 1 );
265    web->cm = curl_multi_init( );
266    web->session = session;
267
268    evtimer_set( &web->timer, pulse, web );
269#if CURL_CHECK_VERSION(7,16,3)
270    curl_multi_setopt( web->cm, CURLMOPT_MAXCONNECTS, 10 );
271#endif
272    pump( web );
273
274    return web;
275}
276
277void
278tr_webClose( tr_web ** web_in )
279{
280    tr_web * web = *web_in;
281    *web_in = NULL;
282
283    if( !web->running )
284        webDestroy( web );
285    else
286        web->dying = 1;
287}
288
289/***
290****
291***/
292
293static struct http_msg {
294    long code;
295    const char * text;
296} http_msg[] = {
297    { 101, "Switching Protocols" },
298    { 200, "OK" },
299    { 201, "Created" },
300    { 202, "Accepted" },
301    { 203, "Non-Authoritative Information" },
302    { 204, "No Content" },
303    { 205, "Reset Content" },
304    { 206, "Partial Content" },
305    { 300, "Multiple Choices" },
306    { 301, "Moved Permanently" },
307    { 302, "Found" },
308    { 303, "See Other" },
309    { 304, "Not Modified" },
310    { 305, "Use Proxy" },
311    { 306, "(Unused)" },
312    { 307, "Temporary Redirect" },
313    { 400, "Bad Request" },
314    { 401, "Unauthorized" },
315    { 402, "Payment Required" },
316    { 403, "Forbidden" },
317    { 404, "Not Found" },
318    { 405, "Method Not Allowed" },
319    { 406, "Not Acceptable" },
320    { 407, "Proxy Authentication Required" },
321    { 408, "Request Timeout" },
322    { 409, "Conflict" },
323    { 410, "Gone" },
324    { 411, "Length Required" },
325    { 412, "Precondition Failed" },
326    { 413, "Request Entity Too Large" },
327    { 414, "Request-URI Too Long" },
328    { 415, "Unsupported Media Type" },
329    { 416, "Requested Range Not Satisfiable" },
330    { 417, "Expectation Failed" },
331    { 500, "Internal Server Error" },
332    { 501, "Not Implemented" },
333    { 502, "Bad Gateway" },
334    { 503, "Service Unavailable" },
335    { 504, "Gateway Timeout" },
336    { 505, "HTTP Version Not Supported" },
337    { 0, NULL }
338};
339
340static int
341compareResponseCodes( const void * va, const void * vb )
342{
343    const long a = *(const long*) va;
344    const struct http_msg * b = vb;
345    return a - b->code;
346}
347
348const char *
349tr_webGetResponseStr( long code )
350{
351    struct http_msg * msg = bsearch( &code,
352                                     http_msg, 
353                                     sizeof( http_msg ) / sizeof( http_msg[0] ),
354                                     sizeof( http_msg[0] ),
355                                     compareResponseCodes );
356    return msg ? msg->text : "Unknown Error";
357}
Note: See TracBrowser for help on using the repository browser.