source: trunk/libtransmission/metainfo.c @ 14241

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

Copyedit the license's revised text: (1) remove unnecessary repitition use of the word 'license' from the top of the header and source files (2) add the standard 'we hope it's useful, but no warranty' clause to COPYING (3) make explicit that linking OpenSSL is allowed (see https://people.gnome.org/~markmc/openssl-and-the-gpl.html for background) (4) sync the Qt and GTK+ clients' license popups with COPYING's revised text

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