source: trunk/libtransmission/web.c @ 14024

Last change on this file since 14024 was 14024, checked in by jordan, 9 years ago

(libT) fix memory leak regression in the nightlies reported by x190

  • Property svn:keywords set to Date Rev Author Id
File size: 16.7 KB
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 14024 2013-02-17 23:00:11Z 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 "log.h"
28#include "net.h" /* tr_address */
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 = 1000,
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  long code;
66  long timeout_secs;
67  bool did_connect;
68  bool did_timeout;
69  struct evbuffer * response;
70  struct evbuffer * freebuf;
71  char * url;
72  char * range;
73  char * cookies;
74  tr_session * session;
75  tr_web_done_func * done_func;
76  void * done_func_user_data;
77  CURL * curl_easy;
78  struct tr_web_task * next;
79};
80
81static void
82task_free (struct tr_web_task * task)
83{
84  if (task->freebuf)
85    evbuffer_free (task->freebuf);
86  tr_free (task->cookies);
87  tr_free (task->range);
88  tr_free (task->url);
89  tr_free (task);
90}
91
92/***
93****
94***/
95
96struct tr_web
97{
98  bool curl_verbose;
99  bool curl_ssl_verify;
100  const char * curl_ca_bundle;
101  int close_mode;
102  struct tr_web_task * tasks;
103  tr_lock * taskLock;
104  char * cookie_filename;
105};
106
107/***
108****
109***/
110
111static size_t
112writeFunc (void * ptr, size_t size, size_t nmemb, void * vtask)
113{
114  const size_t byteCount = size * nmemb;
115  struct tr_web_task * task = vtask;
116  evbuffer_add (task->response, ptr, byteCount);
117  dbgmsg ("wrote %zu bytes to task %p's buffer", byteCount, task);
118  return byteCount;
119}
120
121#ifdef USE_LIBCURL_SOCKOPT
122static int
123sockoptfunction (void * vtask, curl_socket_t fd, curlsocktype purpose UNUSED)
124{
125  struct tr_web_task * task = vtask;
126  const bool isScrape = strstr (task->url, "scrape") != NULL;
127  const bool isAnnounce = strstr (task->url, "announce") != NULL;
128
129  /* announce and scrape requests have tiny payloads. */
130  if (isScrape || isAnnounce)
131    {
132      const int sndbuf = isScrape ? 4096 : 1024;
133      const int rcvbuf = isScrape ? 4096 : 3072;
134      setsockopt (fd, SOL_SOCKET, SO_SNDBUF, &sndbuf, sizeof (sndbuf));
135      setsockopt (fd, SOL_SOCKET, SO_RCVBUF, &rcvbuf, sizeof (rcvbuf));
136    }
137
138  /* return nonzero if this function encountered an error */
139  return 0;
140}
141#endif
142
143static long
144getTimeoutFromURL (const struct tr_web_task * task)
145{
146  long timeout;
147  const tr_session * session = task->session;
148
149  if (!session || session->isClosed) timeout = 20L;
150  else if (strstr (task->url, "scrape") != NULL) timeout = 30L;
151  else if (strstr (task->url, "announce") != NULL) timeout = 90L;
152  else timeout = 240L;
153
154  return timeout;
155}
156
157static CURL *
158createEasy (tr_session * s, struct tr_web * web, struct tr_web_task * task)
159{
160  bool is_default_value;
161  const tr_address * addr;
162  CURL * e = task->curl_easy = curl_easy_init ();
163
164  task->timeout_secs = getTimeoutFromURL (task);
165
166  curl_easy_setopt (e, CURLOPT_AUTOREFERER, 1L);
167  curl_easy_setopt (e, CURLOPT_COOKIEFILE, web->cookie_filename);
168  curl_easy_setopt (e, CURLOPT_ENCODING, "gzip;q=1.0, deflate, identity");
169  curl_easy_setopt (e, CURLOPT_FOLLOWLOCATION, 1L);
170  curl_easy_setopt (e, CURLOPT_MAXREDIRS, -1L);
171  curl_easy_setopt (e, CURLOPT_NOSIGNAL, 1L);
172  curl_easy_setopt (e, CURLOPT_PRIVATE, task);
173#ifdef USE_LIBCURL_SOCKOPT
174  curl_easy_setopt (e, CURLOPT_SOCKOPTFUNCTION, sockoptfunction);
175  curl_easy_setopt (e, CURLOPT_SOCKOPTDATA, task);
176#endif
177  if (web->curl_ssl_verify)
178    {
179      curl_easy_setopt (e, CURLOPT_CAINFO, web->curl_ca_bundle);
180    }
181  else
182    {
183      curl_easy_setopt (e, CURLOPT_SSL_VERIFYHOST, 0L);
184      curl_easy_setopt (e, CURLOPT_SSL_VERIFYPEER, 0L);
185    }
186  curl_easy_setopt (e, CURLOPT_TIMEOUT, task->timeout_secs);
187  curl_easy_setopt (e, CURLOPT_URL, task->url);
188  curl_easy_setopt (e, CURLOPT_USERAGENT, TR_NAME "/" SHORT_VERSION_STRING);
189  curl_easy_setopt (e, CURLOPT_VERBOSE, (long)(web->curl_verbose?1:0));
190  curl_easy_setopt (e, CURLOPT_WRITEDATA, task);
191  curl_easy_setopt (e, CURLOPT_WRITEFUNCTION, writeFunc);
192
193  if (((addr = tr_sessionGetPublicAddress (s, TR_AF_INET, &is_default_value))) && !is_default_value)
194    curl_easy_setopt (e, CURLOPT_INTERFACE, tr_address_to_string (addr));
195  else if (((addr = tr_sessionGetPublicAddress (s, TR_AF_INET6, &is_default_value))) && !is_default_value)
196    curl_easy_setopt (e, CURLOPT_INTERFACE, tr_address_to_string (addr));
197
198  if (task->cookies != NULL)
199    curl_easy_setopt (e, CURLOPT_COOKIE, task->cookies);
200
201  if (task->range != NULL)
202    {
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
250static void tr_webThreadFunc (void * vsession);
251
252struct tr_web_task *
253tr_webRunWithBuffer (tr_session         * session,
254                     const char         * url,
255                     const char         * range,
256                     const char         * cookies,
257                     tr_web_done_func     done_func,
258                     void               * done_func_user_data,
259                     struct evbuffer    * buffer)
260{
261  struct tr_web_task * task = NULL;
262
263  if (!session->isClosing)
264    {
265      if (session->web == NULL)
266        {
267          tr_threadNew (tr_webThreadFunc, session);
268
269          while (session->web == NULL)
270            tr_wait_msec (20);
271        }
272     
273      task = tr_new0 (struct tr_web_task, 1);
274      task->session = session;
275      task->url = tr_strdup (url);
276      task->range = tr_strdup (range);
277      task->cookies = tr_strdup (cookies);
278      task->done_func = done_func;
279      task->done_func_user_data = done_func_user_data;
280      task->response = buffer ? buffer : evbuffer_new ();
281      task->freebuf = buffer ? NULL : task->response;
282
283      tr_lockLock (session->web->taskLock);
284      task->next = session->web->tasks;
285      session->web->tasks = task;
286      tr_lockUnlock (session->web->taskLock);
287    }
288
289  return task;
290}
291
292/**
293 * Portability wrapper for select ().
294 *
295 * http://msdn.microsoft.com/en-us/library/ms740141%28VS.85%29.aspx
296 * On win32, any two of the parameters, readfds, writefds, or exceptfds,
297 * can be given as null. At least one must be non-null, and any non-null
298 * descriptor set must contain at least one handle to a socket.
299 */
300static void
301tr_select (int nfds,
302           fd_set * r_fd_set, fd_set * w_fd_set, fd_set * c_fd_set,
303           struct timeval  * t)
304{
305#ifdef WIN32
306  if (!r_fd_set->fd_count && !w_fd_set->fd_count && !c_fd_set->fd_count)
307    {
308      const long int msec = t->tv_sec*1000 + t->tv_usec/1000;
309      tr_wait_msec (msec);
310    }
311  else if (select (0, r_fd_set->fd_count ? r_fd_set : NULL,
312                      w_fd_set->fd_count ? w_fd_set : NULL,
313                      c_fd_set->fd_count ? c_fd_set : NULL, t) < 0)
314    {
315      char errstr[512];
316      const int e = EVUTIL_SOCKET_ERROR ();
317      tr_net_strerror (errstr, sizeof (errstr), e);
318      dbgmsg ("Error: select (%d) %s", e, errstr);
319    }
320#else
321  select (nfds, r_fd_set, w_fd_set, c_fd_set, t);
322#endif
323}
324
325static void
326tr_webThreadFunc (void * vsession)
327{
328  CURLM * multi;
329  struct tr_web * web;
330  int taskCount = 0;
331  struct tr_web_task * task;
332  tr_session * session = vsession;
333
334  /* try to enable ssl for https support; but if that fails,
335   * try a plain vanilla init */
336  if (curl_global_init (CURL_GLOBAL_SSL))
337    curl_global_init (0);
338
339  web = tr_new0 (struct tr_web, 1);
340  web->close_mode = ~0;
341  web->taskLock = tr_lockNew ();
342  web->tasks = NULL;
343  web->curl_verbose = getenv ("TR_CURL_VERBOSE") != NULL;
344  web->curl_ssl_verify = getenv ("TR_CURL_SSL_VERIFY") != NULL;
345  web->curl_ca_bundle = getenv ("CURL_CA_BUNDLE");
346  if (web->curl_ssl_verify)
347    {
348      tr_logAddNamedInfo ("web", "will verify tracker certs using envvar CURL_CA_BUNDLE: %s",
349               web->curl_ca_bundle == NULL ? "none" : web->curl_ca_bundle);
350      tr_logAddNamedInfo ("web", "NB: this only works if you built against libcurl with openssl or gnutls, NOT nss");
351      tr_logAddNamedInfo ("web", "NB: invalid certs will show up as 'Could not connect to tracker' like many other errors");
352    }
353  web->cookie_filename = tr_buildPath (session->configDir, "cookies.txt", NULL);
354
355  multi = curl_multi_init ();
356  session->web = web;
357
358  for (;;)
359    {
360      long msec;
361      int unused;
362      CURLMsg * msg;
363      CURLMcode mcode;
364
365      if (web->close_mode == TR_WEB_CLOSE_NOW)
366        break;
367      if ((web->close_mode == TR_WEB_CLOSE_WHEN_IDLE) && (web->tasks == NULL))
368        break;
369
370      /* add tasks from the queue */
371      tr_lockLock (web->taskLock);
372      while (web->tasks != NULL)
373        {
374          /* pop the task */
375          task = web->tasks;
376          web->tasks = task->next;
377          task->next = NULL;
378
379          dbgmsg ("adding task to curl: [%s]", task->url);
380          curl_multi_add_handle (multi, createEasy (session, web, task));
381          /*fprintf (stderr, "adding a task.. taskCount is now %d\n", taskCount);*/
382          ++taskCount;
383        }
384      tr_lockUnlock (web->taskLock);
385
386      /* maybe wait a little while before calling curl_multi_perform () */
387      msec = 0;
388      curl_multi_timeout (multi, &msec);
389      if (msec < 0)
390        msec = THREADFUNC_MAX_SLEEP_MSEC;
391      if (session->isClosed)
392        msec = 100; /* on shutdown, call perform () more frequently */
393      if (msec > 0)
394        {
395          int usec;
396          int max_fd;
397          struct timeval t;
398          fd_set r_fd_set, w_fd_set, c_fd_set;
399
400          max_fd = 0;
401          FD_ZERO (&r_fd_set);
402          FD_ZERO (&w_fd_set);
403          FD_ZERO (&c_fd_set);
404          curl_multi_fdset (multi, &r_fd_set, &w_fd_set, &c_fd_set, &max_fd);
405
406          if (msec > THREADFUNC_MAX_SLEEP_MSEC)
407            msec = THREADFUNC_MAX_SLEEP_MSEC;
408
409          usec = msec * 1000;
410          t.tv_sec =  usec / 1000000;
411          t.tv_usec = usec % 1000000;
412          tr_select (max_fd+1, &r_fd_set, &w_fd_set, &c_fd_set, &t);
413        }
414
415      /* call curl_multi_perform () */
416      do
417        mcode = curl_multi_perform (multi, &unused);
418      while (mcode == CURLM_CALL_MULTI_PERFORM);
419
420      /* pump completed tasks from the multi */
421      while ((msg = curl_multi_info_read (multi, &unused)))
422        {
423          if ((msg->msg == CURLMSG_DONE) && (msg->easy_handle != NULL))
424            {
425              double total_time;
426              struct tr_web_task * task;
427              long req_bytes_sent;
428              CURL * e = msg->easy_handle;
429              curl_easy_getinfo (e, CURLINFO_PRIVATE, (void*)&task);
430              curl_easy_getinfo (e, CURLINFO_RESPONSE_CODE, &task->code);
431              curl_easy_getinfo (e, CURLINFO_REQUEST_SIZE, &req_bytes_sent);
432              curl_easy_getinfo (e, CURLINFO_TOTAL_TIME, &total_time);
433              task->did_connect = task->code>0 || req_bytes_sent>0;
434              task->did_timeout = !task->code && (total_time >= task->timeout_secs);
435              curl_multi_remove_handle (multi, e);
436              curl_easy_cleanup (e);
437              tr_runInEventThread (task->session, task_finish_func, task);
438              --taskCount;
439            }
440        }
441    }
442
443  /* Discard any remaining tasks.
444   * This is rare, but can happen on shutdown with unresponsive trackers. */
445  while (web->tasks != NULL)
446    {
447      task = web->tasks;
448      web->tasks = task->next;
449      dbgmsg ("Discarding task \"%s\"", task->url);
450      task_free (task);
451    }
452
453  /* cleanup */
454  curl_multi_cleanup (multi);
455  tr_lockFree (web->taskLock);
456  tr_free (web->cookie_filename);
457  tr_free (web);
458  session->web = NULL;
459}
460
461
462void
463tr_webClose (tr_session * session, tr_web_close_mode close_mode)
464{
465  if (session->web != NULL)
466    {
467      session->web->close_mode = close_mode;
468
469      if (close_mode == TR_WEB_CLOSE_NOW)
470        while (session->web != NULL)
471          tr_wait_msec (100);
472    }
473}
474
475void
476tr_webGetTaskInfo (struct tr_web_task * task, tr_web_task_info info, void * dst)
477{
478  curl_easy_getinfo (task->curl_easy, (CURLINFO) info, dst);
479}
480
481/*****
482******
483******
484*****/
485
486const char *
487tr_webGetResponseStr (long code)
488{
489  switch (code)
490    {
491      case   0: return "No Response";
492      case 101: return "Switching Protocols";
493      case 200: return "OK";
494      case 201: return "Created";
495      case 202: return "Accepted";
496      case 203: return "Non-Authoritative Information";
497      case 204: return "No Content";
498      case 205: return "Reset Content";
499      case 206: return "Partial Content";
500      case 300: return "Multiple Choices";
501      case 301: return "Moved Permanently";
502      case 302: return "Found";
503      case 303: return "See Other";
504      case 304: return "Not Modified";
505      case 305: return "Use Proxy";
506      case 306: return " (Unused)";
507      case 307: return "Temporary Redirect";
508      case 400: return "Bad Request";
509      case 401: return "Unauthorized";
510      case 402: return "Payment Required";
511      case 403: return "Forbidden";
512      case 404: return "Not Found";
513      case 405: return "Method Not Allowed";
514      case 406: return "Not Acceptable";
515      case 407: return "Proxy Authentication Required";
516      case 408: return "Request Timeout";
517      case 409: return "Conflict";
518      case 410: return "Gone";
519      case 411: return "Length Required";
520      case 412: return "Precondition Failed";
521      case 413: return "Request Entity Too Large";
522      case 414: return "Request-URI Too Long";
523      case 415: return "Unsupported Media Type";
524      case 416: return "Requested Range Not Satisfiable";
525      case 417: return "Expectation Failed";
526      case 500: return "Internal Server Error";
527      case 501: return "Not Implemented";
528      case 502: return "Bad Gateway";
529      case 503: return "Service Unavailable";
530      case 504: return "Gateway Timeout";
531      case 505: return "HTTP Version Not Supported";
532      default:  return "Unknown Error";
533    }
534}
535
536void
537tr_http_escape (struct evbuffer  * out,
538                const char       * str,
539                int                len,
540                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    {
549      if ((*str == ',') || (*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}
Note: See TracBrowser for help on using the repository browser.