3 Copyright (c) 2003, Arvid Norberg
6 Redistribution and use in source and binary forms, with or without
7 modification, are permitted provided that the following conditions
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"
44 #pragma warning(push, 1)
47 #include <boost/lexical_cast.hpp>
48 #include <boost/filesystem/path.hpp>
49 #include <boost/next_prior.hpp>
50 #include <boost/bind.hpp>
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
;
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
)
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
90 if (std::distance(i
, end
) < 2)
92 convert_to_utf8(tmp_path
, *i
);
93 valid_encoding
= false;
97 // valid 2-byte utf-8 character
98 if ((i
[0] & 0xe0) == 0xc0
99 && (i
[1] & 0xc0) == 0x80)
107 if (std::distance(i
, end
) < 3)
109 convert_to_utf8(tmp_path
, *i
);
110 valid_encoding
= false;
114 // valid 3-byte utf-8 character
115 if ((i
[0] & 0xf0) == 0xe0
116 && (i
[1] & 0xc0) == 0x80
117 && (i
[2] & 0xc0) == 0x80)
126 if (std::distance(i
, end
) < 4)
128 convert_to_utf8(tmp_path
, *i
);
129 valid_encoding
= false;
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)
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
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"))
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());
211 for (path::iterator i
= boost::next(p
.begin()); i
!= p
.end(); ++i
)
220 // standard constructor that parses a torrent file
221 torrent_info::torrent_info(const entry
& torrent_file
)
223 , m_creation_date(pt::ptime(pt::not_a_date_time
))
226 , m_extra_info(entry::dictionary_t
)
228 , m_half_metadata(false)
233 read_torrent_info(torrent_file
);
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
)
249 , m_info_hash(info_hash
)
251 , m_creation_date(pt::second_clock::universal_time())
254 , m_extra_info(entry::dictionary_t
)
256 , m_half_metadata(false)
261 torrent_info::torrent_info()
267 , m_creation_date(pt::second_clock::universal_time())
270 , m_extra_info(entry::dictionary_t
)
272 , m_half_metadata(false)
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
284 for (int i
= 0; i
< 32; ++i
)
288 assert((size
& ~(1 << i
)) == 0);
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
);
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(); }
324 { m_name
= info
["name"].string(); }
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
+ "'");
333 entry
const* i
= info
.find_key("files");
336 // if there's no list of files, there has to be a length
341 e
.size
= info
["length"].integer();
342 m_files
.push_back(e
);
346 extract_files(i
->list(), m_files
, m_name
);
350 // calculate total size of all pieces
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
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
)
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")
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
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()
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
);
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()));
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
);
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();
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();
488 m_url_seeds
.push_back(i
->string());
492 catch (type_error
&) {}
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
);
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();
546 if (!m_files
.empty())
547 assert(m_name
== *file
.begin());
550 m_name
= *file
.begin();
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
)
585 void torrent_info::set_creator(char const* 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
;
604 info
["length"] = m_files
.front().size
;
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());
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
664 if (m_private
) dict
["private"] = 1;
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
);
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();
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();
739 void torrent_info::set_hash(int index
, const sha1_hash
& h
)
742 assert(index
< (int)m_piece_hash
.size());
743 m_piece_hash
[index
] = h
;
746 void torrent_info::convert_file_names()
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
);
757 m_half_metadata
= true;
761 // ------- start deprecation -------
763 void torrent_info::print(std::ostream
& os
) const
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";
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();
793 assert(size
<= piece_length());
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
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
;
820 for (file_iter
= begin_files();; ++counter
, ++file_iter
)
822 assert(file_iter
!= end_files());
823 if (file_offset
< file_iter
->size
)
826 f
.file_index
= counter
;
827 f
.offset
= file_offset
;
828 f
.size
= (std::min
)(file_iter
->size
- file_offset
, (size_type
)size
);
830 file_offset
+= f
.size
;
835 if (size
<= 0) break;
837 file_offset
-= file_iter
->size
;
842 peer_request
torrent_info::map_file(int file_index
, size_type file_offset
845 assert(file_index
< (int)m_files
.size());
846 assert(file_index
>= 0);
847 size_type offset
= file_offset
+ m_files
[file_index
].offset
;
850 ret
.piece
= offset
/ piece_length();
851 ret
.start
= offset
- ret
.piece
* piece_length();