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