|
|
|
@ -58,7 +58,6 @@ POSSIBILITY OF SUCH DAMAGE.
|
|
|
|
|
#include "libtorrent/hasher.hpp"
|
|
|
|
|
#include "libtorrent/entry.hpp"
|
|
|
|
|
|
|
|
|
|
namespace pt = boost::posix_time;
|
|
|
|
|
namespace gr = boost::gregorian;
|
|
|
|
|
|
|
|
|
|
using namespace libtorrent;
|
|
|
|
@ -160,10 +159,13 @@ namespace
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void extract_single_file(const entry& dict, file_entry& target
|
|
|
|
|
bool extract_single_file(entry const& dict, file_entry& target
|
|
|
|
|
, std::string const& root_dir)
|
|
|
|
|
{
|
|
|
|
|
target.size = dict["length"].integer();
|
|
|
|
|
entry const* length = dict.find_key("length");
|
|
|
|
|
if (length == 0 || length->type() != entry::int_t)
|
|
|
|
|
return false;
|
|
|
|
|
target.size = length->integer();
|
|
|
|
|
target.path = root_dir;
|
|
|
|
|
target.file_base = 0;
|
|
|
|
|
|
|
|
|
@ -172,38 +174,46 @@ namespace
|
|
|
|
|
// likely to be correctly encoded
|
|
|
|
|
|
|
|
|
|
const entry::list_type* list = 0;
|
|
|
|
|
if (entry const* p = dict.find_key("path.utf-8"))
|
|
|
|
|
entry const* p8 = dict.find_key("path.utf-8");
|
|
|
|
|
if (p8 != 0 && p8->type() == entry::list_t)
|
|
|
|
|
{
|
|
|
|
|
list = &p->list();
|
|
|
|
|
list = &p8->list();
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
list = &dict["path"].list();
|
|
|
|
|
entry const* p = dict.find_key("path");
|
|
|
|
|
if (p == 0 || p->type() != entry::list_t)
|
|
|
|
|
return false;
|
|
|
|
|
list = &p->list();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (entry::list_type::const_iterator i = list->begin();
|
|
|
|
|
i != list->end(); ++i)
|
|
|
|
|
{
|
|
|
|
|
if (i->type() != entry::string_t)
|
|
|
|
|
return false;
|
|
|
|
|
if (i->string() != "..")
|
|
|
|
|
target.path /= i->string();
|
|
|
|
|
}
|
|
|
|
|
verify_encoding(target);
|
|
|
|
|
if (target.path.is_complete()) throw std::runtime_error("torrent contains "
|
|
|
|
|
"a file with an absolute path: '"
|
|
|
|
|
+ target.path.native_file_string() + "'");
|
|
|
|
|
if (target.path.is_complete())
|
|
|
|
|
return false;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void extract_files(const entry::list_type& list, std::vector<file_entry>& target
|
|
|
|
|
bool extract_files(const entry::list_type& list, std::vector<file_entry>& target
|
|
|
|
|
, std::string const& root_dir)
|
|
|
|
|
{
|
|
|
|
|
size_type offset = 0;
|
|
|
|
|
for (entry::list_type::const_iterator i = list.begin(); i != list.end(); ++i)
|
|
|
|
|
{
|
|
|
|
|
target.push_back(file_entry());
|
|
|
|
|
extract_single_file(*i, target.back(), root_dir);
|
|
|
|
|
if (!extract_single_file(*i, target.back(), root_dir))
|
|
|
|
|
return false;
|
|
|
|
|
target.back().offset = offset;
|
|
|
|
|
offset += target.back().size;
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
/*
|
|
|
|
|
void remove_dir(fs::path& p)
|
|
|
|
@ -231,14 +241,13 @@ namespace libtorrent
|
|
|
|
|
, m_half_metadata(false)
|
|
|
|
|
#endif
|
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
read_torrent_info(torrent_file);
|
|
|
|
|
}
|
|
|
|
|
catch(type_error&)
|
|
|
|
|
{
|
|
|
|
|
std::string error;
|
|
|
|
|
#ifndef BOOST_NO_EXCEPTIONS
|
|
|
|
|
if (!read_torrent_info(torrent_file, error))
|
|
|
|
|
throw invalid_torrent_file();
|
|
|
|
|
}
|
|
|
|
|
#else
|
|
|
|
|
read_torrent_info(torrent_file, error);
|
|
|
|
|
#endif
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// constructor used for creating new torrents
|
|
|
|
@ -330,8 +339,14 @@ namespace libtorrent
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void torrent_info::parse_info_section(entry const& info)
|
|
|
|
|
bool torrent_info::parse_info_section(entry const& info, std::string& error)
|
|
|
|
|
{
|
|
|
|
|
if (info.type() != entry::dictionary_t)
|
|
|
|
|
{
|
|
|
|
|
error = "'info' entry is not a dictionary";
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// encode the info-field in order to calculate it's sha1-hash
|
|
|
|
|
std::vector<char> buf;
|
|
|
|
|
bencode(std::back_inserter(buf), info);
|
|
|
|
@ -340,36 +355,71 @@ namespace libtorrent
|
|
|
|
|
m_info_hash = h.final();
|
|
|
|
|
|
|
|
|
|
// extract piece length
|
|
|
|
|
m_piece_length = (int)info["piece length"].integer();
|
|
|
|
|
if (m_piece_length <= 0) throw std::runtime_error("invalid torrent. piece length <= 0");
|
|
|
|
|
entry const* piece_length = info.find_key("piece length");
|
|
|
|
|
if (piece_length == 0 || piece_length->type() != entry::int_t)
|
|
|
|
|
{
|
|
|
|
|
error = "invalid or missing 'piece length' entry in torrent file";
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
m_piece_length = (int)piece_length->integer();
|
|
|
|
|
if (m_piece_length <= 0)
|
|
|
|
|
{
|
|
|
|
|
error = "invalid torrent. piece length <= 0";
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// extract file name (or the directory name if it's a multifile libtorrent)
|
|
|
|
|
if (entry const* e = info.find_key("name.utf-8"))
|
|
|
|
|
entry const* e = info.find_key("name.utf-8");
|
|
|
|
|
if (e && e->type() == entry::string_t)
|
|
|
|
|
{ m_name = e->string(); }
|
|
|
|
|
else
|
|
|
|
|
{ m_name = info["name"].string(); }
|
|
|
|
|
{
|
|
|
|
|
entry const* e = info.find_key("name");
|
|
|
|
|
if (e == 0 || e->type() != entry::string_t)
|
|
|
|
|
{
|
|
|
|
|
error = "invalid name in torrent file";
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
m_name = e->string();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fs::path tmp = m_name;
|
|
|
|
|
if (tmp.is_complete()) throw std::runtime_error("torrent contains "
|
|
|
|
|
"a file with an absolute path: '" + m_name + "'");
|
|
|
|
|
if (tmp.has_branch_path()) throw std::runtime_error(
|
|
|
|
|
"torrent contains name with directories: '" + m_name + "'");
|
|
|
|
|
if (tmp.is_complete())
|
|
|
|
|
{
|
|
|
|
|
error = "torrent contains a file with an absolute path: '" + m_name + "'";
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
if (tmp.has_branch_path())
|
|
|
|
|
{
|
|
|
|
|
error = "torrent contains name with directories: '" + m_name + "'";
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// extract file list
|
|
|
|
|
entry const* i = info.find_key("files");
|
|
|
|
|
if (i == 0)
|
|
|
|
|
{
|
|
|
|
|
entry const* length = info.find_key("length");
|
|
|
|
|
if (length == 0 || length->type() != entry::int_t)
|
|
|
|
|
{
|
|
|
|
|
error = "invalid length of torrent";
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
// if there's no list of files, there has to be a length
|
|
|
|
|
// field.
|
|
|
|
|
file_entry e;
|
|
|
|
|
e.path = m_name;
|
|
|
|
|
e.offset = 0;
|
|
|
|
|
e.size = info["length"].integer();
|
|
|
|
|
e.size = length->integer();
|
|
|
|
|
m_files.push_back(e);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
extract_files(i->list(), m_files, m_name);
|
|
|
|
|
if (!extract_files(i->list(), m_files, m_name))
|
|
|
|
|
{
|
|
|
|
|
error = "failed to parse files from torrent file";
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
m_multifile = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@ -382,12 +432,22 @@ namespace libtorrent
|
|
|
|
|
// we want this division to round upwards, that's why we have the
|
|
|
|
|
// extra addition
|
|
|
|
|
|
|
|
|
|
entry const* pieces_ent = info.find_key("pieces");
|
|
|
|
|
if (pieces_ent == 0 || pieces_ent->type() != entry::string_t)
|
|
|
|
|
{
|
|
|
|
|
error = "invalid or missing 'pieces' entry in torrent file";
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
m_num_pieces = static_cast<int>((m_total_size + m_piece_length - 1) / m_piece_length);
|
|
|
|
|
m_piece_hash.resize(m_num_pieces);
|
|
|
|
|
const std::string& hash_string = info["pieces"].string();
|
|
|
|
|
const std::string& hash_string = pieces_ent->string();
|
|
|
|
|
|
|
|
|
|
if ((int)hash_string.length() != m_num_pieces * 20)
|
|
|
|
|
throw invalid_torrent_file();
|
|
|
|
|
{
|
|
|
|
|
error = "incorrect number of piece hashes in torrent file";
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < m_num_pieces; ++i)
|
|
|
|
|
std::copy(
|
|
|
|
@ -423,34 +483,37 @@ namespace libtorrent
|
|
|
|
|
TORRENT_ASSERT(hasher(&info_section_buf[0], info_section_buf.size()).final()
|
|
|
|
|
== m_info_hash);
|
|
|
|
|
#endif
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// extracts information from a libtorrent file and fills in the structures in
|
|
|
|
|
// the torrent object
|
|
|
|
|
void torrent_info::read_torrent_info(const entry& torrent_file)
|
|
|
|
|
bool torrent_info::read_torrent_info(const entry& torrent_file, std::string& error)
|
|
|
|
|
{
|
|
|
|
|
if (torrent_file.type() != entry::dictionary_t)
|
|
|
|
|
{
|
|
|
|
|
error = "torrent file is not a dictionary";
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// extract the url of the tracker
|
|
|
|
|
if (entry const* i = torrent_file.find_key("announce-list"))
|
|
|
|
|
entry const* i = torrent_file.find_key("announce-list");
|
|
|
|
|
if (i && i->type() == entry::list_t)
|
|
|
|
|
{
|
|
|
|
|
const entry::list_type& l = i->list();
|
|
|
|
|
for (entry::list_type::const_iterator j = l.begin(); j != l.end(); ++j)
|
|
|
|
|
{
|
|
|
|
|
if (j->type() != entry::list_t) break;
|
|
|
|
|
const entry::list_type& ll = j->list();
|
|
|
|
|
for (entry::list_type::const_iterator k = ll.begin(); k != ll.end(); ++k)
|
|
|
|
|
{
|
|
|
|
|
if (k->type() != entry::string_t) break;
|
|
|
|
|
announce_entry e(k->string());
|
|
|
|
|
e.tier = (int)std::distance(l.begin(), j);
|
|
|
|
|
m_urls.push_back(e);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (m_urls.size() == 0)
|
|
|
|
|
{
|
|
|
|
|
// the announce-list is empty
|
|
|
|
|
// fall back to look for announce
|
|
|
|
|
m_urls.push_back(announce_entry(
|
|
|
|
|
torrent_file["announce"].string()));
|
|
|
|
|
}
|
|
|
|
|
// shuffle each tier
|
|
|
|
|
std::vector<announce_entry>::iterator start = m_urls.begin();
|
|
|
|
|
std::vector<announce_entry>::iterator stop;
|
|
|
|
@ -466,14 +529,17 @@ namespace libtorrent
|
|
|
|
|
}
|
|
|
|
|
std::random_shuffle(start, stop);
|
|
|
|
|
}
|
|
|
|
|
else if (entry const* i = torrent_file.find_key("announce"))
|
|
|
|
|
|
|
|
|
|
entry const* announce = torrent_file.find_key("announce");
|
|
|
|
|
if (m_urls.empty() && announce && announce->type() == entry::string_t)
|
|
|
|
|
{
|
|
|
|
|
m_urls.push_back(announce_entry(i->string()));
|
|
|
|
|
m_urls.push_back(announce_entry(announce->string()));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (entry const* i = torrent_file.find_key("nodes"))
|
|
|
|
|
entry const* nodes = torrent_file.find_key("nodes");
|
|
|
|
|
if (nodes && nodes->type() == entry::list_t)
|
|
|
|
|
{
|
|
|
|
|
entry::list_type const& list = i->list();
|
|
|
|
|
entry::list_type const& list = nodes->list();
|
|
|
|
|
for (entry::list_type::const_iterator i(list.begin())
|
|
|
|
|
, end(list.end()); i != end; ++i)
|
|
|
|
|
{
|
|
|
|
@ -481,41 +547,40 @@ namespace libtorrent
|
|
|
|
|
entry::list_type const& l = i->list();
|
|
|
|
|
entry::list_type::const_iterator iter = l.begin();
|
|
|
|
|
if (l.size() < 1) continue;
|
|
|
|
|
if (iter->type() != entry::string_t) continue;
|
|
|
|
|
std::string const& hostname = iter->string();
|
|
|
|
|
++iter;
|
|
|
|
|
int port = 6881;
|
|
|
|
|
if (iter->type() != entry::int_t) continue;
|
|
|
|
|
if (l.end() != iter) port = iter->integer();
|
|
|
|
|
m_nodes.push_back(std::make_pair(hostname, port));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// extract creation date
|
|
|
|
|
try
|
|
|
|
|
entry const* creation_date = torrent_file.find_key("creation date");
|
|
|
|
|
if (creation_date && creation_date->type() == entry::int_t)
|
|
|
|
|
{
|
|
|
|
|
m_creation_date = pt::ptime(gr::date(1970, gr::Jan, 1))
|
|
|
|
|
+ pt::seconds(long(torrent_file["creation date"].integer()));
|
|
|
|
|
+ pt::seconds(long(creation_date->integer()));
|
|
|
|
|
}
|
|
|
|
|
catch (type_error) {}
|
|
|
|
|
|
|
|
|
|
// if there are any url-seeds, extract them
|
|
|
|
|
try
|
|
|
|
|
entry const* url_seeds = torrent_file.find_key("url-list");
|
|
|
|
|
if (url_seeds && url_seeds->type() == entry::string_t)
|
|
|
|
|
{
|
|
|
|
|
entry const& url_seeds = torrent_file["url-list"];
|
|
|
|
|
if (url_seeds.type() == entry::string_t)
|
|
|
|
|
m_url_seeds.push_back(url_seeds->string());
|
|
|
|
|
}
|
|
|
|
|
else if (url_seeds && url_seeds->type() == entry::list_t)
|
|
|
|
|
{
|
|
|
|
|
entry::list_type const& l = url_seeds->list();
|
|
|
|
|
for (entry::list_type::const_iterator i = l.begin();
|
|
|
|
|
i != l.end(); ++i)
|
|
|
|
|
{
|
|
|
|
|
m_url_seeds.push_back(url_seeds.string());
|
|
|
|
|
}
|
|
|
|
|
else if (url_seeds.type() == entry::list_t)
|
|
|
|
|
{
|
|
|
|
|
entry::list_type const& l = url_seeds.list();
|
|
|
|
|
for (entry::list_type::const_iterator i = l.begin();
|
|
|
|
|
i != l.end(); ++i)
|
|
|
|
|
{
|
|
|
|
|
m_url_seeds.push_back(i->string());
|
|
|
|
|
}
|
|
|
|
|
if (i->type() != entry::string_t) continue;
|
|
|
|
|
m_url_seeds.push_back(i->string());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
catch (type_error&) {}
|
|
|
|
|
|
|
|
|
|
// extract comment
|
|
|
|
|
if (entry const* e = torrent_file.find_key("comment.utf-8"))
|
|
|
|
@ -528,7 +593,13 @@ namespace libtorrent
|
|
|
|
|
else if (entry const* e = torrent_file.find_key("created by"))
|
|
|
|
|
{ m_created_by = e->string(); }
|
|
|
|
|
|
|
|
|
|
parse_info_section(torrent_file["info"]);
|
|
|
|
|
entry const* info = torrent_file.find_key("info");
|
|
|
|
|
if (info == 0 || info->type() != entry::dictionary_t)
|
|
|
|
|
{
|
|
|
|
|
error = "missing or invalid 'info' section in torrent file";
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
return parse_info_section(*info, error);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
boost::optional<pt::ptime>
|
|
|
|
@ -920,3 +991,4 @@ namespace libtorrent
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|