/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 | | } |