Coverage Report

Created: 2025-07-01 06:55

/src/libtorrent/src/torrent_info.cpp
Line
Count
Source (jump to first uncovered line)
1
/*
2
3
Copyright (c) 2003-2022, Arvid Norberg
4
Copyright (c) 2016-2018, Alden Torres
5
Copyright (c) 2016, 2019, Andrei Kurushin
6
Copyright (c) 2016-2017, Pavel Pimenov
7
Copyright (c) 2016-2019, Steven Siloti
8
All rights reserved.
9
10
Redistribution and use in source and binary forms, with or without
11
modification, are permitted provided that the following conditions
12
are met:
13
14
    * Redistributions of source code must retain the above copyright
15
      notice, this list of conditions and the following disclaimer.
16
    * Redistributions in binary form must reproduce the above copyright
17
      notice, this list of conditions and the following disclaimer in
18
      the documentation and/or other materials provided with the distribution.
19
    * Neither the name of the author nor the names of its
20
      contributors may be used to endorse or promote products derived
21
      from this software without specific prior written permission.
22
23
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
24
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
25
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
26
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
27
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
28
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
29
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
30
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
31
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
32
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
33
POSSIBILITY OF SUCH DAMAGE.
34
35
*/
36
37
#include "libtorrent/config.hpp"
38
#include "libtorrent/torrent_info.hpp"
39
#include "libtorrent/string_util.hpp" // is_space, is_i2p_url
40
#include "libtorrent/bencode.hpp"
41
#include "libtorrent/hasher.hpp"
42
#include "libtorrent/entry.hpp"
43
#include "libtorrent/aux_/path.hpp"
44
#include "libtorrent/aux_/open_mode.hpp"
45
#include "libtorrent/utf8.hpp"
46
#include "libtorrent/time.hpp"
47
#include "libtorrent/random.hpp"
48
#include "libtorrent/aux_/invariant_check.hpp"
49
#include "libtorrent/aux_/escape_string.hpp" // maybe_url_encode
50
#include "libtorrent/aux_/merkle.hpp" // for merkle_*
51
#include "libtorrent/aux_/throw.hpp"
52
#include "libtorrent/add_torrent_params.hpp"
53
#include "libtorrent/magnet_uri.hpp"
54
#include "libtorrent/announce_entry.hpp"
55
#include "libtorrent/hex.hpp" // to_hex
56
#include "libtorrent/aux_/numeric_cast.hpp"
57
#include "libtorrent/aux_/file_pointer.hpp"
58
#include "libtorrent/disk_interface.hpp" // for default_block_size
59
#include "libtorrent/span.hpp"
60
61
#include "libtorrent/aux_/disable_warnings_push.hpp"
62
#include <boost/crc.hpp>
63
#include "libtorrent/aux_/disable_warnings_pop.hpp"
64
65
#include <unordered_map>
66
#include <unordered_set>
67
#include <cstdint>
68
#include <cstdio>
69
#include <cinttypes>
70
#include <iterator>
71
#include <algorithm>
72
#include <set>
73
#include <ctime>
74
#include <array>
75
76
namespace libtorrent {
77
78
  TORRENT_EXPORT from_span_t from_span;
79
80
  constexpr torrent_info_flags_t torrent_info::multifile;
81
  constexpr torrent_info_flags_t torrent_info::private_torrent;
82
  constexpr torrent_info_flags_t torrent_info::i2p;
83
  constexpr torrent_info_flags_t torrent_info::ssl_torrent;
84
  constexpr torrent_info_flags_t torrent_info::v2_has_piece_hashes;
85
86
  namespace {
87
88
  // Which characters are valid is primarily determined by the
89
  // filesystem, so this logic is an approximation. Note that forward- and
90
  // backslash are filtered unconditionally and separately from this function.
91
  bool valid_path_character(std::int32_t const c)
92
10.7M
  {
93
#ifdef TORRENT_WINDOWS
94
    // On windows, both the filesystem and the operating system impose
95
    // restrictions.
96
    static const char invalid_chars[] = "?<>\"|\b*:";
97
#elif defined TORRENT_ANDROID
98
    // The Android kernel probably has similar restrictions as Linux (i.e.
99
    // very few) but it appears some user-space system libraries impose
100
    // additional restrictions, and it's probably more common to use FAT32
101
    // style filesystems, which also further restricts valid characters
102
    // https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/java/android/os/FileUtils.java;l=997?q=isValidFatFilenameChar
103
    static const char invalid_chars[] = "\"*:<>?|";
104
#else
105
10.7M
    static const char invalid_chars[] = "";
106
10.7M
#endif
107
10.7M
    if (c < 32) return false;
108
20.3k
    if (c > 127) return true;
109
19.4k
    return std::strchr(invalid_chars, static_cast<char>(c)) == nullptr;
110
20.3k
  }
111
112
  bool filter_path_character(std::int32_t const c)
113
10.7M
  {
114
    // these unicode characters change the writing direction of the
115
    // string and can be used for attacks:
116
    // https://security.stackexchange.com/questions/158802/how-can-this-executable-have-an-avi-extension
117
10.7M
    static const std::array<std::int32_t, 7> bad_cp = {{0x202a, 0x202b, 0x202c, 0x202d, 0x202e, 0x200e, 0x200f}};
118
10.7M
    if (std::find(bad_cp.begin(), bad_cp.end(), c) != bad_cp.end()) return true;
119
120
10.7M
    static const char invalid_chars[] = "/\\";
121
10.7M
    if (c > 127) return false;
122
10.7M
    return std::strchr(invalid_chars, static_cast<char>(c)) != nullptr;
123
10.7M
  }
124
125
  } // anonymous namespace
126
127
namespace aux {
128
129
  // fixes invalid UTF-8 sequences
130
  bool verify_encoding(std::string& target)
131
4
  {
132
4
    if (target.empty()) return true;
133
134
0
    std::string tmp_path;
135
0
    tmp_path.reserve(target.size()+5);
136
0
    bool valid_encoding = true;
137
138
0
    string_view ptr = target;
139
0
    while (!ptr.empty())
140
0
    {
141
0
      std::int32_t codepoint;
142
0
      int len;
143
144
      // decode a single utf-8 character
145
0
      std::tie(codepoint, len) = parse_utf8_codepoint(ptr);
146
147
      // this was the last character, and nothing was
148
      // written to the destination buffer (i.e. the source character was
149
      // truncated)
150
0
      if (codepoint == -1)
151
0
      {
152
0
        codepoint = '_';
153
0
        valid_encoding = false;
154
0
      }
155
156
0
      ptr = ptr.substr(std::min(std::size_t(len), ptr.size()));
157
158
      // encode codepoint into utf-8
159
0
      append_utf8_codepoint(tmp_path, codepoint);
160
0
    }
161
162
    // the encoding was not valid utf-8
163
    // save the original encoding and replace the
164
    // commonly used path with the correctly
165
    // encoded string
166
0
    if (!valid_encoding) target = tmp_path;
167
0
    return valid_encoding;
168
4
  }
169
170
  // it's important that every call adds a path element to the path, even if
171
  // the name is invalid. It can never be empty. Empty files have special
172
  // meaning in v2 torrents (it means the previous path element was the
173
  // filename). Also, If we're adding the torrent name as the first path
174
  // element, in a multi-file torrent, we must have a directory name.
175
  void sanitize_append_path_element(std::string& path, string_view element, bool const force_element)
176
632
  {
177
632
    if (element.size() == 1 && element[0] == '.' && !force_element) return;
178
179
#ifdef TORRENT_WINDOWS
180
#define TORRENT_SEPARATOR '\\'
181
#else
182
631
#define TORRENT_SEPARATOR '/'
183
631
#endif
184
631
    path.reserve(path.size() + element.size() + 2);
185
631
    int added_separator = 0;
186
631
    if (!path.empty())
187
0
    {
188
0
      path += TORRENT_SEPARATOR;
189
0
      added_separator = 1;
190
0
    }
191
192
631
    if (element.empty())
193
0
    {
194
0
      path += "_";
195
0
      return;
196
0
    }
197
198
#if !TORRENT_USE_UNC_PATHS && defined TORRENT_WINDOWS
199
#pragma message ("building for windows without UNC paths is deprecated")
200
201
    // if we're not using UNC paths on windows, there
202
    // are certain filenames we're not allowed to use
203
    static const char const* reserved_names[] =
204
    {
205
      "con", "prn", "aux", "clock$", "nul",
206
      "com0", "com1", "com2", "com3", "com4",
207
      "com5", "com6", "com7", "com8", "com9",
208
      "lpt0", "lpt1", "lpt2", "lpt3", "lpt4",
209
      "lpt5", "lpt6", "lpt7", "lpt8", "lpt9"
210
    };
211
    int num_names = sizeof(reserved_names)/sizeof(reserved_names[0]);
212
213
    // this is not very efficient, but it only affects some specific
214
    // windows builds for now anyway (not even the default windows build)
215
    std::string pe = element.to_string();
216
    char const* file_end = strrchr(pe.c_str(), '.');
217
    std::string name = file_end
218
      ? std::string(pe.data(), file_end)
219
      : pe;
220
    std::transform(name.begin(), name.end(), name.begin(), &to_lower);
221
    char const** str = std::find(reserved_names, reserved_names + num_names, name);
222
    if (str != reserved_names + num_names)
223
    {
224
      pe = "_" + pe;
225
      element = string_view();
226
    }
227
#endif
228
#ifdef TORRENT_WINDOWS
229
    // this counts the number of unicode characters
230
    // we've added (which is different from the number
231
    // of bytes)
232
    int unicode_chars = 0;
233
#endif
234
235
631
    int added = 0;
236
    // the number of dots we've added
237
631
    char num_dots = 0;
238
631
    bool found_extension = false;
239
240
631
    int seq_len = 0;
241
10.8M
    for (std::size_t i = 0; i < element.size(); i += std::size_t(seq_len))
242
10.8M
    {
243
10.8M
      std::int32_t code_point;
244
10.8M
      std::tie(code_point, seq_len) = parse_utf8_codepoint(element.substr(i));
245
246
10.8M
      if (code_point >= 0 && filter_path_character(code_point))
247
35.4k
      {
248
35.4k
        continue;
249
35.4k
      }
250
251
10.8M
      if (code_point < 0 || !valid_path_character(code_point))
252
10.8M
      {
253
        // invalid utf8 sequence, replace with "_"
254
10.8M
        path += '_';
255
10.8M
        ++added;
256
#ifdef TORRENT_WINDOWS
257
        ++unicode_chars;
258
#endif
259
10.8M
        continue;
260
10.8M
      }
261
262
      // validation passed, add it to the output string
263
42.6k
      for (std::size_t k = i; k < i + std::size_t(seq_len); ++k)
264
22.2k
      {
265
22.2k
        TORRENT_ASSERT(element[k] != 0);
266
22.2k
        path.push_back(element[k]);
267
22.2k
      }
268
269
20.3k
      if (code_point == '.') ++num_dots;
270
271
20.3k
      added += seq_len;
272
#ifdef TORRENT_WINDOWS
273
      ++unicode_chars;
274
#endif
275
276
      // any given path element should not
277
      // be more than 255 characters
278
      // if we exceed 240, pick up any potential
279
      // file extension and add that too
280
#ifdef TORRENT_WINDOWS
281
      if (unicode_chars >= 240 && !found_extension)
282
#else
283
20.3k
      if (added >= 240 && !found_extension)
284
166
#endif
285
166
      {
286
166
        int dot = -1;
287
166
        for (int j = int(element.size()) - 1;
288
698
          j > std::max(int(element.size()) - 10, int(i)); --j)
289
609
        {
290
609
          if (element[aux::numeric_cast<std::size_t>(j)] != '.') continue;
291
77
          dot = j;
292
77
          break;
293
609
        }
294
        // there is no extension
295
166
        if (dot == -1) break;
296
77
        found_extension = true;
297
77
        TORRENT_ASSERT(dot > 0);
298
77
        i = std::size_t(dot - seq_len);
299
77
      }
300
20.3k
    }
301
302
631
    if (added == num_dots && added <= 2)
303
18
    {
304
18
      if (force_element)
305
0
      {
306
        // revert the invalid filename and replace it with an underscore
307
0
        path.erase(path.end() - added, path.end());
308
0
        path += "_";
309
0
      }
310
18
      else
311
18
      {
312
        // revert everything
313
18
        path.erase(path.end() - added - added_separator, path.end());
314
18
      }
315
18
      return;
316
18
    }
317
318
#ifdef TORRENT_WINDOWS
319
    // remove trailing spaces and dots. These aren't allowed in filenames on windows
320
    for (int i = int(path.size()) - 1; i >= 0; --i)
321
    {
322
      if (path[i] != ' ' && path[i] != '.') break;
323
      path.resize(i);
324
      --added;
325
      TORRENT_ASSERT(added >= 0);
326
    }
327
328
    if (force_element && added == 0)
329
    {
330
      path += "_";
331
    }
332
    else if (added == 0 && added_separator)
333
    {
334
      // remove the separator added at the beginning
335
      path.erase(path.end() - 1);
336
      return;
337
    }
338
#endif
339
340
613
    if (path.empty()) path = "_";
341
613
  }
342
}
343
344
namespace {
345
346
  file_flags_t get_file_attributes(bdecode_node const& dict)
347
4
  {
348
4
    file_flags_t file_flags = {};
349
4
    bdecode_node const attr = dict.dict_find_string("attr");
350
4
    if (attr)
351
0
    {
352
0
      for (char const c : attr.string_value())
353
0
      {
354
0
        switch (c)
355
0
        {
356
0
          case 'l': file_flags |= file_storage::flag_symlink; break;
357
0
          case 'x': file_flags |= file_storage::flag_executable; break;
358
0
          case 'h': file_flags |= file_storage::flag_hidden; break;
359
0
          case 'p': file_flags |= file_storage::flag_pad_file; break;
360
0
        }
361
0
      }
362
0
    }
363
4
    return file_flags;
364
4
  }
365
366
  // iterates an array of strings and returns the sum of the lengths of all
367
  // strings + one additional character per entry (to account for the presumed
368
  // forward- or backslash to separate directory entries)
369
  int path_length(bdecode_node const& p, error_code& ec)
370
0
  {
371
0
    int ret = 0;
372
0
    int const len = p.list_size();
373
0
    for (int i = 0; i < len; ++i)
374
0
    {
375
0
      bdecode_node const e = p.list_at(i);
376
0
      if (e.type() != bdecode_node::string_t)
377
0
      {
378
0
        ec = errors::torrent_invalid_name;
379
0
        return -1;
380
0
      }
381
0
      ret += e.string_length();
382
0
    }
383
0
    return ret + len;
384
0
  }
385
386
  bool extract_single_file2(bdecode_node const& dict, file_storage& files
387
    , std::string const& path, string_view const name
388
    , std::ptrdiff_t const info_offset, char const* info_buffer
389
    , error_code& ec)
390
2
  {
391
2
    if (dict.type() != bdecode_node::dict_t) return false;
392
393
2
    file_flags_t file_flags = get_file_attributes(dict);
394
395
2
    if (file_flags & file_storage::flag_pad_file)
396
0
    {
397
0
      ec = errors::torrent_invalid_pad_file;
398
0
      return false;
399
0
    }
400
401
    // symlinks have an implied "size" of zero. i.e. they use up 0 bytes of
402
    // the torrent payload space
403
2
    std::int64_t const file_size = (file_flags & file_storage::flag_symlink)
404
2
      ? 0 : dict.dict_find_int_value("length", -1);
405
406
    // if a file is too big, it will cause integer overflow in our
407
    // calculations of the size of the merkle tree (which is all 'int'
408
    // indices)
409
2
    if (file_size < 0
410
2
      || (file_size / default_block_size) >= file_storage::max_num_pieces
411
2
      || file_size > file_storage::max_file_size)
412
0
    {
413
0
      ec = errors::torrent_invalid_length;
414
0
      return false;
415
0
    }
416
417
2
    std::time_t const mtime = std::time_t(dict.dict_find_int_value("mtime", 0));
418
419
2
    char const* pieces_root = nullptr;
420
421
2
    std::string symlink_path;
422
2
    if (file_flags & file_storage::flag_symlink)
423
0
    {
424
0
      if (bdecode_node const s_p = dict.dict_find_list("symlink path"))
425
0
      {
426
0
        auto const preallocate = static_cast<std::size_t>(path_length(s_p, ec));
427
0
        if (ec) return false;
428
0
        symlink_path.reserve(preallocate);
429
0
        for (int i = 0, end(s_p.list_size()); i < end; ++i)
430
0
        {
431
0
          auto pe = s_p.list_at(i).string_value();
432
0
          aux::sanitize_append_path_element(symlink_path, pe);
433
0
        }
434
0
      }
435
0
    }
436
437
2
    if (symlink_path.empty() && file_size > 0)
438
2
    {
439
2
      bdecode_node const root = dict.dict_find_string("pieces root");
440
2
      if (!root || root.type() != bdecode_node::string_t
441
2
        || root.string_length() != sha256_hash::size())
442
0
      {
443
0
        ec = errors::torrent_missing_pieces_root;
444
0
        return false;
445
0
      }
446
2
      pieces_root = info_buffer + (root.string_offset() - info_offset);
447
2
      if (sha256_hash(pieces_root).is_all_zeros())
448
0
      {
449
0
        ec = errors::torrent_missing_pieces_root;
450
0
        return false;
451
0
      }
452
2
    }
453
454
2
    files.add_file_borrow(ec, name, path, file_size, file_flags, nullptr
455
2
      , mtime, symlink_path, pieces_root);
456
2
    return !ec;
457
2
  }
458
459
  // 'top_level' is extracting the file for a single-file torrent. The
460
  // distinction is that the filename is found in "name" rather than
461
  // "path"
462
  // root_dir is the name of the torrent, unless this is a single file
463
  // torrent, in which case it's empty.
464
  bool extract_single_file(bdecode_node const& dict, file_storage& files
465
    , std::string const& root_dir, std::ptrdiff_t const info_offset
466
    , char const* info_buffer, bool const top_level, error_code& ec)
467
2
  {
468
2
    if (dict.type() != bdecode_node::dict_t) return false;
469
470
2
    file_flags_t file_flags = get_file_attributes(dict);
471
472
    // symlinks have an implied "size" of zero. i.e. they use up 0 bytes of
473
    // the torrent payload space
474
2
    std::int64_t const file_size = (file_flags & file_storage::flag_symlink)
475
2
      ? 0 : dict.dict_find_int_value("length", -1);
476
477
    // if a file is too big, it will cause integer overflow in our
478
    // calculations of the size of the merkle tree (which is all 'int'
479
    // indices)
480
2
    if (file_size < 0
481
2
      || (file_size / default_block_size) >= std::numeric_limits<int>::max() / 2
482
2
      || file_size > file_storage::max_file_size)
483
0
    {
484
0
      ec = errors::torrent_invalid_length;
485
0
      return false;
486
0
    }
487
488
2
    std::time_t const mtime = std::time_t(dict.dict_find_int_value("mtime", 0));
489
490
2
    std::string path = root_dir;
491
2
    string_view filename;
492
493
2
    if (top_level)
494
2
    {
495
      // prefer the name.utf-8 because if it exists, it is more likely to be
496
      // correctly encoded
497
2
      bdecode_node p = dict.dict_find_string("name.utf-8");
498
2
      if (!p) p = dict.dict_find_string("name");
499
2
      if (!p || p.string_length() == 0)
500
0
      {
501
0
        ec = errors::torrent_missing_name;
502
0
        return false;
503
0
      }
504
505
2
      filename = { info_buffer + (p.string_offset() - info_offset)
506
2
        , static_cast<std::size_t>(p.string_length())};
507
508
2
      while (!filename.empty() && filename.front() == TORRENT_SEPARATOR)
509
0
        filename.remove_prefix(1);
510
511
2
      aux::sanitize_append_path_element(path, p.string_value());
512
2
      if (path.empty())
513
0
      {
514
0
        ec = errors::torrent_missing_name;
515
0
        return false;
516
0
      }
517
2
    }
518
0
    else
519
0
    {
520
0
      bdecode_node p = dict.dict_find_list("path.utf-8");
521
0
      if (!p) p = dict.dict_find_list("path");
522
523
0
      if (p && p.list_size() > 0)
524
0
      {
525
0
        std::size_t const preallocate = path.size() + std::size_t(path_length(p, ec));
526
0
        if (ec) return false;
527
0
        path.reserve(preallocate);
528
529
0
        for (int i = 0, end(p.list_size()); i < end; ++i)
530
0
        {
531
0
          bdecode_node const e = p.list_at(i);
532
0
          if (i == end - 1)
533
0
          {
534
0
            filename = {info_buffer + (e.string_offset() - info_offset)
535
0
              , static_cast<std::size_t>(e.string_length()) };
536
0
            while (!filename.empty() && filename.front() == TORRENT_SEPARATOR)
537
0
              filename.remove_prefix(1);
538
0
          }
539
0
          aux::sanitize_append_path_element(path, e.string_value(), true);
540
0
        }
541
0
      }
542
0
      else if (file_flags & file_storage::flag_pad_file)
543
0
      {
544
        // pad files don't need a path element, we'll just store them
545
        // under the .pad directory
546
0
        char cnt[20];
547
0
        std::snprintf(cnt, sizeof(cnt), "%" PRIu64, file_size);
548
0
        path = combine_path(".pad", cnt);
549
0
      }
550
0
      else
551
0
      {
552
0
        ec = errors::torrent_missing_name;
553
0
        return false;
554
0
      }
555
0
    }
556
557
    // bitcomet pad file
558
2
    if (path.find("_____padding_file_") != std::string::npos)
559
0
      file_flags |= file_storage::flag_pad_file;
560
561
2
    bdecode_node const fh = dict.dict_find_string("sha1");
562
2
    char const* filehash = nullptr;
563
2
    if (fh && fh.string_length() == 20)
564
0
      filehash = info_buffer + (fh.string_offset() - info_offset);
565
566
2
    std::string symlink_path;
567
2
    if (file_flags & file_storage::flag_symlink)
568
0
    {
569
0
      if (bdecode_node const s_p = dict.dict_find_list("symlink path"))
570
0
      {
571
0
        auto const preallocate = static_cast<std::size_t>(path_length(s_p, ec));
572
0
        if (ec) return false;
573
0
        symlink_path.reserve(preallocate);
574
0
        for (int i = 0, end(s_p.list_size()); i < end; ++i)
575
0
        {
576
0
          auto pe = s_p.list_at(i).string_value();
577
0
          aux::sanitize_append_path_element(symlink_path, pe);
578
0
        }
579
0
      }
580
0
      else
581
0
      {
582
        // technically this is an invalid torrent. "symlink path" must exist
583
0
        file_flags &= ~file_storage::flag_symlink;
584
0
      }
585
      // symlink targets are validated later, as it may point to a file or
586
      // directory we haven't parsed yet
587
0
    }
588
589
2
    if (filename.size() > path.length()
590
2
      || path.substr(path.size() - filename.size()) != filename)
591
0
    {
592
      // if the filename was sanitized and differ, clear it to just use path
593
0
      filename = {};
594
0
    }
595
596
2
    files.add_file_borrow(ec, filename, path, file_size, file_flags, filehash
597
2
      , mtime, symlink_path);
598
2
    return !ec;
599
2
  }
600
601
  bool extract_files2(bdecode_node const& tree, file_storage& target
602
    , std::string const& root_dir, ptrdiff_t const info_offset
603
    , char const* info_buffer
604
    , bool const has_files, int const depth, error_code& ec)
605
2
  {
606
2
    if (tree.type() != bdecode_node::dict_t)
607
0
    {
608
0
      ec = errors::torrent_file_parse_failed;
609
0
      return false;
610
0
    }
611
612
    // since we're parsing this recursively, we have to be careful not to blow
613
    // up the stack. 100 levels of sub directories should be enough. This
614
    // could be improved by an iterative parser, keeping the state on a more
615
    // compact side-stack
616
2
    if (depth > 100)
617
0
    {
618
0
      ec = errors::torrent_file_parse_failed;
619
0
      return false;
620
0
    }
621
622
4
    for (int i = 0; i < tree.dict_size(); ++i)
623
2
    {
624
2
      auto e = tree.dict_at_node(i);
625
2
      if (e.second.type() != bdecode_node::dict_t || e.first.string_value().empty())
626
0
      {
627
0
        ec = errors::torrent_file_parse_failed;
628
0
        return false;
629
0
      }
630
631
2
      string_view filename = { info_buffer + (e.first.string_offset() - info_offset)
632
2
        , static_cast<size_t>(e.first.string_length()) };
633
2
      while (!filename.empty() && filename.front() == TORRENT_SEPARATOR)
634
0
        filename.remove_prefix(1);
635
636
2
      bool const leaf_node = e.second.dict_size() == 1 && e.second.dict_at(0).first.empty();
637
2
      bool const single_file = leaf_node && !has_files && tree.dict_size() == 1;
638
639
2
      std::string path = single_file ? std::string() : root_dir;
640
2
      aux::sanitize_append_path_element(path, filename, true);
641
642
2
      if (leaf_node)
643
2
      {
644
2
        if (filename.size() > path.length()
645
2
          || path.substr(path.size() - filename.size()) != filename)
646
0
        {
647
          // if the filename was sanitized and differ, clear it to just use path
648
0
          filename = {};
649
0
        }
650
651
2
        if (!extract_single_file2(e.second.dict_at(0).second, target
652
2
          , path, filename, info_offset, info_buffer, ec))
653
0
        {
654
0
          return false;
655
0
        }
656
2
        continue;
657
2
      }
658
659
0
      if (!extract_files2(e.second, target, path, info_offset, info_buffer
660
0
        , true, depth + 1, ec))
661
0
      {
662
0
        return false;
663
0
      }
664
0
    }
665
666
2
    return true;
667
2
  }
668
669
  // root_dir is the name of the torrent, unless this is a single file
670
  // torrent, in which case it's empty.
671
  bool extract_files(bdecode_node const& list, file_storage& target
672
    , std::string const& root_dir, std::ptrdiff_t info_offset
673
    , char const* info_buffer, error_code& ec)
674
0
  {
675
0
    if (list.type() != bdecode_node::list_t)
676
0
    {
677
0
      ec = errors::torrent_file_parse_failed;
678
0
      return false;
679
0
    }
680
0
    target.reserve(list.list_size());
681
682
0
    for (int i = 0, end(list.list_size()); i < end; ++i)
683
0
    {
684
0
      if (!extract_single_file(list.list_at(i), target, root_dir
685
0
        , info_offset, info_buffer, false, ec))
686
0
        return false;
687
0
    }
688
    // this rewrites invalid symlinks to point to themselves
689
0
    target.sanitize_symlinks();
690
0
    return true;
691
0
  }
692
693
  int load_file(std::string const& filename, std::vector<char>& v
694
    , error_code& ec, int const max_buffer_size = 80000000)
695
0
  {
696
0
    ec.clear();
697
#ifdef TORRENT_WINDOWS
698
    aux::file_pointer f(::_wfopen(convert_to_native_path_string(filename).c_str(), L"rb"));
699
#else
700
0
    aux::file_pointer f(std::fopen(filename.c_str(), "rb"));
701
0
#endif
702
0
    if (f.file() == nullptr)
703
0
    {
704
0
      ec.assign(errno, generic_category());
705
0
      return -1;
706
0
    }
707
708
0
    if (std::fseek(f.file(), 0, SEEK_END) < 0)
709
0
    {
710
0
      ec.assign(errno, generic_category());
711
0
      return -1;
712
0
    }
713
0
    std::int64_t const s = std::ftell(f.file());
714
0
    if (s < 0)
715
0
    {
716
0
      ec.assign(errno, generic_category());
717
0
      return -1;
718
0
    }
719
0
    if (s > max_buffer_size)
720
0
    {
721
0
      ec = errors::metadata_too_large;
722
0
      return -1;
723
0
    }
724
0
    if (std::fseek(f.file(), 0, SEEK_SET) < 0)
725
0
    {
726
0
      ec.assign(errno, generic_category());
727
0
      return -1;
728
0
    }
729
0
    v.resize(std::size_t(s));
730
0
    if (s == 0) return 0;
731
0
    std::size_t const read = std::fread(v.data(), 1, v.size(), f.file());
732
0
    if (read != std::size_t(s))
733
0
    {
734
0
      if (std::feof(f.file()))
735
0
      {
736
0
        v.resize(read);
737
0
        return 0;
738
0
      }
739
0
      ec.assign(errno, generic_category());
740
0
      return -1;
741
0
    }
742
0
    return 0;
743
0
  }
744
745
} // anonymous namespace
746
747
  web_seed_entry::web_seed_entry(std::string url_, type_t type_
748
    , std::string auth_
749
    , headers_t extra_headers_)
750
0
    : url(std::move(url_))
751
0
    , auth(std::move(auth_))
752
0
    , extra_headers(std::move(extra_headers_))
753
0
    , type(std::uint8_t(type_))
754
0
  {
755
0
  }
756
757
TORRENT_VERSION_NAMESPACE_3
758
759
1
  torrent_info::torrent_info(torrent_info const&) = default;
760
0
  torrent_info& torrent_info::operator=(torrent_info&&) = default;
761
762
  void torrent_info::resolve_duplicate_filenames()
763
2
  {
764
2
    INVARIANT_CHECK;
765
766
2
    std::unordered_set<std::uint32_t> files;
767
768
2
    std::string const empty_str;
769
770
    // insert all directories first, to make sure no files
771
    // are allowed to collied with them
772
2
    m_files.all_path_hashes(files);
773
2
    for (auto const i : m_files.file_range())
774
2
    {
775
      // as long as this file already exists
776
      // increase the counter
777
2
      std::uint32_t const h = m_files.file_path_hash(i, empty_str);
778
2
      if (!files.insert(h).second)
779
0
      {
780
        // This filename appears to already exist!
781
        // If this happens, just start over and do it the slow way,
782
        // comparing full file names and come up with new names
783
0
        resolve_duplicate_filenames_slow();
784
0
        return;
785
0
      }
786
2
    }
787
2
  }
788
789
namespace {
790
791
  template <class CRC>
792
  void process_string_lowercase(CRC& crc, string_view str)
793
0
  {
794
0
    for (char const c : str)
795
0
      crc.process_byte(to_lower(c) & 0xff);
796
0
  }
797
798
  struct name_entry
799
  {
800
    file_index_t idx;
801
    int length;
802
  };
803
}
804
805
  void torrent_info::resolve_duplicate_filenames_slow()
806
0
  {
807
0
    INVARIANT_CHECK;
808
809
    // maps filename hash to file index
810
    // or, if the file_index is negative, maps into the paths vector
811
0
    std::unordered_multimap<std::uint32_t, name_entry> files;
812
813
0
    std::vector<std::string> const& paths = m_files.paths();
814
0
    files.reserve(paths.size() + aux::numeric_cast<std::size_t>(m_files.num_files()));
815
816
    // insert all directories first, to make sure no files
817
    // are allowed to collied with them
818
0
    {
819
0
      boost::crc_optimal<32, 0x1EDC6F41, 0xFFFFFFFF, 0xFFFFFFFF, true, true> crc;
820
0
      if (!m_files.name().empty())
821
0
      {
822
0
        process_string_lowercase(crc, m_files.name());
823
0
      }
824
0
      file_index_t path_index{-1};
825
0
      for (auto const& path : paths)
826
0
      {
827
0
        auto local_crc = crc;
828
0
        if (!path.empty()) local_crc.process_byte(TORRENT_SEPARATOR);
829
0
        int count = 0;
830
0
        for (char const c : path)
831
0
        {
832
0
          if (c == TORRENT_SEPARATOR)
833
0
            files.insert({local_crc.checksum(), {path_index, count}});
834
0
          local_crc.process_byte(to_lower(c) & 0xff);
835
0
          ++count;
836
0
        }
837
0
        files.insert({local_crc.checksum(), {path_index, int(path.size())}});
838
0
        --path_index;
839
0
      }
840
0
    }
841
842
    // keep track of the total number of name collisions. If there are too
843
    // many, it's probably a malicious torrent and we should just fail
844
0
    int num_collisions = 0;
845
0
    for (auto const i : m_files.file_range())
846
0
    {
847
      // as long as this file already exists
848
      // increase the counter
849
0
      std::uint32_t const hash = m_files.file_path_hash(i, "");
850
0
      auto range = files.equal_range(hash);
851
0
      auto const match = std::find_if(range.first, range.second, [&](std::pair<std::uint32_t, name_entry> const& o)
852
0
      {
853
0
        std::string const other_name = o.second.idx < file_index_t{}
854
0
          ? combine_path(m_files.name(), paths[std::size_t(-static_cast<int>(o.second.idx)-1)].substr(0, std::size_t(o.second.length)))
855
0
          : m_files.file_path(o.second.idx);
856
0
        return string_equal_no_case(other_name, m_files.file_path(i));
857
0
      });
858
859
0
      if (match == range.second)
860
0
      {
861
0
        files.insert({hash, {i, 0}});
862
0
        continue;
863
0
      }
864
865
      // pad files are allowed to collide with each-other, as long as they have
866
      // the same size.
867
0
      file_index_t const other_idx = match->second.idx;
868
0
      if (other_idx >= file_index_t{}
869
0
        && (m_files.file_flags(i) & file_storage::flag_pad_file)
870
0
        && (m_files.file_flags(other_idx) & file_storage::flag_pad_file)
871
0
        && m_files.file_size(i) == m_files.file_size(other_idx))
872
0
        continue;
873
874
0
      std::string filename = m_files.file_path(i);
875
0
      std::string base = remove_extension(filename);
876
0
      std::string ext = extension(filename);
877
0
      int cnt = 0;
878
0
      for (;;)
879
0
      {
880
0
        ++cnt;
881
0
        char new_ext[50];
882
0
        std::snprintf(new_ext, sizeof(new_ext), ".%d%s", cnt, ext.c_str());
883
0
        filename = base + new_ext;
884
885
0
        boost::crc_optimal<32, 0x1EDC6F41, 0xFFFFFFFF, 0xFFFFFFFF, true, true> crc;
886
0
        process_string_lowercase(crc, filename);
887
0
        std::uint32_t const new_hash = crc.checksum();
888
0
        if (files.find(new_hash) == files.end())
889
0
        {
890
0
          files.insert({new_hash, {i, 0}});
891
0
          break;
892
0
        }
893
0
        ++num_collisions;
894
0
        if (num_collisions > 100)
895
0
        {
896
        // TODO: this should be considered a failure, and the .torrent file
897
        // rejected
898
0
        }
899
0
      }
900
901
0
      copy_on_write();
902
0
      m_files.rename_file(i, filename);
903
0
    }
904
0
  }
905
906
  void torrent_info::remap_files(file_storage const& f)
907
0
  {
908
0
    INVARIANT_CHECK;
909
910
0
    TORRENT_ASSERT(is_loaded());
911
    // the new specified file storage must have the exact
912
    // same size as the current file storage
913
0
    TORRENT_ASSERT(m_files.total_size() == f.total_size());
914
915
0
    if (m_files.total_size() != f.total_size()) return;
916
0
    copy_on_write();
917
0
    m_files = f;
918
0
    m_files.set_num_pieces(m_orig_files->num_pieces());
919
0
    m_files.set_piece_length(m_orig_files->piece_length());
920
0
  }
921
922
#if TORRENT_ABI_VERSION == 1
923
  // standard constructor that parses a torrent file
924
  torrent_info::torrent_info(entry const& torrent_file)
925
0
  {
926
0
    std::vector<char> tmp;
927
0
    std::back_insert_iterator<std::vector<char>> out(tmp);
928
0
    bencode(out, torrent_file);
929
930
0
    bdecode_node e;
931
0
    error_code ec;
932
0
    if (tmp.empty() || bdecode(&tmp[0], &tmp[0] + tmp.size(), e, ec) != 0)
933
0
    {
934
0
#ifndef BOOST_NO_EXCEPTIONS
935
0
      aux::throw_ex<system_error>(ec);
936
#else
937
      return;
938
#endif
939
0
    }
940
0
#ifndef BOOST_NO_EXCEPTIONS
941
0
    if (!parse_torrent_file(e, ec, load_torrent_limits{}.max_pieces))
942
0
      aux::throw_ex<system_error>(ec);
943
#else
944
    parse_torrent_file(e, ec, load_torrent_limits{}.max_pieces);
945
#endif
946
0
    INVARIANT_CHECK;
947
0
  }
948
#endif // TORRENT_ABI_VERSION
949
950
#ifndef BOOST_NO_EXCEPTIONS
951
  torrent_info::torrent_info(bdecode_node const& torrent_file)
952
0
    : torrent_info(torrent_file, load_torrent_limits{})
953
0
  {}
954
955
  torrent_info::torrent_info(span<char const> buffer, from_span_t)
956
2
    : torrent_info(buffer, load_torrent_limits{}, from_span)
957
2
  {}
958
959
  torrent_info::torrent_info(std::string const& filename)
960
0
    : torrent_info(filename, load_torrent_limits{})
961
0
  {}
962
963
  torrent_info::torrent_info(bdecode_node const& torrent_file
964
    , load_torrent_limits const& cfg)
965
0
  {
966
0
    error_code ec;
967
0
    if (!parse_torrent_file(torrent_file, ec, cfg.max_pieces))
968
0
      aux::throw_ex<system_error>(ec);
969
970
0
    INVARIANT_CHECK;
971
0
  }
972
973
  torrent_info::torrent_info(span<char const> buffer
974
    , load_torrent_limits const& cfg, from_span_t)
975
2
  {
976
2
    error_code ec;
977
2
    bdecode_node e = bdecode(buffer, ec, nullptr
978
2
      , cfg.max_decode_depth, cfg.max_decode_tokens);
979
2
    if (ec) aux::throw_ex<system_error>(ec);
980
981
2
    if (!parse_torrent_file(e, ec, cfg.max_pieces))
982
0
      aux::throw_ex<system_error>(ec);
983
984
2
    INVARIANT_CHECK;
985
2
  }
986
987
  torrent_info::torrent_info(std::string const& filename
988
    , load_torrent_limits const& cfg)
989
0
  {
990
0
    std::vector<char> buf;
991
0
    error_code ec;
992
0
    int ret = load_file(filename, buf, ec, cfg.max_buffer_size);
993
0
    if (ret < 0) aux::throw_ex<system_error>(ec);
994
995
0
    bdecode_node e = bdecode(buf, ec, nullptr, cfg.max_decode_depth
996
0
      , cfg.max_decode_tokens);
997
0
    if (ec) aux::throw_ex<system_error>(ec);
998
999
0
    if (!parse_torrent_file(e, ec, cfg.max_pieces))
1000
0
      aux::throw_ex<system_error>(ec);
1001
1002
0
    INVARIANT_CHECK;
1003
0
  }
1004
#endif
1005
1006
  file_storage const& torrent_info::orig_files() const
1007
5
  {
1008
5
    TORRENT_ASSERT(is_loaded());
1009
5
    return m_orig_files ? *m_orig_files : m_files;
1010
5
  }
1011
1012
  void torrent_info::rename_file(file_index_t index, std::string const& new_filename)
1013
0
  {
1014
0
    TORRENT_ASSERT(is_loaded());
1015
0
    if (m_files.file_path(index) == new_filename) return;
1016
0
    copy_on_write();
1017
0
    m_files.rename_file(index, new_filename);
1018
0
  }
1019
1020
  torrent_info::torrent_info(bdecode_node const& torrent_file
1021
    , error_code& ec)
1022
0
  {
1023
0
    parse_torrent_file(torrent_file, ec, load_torrent_limits{}.max_pieces);
1024
0
    INVARIANT_CHECK;
1025
0
  }
1026
1027
  torrent_info::torrent_info(span<char const> buffer
1028
    , error_code& ec, from_span_t)
1029
0
  {
1030
0
    bdecode_node e = bdecode(buffer, ec);
1031
0
    if (ec) return;
1032
0
    parse_torrent_file(e, ec, load_torrent_limits{}.max_pieces);
1033
1034
0
    INVARIANT_CHECK;
1035
0
  }
1036
1037
  torrent_info::torrent_info(std::string const& filename, error_code& ec)
1038
0
  {
1039
0
    std::vector<char> buf;
1040
0
    int ret = load_file(filename, buf, ec);
1041
0
    if (ret < 0) return;
1042
1043
0
    bdecode_node e = bdecode(buf, ec);
1044
0
    if (ec) return;
1045
0
    parse_torrent_file(e, ec, load_torrent_limits{}.max_pieces);
1046
1047
0
    INVARIANT_CHECK;
1048
0
  }
1049
1050
  // constructor used for creating new torrents
1051
  // will not contain any hashes, comments, creation date
1052
  // just the necessary to use it with piece manager
1053
  // used for torrents with no metadata
1054
  torrent_info::torrent_info(info_hash_t const& info_hash)
1055
0
    : m_info_hash(info_hash)
1056
0
  {}
1057
1058
2
  torrent_info::~torrent_info() = default;
1059
1060
  // internal
1061
  void torrent_info::set_piece_layers(aux::vector<aux::vector<char>, file_index_t> pl)
1062
0
  {
1063
0
    m_piece_layers = pl;
1064
0
    m_flags |= v2_has_piece_hashes;
1065
0
  }
1066
1067
  sha1_hash torrent_info::hash_for_piece(piece_index_t const index) const
1068
0
  { return sha1_hash(hash_for_piece_ptr(index)); }
1069
1070
  void torrent_info::copy_on_write()
1071
0
  {
1072
0
    TORRENT_ASSERT(is_loaded());
1073
0
    INVARIANT_CHECK;
1074
1075
0
    if (m_orig_files) return;
1076
0
    m_orig_files.reset(new file_storage(m_files));
1077
0
  }
1078
1079
#if TORRENT_ABI_VERSION <= 2
1080
  void torrent_info::swap(torrent_info& ti)
1081
0
  {
1082
0
    INVARIANT_CHECK;
1083
1084
0
    torrent_info tmp = std::move(ti);
1085
0
    ti = std::move(*this);
1086
0
    *this = std::move(tmp);
1087
0
  }
1088
1089
  boost::shared_array<char> torrent_info::metadata() const
1090
0
  {
1091
0
    boost::shared_array<char> ret(new char[std::size_t(m_info_section_size)]);
1092
0
    std::memcpy(ret.get(), m_info_section.get(), std::size_t(m_info_section_size));
1093
0
    return ret;
1094
0
  }
1095
#endif
1096
1097
  string_view torrent_info::ssl_cert() const
1098
1
  {
1099
1
    if (!(m_flags & ssl_torrent)) return "";
1100
1101
    // this is parsed lazily
1102
0
    if (!m_info_dict)
1103
0
    {
1104
0
      error_code ec;
1105
0
      bdecode(m_info_section.get(), m_info_section.get()
1106
0
        + m_info_section_size, m_info_dict, ec);
1107
0
      TORRENT_ASSERT(!ec);
1108
0
      if (ec) return "";
1109
0
    }
1110
0
    TORRENT_ASSERT(m_info_dict.type() == bdecode_node::dict_t);
1111
0
    if (m_info_dict.type() != bdecode_node::dict_t) return "";
1112
0
    return m_info_dict.dict_find_string_value("ssl-cert");
1113
0
  }
1114
1115
#if TORRENT_ABI_VERSION < 3
1116
  bool torrent_info::parse_info_section(bdecode_node const& info, error_code& ec)
1117
0
  {
1118
0
    return parse_info_section(info, ec, 0x200000);
1119
0
  }
1120
#endif
1121
1122
  bool torrent_info::parse_info_section(bdecode_node const& info
1123
    , error_code& ec, int const max_pieces)
1124
2
  {
1125
2
    if (info.type() != bdecode_node::dict_t)
1126
0
    {
1127
0
      ec = errors::torrent_info_no_dict;
1128
0
      return false;
1129
0
    }
1130
1131
    // hash the info-field to calculate info-hash
1132
2
    auto section = info.data_section();
1133
2
    m_info_hash.v1 = hasher(section).final();
1134
2
    m_info_hash.v2 = hasher256(section).final();
1135
2
    if (info.data_section().size() >= std::numeric_limits<int>::max())
1136
0
    {
1137
0
      ec = errors::metadata_too_large;
1138
0
      return false;
1139
0
    }
1140
1141
2
    if (section.empty() || section[0] != 'd' || section[section.size() - 1] != 'e')
1142
0
    {
1143
0
      ec = errors::invalid_bencoding;
1144
0
      return false;
1145
0
    }
1146
1147
    // copy the info section
1148
2
    m_info_section_size = int(section.size());
1149
2
    m_info_section.reset(new char[aux::numeric_cast<std::size_t>(m_info_section_size)]);
1150
2
    std::memcpy(m_info_section.get(), section.data(), aux::numeric_cast<std::size_t>(m_info_section_size));
1151
1152
    // this is the offset from the start of the torrent file buffer to the
1153
    // info-dictionary (within the torrent file).
1154
    // we need this because we copy just the info dictionary buffer and pull
1155
    // out parsed data (strings) from the bdecode_node and need to make them
1156
    // point into our copy of the buffer.
1157
2
    std::ptrdiff_t const info_offset = info.data_offset();
1158
1159
    // check for a version key
1160
2
    int const version = int(info.dict_find_int_value("meta version", -1));
1161
2
    if (version > 0)
1162
2
    {
1163
2
      char error_string[200];
1164
2
      if (info.has_soft_error(error_string))
1165
0
      {
1166
0
        ec = errors::invalid_bencoding;
1167
0
        return false;
1168
0
      }
1169
1170
2
      if (version > 2)
1171
0
      {
1172
0
        ec = errors::torrent_unknown_version;
1173
0
        return false;
1174
0
      }
1175
2
    }
1176
1177
2
    if (version < 2)
1178
0
    {
1179
      // this is a v1 torrent so the v2 info hash has no meaning
1180
      // clear it just to make sure no one tries to use it
1181
0
      m_info_hash.v2.clear();
1182
0
    }
1183
1184
    // extract piece length
1185
2
    std::int64_t const piece_length = info.dict_find_int_value("piece length", -1);
1186
2
    if (piece_length <= 0 || piece_length > file_storage::max_piece_size)
1187
0
    {
1188
0
      ec = errors::torrent_missing_piece_length;
1189
0
      return false;
1190
0
    }
1191
1192
    // according to BEP 52: "It must be a power of two and at least 16KiB."
1193
2
    if (version > 1 && (piece_length < default_block_size
1194
2
      || (piece_length & (piece_length - 1)) != 0))
1195
0
    {
1196
0
      ec = errors::torrent_missing_piece_length;
1197
0
      return false;
1198
0
    }
1199
1200
2
    file_storage files;
1201
2
    files.set_piece_length(static_cast<int>(piece_length));
1202
1203
    // extract file name (or the directory name if it's a multi file libtorrent)
1204
2
    bdecode_node name_ent = info.dict_find_string("name.utf-8");
1205
2
    if (!name_ent) name_ent = info.dict_find_string("name");
1206
2
    if (!name_ent)
1207
0
    {
1208
0
      ec = errors::torrent_missing_name;
1209
      // mark the torrent as invalid
1210
0
      m_files.set_piece_length(0);
1211
0
      return false;
1212
0
    }
1213
1214
2
    std::string name;
1215
2
    aux::sanitize_append_path_element(name, name_ent.string_value());
1216
2
    if (name.empty())
1217
0
    {
1218
0
      if (m_info_hash.has_v1())
1219
0
        name = aux::to_hex(m_info_hash.v1);
1220
0
      else
1221
0
        name = aux::to_hex(m_info_hash.v2);
1222
0
    }
1223
1224
    // extract file list
1225
1226
    // save a copy so that we can extract both v1 and v2 files then compare the results
1227
2
    file_storage v1_files;
1228
2
    if (version >= 2)
1229
2
      v1_files = files;
1230
1231
2
    bdecode_node const files_node = info.dict_find_list("files");
1232
1233
2
    bdecode_node file_tree_node = info.dict_find_dict("file tree");
1234
2
    if (version >= 2 && file_tree_node)
1235
2
    {
1236
2
      if (!extract_files2(file_tree_node, files, name, info_offset
1237
2
        , m_info_section.get(), bool(files_node), 0, ec))
1238
0
      {
1239
        // mark the torrent as invalid
1240
0
        m_files.set_piece_length(0);
1241
0
        return false;
1242
0
      }
1243
1244
2
      files.sanitize_symlinks();
1245
2
      if (files.num_files() > 1)
1246
0
        m_flags |= multifile;
1247
2
      else
1248
2
        m_flags &= ~multifile;
1249
2
    }
1250
0
    else if (version >= 2)
1251
0
    {
1252
      // mark the torrent as invalid
1253
0
      m_files.set_piece_length(0);
1254
0
      ec = errors::torrent_missing_file_tree;
1255
0
      return false;
1256
0
    }
1257
0
    else if (file_tree_node)
1258
0
    {
1259
      // mark the torrent as invalid
1260
0
      m_files.set_piece_length(0);
1261
0
      ec = errors::torrent_missing_meta_version;
1262
0
      return false;
1263
0
    }
1264
1265
2
    if (!files_node)
1266
2
    {
1267
      // if this is a v2 torrent it is ok for the length key to be missing
1268
      // that means it is a v2 only torrent
1269
2
      if (version < 2 || info.dict_find("length"))
1270
2
      {
1271
        // if there's no list of files, there has to be a length
1272
        // field.
1273
2
        if (!extract_single_file(info, version == 2 ? v1_files : files, ""
1274
2
          , info_offset, m_info_section.get(), true, ec))
1275
0
        {
1276
          // mark the torrent as invalid
1277
0
          m_files.set_piece_length(0);
1278
0
          return false;
1279
0
        }
1280
1281
2
        m_flags &= ~multifile;
1282
2
      }
1283
0
      else
1284
0
      {
1285
        // this is a v2 only torrent so clear the v1 info hash to make sure no one uses it
1286
0
        m_info_hash.v1.clear();
1287
0
      }
1288
2
    }
1289
0
    else
1290
0
    {
1291
0
      if (!extract_files(files_node, version == 2 ? v1_files : files, name
1292
0
        , info_offset, m_info_section.get(), ec))
1293
0
      {
1294
        // mark the torrent as invalid
1295
0
        m_files.set_piece_length(0);
1296
0
        return false;
1297
0
      }
1298
0
      m_flags |= multifile;
1299
0
    }
1300
2
    if (files.num_files() == 0)
1301
0
    {
1302
0
      ec = errors::no_files_in_torrent;
1303
      // mark the torrent as invalid
1304
0
      m_files.set_piece_length(0);
1305
0
      return false;
1306
0
    }
1307
2
    if (files.name().empty())
1308
0
    {
1309
0
      ec = errors::torrent_missing_name;
1310
      // mark the torrent as invalid
1311
0
      m_files.set_piece_length(0);
1312
0
      return false;
1313
0
    }
1314
1315
    // ensure hybrid torrents have compatible v1 and v2 file storages
1316
2
    if (version >= 2 && v1_files.num_files() > 0)
1317
2
    {
1318
      // previous versions of libtorrent did not not create hybrid
1319
      // torrents with "tail-padding". When loading, accept both.
1320
2
      if (files.num_files() == v1_files.num_files() + 1)
1321
0
      {
1322
0
        files.remove_tail_padding();
1323
0
      }
1324
1325
2
      if (!aux::files_compatible(files, v1_files))
1326
0
      {
1327
        // mark the torrent as invalid
1328
0
        m_files.set_piece_length(0);
1329
0
        ec = errors::torrent_inconsistent_files;
1330
0
        return false;
1331
0
      }
1332
2
    }
1333
1334
    // extract SHA-1 hashes for all pieces
1335
    // we want this division to round upwards, that's why we have the
1336
    // extra addition
1337
1338
2
    if (files.total_size() / files.piece_length() > file_storage::max_num_pieces)
1339
0
    {
1340
0
      ec = errors::too_many_pieces_in_torrent;
1341
      // mark the torrent as invalid
1342
0
      m_files.set_piece_length(0);
1343
0
      return false;
1344
0
    }
1345
1346
2
    files.set_num_pieces(int((files.total_size() + files.piece_length() - 1)
1347
2
      / files.piece_length()));
1348
1349
    // we expect the piece hashes to be < 2 GB in size
1350
2
    if (files.num_pieces() >= std::numeric_limits<int>::max() / 20
1351
2
      || files.num_pieces() > max_pieces)
1352
0
    {
1353
0
      ec = errors::too_many_pieces_in_torrent;
1354
      // mark the torrent as invalid
1355
0
      m_files.set_piece_length(0);
1356
0
      return false;
1357
0
    }
1358
1359
2
    bdecode_node const pieces = info.dict_find_string("pieces");
1360
2
    if (!pieces)
1361
0
    {
1362
0
      if (version < 2)
1363
0
      {
1364
0
        ec = errors::torrent_missing_pieces;
1365
        // mark the torrent as invalid
1366
0
        m_files.set_piece_length(0);
1367
0
        return false;
1368
0
      }
1369
0
    }
1370
2
    else
1371
2
    {
1372
2
      if (pieces.string_length() != files.num_pieces() * 20)
1373
0
      {
1374
0
        ec = errors::torrent_invalid_hashes;
1375
        // mark the torrent as invalid
1376
0
        m_files.set_piece_length(0);
1377
0
        return false;
1378
0
      }
1379
1380
2
      std::ptrdiff_t const hash_offset = pieces.string_offset() - info_offset;
1381
2
      TORRENT_ASSERT(hash_offset < std::numeric_limits<std::int32_t>::max());
1382
2
      TORRENT_ASSERT(hash_offset >= 0);
1383
2
      m_piece_hashes = static_cast<std::int32_t>(hash_offset);
1384
2
      TORRENT_ASSERT(m_piece_hashes > 0);
1385
2
      TORRENT_ASSERT(m_piece_hashes < m_info_section_size);
1386
2
    }
1387
1388
2
    m_flags |= (info.dict_find_int_value("private", 0) != 0)
1389
2
      ? private_torrent : torrent_info_flags_t{};
1390
1391
2
#ifndef TORRENT_DISABLE_MUTABLE_TORRENTS
1392
2
    bdecode_node const similar = info.dict_find_list("similar");
1393
2
    if (similar)
1394
0
    {
1395
0
      for (int i = 0; i < similar.list_size(); ++i)
1396
0
      {
1397
0
        if (similar.list_at(i).type() != bdecode_node::string_t)
1398
0
          continue;
1399
1400
0
        if (similar.list_at(i).string_length() != 20)
1401
0
          continue;
1402
0
        m_similar_torrents.push_back(static_cast<std::int32_t>(
1403
0
          similar.list_at(i).string_offset() - info_offset));
1404
0
      }
1405
0
    }
1406
1407
2
    bdecode_node const collections = info.dict_find_list("collections");
1408
2
    if (collections)
1409
0
    {
1410
0
      for (int i = 0; i < collections.list_size(); ++i)
1411
0
      {
1412
0
        bdecode_node const str = collections.list_at(i);
1413
1414
0
        if (str.type() != bdecode_node::string_t) continue;
1415
1416
0
        m_collections.emplace_back(std::int32_t(str.string_offset() - info_offset)
1417
0
          , str.string_length());
1418
0
      }
1419
0
    }
1420
2
#endif // TORRENT_DISABLE_MUTABLE_TORRENTS
1421
1422
2
    if (info.dict_find_string("ssl-cert"))
1423
0
      m_flags |= ssl_torrent;
1424
1425
2
    if (files.total_size() == 0)
1426
0
    {
1427
0
      ec = errors::torrent_invalid_length;
1428
      // mark the torrent as invalid
1429
0
      m_files.set_piece_length(0);
1430
0
      return false;
1431
0
    }
1432
1433
    // now, commit the files structure we just parsed out
1434
    // into the torrent_info object.
1435
2
    m_files.swap(files);
1436
1437
2
    TORRENT_ASSERT(m_info_hash.has_v2() == m_files.v2());
1438
2
    return true;
1439
2
  }
1440
1441
  bool torrent_info::parse_piece_layers(bdecode_node const& e, error_code& ec)
1442
2
  {
1443
2
    std::map<sha256_hash, string_view> piece_layers;
1444
1445
2
    if (e.type() != bdecode_node::dict_t)
1446
0
    {
1447
0
      ec = errors::torrent_missing_piece_layer;
1448
0
      return false;
1449
0
    }
1450
1451
2
    std::set<sha256_hash> all_file_roots;
1452
2
    auto const& fs = orig_files();
1453
2
    for (file_index_t i : fs.file_range())
1454
2
    {
1455
2
      if (fs.file_size(i) <= fs.piece_length())
1456
0
        continue;
1457
2
      all_file_roots.insert(fs.root(i));
1458
2
    }
1459
1460
4
    for (int i = 0; i < e.dict_size(); ++i)
1461
2
    {
1462
2
      auto const f = e.dict_at(i);
1463
2
      if (f.first.size() != static_cast<std::size_t>(sha256_hash::size())
1464
2
        || f.second.type() != bdecode_node::string_t
1465
2
        || f.second.string_length() % sha256_hash::size() != 0)
1466
0
      {
1467
0
        ec = errors::torrent_invalid_piece_layer;
1468
0
        return false;
1469
0
      }
1470
1471
2
      sha256_hash const root(f.first);
1472
2
      if (all_file_roots.find(root) == all_file_roots.end())
1473
0
      {
1474
        // This piece layer doesn't refer to any file in this torrent
1475
0
        ec = errors::torrent_invalid_piece_layer;
1476
0
        return false;
1477
0
      }
1478
1479
2
      piece_layers.emplace(sha256_hash(f.first), f.second.string_value());
1480
2
    }
1481
1482
2
    m_piece_layers.resize(fs.num_files());
1483
1484
2
    for (file_index_t i : fs.file_range())
1485
2
    {
1486
2
      if (fs.file_size(i) <= fs.piece_length())
1487
0
        continue;
1488
1489
2
      auto const piece_layer = piece_layers.find(fs.root(i));
1490
2
      if (piece_layer == piece_layers.end()) continue;
1491
1492
2
      int const num_pieces = fs.file_num_pieces(i);
1493
1494
2
      if (ptrdiff_t(piece_layer->second.size()) != num_pieces * sha256_hash::size())
1495
0
      {
1496
0
        ec = errors::torrent_invalid_piece_layer;
1497
0
        return false;
1498
0
      }
1499
1500
2
      auto const hashes = piece_layer->second;
1501
2
      if ((hashes.size() % sha256_hash::size()) != 0)
1502
0
      {
1503
0
        ec = errors::torrent_invalid_piece_layer;
1504
0
        return false;
1505
0
      }
1506
1507
2
      m_piece_layers[i].assign(hashes.begin(), hashes.end());
1508
2
    }
1509
1510
2
    m_flags |= v2_has_piece_hashes;
1511
2
    return true;
1512
2
  }
1513
1514
  span<char const> torrent_info::piece_layer(file_index_t f) const
1515
1
  {
1516
1
    TORRENT_ASSERT_PRECOND(f >= file_index_t(0));
1517
1
    if (f >= m_piece_layers.end_index()) return {};
1518
1
    if (m_files.pad_file_at(f)) return {};
1519
1520
1
    if (m_files.file_size(f) <= piece_length())
1521
0
    {
1522
0
      auto const root_ptr = m_files.root_ptr(f);
1523
0
      if (root_ptr == nullptr) return {};
1524
0
      return {root_ptr, lt::sha256_hash::size()};
1525
0
    }
1526
1
    return m_piece_layers[f];
1527
1
  }
1528
1529
  void torrent_info::free_piece_layers()
1530
1
  {
1531
1
    m_piece_layers.clear();
1532
1
    m_piece_layers.shrink_to_fit();
1533
1534
1
    m_flags &= ~v2_has_piece_hashes;
1535
1
  }
1536
1537
  void torrent_info::internal_set_creator(string_view const c)
1538
0
  { m_created_by = std::string(c); }
1539
1540
  void torrent_info::internal_set_creation_date(std::time_t const t)
1541
0
  { m_creation_date = t; }
1542
1543
  void torrent_info::internal_set_comment(string_view const s)
1544
0
  { m_comment = std::string(s); }
1545
1546
  bdecode_node torrent_info::info(char const* key) const
1547
0
  {
1548
0
    if (m_info_dict.type() == bdecode_node::none_t)
1549
0
    {
1550
0
      error_code ec;
1551
0
      bdecode(m_info_section.get(), m_info_section.get()
1552
0
        + m_info_section_size, m_info_dict, ec);
1553
0
      if (ec) return bdecode_node();
1554
0
    }
1555
0
    return m_info_dict.dict_find(key);
1556
0
  }
1557
1558
  bool torrent_info::parse_torrent_file(bdecode_node const& torrent_file
1559
    , error_code& ec, int const piece_limit)
1560
2
  {
1561
2
    if (torrent_file.type() != bdecode_node::dict_t)
1562
0
    {
1563
0
      ec = errors::torrent_is_no_dict;
1564
0
      return false;
1565
0
    }
1566
1567
2
    bdecode_node const info = torrent_file.dict_find_dict("info");
1568
2
    if (!info)
1569
0
    {
1570
0
      bdecode_node const uri = torrent_file.dict_find_string("magnet-uri");
1571
0
      if (uri)
1572
0
      {
1573
0
        auto const p = parse_magnet_uri(uri.string_value(), ec);
1574
0
        if (ec) return false;
1575
1576
0
        m_info_hash = p.info_hashes;
1577
0
        m_urls.reserve(m_urls.size() + p.trackers.size());
1578
0
        for (auto const& url : p.trackers)
1579
0
          m_urls.emplace_back(url);
1580
1581
0
        return true;
1582
0
      }
1583
1584
0
      ec = errors::torrent_missing_info;
1585
0
      return false;
1586
0
    }
1587
1588
2
    if (!parse_info_section(info, ec, piece_limit)) return false;
1589
2
    resolve_duplicate_filenames();
1590
1591
2
    if (m_info_hash.has_v2())
1592
2
    {
1593
      // allow torrent files without piece layers, just like we allow magnet
1594
      // links. However, if there are piece layers, make sure they're
1595
      // valid
1596
2
      bdecode_node const& e = torrent_file.dict_find_dict("piece layers");
1597
2
      if (e && !parse_piece_layers(e, ec))
1598
0
      {
1599
0
        TORRENT_ASSERT(ec);
1600
        // mark the torrent as invalid
1601
0
        m_files.set_piece_length(0);
1602
0
        return false;
1603
0
      }
1604
2
    }
1605
1606
2
#ifndef TORRENT_DISABLE_MUTABLE_TORRENTS
1607
2
    bdecode_node const similar = torrent_file.dict_find_list("similar");
1608
2
    if (similar)
1609
0
    {
1610
0
      for (int i = 0; i < similar.list_size(); ++i)
1611
0
      {
1612
0
        if (similar.list_at(i).type() != bdecode_node::string_t)
1613
0
          continue;
1614
1615
0
        if (similar.list_at(i).string_length() != 20)
1616
0
          continue;
1617
1618
0
        m_owned_similar_torrents.emplace_back(
1619
0
          similar.list_at(i).string_ptr());
1620
0
      }
1621
0
    }
1622
1623
2
    bdecode_node const collections = torrent_file.dict_find_list("collections");
1624
2
    if (collections)
1625
0
    {
1626
0
      for (int i = 0; i < collections.list_size(); ++i)
1627
0
      {
1628
0
        bdecode_node const str = collections.list_at(i);
1629
1630
0
        if (str.type() != bdecode_node::string_t) continue;
1631
1632
0
        m_owned_collections.emplace_back(str.string_ptr()
1633
0
          , aux::numeric_cast<std::size_t>(str.string_length()));
1634
0
      }
1635
0
    }
1636
2
#endif // TORRENT_DISABLE_MUTABLE_TORRENTS
1637
1638
    // extract the url of the tracker
1639
2
    bdecode_node const announce_node = torrent_file.dict_find_list("announce-list");
1640
2
    if (announce_node)
1641
0
    {
1642
0
      m_urls.reserve(announce_node.list_size());
1643
0
      for (int j = 0, end(announce_node.list_size()); j < end; ++j)
1644
0
      {
1645
0
        bdecode_node const tier = announce_node.list_at(j);
1646
0
        if (tier.type() != bdecode_node::list_t) continue;
1647
0
        for (int k = 0, end2(tier.list_size()); k < end2; ++k)
1648
0
        {
1649
0
          announce_entry e(tier.list_string_value_at(k).to_string());
1650
0
          ltrim(e.url);
1651
0
          if (e.url.empty()) continue;
1652
0
          e.tier = std::uint8_t(j);
1653
0
          e.fail_limit = 0;
1654
0
          e.source = announce_entry::source_torrent;
1655
0
#if TORRENT_USE_I2P
1656
0
          if (is_i2p_url(e.url)) m_flags |= i2p;
1657
0
#endif
1658
0
          m_urls.push_back(e);
1659
0
        }
1660
0
      }
1661
1662
0
      if (!m_urls.empty())
1663
0
      {
1664
        // shuffle each tier
1665
0
        aux::random_shuffle(m_urls);
1666
0
        std::stable_sort(m_urls.begin(), m_urls.end()
1667
0
          , [](announce_entry const& lhs, announce_entry const& rhs)
1668
0
          { return lhs.tier < rhs.tier; });
1669
0
      }
1670
0
    }
1671
1672
2
    if (m_urls.empty())
1673
2
    {
1674
2
      announce_entry e(torrent_file.dict_find_string_value("announce"));
1675
2
      e.fail_limit = 0;
1676
2
      e.source = announce_entry::source_torrent;
1677
2
      ltrim(e.url);
1678
2
#if TORRENT_USE_I2P
1679
2
      if (is_i2p_url(e.url)) m_flags |= i2p;
1680
2
#endif
1681
2
      if (!e.url.empty()) m_urls.push_back(e);
1682
2
    }
1683
1684
2
    bdecode_node const nodes = torrent_file.dict_find_list("nodes");
1685
2
    if (nodes)
1686
0
    {
1687
0
      for (int i = 0, end(nodes.list_size()); i < end; ++i)
1688
0
      {
1689
0
        bdecode_node const n = nodes.list_at(i);
1690
0
        if (n.type() != bdecode_node::list_t
1691
0
          || n.list_size() < 2
1692
0
          || n.list_at(0).type() != bdecode_node::string_t
1693
0
          || n.list_at(1).type() != bdecode_node::int_t)
1694
0
          continue;
1695
0
        m_nodes.emplace_back(
1696
0
          n.list_at(0).string_value().to_string()
1697
0
          , int(n.list_at(1).int_value()));
1698
0
      }
1699
0
    }
1700
1701
    // extract creation date
1702
2
    std::int64_t const cd = torrent_file.dict_find_int_value("creation date", -1);
1703
2
    if (cd >= 0)
1704
2
    {
1705
2
      m_creation_date = std::time_t(cd);
1706
2
    }
1707
1708
    // if there are any url-seeds, extract them
1709
2
    bdecode_node const url_seeds = torrent_file.dict_find("url-list");
1710
2
    if (url_seeds && url_seeds.type() == bdecode_node::string_t
1711
2
      && url_seeds.string_length() > 0)
1712
0
    {
1713
0
      web_seed_entry ent(maybe_url_encode(url_seeds.string_value().to_string())
1714
0
        , web_seed_entry::url_seed);
1715
0
      if ((m_flags & multifile) && num_files() > 1)
1716
0
        ensure_trailing_slash(ent.url);
1717
0
      m_web_seeds.push_back(std::move(ent));
1718
0
    }
1719
2
    else if (url_seeds && url_seeds.type() == bdecode_node::list_t)
1720
0
    {
1721
      // only add a URL once
1722
0
      std::set<std::string> unique;
1723
0
      for (int i = 0, end(url_seeds.list_size()); i < end; ++i)
1724
0
      {
1725
0
        bdecode_node const url = url_seeds.list_at(i);
1726
0
        if (url.type() != bdecode_node::string_t) continue;
1727
0
        if (url.string_length() == 0) continue;
1728
0
        web_seed_entry ent(maybe_url_encode(url.string_value().to_string())
1729
0
          , web_seed_entry::url_seed);
1730
0
        if ((m_flags & multifile) && num_files() > 1)
1731
0
          ensure_trailing_slash(ent.url);
1732
0
        if (!unique.insert(ent.url).second) continue;
1733
0
        m_web_seeds.push_back(std::move(ent));
1734
0
      }
1735
0
    }
1736
1737
    // if there are any http-seeds, extract them
1738
2
    bdecode_node const http_seeds = torrent_file.dict_find("httpseeds");
1739
2
    if (http_seeds && http_seeds.type() == bdecode_node::string_t
1740
2
      && http_seeds.string_length() > 0)
1741
0
    {
1742
0
      m_web_seeds.emplace_back(maybe_url_encode(http_seeds.string_value().to_string())
1743
0
        , web_seed_entry::http_seed);
1744
0
    }
1745
2
    else if (http_seeds && http_seeds.type() == bdecode_node::list_t)
1746
0
    {
1747
      // only add a URL once
1748
0
      std::set<std::string> unique;
1749
0
      for (int i = 0, end(http_seeds.list_size()); i < end; ++i)
1750
0
      {
1751
0
        bdecode_node const url = http_seeds.list_at(i);
1752
0
        if (url.type() != bdecode_node::string_t || url.string_length() == 0) continue;
1753
0
        std::string u = maybe_url_encode(url.string_value().to_string());
1754
0
        if (!unique.insert(u).second) continue;
1755
0
        m_web_seeds.emplace_back(std::move(u), web_seed_entry::http_seed);
1756
0
      }
1757
0
    }
1758
1759
2
    m_comment = torrent_file.dict_find_string_value("comment.utf-8").to_string();
1760
2
    if (m_comment.empty()) m_comment = torrent_file.dict_find_string_value("comment").to_string();
1761
2
    aux::verify_encoding(m_comment);
1762
1763
2
    m_created_by = torrent_file.dict_find_string_value("created by.utf-8").to_string();
1764
2
    if (m_created_by.empty()) m_created_by = torrent_file.dict_find_string_value("created by").to_string();
1765
2
    aux::verify_encoding(m_created_by);
1766
1767
2
    return true;
1768
2
  }
1769
1770
  void torrent_info::add_tracker(std::string const& url, int const tier)
1771
0
  {
1772
0
    add_tracker(url, tier, announce_entry::source_client);
1773
0
  }
1774
1775
  void torrent_info::add_tracker(std::string const& url, int const tier
1776
    , announce_entry::tracker_source const source)
1777
0
  {
1778
0
    TORRENT_ASSERT_PRECOND(!url.empty());
1779
0
    auto const i = std::find_if(m_urls.begin(), m_urls.end()
1780
0
      , [&url](announce_entry const& ae) { return ae.url == url; });
1781
0
    if (i != m_urls.end()) return;
1782
1783
0
    announce_entry e(url);
1784
0
    e.tier = std::uint8_t(tier);
1785
0
    e.source = source;
1786
0
    m_urls.push_back(e);
1787
1788
0
    std::sort(m_urls.begin(), m_urls.end()
1789
0
      , [] (announce_entry const& lhs, announce_entry const& rhs)
1790
0
      { return lhs.tier < rhs.tier; });
1791
0
  }
1792
1793
  void torrent_info::clear_trackers()
1794
0
  {
1795
0
    m_urls.clear();
1796
0
  }
1797
1798
#if TORRENT_ABI_VERSION == 1
1799
namespace {
1800
1801
  struct filter_web_seed_type
1802
  {
1803
0
    explicit filter_web_seed_type(web_seed_entry::type_t t_) : t(t_) {}
1804
    void operator() (web_seed_entry const& w)
1805
0
    { if (w.type == t) urls.push_back(w.url); }
1806
    std::vector<std::string> urls;
1807
    web_seed_entry::type_t t;
1808
  };
1809
}
1810
1811
  std::vector<std::string> torrent_info::url_seeds() const
1812
0
  {
1813
0
    return std::for_each(m_web_seeds.begin(), m_web_seeds.end()
1814
0
      , filter_web_seed_type(web_seed_entry::url_seed)).urls;
1815
0
  }
1816
1817
  std::vector<std::string> torrent_info::http_seeds() const
1818
0
  {
1819
0
    return std::for_each(m_web_seeds.begin(), m_web_seeds.end()
1820
0
      , filter_web_seed_type(web_seed_entry::http_seed)).urls;
1821
0
  }
1822
#endif // TORRENT_ABI_VERSION
1823
1824
  void torrent_info::add_url_seed(std::string const& url
1825
    , std::string const& ext_auth
1826
    , web_seed_entry::headers_t const& ext_headers)
1827
0
  {
1828
0
    m_web_seeds.emplace_back(url, web_seed_entry::url_seed
1829
0
      , ext_auth, ext_headers);
1830
0
  }
1831
1832
  void torrent_info::add_http_seed(std::string const& url
1833
    , std::string const& auth
1834
    , web_seed_entry::headers_t const& extra_headers)
1835
0
  {
1836
0
    m_web_seeds.emplace_back(url, web_seed_entry::http_seed
1837
0
      , auth, extra_headers);
1838
0
  }
1839
1840
  void torrent_info::set_web_seeds(std::vector<web_seed_entry> seeds)
1841
0
  {
1842
0
    m_web_seeds = std::move(seeds);
1843
0
  }
1844
1845
  std::vector<sha1_hash> torrent_info::similar_torrents() const
1846
1
  {
1847
1
    std::vector<sha1_hash> ret;
1848
1
#ifndef TORRENT_DISABLE_MUTABLE_TORRENTS
1849
1
    ret.reserve(m_similar_torrents.size() + m_owned_similar_torrents.size());
1850
1851
1
    for (auto const& st : m_similar_torrents)
1852
0
      ret.emplace_back(m_info_section.get() + st);
1853
1854
1
    for (auto const& st : m_owned_similar_torrents)
1855
0
      ret.push_back(st);
1856
1
#endif
1857
1858
1
    return ret;
1859
1
  }
1860
1861
  std::vector<std::string> torrent_info::collections() const
1862
1
  {
1863
1
    std::vector<std::string> ret;
1864
1
#ifndef TORRENT_DISABLE_MUTABLE_TORRENTS
1865
1
    ret.reserve(m_collections.size() + m_owned_collections.size());
1866
1867
1
    for (auto const& c : m_collections)
1868
0
      ret.emplace_back(m_info_section.get() + c.first, aux::numeric_cast<std::size_t>(c.second));
1869
1870
1
    for (auto const& c : m_owned_collections)
1871
0
      ret.push_back(c);
1872
1
#endif // TORRENT_DISABLE_MUTABLE_TORRENTS
1873
1874
1
    return ret;
1875
1
  }
1876
1877
#if TORRENT_USE_INVARIANT_CHECKS
1878
  void torrent_info::check_invariant() const
1879
  {
1880
    for (auto const i : m_files.file_range())
1881
    {
1882
      TORRENT_ASSERT(m_files.file_name(i).data() != nullptr);
1883
      if (!m_files.owns_name(i))
1884
      {
1885
        // name needs to point into the allocated info section buffer
1886
        TORRENT_ASSERT(m_files.file_name(i).data() >= m_info_section.get());
1887
        TORRENT_ASSERT(m_files.file_name(i).data() < m_info_section.get() + m_info_section_size);
1888
      }
1889
      else
1890
      {
1891
        // name must be a null terminated string
1892
        string_view const name = m_files.file_name(i);
1893
        TORRENT_ASSERT(name.data()[name.size()] == '\0');
1894
      }
1895
    }
1896
1897
    TORRENT_ASSERT(m_piece_hashes <= m_info_section_size);
1898
  }
1899
#endif
1900
1901
  sha1_hash torrent_info::info_hash() const noexcept
1902
0
  {
1903
0
    return m_info_hash.get_best();
1904
0
  }
1905
1906
0
  bool torrent_info::v1() const { return m_info_hash.has_v1(); }
1907
0
  bool torrent_info::v2() const { return m_info_hash.has_v2(); }
1908
1909
TORRENT_VERSION_NAMESPACE_3_END
1910
1911
}