source: trunk/libtransmission/metainfo.c

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

Explicitly compare result of str(n)cmp/memcmp to signify that it's not boolean

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