diff --git a/ChangeLog b/ChangeLog index fab8b9123..a976bde9e 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,5 @@ + * implemented support magnet URI extension, select specific file indices + for download, BEP53 * make tracker keys multi-homed. remove set_key() function on session. * add API to query whether alerts have been dropped or not * add flags()/set_flags()/unset_flags() to torrent_handle, deprecate individual functions diff --git a/docs/features.rst b/docs/features.rst index 4823ec0f4..f81c0e1ca 100644 --- a/docs/features.rst +++ b/docs/features.rst @@ -51,6 +51,8 @@ extensions scale well with the size of the content. * share-mode. This is a special mode torrents can be put in to optimize share ratio rather than downloading the torrent. +* supports the Magnet URI extension - Select specific file indices for + download. `BEP 53`_. .. _article: utp.html .. _extensions: manual-ref.html#extensions diff --git a/include/libtorrent/download_priority.hpp b/include/libtorrent/download_priority.hpp index ad0cf1931..4325e8d2d 100644 --- a/include/libtorrent/download_priority.hpp +++ b/include/libtorrent/download_priority.hpp @@ -48,4 +48,3 @@ constexpr download_priority_t top_priority{7}; } #endif - diff --git a/src/magnet_uri.cpp b/src/magnet_uri.cpp index 6b927a9c5..f7a43f3da 100644 --- a/src/magnet_uri.cpp +++ b/src/magnet_uri.cpp @@ -258,6 +258,65 @@ namespace libtorrent { return p; } + auto select_pos = std::string::npos; + string_view select = url_has_argument(uri, "so", &select_pos); + while (!select.empty()) + { + // parse the ranges or indices + do + { + // accept only digits, '-' and ',' + if (std::any_of(select.begin(), select.end(), [](char c) + { return !is_digit(c) && c != '-' && c != ','; })) + break; + + string_view token; + std::tie(token, select) = split_string(select, ','); + + int idx1, idx2; + // TODO: what's the right number here? + constexpr int max_index = 10000; // can't risk out of memory + + auto const divider = token.find_first_of('-'); + if (divider != std::string::npos) // it's a range + { + if (divider == 0) // no start index + continue; + if (divider == token.size() - 1) // no end index + continue; + + idx1 = std::atoi(token.substr(0, divider).to_string().c_str()); + if (idx1 < 0 || idx1 > max_index) // invalid index + continue; + idx2 = std::atoi(token.substr(divider + 1).to_string().c_str()); + if (idx2 < 0 || idx2 > max_index) // invalid index + continue; + + if (idx1 > idx2) // wrong range limits + continue; + } + else // it's an index + { + idx1 = std::atoi(token.to_string().c_str()); + if (idx1 < 0 || idx1 > max_index) // invalid index + continue; + idx2 = idx1; + } + + if (int(p.file_priorities.size()) <= idx2) + p.file_priorities.resize(std::size_t(idx2 + 1), dont_download); + + for (int i = idx1; i <= idx2; i++) + p.file_priorities[std::size_t(i)] = default_priority; + + } while (!select.empty()); + + select_pos = find(uri, "&so=", select_pos); + if (select_pos == std::string::npos) break; + select_pos += 4; + select = uri.substr(select_pos, find(uri, "&", select_pos) - select_pos); + } + std::string::size_type peer_pos = std::string::npos; string_view peer = url_has_argument(uri, "x.pe", &peer_pos); while (!peer.empty()) @@ -265,7 +324,7 @@ namespace libtorrent { error_code e; tcp::endpoint endp = parse_endpoint(peer, e); if (!e) - p.peers.push_back(endp); + p.peers.push_back(std::move(endp)); peer_pos = find(uri, "&x.pe=", peer_pos); if (peer_pos == std::string::npos) break; @@ -278,12 +337,12 @@ namespace libtorrent { string_view node = url_has_argument(uri, "dht", &node_pos); while (!node.empty()) { - std::string::size_type divider = node.find_last_of(':'); + std::string::size_type const divider = node.find_last_of(':'); if (divider != std::string::npos) { - int port = atoi(node.substr(divider + 1).to_string().c_str()); - if (port != 0) - p.dht_nodes.push_back(std::make_pair(node.substr(0, divider).to_string(), port)); + int const port = std::atoi(node.substr(divider + 1).to_string().c_str()); + if (port > 0 && port < int(std::numeric_limits::max())) + p.dht_nodes.emplace_back(node.substr(0, divider).to_string(), port); } node_pos = find(uri, "&dht=", node_pos); @@ -297,7 +356,7 @@ namespace libtorrent { if (btih.size() == 40 + 9) aux::from_hex({&btih[9], 40}, info_hash.data()); else if (btih.size() == 32 + 9) { - std::string ih = base32decode(btih.substr(9)); + std::string const ih = base32decode(btih.substr(9)); if (ih.size() != 20) { ec = errors::invalid_info_hash; diff --git a/test/test_magnet.cpp b/test/test_magnet.cpp index b6699289a..c659923b1 100644 --- a/test/test_magnet.cpp +++ b/test/test_magnet.cpp @@ -476,3 +476,128 @@ TORRENT_TEST(invalid_web_seed_escaping) TEST_CHECK(ec); } +TORRENT_TEST(parse_magnet_select_only) +{ + error_code ec; + add_torrent_params p = parse_magnet_uri("magnet:?xt=urn:btih:cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd" + "&dn=foo&so=0,2,4,6-8", ec); + TEST_CHECK(!ec); + + auto const yes = default_priority; + auto const no = dont_download; + std::vector result = std::vector + {yes, no, yes, no, yes, no, yes, yes, yes}; + TEST_CHECK(p.file_priorities == result); +} + +TORRENT_TEST(parse_magnet_select_only_overlap_range) +{ + error_code ec; + add_torrent_params p = parse_magnet_uri("magnet:?xt=urn:btih:cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd" + "&dn=foo&so=0,2-4,3-5&dht=10.0.0.1:1337", ec); + TEST_CHECK(!ec); + + auto const yes = default_priority; + auto const no = dont_download; + std::vector result = std::vector + {yes, no, yes, yes, yes, yes}; + TEST_CHECK(p.file_priorities == result); +} + +TORRENT_TEST(parse_magnet_select_only_multiple) +{ + error_code ec; + add_torrent_params p = parse_magnet_uri("magnet:?xt=urn:btih:cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd" + "&dn=foo&so=2-4&dht=10.0.0.1:1337&so=1", ec); + TEST_CHECK(!ec); + + auto const yes = default_priority; + auto const no = dont_download; + std::vector result = std::vector + {no, yes, yes, yes, yes}; + TEST_CHECK(p.file_priorities == result); +} + +TORRENT_TEST(parse_magnet_select_only_invalid_index_and_range) +{ + error_code ec; + add_torrent_params p = parse_magnet_uri("magnet:?xt=urn:btih:cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd" + "&dn=foo&so=-4,3-,7-4,a,100000000&dht=10.0.0.1:1337&so=10", ec); + TEST_CHECK(!ec); + + auto const yes = default_priority; + auto const no = dont_download; + std::vector result = std::vector + {no, no, no, no, no, no, no, no, no, no, yes}; + TEST_CHECK(p.file_priorities == result); +} + +TORRENT_TEST(parse_magnet_select_only_invalid_range1) +{ + error_code ec; + add_torrent_params p = parse_magnet_uri("magnet:?xt=urn:btih:cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd" + "&dn=foo&so=-4", ec); + TEST_CHECK(!ec); + + TEST_CHECK(p.file_priorities.empty()); +} + +TORRENT_TEST(parse_magnet_select_only_invalid_range2) +{ + error_code ec; + add_torrent_params p = parse_magnet_uri("magnet:?xt=urn:btih:cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd" + "&dn=foo&so=3-", ec); + TEST_CHECK(!ec); + + TEST_CHECK(p.file_priorities.empty()); +} + +TORRENT_TEST(parse_magnet_select_only_invalid_range3) +{ + error_code ec; + add_torrent_params p = parse_magnet_uri("magnet:?xt=urn:btih:cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd" + "&dn=foo&so=7-4", ec); + TEST_CHECK(!ec); + + TEST_CHECK(p.file_priorities.empty()); +} + +TORRENT_TEST(parse_magnet_select_only_invalid_index_character) +{ + error_code ec; + add_torrent_params p = parse_magnet_uri("magnet:?xt=urn:btih:cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd" + "&dn=foo&so=a", ec); + TEST_CHECK(!ec); + + TEST_CHECK(p.file_priorities.empty()); +} + +TORRENT_TEST(parse_magnet_select_only_invalid_index_value) +{ + error_code ec; + add_torrent_params p = parse_magnet_uri("magnet:?xt=urn:btih:cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd" + "&dn=foo&so=100000000", ec); + TEST_CHECK(!ec); + + TEST_CHECK(p.file_priorities.empty()); +} + +TORRENT_TEST(parse_magnet_select_only_invalid_no_values) +{ + error_code ec; + add_torrent_params p = parse_magnet_uri("magnet:?xt=urn:btih:cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd" + "&dn=foo&so=&dht=10.0.0.1:1337&so=", ec); + TEST_CHECK(!ec); + + TEST_CHECK(p.file_priorities.empty()); +} + +TORRENT_TEST(parse_magnet_select_only_invalid_quotes) +{ + error_code ec; + add_torrent_params p = parse_magnet_uri("magnet:?xt=urn:btih:cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd" + "&dn=foo&so=\"1,2\"", ec); + TEST_CHECK(!ec); + + TEST_CHECK(p.file_priorities.empty()); +}