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