string: replace AsciiCaseInsensitiveKeyEqual with CaseInsensitiveEqual
What changed, and why it matters
This commit is a routine code cleanup. It removes a specialized case-insensitive string comparator and hash helper that were no longer needed, and moves a simpler case-insensitive equality function from test-only code into the main utility library. There is no indication this fixes or introduces a security bug.
No security action required. Treat as normal refactoring/review as part of standard code review.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The change reverts an earlier commit (eea38787 from PR #34242) that added AsciiCaseInsensitiveKeyEqual and AsciiCaseInsensitiveHash for use with HTTP headers. Because HTTP headers are no longer stored in an unordered_map, those comparator/hash types are unnecessary. The commit deletes them, removes the test-only CaseInsensitiveEqual implementation in src/test/util/str.cpp, and adds an equivalent CaseInsensitiveEqual(std::string_view, std::string_view) to src/util/strencodings.cpp/.h. Tests and fuzz targets are updated to use the new location. The function behavior is unchanged: locale-independent, ASCII-only lowercase comparison.
Changed components
src/util/strencodings.hsrc/util/strencodings.cppsrc/test/util/str.hsrc/test/util/str.cppsrc/test/util/CMakeLists.txtsrc/test/bech32_tests.cppsrc/test/fuzz/bech32.cppsrc/test/util_string_tests.cppInspect captured patch +39 / −69
diff --git a/src/test/bech32_tests.cpp b/src/test/bech32_tests.cpp
index c9eed385..795510cf 100644
--- a/src/test/bech32_tests.cpp
+++ b/src/test/bech32_tests.cpp
@@ -4,7 +4,7 @@
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include <bech32.h>
-#include <test/util/str.h>
+#include <util/strencodings.h>
#include <boost/test/unit_test.hpp>
diff --git a/src/test/fuzz/bech32.cpp b/src/test/fuzz/bech32.cpp
index 8c43bbbb..ae8e73cb 100644
--- a/src/test/fuzz/bech32.cpp
+++ b/src/test/fuzz/bech32.cpp
@@ -5,7 +5,6 @@
#include <bech32.h>
#include <test/fuzz/fuzz.h>
#include <test/fuzz/FuzzedDataProvider.h>
-#include <test/util/str.h>
#include <util/strencodings.h>
#include <cassert>
diff --git a/src/test/util/CMakeLists.txt b/src/test/util/CMakeLists.txt
index 32396c41..d6864a27 100644
--- a/src/test/util/CMakeLists.txt
+++ b/src/test/util/CMakeLists.txt
@@ -13,7 +13,6 @@ add_library(test_util STATIC EXCLUDE_FROM_ALL
random.cpp
script.cpp
setup_common.cpp
- str.cpp
time.cpp
transaction_utils.cpp
txmempool.cpp
diff --git a/src/test/util/str.cpp b/src/test/util/str.cpp
deleted file mode 100644
index 32a6825d..00000000
--- a/src/test/util/str.cpp
+++ /dev/null
@@ -1,21 +0,0 @@
-// Copyright (c) 2019-present The Bitcoin Core developers
-// Distributed under the MIT software license, see the accompanying
-// file COPYING or http://www.opensource.org/licenses/mit-license.php.
-
-#include <test/util/str.h>
-
-#include <cstdint>
-#include <string>
-
-bool CaseInsensitiveEqual(const std::string& s1, const std::string& s2)
-{
- if (s1.size() != s2.size()) return false;
- for (size_t i = 0; i < s1.size(); ++i) {
- char c1 = s1[i];
- if (c1 >= 'A' && c1 <= 'Z') c1 -= ('A' - 'a');
- char c2 = s2[i];
- if (c2 >= 'A' && c2 <= 'Z') c2 -= ('A' - 'a');
- if (c1 != c2) return false;
- }
- return true;
-}
diff --git a/src/test/util/str.h b/src/test/util/str.h
index 39e2bafc..c9b9d60d 100644
--- a/src/test/util/str.h
+++ b/src/test/util/str.h
@@ -7,8 +7,6 @@
#include <string>
-bool CaseInsensitiveEqual(const std::string& s1, const std::string& s2);
-
/**
* Increment a string. Useful to enumerate all fixed length strings with
* characters in [min_char, max_char].
diff --git a/src/test/util_string_tests.cpp b/src/test/util_string_tests.cpp
index ad54db45..dea45f6f 100644
--- a/src/test/util_string_tests.cpp
+++ b/src/test/util_string_tests.cpp
@@ -4,7 +4,6 @@
#include <util/strencodings.h>
#include <util/string.h>
-#include <vector>
#include <boost/test/unit_test.hpp>
#include <test/util/common.h>
@@ -155,38 +154,27 @@ BOOST_AUTO_TEST_CASE(ConstevalFormatString_NumSpec)
HasReason{"tinyformat: Too many conversion specifiers in format string"});
}
-BOOST_AUTO_TEST_CASE(ascii_case_insensitive_key_equal_test)
+BOOST_AUTO_TEST_CASE(case_insensitive_equal_test)
{
- AsciiCaseInsensitiveKeyEqual cmp;
- BOOST_CHECK(!cmp("A", "B"));
- BOOST_CHECK(!cmp("A", "b"));
- BOOST_CHECK(!cmp("a", "B"));
- BOOST_CHECK(!cmp("B", "A"));
- BOOST_CHECK(!cmp("B", "a"));
- BOOST_CHECK(!cmp("b", "A"));
- BOOST_CHECK(!cmp("A", "AA"));
- BOOST_CHECK(cmp("A-A", "a-a"));
- BOOST_CHECK(cmp("A", "A"));
- BOOST_CHECK(cmp("A", "a"));
- BOOST_CHECK(cmp("a", "a"));
- BOOST_CHECK(cmp("B", "b"));
- BOOST_CHECK(cmp("ab", "aB"));
- BOOST_CHECK(cmp("Ab", "aB"));
- BOOST_CHECK(cmp("AB", "ab"));
+ BOOST_CHECK(!CaseInsensitiveEqual("A", "B"));
+ BOOST_CHECK(!CaseInsensitiveEqual("A", "b"));
+ BOOST_CHECK(!CaseInsensitiveEqual("a", "B"));
+ BOOST_CHECK(!CaseInsensitiveEqual("B", "A"));
+ BOOST_CHECK(!CaseInsensitiveEqual("B", "a"));
+ BOOST_CHECK(!CaseInsensitiveEqual("b", "A"));
+ BOOST_CHECK(!CaseInsensitiveEqual("A", "AA"));
+ BOOST_CHECK(CaseInsensitiveEqual("A-A", "a-a"));
+ BOOST_CHECK(CaseInsensitiveEqual("A", "A"));
+ BOOST_CHECK(CaseInsensitiveEqual("A", "a"));
+ BOOST_CHECK(CaseInsensitiveEqual("a", "a"));
+ BOOST_CHECK(CaseInsensitiveEqual("B", "b"));
+ BOOST_CHECK(CaseInsensitiveEqual("ab", "aB"));
+ BOOST_CHECK(CaseInsensitiveEqual("Ab", "aB"));
+ BOOST_CHECK(CaseInsensitiveEqual("AB", "ab"));
// Use a character with value > 127
// to ensure we don't trigger implicit-integer-sign-change
- BOOST_CHECK(!cmp("a", "\xe4"));
-}
-
-BOOST_AUTO_TEST_CASE(ascii_case_insensitive_hash_test)
-{
- AsciiCaseInsensitiveHash hsh;
- BOOST_CHECK_NE(hsh("A"), hsh("B"));
- BOOST_CHECK_NE(hsh("AA"), hsh("A"));
- BOOST_CHECK_EQUAL(hsh("A"), hsh("a"));
- BOOST_CHECK_EQUAL(hsh("Ab"), hsh("aB"));
- BOOST_CHECK_EQUAL(hsh("A\xfe"), hsh("a\xfe"));
+ BOOST_CHECK(!CaseInsensitiveEqual("a", "\xe4"));
}
BOOST_AUTO_TEST_CASE(line_reader_test)
diff --git a/src/util/strencodings.cpp b/src/util/strencodings.cpp
index e64b677f..5edc686c 100644
--- a/src/util/strencodings.cpp
+++ b/src/util/strencodings.cpp
@@ -427,3 +427,16 @@ std::optional<uint64_t> ParseByteUnits(std::string_view str, ByteUnit default_mu
}
return *parsed_num * unit_amount;
}
+
+bool CaseInsensitiveEqual(std::string_view s1, std::string_view s2)
+{
+ if (s1.size() != s2.size()) return false;
+ for (size_t i = 0; i < s1.size(); ++i) {
+ char c1 = s1[i];
+ if (c1 >= 'A' && c1 <= 'Z') c1 -= ('A' - 'a');
+ char c2 = s2[i];
+ if (c2 >= 'A' && c2 <= 'Z') c2 -= ('A' - 'a');
+ if (c1 != c2) return false;
+ }
+ return true;
+}
diff --git a/src/util/strencodings.h b/src/util/strencodings.h
index e5279722..30770e6d 100644
--- a/src/util/strencodings.h
+++ b/src/util/strencodings.h
@@ -325,6 +325,14 @@ std::string Capitalize(std::string str);
*/
std::optional<uint64_t> ParseByteUnits(std::string_view str, ByteUnit default_multiplier);
+/**
+ * Locale-independent, ASCII-only comparator
+ * @param[in] s1 a string to compare
+ * @param[in] s2 another string to compare
+ * @returns true if s1 == s2 when both strings are converted to lowercase
+ */
+bool CaseInsensitiveEqual(std::string_view s1, std::string_view s2);
+
namespace util {
/** consteval version of HexDigit() without the lookup table. */
consteval uint8_t ConstevalHexDigit(const char c)
@@ -353,20 +361,6 @@ struct Hex {
};
} // namespace detail
-struct AsciiCaseInsensitiveKeyEqual {
- bool operator()(std::string_view s1, std::string_view s2) const
- {
- return ToLower(s1) == ToLower(s2);
- }
-};
-
-struct AsciiCaseInsensitiveHash {
- size_t operator()(std::string_view s) const
- {
- return std::hash<std::string>{}(ToLower(s));
- }
-};
-
/**
* ""_hex is a compile-time user-defined literal returning a
* `std::array<std::byte>`, equivalent to ParseHex(). Variants provided:
Why this scored 13/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.