source: trunk/libtransmission/metainfo.c @ 14231

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

(trunk, libT) #5517 'fix error adding torrents whose names with an ellipsis' -- fix by e5g6s.

  • 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 Public License v2 or v3 licenses,
5 * or any future license endorsed by Mnemosyne LLC.
6 *
7 * $Id: metainfo.c 14231 2014-01-19 04:19:14Z 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.