1 /*
2 * (C) 2019 Nuno Goncalves <nunojpg@gmail.com>
3 *
4 * Botan is released under the Simplified BSD License (see license.txt)
5 */
6
7 #include <botan/internal/uri.h>
8 #include <botan/exceptn.h>
9
10 #include <regex>
11
12 #if defined(BOTAN_TARGET_OS_HAS_SOCKETS)
13 #include <arpa/inet.h>
14 #include <sys/socket.h>
15 #include <netinet/in.h>
16 #elif defined(BOTAN_TARGET_OS_HAS_WINSOCK2)
17 #include <ws2tcpip.h>
18 #endif
19
20 #if defined(BOTAN_TARGET_OS_HAS_SOCKETS) || defined(BOTAN_TARGET_OS_HAS_WINSOCK2)
21
22 namespace {
23
isdigit(char ch)24 constexpr bool isdigit(char ch)
25 {
26 return ch >= '0' && ch <= '9';
27 }
28
isDomain(const std::string & domain)29 bool isDomain(const std::string& domain)
30 {
31 #if defined(__GLIBCXX__) && (__GLIBCXX__ < 20160726)
32 // GCC 4.8 does not support regex
33 BOTAN_UNUSED(domain);
34 return true;
35 #else
36 std::regex re(
37 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])$)");
38 std::cmatch m;
39 return std::regex_match(domain.c_str(), m, re);
40 #endif
41 }
42
isIPv4(const std::string & ip)43 bool isIPv4(const std::string& ip)
44 {
45 sockaddr_storage inaddr;
46 return !!inet_pton(AF_INET, ip.c_str(), &inaddr);
47 }
48
isIPv6(const std::string & ip)49 bool isIPv6(const std::string& ip)
50 {
51 sockaddr_storage in6addr;
52 return !!inet_pton(AF_INET6, ip.c_str(), &in6addr);
53 }
54 }
55
56 namespace Botan {
57
fromDomain(const std::string & uri)58 URI URI::fromDomain(const std::string& uri)
59 {
60 unsigned port = 0;
61 const auto port_pos = uri.find(':');
62 if(port_pos != std::string::npos)
63 {
64 for(char c : uri.substr(port_pos+1))
65 {
66 if(!isdigit(c))
67 { throw Invalid_Argument("invalid"); }
68 port = port*10 + c - '0';
69 if(port > 65535)
70 { throw Invalid_Argument("invalid"); }
71 }
72 }
73 const auto domain = uri.substr(0, port_pos);
74 if(isIPv4(domain))
75 { throw Invalid_Argument("invalid"); }
76 if(!isDomain(domain))
77 { throw Invalid_Argument("invalid"); }
78 return {Type::Domain, domain, uint16_t(port)};
79 }
80
fromIPv4(const std::string & uri)81 URI URI::fromIPv4(const std::string& uri)
82 {
83 unsigned port = 0;
84 const auto port_pos = uri.find(':');
85 if(port_pos != std::string::npos)
86 {
87 for(char c : uri.substr(port_pos+1))
88 {
89 if(!isdigit(c))
90 { throw Invalid_Argument("invalid"); }
91 port = port*10 + c - '0';
92 if(port > 65535)
93 { throw Invalid_Argument("invalid"); }
94 }
95 }
96 const auto ip = uri.substr(0, port_pos);
97 if(!isIPv4(ip))
98 { throw Invalid_Argument("invalid"); }
99 return { Type::IPv4, ip, uint16_t(port) };
100 }
101
fromIPv6(const std::string & uri)102 URI URI::fromIPv6(const std::string& uri)
103 {
104 unsigned port = 0;
105 const auto port_pos = uri.find(']');
106 const bool with_braces = (port_pos != std::string::npos);
107 if((uri[0]=='[') != with_braces)
108 { throw Invalid_Argument("invalid"); }
109
110 if(with_braces && (uri.size() > port_pos + 1))
111 {
112 if(uri[port_pos+1]!=':')
113 { throw Invalid_Argument("invalid"); }
114 for(char c : uri.substr(port_pos+2))
115 {
116 if(!isdigit(c))
117 { throw Invalid_Argument("invalid"); }
118 port = port*10 + c - '0';
119 if(port > 65535)
120 { throw Invalid_Argument("invalid"); }
121 }
122 }
123 const auto ip = uri.substr((with_braces ? 1 : 0), port_pos - with_braces);
124 if(!isIPv6(ip))
125 { throw Invalid_Argument("invalid"); }
126 return { Type::IPv6, ip, uint16_t(port) };
127 }
128
fromAny(const std::string & uri)129 URI URI::fromAny(const std::string& uri)
130 {
131
132 bool colon_seen=false;
133 bool non_number=false;
134 if(uri[0]=='[')
135 { return fromIPv6(uri); }
136 for(auto c : uri)
137 {
138 if(c == ':')
139 {
140 if(colon_seen) //seen two ':'
141 { return fromIPv6(uri); }
142 colon_seen = true;
143 }
144 else if(!isdigit(c) && c != '.')
145 {
146 non_number=true;
147 }
148 }
149 if(!non_number)
150 {
151 if(isIPv4(uri.substr(0, uri.find(':'))))
152 {
153 return fromIPv4(uri);
154 }
155 }
156 return fromDomain(uri);
157 }
158
to_string() const159 std::string URI::to_string() const
160 {
161 if(type == Type::NotSet)
162 {
163 throw Invalid_Argument("not set");
164 }
165
166 if(port != 0)
167 {
168 if(type == Type::IPv6)
169 { return "[" + host + "]:" + std::to_string(port); }
170 return host + ":" + std::to_string(port);
171 }
172 return host;
173 }
174
175 }
176
177 #else
178
179 namespace Botan {
180
fromDomain(const std::string &)181 URI URI::fromDomain(const std::string&) {throw Not_Implemented("No socket support enabled in build");}
fromIPv4(const std::string &)182 URI URI::fromIPv4(const std::string&) {throw Not_Implemented("No socket support enabled in build");}
fromIPv6(const std::string &)183 URI URI::fromIPv6(const std::string&) {throw Not_Implemented("No socket support enabled in build");}
fromAny(const std::string &)184 URI URI::fromAny(const std::string&) {throw Not_Implemented("No socket support enabled in build");}
185
186 }
187
188 #endif
189