diff --git a/src/fuzzer/uri.cpp b/src/fuzzer/uri.cpp index 989cbacba86..db9d0f2dcec 100644 --- a/src/fuzzer/uri.cpp +++ b/src/fuzzer/uri.cpp @@ -8,12 +8,12 @@ #include -void fuzz(std::span in) { - if(in.size() > max_fuzzer_input_size) { +void fuzz(std::span input) { + if(input.size() > max_fuzzer_input_size) { return; } try { - Botan::URI::fromAny(std::string(reinterpret_cast(in.data()), in.size())); + Botan::URI::from_any(std::string(reinterpret_cast(input.data()), input.size())); } catch(Botan::Exception& e) {} } diff --git a/src/lib/utils/parsing.cpp b/src/lib/utils/parsing.cpp index c0c2cec00c2..d18c7341a03 100644 --- a/src/lib/utils/parsing.cpp +++ b/src/lib/utils/parsing.cpp @@ -364,4 +364,63 @@ bool host_wildcard_match(std::string_view issued_, std::string_view host_) { return true; } +std::string check_and_canonicalize_dns_name(std::string_view name) { + if(name.size() > 255) { + throw Decoding_Error("DNS name exceeds maximum allowed length"); + } + + if(name.empty()) { + throw Decoding_Error("DNS name cannot be empty"); + } + + if(name.starts_with(".")) { + throw Decoding_Error("DNS name cannot start with a dot"); + } + + /* + * Table mapping uppercase to lowercase and only including values for valid DNS names + * namely A-Z, a-z, 0-9, hypen, and dot, plus '*' for wildcarding. + */ + // clang-format off + constexpr uint8_t DNS_CHAR_MAPPING[128] = { + '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', + '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', + '\0', '\0', '\0', '\0', '*', '\0', '\0', '-', '.', '\0', '0', '1', '2', '3', '4', '5', '6', '7', '8', + '9', '\0', '\0', '\0', '\0', '\0', '\0', '\0', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', + 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '\0', '\0', '\0', '\0', + '\0', '\0', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', + 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '\0', '\0', '\0', '\0', '\0', + }; + // clang-format on + + std::string canon; + canon.reserve(name.size()); + + for(size_t i = 0; i != name.size(); ++i) { + char c = name[i]; + + if(c == '.') { + if(name[i - 1] == '.') { + throw Decoding_Error("DNS name contains sequential period chars"); + } + if(i == name.size() - 1) { + throw Decoding_Error("DNS name cannot end in a period"); + } + } + + const uint8_t cu = static_cast(c); + if(cu >= 128) { + throw Decoding_Error("DNS name must not contain any extended ASCII code points"); + } + const uint8_t mapped = DNS_CHAR_MAPPING[cu]; + if(mapped == 0) { + throw Decoding_Error("DNS name includes invalid character"); + } + // TODO check label lengths + canon.push_back(static_cast(mapped)); + } + + return canon; +} + } // namespace Botan diff --git a/src/lib/utils/parsing.h b/src/lib/utils/parsing.h index dde7ceaaf40..21a4f12e055 100644 --- a/src/lib/utils/parsing.h +++ b/src/lib/utils/parsing.h @@ -97,6 +97,13 @@ std::string tolower_string(std::string_view s); BOTAN_TEST_API bool host_wildcard_match(std::string_view wildcard, std::string_view host); +/** +* If name is a valid DNS name, return it canonicalized +* +* Otherwise throws Decoding_Error +*/ +std::string check_and_canonicalize_dns_name(std::string_view name); + } // namespace Botan #endif diff --git a/src/lib/utils/socket/socket_udp.cpp b/src/lib/utils/socket/socket_udp.cpp index 864f68822ce..5dbb534cbbb 100644 --- a/src/lib/utils/socket/socket_udp.cpp +++ b/src/lib/utils/socket/socket_udp.cpp @@ -331,11 +331,11 @@ std::unique_ptr OS::open_socket_udp(std::string_view hostname, } std::unique_ptr OS::open_socket_udp(std::string_view uri_string, std::chrono::microseconds timeout) { - const auto uri = URI::fromAny(uri_string); - if(uri.port == 0) { + const auto uri = URI::from_any(uri_string); + if(uri.port() == 0) { throw Invalid_Argument("UDP port not specified"); } - return open_socket_udp(uri.host, std::to_string(uri.port), timeout); + return open_socket_udp(uri.host(), std::to_string(uri.port()), timeout); } } // namespace Botan diff --git a/src/lib/utils/socket/uri.cpp b/src/lib/utils/socket/uri.cpp index f594687de72..0b6274a2fcf 100644 --- a/src/lib/utils/socket/uri.cpp +++ b/src/lib/utils/socket/uri.cpp @@ -1,5 +1,6 @@ /* * (C) 2019 Nuno Goncalves +* 2023,2024 Jack Lloyd * * Botan is released under the Simplified BSD License (see license.txt) */ @@ -7,8 +8,8 @@ #include #include - -#include +#include +#include #if defined(BOTAN_TARGET_OS_HAS_SOCKETS) #include @@ -20,146 +21,133 @@ #if defined(BOTAN_TARGET_OS_HAS_SOCKETS) || defined(BOTAN_TARGET_OS_HAS_WINSOCK2) -namespace { +namespace Botan { -constexpr bool isdigit(char ch) { - return ch >= '0' && ch <= '9'; -} +namespace { -bool isDomain(std::string_view domain) { - std::string domain_str(domain); - std::regex re( - R"(^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9])$)"); - std::cmatch m; - return std::regex_match(domain_str.c_str(), m, re); +bool is_domain_name(std::string_view domain) { + try { + check_and_canonicalize_dns_name(domain); + return true; + } catch(Decoding_Error&) { + return false; + } } -bool isIPv4(std::string_view ip) { +bool is_ipv4(std::string_view ip) { std::string ip_str(ip); sockaddr_storage inaddr; return !!inet_pton(AF_INET, ip_str.c_str(), &inaddr); } -bool isIPv6(std::string_view ip) { +bool is_ipv6(std::string_view ip) { std::string ip_str(ip); sockaddr_storage in6addr; return !!inet_pton(AF_INET6, ip_str.c_str(), &in6addr); } + +uint16_t parse_port_number(const char* func_name, std::string_view uri, size_t pos) { + if(pos == std::string::npos || uri.empty()) { + return 0; + } + + BOTAN_ARG_CHECK(pos < uri.size(), "URI invalid port specifier"); + + uint32_t port = 0; + + for(char c : uri.substr(pos + 1)) { + size_t digit = c - '0'; + if(digit >= 10) { + throw Invalid_Argument(fmt("URI::{} invalid port field in {}", func_name, uri)); + } + port = port * 10 + (c - '0'); + if(port > 65535) { + throw Invalid_Argument(fmt("URI::{} invalid port field in {}", func_name, uri)); + } + } + + return static_cast(port); +} + } // namespace -namespace Botan { +URI URI::from_domain(std::string_view uri) { + BOTAN_ARG_CHECK(!uri.empty(), "URI::from_domain empty URI is invalid"); -URI URI::fromDomain(std::string_view uri) { - unsigned port = 0; + uint16_t port = 0; const auto port_pos = uri.find(':'); if(port_pos != std::string::npos) { - for(char c : uri.substr(port_pos + 1)) { - if(!isdigit(c)) { - throw Invalid_Argument("invalid"); - } - port = port * 10 + c - '0'; - if(port > 65535) { - throw Invalid_Argument("invalid"); - } - } + port = parse_port_number("from_domain", uri, port_pos); } const auto domain = uri.substr(0, port_pos); - if(isIPv4(domain)) { - throw Invalid_Argument("invalid"); + if(is_ipv4(domain)) { + throw Invalid_Argument("URI::from_domain domain name should not be IP address"); } - if(!isDomain(domain)) { - throw Invalid_Argument("invalid"); + if(!is_domain_name(domain)) { + throw Invalid_Argument(fmt("URI::from_domain domain name '{}' not valid", domain)); } - return {Type::Domain, domain, uint16_t(port)}; + + return URI(Type::Domain, domain, port); } -URI URI::fromIPv4(std::string_view uri) { - unsigned port = 0; +URI URI::from_ipv4(std::string_view uri) { + BOTAN_ARG_CHECK(!uri.empty(), "URI::from_ipv4 empty URI is invalid"); + const auto port_pos = uri.find(':'); - if(port_pos != std::string::npos) { - for(char c : uri.substr(port_pos + 1)) { - if(!isdigit(c)) { - throw Invalid_Argument("invalid"); - } - port = port * 10 + c - '0'; - if(port > 65535) { - throw Invalid_Argument("invalid"); - } - } - } + const uint16_t port = parse_port_number("from_ipv4", uri, port_pos); const auto ip = uri.substr(0, port_pos); - if(!isIPv4(ip)) { - throw Invalid_Argument("invalid"); + if(!is_ipv4(ip)) { + throw Invalid_Argument("URI::from_ipv4: Invalid IPv4 specifier"); } - return {Type::IPv4, ip, uint16_t(port)}; + return URI(Type::IPv4, ip, port); } -URI URI::fromIPv6(std::string_view uri) { - unsigned port = 0; +URI URI::from_ipv6(std::string_view uri) { + BOTAN_ARG_CHECK(!uri.empty(), "URI::from_ipv6 empty URI is invalid"); + const auto port_pos = uri.find(']'); const bool with_braces = (port_pos != std::string::npos); if((uri[0] == '[') != with_braces) { - throw Invalid_Argument("invalid"); + throw Invalid_Argument("URI::from_ipv6 Invalid IPv6 address with mismatch braces"); } + uint16_t port = 0; if(with_braces && (uri.size() > port_pos + 1)) { if(uri[port_pos + 1] != ':') { - throw Invalid_Argument("invalid"); - } - for(char c : uri.substr(port_pos + 2)) { - if(!isdigit(c)) { - throw Invalid_Argument("invalid"); - } - port = port * 10 + c - '0'; - if(port > 65535) { - throw Invalid_Argument("invalid"); - } + throw Invalid_Argument("URI::from_ipv6 Invalid IPv6 address"); } + + port = parse_port_number("from_ipv6", uri, port_pos + 1); } const auto ip = uri.substr((with_braces ? 1 : 0), port_pos - with_braces); - if(!isIPv6(ip)) { - throw Invalid_Argument("invalid"); + if(!is_ipv6(ip)) { + throw Invalid_Argument("URI::from_ipv6 URI has invalid IPv6 address"); } - return {Type::IPv6, ip, uint16_t(port)}; + return URI(Type::IPv6, ip, port); } -URI URI::fromAny(std::string_view uri) { - bool colon_seen = false; - bool non_number = false; - if(uri[0] == '[') { - return fromIPv6(uri); - } - for(auto c : uri) { - if(c == ':') { - if(colon_seen) //seen two ':' - { - return fromIPv6(uri); - } - colon_seen = true; - } else if(!isdigit(c) && c != '.') { - non_number = true; - } - } - if(!non_number) { - if(isIPv4(uri.substr(0, uri.find(':')))) { - return fromIPv4(uri); - } - } - return fromDomain(uri); +URI URI::from_any(std::string_view uri) { + BOTAN_ARG_CHECK(!uri.empty(), "URI::from_any empty URI is invalid"); + + try { + return URI::from_ipv4(uri); + } catch(Invalid_Argument&) {} + + try { + return URI::from_ipv6(uri); + } catch(Invalid_Argument&) {} + + return URI::from_domain(uri); } std::string URI::to_string() const { - if(type == Type::NotSet) { - throw Invalid_Argument("not set"); - } - - if(port != 0) { - if(type == Type::IPv6) { - return "[" + host + "]:" + std::to_string(port); + if(m_port != 0) { + if(m_type == Type::IPv6) { + return "[" + m_host + "]:" + std::to_string(m_port); } - return host + ":" + std::to_string(port); + return m_host + ":" + std::to_string(m_port); } - return host; + return m_host; } } // namespace Botan @@ -168,19 +156,19 @@ std::string URI::to_string() const { namespace Botan { -URI URI::fromDomain(std::string_view) { +URI URI::from_domain(std::string_view) { throw Not_Implemented("No socket support enabled in build"); } -URI URI::fromIPv4(std::string_view) { +URI URI::from_ipv4(std::string_view) { throw Not_Implemented("No socket support enabled in build"); } -URI URI::fromIPv6(std::string_view) { +URI URI::from_ipv6(std::string_view) { throw Not_Implemented("No socket support enabled in build"); } -URI URI::fromAny(std::string_view) { +URI URI::from_any(std::string_view) { throw Not_Implemented("No socket support enabled in build"); } diff --git a/src/lib/utils/socket/uri.h b/src/lib/utils/socket/uri.h index 1bc412d9052..9d9f7957aa6 100644 --- a/src/lib/utils/socket/uri.h +++ b/src/lib/utils/socket/uri.h @@ -1,5 +1,6 @@ /* * (C) 2019 Nuno Goncalves +* 2023,2024 Jack Lloyd * * Botan is released under the Simplified BSD License (see license.txt) */ @@ -14,28 +15,35 @@ namespace Botan { -struct BOTAN_TEST_API URI { +class BOTAN_TEST_API URI { + public: enum class Type : uint8_t { - NotSet, IPv4, IPv6, Domain, }; - static URI fromAny(std::string_view uri); - static URI fromIPv4(std::string_view uri); - static URI fromIPv6(std::string_view uri); - static URI fromDomain(std::string_view uri); - URI() = default; - URI(Type xtype, std::string_view xhost, unsigned short xport) : type{xtype}, host{xhost}, port{xport} {} + static URI from_any(std::string_view uri); + static URI from_ipv4(std::string_view uri); + static URI from_ipv6(std::string_view uri); + static URI from_domain(std::string_view uri); - bool operator==(const URI& a) const { return type == a.type && host == a.host && port == a.port; } + URI(Type type, std::string_view host, uint16_t port) : m_type(type), m_host(host), m_port(port) {} + + bool operator==(const URI& a) const { return m_type == a.m_type && m_host == a.m_host && m_port == a.m_port; } std::string to_string() const; - const Type type{Type::NotSet}; - const std::string host{}; - const uint16_t port{}; + const std::string& host() const { return m_host; } + + uint16_t port() const { return m_port; } + + Type type() const { return m_type; } + + private: + const Type m_type; + const std::string m_host; + const uint16_t m_port; }; } // namespace Botan diff --git a/src/lib/x509/alt_name.cpp b/src/lib/x509/alt_name.cpp index d835cc856b9..05f9a6fbcec 100644 --- a/src/lib/x509/alt_name.cpp +++ b/src/lib/x509/alt_name.cpp @@ -109,57 +109,6 @@ void AlternativeName::encode_into(DER_Encoder& der) const { der.end_cons(); } -namespace { - -std::string check_and_canonicalize_dns_name(std::string_view name) { - if(name.size() > 255) { - throw Decoding_Error("DNS SAN exceeds maximum allowed length"); - } - - if(name.empty()) { - throw Decoding_Error("DNS SAN cannot be empty"); - } - - if(name.starts_with(".")) { - throw Decoding_Error("DNS SAN cannot start with a dot"); - } - - /* - * Table mapping uppercase to lowercase and only including values for valid DNS names - * namely A-Z, a-z, 0-9, hypen, and dot, plus '*' for wildcarding. - */ - // clang-format off - constexpr uint8_t DNS_CHAR_MAPPING[128] = { - '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', - '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', - '\0', '\0', '\0', '\0', '*', '\0', '\0', '-', '.', '\0', '0', '1', '2', '3', '4', '5', '6', '7', '8', - '9', '\0', '\0', '\0', '\0', '\0', '\0', '\0', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', - 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '\0', '\0', '\0', '\0', - '\0', '\0', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', - 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '\0', '\0', '\0', '\0', '\0', - }; - // clang-format on - - std::string canon; - canon.reserve(name.size()); - - for(char c : name) { - const uint8_t cu = static_cast(c); - if(cu >= 128) { - throw Decoding_Error("DNS name must not contain any extended ASCII code points"); - } - const uint8_t mapped = DNS_CHAR_MAPPING[cu]; - if(mapped == 0) { - throw Decoding_Error("Name in DNS SAN includes invalid character"); - } - canon.push_back(static_cast(mapped)); - } - - return canon; -} - -} // namespace - void AlternativeName::decode_from(BER_Decoder& source) { BER_Decoder names = source.start_sequence(); diff --git a/src/tests/test_uri.cpp b/src/tests/test_uri.cpp index aa5c722e254..afa4664a088 100644 --- a/src/tests/test_uri.cpp +++ b/src/tests/test_uri.cpp @@ -1,5 +1,6 @@ /* * (C) 2019 Nuno Goncalves +* 2023,2024 Jack Lloyd * * Botan is released under the Simplified BSD License (see license.txt) */ @@ -13,40 +14,36 @@ namespace Botan_Tests { class URI_Tests final : public Test { - static void test_uri_ctor(std::vector& results) { - Test::Result result("uri constructors"); - Botan::URI uri(Botan::URI::Type::Domain, "localhost", 80); - result.confirm("type", uri.type == Botan::URI::Type::Domain); - result.test_eq("host", uri.host, "localhost"); - result.confirm("post", uri.port == 80); - results.push_back(result); + private: + static Test::Result test_uri_ctor() { + Test::Result result("URI constructors"); + Botan::URI uri(Botan::URI::Type::Domain, "localhost", 9000); + result.confirm("type", uri.type() == Botan::URI::Type::Domain); + result.test_eq("host", uri.host(), "localhost"); + result.test_eq("post", size_t(uri.port()), 9000); + return result; } - static void test_uri_tostring(std::vector& results) { - Test::Result result("uri to_string"); + static Test::Result test_uri_tostring() { + Test::Result result("URI to_string"); - result.test_eq("domain", Botan::URI(Botan::URI::Type::Domain, "localhost", 80).to_string(), "localhost:80"); - result.test_eq("IPv4", Botan::URI(Botan::URI::Type::IPv4, "192.168.1.1", 80).to_string(), "192.168.1.1:80"); - result.test_eq("IPv6", Botan::URI(Botan::URI::Type::IPv6, "::1", 80).to_string(), "[::1]:80"); + result.test_eq("domain", Botan::URI(Botan::URI::Type::Domain, "localhost", 23).to_string(), "localhost:23"); + result.test_eq("IPv4", Botan::URI(Botan::URI::Type::IPv4, "192.168.1.1", 25).to_string(), "192.168.1.1:25"); + result.test_eq("IPv6", Botan::URI(Botan::URI::Type::IPv6, "::1", 65535).to_string(), "[::1]:65535"); result.test_eq("IPv6 no port", Botan::URI(Botan::URI::Type::IPv6, "::1", 0).to_string(), "::1"); - result.test_throws("invalid", []() { Botan::URI(Botan::URI::Type::NotSet, "", 0).to_string(); }); - results.push_back(result); + return result; } - static void test_uri_factories(std::vector& results) { - Test::Result result("uri factories"); + static Test::Result test_uri_parsing() { + Test::Result result("URI parsing"); struct { std::string uri; std::string host; Botan::URI::Type type; - unsigned port; + uint16_t port; } tests[]{ - {"localhost::80", {}, Botan::URI::Type::NotSet, 0}, - {"localhost:70000", {}, Botan::URI::Type::NotSet, 0}, - {"[::1]:a", {}, Botan::URI::Type::NotSet, 0}, - {"[::1]:70000", {}, Botan::URI::Type::NotSet, 0}, {"localhost:80", "localhost", Botan::URI::Type::Domain, 80}, {"www.example.com", "www.example.com", Botan::URI::Type::Domain, 0}, {"192.168.1.1", "192.168.1.1", Botan::URI::Type::IPv4, 0}, @@ -55,51 +52,73 @@ class URI_Tests final : public Test { }; for(const auto& t : tests) { - auto test_URI = [&result](const Botan::URI& uri, const std::string& host, const unsigned port) { - result.test_eq("host", uri.host, host); - result.confirm("port", uri.port == port); + auto test_URI = [&result](const Botan::URI& uri, const std::string& host, const uint16_t port) { + result.test_eq("host", uri.host(), host); + result.test_int_eq("port", uri.port(), port); }; if(t.type != Botan::URI::Type::IPv4) { - result.test_throws("invalid", [&t]() { Botan::URI::fromIPv4(t.uri); }); + result.test_throws("invalid", [&t]() { Botan::URI::from_ipv4(t.uri); }); } if(t.type != Botan::URI::Type::IPv6) { - result.test_throws("invalid", [&t]() { Botan::URI::fromIPv6(t.uri); }); + result.test_throws("invalid", [&t]() { Botan::URI::from_ipv6(t.uri); }); } if(t.type != Botan::URI::Type::Domain) { - result.test_throws("invalid", [&t]() { Botan::URI::fromDomain(t.uri); }); + result.test_throws("invalid", [&t]() { Botan::URI::from_domain(t.uri); }); } - if(t.type == Botan::URI::Type::NotSet) { - result.test_throws("invalid", [&t]() { Botan::URI::fromAny(t.uri); }); - } else { - const auto any = Botan::URI::fromAny(t.uri); - result.confirm("type any", any.type == t.type); - test_URI(any, t.host, t.port); - if(t.type == Botan::URI::Type::Domain) { - test_URI(Botan::URI::fromDomain(t.uri), t.host, t.port); - } else if(t.type == Botan::URI::Type::IPv4) { - test_URI(Botan::URI::fromIPv4(t.uri), t.host, t.port); - } else if(t.type == Botan::URI::Type::IPv6) { - test_URI(Botan::URI::fromIPv6(t.uri), t.host, t.port); - } + + const auto any = Botan::URI::from_any(t.uri); + result.confirm("from_any type is expected", any.type() == t.type); + test_URI(any, t.host, t.port); + if(t.type == Botan::URI::Type::Domain) { + test_URI(Botan::URI::from_domain(t.uri), t.host, t.port); + } else if(t.type == Botan::URI::Type::IPv4) { + test_URI(Botan::URI::from_ipv4(t.uri), t.host, t.port); + } else if(t.type == Botan::URI::Type::IPv6) { + test_URI(Botan::URI::from_ipv6(t.uri), t.host, t.port); } } //since GCC 4.8 does not support regex this would possibly be acceped as valid domains, //but we just want to test IPv6 parsing, so the test needs to be individual - result.test_throws("invalid IPv6", []() { Botan::URI::fromIPv6("]"); }); - result.test_throws("invalid IPv6", []() { Botan::URI::fromIPv6("[::1]1"); }); + result.test_throws("invalid IPv6", []() { Botan::URI::from_ipv6("]"); }); + result.test_throws("invalid IPv6", []() { Botan::URI::from_ipv6("[::1]1"); }); + + return result; + } - results.push_back(result); + static Test::Result test_uri_parsing_invalid() { + Test::Result result("URI parsing invalid"); + + const std::vector invalid_uris = { + "localhost::80", + "localhost:70000", + "[::1]:a", + "[::1]:70000", + "hello..com", + ".leading.dot", + "yeah.i.thought.so.", + }; + + for(const auto& invalid_uri : invalid_uris) { + try { + auto uri = Botan::URI::from_any(invalid_uri); + result.test_failure("Failed to reject invalid URI '" + invalid_uri + "'"); + } catch(Botan::Invalid_Argument&) { + result.test_success("Rejected invalid URI"); + } + } + return result; } public: std::vector run() override { std::vector results; - test_uri_ctor(results); - test_uri_tostring(results); - test_uri_factories(results); + results.push_back(test_uri_ctor()); + results.push_back(test_uri_tostring()); + results.push_back(test_uri_parsing()); + results.push_back(test_uri_parsing_invalid()); return results; }