diff --git a/src/net/host.h b/src/net/host.h new file mode 100644 index 000000000..21bcff2ae --- /dev/null +++ b/src/net/host.h @@ -0,0 +1,20 @@ +#pragma once + +#include + +namespace net +{ + /*! + * \brief Canonicalize a hostname by converting letters to lowercase. + * + * \param host Hostname to canonicalize in-place. + */ + inline void canonicalize_host(std::string& host) noexcept + { + for (char& c : host) + { + if (u8'A' <= c && c <= u8'Z') + c = static_cast(c - u8'A' + u8'a'); + } + } +} diff --git a/src/net/i2p_address.cpp b/src/net/i2p_address.cpp index e21bc37e2..586432076 100644 --- a/src/net/i2p_address.cpp +++ b/src/net/i2p_address.cpp @@ -36,6 +36,7 @@ #include #include "net/error.h" +#include "net/host.h" #include "serialization/keyvalue_serialization.h" #include "storages/portable_storage.h" #include "string_tools_lexical.h" @@ -105,10 +106,12 @@ namespace net expect i2p_address::make(const boost::string_ref address) { boost::string_ref host = address.substr(0, address.rfind(':')); - MONERO_CHECK(host_check(host)); + std::string normalized_host{host}; + net::canonicalize_host(normalized_host); + MONERO_CHECK(host_check(normalized_host)); static_assert(b32_length + sizeof(tld) == sizeof(i2p_address::host_), "bad internal host size"); - return i2p_address{host}; + return i2p_address{normalized_host}; } bool i2p_address::_load(epee::serialization::portable_storage& src, epee::serialization::section* hparent) diff --git a/src/net/tor_address.cpp b/src/net/tor_address.cpp index 0697bc1cb..c48c5e07a 100644 --- a/src/net/tor_address.cpp +++ b/src/net/tor_address.cpp @@ -40,6 +40,7 @@ #include #include "net/error.h" +#include "net/host.h" #include "serialization/keyvalue_serialization.h" #include "storages/portable_storage.h" #include "string_tools_lexical.h" @@ -134,14 +135,16 @@ namespace net const boost::string_ref port = address.substr(host.size() + (host.size() == address.size() ? 0 : 1)); - MONERO_CHECK(host_check(host)); + std::string normalized_host{host}; + net::canonicalize_host(normalized_host); + MONERO_CHECK(host_check(normalized_host)); std::uint16_t porti = default_port; if (!port.empty() && !epee::string_tools::get_xtype_from_string(porti, std::string{port})) return {net::error::invalid_port}; static_assert(v3_length + sizeof(tld) == sizeof(tor_address::host_), "bad internal host size"); - return tor_address{host, porti}; + return tor_address{normalized_host, porti}; } bool tor_address::_load(epee::serialization::portable_storage& src, epee::serialization::section* hparent) diff --git a/tests/unit_tests/net.cpp b/tests/unit_tests/net.cpp index 4f4399eac..6caaf3ce6 100644 --- a/tests/unit_tests/net.cpp +++ b/tests/unit_tests/net.cpp @@ -57,6 +57,7 @@ #include "crypto/crypto.h" #include "net/dandelionpp.h" #include "net/error.h" +#include "net/host.h" #include "net/i2p_address.h" #include "net/net_utils_base.h" #include "net/socks.h" @@ -68,12 +69,21 @@ #include "serialization/keyvalue_serialization.h" #include "storages/portable_storage.h" +TEST(host, canonicalize_host) +{ + std::string host{"ABCdef123.ONION"}; + net::canonicalize_host(host); + EXPECT_EQ("abcdef123.onion", host); +} + namespace { static constexpr const char v2_onion[] = "xmrto2bturnore26.onion"; static constexpr const char v3_onion[] = "vww6ybal4bd7szmgncyruucpgfkqahzddi37ktceo3ah7ngmcopnpyyd.onion"; + static constexpr const char v3_onion_upper[] = + "VWW6YBAL4BD7SZMGNCYRUUCPGFKQAHZDDI37KTCEO3AH7NGMCOPNPYYD.ONION"; static constexpr const char v3_onion_2[] = "zpv4fa3szgel7vf6jdjeugizdclq2vzkelscs2bhbgnlldzzggcen3ad.onion"; @@ -157,6 +167,10 @@ TEST(tor_address, valid) EXPECT_STREQ(v3_onion, address1->str().c_str()); EXPECT_TRUE(address1->is_blockable()); + const auto uppercase = net::tor_address::make(v3_onion_upper); + ASSERT_TRUE(uppercase.has_value()); + EXPECT_EQ(*address1, *uppercase); + net::tor_address address2{*address1}; EXPECT_EQ(0u, address2.port()); @@ -459,6 +473,8 @@ namespace { static constexpr const char b32_i2p[] = "vww6ybal4bd7szmgncyruucpgfkqahzddi37ktceo3ah7ngmcopn.b32.i2p"; + static constexpr const char b32_i2p_upper[] = + "VWW6YBAL4BD7SZMGNCYRUUCPGFKQAHZDDI37KTCEO3AH7NGMCOPN.B32.I2P"; static constexpr const char b32_i2p_2[] = "xmrto2bturnore26xmrto2bturnore26xmrto2bturnore26xmr2.b32.i2p"; } @@ -527,6 +543,10 @@ TEST(i2p_address, valid) EXPECT_STREQ(b32_i2p, address1->str().c_str()); EXPECT_TRUE(address1->is_blockable()); + const auto uppercase = net::i2p_address::make(b32_i2p_upper); + ASSERT_TRUE(uppercase.has_value()); + EXPECT_EQ(*address1, *uppercase); + net::i2p_address address2{*address1}; EXPECT_EQ(1u, address2.port());