diff --git a/AUTHORS b/AUTHORS index a02387442..7c2005c15 100644 --- a/AUTHORS +++ b/AUTHORS @@ -14,6 +14,8 @@ Stas Khirman Ryan Norton Andrew Resch +Thanks to (github user) nervoir for bug reports + Building and maintainance of the autotools scripts: Michael Wojciechowski Peter Koeleman diff --git a/ChangeLog b/ChangeLog index d5d7d8f20..938c6eef8 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,4 @@ + * fix infinite loop when parsing maliciously crafted torrents * fix invalid read in parse_int in bdecoder * fix issue with very long tracker- and web seed URLs * don't attempt to create empty files on startup, if they already exist diff --git a/include/libtorrent/string_util.hpp b/include/libtorrent/string_util.hpp index e8d90d427..6babe8147 100644 --- a/include/libtorrent/string_util.hpp +++ b/include/libtorrent/string_util.hpp @@ -103,6 +103,18 @@ namespace libtorrent bool is_i2p_url(std::string const& url); #endif + + // this can be used as the hash function in std::unordered_* + struct TORRENT_EXTRA_EXPORT string_hash_no_case + { size_t operator()(std::string const& s) const; }; + + // these can be used as the comparison functions in std::map and std::set + struct TORRENT_EXTRA_EXPORT string_eq_no_case + { bool operator()(std::string const& lhs, std::string const& rhs) const; }; + + struct TORRENT_EXTRA_EXPORT string_less_no_case + { bool operator()(std::string const& lhs, std::string const& rhs) const; }; + } #endif diff --git a/src/string_util.cpp b/src/string_util.cpp index 5cfca912d..00d90be18 100644 --- a/src/string_util.cpp +++ b/src/string_util.cpp @@ -277,5 +277,48 @@ namespace libtorrent #endif + std::size_t string_hash_no_case::operator()(std::string const& s) const + { + size_t ret = 5381; + for (std::string::const_iterator i = s.begin(); i != s.end(); ++i) + ret = (ret * 33) ^ to_lower(*i); + return ret; + } + + bool string_eq_no_case::operator()(std::string const& lhs, std::string const& rhs) const + { + if (lhs.size() != rhs.size()) return false; + + std::string::const_iterator s1 = lhs.begin(); + std::string::const_iterator s2 = rhs.begin(); + + while (s1 != lhs.end() && s2 != rhs.end()) + { + if (to_lower(*s1) != to_lower(*s2)) return false; + ++s1; + ++s2; + } + return true; + } + + bool string_less_no_case::operator()(std::string const& lhs, std::string const& rhs) const + { + std::string::const_iterator s1 = lhs.begin(); + std::string::const_iterator s2 = rhs.begin(); + + while (s1 != lhs.end() && s2 != rhs.end()) + { + char const c1 = to_lower(*s1); + char const c2 = to_lower(*s2); + if (c1 < c2) return true; + if (c1 > c2) return false; + ++s1; + ++s2; + } + + // this is the tie-breaker + return lhs.size() < rhs.size(); + } + } diff --git a/src/torrent_info.cpp b/src/torrent_info.cpp index 92053c745..a7124226a 100644 --- a/src/torrent_info.cpp +++ b/src/torrent_info.cpp @@ -497,65 +497,6 @@ namespace libtorrent return true; } -#if TORRENT_HAS_BOOST_UNORDERED - struct string_hash_no_case - { - size_t operator()(std::string const& s) const - { - char const* s1 = s.c_str(); - size_t ret = 5381; - int c; - - while ((c = *s1++)) - ret = (ret * 33) ^ to_lower(c); - - return ret; - } - }; - - struct string_eq_no_case - { - bool operator()(std::string const& lhs, std::string const& rhs) const - { - char c1, c2; - char const* s1 = lhs.c_str(); - char const* s2 = rhs.c_str(); - - while (*s1 != 0 && *s2 != 0) - { - c1 = to_lower(*s1); - c2 = to_lower(*s2); - if (c1 != c2) return false; - ++s1; - ++s2; - } - return *s1 == *s2; - } - }; - -#else - struct string_less_no_case - { - bool operator()(std::string const& lhs, std::string const& rhs) const - { - char c1, c2; - char const* s1 = lhs.c_str(); - char const* s2 = rhs.c_str(); - - while (*s1 != 0 || *s2 != 0) - { - c1 = to_lower(*s1); - c2 = to_lower(*s2); - if (c1 < c2) return true; - if (c1 > c2) return false; - ++s1; - ++s2; - } - return false; - } - }; -#endif - // root_dir is the name of the torrent, unless this is a single file // torrent, in which case it's empty. bool extract_files(bdecode_node const& list, file_storage& target diff --git a/test/test_string.cpp b/test/test_string.cpp index 6c74457dd..8c5e30822 100644 --- a/test/test_string.cpp +++ b/test/test_string.cpp @@ -310,3 +310,70 @@ TORRENT_TEST(string) , convert_to_native("foo") + convert_to_native("bar")); } +TORRENT_TEST(string_hash_no_case) +{ + string_hash_no_case hsh; + + // make sure different strings yield different hashes + TEST_CHECK(hsh("ab") != hsh("ba")); + + // make sure case is ignored + TEST_EQUAL(hsh("Ab"), hsh("ab")); + TEST_EQUAL(hsh("Ab"), hsh("aB")); + + // make sure zeroes in strings are supported + TEST_CHECK(hsh(std::string("\0a", 2)) != hsh(std::string("\0b", 2))); + TEST_EQUAL(hsh(std::string("\0a", 2)), hsh(std::string("\0a", 2))); +} + +TORRENT_TEST(string_eq_no_case) +{ + string_eq_no_case cmp; + TEST_CHECK(cmp("ab", "ba") == false); + TEST_CHECK(cmp("", "")); + TEST_CHECK(cmp("abc", "abc")); + + // make sure different lengths are correctly treated as different + TEST_CHECK(cmp("abc", "ab") == false); + + // make sure case is ignored + TEST_CHECK(cmp("Ab", "ab")); + TEST_CHECK(cmp("Ab", "aB")); + + // make sure zeros are supported + TEST_CHECK(cmp(std::string("\0a", 2), std::string("\0b", 2)) == false); + TEST_CHECK(cmp(std::string("\0a", 2), std::string("\0a", 2))); +} + +TORRENT_TEST(string_less_no_case) +{ + string_less_no_case cmp; + // ab < ba + TEST_CHECK(cmp("ab", "ba")); + TEST_CHECK(cmp("ba", "ab") == false); + + TEST_CHECK(cmp("", "") == false); + TEST_CHECK(cmp("", "a")); + TEST_CHECK(cmp("abc", "abc") == false); + + // shorter strings come before longer ones + TEST_CHECK(cmp("abc", "ab") == false); + TEST_CHECK(cmp("ab", "abc")); + + // make sure case is ignored + TEST_CHECK(cmp("Ab", "ba")); + TEST_CHECK(cmp("Ba", "ab") == false); + + TEST_CHECK(cmp("", "") == false); + TEST_CHECK(cmp("", "a")); + TEST_CHECK(cmp("abc", "Abc") == false); + + // shorter strings come before longer ones + TEST_CHECK(cmp("Abc", "ab") == false); + TEST_CHECK(cmp("Ab", "abc")); + + // make sure zeros are supported + TEST_CHECK(cmp(std::string("\0a", 2), std::string("\0b", 2))); + TEST_CHECK(cmp(std::string("\0a", 2), std::string("\0a", 2)) == false); +} +