Coverage Report

Created: 2023-06-07 07:00

/src/botan/src/lib/utils/parsing.cpp
Line
Count
Source (jump to first uncovered line)
1
/*
2
* Various string utils and parsing functions
3
* (C) 1999-2007,2013,2014,2015,2018 Jack Lloyd
4
* (C) 2015 Simon Warta (Kullo GmbH)
5
* (C) 2017 René Korthaus, Rohde & Schwarz Cybersecurity
6
*
7
* Botan is released under the Simplified BSD License (see license.txt)
8
*/
9
10
#include <botan/internal/parsing.h>
11
12
#include <botan/exceptn.h>
13
#include <botan/internal/fmt.h>
14
#include <botan/internal/loadstor.h>
15
#include <algorithm>
16
#include <cctype>
17
#include <limits>
18
#include <sstream>
19
20
namespace Botan {
21
22
0
uint16_t to_uint16(std::string_view str) {
23
0
   const uint32_t x = to_u32bit(str);
24
25
0
   if(x >> 16) {
26
0
      throw Invalid_Argument("Integer value exceeds 16 bit range");
27
0
   }
28
29
0
   return static_cast<uint16_t>(x);
30
0
}
31
32
4.35k
uint32_t to_u32bit(std::string_view str_view) {
33
4.35k
   const std::string str(str_view);
34
35
   // std::stoul is not strict enough. Ensure that str is digit only [0-9]*
36
8.71k
   for(const char chr : str) {
37
8.71k
      if(chr < '0' || chr > '9') {
38
0
         throw Invalid_Argument("to_u32bit invalid decimal string '" + str + "'");
39
0
      }
40
8.71k
   }
41
42
4.35k
   const unsigned long int x = std::stoul(str);
43
44
4.35k
   if constexpr(sizeof(unsigned long int) > 4) {
45
      // x might be uint64
46
4.35k
      if(x > std::numeric_limits<uint32_t>::max()) {
47
0
         throw Invalid_Argument("Integer value of " + str + " exceeds 32 bit range");
48
0
      }
49
4.35k
   }
50
51
4.35k
   return static_cast<uint32_t>(x);
52
4.35k
}
53
54
/*
55
* Parse a SCAN-style algorithm name
56
*/
57
0
std::vector<std::string> parse_algorithm_name(std::string_view namex) {
58
0
   if(namex.find('(') == std::string::npos && namex.find(')') == std::string::npos) {
59
0
      return {std::string(namex)};
60
0
   }
61
62
0
   std::string name(namex);
63
0
   std::string substring;
64
0
   std::vector<std::string> elems;
65
0
   size_t level = 0;
66
67
0
   elems.push_back(name.substr(0, name.find('(')));
68
0
   name = name.substr(name.find('('));
69
70
0
   for(auto i = name.begin(); i != name.end(); ++i) {
71
0
      char c = *i;
72
73
0
      if(c == '(') {
74
0
         ++level;
75
0
      }
76
0
      if(c == ')') {
77
0
         if(level == 1 && i == name.end() - 1) {
78
0
            if(elems.size() == 1) {
79
0
               elems.push_back(substring.substr(1));
80
0
            } else {
81
0
               elems.push_back(substring);
82
0
            }
83
0
            return elems;
84
0
         }
85
86
0
         if(level == 0 || (level == 1 && i != name.end() - 1)) {
87
0
            throw Invalid_Algorithm_Name(namex);
88
0
         }
89
0
         --level;
90
0
      }
91
92
0
      if(c == ',' && level == 1) {
93
0
         if(elems.size() == 1) {
94
0
            elems.push_back(substring.substr(1));
95
0
         } else {
96
0
            elems.push_back(substring);
97
0
         }
98
0
         substring.clear();
99
0
      } else {
100
0
         substring += c;
101
0
      }
102
0
   }
103
104
0
   if(!substring.empty()) {
105
0
      throw Invalid_Algorithm_Name(namex);
106
0
   }
107
108
0
   return elems;
109
0
}
110
111
3.60k
std::vector<std::string> split_on(std::string_view str, char delim) {
112
3.60k
   std::vector<std::string> elems;
113
3.60k
   if(str.empty()) {
114
0
      return elems;
115
0
   }
116
117
3.60k
   std::string substr;
118
19.6k
   for(auto i = str.begin(); i != str.end(); ++i) {
119
16.0k
      if(*i == delim) {
120
120
         if(!substr.empty()) {
121
120
            elems.push_back(substr);
122
120
         }
123
120
         substr.clear();
124
15.8k
      } else {
125
15.8k
         substr += *i;
126
15.8k
      }
127
16.0k
   }
128
129
3.60k
   if(substr.empty()) {
130
0
      throw Invalid_Argument(fmt("Unable to split string '{}", str));
131
0
   }
132
3.60k
   elems.push_back(substr);
133
134
3.60k
   return elems;
135
3.60k
}
136
137
/*
138
* Join a string
139
*/
140
0
std::string string_join(const std::vector<std::string>& strs, char delim) {
141
0
   std::ostringstream out;
142
143
0
   for(size_t i = 0; i != strs.size(); ++i) {
144
0
      if(i != 0) {
145
0
         out << delim;
146
0
      }
147
0
      out << strs[i];
148
0
   }
149
150
0
   return out.str();
151
0
}
152
153
/*
154
* Convert a decimal-dotted string to binary IP
155
*/
156
0
uint32_t string_to_ipv4(std::string_view str) {
157
0
   const auto parts = split_on(str, '.');
158
159
0
   if(parts.size() != 4) {
160
0
      throw Decoding_Error(fmt("Invalid IPv4 string '{}'", str));
161
0
   }
162
163
0
   uint32_t ip = 0;
164
165
0
   for(auto part = parts.begin(); part != parts.end(); ++part) {
166
0
      uint32_t octet = to_u32bit(*part);
167
168
0
      if(octet > 255) {
169
0
         throw Decoding_Error(fmt("Invalid IPv4 string '{}'", str));
170
0
      }
171
172
0
      ip = (ip << 8) | (octet & 0xFF);
173
0
   }
174
175
0
   return ip;
176
0
}
177
178
/*
179
* Convert an IP address to decimal-dotted string
180
*/
181
0
std::string ipv4_to_string(uint32_t ip) {
182
0
   std::string str;
183
0
   uint8_t bits[4];
184
0
   store_be(ip, bits);
185
186
0
   for(size_t i = 0; i != 4; ++i) {
187
0
      if(i > 0) {
188
0
         str += ".";
189
0
      }
190
0
      str += std::to_string(bits[i]);
191
0
   }
192
193
0
   return str;
194
0
}
195
196
0
std::string tolower_string(std::string_view in) {
197
0
   std::string s(in);
198
0
   for(size_t i = 0; i != s.size(); ++i) {
199
0
      const int cu = static_cast<unsigned char>(s[i]);
200
0
      if(std::isalpha(cu)) {
201
0
         s[i] = static_cast<char>(std::tolower(cu));
202
0
      }
203
0
   }
204
0
   return s;
205
0
}
206
207
0
bool host_wildcard_match(std::string_view issued_, std::string_view host_) {
208
0
   const std::string issued = tolower_string(issued_);
209
0
   const std::string host = tolower_string(host_);
210
211
0
   if(host.empty() || issued.empty()) {
212
0
      return false;
213
0
   }
214
215
   /*
216
   If there are embedded nulls in your issued name
217
   Well I feel bad for you son
218
   */
219
0
   if(std::count(issued.begin(), issued.end(), char(0)) > 0) {
220
0
      return false;
221
0
   }
222
223
   // If more than one wildcard, then issued name is invalid
224
0
   const size_t stars = std::count(issued.begin(), issued.end(), '*');
225
0
   if(stars > 1) {
226
0
      return false;
227
0
   }
228
229
   // '*' is not a valid character in DNS names so should not appear on the host side
230
0
   if(std::count(host.begin(), host.end(), '*') != 0) {
231
0
      return false;
232
0
   }
233
234
   // Similarly a DNS name can't end in .
235
0
   if(host[host.size() - 1] == '.') {
236
0
      return false;
237
0
   }
238
239
   // And a host can't have an empty name component, so reject that
240
0
   if(host.find("..") != std::string::npos) {
241
0
      return false;
242
0
   }
243
244
   // Exact match: accept
245
0
   if(issued == host) {
246
0
      return true;
247
0
   }
248
249
   /*
250
   Otherwise it might be a wildcard
251
252
   If the issued size is strictly longer than the hostname size it
253
   couldn't possibly be a match, even if the issued value is a
254
   wildcard. The only exception is when the wildcard ends up empty
255
   (eg www.example.com matches www*.example.com)
256
   */
257
0
   if(issued.size() > host.size() + 1) {
258
0
      return false;
259
0
   }
260
261
   // If no * at all then not a wildcard, and so not a match
262
0
   if(stars != 1) {
263
0
      return false;
264
0
   }
265
266
   /*
267
   Now walk through the issued string, making sure every character
268
   matches. When we come to the (singular) '*', jump forward in the
269
   hostname by the corresponding amount. We know exactly how much
270
   space the wildcard takes because it must be exactly `len(host) -
271
   len(issued) + 1 chars`.
272
273
   We also verify that the '*' comes in the leftmost component, and
274
   doesn't skip over any '.' in the hostname.
275
   */
276
0
   size_t dots_seen = 0;
277
0
   size_t host_idx = 0;
278
279
0
   for(size_t i = 0; i != issued.size(); ++i) {
280
0
      dots_seen += (issued[i] == '.');
281
282
0
      if(issued[i] == '*') {
283
         // Fail: wildcard can only come in leftmost component
284
0
         if(dots_seen > 0) {
285
0
            return false;
286
0
         }
287
288
         /*
289
         Since there is only one * we know the tail of the issued and
290
         hostname must be an exact match. In this case advance host_idx
291
         to match.
292
         */
293
0
         const size_t advance = (host.size() - issued.size() + 1);
294
295
0
         if(host_idx + advance > host.size()) {  // shouldn't happen
296
0
            return false;
297
0
         }
298
299
         // Can't be any intervening .s that we would have skipped
300
0
         if(std::count(host.begin() + host_idx, host.begin() + host_idx + advance, '.') != 0) {
301
0
            return false;
302
0
         }
303
304
0
         host_idx += advance;
305
0
      } else {
306
0
         if(issued[i] != host[host_idx]) {
307
0
            return false;
308
0
         }
309
310
0
         host_idx += 1;
311
0
      }
312
0
   }
313
314
   // Wildcard issued name must have at least 3 components
315
0
   if(dots_seen < 2) {
316
0
      return false;
317
0
   }
318
319
0
   return true;
320
0
}
321
322
}  // namespace Botan