00001
00002
00003
00004
00005
00006
00007
00008 #include <ctime>
00009 #include <iostream>
00010 #include <fstream>
00011 #include <iomanip>
00012 #include <iterator>
00013 #include <algorithm>
00014 #include <set>
00015
00016 #ifdef _MSC_VER
00017 #pragma warning(push, 1)
00018 #endif
00019
00020 #include <boost/lexical_cast.hpp>
00021 #include <boost/date_time/gregorian/gregorian_types.hpp>
00022 #include <boost/filesystem/path.hpp>
00023 #include <boost/next_prior.hpp>
00024 #include <boost/bind.hpp>
00025
00026 #ifdef _MSC_VER
00027 #pragma warning(pop)
00028 #endif
00029
00030 #include "libtorrent/torrent_info.hpp"
00031 #include "libtorrent/bencode.hpp"
00032 #include "libtorrent/hasher.hpp"
00033 #include "libtorrent/entry.hpp"
00034
00035 using namespace libtorrent;
00036 using namespace boost::filesystem;
00037
00038 namespace
00039 {
00040 void convert_to_utf8(std::string& str, unsigned char chr)
00041 {
00042 str += 0xc0 | ((chr & 0xff) >> 6);
00043 str += 0x80 | (chr & 0x3f);
00044 }
00045
00046 void verify_encoding(file_entry& target)
00047 {
00048 std::string tmp_path;
00049 std::string file_path = target.path.string();
00050 bool valid_encoding = true;
00051 for (std::string::iterator i = file_path.begin()
00052 , end(file_path.end()); i != end; ++i)
00053 {
00054
00055 if ((*i & 0x80) == 0)
00056 {
00057 tmp_path += *i;
00058 continue;
00059 }
00060
00061 if (std::distance(i, end) < 2)
00062 {
00063 convert_to_utf8(tmp_path, *i);
00064 valid_encoding = false;
00065 continue;
00066 }
00067
00068
00069 if ((i[0] & 0xe0) == 0xc0
00070 && (i[1] & 0xc0) == 0x80)
00071 {
00072 tmp_path += i[0];
00073 tmp_path += i[1];
00074 i += 1;
00075 continue;
00076 }
00077
00078 if (std::distance(i, end) < 3)
00079 {
00080 convert_to_utf8(tmp_path, *i);
00081 valid_encoding = false;
00082 continue;
00083 }
00084
00085
00086 if ((i[0] & 0xf0) == 0xe0
00087 && (i[1] & 0xc0) == 0x80
00088 && (i[2] & 0xc0) == 0x80)
00089 {
00090 tmp_path += i[0];
00091 tmp_path += i[1];
00092 tmp_path += i[2];
00093 i += 2;
00094 continue;
00095 }
00096
00097 if (std::distance(i, end) < 4)
00098 {
00099 convert_to_utf8(tmp_path, *i);
00100 valid_encoding = false;
00101 continue;
00102 }
00103
00104
00105 if ((i[0] & 0xf0) == 0xe0
00106 && (i[1] & 0xc0) == 0x80
00107 && (i[2] & 0xc0) == 0x80
00108 && (i[3] & 0xc0) == 0x80)
00109 {
00110 tmp_path += i[0];
00111 tmp_path += i[1];
00112 tmp_path += i[2];
00113 tmp_path += i[3];
00114 i += 3;
00115 continue;
00116 }
00117
00118 convert_to_utf8(tmp_path, *i);
00119 valid_encoding = false;
00120 }
00121
00122
00123
00124
00125 if (!valid_encoding)
00126 {
00127 target.orig_path.reset(new path(target.path));
00128 target.path = tmp_path;
00129 }
00130 }
00131
00132 void extract_single_file(const entry& dict, file_entry& target
00133 , std::string const& root_dir)
00134 {
00135 target.size = dict["length"].integer();
00136 target.path = root_dir;
00137
00138
00139
00140
00141
00142 const entry::list_type* list = 0;
00143 if (entry const* p = dict.find_key("path.utf-8"))
00144 {
00145 list = &p->list();
00146 }
00147 else
00148 {
00149 list = &dict["path"].list();
00150 }
00151
00152 for (entry::list_type::const_iterator i = list->begin();
00153 i != list->end(); ++i)
00154 {
00155 if (i->string() != "..")
00156 target.path /= i->string();
00157 }
00158 verify_encoding(target);
00159 if (target.path.is_complete()) throw std::runtime_error("torrent contains "
00160 "a file with an absolute path: '"
00161 + target.path.native_file_string() + "'");
00162 }
00163
00164 void extract_files(const entry::list_type& list, std::vector<file_entry>& target
00165 , std::string const& root_dir)
00166 {
00167 size_type offset = 0;
00168 for (entry::list_type::const_iterator i = list.begin(); i != list.end(); ++i)
00169 {
00170 target.push_back(file_entry());
00171 extract_single_file(*i, target.back(), root_dir);
00172 target.back().offset = offset;
00173 offset += target.back().size;
00174 }
00175 }
00176
00177 void remove_dir(path& p)
00178 {
00179 assert(p.begin() != p.end());
00180 path tmp;
00181 for (path::iterator i = boost::next(p.begin()); i != p.end(); ++i)
00182 tmp /= *i;
00183 p = tmp;
00184 }
00185 }
00186
00187 namespace libtorrent
00188 {
00189
00190 using namespace boost::gregorian;
00191 using namespace boost::posix_time;
00192
00193
00194 torrent_info::torrent_info(const entry& torrent_file)
00195 : m_creation_date(date(not_a_date_time))
00196 , m_multifile(false)
00197 , m_private(false)
00198 , m_extra_info(entry::dictionary_t)
00199 {
00200 try
00201 {
00202 read_torrent_info(torrent_file);
00203 }
00204 catch(type_error&)
00205 {
00206 throw invalid_torrent_file();
00207 }
00208 }
00209
00210
00211
00212
00213
00214 torrent_info::torrent_info(sha1_hash const& info_hash)
00215 : m_piece_length(0)
00216 , m_total_size(0)
00217 , m_info_hash(info_hash)
00218 , m_name()
00219 , m_creation_date(second_clock::universal_time())
00220 , m_multifile(false)
00221 , m_private(false)
00222 , m_extra_info(entry::dictionary_t)
00223 {
00224 }
00225
00226 torrent_info::torrent_info()
00227 : m_piece_length(0)
00228 , m_total_size(0)
00229 , m_info_hash(0)
00230 , m_name()
00231 , m_creation_date(second_clock::universal_time())
00232 , m_multifile(false)
00233 , m_private(false)
00234 , m_extra_info(entry::dictionary_t)
00235 {
00236 }
00237
00238 torrent_info::~torrent_info()
00239 {}
00240
00241 void torrent_info::set_piece_size(int size)
00242 {
00243
00244 #ifndef NDEBUG
00245 for (int i = 0; i < 32; ++i)
00246 {
00247 if (size & (1 << i))
00248 {
00249 assert((size & ~(1 << i)) == 0);
00250 break;
00251 }
00252 }
00253 #endif
00254 m_piece_length = size;
00255
00256 int num_pieces = static_cast<int>(
00257 (m_total_size + m_piece_length - 1) / m_piece_length);
00258 int old_num_pieces = static_cast<int>(m_piece_hash.size());
00259
00260 m_piece_hash.resize(num_pieces);
00261 for (int i = old_num_pieces; i < num_pieces; ++i)
00262 {
00263 m_piece_hash[i].clear();
00264 }
00265 }
00266
00267 void torrent_info::parse_info_section(entry const& info)
00268 {
00269
00270 std::vector<char> buf;
00271 bencode(std::back_inserter(buf), info);
00272 hasher h;
00273 h.update(&buf[0], (int)buf.size());
00274 m_info_hash = h.final();
00275
00276
00277 m_piece_length = (int)info["piece length"].integer();
00278 if (m_piece_length <= 0) throw std::runtime_error("invalid torrent. piece length <= 0");
00279
00280
00281 if (entry const* e = info.find_key("name.utf-8"))
00282 { m_name = e->string(); }
00283 else
00284 { m_name = info["name"].string(); }
00285
00286 path tmp = m_name;
00287 if (tmp.is_complete()) throw std::runtime_error("torrent contains "
00288 "a file with an absolute path: '" + m_name + "'");
00289 if (tmp.has_branch_path()) throw std::runtime_error(
00290 "torrent contains name with directories: '" + m_name + "'");
00291
00292
00293 entry const* i = info.find_key("files");
00294 if (i == 0)
00295 {
00296
00297
00298 file_entry e;
00299 e.path = m_name;
00300 e.offset = 0;
00301 e.size = info["length"].integer();
00302 m_files.push_back(e);
00303 }
00304 else
00305 {
00306 extract_files(i->list(), m_files, m_name);
00307 m_multifile = true;
00308 }
00309
00310
00311 m_total_size = 0;
00312 for (std::vector<file_entry>::iterator i = m_files.begin(); i != m_files.end(); ++i)
00313 m_total_size += i->size;
00314
00315
00316
00317
00318
00319 int num_pieces = static_cast<int>((m_total_size + m_piece_length - 1) / m_piece_length);
00320 m_piece_hash.resize(num_pieces);
00321 const std::string& hash_string = info["pieces"].string();
00322
00323 if ((int)hash_string.length() != num_pieces * 20)
00324 throw invalid_torrent_file();
00325
00326 for (int i = 0; i < num_pieces; ++i)
00327 std::copy(
00328 hash_string.begin() + i*20
00329 , hash_string.begin() + (i+1)*20
00330 , m_piece_hash[i].begin());
00331
00332 for (entry::dictionary_type::const_iterator i = info.dict().begin()
00333 , end(info.dict().end()); i != end; ++i)
00334 {
00335 if (i->first == "pieces"
00336 || i->first == "piece length"
00337 || i->first == "length")
00338 continue;
00339 m_extra_info[i->first] = i->second;
00340 }
00341
00342 if (entry const* priv = info.find_key("private"))
00343 {
00344 if (priv->type() != entry::int_t
00345 || priv->integer() != 0)
00346 {
00347
00348
00349 m_private = true;
00350 }
00351 }
00352
00353 #ifndef NDEBUG
00354 std::vector<char> info_section_buf;
00355 entry gen_info_section = create_info_metadata();
00356 bencode(std::back_inserter(info_section_buf), gen_info_section);
00357 assert(hasher(&info_section_buf[0], info_section_buf.size()).final()
00358 == m_info_hash);
00359 #endif
00360 }
00361
00362
00363
00364 void torrent_info::read_torrent_info(const entry& torrent_file)
00365 {
00366
00367 if (entry const* i = torrent_file.find_key("announce-list"))
00368 {
00369 const entry::list_type& l = i->list();
00370 for (entry::list_type::const_iterator j = l.begin(); j != l.end(); ++j)
00371 {
00372 const entry::list_type& ll = j->list();
00373 for (entry::list_type::const_iterator k = ll.begin(); k != ll.end(); ++k)
00374 {
00375 announce_entry e(k->string());
00376 e.tier = (int)std::distance(l.begin(), j);
00377 m_urls.push_back(e);
00378 }
00379 }
00380
00381 if (m_urls.size() == 0)
00382 {
00383
00384
00385 m_urls.push_back(announce_entry(
00386 torrent_file["announce"].string()));
00387 }
00388
00389 std::vector<announce_entry>::iterator start = m_urls.begin();
00390 std::vector<announce_entry>::iterator stop;
00391 int current_tier = m_urls.front().tier;
00392 for (stop = m_urls.begin(); stop != m_urls.end(); ++stop)
00393 {
00394 if (stop->tier != current_tier)
00395 {
00396 std::random_shuffle(start, stop);
00397 start = stop;
00398 current_tier = stop->tier;
00399 }
00400 }
00401 std::random_shuffle(start, stop);
00402 }
00403 else if (entry const* i = torrent_file.find_key("announce"))
00404 {
00405 m_urls.push_back(announce_entry(i->string()));
00406 }
00407
00408 if (entry const* i = torrent_file.find_key("nodes"))
00409 {
00410 entry::list_type const& list = i->list();
00411 for (entry::list_type::const_iterator i(list.begin())
00412 , end(list.end()); i != end; ++i)
00413 {
00414 if (i->type() != entry::list_t) continue;
00415 entry::list_type const& l = i->list();
00416 entry::list_type::const_iterator iter = l.begin();
00417 if (l.size() < 1) continue;
00418 std::string const& hostname = iter->string();
00419 ++iter;
00420 int port = 6881;
00421 if (l.end() != iter) port = iter->integer();
00422 m_nodes.push_back(std::make_pair(hostname, port));
00423 }
00424 }
00425
00426
00427 try
00428 {
00429 m_creation_date = ptime(date(1970, Jan, 1))
00430 + seconds(long(torrent_file["creation date"].integer()));
00431 }
00432 catch (type_error) {}
00433
00434
00435 try
00436 {
00437 entry const& url_seeds = torrent_file["url-list"];
00438 if (url_seeds.type() == entry::string_t)
00439 {
00440 m_url_seeds.push_back(url_seeds.string());
00441 }
00442 else if (url_seeds.type() == entry::list_t)
00443 {
00444 entry::list_type const& l = url_seeds.list();
00445 for (entry::list_type::const_iterator i = l.begin();
00446 i != l.end(); ++i)
00447 {
00448 m_url_seeds.push_back(i->string());
00449 }
00450 }
00451 }
00452 catch (type_error&) {}
00453
00454
00455 if (entry const* e = torrent_file.find_key("comment.utf-8"))
00456 { m_comment = e->string(); }
00457 else if (entry const* e = torrent_file.find_key("comment"))
00458 { m_comment = e->string(); }
00459
00460 if (entry const* e = torrent_file.find_key("created by.utf-8"))
00461 { m_created_by = e->string(); }
00462 else if (entry const* e = torrent_file.find_key("created by"))
00463 { m_created_by = e->string(); }
00464
00465 parse_info_section(torrent_file["info"]);
00466 }
00467
00468 boost::optional<ptime>
00469 torrent_info::creation_date() const
00470 {
00471 if (m_creation_date != ptime(date(not_a_date_time)))
00472 {
00473 return boost::optional<ptime>(m_creation_date);
00474 }
00475 return boost::optional<ptime>();
00476 }
00477
00478 void torrent_info::add_tracker(std::string const& url, int tier)
00479 {
00480 announce_entry e(url);
00481 e.tier = tier;
00482 m_urls.push_back(e);
00483
00484 using boost::bind;
00485 std::sort(m_urls.begin(), m_urls.end(), boost::bind<bool>(std::less<int>()
00486 , bind(&announce_entry::tier, _1), bind(&announce_entry::tier, _2)));
00487 }
00488
00489 void torrent_info::add_file(boost::filesystem::path file, size_type size)
00490 {
00491 assert(file.begin() != file.end());
00492
00493 if (!file.has_branch_path())
00494 {
00495
00496
00497
00498
00499 assert(m_files.empty());
00500 assert(!m_multifile);
00501 m_name = file.string();
00502 }
00503 else
00504 {
00505 #ifndef NDEBUG
00506 if (!m_files.empty())
00507 assert(m_name == *file.begin());
00508 #endif
00509 m_multifile = true;
00510 m_name = *file.begin();
00511 }
00512
00513 file_entry e;
00514 e.path = file;
00515 e.size = size;
00516 e.offset = m_files.empty() ? 0 : m_files.back().offset
00517 + m_files.back().size;
00518 m_files.push_back(e);
00519
00520 m_total_size += size;
00521
00522 if (m_piece_length == 0)
00523 m_piece_length = 256 * 1024;
00524
00525 int num_pieces = static_cast<int>(
00526 (m_total_size + m_piece_length - 1) / m_piece_length);
00527 int old_num_pieces = static_cast<int>(m_piece_hash.size());
00528
00529 m_piece_hash.resize(num_pieces);
00530 if (num_pieces > old_num_pieces)
00531 std::for_each(m_piece_hash.begin() + old_num_pieces
00532 , m_piece_hash.end(), boost::bind(&sha1_hash::clear, _1));
00533 }
00534
00535 void torrent_info::add_url_seed(std::string const& url)
00536 {
00537 m_url_seeds.push_back(url);
00538 }
00539
00540 void torrent_info::set_comment(char const* str)
00541 {
00542 m_comment = str;
00543 }
00544
00545 void torrent_info::set_creator(char const* str)
00546 {
00547 m_created_by = str;
00548 }
00549
00550 entry torrent_info::create_info_metadata() const
00551 {
00552 namespace fs = boost::filesystem;
00553
00554
00555 assert(!m_files.empty());
00556
00557 entry info(m_extra_info);
00558
00559 if (!info.find_key("name"))
00560 info["name"] = m_name;
00561
00562 if (!m_multifile)
00563 {
00564 info["length"] = m_files.front().size;
00565 }
00566 else
00567 {
00568 if (!info.find_key("files"))
00569 {
00570 entry& files = info["files"];
00571
00572 for (std::vector<file_entry>::const_iterator i = m_files.begin();
00573 i != m_files.end(); ++i)
00574 {
00575 files.list().push_back(entry());
00576 entry& file_e = files.list().back();
00577 file_e["length"] = i->size;
00578 entry& path_e = file_e["path"];
00579
00580 fs::path const* file_path;
00581 if (i->orig_path) file_path = &(*i->orig_path);
00582 else file_path = &i->path;
00583 assert(file_path->has_branch_path());
00584 assert(*file_path->begin() == m_name);
00585
00586 for (fs::path::iterator j = boost::next(file_path->begin());
00587 j != file_path->end(); ++j)
00588 {
00589 path_e.list().push_back(entry(*j));
00590 }
00591 }
00592 }
00593 }
00594
00595 info["piece length"] = piece_length();
00596 entry& pieces = info["pieces"];
00597
00598 std::string& p = pieces.string();
00599
00600 for (std::vector<sha1_hash>::const_iterator i = m_piece_hash.begin();
00601 i != m_piece_hash.end(); ++i)
00602 {
00603 p.append((char*)i->begin(), (char*)i->end());
00604 }
00605
00606 return info;
00607 }
00608
00609 entry torrent_info::create_torrent() const
00610 {
00611 assert(m_piece_length > 0);
00612
00613 using namespace boost::gregorian;
00614 using namespace boost::posix_time;
00615
00616 namespace fs = boost::filesystem;
00617
00618 if ((m_urls.empty() && m_nodes.empty()) || m_files.empty())
00619 {
00620
00621
00622 return entry();
00623 }
00624
00625 entry dict;
00626
00627 if (m_private) dict["private"] = 1;
00628
00629 if (!m_urls.empty())
00630 dict["announce"] = m_urls.front().url;
00631
00632 if (!m_nodes.empty())
00633 {
00634 entry& nodes = dict["nodes"];
00635 entry::list_type& nodes_list = nodes.list();
00636 for (nodes_t::const_iterator i = m_nodes.begin()
00637 , end(m_nodes.end()); i != end; ++i)
00638 {
00639 entry::list_type node;
00640 node.push_back(entry(i->first));
00641 node.push_back(entry(i->second));
00642 nodes_list.push_back(entry(node));
00643 }
00644 }
00645
00646 if (m_urls.size() > 1)
00647 {
00648 entry trackers(entry::list_t);
00649 entry tier(entry::list_t);
00650 int current_tier = m_urls.front().tier;
00651 for (std::vector<announce_entry>::const_iterator i = m_urls.begin();
00652 i != m_urls.end(); ++i)
00653 {
00654 if (i->tier != current_tier)
00655 {
00656 current_tier = i->tier;
00657 trackers.list().push_back(tier);
00658 tier.list().clear();
00659 }
00660 tier.list().push_back(entry(i->url));
00661 }
00662 trackers.list().push_back(tier);
00663 dict["announce-list"] = trackers;
00664 }
00665
00666 if (!m_comment.empty())
00667 dict["comment"] = m_comment;
00668
00669 dict["creation date"] =
00670 (m_creation_date - ptime(date(1970, Jan, 1))).total_seconds();
00671
00672 if (!m_created_by.empty())
00673 dict["created by"] = m_created_by;
00674
00675 if (!m_url_seeds.empty())
00676 {
00677 if (m_url_seeds.size() == 1)
00678 {
00679 dict["url-list"] = m_url_seeds.front();
00680 }
00681 else
00682 {
00683 entry& list = dict["url-list"];
00684 for (std::vector<std::string>::const_iterator i
00685 = m_url_seeds.begin(); i != m_url_seeds.end(); ++i)
00686 {
00687 list.list().push_back(entry(*i));
00688 }
00689 }
00690 }
00691
00692 dict["info"] = create_info_metadata();
00693
00694 entry const& info_section = dict["info"];
00695 std::vector<char> buf;
00696 bencode(std::back_inserter(buf), info_section);
00697 m_info_hash = hasher(&buf[0], buf.size()).final();
00698
00699 return dict;
00700 }
00701
00702 void torrent_info::set_hash(int index, const sha1_hash& h)
00703 {
00704 assert(index >= 0);
00705 assert(index < (int)m_piece_hash.size());
00706 m_piece_hash[index] = h;
00707 }
00708
00709 void torrent_info::convert_file_names()
00710 {
00711 assert(false);
00712 }
00713
00714 void torrent_info::print(std::ostream& os) const
00715 {
00716 os << "trackers:\n";
00717 for (std::vector<announce_entry>::const_iterator i = trackers().begin();
00718 i != trackers().end(); ++i)
00719 {
00720 os << i->tier << ": " << i->url << "\n";
00721 }
00722 if (!m_comment.empty())
00723 os << "comment: " << m_comment << "\n";
00724 if (m_creation_date != ptime(date(not_a_date_time)))
00725 os << "creation date: " << to_simple_string(m_creation_date) << "\n";
00726 os << "private: " << (m_private?"yes":"no") << "\n";
00727 os << "number of pieces: " << num_pieces() << "\n";
00728 os << "piece length: " << piece_length() << "\n";
00729 os << "files:\n";
00730 for (file_iterator i = begin_files(); i != end_files(); ++i)
00731 os << " " << std::setw(11) << i->size << " " << i->path.string() << "\n";
00732 }
00733
00734 size_type torrent_info::piece_size(int index) const
00735 {
00736 assert(index >= 0 && index < num_pieces());
00737 if (index == num_pieces()-1)
00738 {
00739 size_type size = total_size()
00740 - (num_pieces() - 1) * piece_length();
00741 assert(size > 0);
00742 assert(size <= piece_length());
00743 return size;
00744 }
00745 else
00746 return piece_length();
00747 }
00748
00749 void torrent_info::add_node(std::pair<std::string, int> const& node)
00750 {
00751 m_nodes.push_back(node);
00752 }
00753
00754 std::vector<file_slice> torrent_info::map_block(int piece, size_type offset
00755 , int size) const
00756 {
00757 assert(num_files() > 0);
00758 std::vector<file_slice> ret;
00759
00760 size_type start = piece * (size_type)m_piece_length + offset;
00761 assert(start + size <= m_total_size);
00762
00763
00764
00765 size_type file_offset = start;
00766 std::vector<file_entry>::const_iterator file_iter;
00767
00768 int counter = 0;
00769 for (file_iter = begin_files();; ++counter, ++file_iter)
00770 {
00771 assert(file_iter != end_files());
00772 if (file_offset < file_iter->size)
00773 {
00774 file_slice f;
00775 f.file_index = counter;
00776 f.offset = file_offset;
00777 f.size = (std::min)(file_iter->size - file_offset, (size_type)size);
00778 size -= f.size;
00779 file_offset += f.size;
00780 ret.push_back(f);
00781 }
00782
00783 assert(size >= 0);
00784 if (size <= 0) break;
00785
00786 file_offset -= file_iter->size;
00787 }
00788 return ret;
00789 }
00790
00791 peer_request torrent_info::map_file(int file_index, size_type file_offset
00792 , int size) const
00793 {
00794 assert(file_index < (int)m_files.size());
00795 assert(file_index >= 0);
00796 size_type offset = file_offset + m_files[file_index].offset;
00797
00798 peer_request ret;
00799 ret.piece = offset / piece_length();
00800 ret.start = offset - ret.piece * piece_length();
00801 ret.length = size;
00802 return ret;
00803 }
00804
00805 }