1 /* 2 3 Copyright (c) 2007-2018, Arvid Norberg 4 All rights reserved. 5 6 Redistribution and use in source and binary forms, with or without 7 modification, are permitted provided that the following conditions 8 are met: 9 10 * Redistributions of source code must retain the above copyright 11 notice, this list of conditions and the following disclaimer. 12 * Redistributions in binary form must reproduce the above copyright 13 notice, this list of conditions and the following disclaimer in 14 the documentation and/or other materials provided with the distribution. 15 * Neither the name of the author nor the names of its 16 contributors may be used to endorse or promote products derived 17 from this software without specific prior written permission. 18 19 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 22 ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 23 LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 24 CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 25 SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 26 INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 27 CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 28 ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 29 POSSIBILITY OF SUCH DAMAGE. 30 31 */ 32 33 #include "libtorrent/magnet_uri.hpp" 34 #include "libtorrent/session.hpp" 35 #include "libtorrent/aux_/escape_string.hpp" 36 #include "libtorrent/aux_/throw.hpp" 37 #include "libtorrent/torrent_status.hpp" 38 #include "libtorrent/torrent_info.hpp" 39 #include "libtorrent/announce_entry.hpp" 40 #include "libtorrent/hex.hpp" // to_hex, from_hex 41 #include "libtorrent/socket_io.hpp" 42 43 namespace libtorrent { 44 make_magnet_uri(torrent_handle const & handle)45 std::string make_magnet_uri(torrent_handle const& handle) 46 { 47 if (!handle.is_valid()) return ""; 48 49 std::string ret; 50 sha1_hash const& ih = handle.info_hash(); 51 ret += "magnet:?xt=urn:btih:"; 52 ret += aux::to_hex(ih); 53 54 torrent_status st = handle.status(torrent_handle::query_name); 55 if (!st.name.empty()) 56 { 57 ret += "&dn="; 58 ret += escape_string(st.name); 59 } 60 61 for (auto const& tr : handle.trackers()) 62 { 63 ret += "&tr="; 64 ret += escape_string(tr.url); 65 } 66 67 for (auto const& s : handle.url_seeds()) 68 { 69 ret += "&ws="; 70 ret += escape_string(s); 71 } 72 73 return ret; 74 } 75 make_magnet_uri(torrent_info const & info)76 std::string make_magnet_uri(torrent_info const& info) 77 { 78 std::string ret; 79 sha1_hash const& ih = info.info_hash(); 80 ret += "magnet:?xt=urn:btih:"; 81 ret += aux::to_hex(ih); 82 83 std::string const& name = info.name(); 84 85 if (!name.empty()) 86 { 87 ret += "&dn="; 88 ret += escape_string(name); 89 } 90 91 for (auto const& tr : info.trackers()) 92 { 93 ret += "&tr="; 94 ret += escape_string(tr.url); 95 } 96 97 for (auto const& s : info.web_seeds()) 98 { 99 if (s.type != web_seed_entry::url_seed) continue; 100 101 ret += "&ws="; 102 ret += escape_string(s.url); 103 } 104 105 return ret; 106 } 107 108 #if TORRENT_ABI_VERSION == 1 109 110 namespace { add_magnet_uri_deprecated(session & ses,std::string const & uri,add_torrent_params const & p,error_code & ec)111 torrent_handle add_magnet_uri_deprecated(session& ses, std::string const& uri 112 , add_torrent_params const& p, error_code& ec) 113 { 114 add_torrent_params params(p); 115 parse_magnet_uri(uri, params, ec); 116 if (ec) return torrent_handle(); 117 return ses.add_torrent(std::move(params), ec); 118 } 119 } 120 add_magnet_uri(session & ses,std::string const & uri,add_torrent_params const & p,error_code & ec)121 torrent_handle add_magnet_uri(session& ses, std::string const& uri 122 , add_torrent_params const& p, error_code& ec) 123 { 124 return add_magnet_uri_deprecated(ses, uri, p, ec); 125 } 126 127 #ifndef BOOST_NO_EXCEPTIONS add_magnet_uri(session & ses,std::string const & uri,std::string const & save_path,storage_mode_t storage_mode,bool paused,storage_constructor_type sc,void * userdata)128 torrent_handle add_magnet_uri(session& ses, std::string const& uri 129 , std::string const& save_path 130 , storage_mode_t storage_mode 131 , bool paused 132 , storage_constructor_type sc 133 , void* userdata) 134 { 135 add_torrent_params params(std::move(sc)); 136 error_code ec; 137 parse_magnet_uri(uri, params, ec); 138 params.storage_mode = storage_mode; 139 params.userdata = userdata; 140 params.save_path = save_path; 141 142 if (paused) params.flags |= add_torrent_params::flag_paused; 143 else params.flags &= ~add_torrent_params::flag_paused; 144 145 return ses.add_torrent(std::move(params)); 146 } 147 add_magnet_uri(session & ses,std::string const & uri,add_torrent_params const & p)148 torrent_handle add_magnet_uri(session& ses, std::string const& uri 149 , add_torrent_params const& p) 150 { 151 error_code ec; 152 torrent_handle ret = add_magnet_uri_deprecated(ses, uri, p, ec); 153 if (ec) aux::throw_ex<system_error>(ec); 154 return ret; 155 } 156 #endif // BOOST_NO_EXCEPTIONS 157 #endif // TORRENT_ABI_VERSION 158 parse_magnet_uri(string_view uri,error_code & ec)159 add_torrent_params parse_magnet_uri(string_view uri, error_code& ec) 160 { 161 add_torrent_params ret; 162 parse_magnet_uri(uri, ret, ec); 163 return ret; 164 } 165 parse_magnet_uri(string_view uri,add_torrent_params & p,error_code & ec)166 void parse_magnet_uri(string_view uri, add_torrent_params& p, error_code& ec) 167 { 168 ec.clear(); 169 std::string display_name; 170 171 string_view sv(uri); 172 if (sv.substr(0, 8) != "magnet:?"_sv) 173 { 174 ec = errors::unsupported_url_protocol; 175 return; 176 } 177 sv = sv.substr(8); 178 179 int tier = 0; 180 bool has_ih = false; 181 while (!sv.empty()) 182 { 183 string_view name; 184 std::tie(name, sv) = split_string(sv, '='); 185 string_view value; 186 std::tie(value, sv) = split_string(sv, '&'); 187 188 // parameter names are allowed to have a .<number>-suffix. 189 // the number has no meaning, just strip it 190 // if the characters after the period are not digits, don't strip 191 // anything 192 string_view number; 193 string_view stripped_name; 194 std::tie(stripped_name, number) = split_string(name, '.'); 195 if (std::all_of(number.begin(), number.end(), [](char const c) { return is_digit(c); } )) 196 name = stripped_name; 197 198 if (string_equal_no_case(name, "dn"_sv)) // display name 199 { 200 error_code e; 201 display_name = unescape_string(value, e); 202 } 203 else if (string_equal_no_case(name, "tr"_sv)) // tracker 204 { 205 // since we're about to assign tiers to the trackers, make sure the two 206 // vectors are aligned 207 if (p.tracker_tiers.size() != p.trackers.size()) 208 p.tracker_tiers.resize(p.trackers.size(), 0); 209 error_code e; 210 std::string tracker = unescape_string(value, e); 211 if (!e && !tracker.empty()) 212 { 213 p.trackers.push_back(std::move(tracker)); 214 p.tracker_tiers.push_back(tier++); 215 } 216 } 217 else if (string_equal_no_case(name, "ws"_sv)) // web seed 218 { 219 error_code e; 220 std::string webseed = unescape_string(value, e); 221 if (!e) p.url_seeds.push_back(std::move(webseed)); 222 } 223 else if (string_equal_no_case(name, "xt"_sv)) 224 { 225 std::string unescaped_btih; 226 if (value.find('%') != string_view::npos) 227 { 228 unescaped_btih = unescape_string(value, ec); 229 if (ec) return; 230 value = unescaped_btih; 231 } 232 233 if (value.substr(0, 9) != "urn:btih:") continue; 234 value = value.substr(9); 235 236 sha1_hash info_hash; 237 if (value.size() == 40) aux::from_hex({value.data(), 40}, info_hash.data()); 238 else if (value.size() == 32) 239 { 240 std::string const ih = base32decode(value); 241 if (ih.size() != 20) 242 { 243 ec = errors::invalid_info_hash; 244 return; 245 } 246 info_hash.assign(ih); 247 } 248 else 249 { 250 ec = errors::invalid_info_hash; 251 return; 252 } 253 p.info_hash = info_hash; 254 has_ih = true; 255 } 256 else if (string_equal_no_case(name, "so"_sv)) // select-only (files) 257 { 258 // accept only digits, '-' and ',' 259 if (std::any_of(value.begin(), value.end(), [](char c) 260 { return !is_digit(c) && c != '-' && c != ','; })) 261 continue; 262 263 do 264 { 265 string_view token; 266 std::tie(token, value) = split_string(value, ','); 267 268 if (token.empty()) continue; 269 270 int idx1, idx2; 271 // TODO: what's the right number here? 272 constexpr int max_index = 10000; // can't risk out of memory 273 274 auto const divider = token.find_first_of('-'); 275 if (divider != std::string::npos) // it's a range 276 { 277 if (divider == 0) // no start index 278 continue; 279 if (divider == token.size() - 1) // no end index 280 continue; 281 282 idx1 = std::atoi(token.substr(0, divider).to_string().c_str()); 283 if (idx1 < 0 || idx1 > max_index) // invalid index 284 continue; 285 idx2 = std::atoi(token.substr(divider + 1).to_string().c_str()); 286 if (idx2 < 0 || idx2 > max_index) // invalid index 287 continue; 288 289 if (idx1 > idx2) // wrong range limits 290 continue; 291 } 292 else // it's an index 293 { 294 idx1 = std::atoi(token.to_string().c_str()); 295 if (idx1 < 0 || idx1 > max_index) // invalid index 296 continue; 297 idx2 = idx1; 298 } 299 300 if (int(p.file_priorities.size()) <= idx2) 301 p.file_priorities.resize(static_cast<std::size_t>(idx2) + 1, dont_download); 302 303 for (int i = idx1; i <= idx2; i++) 304 p.file_priorities[std::size_t(i)] = default_priority; 305 306 } while (!value.empty()); 307 } 308 else if (string_equal_no_case(name, "x.pe"_sv)) 309 { 310 error_code e; 311 tcp::endpoint endp = parse_endpoint(value, e); 312 if (!e) p.peers.push_back(std::move(endp)); 313 } 314 #ifndef TORRENT_DISABLE_DHT 315 else if (string_equal_no_case(name, "dht"_sv)) 316 { 317 auto const divider = value.find_last_of(':'); 318 if (divider != std::string::npos) 319 { 320 int const port = std::atoi(value.substr(divider + 1).to_string().c_str()); 321 if (port > 0 && port < int(std::numeric_limits<std::uint16_t>::max())) 322 p.dht_nodes.emplace_back(value.substr(0, divider).to_string(), port); 323 } 324 } 325 #endif 326 } 327 328 if (!has_ih) 329 { 330 ec = errors::missing_info_hash_in_uri; 331 return; 332 } 333 334 if (!display_name.empty()) p.name = display_name; 335 } 336 parse_magnet_uri(string_view uri)337 add_torrent_params parse_magnet_uri(string_view uri) 338 { 339 error_code ec; 340 add_torrent_params ret; 341 parse_magnet_uri(uri, ret, ec); 342 if (ec) aux::throw_ex<system_error>(ec); 343 return ret; 344 } 345 } 346