source: trunk/libtransmission/metainfo.c @ 13625

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

Follow more common whitespace style conventions in the C code (libtransmission, daemon, utils, cli, gtk).

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