From 438f7f9fc438c02504db34704c9fc7d015ca5b2e Mon Sep 17 00:00:00 2001 From: selsta Date: Thu, 21 May 2026 22:47:02 +0200 Subject: [PATCH] net: canonicalize Tor and I2P hostnames --- src/net/host.h | 20 ++++++++++++++++++++ src/net/i2p_address.cpp | 7 +++++-- src/net/tor_address.cpp | 7 +++++-- tests/unit_tests/net.cpp | 20 ++++++++++++++++++++ 4 files changed, 50 insertions(+), 4 deletions(-) create mode 100644 src/net/host.h 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 e9f1b7a97..103843e18 100644 --- a/src/net/tor_address.cpp +++ b/src/net/tor_address.cpp @@ -37,6 +37,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" @@ -111,14 +112,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 acb3b95bf..5c3d9ac9c 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"; } @@ -146,6 +156,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()); @@ -448,6 +462,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"; } @@ -516,6 +532,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());