source: trunk/libtransmission/web.c @ 6120

Last change on this file since 6120 was 6120, checked in by charles, 14 years ago

wire up the backend proxy support.

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