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  */
8 
9 #include <string.h> /* strlen() */
10 
11 #include <event2/buffer.h>
12 
13 #include "transmission.h"
14 #include "crypto-utils.h" /* tr_sha1 */
15 #include "file.h"
16 #include "log.h"
17 #include "metainfo.h"
18 #include "platform.h" /* tr_getTorrentDir() */
19 #include "session.h"
20 #include "tr-assert.h"
21 #include "utils.h"
22 #include "variant.h"
23 
24 /***
25 ****
26 ***/
27 
28 #ifdef _WIN32
29 #define PATH_DELIMITER_CHARS "/\\"
30 #else
31 #define PATH_DELIMITER_CHARS "/"
32 #endif
33 
char_is_path_separator(char c)34 static inline bool char_is_path_separator(char c)
35 {
36     return strchr(PATH_DELIMITER_CHARS, c) != NULL;
37 }
38 
metainfoGetBasenameNameAndPartialHash(tr_info const * inf)39 static char* metainfoGetBasenameNameAndPartialHash(tr_info const* inf)
40 {
41     char const* name = inf->originalName;
42     size_t const name_len = strlen(name);
43     char* ret = tr_strdup_printf("%s.%16.16s", name, inf->hashString);
44 
45     for (size_t i = 0; i < name_len; ++i)
46     {
47         if (char_is_path_separator(ret[i]))
48         {
49             ret[i] = '_';
50         }
51     }
52 
53     return ret;
54 }
55 
metainfoGetBasenameHashOnly(tr_info const * inf)56 static char* metainfoGetBasenameHashOnly(tr_info const* inf)
57 {
58     return tr_strdup(inf->hashString);
59 }
60 
tr_metainfoGetBasename(tr_info const * inf,enum tr_metainfo_basename_format format)61 char* tr_metainfoGetBasename(tr_info const* inf, enum tr_metainfo_basename_format format)
62 {
63     switch (format)
64     {
65     case TR_METAINFO_BASENAME_NAME_AND_PARTIAL_HASH:
66         return metainfoGetBasenameNameAndPartialHash(inf);
67 
68     case TR_METAINFO_BASENAME_HASH:
69         return metainfoGetBasenameHashOnly(inf);
70 
71     default:
72         TR_ASSERT_MSG(false, "unknown metainfo basename format %d", (int)format);
73         return NULL;
74     }
75 }
76 
getTorrentFilename(tr_session const * session,tr_info const * inf,enum tr_metainfo_basename_format format)77 static char* getTorrentFilename(tr_session const* session, tr_info const* inf, enum tr_metainfo_basename_format format)
78 {
79     char* base = tr_metainfoGetBasename(inf, format);
80     char* filename = tr_strdup_printf("%s" TR_PATH_DELIMITER_STR "%s.torrent", tr_getTorrentDir(session), base);
81     tr_free(base);
82     return filename;
83 }
84 
85 /***
86 ****
87 ***/
88 
tr_metainfo_sanitize_path_component(char const * str,size_t len,bool * is_adjusted)89 char* tr_metainfo_sanitize_path_component(char const* str, size_t len, bool* is_adjusted)
90 {
91     if (len == 0 || (len == 1 && str[0] == '.'))
92     {
93         return NULL;
94     }
95 
96     *is_adjusted = false;
97 
98     /* https://docs.microsoft.com/en-us/windows/desktop/FileIO/naming-a-file */
99     char const* const reserved_chars = "<>:\"/\\|?*";
100     char const* const reserved_names[] =
101     {
102         "CON", "PRN", "AUX", "NUL",
103         "COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8", "COM9",
104         "LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9"
105     };
106 
107     char* const ret = tr_new(char, len + 2);
108     memcpy(ret, str, len);
109     ret[len] = '\0';
110 
111     for (size_t i = 0; i < len; ++i)
112     {
113         if (strchr(reserved_chars, ret[i]) != NULL || (unsigned char)ret[i] < 0x20)
114         {
115             ret[i] = '_';
116             *is_adjusted = true;
117         }
118     }
119 
120     for (size_t i = 0; i < TR_N_ELEMENTS(reserved_names); ++i)
121     {
122         size_t const reserved_name_len = strlen(reserved_names[i]);
123         if (evutil_ascii_strncasecmp(ret, reserved_names[i], reserved_name_len) != 0 ||
124             (ret[reserved_name_len] != '\0' && ret[reserved_name_len] != '.'))
125         {
126             continue;
127         }
128 
129         memmove(&ret[reserved_name_len + 1], &ret[reserved_name_len], len - reserved_name_len + 1);
130         ret[reserved_name_len] = '_';
131         *is_adjusted = true;
132         ++len;
133         break;
134     }
135 
136     size_t start_pos = 0;
137     size_t end_pos = len;
138 
139     while (start_pos < len && ret[start_pos] == ' ')
140     {
141         ++start_pos;
142     }
143 
144     while (end_pos > start_pos && (ret[end_pos - 1] == ' ' || ret[end_pos - 1] == '.'))
145     {
146         --end_pos;
147     }
148 
149     if (start_pos == end_pos)
150     {
151         tr_free(ret);
152         return NULL;
153     }
154 
155     if (start_pos != 0 || end_pos != len)
156     {
157         len = end_pos - start_pos;
158         memmove(ret, &ret[start_pos], len);
159         ret[len] = '\0';
160         *is_adjusted = true;
161     }
162 
163     return ret;
164 }
165 
getfile(char ** setme,bool * is_adjusted,char const * root,tr_variant * path,struct evbuffer * buf)166 static bool getfile(char** setme, bool* is_adjusted, char const* root, tr_variant* path, struct evbuffer* buf)
167 {
168     bool success = false;
169     size_t root_len = 0;
170 
171     *setme = NULL;
172     *is_adjusted = false;
173 
174     if (tr_variantIsList(path))
175     {
176         success = true;
177         evbuffer_drain(buf, evbuffer_get_length(buf));
178         root_len = strlen(root);
179         evbuffer_add(buf, root, root_len);
180 
181         for (int i = 0, n = tr_variantListSize(path); i < n; i++)
182         {
183             size_t len;
184             char const* str;
185 
186             if (!tr_variantGetStr(tr_variantListChild(path, i), &str, &len))
187             {
188                 success = false;
189                 break;
190             }
191 
192             bool is_component_adjusted;
193             char* final_str = tr_metainfo_sanitize_path_component(str, len, &is_component_adjusted);
194             if (final_str == NULL)
195             {
196                 continue;
197             }
198 
199             *is_adjusted = *is_adjusted || is_component_adjusted;
200 
201             evbuffer_add(buf, TR_PATH_DELIMITER_STR, 1);
202             evbuffer_add(buf, final_str, strlen(final_str));
203 
204             tr_free(final_str);
205         }
206     }
207 
208     if (success && evbuffer_get_length(buf) <= root_len)
209     {
210         success = false;
211     }
212 
213     if (success)
214     {
215         char const* const buf_data = (char*)evbuffer_pullup(buf, -1);
216         size_t const buf_len = evbuffer_get_length(buf);
217 
218         *setme = tr_utf8clean(buf_data, buf_len);
219 
220         if (!*is_adjusted)
221         {
222             *is_adjusted = buf_len != strlen(*setme) || strncmp(buf_data, *setme, buf_len) != 0;
223         }
224     }
225 
226     return success;
227 }
228 
parseFiles(tr_info * inf,tr_variant * files,tr_variant const * length)229 static char const* parseFiles(tr_info* inf, tr_variant* files, tr_variant const* length)
230 {
231     int64_t len;
232 
233     inf->totalSize = 0;
234 
235     bool is_root_adjusted;
236     char* const root_name = tr_metainfo_sanitize_path_component(inf->name, strlen(inf->name), &is_root_adjusted);
237     if (root_name == NULL)
238     {
239         return "path";
240     }
241 
242     char const* result = NULL;
243 
244     if (tr_variantIsList(files)) /* multi-file mode */
245     {
246         struct evbuffer* buf;
247 
248         buf = evbuffer_new();
249         result = NULL;
250 
251         inf->isFolder = true;
252         inf->fileCount = tr_variantListSize(files);
253         inf->files = tr_new0(tr_file, inf->fileCount);
254 
255         for (tr_file_index_t i = 0; i < inf->fileCount; i++)
256         {
257             tr_variant* file;
258             tr_variant* path;
259 
260             file = tr_variantListChild(files, i);
261 
262             if (!tr_variantIsDict(file))
263             {
264                 result = "files";
265                 break;
266             }
267 
268             if (!tr_variantDictFindList(file, TR_KEY_path_utf_8, &path))
269             {
270                 if (!tr_variantDictFindList(file, TR_KEY_path, &path))
271                 {
272                     result = "path";
273                     break;
274                 }
275             }
276 
277             bool is_file_adjusted;
278             if (!getfile(&inf->files[i].name, &is_file_adjusted, root_name, path, buf))
279             {
280                 result = "path";
281                 break;
282             }
283 
284             if (!tr_variantDictFindInt(file, TR_KEY_length, &len))
285             {
286                 result = "length";
287                 break;
288             }
289 
290             inf->files[i].length = len;
291             inf->files[i].is_renamed = is_root_adjusted || is_file_adjusted;
292             inf->totalSize += len;
293         }
294 
295         evbuffer_free(buf);
296     }
297     else if (tr_variantGetInt(length, &len)) /* single-file mode */
298     {
299         inf->isFolder = false;
300         inf->fileCount = 1;
301         inf->files = tr_new0(tr_file, 1);
302         inf->files[0].name = tr_strdup(root_name);
303         inf->files[0].length = len;
304         inf->files[0].is_renamed = is_root_adjusted;
305         inf->totalSize += len;
306     }
307     else
308     {
309         result = "length";
310     }
311 
312     tr_free(root_name);
313     return result;
314 }
315 
tr_convertAnnounceToScrape(char const * announce)316 static char* tr_convertAnnounceToScrape(char const* announce)
317 {
318     char* scrape = NULL;
319 
320     /* To derive the scrape URL use the following steps:
321      * Begin with the announce URL. Find the last '/' in it.
322      * If the text immediately following that '/' isn't 'announce'
323      * it will be taken as a sign that that tracker doesn't support
324      * the scrape convention. If it does, substitute 'scrape' for
325      * 'announce' to find the scrape page. */
326 
327     char const* s = strrchr(announce, '/');
328 
329     if (s != NULL && strncmp(s + 1, "announce", 8) == 0)
330     {
331         char const* prefix = announce;
332         size_t const prefix_len = s + 1 - announce;
333         char const* suffix = s + 1 + 8;
334         size_t const suffix_len = strlen(suffix);
335         size_t const alloc_len = prefix_len + 6 + suffix_len + 1;
336 
337         scrape = tr_new(char, alloc_len);
338 
339         char* walk = scrape;
340         memcpy(walk, prefix, prefix_len);
341         walk += prefix_len;
342         memcpy(walk, "scrape", 6);
343         walk += 6;
344         memcpy(walk, suffix, suffix_len);
345         walk += suffix_len;
346         *walk++ = '\0';
347 
348         TR_ASSERT((size_t)(walk - scrape) == alloc_len);
349     }
350     /* Some torrents with UDP announce URLs don't have /announce. */
351     else if (strncmp(announce, "udp:", 4) == 0)
352     {
353         scrape = tr_strdup(announce);
354     }
355 
356     return scrape;
357 }
358 
getannounce(tr_info * inf,tr_variant * meta)359 static char const* getannounce(tr_info* inf, tr_variant* meta)
360 {
361     size_t len;
362     char const* str;
363     tr_tracker_info* trackers = NULL;
364     int trackerCount = 0;
365     tr_variant* tiers;
366 
367     /* Announce-list */
368     if (tr_variantDictFindList(meta, TR_KEY_announce_list, &tiers))
369     {
370         int n;
371         int const numTiers = tr_variantListSize(tiers);
372 
373         n = 0;
374 
375         for (int i = 0; i < numTiers; i++)
376         {
377             n += tr_variantListSize(tr_variantListChild(tiers, i));
378         }
379 
380         trackers = tr_new0(tr_tracker_info, n);
381 
382         for (int i = 0, validTiers = 0; i < numTiers; i++)
383         {
384             tr_variant* tier = tr_variantListChild(tiers, i);
385             int const tierSize = tr_variantListSize(tier);
386             bool anyAdded = false;
387 
388             for (int j = 0; j < tierSize; j++)
389             {
390                 if (tr_variantGetStr(tr_variantListChild(tier, j), &str, &len))
391                 {
392                     char* url = tr_strstrip(tr_strndup(str, len));
393 
394                     if (!tr_urlIsValidTracker(url))
395                     {
396                         tr_free(url);
397                     }
398                     else
399                     {
400                         tr_tracker_info* t = trackers + trackerCount;
401                         t->tier = validTiers;
402                         t->announce = url;
403                         t->scrape = tr_convertAnnounceToScrape(url);
404                         t->id = trackerCount;
405 
406                         anyAdded = true;
407                         ++trackerCount;
408                     }
409                 }
410             }
411 
412             if (anyAdded)
413             {
414                 ++validTiers;
415             }
416         }
417 
418         /* did we use any of the tiers? */
419         if (trackerCount == 0)
420         {
421             tr_free(trackers);
422             trackers = NULL;
423         }
424     }
425 
426     /* Regular announce value */
427     if (trackerCount == 0 && tr_variantDictFindStr(meta, TR_KEY_announce, &str, &len))
428     {
429         char* url = tr_strstrip(tr_strndup(str, len));
430 
431         if (!tr_urlIsValidTracker(url))
432         {
433             tr_free(url);
434         }
435         else
436         {
437             trackers = tr_new0(tr_tracker_info, 1);
438             trackers[trackerCount].tier = 0;
439             trackers[trackerCount].announce = url;
440             trackers[trackerCount].scrape = tr_convertAnnounceToScrape(url);
441             trackers[trackerCount].id = 0;
442             trackerCount++;
443             /* fprintf(stderr, "single announce: [%s]\n", url); */
444         }
445     }
446 
447     inf->trackers = trackers;
448     inf->trackerCount = trackerCount;
449 
450     return NULL;
451 }
452 
453 /**
454  * @brief Ensure that the URLs for multfile torrents end in a slash.
455  *
456  * See http://bittorrent.org/beps/bep_0019.html#metadata-extension
457  * for background on how the trailing slash is used for "url-list"
458  * fields.
459  *
460  * This function is to workaround some .torrent generators, such as
461  * mktorrent and very old versions of utorrent, that don't add the
462  * trailing slash for multifile torrents if omitted by the end user.
463  */
fix_webseed_url(tr_info const * inf,char const * url_in)464 static char* fix_webseed_url(tr_info const* inf, char const* url_in)
465 {
466     size_t len;
467     char* url;
468     char* ret = NULL;
469 
470     url = tr_strdup(url_in);
471     tr_strstrip(url);
472     len = strlen(url);
473 
474     if (tr_urlIsValid(url, len))
475     {
476         if (inf->fileCount > 1 && len > 0 && url[len - 1] != '/')
477         {
478             ret = tr_strdup_printf("%*.*s/", (int)len, (int)len, url);
479         }
480         else
481         {
482             ret = tr_strndup(url, len);
483         }
484     }
485 
486     tr_free(url);
487     return ret;
488 }
489 
geturllist(tr_info * inf,tr_variant * meta)490 static void geturllist(tr_info* inf, tr_variant* meta)
491 {
492     tr_variant* urls;
493     char const* url;
494 
495     if (tr_variantDictFindList(meta, TR_KEY_url_list, &urls))
496     {
497         int const n = tr_variantListSize(urls);
498 
499         inf->webseedCount = 0;
500         inf->webseeds = tr_new0(char*, n);
501 
502         for (int i = 0; i < n; i++)
503         {
504             if (tr_variantGetStr(tr_variantListChild(urls, i), &url, NULL))
505             {
506                 char* fixed_url = fix_webseed_url(inf, url);
507 
508                 if (fixed_url != NULL)
509                 {
510                     inf->webseeds[inf->webseedCount++] = fixed_url;
511                 }
512             }
513         }
514     }
515     else if (tr_variantDictFindStr(meta, TR_KEY_url_list, &url, NULL)) /* handle single items in webseeds */
516     {
517         char* fixed_url = fix_webseed_url(inf, url);
518 
519         if (fixed_url != NULL)
520         {
521             inf->webseedCount = 1;
522             inf->webseeds = tr_new0(char*, 1);
523             inf->webseeds[0] = fixed_url;
524         }
525     }
526 }
527 
tr_metainfoParseImpl(tr_session const * session,tr_info * inf,bool * hasInfoDict,size_t * infoDictLength,tr_variant const * meta_in)528 static char const* tr_metainfoParseImpl(tr_session const* session, tr_info* inf, bool* hasInfoDict, size_t* infoDictLength,
529     tr_variant const* meta_in)
530 {
531     int64_t i;
532     size_t len;
533     char const* str;
534     uint8_t const* raw;
535     tr_variant* d;
536     tr_variant* infoDict = NULL;
537     tr_variant* meta = (tr_variant*)meta_in;
538     bool b;
539     bool isMagnet = false;
540 
541     /* info_hash: urlencoded 20-byte SHA1 hash of the value of the info key
542      * from the Metainfo file. Note that the value will be a bencoded
543      * dictionary, given the definition of the info key above. */
544     b = tr_variantDictFindDict(meta, TR_KEY_info, &infoDict);
545 
546     if (hasInfoDict != NULL)
547     {
548         *hasInfoDict = b;
549     }
550 
551     if (!b)
552     {
553         /* no info dictionary... is this a magnet link? */
554         if (tr_variantDictFindDict(meta, TR_KEY_magnet_info, &d))
555         {
556             isMagnet = true;
557 
558             /* get the info-hash */
559             if (!tr_variantDictFindRaw(d, TR_KEY_info_hash, &raw, &len))
560             {
561                 return "info_hash";
562             }
563 
564             if (len != SHA_DIGEST_LENGTH)
565             {
566                 return "info_hash";
567             }
568 
569             memcpy(inf->hash, raw, len);
570             tr_sha1_to_hex(inf->hashString, inf->hash);
571 
572             /* maybe get the display name */
573             if (tr_variantDictFindStr(d, TR_KEY_display_name, &str, &len))
574             {
575                 tr_free(inf->name);
576                 tr_free(inf->originalName);
577                 inf->name = tr_strndup(str, len);
578                 inf->originalName = tr_strndup(str, len);
579             }
580 
581             if (inf->name == NULL)
582             {
583                 inf->name = tr_strdup(inf->hashString);
584             }
585 
586             if (inf->originalName == NULL)
587             {
588                 inf->originalName = tr_strdup(inf->hashString);
589             }
590         }
591         else /* not a magnet link and has no info dict... */
592         {
593             return "info";
594         }
595     }
596     else
597     {
598         size_t len;
599         char* bstr = tr_variantToStr(infoDict, TR_VARIANT_FMT_BENC, &len);
600         tr_sha1(inf->hash, bstr, (int)len, NULL);
601         tr_sha1_to_hex(inf->hashString, inf->hash);
602 
603         if (infoDictLength != NULL)
604         {
605             *infoDictLength = len;
606         }
607 
608         tr_free(bstr);
609     }
610 
611     /* name */
612     if (!isMagnet)
613     {
614         len = 0;
615 
616         if (!tr_variantDictFindStr(infoDict, TR_KEY_name_utf_8, &str, &len))
617         {
618             if (!tr_variantDictFindStr(infoDict, TR_KEY_name, &str, &len))
619             {
620                 str = "";
621             }
622         }
623 
624         if (tr_str_is_empty(str))
625         {
626             return "name";
627         }
628 
629         tr_free(inf->name);
630         tr_free(inf->originalName);
631         inf->name = tr_utf8clean(str, len);
632         inf->originalName = tr_strdup(inf->name);
633     }
634 
635     /* comment */
636     len = 0;
637 
638     if (!tr_variantDictFindStr(meta, TR_KEY_comment_utf_8, &str, &len))
639     {
640         if (!tr_variantDictFindStr(meta, TR_KEY_comment, &str, &len))
641         {
642             str = "";
643         }
644     }
645 
646     tr_free(inf->comment);
647     inf->comment = tr_utf8clean(str, len);
648 
649     /* created by */
650     len = 0;
651 
652     if (!tr_variantDictFindStr(meta, TR_KEY_created_by_utf_8, &str, &len))
653     {
654         if (!tr_variantDictFindStr(meta, TR_KEY_created_by, &str, &len))
655         {
656             str = "";
657         }
658     }
659 
660     tr_free(inf->creator);
661     inf->creator = tr_utf8clean(str, len);
662 
663     /* creation date */
664     if (!tr_variantDictFindInt(meta, TR_KEY_creation_date, &i))
665     {
666         i = 0;
667     }
668 
669     inf->dateCreated = i;
670 
671     /* private */
672     if (!tr_variantDictFindInt(infoDict, TR_KEY_private, &i))
673     {
674         if (!tr_variantDictFindInt(meta, TR_KEY_private, &i))
675         {
676             i = 0;
677         }
678     }
679 
680     inf->isPrivate = i != 0;
681 
682     /* piece length */
683     if (!isMagnet)
684     {
685         if (!tr_variantDictFindInt(infoDict, TR_KEY_piece_length, &i) || (i < 1))
686         {
687             return "piece length";
688         }
689 
690         inf->pieceSize = i;
691     }
692 
693     /* pieces */
694     if (!isMagnet)
695     {
696         if (!tr_variantDictFindRaw(infoDict, TR_KEY_pieces, &raw, &len))
697         {
698             return "pieces";
699         }
700 
701         if (len % SHA_DIGEST_LENGTH != 0)
702         {
703             return "pieces";
704         }
705 
706         inf->pieceCount = len / SHA_DIGEST_LENGTH;
707         inf->pieces = tr_new0(tr_piece, inf->pieceCount);
708 
709         for (tr_piece_index_t i = 0; i < inf->pieceCount; i++)
710         {
711             memcpy(inf->pieces[i].hash, &raw[i * SHA_DIGEST_LENGTH], SHA_DIGEST_LENGTH);
712         }
713     }
714 
715     /* files */
716     if (!isMagnet)
717     {
718         if ((str = parseFiles(inf, tr_variantDictFind(infoDict, TR_KEY_files), tr_variantDictFind(infoDict,
719             TR_KEY_length))) != NULL)
720         {
721             return str;
722         }
723 
724         if (inf->fileCount == 0 || inf->totalSize == 0)
725         {
726             return "files";
727         }
728 
729         if ((uint64_t)inf->pieceCount != (inf->totalSize + inf->pieceSize - 1) / inf->pieceSize)
730         {
731             return "files";
732         }
733     }
734 
735     /* get announce or announce-list */
736     if ((str = getannounce(inf, meta)) != NULL)
737     {
738         return str;
739     }
740 
741     /* get the url-list */
742     geturllist(inf, meta);
743 
744     /* filename of Transmission's copy */
745     tr_free(inf->torrent);
746     inf->torrent = session != NULL ? getTorrentFilename(session, inf, TR_METAINFO_BASENAME_HASH) : NULL;
747 
748     return NULL;
749 }
750 
tr_metainfoParse(tr_session const * session,tr_variant const * meta_in,tr_info * inf,bool * hasInfoDict,size_t * infoDictLength)751 bool tr_metainfoParse(tr_session const* session, tr_variant const* meta_in, tr_info* inf, bool* hasInfoDict,
752     size_t* infoDictLength)
753 {
754     char const* badTag = tr_metainfoParseImpl(session, inf, hasInfoDict, infoDictLength, meta_in);
755     bool const success = badTag == NULL;
756 
757     if (badTag != NULL)
758     {
759         tr_logAddNamedError(inf->name, _("Invalid metadata entry \"%s\""), badTag);
760         tr_metainfoFree(inf);
761     }
762 
763     return success;
764 }
765 
tr_metainfoFree(tr_info * inf)766 void tr_metainfoFree(tr_info* inf)
767 {
768     for (unsigned int i = 0; i < inf->webseedCount; i++)
769     {
770         tr_free(inf->webseeds[i]);
771     }
772 
773     for (tr_file_index_t ff = 0; ff < inf->fileCount; ff++)
774     {
775         tr_free(inf->files[ff].name);
776     }
777 
778     tr_free(inf->webseeds);
779     tr_free(inf->pieces);
780     tr_free(inf->files);
781     tr_free(inf->comment);
782     tr_free(inf->creator);
783     tr_free(inf->torrent);
784     tr_free(inf->originalName);
785     tr_free(inf->name);
786 
787     for (unsigned int i = 0; i < inf->trackerCount; i++)
788     {
789         tr_free(inf->trackers[i].announce);
790         tr_free(inf->trackers[i].scrape);
791     }
792 
793     tr_free(inf->trackers);
794 
795     memset(inf, '\0', sizeof(tr_info));
796 }
797 
tr_metainfoRemoveSaved(tr_session const * session,tr_info const * inf)798 void tr_metainfoRemoveSaved(tr_session const* session, tr_info const* inf)
799 {
800     char* filename;
801 
802     filename = getTorrentFilename(session, inf, TR_METAINFO_BASENAME_HASH);
803     tr_sys_path_remove(filename, NULL);
804     tr_free(filename);
805 
806     filename = getTorrentFilename(session, inf, TR_METAINFO_BASENAME_NAME_AND_PARTIAL_HASH);
807     tr_sys_path_remove(filename, NULL);
808     tr_free(filename);
809 }
810 
tr_metainfoMigrateFile(tr_session const * session,tr_info const * info,enum tr_metainfo_basename_format old_format,enum tr_metainfo_basename_format new_format)811 void tr_metainfoMigrateFile(tr_session const* session, tr_info const* info, enum tr_metainfo_basename_format old_format,
812     enum tr_metainfo_basename_format new_format)
813 {
814     char* old_filename = getTorrentFilename(session, info, old_format);
815     char* new_filename = getTorrentFilename(session, info, new_format);
816 
817     if (tr_sys_path_rename(old_filename, new_filename, NULL))
818     {
819         tr_logAddNamedError(info->name, "Migrated torrent file from \"%s\" to \"%s\"", old_filename, new_filename);
820     }
821 
822     tr_free(new_filename);
823     tr_free(old_filename);
824 }
825