Coverage Report

Created: 2023-06-07 06:59

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