Coverage Report

Created: 2020-02-14 15:38

/src/botan/src/lib/utils/socket/uri.cpp
Line
Count
Source (jump to first uncovered line)
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
24
constexpr bool isdigit(char ch)
25
237k
   {
26
237k
   return ch >= '0' && ch <= '9';
27
237k
   }
28
29
bool isDomain(const std::string& domain)
30
470
   {
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
470
      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
470
   std::cmatch m;
39
470
   return std::regex_match(domain.c_str(), m, re);
40
470
#endif
41
470
   }
42
43
bool isIPv4(const std::string& ip)
44
635
   {
45
635
   sockaddr_storage inaddr;
46
635
   return !!inet_pton(AF_INET, ip.c_str(), &inaddr);
47
635
   }
48
49
bool isIPv6(const std::string& ip)
50
50
   {
51
50
   sockaddr_storage in6addr;
52
50
   return !!inet_pton(AF_INET6, ip.c_str(), &in6addr);
53
50
   }
54
}
55
56
namespace Botan {
57
58
URI URI::fromDomain(const std::string& uri)
59
484
   {
60
484
   unsigned port = 0;
61
484
   const auto port_pos = uri.find(':');
62
484
   if(port_pos != std::string::npos)
63
77
      {
64
77
      for(char c : uri.substr(port_pos+1))
65
834
         {
66
834
         if(!isdigit(c))
67
7
            { throw Invalid_Argument("invalid"); }
68
827
         port = port*10 + c - '0';
69
827
         if(port > 65535)
70
6
            { throw Invalid_Argument("invalid"); }
71
827
         }
72
77
      }
73
484
   const auto domain = uri.substr(0, port_pos);
74
471
   if(isIPv4(domain))
75
1
      { throw Invalid_Argument("invalid"); }
76
470
   if(!isDomain(domain))
77
365
      { throw Invalid_Argument("invalid"); }
78
105
   return {Type::Domain, domain, uint16_t(port)};
79
105
   }
80
81
URI URI::fromIPv4(const std::string& uri)
82
45
   {
83
45
   unsigned port = 0;
84
45
   const auto port_pos = uri.find(':');
85
45
   if(port_pos != std::string::npos)
86
44
      {
87
44
      for(char c : uri.substr(port_pos+1))
88
342
         {
89
342
         if(!isdigit(c))
90
2
            { throw Invalid_Argument("invalid"); }
91
340
         port = port*10 + c - '0';
92
340
         if(port > 65535)
93
7
            { throw Invalid_Argument("invalid"); }
94
340
         }
95
44
      }
96
45
   const auto ip = uri.substr(0, port_pos);
97
36
   if(!isIPv4(ip))
98
0
      { throw Invalid_Argument("invalid"); }
99
36
   return { Type::IPv4, ip, uint16_t(port) };
100
36
   }
101
102
URI URI::fromIPv6(const std::string& uri)
103
113
   {
104
113
   unsigned port = 0;
105
113
   const auto port_pos = uri.find(']');
106
113
   const bool with_braces = (port_pos != std::string::npos);
107
113
   if((uri[0]=='[') != with_braces)
108
17
      { throw Invalid_Argument("invalid"); }
109
96
110
96
   if(with_braces && (uri.size() > port_pos + 1))
111
81
      {
112
81
      if(uri[port_pos+1]!=':')
113
33
         { throw Invalid_Argument("invalid"); }
114
48
      for(char c : uri.substr(port_pos+2))
115
356
         {
116
356
         if(!isdigit(c))
117
5
            { throw Invalid_Argument("invalid"); }
118
351
         port = port*10 + c - '0';
119
351
         if(port > 65535)
120
8
            { throw Invalid_Argument("invalid"); }
121
351
         }
122
48
      }
123
96
   const auto ip = uri.substr((with_braces ? 1 : 0), port_pos - with_braces);
124
50
   if(!isIPv6(ip))
125
48
      { throw Invalid_Argument("invalid"); }
126
2
   return { Type::IPv6, ip, uint16_t(port) };
127
2
   }
128
129
URI URI::fromAny(const std::string& uri)
130
642
   {
131
642
132
642
   bool colon_seen=false;
133
642
   bool non_number=false;
134
642
   if(uri[0]=='[')
135
88
      { return fromIPv6(uri); }
136
554
   for(auto c : uri)
137
235k
      {
138
235k
      if(c == ':')
139
171
         {
140
171
         if(colon_seen) //seen two ':'
141
25
            { return fromIPv6(uri); }
142
146
         colon_seen = true;
143
146
         }
144
235k
      else if(!isdigit(c) && c !=  '.')
145
200k
         {
146
200k
         non_number=true;
147
200k
         }
148
235k
      }
149
554
   if(!non_number)
150
128
      {
151
128
      if(isIPv4(uri.substr(0, uri.find(':'))))
152
45
         {
153
45
         return fromIPv4(uri);
154
45
         }
155
484
      }
156
484
   return fromDomain(uri);
157
484
   }
158
159
std::string URI::to_string() const
160
0
   {
161
0
   if(type == Type::NotSet)
162
0
      {
163
0
      throw Invalid_Argument("not set");
164
0
      }
165
0
166
0
   if(port != 0)
167
0
      {
168
0
      if(type == Type::IPv6)
169
0
         { return "[" + host + "]:" + std::to_string(port); }
170
0
      return host + ":" + std::to_string(port);
171
0
      }
172
0
   return host;
173
0
   }
174
175
}
176
177
#else
178
179
namespace Botan {
180
181
URI URI::fromDomain(const std::string&) {throw Not_Implemented("No socket support enabled in build");}
182
URI URI::fromIPv4(const std::string&) {throw Not_Implemented("No socket support enabled in build");}
183
URI URI::fromIPv6(const std::string&) {throw Not_Implemented("No socket support enabled in build");}
184
URI URI::fromAny(const std::string&) {throw Not_Implemented("No socket support enabled in build");}
185
186
}
187
188
#endif