1
// libTorrent - BitTorrent library
2
// Copyright (C) 2005-2007, Jari Sundell
4
// This program is free software; you can redistribute it and/or modify
5
// it under the terms of the GNU General Public License as published by
6
// the Free Software Foundation; either version 2 of the License, or
7
// (at your option) any later version.
9
// This program is distributed in the hope that it will be useful,
10
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
// GNU General Public License for more details.
14
// You should have received a copy of the GNU General Public License
15
// along with this program; if not, write to the Free Software
16
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18
// In addition, as a special exception, the copyright holders give
19
// permission to link the code of portions of this program with the
20
// OpenSSL library under certain conditions as described in each
21
// individual source file, and distribute linked combinations
24
// You must obey the GNU General Public License in all respects for
25
// all of the code used other than OpenSSL. If you modify file(s)
26
// with this exception, you may extend this exception to your version
27
// of the file(s), but you are not obligated to do so. If you do not
28
// wish to do so, delete this exception statement from your version.
29
// If you delete this exception statement from all source files in the
30
// program, then also delete it here.
32
// Contact: Jari Sundell <jaris@ifi.uio.no>
35
// 3185 Skoppum, NORWAY
42
#include <rak/functional.h>
43
#include <rak/string_manip.h>
45
#include "download/download_wrapper.h"
46
#include "torrent/dht_manager.h"
47
#include "torrent/exceptions.h"
48
#include "torrent/object.h"
49
#include "torrent/data/file.h"
50
#include "torrent/data/file_list.h"
51
#include "tracker/tracker_manager.h"
53
#include "download_constructor.h"
59
struct download_constructor_is_single_path {
60
bool operator () (Object::map_type::const_reference v) const {
62
std::strncmp(v.first.c_str(), "name.", sizeof("name.") - 1) == 0 &&
67
struct download_constructor_is_multi_path {
68
bool operator () (Object::map_type::const_reference v) const {
70
std::strncmp(v.first.c_str(), "path.", sizeof("path.") - 1) == 0 &&
75
struct download_constructor_encoding_match :
76
public std::binary_function<const Path&, const char*, bool> {
78
bool operator () (const Path& p, const char* enc) {
79
return strcasecmp(p.encoding().c_str(), enc) == 0;
84
DownloadConstructor::initialize(Object& b) {
85
if (!b.has_key_map("info") && b.has_key_string("magnet-uri"))
86
parse_magnet_uri(b, b.get_key_string("magnet-uri"));
88
if (b.has_key_string("encoding"))
89
m_defaultEncoding = b.get_key_string("encoding");
91
if (b.has_key_value("creation date"))
92
m_download->info()->set_creation_date(b.get_key_value("creation date"));
94
m_download->info()->change_flags(DownloadInfo::flag_private,
95
b.get_key("info").has_key_value("private") &&
96
b.get_key("info").get_key_value("private") == 1);
98
parse_name(b.get_key("info"));
99
parse_info(b.get_key("info"));
104
// Currently using a hack of the path thingie to extract the correct
107
DownloadConstructor::parse_name(const Object& b) {
108
if (is_invalid_path_element(b.get_key("name")))
109
throw input_error("Bad torrent file, \"name\" is an invalid path name.");
111
std::list<Path> pathList;
113
pathList.push_back(Path());
114
pathList.back().set_encoding(m_defaultEncoding);
115
pathList.back().push_back(b.get_key_string("name"));
117
for (Object::map_const_iterator itr = b.as_map().begin();
118
(itr = std::find_if(itr, b.as_map().end(), download_constructor_is_single_path())) != b.as_map().end();
120
pathList.push_back(Path());
121
pathList.back().set_encoding(itr->first.substr(sizeof("name.") - 1));
122
pathList.back().push_back(itr->second.as_string());
125
if (pathList.empty())
126
throw input_error("Bad torrent file, an entry has no valid name.");
128
Path name = choose_path(&pathList);
131
throw internal_error("DownloadConstructor::parse_name(...) Ended up with an empty Path.");
133
m_download->info()->set_name(name.front());
137
DownloadConstructor::parse_info(const Object& b) {
138
FileList* fileList = m_download->main()->file_list();
140
if (!fileList->empty())
141
throw internal_error("parse_info received an already initialized Content object.");
143
if (b.flags() & Object::flag_unordered)
144
throw input_error("Download has unordered info dictionary.");
148
if (b.has_key_value("meta_download") && b.get_key_value("meta_download"))
149
m_download->info()->set_flags(DownloadInfo::flag_meta_download);
151
if (m_download->info()->is_meta_download()) {
152
if (b.get_key_string("pieces").length() != HashString::size_data)
153
throw input_error("Meta-download has invalid piece data.");
156
parse_single_file(b, chunkSize);
159
chunkSize = b.get_key_value("piece length");
161
if (chunkSize <= (1 << 10) || chunkSize > (128 << 20))
162
throw input_error("Torrent has an invalid \"piece length\".");
165
if (b.has_key("length")) {
166
parse_single_file(b, chunkSize);
168
} else if (b.has_key("files")) {
169
parse_multi_files(b.get_key("files"), chunkSize);
170
fileList->set_root_dir("./" + m_download->info()->name());
172
} else if (!m_download->info()->is_meta_download()) {
173
throw input_error("Torrent must have either length or files entry.");
176
if (fileList->size_bytes() == 0 && !m_download->info()->is_meta_download())
177
throw input_error("Torrent has zero length.");
179
// Set chunksize before adding files to make sure the index range is
181
m_download->set_complete_hash(b.get_key_string("pieces"));
183
if (m_download->complete_hash().size() / 20 < fileList->size_chunks())
184
throw bencode_error("Torrent size and 'info:pieces' length does not match.");
188
DownloadConstructor::parse_tracker(const Object& b) {
189
TrackerManager* tracker = m_download->main()->tracker_manager();
191
if (b.has_key_list("announce-list"))
192
std::for_each(b.get_key_list("announce-list").begin(), b.get_key_list("announce-list").end(),
193
rak::make_mem_fun(this, &DownloadConstructor::add_tracker_group));
195
else if (b.has_key("announce"))
196
add_tracker_single(b.get_key("announce"), 0);
198
else if (!manager->dht_manager()->is_valid() || m_download->info()->is_private())
199
throw bencode_error("Could not find any trackers");
201
if (manager->dht_manager()->is_valid() && !m_download->info()->is_private())
202
tracker->insert(tracker->group_size(), "dht://");
204
if (manager->dht_manager()->is_valid() && b.has_key_list("nodes"))
205
std::for_each(b.get_key_list("nodes").begin(), b.get_key_list("nodes").end(),
206
rak::make_mem_fun(this, &DownloadConstructor::add_dht_node));
208
tracker->randomize();
212
DownloadConstructor::add_tracker_group(const Object& b) {
214
throw bencode_error("Tracker group list not a list");
216
std::for_each(b.as_list().begin(), b.as_list().end(),
217
rak::bind2nd(rak::make_mem_fun(this, &DownloadConstructor::add_tracker_single),
218
m_download->main()->tracker_manager()->group_size()));
222
DownloadConstructor::add_tracker_single(const Object& b, int group) {
224
throw bencode_error("Tracker entry not a string");
226
m_download->main()->tracker_manager()->insert(group, rak::trim_classic(b.as_string()));
230
DownloadConstructor::add_dht_node(const Object& b) {
231
if (!b.is_list() || b.as_list().size() < 2)
234
Object::list_type::const_iterator el = b.as_list().begin();
236
if (!el->is_string())
239
const std::string& host = el->as_string();
241
if (!(++el)->is_value())
244
manager->dht_manager()->add_node(host, el->as_value());
248
DownloadConstructor::is_valid_path_element(const Object& b) {
251
b.as_string() != "." &&
252
b.as_string() != ".." &&
253
std::find(b.as_string().begin(), b.as_string().end(), '/') == b.as_string().end() &&
254
std::find(b.as_string().begin(), b.as_string().end(), '\0') == b.as_string().end();
258
DownloadConstructor::parse_single_file(const Object& b, uint32_t chunkSize) {
259
if (is_invalid_path_element(b.get_key("name")))
260
throw input_error("Bad torrent file, \"name\" is an invalid path name.");
262
FileList* fileList = m_download->main()->file_list();
263
fileList->initialize(chunkSize == 1 ? 1 : b.get_key_value("length"), chunkSize);
264
fileList->set_multi_file(false);
266
std::list<Path> pathList;
268
pathList.push_back(Path());
269
pathList.back().set_encoding(m_defaultEncoding);
270
pathList.back().push_back(b.get_key_string("name"));
272
for (Object::map_const_iterator itr = b.as_map().begin();
273
(itr = std::find_if(itr, b.as_map().end(), download_constructor_is_single_path())) != b.as_map().end();
275
pathList.push_back(Path());
276
pathList.back().set_encoding(itr->first.substr(sizeof("name.") - 1));
277
pathList.back().push_back(itr->second.as_string());
280
if (pathList.empty())
281
throw input_error("Bad torrent file, an entry has no valid filename.");
283
*fileList->front()->mutable_path() = choose_path(&pathList);
284
fileList->update_paths(fileList->begin(), fileList->end());
288
DownloadConstructor::parse_multi_files(const Object& b, uint32_t chunkSize) {
289
const Object::list_type& objectList = b.as_list();
291
// Multi file torrent
292
if (objectList.empty())
293
throw input_error("Bad torrent file, entry has no files.");
295
int64_t torrentSize = 0;
296
std::vector<FileList::split_type> splitList(objectList.size());
297
std::vector<FileList::split_type>::iterator splitItr = splitList.begin();
299
for (Object::list_const_iterator listItr = objectList.begin(), listLast = objectList.end(); listItr != listLast; ++listItr, ++splitItr) {
300
std::list<Path> pathList;
302
if (listItr->has_key_list("path"))
303
pathList.push_back(create_path(listItr->get_key_list("path"), m_defaultEncoding));
305
Object::map_const_iterator itr = listItr->as_map().begin();
306
Object::map_const_iterator last = listItr->as_map().end();
308
while ((itr = std::find_if(itr, last, download_constructor_is_multi_path())) != last) {
309
pathList.push_back(create_path(itr->second.as_list(), itr->first.substr(sizeof("path.") - 1)));
313
if (pathList.empty())
314
throw input_error("Bad torrent file, an entry has no valid filename.");
316
int64_t length = listItr->get_key_value("length");
318
if (length < 0 || torrentSize + length < 0)
319
throw input_error("Bad torrent file, invalid length for file.");
321
torrentSize += length;
322
*splitItr = FileList::split_type(length, choose_path(&pathList));
325
FileList* fileList = m_download->main()->file_list();
326
fileList->set_multi_file(true);
328
fileList->initialize(torrentSize, chunkSize);
329
fileList->split(fileList->begin(), &*splitList.begin(), &*splitList.end());
330
fileList->update_paths(fileList->begin(), fileList->end());
334
DownloadConstructor::create_path(const Object::list_type& plist, const std::string enc) {
335
// Make sure we are given a proper file path.
337
throw input_error("Bad torrent file, \"path\" has zero entries.");
339
if (std::find_if(plist.begin(), plist.end(), std::ptr_fun(&DownloadConstructor::is_invalid_path_element)) != plist.end())
340
throw input_error("Bad torrent file, \"path\" has zero entries or a zero lenght entry.");
345
std::transform(plist.begin(), plist.end(), std::back_inserter(p), std::mem_fun_ref<const Object::string_type&>(&Object::as_string));
351
DownloadConstructor::choose_path(std::list<Path>* pathList) {
352
std::list<Path>::iterator pathFirst = pathList->begin();
353
std::list<Path>::iterator pathLast = pathList->end();
354
EncodingList::const_iterator encodingFirst = m_encodingList->begin();
355
EncodingList::const_iterator encodingLast = m_encodingList->end();
357
for ( ; encodingFirst != encodingLast; ++encodingFirst) {
358
std::list<Path>::iterator itr = std::find_if(pathFirst, pathLast, rak::bind2nd(download_constructor_encoding_match(), encodingFirst->c_str()));
361
pathList->splice(pathFirst, *pathList, itr);
364
return pathList->front();
368
parse_base32_sha1(const char* pos, HashString& hash) {
369
HashString::iterator hashItr = hash.begin();
371
static const int base_shift = 8+8-5;
372
int shift = base_shift;
373
uint16_t decoded = 0;
379
if (c >= 'A' && c <= 'Z')
381
else if (c >= 'a' && c <= 'z')
383
else if (c >= '2' && c <= '7')
384
value = 26 + c - '2';
390
decoded |= (value << shift);
392
// Too many characters for a base32 SHA1.
393
if (hashItr == hash.end())
396
*hashItr++ = (decoded >> 8);
404
return hashItr != hash.end() || shift != base_shift ? NULL : pos;
408
DownloadConstructor::parse_magnet_uri(Object& b, const std::string& uri) {
409
if (std::strncmp(uri.c_str(), "magnet:?", 8))
410
throw input_error("Invalid magnet URI.");
412
const char* pos = uri.c_str() + 8;
414
Object trackers(Object::create_list());
416
bool hashValid = false;
419
const char* tagStart = pos;
424
raw_string tag(tagStart, pos - tagStart);
427
// hash may be base32 encoded (optional in BEP 0009 and common practice)
428
if (raw_bencode_equal_c_str(tag, "xt")) {
429
if (strncmp(pos, "urn:btih:", 9))
430
throw input_error("Invalid magnet URI.");
434
const char* nextPos = parse_base32_sha1(pos, hash);
435
if (nextPos != NULL) {
442
// everything else, including sometimes the hash, is url encoded.
447
if (sscanf(pos, "%02hhx", &c) != 1)
448
throw input_error("Invalid magnet URI.");
452
} else if (c == '&') {
456
decoded.push_back(c);
459
if (raw_bencode_equal_c_str(tag, "xt")) {
460
// url-encoded hash as per magnet URN specs
461
if (decoded.length() == hash.size_data) {
462
hash = *HashString::cast_from(decoded);
465
// hex-encoded hash as per BEP 0009
466
} else if (decoded.length() == hash.size_data * 2) {
467
std::string::iterator hexItr = decoded.begin();
468
for (HashString::iterator itr = hash.begin(), last = hash.end(); itr != last; itr++, hexItr += 2)
469
*itr = (rak::hexchar_to_value(*hexItr) << 4) + rak::hexchar_to_value(*(hexItr + 1));
473
throw input_error("Invalid magnet URI.");
476
} else if (raw_bencode_equal_c_str(tag, "tr")) {
477
trackers.insert_back(Object::create_list()).insert_back(decoded);
479
// could also handle "dn" = display name (torrent name), but we can't really use that
483
throw input_error("Invalid magnet URI.");
485
Object& info = b.insert_key("info", Object::create_map());
486
info.insert_key("pieces", hash.str());
487
info.insert_key("name", rak::transform_hex(hash.str()) + ".meta");
488
info.insert_key("meta_download", (int64_t)1);
490
if (!trackers.as_list().empty()) {
491
b.insert_preserve_copy("announce", trackers.as_list().begin()->as_list().begin()->as_string());
492
b.insert_preserve_type("announce-list", trackers);