source: trunk/libtransmission/metainfo.c @ 14264

Last change on this file since 14264 was 14264, checked in by jordan, 8 years ago

(trunk, libT) #5517 'Don't create or add torrents with "../" at the beginning of the path or "/../" anywhere in the path' -- fixed.

  • Property svn:keywords set to Date Rev Author Id
File size: 16.3 KB
Line 
1/*
2 * This file Copyright (C) 2007-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: metainfo.c 14264 2014-04-27 20:17:16Z jordan $
8 */
9
10#include <assert.h>
11#include <errno.h>
12#include <stdio.h> /* fopen (), fwrite (), fclose () */
13#include <string.h> /* strlen () */
14
15#include <sys/types.h>
16#include <unistd.h> /* stat */
17
18#include <event2/buffer.h>
19
20#include "transmission.h"
21#include "session.h"
22#include "crypto.h" /* tr_sha1 */
23#include "log.h"
24#include "metainfo.h"
25#include "platform.h" /* tr_getTorrentDir () */
26#include "utils.h"
27#include "variant.h"
28
29/***
30****
31***/
32
33
34#ifdef WIN32
35  #define PATH_DELIMITER_CHARS "/\\"
36#else
37  #define PATH_DELIMITER_CHARS "/"
38#endif
39
40static inline bool
41char_is_path_separator (char c)
42{
43  return strchr(PATH_DELIMITER_CHARS, c) != NULL;
44}
45
46char*
47tr_metainfoGetBasename (const tr_info * inf)
48{
49  size_t i;
50  const char * name = inf->originalName;
51  const size_t name_len = strlen (name);
52  char * ret = tr_strdup_printf ("%s.%16.16s", name, inf->hashString);
53
54  for (i=0; i<name_len; ++i)
55    if (char_is_path_separator (ret[i]))
56      ret[i] = '_';
57
58  return ret;
59}
60
61static char*
62getTorrentFilename (const tr_session * session, const tr_info * inf)
63{
64  char * base = tr_metainfoGetBasename (inf);
65  char * filename = tr_strdup_printf ("%s" TR_PATH_DELIMITER_STR "%s.torrent",
66                                      tr_getTorrentDir (session), base);
67  tr_free (base);
68  return filename;
69}
70
71/***
72****
73***/
74
75static bool
76path_component_is_suspicious (const char * component)
77{
78  return (component == NULL)
79      || (*component == '\0')
80      || (strpbrk (component, PATH_DELIMITER_CHARS) != NULL)
81      || (strcmp (component, ".") == 0)
82      || (strcmp (component, "..") == 0);
83}
84
85static bool
86getfile (char ** setme, const char * root, tr_variant * path, struct evbuffer * buf)
87{
88  bool success = false;
89
90  *setme = NULL;
91
92  /* root's already been checked by caller */
93  assert (!path_component_is_suspicious (root));
94
95  if (tr_variantIsList (path))
96    {
97      int i;
98      const int n = tr_variantListSize (path);
99
100      success = true;
101      evbuffer_drain (buf, evbuffer_get_length (buf));
102      evbuffer_add (buf, root, strlen (root));
103
104      for (i=0; i<n; i++)
105        {
106          size_t len;
107          const char * str;
108
109          if (!tr_variantGetStr (tr_variantListChild (path, i), &str, &len) ||
110              path_component_is_suspicious (str))
111            {
112              success = false;
113              break;
114            }
115
116          evbuffer_add (buf, TR_PATH_DELIMITER_STR, 1);
117          evbuffer_add (buf, str, len);
118        }
119    }
120
121  if (success)
122    {
123      *setme = tr_utf8clean ((char*)evbuffer_pullup (buf, -1), evbuffer_get_length (buf));
124      /*fprintf (stderr, "[%s]\n", *setme);*/
125    }
126
127  return success;
128}
129
130static const char*
131parseFiles (tr_info * inf, tr_variant * files, const tr_variant * length)
132{
133  int64_t len;
134
135  inf->totalSize = 0;
136
137  if (tr_variantIsList (files)) /* multi-file mode */
138    {
139      tr_file_index_t i;
140      struct evbuffer * buf;
141      const char * result;
142
143      if (path_component_is_suspicious (inf->name))
144        return "path";
145
146      buf = evbuffer_new ();
147      result = NULL;
148
149      inf->isMultifile = 1;
150      inf->fileCount = tr_variantListSize (files);
151      inf->files = tr_new0 (tr_file, inf->fileCount);
152
153      for (i=0; i<inf->fileCount; i++)
154        {
155          tr_variant * file;
156          tr_variant * path;
157
158          file = tr_variantListChild (files, i);
159          if (!tr_variantIsDict (file))
160            {
161              result = "files";
162              break;
163            }
164
165          if (!tr_variantDictFindList (file, TR_KEY_path_utf_8, &path))
166            if (!tr_variantDictFindList (file, TR_KEY_path, &path))
167              {
168                result = "path";
169                break;
170              }
171
172          if (!getfile (&inf->files[i].name, inf->name, path, buf))
173            {
174              result = "path";
175              break;
176            }
177
178          if (!tr_variantDictFindInt (file, TR_KEY_length, &len))
179            {
180              result = "length";
181              break;
182            }
183
184          inf->files[i].length = len;
185          inf->totalSize      += len;
186        }
187
188      evbuffer_free (buf);
189      return result;
190    }
191  else if (tr_variantGetInt (length, &len)) /* single-file mode */
192    {
193      if (path_component_is_suspicious (inf->name))
194        return "path";
195
196      inf->isMultifile      = 0;
197      inf->fileCount        = 1;
198      inf->files            = tr_new0 (tr_file, 1);
199      inf->files[0].name    = tr_strdup (inf->name);
200      inf->files[0].length  = len;
201      inf->totalSize       += len;
202    }
203  else
204    {
205      return "length";
206    }
207
208  return NULL;
209}
210
211static char *
212tr_convertAnnounceToScrape (const char * announce)
213{
214  char * scrape = NULL;
215  const char * s;
216
217  /* To derive the scrape URL use the following steps:
218   * Begin with the announce URL. Find the last '/' in it.
219   * If the text immediately following that '/' isn't 'announce'
220   * it will be taken as a sign that that tracker doesn't support
221   * the scrape convention. If it does, substitute 'scrape' for
222   * 'announce' to find the scrape page. */
223  if (((s = strrchr (announce, '/'))) && !strncmp (++s, "announce", 8))
224    {
225      const char * prefix = announce;
226      const size_t prefix_len = s - announce;
227      const char * suffix = s + 8;
228      const size_t suffix_len = strlen (suffix);
229      const size_t alloc_len = prefix_len + 6 + suffix_len + 1;
230      char * walk = scrape = tr_new (char, alloc_len);
231      memcpy (walk, prefix, prefix_len); walk += prefix_len;
232      memcpy (walk, "scrape", 6);        walk += 6;
233      memcpy (walk, suffix, suffix_len); walk += suffix_len;
234      *walk++ = '\0';
235      assert (walk - scrape == (int)alloc_len);
236    }
237  /* Some torrents with UDP annouce URLs don't have /announce. */
238  else if (!strncmp (announce, "udp:", 4))
239    {
240      scrape = tr_strdup (announce);
241    }
242
243  return scrape;
244}
245
246static const char*
247getannounce (tr_info * inf, tr_variant * meta)
248{
249  size_t len;
250  const char * str;
251  tr_tracker_info * trackers = NULL;
252  int trackerCount = 0;
253  tr_variant * tiers;
254
255  /* Announce-list */
256  if (tr_variantDictFindList (meta, TR_KEY_announce_list, &tiers))
257    {
258      int n;
259      int i, j, validTiers;
260      const int numTiers = tr_variantListSize (tiers);
261
262      n = 0;
263      for (i=0; i<numTiers; i++)
264        n += tr_variantListSize (tr_variantListChild (tiers, i));
265
266      trackers = tr_new0 (tr_tracker_info, n);
267
268      for (i=0, validTiers=0; i<numTiers; i++)
269        {
270          tr_variant * tier = tr_variantListChild (tiers, i);
271          const int tierSize = tr_variantListSize (tier);
272          bool anyAdded = false;
273          for (j=0; j<tierSize; j++)
274            {
275              if (tr_variantGetStr (tr_variantListChild (tier, j), &str, &len))
276                {
277                  char * url = tr_strstrip (tr_strndup (str, len));
278                  if (!tr_urlIsValidTracker (url))
279                    {
280                      tr_free (url);
281                    }
282                  else
283                    {
284                      tr_tracker_info * t = trackers + trackerCount;
285                      t->tier = validTiers;
286                      t->announce = url;
287                      t->scrape = tr_convertAnnounceToScrape (url);
288                      t->id = trackerCount;
289
290                      anyAdded = true;
291                      ++trackerCount;
292                    }
293                }
294            }
295
296          if (anyAdded)
297            ++validTiers;
298        }
299
300      /* did we use any of the tiers? */
301      if (!trackerCount)
302        {
303          tr_free (trackers);
304          trackers = NULL;
305        }
306    }
307
308  /* Regular announce value */
309  if (!trackerCount && tr_variantDictFindStr (meta, TR_KEY_announce, &str, &len))
310    {
311      char * url = tr_strstrip (tr_strndup (str, len));
312      if (!tr_urlIsValidTracker (url))
313        {
314          tr_free (url);
315        }
316      else
317        {
318          trackers = tr_new0 (tr_tracker_info, 1);
319          trackers[trackerCount].tier = 0;
320          trackers[trackerCount].announce = url;
321          trackers[trackerCount].scrape = tr_convertAnnounceToScrape (url);
322          trackers[trackerCount].id = 0;
323          trackerCount++;
324          /*fprintf (stderr, "single announce: [%s]\n", url);*/
325        }
326    }
327
328  inf->trackers = trackers;
329  inf->trackerCount = trackerCount;
330
331  return NULL;
332}
333
334/**
335 * @brief Ensure that the URLs for multfile torrents end in a slash.
336 *
337 * See http://bittorrent.org/beps/bep_0019.html#metadata-extension
338 * for background on how the trailing slash is used for "url-list"
339 * fields.
340 *
341 * This function is to workaround some .torrent generators, such as
342 * mktorrent and very old versions of utorrent, that don't add the
343 * trailing slash for multifile torrents if omitted by the end user.
344 */
345static char*
346fix_webseed_url (const tr_info * inf, const char * url_in)
347{
348  size_t len;
349  char * url;
350  char * ret = NULL;
351
352  url = tr_strdup (url_in);
353  tr_strstrip (url);
354  len = strlen (url);
355
356  if (tr_urlIsValid (url, len))
357    {
358      if ((inf->fileCount > 1) && (len > 0) && (url[len-1] != '/'))
359        ret = tr_strdup_printf ("%*.*s/", (int)len, (int)len, url);
360      else
361        ret = tr_strndup (url, len);
362    }
363
364  tr_free (url);
365  return ret;
366}
367
368static void
369geturllist (tr_info * inf, tr_variant * meta)
370{
371  tr_variant * urls;
372  const char * url;
373
374  if (tr_variantDictFindList (meta, TR_KEY_url_list, &urls))
375    {
376      int i;
377      const int n = tr_variantListSize (urls);
378
379      inf->webseedCount = 0;
380      inf->webseeds = tr_new0 (char*, n);
381
382      for (i=0; i<n; i++)
383        {
384          if (tr_variantGetStr (tr_variantListChild (urls, i), &url, NULL))
385            {
386              char * fixed_url = fix_webseed_url (inf, url);
387
388              if (fixed_url != NULL)
389                inf->webseeds[inf->webseedCount++] = fixed_url;
390            }
391        }
392    }
393  else if (tr_variantDictFindStr (meta, TR_KEY_url_list, &url, NULL)) /* handle single items in webseeds */
394    {
395      char * fixed_url = fix_webseed_url (inf, url);
396
397      if (fixed_url != NULL)
398        {
399          inf->webseedCount = 1;
400          inf->webseeds = tr_new0 (char*, 1);
401          inf->webseeds[0] = fixed_url;
402        }
403    }
404}
405
406static const char*
407tr_metainfoParseImpl (const tr_session  * session,
408                      tr_info           * inf,
409                      bool              * hasInfoDict,
410                      int               * infoDictLength,
411                      const tr_variant     * meta_in)
412{
413  int64_t i;
414  size_t len;
415  const char * str;
416  const uint8_t * raw;
417  tr_variant * d;
418  tr_variant * infoDict = NULL;
419  tr_variant * meta = (tr_variant *) meta_in;
420  bool b;
421  bool isMagnet = false;
422
423  /* info_hash: urlencoded 20-byte SHA1 hash of the value of the info key
424   * from the Metainfo file. Note that the value will be a bencoded
425   * dictionary, given the definition of the info key above. */
426  b = tr_variantDictFindDict (meta, TR_KEY_info, &infoDict);
427  if (hasInfoDict != NULL)
428    *hasInfoDict = b;
429
430  if (!b)
431    {
432      /* no info dictionary... is this a magnet link? */
433      if (tr_variantDictFindDict (meta, TR_KEY_magnet_info, &d))
434        {
435          isMagnet = true;
436
437          /* get the info-hash */
438          if (!tr_variantDictFindRaw (d, TR_KEY_info_hash, &raw, &len))
439            return "info_hash";
440          if (len != SHA_DIGEST_LENGTH)
441            return "info_hash";
442          memcpy (inf->hash, raw, len);
443          tr_sha1_to_hex (inf->hashString, inf->hash);
444
445          /* maybe get the display name */
446          if (tr_variantDictFindStr (d, TR_KEY_display_name, &str, &len))
447            {
448              tr_free (inf->name);
449              tr_free (inf->originalName);
450              inf->name = tr_strndup (str, len);
451              inf->originalName = tr_strndup (str, len);
452            }
453
454          if (!inf->name)
455              inf->name = tr_strdup (inf->hashString);
456          if (!inf->originalName)
457              inf->originalName = tr_strdup (inf->hashString);
458        }
459      else /* not a magnet link and has no info dict... */
460        {
461          return "info";
462        }
463    }
464  else
465    {
466      int len;
467      char * bstr = tr_variantToStr (infoDict, TR_VARIANT_FMT_BENC, &len);
468      tr_sha1 (inf->hash, bstr, len, NULL);
469      tr_sha1_to_hex (inf->hashString, inf->hash);
470
471      if (infoDictLength != NULL)
472        *infoDictLength = len;
473
474      tr_free (bstr);
475    }
476
477  /* name */
478  if (!isMagnet)
479    {
480      len = 0;
481      if (!tr_variantDictFindStr (infoDict, TR_KEY_name_utf_8, &str, &len))
482        if (!tr_variantDictFindStr (infoDict, TR_KEY_name, &str, &len))
483          str = "";
484      if (!str || !*str)
485        return "name";
486      tr_free (inf->name);
487      tr_free (inf->originalName);
488      inf->name = tr_utf8clean (str, len);
489      inf->originalName = tr_strdup (inf->name);
490    }
491
492  /* comment */
493  len = 0;
494  if (!tr_variantDictFindStr (meta, TR_KEY_comment_utf_8, &str, &len))
495    if (!tr_variantDictFindStr (meta, TR_KEY_comment, &str, &len))
496      str = "";
497  tr_free (inf->comment);
498  inf->comment = tr_utf8clean (str, len);
499
500  /* created by */
501  len = 0;
502  if (!tr_variantDictFindStr (meta, TR_KEY_created_by_utf_8, &str, &len))
503    if (!tr_variantDictFindStr (meta, TR_KEY_created_by, &str, &len))
504      str = "";
505  tr_free (inf->creator);
506  inf->creator = tr_utf8clean (str, len);
507
508  /* creation date */
509  if (!tr_variantDictFindInt (meta, TR_KEY_creation_date, &i))
510    i = 0;
511  inf->dateCreated = i;
512
513  /* private */
514  if (!tr_variantDictFindInt (infoDict, TR_KEY_private, &i))
515    if (!tr_variantDictFindInt (meta, TR_KEY_private, &i))
516      i = 0;
517  inf->isPrivate = i != 0;
518
519  /* piece length */
520  if (!isMagnet)
521    {
522      if (!tr_variantDictFindInt (infoDict, TR_KEY_piece_length, &i) || (i < 1))
523        return "piece length";
524      inf->pieceSize = i;
525    }
526
527  /* pieces */
528  if (!isMagnet)
529    {
530      if (!tr_variantDictFindRaw (infoDict, TR_KEY_pieces, &raw, &len))
531        return "pieces";
532      if (len % SHA_DIGEST_LENGTH)
533        return "pieces";
534
535      inf->pieceCount = len / SHA_DIGEST_LENGTH;
536      inf->pieces = tr_new0 (tr_piece, inf->pieceCount);
537      for (i=0; i<inf->pieceCount; i++)
538        memcpy (inf->pieces[i].hash, &raw[i * SHA_DIGEST_LENGTH], SHA_DIGEST_LENGTH);
539    }
540
541  /* files */
542  if (!isMagnet)
543    {
544      if ((str = parseFiles (inf, tr_variantDictFind (infoDict, TR_KEY_files),
545                                  tr_variantDictFind (infoDict, TR_KEY_length))))
546        return str;
547
548      if (!inf->fileCount || !inf->totalSize)
549        return "files";
550
551      if ((uint64_t) inf->pieceCount != (inf->totalSize + inf->pieceSize - 1) / inf->pieceSize)
552        return "files";
553    }
554
555  /* get announce or announce-list */
556  if ((str = getannounce (inf, meta)))
557    return str;
558
559  /* get the url-list */
560  geturllist (inf, meta);
561
562  /* filename of Transmission's copy */
563  tr_free (inf->torrent);
564  inf->torrent = session ?  getTorrentFilename (session, inf) : NULL;
565
566  return NULL;
567}
568
569bool
570tr_metainfoParse (const tr_session * session,
571                  const tr_variant * meta_in,
572                  tr_info          * inf,
573                  bool             * hasInfoDict,
574                  int              * infoDictLength)
575{
576  const char * badTag = tr_metainfoParseImpl (session,
577                                              inf,
578                                              hasInfoDict,
579                                              infoDictLength,
580                                              meta_in);
581  const bool success = badTag == NULL;
582
583  if (badTag)
584    {
585      tr_logAddNamedError (inf->name, _("Invalid metadata entry \"%s\""), badTag);
586      tr_metainfoFree (inf);
587    }
588
589  return success;
590}
591
592void
593tr_metainfoFree (tr_info * inf)
594{
595  unsigned int i;
596  tr_file_index_t ff;
597
598  for (i=0; i<inf->webseedCount; i++)
599    tr_free (inf->webseeds[i]);
600
601  for (ff=0; ff<inf->fileCount; ff++)
602      tr_free (inf->files[ff].name);
603
604  tr_free (inf->webseeds);
605  tr_free (inf->pieces);
606  tr_free (inf->files);
607  tr_free (inf->comment);
608  tr_free (inf->creator);
609  tr_free (inf->torrent);
610  tr_free (inf->originalName);
611  tr_free (inf->name);
612
613  for (i=0; i<inf->trackerCount; i++)
614    {
615      tr_free (inf->trackers[i].announce);
616      tr_free (inf->trackers[i].scrape);
617    }
618  tr_free (inf->trackers);
619
620  memset (inf, '\0', sizeof (tr_info));
621}
622
623void
624tr_metainfoRemoveSaved (const tr_session * session, const tr_info * inf)
625{
626  char * filename;
627
628  filename = getTorrentFilename (session, inf);
629  tr_remove (filename);
630  tr_free (filename);
631}
632
Note: See TracBrowser for help on using the repository browser.