From 20621cae0273a3f1a4e667cff5270c8ed8ce35ee Mon Sep 17 00:00:00 2001 From: Arvid Norberg Date: Mon, 3 Dec 2007 06:03:16 +0000 Subject: [PATCH] added magnet-uri functions and a base32 decode function --- Jamfile | 1 + examples/client_test.cpp | 2 + include/libtorrent/escape_string.hpp | 1 + include/libtorrent/magnet_uri.hpp | 59 +++++++++++++++++ src/escape_string.cpp | 75 +++++++++++++++++---- src/magnet_uri.cpp | 99 ++++++++++++++++++++++++++++ test/test_primitives.cpp | 19 ++++++ 7 files changed, 244 insertions(+), 12 deletions(-) create mode 100644 include/libtorrent/magnet_uri.hpp create mode 100644 src/magnet_uri.cpp diff --git a/Jamfile b/Jamfile index 19e52c082..96ad7b9e4 100755 --- a/Jamfile +++ b/Jamfile @@ -243,6 +243,7 @@ SOURCES = disk_io_thread enum_net broadcast_socket + magnet_uri ; KADEMLIA_SOURCES = diff --git a/examples/client_test.cpp b/examples/client_test.cpp index 7cf1485c6..a65b83fb5 100644 --- a/examples/client_test.cpp +++ b/examples/client_test.cpp @@ -62,6 +62,7 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/identify_client.hpp" #include "libtorrent/alert_types.hpp" #include "libtorrent/ip_filter.hpp" +#include "libtorrent/magnet_uri.hpp" using boost::bind; @@ -1049,6 +1050,7 @@ int main(int ac, char* av[]) out << "peers: " << s.num_peers << " " << "seeds: " << s.num_seeds << " " << "distributed copies: " << s.distributed_copies << "\n" + << " magnet-link: " << make_magnet_uri(h) << "\n" << " download: " << esc("32") << (s.download_rate > 0 ? add_suffix(s.download_rate) + "/s ": " ") << esc("0") << "(" << esc("32") << add_suffix(s.total_download) << esc("0") << ") "; } diff --git a/include/libtorrent/escape_string.hpp b/include/libtorrent/escape_string.hpp index 4745560eb..b663d094d 100755 --- a/include/libtorrent/escape_string.hpp +++ b/include/libtorrent/escape_string.hpp @@ -47,6 +47,7 @@ namespace libtorrent TORRENT_EXPORT std::string base64encode(std::string const& s); // encodes a string using the base32 scheme TORRENT_EXPORT std::string base32encode(std::string const& s); + TORRENT_EXPORT std::string base32decode(std::string const& s); TORRENT_EXPORT boost::optional url_has_argument( std::string const& url, std::string argument); diff --git a/include/libtorrent/magnet_uri.hpp b/include/libtorrent/magnet_uri.hpp new file mode 100644 index 000000000..2e947efa0 --- /dev/null +++ b/include/libtorrent/magnet_uri.hpp @@ -0,0 +1,59 @@ +/* + +Copyright (c) 2007, 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. + +*/ + +#ifndef TORRENT_MAGNET_URI_HPP_INCLUDED +#define TORRENT_MAGNET_URI_HPP_INCLUDED + +#include +#include "libtorrent/config.hpp" +#include "libtorrent/torrent_handle.hpp" +#include "libtorrent/session.hpp" +#include + +namespace libtorrent +{ + namespace fs = boost::filesystem; + + struct torrent_handle; + + std::string TORRENT_EXPORT make_magnet_uri(torrent_handle const& handle); + + torrent_handle TORRENT_EXPORT add_magnet_uri(session& ses, std::string const& uri + , fs::path const& save_path + , storage_mode_t storage_mode = storage_mode_sparse + , bool paused = false + , storage_constructor_type sc = default_storage_constructor + , void* userdata = 0); +} + +#endif + diff --git a/src/escape_string.cpp b/src/escape_string.cpp index 7477a81d5..9979a5a46 100755 --- a/src/escape_string.cpp +++ b/src/escape_string.cpp @@ -38,6 +38,7 @@ POSSIBILITY OF SUCH DAMAGE. #include #include #include +#include #include @@ -178,11 +179,8 @@ namespace libtorrent std::fill(inbuf, inbuf+3, 0); // read a chunk of input into inbuf - for (int j = 0; j < available_input; ++j) - { - inbuf[j] = *i; - ++i; - } + std::copy(i, i + available_input, inbuf); + i += available_input; // encode inbuf to outbuf outbuf[0] = (inbuf[0] & 0xfc) >> 2; @@ -223,19 +221,14 @@ namespace libtorrent std::string ret; for (std::string::const_iterator i = s.begin(); i != s.end();) { - // available input is 1,2 or 3 bytes - // since we read 3 bytes at a time at most int available_input = (std::min)(5, (int)std::distance(i, s.end())); // clear input buffer std::fill(inbuf, inbuf+5, 0); // read a chunk of input into inbuf - for (int j = 0; j < available_input; ++j) - { - inbuf[j] = *i; - ++i; - } + std::copy(i, i + available_input, inbuf); + i += available_input; // encode inbuf to outbuf outbuf[0] = (inbuf[0] & 0xf8) >> 3; @@ -263,6 +256,64 @@ namespace libtorrent return ret; } + std::string base32decode(std::string const& s) + { + unsigned char inbuf[8]; + unsigned char outbuf[5]; + + std::string ret; + for (std::string::const_iterator i = s.begin(); i != s.end();) + { + int available_input = (std::min)(8, (int)std::distance(i, s.end())); + + int pad_start = 0; + if (available_input < 8) pad_start = available_input; + + // clear input buffer + std::fill(inbuf, inbuf+8, 0); + for (int j = 0; j < available_input; ++j) + { + char in = std::toupper(*i++); + if (in >= 'A' && in <= 'Z') + inbuf[j] = in - 'A'; + else if (in >= '2' && in <= '7') + inbuf[j] = in - '2' + ('Z' - 'A') + 1; + else if (in == '=') + { + inbuf[j] = 0; + if (pad_start == 0) pad_start = j; + } + else if (in == '1') + inbuf[j] = 'I' - 'A'; + else + return std::string(); + TORRENT_ASSERT(inbuf[j] == (inbuf[j] & 0x1f)); + } + + // decode inbuf to outbuf + outbuf[0] = inbuf[0] << 3; + outbuf[0] |= inbuf[1] >> 2; + outbuf[1] = (inbuf[1] & 0x3) << 6; + outbuf[1] |= inbuf[2] << 1; + outbuf[1] |= (inbuf[3] & 0x10) >> 4; + outbuf[2] = (inbuf[3] & 0x0f) << 4; + outbuf[2] |= (inbuf[4] & 0x1e) >> 1; + outbuf[3] = (inbuf[4] & 0x01) << 7; + outbuf[3] |= (inbuf[5] & 0x1f) << 2; + outbuf[3] |= (inbuf[6] & 0x18) >> 3; + outbuf[4] = (inbuf[6] & 0x07) << 5; + outbuf[4] |= inbuf[7]; + + int input_output_mapping[] = {5, 1, 1, 2, 2, 3, 4, 4, 5}; + int num_out = input_output_mapping[pad_start]; + + // write output + std::copy(outbuf, outbuf + num_out, std::back_inserter(ret)); + } + std::cerr << " base32decode(): " << ret << std::endl; + return ret; + } + boost::optional url_has_argument( std::string const& url, std::string argument) { diff --git a/src/magnet_uri.cpp b/src/magnet_uri.cpp new file mode 100644 index 000000000..d77cb7338 --- /dev/null +++ b/src/magnet_uri.cpp @@ -0,0 +1,99 @@ +/* + +Copyright (c) 2007, 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/magnet_uri.hpp" +#include "libtorrent/session.hpp" +#include "libtorrent/torrent_handle.hpp" +#include "libtorrent/escape_string.hpp" + +#include +#include + +namespace libtorrent +{ + std::string make_magnet_uri(torrent_handle const& handle) + { + std::stringstream ret; + if (!handle.is_valid()) return ret.str(); + + std::string name = handle.name(); + + ret << "magnet:?xt=urn:btih:" << base32encode((char*)handle.info_hash().begin()); + if (!name.empty()) + ret << "&dn=" << escape_string(name.c_str(), name.length()); + torrent_status st = handle.status(); + if (!st.current_tracker.empty()) + { + ret << "&tr=" << escape_string(st.current_tracker.c_str() + , st.current_tracker.length()); + } + else + { + std::vector const& tr = handle.trackers(); + if (!tr.empty()) + { + ret << "&tr=" << escape_string(tr[0].url.c_str() + , tr[0].url.length()); + } + } + return ret.str(); + } + + torrent_handle add_magnet_uri(session& ses, std::string const& uri + , fs::path const& save_path + , storage_mode_t storage_mode + , bool paused + , storage_constructor_type sc + , void* userdata) + { + std::string name; + std::string tracker; + + boost::optional display_name = url_has_argument(uri, "dn"); + if (display_name) name = unescape_string(display_name->c_str()); + boost::optional tracker_string = url_has_argument(uri, "tr"); + if (tracker_string) tracker = unescape_string(tracker_string->c_str()); + + boost::optional btih = url_has_argument(uri, "xt"); + if (!btih) return torrent_handle(); + + if (btih->compare(0, 9, "urn:btih:") != 0) return torrent_handle(); + + sha1_hash info_hash(base32decode(btih->substr(9))); + + return ses.add_torrent(tracker.empty() ? 0 : tracker.c_str(), info_hash + , name.empty() ? 0 : name.c_str(), save_path, entry() + , storage_mode, paused, sc, userdata); + } +} + + diff --git a/test/test_primitives.cpp b/test/test_primitives.cpp index 761998e71..858099cf2 100644 --- a/test/test_primitives.cpp +++ b/test/test_primitives.cpp @@ -101,6 +101,25 @@ int test_main() TEST_CHECK(base32encode("fooba") == "MZXW6YTB"); TEST_CHECK(base32encode("foobar") == "MZXW6YTBOI======"); + TEST_CHECK(base32decode("") == ""); + TEST_CHECK(base32decode("MY======") == "f"); + TEST_CHECK(base32decode("MZXQ====") == "fo"); + TEST_CHECK(base32decode("MZXW6===") == "foo"); + TEST_CHECK(base32decode("MZXW6YQ=") == "foob"); + TEST_CHECK(base32decode("MZXW6YTB") == "fooba"); + TEST_CHECK(base32decode("MZXW6YTBOI======") == "foobar"); + + TEST_CHECK(base32decode("MY") == "f"); + TEST_CHECK(base32decode("MZXW6YQ") == "foob"); + TEST_CHECK(base32decode("MZXW6YTBOI") == "foobar"); + TEST_CHECK(base32decode("mZXw6yTBO1======") == "foobar"); + + std::string test; + for (int i = 0; i < 255; ++i) + test += char(i); + + TEST_CHECK(base32decode(base32encode(test)) == test); + // url_has_argument TEST_CHECK(!url_has_argument("http://127.0.0.1/test", "test"));