fix infinite loop when parsing torrents whose filenames have zeroes. #2247

This commit is contained in:
arvidn 2017-08-15 11:59:13 +02:00 committed by Arvid Norberg
parent fcb9c7b6f3
commit b70d3efba9
6 changed files with 125 additions and 59 deletions

View File

@ -14,6 +14,8 @@ Stas Khirman
Ryan Norton Ryan Norton
Andrew Resch Andrew Resch
Thanks to (github user) nervoir for bug reports
Building and maintainance of the autotools scripts: Building and maintainance of the autotools scripts:
Michael Wojciechowski Michael Wojciechowski
Peter Koeleman Peter Koeleman

View File

@ -1,3 +1,4 @@
* fix infinite loop when parsing maliciously crafted torrents
* fix invalid read in parse_int in bdecoder * fix invalid read in parse_int in bdecoder
* fix issue with very long tracker- and web seed URLs * fix issue with very long tracker- and web seed URLs
* don't attempt to create empty files on startup, if they already exist * don't attempt to create empty files on startup, if they already exist

View File

@ -103,6 +103,18 @@ namespace libtorrent
bool is_i2p_url(std::string const& url); bool is_i2p_url(std::string const& url);
#endif #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 #endif

View File

@ -277,5 +277,48 @@ namespace libtorrent
#endif #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();
}
} }

View File

@ -497,65 +497,6 @@ namespace libtorrent
return true; 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 // root_dir is the name of the torrent, unless this is a single file
// torrent, in which case it's empty. // torrent, in which case it's empty.
bool extract_files(bdecode_node const& list, file_storage& target bool extract_files(bdecode_node const& list, file_storage& target

View File

@ -310,3 +310,70 @@ TORRENT_TEST(string)
, convert_to_native("foo") + convert_to_native("bar")); , 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);
}