From 985436636e7407018ff56537858feeb5f306d790 Mon Sep 17 00:00:00 2001 From: Arvid Norberg Date: Thu, 5 May 2016 21:38:57 -0400 Subject: [PATCH] added new preformatted type to bencode entry (#698) added new preformatted type to bencode entry to support carrying a verbatim copy of an already bencoded subtree. This is to support saving torrents in resume data and create_torrent based on existing torrents, in order to preserve key order --- ChangeLog | 1 + include/libtorrent/bencode.hpp | 4 ++ include/libtorrent/entry.hpp | 18 ++++-- src/create_torrent.cpp | 7 ++- src/entry.cpp | 63 ++++++++++++++++++++ src/torrent.cpp | 7 ++- test/Jamfile | 1 + test/Makefile.am | 2 + test/test_bencoding.cpp | 102 +++++++++++++++++++-------------- test/test_create_torrent.cpp | 67 ++++++++++++++++++++++ 10 files changed, 220 insertions(+), 52 deletions(-) create mode 100644 test/test_create_torrent.cpp diff --git a/ChangeLog b/ChangeLog index ea7c5e6f6..eeaa76991 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,6 @@ 1.1.1 release + * added a new "preformatted" type to bencode entry variant type * improved Socks5 support and test coverage * fix set_settings in python binding * Added missing alert categories in python binding diff --git a/include/libtorrent/bencode.hpp b/include/libtorrent/bencode.hpp index 44a3e48f1..7e3a7848d 100644 --- a/include/libtorrent/bencode.hpp +++ b/include/libtorrent/bencode.hpp @@ -210,6 +210,10 @@ namespace libtorrent write_char(out, 'e'); ret += 2; break; + case entry::preformatted_t: + std::copy(e.preformatted().begin(), e.preformatted().end(), out); + ret += e.preformatted().size(); + break; case entry::undefined_t: // trying to encode a structure with uninitialized values! // TORRENT_ASSERT_VAL(false, e.type()); diff --git a/include/libtorrent/entry.hpp b/include/libtorrent/entry.hpp index effb8f78f..e365fd5e6 100644 --- a/include/libtorrent/entry.hpp +++ b/include/libtorrent/entry.hpp @@ -108,6 +108,7 @@ namespace libtorrent typedef std::string string_type; typedef std::list list_type; typedef boost::int64_t integer_type; + typedef std::vector preformatted_type; // the types an entry can have enum data_type @@ -116,7 +117,8 @@ namespace libtorrent string_t, list_t, dictionary_t, - undefined_t + undefined_t, + preformatted_t }; // returns the concrete type of the entry @@ -129,6 +131,7 @@ namespace libtorrent entry(string_type const&); entry(list_type const&); entry(integer_type const&); + entry(preformatted_type const&); // construct an empty entry of the specified type. // see data_type enum. @@ -158,6 +161,7 @@ namespace libtorrent void operator=(string_type const&); void operator=(list_type const&); void operator=(integer_type const&); + void operator=(preformatted_type const&); // The ``integer()``, ``string()``, ``list()`` and ``dict()`` functions // are accessors that return the respective type. If the ``entry`` object @@ -214,6 +218,8 @@ namespace libtorrent const list_type& list() const; dictionary_type& dict(); const dictionary_type& dict() const; + preformatted_type& preformatted(); + const preformatted_type& preformatted() const; // swaps the content of *this* with ``e``. void swap(entry& e); @@ -266,17 +272,19 @@ namespace libtorrent // assumes sizeof(map) == sizeof(map) // and sizeof(list) == sizeof(list) enum { union_size - = max4) + = max5) , sizeof(std::map) , sizeof(string_type) - , sizeof(integer_type)>::value + , sizeof(integer_type) + , sizeof(preformatted_type)>::value }; #else enum { union_size - = max4::value + , sizeof(integer_type) + , sizeof(preformatted_type)>::value }; #endif integer_type data[(union_size + sizeof(integer_type) - 1) diff --git a/src/create_torrent.cpp b/src/create_torrent.cpp index 091a9bb1a..8343054dd 100644 --- a/src/create_torrent.cpp +++ b/src/create_torrent.cpp @@ -416,7 +416,9 @@ namespace libtorrent m_piece_hash.resize(m_files.num_pieces()); for (int i = 0; i < num_pieces(); ++i) set_hash(i, ti.hash_for_piece(i)); - m_info_dict = bdecode(&ti.metadata()[0], &ti.metadata()[0] + ti.metadata_size()); + boost::shared_array const info = ti.metadata(); + int const size = ti.metadata_size(); + m_info_dict.preformatted().assign(&info[0], &info[0] + size); m_info_hash = ti.info_hash(); } @@ -508,7 +510,8 @@ namespace libtorrent } entry& info = dict["info"]; - if (m_info_dict.type() == entry::dictionary_t) + if (m_info_dict.type() == entry::dictionary_t + || m_info_dict.type() == entry::preformatted_t) { info = m_info_dict; return dict; diff --git a/src/entry.cpp b/src/entry.cpp index 97f08d5a9..d47eeb3af 100644 --- a/src/entry.cpp +++ b/src/entry.cpp @@ -158,6 +158,7 @@ namespace libtorrent void entry::operator=(const entry& e) { + if (&e == this) return; destruct(); copy(e); } @@ -254,6 +255,29 @@ namespace libtorrent return *reinterpret_cast(data); } + entry::preformatted_type& entry::preformatted() + { + if (m_type == undefined_t) construct(preformatted_t); +#ifndef BOOST_NO_EXCEPTIONS + if (m_type != preformatted_t) throw_type_error(); +#elif defined TORRENT_DEBUG + TORRENT_ASSERT(m_type_queried); +#endif + TORRENT_ASSERT(m_type == preformatted_t); + return *reinterpret_cast(data); + } + + entry::preformatted_type const& entry::preformatted() const + { +#ifndef BOOST_NO_EXCEPTIONS + if (m_type != preformatted_t) throw_type_error(); +#elif defined TORRENT_DEBUG + TORRENT_ASSERT(m_type_queried); +#endif + TORRENT_ASSERT(m_type == preformatted_t); + return *reinterpret_cast(data); + } + entry::entry() : m_type(undefined_t) { @@ -320,6 +344,16 @@ namespace libtorrent m_type = int_t; } + entry::entry(preformatted_type const& v) + : m_type(undefined_t) + { +#ifdef TORRENT_DEBUG + m_type_queried = true; +#endif + new(data) preformatted_type(v); + m_type = preformatted_t; + } + // convert a bdecode_node into an old skool entry void entry::operator=(bdecode_node const& e) { @@ -396,6 +430,16 @@ namespace libtorrent } #endif + void entry::operator=(preformatted_type const& v) + { + destruct(); + new(data) preformatted_type(v); + m_type = preformatted_t; +#ifdef TORRENT_DEBUG + m_type_queried = true; +#endif + } + void entry::operator=(dictionary_type const& v) { destruct(); @@ -450,6 +494,8 @@ namespace libtorrent return list() == e.list(); case dictionary_t: return dict() == e.dict(); + case preformatted_t: + return preformatted() == e.preformatted(); default: TORRENT_ASSERT(m_type == undefined_t); return true; @@ -474,6 +520,9 @@ namespace libtorrent break; case undefined_t: break; + case preformatted_t: + new (data) preformatted_type; + break; } m_type = t; #ifdef TORRENT_DEBUG @@ -499,6 +548,10 @@ namespace libtorrent break; case undefined_t: TORRENT_ASSERT(e.type() == undefined_t); + break; + case preformatted_t: + new (data) preformatted_type(e.preformatted()); + break; } m_type = e.type(); #ifdef TORRENT_DEBUG @@ -522,6 +575,9 @@ namespace libtorrent case dictionary_t: call_destructor(reinterpret_cast(data)); break; + case preformatted_t: + call_destructor(reinterpret_cast(data)); + break; default: TORRENT_ASSERT(m_type == undefined_t); break; @@ -572,6 +628,10 @@ namespace libtorrent std::swap(*reinterpret_cast(data) , *reinterpret_cast(e.data)); break; + case preformatted_t: + std::swap(*reinterpret_cast(data) + , *reinterpret_cast(e.data)); + break; default: break; } @@ -664,6 +724,9 @@ namespace libtorrent i->second.to_string_impl(out, indent+2); } } break; + case preformatted_t: + out += "\n"; + break; case undefined_t: default: out += "\n"; diff --git a/src/torrent.cpp b/src/torrent.cpp index 83dc1781d..359b58368 100644 --- a/src/torrent.cpp +++ b/src/torrent.cpp @@ -7188,8 +7188,11 @@ namespace libtorrent if (valid_metadata()) { if (m_magnet_link || (m_save_resume_flags & torrent_handle::save_info_dict)) - ret["info"] = bdecode(&torrent_file().metadata()[0] - , &torrent_file().metadata()[0] + torrent_file().metadata_size()); + { + boost::shared_array const info = torrent_file().metadata(); + int const size = torrent_file().metadata_size(); + ret["info"].preformatted().assign(&info[0], &info[0] + size); + } } // blocks per piece diff --git a/test/Jamfile b/test/Jamfile index 4d89d2e09..89bce82e5 100644 --- a/test/Jamfile +++ b/test/Jamfile @@ -108,6 +108,7 @@ feature.compose valgrind : "valgrind --tool=memcheck test-suite libtorrent : [ run test_primitives.cpp + test_create_torrent.cpp test_packet_buffer.cpp test_timestamp_history.cpp test_sha1_hash.cpp diff --git a/test/Makefile.am b/test/Makefile.am index 2ad4f05b1..ac8899f29 100644 --- a/test/Makefile.am +++ b/test/Makefile.am @@ -26,6 +26,7 @@ test_programs = \ test_torrent \ test_tracker \ test_transfer \ + test_create_torrent \ enum_if \ test_utp \ test_session \ @@ -206,6 +207,7 @@ test_ssl_SOURCES = test_ssl.cpp test_torrent_SOURCES = test_torrent.cpp test_tracker_SOURCES = test_tracker.cpp test_transfer_SOURCES = test_transfer.cpp +test_create_torrent_SOURCES = test_create_torrent.cpp enum_if_SOURCES = enum_if.cpp test_utp_SOURCES = test_utp.cpp test_session_SOURCES = test_session.cpp diff --git a/test/test_bencoding.cpp b/test/test_bencoding.cpp index 7d1cc2c8f..1502437fb 100644 --- a/test/test_bencoding.cpp +++ b/test/test_bencoding.cpp @@ -58,54 +58,72 @@ entry decode(std::string const& str) return bdecode(str.begin(), str.end()); } -TORRENT_TEST(bencoding) +TORRENT_TEST(strings) { - // ** strings ** - { - entry e("spam"); - TEST_CHECK(encode(e) == "4:spam"); - TEST_CHECK(decode(encode(e)) == e); - } + entry e("spam"); + TEST_CHECK(encode(e) == "4:spam"); + TEST_CHECK(decode(encode(e)) == e); +} - // ** integers ** - { - entry e(3); - TEST_CHECK(encode(e) == "i3e"); - TEST_CHECK(decode(encode(e)) == e); - } +TORRENT_TEST(integers) +{ + entry e(3); + TEST_CHECK(encode(e) == "i3e"); + TEST_CHECK(decode(encode(e)) == e); +} - { - entry e(-3); - TEST_CHECK(encode(e) == "i-3e"); - TEST_CHECK(decode(encode(e)) == e); - } +TORRENT_TEST(integers2) +{ + entry e(-3); + TEST_CHECK(encode(e) == "i-3e"); + TEST_CHECK(decode(encode(e)) == e); +} - { - entry e(int(0)); - TEST_CHECK(encode(e) == "i0e"); - TEST_CHECK(decode(encode(e)) == e); - } +TORRENT_TEST(integers3) +{ + entry e(int(0)); + TEST_CHECK(encode(e) == "i0e"); + TEST_CHECK(decode(encode(e)) == e); +} - // ** lists ** - { - entry::list_type l; - l.push_back(entry("spam")); - l.push_back(entry("eggs")); - entry e(l); - TEST_CHECK(encode(e) == "l4:spam4:eggse"); - TEST_CHECK(decode(encode(e)) == e); - } +TORRENT_TEST(lists) +{ + entry::list_type l; + l.push_back(entry("spam")); + l.push_back(entry("eggs")); + entry e(l); + TEST_CHECK(encode(e) == "l4:spam4:eggse"); + TEST_CHECK(decode(encode(e)) == e); +} - // ** dictionaries ** - { - entry e(entry::dictionary_t); - e["spam"] = entry("eggs"); - e["cow"] = entry("moo"); - TEST_CHECK(encode(e) == "d3:cow3:moo4:spam4:eggse"); - TEST_CHECK(decode(encode(e)) == e); - } +TORRENT_TEST(dictionaries) +{ + entry e(entry::dictionary_t); + e["spam"] = entry("eggs"); + e["cow"] = entry("moo"); + TEST_CHECK(encode(e) == "d3:cow3:moo4:spam4:eggse"); + TEST_CHECK(decode(encode(e)) == e); +} + +TORRENT_TEST(preformatted) +{ + entry e(entry::preformatted_t); + char const str[] = "foobar"; + e.preformatted().assign(str, str + sizeof(str)-1); + TEST_EQUAL(encode(e), "foobar"); +} + +TORRENT_TEST(preformatted_node) +{ + entry e(entry::dictionary_t); + char const str[] = "foobar"; + e["info"] = entry::preformatted_type(str, str + sizeof(str)-1); + TEST_EQUAL(encode(e), "d4:infofoobare"); +} #ifndef TORRENT_NO_DEPRECATE +TORRENT_TEST(lazy_entry) +{ { char b[] = "i12453e"; lazy_entry e; @@ -609,8 +627,6 @@ TORRENT_TEST(bencoding) printf("%s\n", print_entry(e).c_str()); } } - - -#endif // TORRENT_NO_DEPRECATE } +#endif // TORRENT_NO_DEPRECATE diff --git a/test/test_create_torrent.cpp b/test/test_create_torrent.cpp new file mode 100644 index 000000000..3fb106279 --- /dev/null +++ b/test/test_create_torrent.cpp @@ -0,0 +1,67 @@ +/* + +Copyright (c) 2016, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "test.hpp" + +#include "libtorrent/torrent_info.hpp" +#include "libtorrent/create_torrent.hpp" +#include "libtorrent/bencode.hpp" +#include "libtorrent/aux_/escape_string.hpp" // for convert_path_to_posix +#include +#include + +namespace lt = libtorrent; + +// make sure creating a torrent from an existing handle preserves the +// info-dictionary verbatim, so as to not alter the info-hash +TORRENT_TEST(create_verbatim_torrent) +{ + char const test_torrent[] = "d4:infod4:name6:foobar6:lengthi12345e12:piece lengthi65536e6:pieces20:ababababababababababee"; + + lt::torrent_info info(test_torrent, sizeof(test_torrent) - 1); + + lt::create_torrent t(info); + + std::vector buffer; + lt::bencode(std::back_inserter(buffer), t.generate()); + + // now, make sure the info dictionary was unchanged + buffer.push_back('\0'); + char const* dest_info = std::strstr(&buffer[0], "4:info"); + + TEST_CHECK(dest_info != NULL); + + // +1 and -2 here is to strip the outermost dictionary from the source + // torrent, since create_torrent may have added items next to the info dict + TEST_CHECK(memcmp(dest_info, test_torrent + 1, sizeof(test_torrent)-3) == 0); +} +