source: trunk/libtransmission/web.c

Last change on this file was 14644, checked in by mikedld, 5 years ago

Remove useless checks and definitions (C99)

Now that MSVC support for C99 is quite good, remove previously needed but
now unused checks and definitions, like PRI* format macros (including
PRIdMAX and TR_PRIuSIZE, replaced with %jd and %zu) and inline macro.
Also, remove ssize_t typedef and replace few occurences with ev_ssize_t.
Also, remove check for stdbool.h availability (guaranteed by C99) and
include it unconditionally (except when in C++ mode).

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