source: trunk/libtransmission/web.c @ 6073

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

#800 initial support for GetRight?-style fetching of data through http and ftp servers specified in the .torrent's "url-list" tag

  • Property svn:keywords set to Date Rev Author Id
File size: 8.9 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 6073 2008-06-07 21:26:41Z 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
136    if( task->session && task->session->web )
137    {
138        struct tr_web * web = task->session->web;
139        CURL * ch;
140
141        ensureTimerIsRunning( web );
142
143        ++web->remain;
144        dbgmsg( "adding task #%lu [%s] (%d remain)", task->tag, task->url, web->remain );
145
146        ch = curl_easy_init( );
147        curl_easy_setopt( ch, CURLOPT_PRIVATE, task );
148        curl_easy_setopt( ch, CURLOPT_URL, task->url );
149        curl_easy_setopt( ch, CURLOPT_WRITEFUNCTION, writeFunc );
150        curl_easy_setopt( ch, CURLOPT_WRITEDATA, task );
151        curl_easy_setopt( ch, CURLOPT_USERAGENT, TR_NAME "/" LONG_VERSION_STRING );
152        curl_easy_setopt( ch, CURLOPT_SSL_VERIFYHOST, 0 );
153        curl_easy_setopt( ch, CURLOPT_SSL_VERIFYPEER, 0 );
154        curl_easy_setopt( ch, CURLOPT_FORBID_REUSE, 1 );
155        curl_easy_setopt( ch, CURLOPT_NOSIGNAL, 1 );
156        curl_easy_setopt( ch, CURLOPT_FOLLOWLOCATION, 1 );
157        curl_easy_setopt( ch, CURLOPT_MAXREDIRS, 5 );
158        curl_easy_setopt( ch, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4 );
159        if( task->range )
160            curl_easy_setopt( ch, CURLOPT_RANGE, task->range );
161        else /* don't set encoding if range is sent; it messes up binary data */
162            curl_easy_setopt( ch, CURLOPT_ENCODING, "" );
163        curl_multi_add_handle( web->cm, ch );
164    }
165}
166
167void
168tr_webRun( tr_session         * session,
169           const char         * url,
170           const char         * range,
171           tr_web_done_func   * done_func,
172           void               * done_func_user_data )
173{
174    if( session->web )
175    {
176        static unsigned long tag = 0;
177        struct tr_web_task * task;
178
179        task = tr_new0( struct tr_web_task, 1 );
180        task->session = session;
181        task->url = tr_strdup( url );
182        task->range = tr_strdup( range );
183        task->done_func = done_func;
184        task->done_func_user_data = done_func_user_data;
185        task->tag = ++tag;
186        task->response = evbuffer_new( );
187
188        tr_runInEventThread( session, addTask, task );
189    }
190}
191
192static void
193webDestroy( tr_web * web )
194{
195    dbgmsg( "deleting web timer" );
196    assert( !web->running );
197    evtimer_del( &web->timer );
198    curl_multi_cleanup( web->cm );
199    tr_free( web );
200}
201
202static void
203pulse( int socket UNUSED, short action UNUSED, void * vweb )
204{
205    tr_web * web = vweb;
206    assert( web->running );
207
208    pump( web );
209
210    evtimer_del( &web->timer );
211
212    web->running = web->remain > 0;
213
214    if( web->running ) {
215        struct timeval tv = tr_timevalMsec( PULSE_MSEC );
216        evtimer_add( &web->timer, &tv );
217    } else if( web->dying ) {
218        webDestroy( web );
219    } else {
220        dbgmsg( "stopping web timer" );
221    }
222}
223
224tr_web*
225tr_webInit( tr_session * session )
226{
227    static int curlInited = FALSE;
228    tr_web * web;
229
230    /* call curl_global_init if we haven't done it already.
231     * try to enable ssl for https support; but if that fails,
232     * try a plain vanilla init */ 
233    if( curlInited == FALSE ) {
234        curlInited = TRUE;
235        if( curl_global_init( CURL_GLOBAL_SSL ) )
236            curl_global_init( 0 );
237    }
238   
239    web = tr_new0( struct tr_web, 1 );
240    web->cm = curl_multi_init( );
241    web->session = session;
242
243    evtimer_set( &web->timer, pulse, web );
244#if CURL_CHECK_VERSION(7,16,3)
245    curl_multi_setopt( web->cm, CURLMOPT_MAXCONNECTS, 10 );
246#endif
247    pump( web );
248
249    return web;
250}
251
252void
253tr_webClose( tr_web ** web_in )
254{
255    tr_web * web = *web_in;
256    *web_in = NULL;
257
258    if( !web->running )
259        webDestroy( web );
260    else
261        web->dying = 1;
262}
263
264/***
265****
266***/
267
268static struct http_msg {
269    long code;
270    const char * text;
271} http_msg[] = {
272    { 101, "Switching Protocols" },
273    { 200, "OK" },
274    { 201, "Created" },
275    { 202, "Accepted" },
276    { 203, "Non-Authoritative Information" },
277    { 204, "No Content" },
278    { 205, "Reset Content" },
279    { 206, "Partial Content" },
280    { 300, "Multiple Choices" },
281    { 301, "Moved Permanently" },
282    { 302, "Found" },
283    { 303, "See Other" },
284    { 304, "Not Modified" },
285    { 305, "Use Proxy" },
286    { 306, "(Unused)" },
287    { 307, "Temporary Redirect" },
288    { 400, "Bad Request" },
289    { 401, "Unauthorized" },
290    { 402, "Payment Required" },
291    { 403, "Forbidden" },
292    { 404, "Not Found" },
293    { 405, "Method Not Allowed" },
294    { 406, "Not Acceptable" },
295    { 407, "Proxy Authentication Required" },
296    { 408, "Request Timeout" },
297    { 409, "Conflict" },
298    { 410, "Gone" },
299    { 411, "Length Required" },
300    { 412, "Precondition Failed" },
301    { 413, "Request Entity Too Large" },
302    { 414, "Request-URI Too Long" },
303    { 415, "Unsupported Media Type" },
304    { 416, "Requested Range Not Satisfiable" },
305    { 417, "Expectation Failed" },
306    { 500, "Internal Server Error" },
307    { 501, "Not Implemented" },
308    { 502, "Bad Gateway" },
309    { 503, "Service Unavailable" },
310    { 504, "Gateway Timeout" },
311    { 505, "HTTP Version Not Supported" },
312    { 0, NULL }
313};
314
315static int
316compareResponseCodes( const void * va, const void * vb )
317{
318    const long a = *(const long*) va;
319    const struct http_msg * b = vb;
320    return a - b->code;
321}
322
323const char *
324tr_webGetResponseStr( long code )
325{
326    struct http_msg * msg = bsearch( &code,
327                                     http_msg, 
328                                     sizeof( http_msg ) / sizeof( http_msg[0] ),
329                                     sizeof( http_msg[0] ),
330                                     compareResponseCodes );
331    return msg ? msg->text : "Unknown Error";
332}
Note: See TracBrowser for help on using the repository browser.