Botan 3.7.1
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
15#if defined(BOTAN_TARGET_OS_HAS_SOCKETS)
16 #include <arpa/inet.h>
17 #include <netinet/in.h>
18 #include <sys/socket.h>
19#elif defined(BOTAN_TARGET_OS_HAS_WINSOCK2)
20 #include <ws2tcpip.h>
21#endif
22
23#if defined(BOTAN_TARGET_OS_HAS_SOCKETS) || defined(BOTAN_TARGET_OS_HAS_WINSOCK2)
24
25namespace Botan {
26
27namespace {
28
29bool is_domain_name(std::string_view domain) {
30 try {
32 return true;
33 } catch(Decoding_Error&) {
34 return false;
35 }
36}
37
38bool is_ipv4(std::string_view ip) {
39 std::string ip_str(ip);
40 sockaddr_storage inaddr;
41 return !!inet_pton(AF_INET, ip_str.c_str(), &inaddr);
42}
43
44bool is_ipv6(std::string_view ip) {
45 std::string ip_str(ip);
46 sockaddr_storage in6addr;
47 return !!inet_pton(AF_INET6, ip_str.c_str(), &in6addr);
48}
49
50uint16_t parse_port_number(const char* func_name, std::string_view uri, size_t pos) {
51 if(pos == std::string::npos || uri.empty()) {
52 return 0;
53 }
54
55 BOTAN_ARG_CHECK(pos < uri.size(), "URI invalid port specifier");
56
57 uint32_t port = 0;
58
59 for(char c : uri.substr(pos + 1)) {
60 size_t digit = c - '0';
61 if(digit >= 10) {
62 throw Invalid_Argument(fmt("URI::{} invalid port field in {}", func_name, uri));
63 }
64 port = port * 10 + (c - '0');
65 if(port > 65535) {
66 throw Invalid_Argument(fmt("URI::{} invalid port field in {}", func_name, uri));
67 }
68 }
69
70 return static_cast<uint16_t>(port);
71}
72
73} // namespace
74
75URI URI::from_domain(std::string_view uri) {
76 BOTAN_ARG_CHECK(!uri.empty(), "URI::from_domain empty URI is invalid");
77
78 uint16_t port = 0;
79 const auto port_pos = uri.find(':');
80 if(port_pos != std::string::npos) {
81 port = parse_port_number("from_domain", uri, port_pos);
82 }
83 const auto domain = uri.substr(0, port_pos);
84 if(is_ipv4(domain)) {
85 throw Invalid_Argument("URI::from_domain domain name should not be IP address");
86 }
87 if(!is_domain_name(domain)) {
88 throw Invalid_Argument(fmt("URI::from_domain domain name '{}' not valid", domain));
89 }
90
91 return URI(Type::Domain, domain, port);
92}
93
94URI URI::from_ipv4(std::string_view uri) {
95 BOTAN_ARG_CHECK(!uri.empty(), "URI::from_ipv4 empty URI is invalid");
96
97 const auto port_pos = uri.find(':');
98 const uint16_t port = parse_port_number("from_ipv4", uri, port_pos);
99 const auto ip = uri.substr(0, port_pos);
100 if(!is_ipv4(ip)) {
101 throw Invalid_Argument("URI::from_ipv4: Invalid IPv4 specifier");
102 }
103 return URI(Type::IPv4, ip, port);
104}
105
106URI URI::from_ipv6(std::string_view uri) {
107 BOTAN_ARG_CHECK(!uri.empty(), "URI::from_ipv6 empty URI is invalid");
108
109 const auto port_pos = uri.find(']');
110 const bool with_braces = (port_pos != std::string::npos);
111 if((uri[0] == '[') != with_braces) {
112 throw Invalid_Argument("URI::from_ipv6 Invalid IPv6 address with mismatch braces");
113 }
114
115 uint16_t port = 0;
116 if(with_braces && (uri.size() > port_pos + 1)) {
117 if(uri[port_pos + 1] != ':') {
118 throw Invalid_Argument("URI::from_ipv6 Invalid IPv6 address");
119 }
120
121 port = parse_port_number("from_ipv6", uri, port_pos + 1);
122 }
123 const auto ip = uri.substr((with_braces ? 1 : 0), port_pos - with_braces);
124 if(!is_ipv6(ip)) {
125 throw Invalid_Argument("URI::from_ipv6 URI has invalid IPv6 address");
126 }
127 return URI(Type::IPv6, ip, port);
128}
129
130URI URI::from_any(std::string_view uri) {
131 BOTAN_ARG_CHECK(!uri.empty(), "URI::from_any empty URI is invalid");
132
133 try {
134 return URI::from_ipv4(uri);
135 } catch(Invalid_Argument&) {}
136
137 try {
138 return URI::from_ipv6(uri);
139 } catch(Invalid_Argument&) {}
140
141 return URI::from_domain(uri);
142}
143
144std::string URI::to_string() const {
145 if(m_port != 0) {
146 if(m_type == Type::IPv6) {
147 return "[" + m_host + "]:" + std::to_string(m_port);
148 }
149 return m_host + ":" + std::to_string(m_port);
150 }
151 return m_host;
152}
153
154} // namespace Botan
155
156#else
157
158namespace Botan {
159
160URI URI::from_domain(std::string_view) {
161 throw Not_Implemented("No socket support enabled in build");
162}
163
164URI URI::from_ipv4(std::string_view) {
165 throw Not_Implemented("No socket support enabled in build");
166}
167
168URI URI::from_ipv6(std::string_view) {
169 throw Not_Implemented("No socket support enabled in build");
170}
171
172URI URI::from_any(std::string_view) {
173 throw Not_Implemented("No socket support enabled in build");
174}
175
176} // namespace Botan
177
178#endif
#define BOTAN_ARG_CHECK(expr, msg)
Definition assert.h:29
static URI from_ipv4(std::string_view uri)
Definition uri.cpp:164
static URI from_domain(std::string_view uri)
Definition uri.cpp:160
static URI from_ipv6(std::string_view uri)
Definition uri.cpp:168
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:172
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:367