diff --git a/ChangeLog b/ChangeLog index 8646b6fd0..4667a6b20 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,6 @@ 1.1.1 release + * fix filename escaping when repairing torrents with broken web seeds * fix bug where file_completed_alert would not be posted unless file_progress had been queries by the client * move files one-by-one when moving storage for a torrent diff --git a/simulation/Jamfile b/simulation/Jamfile index d9f6a45ac..fb8a38ab8 100644 --- a/simulation/Jamfile +++ b/simulation/Jamfile @@ -30,6 +30,7 @@ alias libtorrent-sims : [ run test_optimistic_unchoke.cpp ] [ run test_transfer.cpp ] [ run test_http_connection.cpp ] + [ run test_web_seed.cpp ] [ run test_auto_manage.cpp ] [ run test_torrent_status.cpp ] [ run test_swarm.cpp ] diff --git a/simulation/test_web_seed.cpp b/simulation/test_web_seed.cpp new file mode 100644 index 000000000..1468f7db6 --- /dev/null +++ b/simulation/test_web_seed.cpp @@ -0,0 +1,164 @@ +/* + +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 "libtorrent/session.hpp" +#include "libtorrent/settings_pack.hpp" +#include "libtorrent/deadline_timer.hpp" +#include "libtorrent/torrent_info.hpp" +#include "simulator/http_server.hpp" +#include "settings.hpp" +#include "libtorrent/create_torrent.hpp" +#include "simulator/simulator.hpp" +#include "setup_swarm.hpp" +#include "utils.hpp" +#include "simulator/utils.hpp" +#include + +using namespace sim; +using namespace libtorrent; + +namespace lt = libtorrent; + +std::unique_ptr make_io_service(sim::simulation& sim, int i) +{ + char ep[30]; + snprintf(ep, sizeof(ep), "50.0.%d.%d", (i + 1) >> 8, (i + 1) & 0xff); + return std::unique_ptr(new sim::asio::io_service( + sim, address_v4::from_string(ep))); +} + +boost::shared_ptr create_torrent(file_storage& fs) +{ + int const piece_size = 0x4000; + libtorrent::create_torrent t(fs, piece_size); + + std::vector piece(piece_size); + for (int i = 0; i < int(piece.size()); ++i) + piece[i] = (i % 26) + 'A'; + + // calculate the hash for all pieces + int const num = t.num_pieces(); + sha1_hash ph = hasher(&piece[0], piece.size()).final(); + for (int i = 0; i < num; ++i) + t.set_hash(i, ph); + + std::vector tmp; + std::back_insert_iterator > out(tmp); + + entry tor = t.generate(); + + bencode(out, tor); + error_code ec; + return boost::make_shared( + &tmp[0], tmp.size(), boost::ref(ec), 0); +} +// this is the general template for these tests. create the session with custom +// settings (Settings), set up the test, by adding torrents with certain +// arguments (Setup), run the test and verify the end state (Test) +template +void run_test(Setup const& setup + , HandleAlerts const& on_alert + , Test const& test) +{ + // setup the simulation + sim::default_config network_cfg; + sim::simulation sim{network_cfg}; + std::unique_ptr ios = make_io_service(sim, 0); + lt::session_proxy zombie; + + lt::settings_pack pack = settings(); + // create session + std::shared_ptr ses = std::make_shared(pack, *ios); + + // set up test, like adding torrents (customization point) + setup(*ses); + + // only monitor alerts for session 0 (the downloader) + print_alerts(*ses, [=](lt::session& ses, lt::alert const* a) { + on_alert(ses, a); + }); + + // set up a timer to fire later, to verify everything we expected to happen + // happened + sim::timer t(sim, lt::seconds(100), [&](boost::system::error_code const& ec) + { + fprintf(stderr, "shutting down\n"); + // shut down + ses->set_alert_notify([] {}); + zombie = ses->abort(); + ses.reset(); + }); + + test(sim, *ses); +} + +TORRENT_TEST(single_file_torrent) +{ + using namespace libtorrent; + bool expected = false; + run_test( + [](lt::session& ses) + { + file_storage fs; + fs.add_file("abc'abc", 0x8000); // this filename will have to be escaped + lt::add_torrent_params params; + params.ti = ::create_torrent(fs); + params.url_seeds.push_back("http://2.2.2.2:8080/"); + params.flags &= ~lt::add_torrent_params::flag_auto_managed; + params.flags &= ~lt::add_torrent_params::flag_paused; + params.save_path = "."; + ses.async_add_torrent(params); + }, + [](lt::session& ses, lt::alert const* alert) { + }, + [&expected](sim::simulation& sim, lt::session& ses) + { + sim::asio::io_service web_server(sim, address_v4::from_string("2.2.2.2")); + // listen on port 8080 + sim::http_server http(web_server, 8080); + + // make sure the requested file is correctly escaped + http.register_handler("/abc%27abc" + , [&expected](std::string method, std::string req + , std::map&) + { + expected = true; + return sim::send_response(404, "Not Found", 0); + }); + + sim.run(); + } + ); + + TEST_CHECK(expected); +} + diff --git a/src/web_peer_connection.cpp b/src/web_peer_connection.cpp index 518dcd636..d5b97ab9a 100644 --- a/src/web_peer_connection.cpp +++ b/src/web_peer_connection.cpp @@ -97,6 +97,37 @@ web_peer_connection::web_peer_connection(peer_connection_args const& pack prefer_contiguous_blocks((std::max)(preferred_size / tor->block_size(), 1)); + boost::shared_ptr t = associated_torrent().lock(); + bool const single_file_request = t->torrent_file().num_files() == 1; + + if (!single_file_request) + { + // handle incorrect .torrent files which are multi-file + // but have web seeds not ending with a slash + if (m_path.empty() || m_path[m_path.size()-1] != '/') m_path += '/'; + if (m_url.empty() || m_url[m_url.size()-1] != '/') m_url += '/'; + } + else + { + // handle .torrent files that don't include the filename in the url + if (m_path.empty()) m_path += '/'; + if (m_path[m_path.size()-1] == '/') + { + std::string const& name = t->torrent_file().name(); + m_path += escape_string(name.c_str(), name.size()); + } + + if (!m_url.empty() && m_url[m_url.size() - 1] == '/') + { + std::string tmp = t->torrent_file().files().file_path(0); +#ifdef TORRENT_WINDOWS + convert_path_to_posix(tmp); +#endif + tmp = escape_path(tmp.c_str(), tmp.size()); + m_url += tmp; + } + } + // we want large blocks as well, so // we can request more bytes at once // this setting will merge adjacent requests @@ -232,37 +263,6 @@ void web_peer_connection::write_request(peer_request const& r) TORRENT_ASSERT(t->valid_metadata()); - bool single_file_request = t->torrent_file().num_files() == 1; - - if (!single_file_request) - { - // handle incorrect .torrent files which are multi-file - // but have web seeds not ending with a slash - if (m_path.empty() || m_path[m_path.size() - 1] != '/') m_path += "/"; - if (m_url.empty() || m_url[m_url.size() - 1] != '/') m_url += "/"; - } - else - { - // handle .torrent files that don't include the filename in the url - if (m_path.empty()) m_path += "/" + t->torrent_file().name(); - else if (m_path[m_path.size() - 1] == '/') - { - std::string tmp = t->torrent_file().files().file_path(0); -#ifdef TORRENT_WINDOWS - convert_path_to_posix(tmp); -#endif - m_path += tmp; - } - else if (!m_url.empty() && m_url[m_url.size() - 1] == '/') - { - std::string tmp = t->torrent_file().files().file_path(0); -#ifdef TORRENT_WINDOWS - convert_path_to_posix(tmp); -#endif - m_url += tmp; - } - } - torrent_info const& info = t->torrent_file(); peer_request req = r; @@ -315,8 +315,9 @@ void web_peer_connection::write_request(peer_request const& r) size -= pr.length; } - int proxy_type = m_settings.get_int(settings_pack::proxy_type); - bool using_proxy = (proxy_type == settings_pack::http + bool const single_file_request = t->torrent_file().num_files() == 1; + int const proxy_type = m_settings.get_int(settings_pack::proxy_type); + bool const using_proxy = (proxy_type == settings_pack::http || proxy_type == settings_pack::http_pw) && !m_ssl; // the number of pad files that have been "requested". In case we _only_ @@ -585,9 +586,8 @@ void web_peer_connection::handle_redirect(int bytes_left) return; } - bool single_file_request = false; - if (!m_path.empty() && m_path[m_path.size() - 1] != '/') - single_file_request = true; + bool const single_file_request = !m_path.empty() + && m_path[m_path.size() - 1] != '/'; // add the redirected url and remove the current one if (!single_file_request)