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