formatting fixes in client test, and made the test build when resolve countries is...
[libtorrent-kjk.git] / src / torrent_info.cpp
blobe546a12431454923dd3cf8f863fa10487f7891f0
1 /*
3 Copyright (c) 2003, Arvid Norberg
4 All rights reserved.
6 Redistribution and use in source and binary forms, with or without
7 modification, are permitted provided that the following conditions
8 are met:
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.
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.
33 #include "libtorrent/pch.hpp"
35 #include <ctime>
36 #include <iostream>
37 #include <fstream>
38 #include <iomanip>
39 #include <iterator>
40 #include <algorithm>
41 #include <set>
43 #ifdef _MSC_VER
44 #pragma warning(push, 1)
45 #endif
47 #include <boost/lexical_cast.hpp>
48 #include <boost/filesystem/path.hpp>
49 #include <boost/next_prior.hpp>
50 #include <boost/bind.hpp>
52 #ifdef _MSC_VER
53 #pragma warning(pop)
54 #endif
56 #include "libtorrent/torrent_info.hpp"
57 #include "libtorrent/bencode.hpp"
58 #include "libtorrent/hasher.hpp"
59 #include "libtorrent/entry.hpp"
61 namespace pt = boost::posix_time;
62 namespace gr = boost::gregorian;
64 using namespace libtorrent;
65 using namespace boost::filesystem;
67 namespace
69 void convert_to_utf8(std::string& str, unsigned char chr)
71 str += 0xc0 | ((chr & 0xff) >> 6);
72 str += 0x80 | (chr & 0x3f);
75 void verify_encoding(file_entry& target)
77 std::string tmp_path;
78 std::string file_path = target.path.string();
79 bool valid_encoding = true;
80 for (std::string::iterator i = file_path.begin()
81 , end(file_path.end()); i != end; ++i)
83 // valid ascii-character
84 if ((*i & 0x80) == 0)
86 tmp_path += *i;
87 continue;
90 if (std::distance(i, end) < 2)
92 convert_to_utf8(tmp_path, *i);
93 valid_encoding = false;
94 continue;
97 // valid 2-byte utf-8 character
98 if ((i[0] & 0xe0) == 0xc0
99 && (i[1] & 0xc0) == 0x80)
101 tmp_path += i[0];
102 tmp_path += i[1];
103 i += 1;
104 continue;
107 if (std::distance(i, end) < 3)
109 convert_to_utf8(tmp_path, *i);
110 valid_encoding = false;
111 continue;
114 // valid 3-byte utf-8 character
115 if ((i[0] & 0xf0) == 0xe0
116 && (i[1] & 0xc0) == 0x80
117 && (i[2] & 0xc0) == 0x80)
119 tmp_path += i[0];
120 tmp_path += i[1];
121 tmp_path += i[2];
122 i += 2;
123 continue;
126 if (std::distance(i, end) < 4)
128 convert_to_utf8(tmp_path, *i);
129 valid_encoding = false;
130 continue;
133 // valid 4-byte utf-8 character
134 if ((i[0] & 0xf0) == 0xe0
135 && (i[1] & 0xc0) == 0x80
136 && (i[2] & 0xc0) == 0x80
137 && (i[3] & 0xc0) == 0x80)
139 tmp_path += i[0];
140 tmp_path += i[1];
141 tmp_path += i[2];
142 tmp_path += i[3];
143 i += 3;
144 continue;
147 convert_to_utf8(tmp_path, *i);
148 valid_encoding = false;
150 // the encoding was not valid utf-8
151 // save the original encoding and replace the
152 // commonly used path with the correctly
153 // encoded string
154 if (!valid_encoding)
156 target.orig_path.reset(new path(target.path));
157 target.path = tmp_path;
161 void extract_single_file(const entry& dict, file_entry& target
162 , std::string const& root_dir)
164 target.size = dict["length"].integer();
165 target.path = root_dir;
168 // prefer the name.utf-8
169 // because if it exists, it is more
170 // likely to be correctly encoded
172 const entry::list_type* list = 0;
173 if (entry const* p = dict.find_key("path.utf-8"))
175 list = &p->list();
177 else
179 list = &dict["path"].list();
182 for (entry::list_type::const_iterator i = list->begin();
183 i != list->end(); ++i)
185 if (i->string() != "..")
186 target.path /= i->string();
188 verify_encoding(target);
189 if (target.path.is_complete()) throw std::runtime_error("torrent contains "
190 "a file with an absolute path: '"
191 + target.path.native_file_string() + "'");
194 void extract_files(const entry::list_type& list, std::vector<file_entry>& target
195 , std::string const& root_dir)
197 size_type offset = 0;
198 for (entry::list_type::const_iterator i = list.begin(); i != list.end(); ++i)
200 target.push_back(file_entry());
201 extract_single_file(*i, target.back(), root_dir);
202 target.back().offset = offset;
203 offset += target.back().size;
207 void remove_dir(path& p)
209 assert(p.begin() != p.end());
210 path tmp;
211 for (path::iterator i = boost::next(p.begin()); i != p.end(); ++i)
212 tmp /= *i;
213 p = tmp;
217 namespace libtorrent
220 // standard constructor that parses a torrent file
221 torrent_info::torrent_info(const entry& torrent_file)
222 : m_num_pieces(0)
223 , m_creation_date(pt::ptime(pt::not_a_date_time))
224 , m_multifile(false)
225 , m_private(false)
226 , m_extra_info(entry::dictionary_t)
227 #ifndef NDEBUG
228 , m_half_metadata(false)
229 #endif
233 read_torrent_info(torrent_file);
235 catch(type_error&)
237 throw invalid_torrent_file();
241 // constructor used for creating new torrents
242 // will not contain any hashes, comments, creation date
243 // just the necessary to use it with piece manager
244 // used for torrents with no metadata
245 torrent_info::torrent_info(sha1_hash const& info_hash)
246 : m_piece_length(0)
247 , m_total_size(0)
248 , m_num_pieces(0)
249 , m_info_hash(info_hash)
250 , m_name()
251 , m_creation_date(pt::second_clock::universal_time())
252 , m_multifile(false)
253 , m_private(false)
254 , m_extra_info(entry::dictionary_t)
255 #ifndef NDEBUG
256 , m_half_metadata(false)
257 #endif
261 torrent_info::torrent_info()
262 : m_piece_length(0)
263 , m_total_size(0)
264 , m_num_pieces(0)
265 , m_info_hash(0)
266 , m_name()
267 , m_creation_date(pt::second_clock::universal_time())
268 , m_multifile(false)
269 , m_private(false)
270 , m_extra_info(entry::dictionary_t)
271 #ifndef NDEBUG
272 , m_half_metadata(false)
273 #endif
277 torrent_info::~torrent_info()
280 void torrent_info::set_piece_size(int size)
282 // make sure the size is an even power of 2
283 #ifndef NDEBUG
284 for (int i = 0; i < 32; ++i)
286 if (size & (1 << i))
288 assert((size & ~(1 << i)) == 0);
289 break;
292 #endif
293 assert(!m_half_metadata);
294 m_piece_length = size;
296 m_num_pieces = static_cast<int>(
297 (m_total_size + m_piece_length - 1) / m_piece_length);
298 int old_num_pieces = static_cast<int>(m_piece_hash.size());
300 m_piece_hash.resize(m_num_pieces);
301 for (int i = old_num_pieces; i < m_num_pieces; ++i)
303 m_piece_hash[i].clear();
307 void torrent_info::parse_info_section(entry const& info)
309 // encode the info-field in order to calculate it's sha1-hash
310 std::vector<char> buf;
311 bencode(std::back_inserter(buf), info);
312 hasher h;
313 h.update(&buf[0], (int)buf.size());
314 m_info_hash = h.final();
316 // extract piece length
317 m_piece_length = (int)info["piece length"].integer();
318 if (m_piece_length <= 0) throw std::runtime_error("invalid torrent. piece length <= 0");
320 // extract file name (or the directory name if it's a multifile libtorrent)
321 if (entry const* e = info.find_key("name.utf-8"))
322 { m_name = e->string(); }
323 else
324 { m_name = info["name"].string(); }
326 path tmp = m_name;
327 if (tmp.is_complete()) throw std::runtime_error("torrent contains "
328 "a file with an absolute path: '" + m_name + "'");
329 if (tmp.has_branch_path()) throw std::runtime_error(
330 "torrent contains name with directories: '" + m_name + "'");
332 // extract file list
333 entry const* i = info.find_key("files");
334 if (i == 0)
336 // if there's no list of files, there has to be a length
337 // field.
338 file_entry e;
339 e.path = m_name;
340 e.offset = 0;
341 e.size = info["length"].integer();
342 m_files.push_back(e);
344 else
346 extract_files(i->list(), m_files, m_name);
347 m_multifile = true;
350 // calculate total size of all pieces
351 m_total_size = 0;
352 for (std::vector<file_entry>::iterator i = m_files.begin(); i != m_files.end(); ++i)
353 m_total_size += i->size;
355 // extract sha-1 hashes for all pieces
356 // we want this division to round upwards, that's why we have the
357 // extra addition
359 m_num_pieces = static_cast<int>((m_total_size + m_piece_length - 1) / m_piece_length);
360 m_piece_hash.resize(m_num_pieces);
361 const std::string& hash_string = info["pieces"].string();
363 if ((int)hash_string.length() != m_num_pieces * 20)
364 throw invalid_torrent_file();
366 for (int i = 0; i < m_num_pieces; ++i)
367 std::copy(
368 hash_string.begin() + i*20
369 , hash_string.begin() + (i+1)*20
370 , m_piece_hash[i].begin());
372 for (entry::dictionary_type::const_iterator i = info.dict().begin()
373 , end(info.dict().end()); i != end; ++i)
375 if (i->first == "pieces"
376 || i->first == "piece length"
377 || i->first == "length")
378 continue;
379 m_extra_info[i->first] = i->second;
382 if (entry const* priv = info.find_key("private"))
384 if (priv->type() != entry::int_t
385 || priv->integer() != 0)
387 // this key exists and it's not 0.
388 // consider the torrent private
389 m_private = true;
393 #ifndef NDEBUG
394 std::vector<char> info_section_buf;
395 entry gen_info_section = create_info_metadata();
396 bencode(std::back_inserter(info_section_buf), gen_info_section);
397 assert(hasher(&info_section_buf[0], info_section_buf.size()).final()
398 == m_info_hash);
399 #endif
402 // extracts information from a libtorrent file and fills in the structures in
403 // the torrent object
404 void torrent_info::read_torrent_info(const entry& torrent_file)
406 // extract the url of the tracker
407 if (entry const* i = torrent_file.find_key("announce-list"))
409 const entry::list_type& l = i->list();
410 for (entry::list_type::const_iterator j = l.begin(); j != l.end(); ++j)
412 const entry::list_type& ll = j->list();
413 for (entry::list_type::const_iterator k = ll.begin(); k != ll.end(); ++k)
415 announce_entry e(k->string());
416 e.tier = (int)std::distance(l.begin(), j);
417 m_urls.push_back(e);
421 if (m_urls.size() == 0)
423 // the announce-list is empty
424 // fall back to look for announce
425 m_urls.push_back(announce_entry(
426 torrent_file["announce"].string()));
428 // shuffle each tier
429 std::vector<announce_entry>::iterator start = m_urls.begin();
430 std::vector<announce_entry>::iterator stop;
431 int current_tier = m_urls.front().tier;
432 for (stop = m_urls.begin(); stop != m_urls.end(); ++stop)
434 if (stop->tier != current_tier)
436 std::random_shuffle(start, stop);
437 start = stop;
438 current_tier = stop->tier;
441 std::random_shuffle(start, stop);
443 else if (entry const* i = torrent_file.find_key("announce"))
445 m_urls.push_back(announce_entry(i->string()));
448 if (entry const* i = torrent_file.find_key("nodes"))
450 entry::list_type const& list = i->list();
451 for (entry::list_type::const_iterator i(list.begin())
452 , end(list.end()); i != end; ++i)
454 if (i->type() != entry::list_t) continue;
455 entry::list_type const& l = i->list();
456 entry::list_type::const_iterator iter = l.begin();
457 if (l.size() < 1) continue;
458 std::string const& hostname = iter->string();
459 ++iter;
460 int port = 6881;
461 if (l.end() != iter) port = iter->integer();
462 m_nodes.push_back(std::make_pair(hostname, port));
466 // extract creation date
469 m_creation_date = pt::ptime(gr::date(1970, gr::Jan, 1))
470 + pt::seconds(long(torrent_file["creation date"].integer()));
472 catch (type_error) {}
474 // if there are any url-seeds, extract them
477 entry const& url_seeds = torrent_file["url-list"];
478 if (url_seeds.type() == entry::string_t)
480 m_url_seeds.push_back(url_seeds.string());
482 else if (url_seeds.type() == entry::list_t)
484 entry::list_type const& l = url_seeds.list();
485 for (entry::list_type::const_iterator i = l.begin();
486 i != l.end(); ++i)
488 m_url_seeds.push_back(i->string());
492 catch (type_error&) {}
494 // extract comment
495 if (entry const* e = torrent_file.find_key("comment.utf-8"))
496 { m_comment = e->string(); }
497 else if (entry const* e = torrent_file.find_key("comment"))
498 { m_comment = e->string(); }
500 if (entry const* e = torrent_file.find_key("created by.utf-8"))
501 { m_created_by = e->string(); }
502 else if (entry const* e = torrent_file.find_key("created by"))
503 { m_created_by = e->string(); }
505 parse_info_section(torrent_file["info"]);
508 boost::optional<pt::ptime>
509 torrent_info::creation_date() const
511 if (m_creation_date != pt::ptime(gr::date(pt::not_a_date_time)))
513 return boost::optional<pt::ptime>(m_creation_date);
515 return boost::optional<pt::ptime>();
518 void torrent_info::add_tracker(std::string const& url, int tier)
520 announce_entry e(url);
521 e.tier = tier;
522 m_urls.push_back(e);
524 using boost::bind;
525 std::sort(m_urls.begin(), m_urls.end(), boost::bind<bool>(std::less<int>()
526 , bind(&announce_entry::tier, _1), bind(&announce_entry::tier, _2)));
529 void torrent_info::add_file(boost::filesystem::path file, size_type size)
531 assert(file.begin() != file.end());
533 if (!file.has_branch_path())
535 // you have already added at least one file with a
536 // path to the file (branch_path), which means that
537 // all the other files need to be in the same top
538 // directory as the first file.
539 assert(m_files.empty());
540 assert(!m_multifile);
541 m_name = file.string();
543 else
545 #ifndef NDEBUG
546 if (!m_files.empty())
547 assert(m_name == *file.begin());
548 #endif
549 m_multifile = true;
550 m_name = *file.begin();
553 file_entry e;
554 e.path = file;
555 e.size = size;
556 e.offset = m_files.empty() ? 0 : m_files.back().offset
557 + m_files.back().size;
558 m_files.push_back(e);
560 m_total_size += size;
562 if (m_piece_length == 0)
563 m_piece_length = 256 * 1024;
565 m_num_pieces = static_cast<int>(
566 (m_total_size + m_piece_length - 1) / m_piece_length);
567 int old_num_pieces = static_cast<int>(m_piece_hash.size());
569 m_piece_hash.resize(m_num_pieces);
570 if (m_num_pieces > old_num_pieces)
571 std::for_each(m_piece_hash.begin() + old_num_pieces
572 , m_piece_hash.end(), boost::bind(&sha1_hash::clear, _1));
575 void torrent_info::add_url_seed(std::string const& url)
577 m_url_seeds.push_back(url);
580 void torrent_info::set_comment(char const* str)
582 m_comment = str;
585 void torrent_info::set_creator(char const* str)
587 m_created_by = str;
590 entry torrent_info::create_info_metadata() const
592 namespace fs = boost::filesystem;
594 // you have to add files to the torrent first
595 assert(!m_files.empty());
597 entry info(m_extra_info);
599 if (!info.find_key("name"))
600 info["name"] = m_name;
602 if (!m_multifile)
604 info["length"] = m_files.front().size;
606 else
608 if (!info.find_key("files"))
610 entry& files = info["files"];
612 for (std::vector<file_entry>::const_iterator i = m_files.begin();
613 i != m_files.end(); ++i)
615 files.list().push_back(entry());
616 entry& file_e = files.list().back();
617 file_e["length"] = i->size;
618 entry& path_e = file_e["path"];
620 fs::path const* file_path;
621 if (i->orig_path) file_path = &(*i->orig_path);
622 else file_path = &i->path;
623 assert(file_path->has_branch_path());
624 assert(*file_path->begin() == m_name);
626 for (fs::path::iterator j = boost::next(file_path->begin());
627 j != file_path->end(); ++j)
629 path_e.list().push_back(entry(*j));
635 info["piece length"] = piece_length();
636 entry& pieces = info["pieces"];
638 std::string& p = pieces.string();
640 for (std::vector<sha1_hash>::const_iterator i = m_piece_hash.begin();
641 i != m_piece_hash.end(); ++i)
643 p.append((char*)i->begin(), (char*)i->end());
646 return info;
649 entry torrent_info::create_torrent() const
651 assert(m_piece_length > 0);
653 namespace fs = boost::filesystem;
655 if ((m_urls.empty() && m_nodes.empty()) || m_files.empty())
657 // TODO: throw something here
658 // throw
659 return entry();
662 entry dict;
664 if (m_private) dict["private"] = 1;
666 if (!m_urls.empty())
667 dict["announce"] = m_urls.front().url;
669 if (!m_nodes.empty())
671 entry& nodes = dict["nodes"];
672 entry::list_type& nodes_list = nodes.list();
673 for (nodes_t::const_iterator i = m_nodes.begin()
674 , end(m_nodes.end()); i != end; ++i)
676 entry::list_type node;
677 node.push_back(entry(i->first));
678 node.push_back(entry(i->second));
679 nodes_list.push_back(entry(node));
683 if (m_urls.size() > 1)
685 entry trackers(entry::list_t);
686 entry tier(entry::list_t);
687 int current_tier = m_urls.front().tier;
688 for (std::vector<announce_entry>::const_iterator i = m_urls.begin();
689 i != m_urls.end(); ++i)
691 if (i->tier != current_tier)
693 current_tier = i->tier;
694 trackers.list().push_back(tier);
695 tier.list().clear();
697 tier.list().push_back(entry(i->url));
699 trackers.list().push_back(tier);
700 dict["announce-list"] = trackers;
703 if (!m_comment.empty())
704 dict["comment"] = m_comment;
706 dict["creation date"] =
707 (m_creation_date - pt::ptime(gr::date(1970, gr::Jan, 1))).total_seconds();
709 if (!m_created_by.empty())
710 dict["created by"] = m_created_by;
712 if (!m_url_seeds.empty())
714 if (m_url_seeds.size() == 1)
716 dict["url-list"] = m_url_seeds.front();
718 else
720 entry& list = dict["url-list"];
721 for (std::vector<std::string>::const_iterator i
722 = m_url_seeds.begin(); i != m_url_seeds.end(); ++i)
724 list.list().push_back(entry(*i));
729 dict["info"] = create_info_metadata();
731 entry const& info_section = dict["info"];
732 std::vector<char> buf;
733 bencode(std::back_inserter(buf), info_section);
734 m_info_hash = hasher(&buf[0], buf.size()).final();
736 return dict;
739 void torrent_info::set_hash(int index, const sha1_hash& h)
741 assert(index >= 0);
742 assert(index < (int)m_piece_hash.size());
743 m_piece_hash[index] = h;
746 void torrent_info::convert_file_names()
748 assert(false);
751 void torrent_info::seed_free()
753 std::vector<std::string>().swap(m_url_seeds);
754 nodes_t().swap(m_nodes);
755 std::vector<sha1_hash>().swap(m_piece_hash);
756 #ifndef NDEBUG
757 m_half_metadata = true;
758 #endif
761 // ------- start deprecation -------
763 void torrent_info::print(std::ostream& os) const
765 os << "trackers:\n";
766 for (std::vector<announce_entry>::const_iterator i = trackers().begin();
767 i != trackers().end(); ++i)
769 os << i->tier << ": " << i->url << "\n";
771 if (!m_comment.empty())
772 os << "comment: " << m_comment << "\n";
773 // if (m_creation_date != pt::ptime(gr::date(pt::not_a_date_time)))
774 // os << "creation date: " << to_simple_string(m_creation_date) << "\n";
775 os << "private: " << (m_private?"yes":"no") << "\n";
776 os << "number of pieces: " << num_pieces() << "\n";
777 os << "piece length: " << piece_length() << "\n";
778 os << "files:\n";
779 for (file_iterator i = begin_files(); i != end_files(); ++i)
780 os << " " << std::setw(11) << i->size << " " << i->path.string() << "\n";
783 // ------- end deprecation -------
785 size_type torrent_info::piece_size(int index) const
787 assert(index >= 0 && index < num_pieces());
788 if (index == num_pieces()-1)
790 size_type size = total_size()
791 - (num_pieces() - 1) * piece_length();
792 assert(size > 0);
793 assert(size <= piece_length());
794 return size;
796 else
797 return piece_length();
800 void torrent_info::add_node(std::pair<std::string, int> const& node)
802 m_nodes.push_back(node);
805 std::vector<file_slice> torrent_info::map_block(int piece, size_type offset
806 , int size) const
808 assert(num_files() > 0);
809 std::vector<file_slice> ret;
811 size_type start = piece * (size_type)m_piece_length + offset;
812 assert(start + size <= m_total_size);
814 // find the file iterator and file offset
815 // TODO: make a vector that can map piece -> file index in O(1)
816 size_type file_offset = start;
817 std::vector<file_entry>::const_iterator file_iter;
819 int counter = 0;
820 for (file_iter = begin_files();; ++counter, ++file_iter)
822 assert(file_iter != end_files());
823 if (file_offset < file_iter->size)
825 file_slice f;
826 f.file_index = counter;
827 f.offset = file_offset;
828 f.size = (std::min)(file_iter->size - file_offset, (size_type)size);
829 size -= f.size;
830 file_offset += f.size;
831 ret.push_back(f);
834 assert(size >= 0);
835 if (size <= 0) break;
837 file_offset -= file_iter->size;
839 return ret;
842 peer_request torrent_info::map_file(int file_index, size_type file_offset
843 , int size) const
845 assert(file_index < (int)m_files.size());
846 assert(file_index >= 0);
847 size_type offset = file_offset + m_files[file_index].offset;
849 peer_request ret;
850 ret.piece = offset / piece_length();
851 ret.start = offset - ret.piece * piece_length();
852 ret.length = size;
853 return ret;