Ticket #1079: web.c

File web.c, 17.7 KB (added by vipjml, 9 years ago)

use this "web.c" can fix this problen, this web.c is modified based on 2.77

Line 
1/*
2 * This file Copyright (C) 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 13625 2012-12-05 17:29:46Z jordan $
11 */
12
13#include <string.h> /* strlen (), strstr () */
14#include <stdlib.h> /* getenv () */
15
16#ifdef WIN32
17  #include <ws2tcpip.h>
18#else
19  #include <sys/select.h>
20#endif
21
22#include <curl/curl.h>
23
24#include <event2/buffer.h>
25
26#include "transmission.h"
27#include "net.h" /* tr_address */
28#include "platform.h" /* mutex */
29#include "session.h"
30#include "trevent.h" /* tr_runInEventThread () */
31#include "utils.h"
32#include "version.h" /* User-Agent */
33#include "web.h"
34#include "list.h"
35#if LIBCURL_VERSION_NUM >= 0x070F06 /* CURLOPT_SOCKOPT* was added in 7.15.6 */
36 #define USE_LIBCURL_SOCKOPT
37#endif
38
39enum
40{
41    THREADFUNC_MAX_SLEEP_MSEC = 200,
42};
43
44#if 0
45#define dbgmsg(...) \
46    do { \
47        fprintf (stderr, __VA_ARGS__); \
48        fprintf (stderr, "\n"); \
49    } while (0)
50#else
51#define dbgmsg(...) \
52    do { \
53        if (tr_deepLoggingIsActive ()) \
54            tr_deepLog (__FILE__, __LINE__, "web", __VA_ARGS__); \
55    } while (0)
56#endif
57
58/***
59****
60***/
61tr_list * stopeasyhandle=NULL;
62struct tr_web_task
63{
64    long code;
65    long timeout_secs;
66    bool did_connect;
67    bool did_timeout;
68    struct evbuffer * response;
69    struct evbuffer * freebuf;
70    char * url;
71    char * range;
72    char * cookies;
73    tr_session * session;
74    tr_web_done_func * done_func;
75    void * done_func_user_data;
76    CURL * curl_easy;
77    struct tr_web_task * next;
78};
79
80static void
81task_free (struct tr_web_task * task)
82{
83    if (task->freebuf)
84        evbuffer_free (task->freebuf);
85    tr_free (task->cookies);
86    tr_free (task->range);
87    tr_free (task->url);
88    tr_free (task);
89}
90
91/***
92****
93***/
94
95struct tr_web
96{
97    bool curl_verbose;
98    bool curl_ssl_verify;
99    const char * curl_ca_bundle;
100    int close_mode;
101    struct tr_web_task * tasks;
102    tr_lock * taskLock;
103    char * cookie_filename;
104};
105
106/***
107****
108***/
109
110static size_t
111writeFunc (void * ptr, size_t size, size_t nmemb, void * vtask)
112{
113    const size_t byteCount = size * nmemb;
114    struct tr_web_task * task = vtask;
115        unsigned int n=tr_bandwidthClamp(&(task->session->bandwidth), TR_DOWN, nmemb );
116        if(n<1&&task->freebuf==NULL/*only limit webseed*/) {
117                tr_list_append(&stopeasyhandle,task->curl_easy);
118                return CURL_WRITEFUNC_PAUSE;
119        }
120    evbuffer_add (task->response, ptr, byteCount);
121    dbgmsg ("wrote %zu bytes to task %p's buffer", byteCount, task);
122    return byteCount;
123}
124
125#ifdef USE_LIBCURL_SOCKOPT
126static int
127sockoptfunction (void * vtask, curl_socket_t fd, curlsocktype purpose UNUSED)
128{
129    struct tr_web_task * task = vtask;
130    const bool isScrape = strstr (task->url, "scrape") != NULL;
131    const bool isAnnounce = strstr (task->url, "announce") != NULL;
132
133    /* announce and scrape requests have tiny payloads. */
134    if (isScrape || isAnnounce)
135    {
136        const int sndbuf = 1024;
137        const int rcvbuf = isScrape ? 2048 : 3072;
138        setsockopt (fd, SOL_SOCKET, SO_SNDBUF, &sndbuf, sizeof (sndbuf));
139        setsockopt (fd, SOL_SOCKET, SO_RCVBUF, &rcvbuf, sizeof (rcvbuf));
140    }
141
142    /* return nonzero if this function encountered an error */
143    return 0;
144}
145#endif
146
147static long
148getTimeoutFromURL (const struct tr_web_task * task)
149{
150    long timeout;
151    const tr_session * session = task->session;
152
153    if (!session || session->isClosed) timeout = 20L;
154    else if (strstr (task->url, "scrape") != NULL) timeout = 30L;
155    else if (strstr (task->url, "announce") != NULL) timeout = 90L;
156    else timeout = 240L;
157
158    return timeout;
159}
160
161static CURL *
162createEasy (tr_session * s, struct tr_web * web, struct tr_web_task * task)
163{
164    bool is_default_value;
165    const tr_address * addr;
166    CURL * e = task->curl_easy = curl_easy_init ();
167
168    task->timeout_secs = getTimeoutFromURL (task);
169
170    curl_easy_setopt (e, CURLOPT_AUTOREFERER, 1L);
171    curl_easy_setopt (e, CURLOPT_COOKIEFILE, web->cookie_filename);
172    curl_easy_setopt (e, CURLOPT_ENCODING, "gzip;q=1.0, deflate, identity");
173    curl_easy_setopt (e, CURLOPT_FOLLOWLOCATION, 1L);
174    curl_easy_setopt (e, CURLOPT_MAXREDIRS, -1L);
175    curl_easy_setopt (e, CURLOPT_NOSIGNAL, 1L);
176    curl_easy_setopt (e, CURLOPT_PRIVATE, task);
177#ifdef USE_LIBCURL_SOCKOPT
178    curl_easy_setopt (e, CURLOPT_SOCKOPTFUNCTION, sockoptfunction);
179    curl_easy_setopt (e, CURLOPT_SOCKOPTDATA, task);
180#endif
181    if (web->curl_ssl_verify)
182        curl_easy_setopt (e, CURLOPT_CAINFO, web->curl_ca_bundle);
183    else {
184        curl_easy_setopt (e, CURLOPT_SSL_VERIFYHOST, 0L);
185        curl_easy_setopt (e, CURLOPT_SSL_VERIFYPEER, 0L);
186    }
187    curl_easy_setopt (e, CURLOPT_TIMEOUT, task->timeout_secs);
188    curl_easy_setopt (e, CURLOPT_URL, task->url);
189    curl_easy_setopt (e, CURLOPT_USERAGENT, TR_NAME "/" SHORT_VERSION_STRING);
190    curl_easy_setopt (e, CURLOPT_VERBOSE, (long)(web->curl_verbose?1:0));
191    curl_easy_setopt (e, CURLOPT_WRITEDATA, task);
192    curl_easy_setopt (e, CURLOPT_WRITEFUNCTION, writeFunc);
193
194    if (((addr = tr_sessionGetPublicAddress (s, TR_AF_INET, &is_default_value))) && !is_default_value)
195        curl_easy_setopt (e, CURLOPT_INTERFACE, tr_address_to_string (addr));
196    else if (((addr = tr_sessionGetPublicAddress (s, TR_AF_INET6, &is_default_value))) && !is_default_value)
197        curl_easy_setopt (e, CURLOPT_INTERFACE, tr_address_to_string (addr));
198
199    if (task->cookies != NULL)
200        curl_easy_setopt (e, CURLOPT_COOKIE, task->cookies);
201
202    if (task->range != NULL) {
203        curl_easy_setopt (e, CURLOPT_RANGE, task->range);
204        /* don't bother asking the server to compress webseed fragments */
205        curl_easy_setopt (e, CURLOPT_ENCODING, "identity");
206    }
207
208    return e;
209}
210
211/***
212****
213***/
214
215static void
216task_finish_func (void * vtask)
217{
218    struct tr_web_task * task = vtask;
219    dbgmsg ("finished web task %p; got %ld", task, task->code);
220
221    if (task->done_func != NULL)
222        task->done_func (task->session,
223                         task->did_connect,
224                         task->did_timeout,
225                         task->code,
226                         evbuffer_pullup (task->response, -1),
227                         evbuffer_get_length (task->response),
228                         task->done_func_user_data);
229
230    task_free (task);
231}
232
233/****
234*****
235****/
236
237struct tr_web_task *
238tr_webRun (tr_session         * session,
239           const char         * url,
240           const char         * range,
241           const char         * cookies,
242           tr_web_done_func     done_func,
243           void               * done_func_user_data)
244{
245    return tr_webRunWithBuffer (session, url, range, cookies,
246                                done_func, done_func_user_data,
247                                NULL);
248}
249
250struct tr_web_task *
251tr_webRunWithBuffer (tr_session         * session,
252                     const char         * url,
253                     const char         * range,
254                     const char         * cookies,
255                     tr_web_done_func     done_func,
256                     void               * done_func_user_data,
257                     struct evbuffer    * buffer)
258{
259    struct tr_web * web = session->web;
260
261    if (web != NULL)
262    {
263        struct tr_web_task * task = tr_new0 (struct tr_web_task, 1);
264
265        task->session = session;
266        task->url = tr_strdup (url);
267        task->range = tr_strdup (range);
268        task->cookies = tr_strdup (cookies);
269        task->done_func = done_func;
270        task->done_func_user_data = done_func_user_data;
271        task->response = buffer ? buffer : evbuffer_new ();
272        task->freebuf = buffer ? NULL : task->response;
273
274        tr_lockLock (web->taskLock);
275        task->next = web->tasks;
276        web->tasks = task;
277        tr_lockUnlock (web->taskLock);
278        return task;
279    }
280    return NULL;
281}
282
283/**
284 * Portability wrapper for select ().
285 *
286 * http://msdn.microsoft.com/en-us/library/ms740141%28VS.85%29.aspx
287 * On win32, any two of the parameters, readfds, writefds, or exceptfds,
288 * can be given as null. At least one must be non-null, and any non-null
289 * descriptor set must contain at least one handle to a socket.
290 */
291static void
292tr_select (int nfds,
293           fd_set * r_fd_set, fd_set * w_fd_set, fd_set * c_fd_set,
294           struct timeval  * t)
295{
296#ifdef WIN32
297    if (!r_fd_set->fd_count && !w_fd_set->fd_count && !c_fd_set->fd_count)
298    {
299        const long int msec = t->tv_sec*1000 + t->tv_usec/1000;
300        tr_wait_msec (msec);
301    }
302    else if (select (0, r_fd_set->fd_count ? r_fd_set : NULL,
303                        w_fd_set->fd_count ? w_fd_set : NULL,
304                        c_fd_set->fd_count ? c_fd_set : NULL, t) < 0)
305    {
306        char errstr[512];
307        const int e = EVUTIL_SOCKET_ERROR ();
308        tr_net_strerror (errstr, sizeof (errstr), e);
309        dbgmsg ("Error: select (%d) %s", e, errstr);
310    }
311#else
312    select (nfds, r_fd_set, w_fd_set, c_fd_set, t);
313#endif
314}
315
316static void
317tr_webThreadFunc (void * vsession)
318{
319    CURLM * multi;
320    struct tr_web * web;
321    int taskCount = 0;
322    struct tr_web_task * task;
323    tr_session * session = vsession;
324
325    /* try to enable ssl for https support; but if that fails,
326     * try a plain vanilla init */
327    if (curl_global_init (CURL_GLOBAL_SSL))
328        curl_global_init (0);
329
330    web = tr_new0 (struct tr_web, 1);
331    web->close_mode = ~0;
332    web->taskLock = tr_lockNew ();
333    web->tasks = NULL;
334    web->curl_verbose = getenv ("TR_CURL_VERBOSE") != NULL;
335    web->curl_ssl_verify = getenv ("TR_CURL_SSL_VERIFY") != NULL;
336    web->curl_ca_bundle = getenv ("CURL_CA_BUNDLE");
337    if (web->curl_ssl_verify) {
338        tr_ninf ("web", "will verify tracker certs using envvar CURL_CA_BUNDLE: %s",
339                  web->curl_ca_bundle == NULL ? "none" : web->curl_ca_bundle);
340        tr_ninf ("web", "NB: this only works if you built against libcurl with openssl or gnutls, NOT nss");
341        tr_ninf ("web", "NB: invalid certs will show up as 'Could not connect to tracker' like many other errors");
342    }
343    web->cookie_filename = tr_buildPath (session->configDir, "cookies.txt", NULL);
344
345    multi = curl_multi_init ();
346    session->web = web;
347
348    for (;;)
349    {
350        long msec;
351        int unused;
352        CURLMsg * msg;
353        CURLMcode mcode;
354
355        if (web->close_mode == TR_WEB_CLOSE_NOW)
356            break;
357        if ((web->close_mode == TR_WEB_CLOSE_WHEN_IDLE) && (web->tasks == NULL))
358            break;
359
360        /* add tasks from the queue */
361        tr_lockLock (web->taskLock);
362        while (web->tasks != NULL)
363        {
364            /* pop the task */
365            task = web->tasks;
366            web->tasks = task->next;
367            task->next = NULL;
368
369            dbgmsg ("adding task to curl: [%s]", task->url);
370            curl_multi_add_handle (multi, createEasy (session, web, task));
371            /*fprintf (stderr, "adding a task.. taskCount is now %d\n", taskCount);*/
372            ++taskCount;
373        }
374        tr_lockUnlock (web->taskLock);
375               
376                //restart stopped curl handle;
377                CURL *handle;
378                int n=tr_list_size(stopeasyhandle);
379                while(n--){
380                        handle=tr_list_pop_front(&stopeasyhandle);
381                        curl_easy_pause(handle,CURLPAUSE_CONT);
382                }
383        /* maybe wait a little while before calling curl_multi_perform () */
384        msec = 0;
385        curl_multi_timeout (multi, &msec);
386        if (msec < 0)
387            msec = THREADFUNC_MAX_SLEEP_MSEC;
388        if (session->isClosed)
389            msec = 100; /* on shutdown, call perform () more frequently */
390        if (msec > 0)
391        {
392            int usec;
393            int max_fd;
394            struct timeval t;
395            fd_set r_fd_set, w_fd_set, c_fd_set;
396
397            max_fd = 0;
398            FD_ZERO (&r_fd_set);
399            FD_ZERO (&w_fd_set);
400            FD_ZERO (&c_fd_set);
401            curl_multi_fdset (multi, &r_fd_set, &w_fd_set, &c_fd_set, &max_fd);
402
403            if (msec > THREADFUNC_MAX_SLEEP_MSEC)
404                msec = THREADFUNC_MAX_SLEEP_MSEC;
405
406            usec = msec * 1000;
407            t.tv_sec =  usec / 1000000;
408            t.tv_usec = usec % 1000000;
409            tr_select (max_fd+1, &r_fd_set, &w_fd_set, &c_fd_set, &t);
410        }
411
412        /* call curl_multi_perform () */
413        do {
414            mcode = curl_multi_perform (multi, &unused);
415        } while (mcode == CURLM_CALL_MULTI_PERFORM);
416
417        /* pump completed tasks from the multi */
418        while ((msg = curl_multi_info_read (multi, &unused)))
419        {
420            if ((msg->msg == CURLMSG_DONE) && (msg->easy_handle != NULL))
421            {
422                double total_time;
423                struct tr_web_task * task;
424                long req_bytes_sent;
425                CURL * e = msg->easy_handle;
426                curl_easy_getinfo (e, CURLINFO_PRIVATE, (void*)&task);
427                curl_easy_getinfo (e, CURLINFO_RESPONSE_CODE, &task->code);
428                curl_easy_getinfo (e, CURLINFO_REQUEST_SIZE, &req_bytes_sent);
429                curl_easy_getinfo (e, CURLINFO_TOTAL_TIME, &total_time);
430                task->did_connect = task->code>0 || req_bytes_sent>0;
431                task->did_timeout = !task->code && (total_time >= task->timeout_secs);
432                curl_multi_remove_handle (multi, e);
433                curl_easy_cleanup (e);
434/*fprintf (stderr, "removing a completed task.. taskCount is now %d (response code: %d, response len: %d)\n", taskCount, (int)task->code, (int)evbuffer_get_length (task->response));*/
435                tr_runInEventThread (task->session, task_finish_func, task);
436                --taskCount;
437            }
438        }
439    }
440
441    /* Discard any remaining tasks.
442     * This is rare, but can happen on shutdown with unresponsive trackers. */
443    while (web->tasks != NULL) {
444        task = web->tasks;
445        web->tasks = task->next;
446        dbgmsg ("Discarding task \"%s\"", task->url);
447        task_free (task);
448    }
449
450    /* cleanup */
451    curl_multi_cleanup (multi);
452    tr_lockFree (web->taskLock);
453    tr_free (web->cookie_filename);
454    tr_free (web);
455    session->web = NULL;
456}
457
458void
459tr_webInit (tr_session * session)
460{
461    tr_threadNew (tr_webThreadFunc, session);
462}
463
464void
465tr_webClose (tr_session * session, tr_web_close_mode close_mode)
466{
467    if (session->web != NULL)
468    {
469        session->web->close_mode = close_mode;
470
471        if (close_mode == TR_WEB_CLOSE_NOW)
472            while (session->web != NULL)
473                tr_wait_msec (100);
474    }
475}
476
477void
478tr_webGetTaskInfo (struct tr_web_task * task, tr_web_task_info info, void * dst)
479{
480    curl_easy_getinfo (task->curl_easy, (CURLINFO) info, dst);
481}
482
483/*****
484******
485******
486*****/
487
488const char *
489tr_webGetResponseStr (long code)
490{
491    switch (code)
492    {
493        case   0: return "No Response";
494        case 101: return "Switching Protocols";
495        case 200: return "OK";
496        case 201: return "Created";
497        case 202: return "Accepted";
498        case 203: return "Non-Authoritative Information";
499        case 204: return "No Content";
500        case 205: return "Reset Content";
501        case 206: return "Partial Content";
502        case 300: return "Multiple Choices";
503        case 301: return "Moved Permanently";
504        case 302: return "Found";
505        case 303: return "See Other";
506        case 304: return "Not Modified";
507        case 305: return "Use Proxy";
508        case 306: return " (Unused)";
509        case 307: return "Temporary Redirect";
510        case 400: return "Bad Request";
511        case 401: return "Unauthorized";
512        case 402: return "Payment Required";
513        case 403: return "Forbidden";
514        case 404: return "Not Found";
515        case 405: return "Method Not Allowed";
516        case 406: return "Not Acceptable";
517        case 407: return "Proxy Authentication Required";
518        case 408: return "Request Timeout";
519        case 409: return "Conflict";
520        case 410: return "Gone";
521        case 411: return "Length Required";
522        case 412: return "Precondition Failed";
523        case 413: return "Request Entity Too Large";
524        case 414: return "Request-URI Too Long";
525        case 415: return "Unsupported Media Type";
526        case 416: return "Requested Range Not Satisfiable";
527        case 417: return "Expectation Failed";
528        case 500: return "Internal Server Error";
529        case 501: return "Not Implemented";
530        case 502: return "Bad Gateway";
531        case 503: return "Service Unavailable";
532        case 504: return "Gateway Timeout";
533        case 505: return "HTTP Version Not Supported";
534        default:  return "Unknown Error";
535    }
536}
537
538void
539tr_http_escape (struct evbuffer  * out,
540                const char * str, int len, bool escape_slashes)
541{
542    const char * end;
543
544    if ((len < 0) && (str != NULL))
545        len = strlen (str);
546
547    for (end=str+len; str && str!=end; ++str) {
548        if ((*str == ',')
549            || (*str == '-')
550            || (*str == '.')
551            || (('0' <= *str) && (*str <= '9'))
552            || (('A' <= *str) && (*str <= 'Z'))
553            || (('a' <= *str) && (*str <= 'z'))
554            || ((*str == '/') && (!escape_slashes)))
555            evbuffer_add_printf (out, "%c", *str);
556        else
557            evbuffer_add_printf (out, "%%%02X", (unsigned)(*str&0xFF));
558    }
559}
560
561char *
562tr_http_unescape (const char * str, int len)
563{
564    char * tmp = curl_unescape (str, len);
565    char * ret = tr_strdup (tmp);
566    curl_free (tmp);
567    return ret;
568}
569
570static int
571is_rfc2396_alnum (uint8_t ch)
572{
573    return ('0' <= ch && ch <= '9')
574        || ('A' <= ch && ch <= 'Z')
575        || ('a' <= ch && ch <= 'z')
576        || ch == '.'
577        || ch == '-'
578        || ch == '_'
579        || ch == '~';
580}
581
582void
583tr_http_escape_sha1 (char * out, const uint8_t * sha1_digest)
584{
585    const uint8_t * in = sha1_digest;
586    const uint8_t * end = in + SHA_DIGEST_LENGTH;
587
588    while (in != end)
589        if (is_rfc2396_alnum (*in))
590            *out++ = (char) *in++;
591        else
592            out += tr_snprintf (out, 4, "%%%02x", (unsigned int)*in++);
593
594    *out = '\0';
595}