Botan 3.9.0
Crypto and TLS for C&
uri.cpp
Go to the documentation of this file.
1/*
2* (C) 2019 Nuno Goncalves <nunojpg@gmail.com>
3* 2023,2024 Jack Lloyd
4*
5* Botan is released under the Simplified BSD License (see license.txt)
6*/
7
8#include <botan/internal/uri.h>
9
10#include <botan/assert.h>
11#include <botan/exceptn.h>
12#include <botan/internal/fmt.h>
13#include <botan/internal/parsing.h>
14#include <botan/internal/target_info.h>
15
16#if defined(BOTAN_TARGET_OS_HAS_SOCKETS)
17 #include <arpa/inet.h>
18 #include <netinet/in.h>
19 #include <sys/socket.h>
20#elif defined(BOTAN_TARGET_OS_HAS_WINSOCK2)
21 #include <ws2tcpip.h>
22#endif
23
24#if defined(BOTAN_TARGET_OS_HAS_SOCKETS) || defined(BOTAN_TARGET_OS_HAS_WINSOCK2)
25
26namespace Botan {
27
28namespace {
29
30bool is_domain_name(std::string_view domain) {
31 try {
33 return true;
34 } catch(Decoding_Error&) {
35 return false;
36 }
37}
38
39bool is_ipv4(std::string_view ip) {
40 std::string ip_str(ip);
41 sockaddr_storage inaddr{};
42
43 // TODO use string_to_ipv4 here
44 // NOLINTNEXTLINE(*-implicit-bool-conversion)
45 return !!inet_pton(AF_INET, ip_str.c_str(), &inaddr);
46}
47
48bool is_ipv6(std::string_view ip) {
49 std::string ip_str(ip);
50 sockaddr_storage in6addr{};
51
52 // TODO use string_to_ipv6 here
53 // NOLINTNEXTLINE(*-implicit-bool-conversion)
54 return !!inet_pton(AF_INET6, ip_str.c_str(), &in6addr);
55}
56
57uint16_t parse_port_number(const char* func_name, std::string_view uri, size_t pos) {
58 if(pos == std::string::npos || uri.empty()) {
59 return 0;
60 }
61
62 BOTAN_ARG_CHECK(pos < uri.size(), "URI invalid port specifier");
63
64 uint32_t port = 0;
65
66 for(char c : uri.substr(pos + 1)) {
67 size_t digit = c - '0';
68 if(digit >= 10) {
69 throw Invalid_Argument(fmt("URI::{} invalid port field in {}", func_name, uri));
70 }
71 port = port * 10 + (c - '0');
72 if(port > 65535) {
73 throw Invalid_Argument(fmt("URI::{} invalid port field in {}", func_name, uri));
74 }
75 }
76
77 return static_cast<uint16_t>(port);
78}
79
80} // namespace
81
82URI URI::from_domain(std::string_view uri) {
83 BOTAN_ARG_CHECK(!uri.empty(), "URI::from_domain empty URI is invalid");
84
85 uint16_t port = 0;
86 const auto port_pos = uri.find(':');
87 if(port_pos != std::string::npos) {
88 port = parse_port_number("from_domain", uri, port_pos);
89 }
90 const auto domain = uri.substr(0, port_pos);
91 if(is_ipv4(domain)) {
92 throw Invalid_Argument("URI::from_domain domain name should not be IP address");
93 }
94 if(!is_domain_name(domain)) {
95 throw Invalid_Argument(fmt("URI::from_domain domain name '{}' not valid", domain));
96 }
97
98 return URI(Type::Domain, domain, port);
99}
100
101URI URI::from_ipv4(std::string_view uri) {
102 BOTAN_ARG_CHECK(!uri.empty(), "URI::from_ipv4 empty URI is invalid");
103
104 const auto port_pos = uri.find(':');
105 const uint16_t port = parse_port_number("from_ipv4", uri, port_pos);
106 const auto ip = uri.substr(0, port_pos);
107 if(!is_ipv4(ip)) {
108 throw Invalid_Argument("URI::from_ipv4: Invalid IPv4 specifier");
109 }
110 return URI(Type::IPv4, ip, port);
111}
112
113URI URI::from_ipv6(std::string_view uri) {
114 BOTAN_ARG_CHECK(!uri.empty(), "URI::from_ipv6 empty URI is invalid");
115
116 const auto port_pos = uri.find(']');
117 const bool with_braces = (port_pos != std::string::npos);
118 if((uri[0] == '[') != with_braces) {
119 throw Invalid_Argument("URI::from_ipv6 Invalid IPv6 address with mismatch braces");
120 }
121
122 uint16_t port = 0;
123 if(with_braces && (uri.size() > port_pos + 1)) {
124 if(uri[port_pos + 1] != ':') {
125 throw Invalid_Argument("URI::from_ipv6 Invalid IPv6 address");
126 }
127
128 port = parse_port_number("from_ipv6", uri, port_pos + 1);
129 }
130 const auto ip = with_braces ? uri.substr(1, port_pos - 1) : uri.substr(0, port_pos);
131
132 if(!is_ipv6(ip)) {
133 throw Invalid_Argument("URI::from_ipv6 URI has invalid IPv6 address");
134 }
135 return URI(Type::IPv6, ip, port);
136}
137
138URI URI::from_any(std::string_view uri) {
139 BOTAN_ARG_CHECK(!uri.empty(), "URI::from_any empty URI is invalid");
140
141 try {
142 return URI::from_ipv4(uri);
143 } catch(Invalid_Argument&) {}
144
145 try {
146 return URI::from_ipv6(uri);
147 } catch(Invalid_Argument&) {}
148
149 return URI::from_domain(uri);
150}
151
152std::string URI::to_string() const {
153 if(m_port != 0) {
154 if(m_type == Type::IPv6) {
155 return "[" + m_host + "]:" + std::to_string(m_port);
156 }
157 return m_host + ":" + std::to_string(m_port);
158 }
159 return m_host;
160}
161
162} // namespace Botan
163
164#else
165
166namespace Botan {
167
168URI URI::from_domain(std::string_view) {
169 throw Not_Implemented("No socket support enabled in build");
170}
171
172URI URI::from_ipv4(std::string_view) {
173 throw Not_Implemented("No socket support enabled in build");
174}
175
176URI URI::from_ipv6(std::string_view) {
177 throw Not_Implemented("No socket support enabled in build");
178}
179
180URI URI::from_any(std::string_view) {
181 throw Not_Implemented("No socket support enabled in build");
182}
183
184} // namespace Botan
185
186#endif
#define BOTAN_ARG_CHECK(expr, msg)
Definition assert.h:33
static URI from_ipv4(std::string_view uri)
Definition uri.cpp:172
static URI from_domain(std::string_view uri)
Definition uri.cpp:168
static URI from_ipv6(std::string_view uri)
Definition uri.cpp:176
uint16_t port() const
Definition uri.h:39
URI(Type type, std::string_view host, uint16_t port)
Definition uri.h:31
static URI from_any(std::string_view uri)
Definition uri.cpp:180
std::string to_string() const
std::string fmt(std::string_view format, const T &... args)
Definition fmt.h:53
std::string check_and_canonicalize_dns_name(std::string_view name)
Definition parsing.cpp:369