1 #pragma once
2
3 #include "bmcweb_config.h"
4
5 extern "C"
6 {
7 #include <openssl/crypto.h>
8 }
9
10 #include <boost/callable_traits.hpp>
11 #include <boost/url/parse.hpp>
12 #include <boost/url/url.hpp>
13 #include <boost/url/url_view.hpp>
14 #include <boost/url/url_view_base.hpp>
15 #include <nlohmann/json.hpp>
16
17 #include <array>
18 #include <chrono>
19 #include <cstddef>
20 #include <cstdint>
21 #include <ctime>
22 #include <functional>
23 #include <iomanip>
24 #include <limits>
25 #include <stdexcept>
26 #include <string>
27 #include <string_view>
28 #include <tuple>
29 #include <type_traits>
30 #include <utility>
31 #include <variant>
32
33 namespace crow
34 {
35 namespace utility
36 {
37
getParameterTag(std::string_view url)38 constexpr uint64_t getParameterTag(std::string_view url)
39 {
40 uint64_t tagValue = 0;
41 size_t urlSegmentIndex = std::string_view::npos;
42
43 for (size_t urlIndex = 0; urlIndex < url.size(); urlIndex++)
44 {
45 char character = url[urlIndex];
46 if (character == '<')
47 {
48 if (urlSegmentIndex != std::string_view::npos)
49 {
50 return 0;
51 }
52 urlSegmentIndex = urlIndex;
53 }
54 if (character == '>')
55 {
56 if (urlSegmentIndex == std::string_view::npos)
57 {
58 return 0;
59 }
60 std::string_view tag = url.substr(urlSegmentIndex,
61 urlIndex + 1 - urlSegmentIndex);
62
63 if (tag == "<str>" || tag == "<string>")
64 {
65 tagValue++;
66 }
67 if (tag == "<path>")
68 {
69 tagValue++;
70 }
71 urlSegmentIndex = std::string_view::npos;
72 }
73 }
74 if (urlSegmentIndex != std::string_view::npos)
75 {
76 return 0;
77 }
78 return tagValue;
79 }
80
81 class Base64Encoder
82 {
83 char overflow1 = '\0';
84 char overflow2 = '\0';
85 uint8_t overflowCount = 0;
86
87 constexpr static std::array<char, 64> key = {
88 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
89 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
90 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
91 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
92 '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'};
93
94 // Takes 3 ascii chars, and encodes them as 4 base64 chars
encodeTriple(char first,char second,char third,std::string & output)95 static void encodeTriple(char first, char second, char third,
96 std::string& output)
97 {
98 size_t keyIndex = 0;
99
100 keyIndex = static_cast<size_t>(first & 0xFC) >> 2;
101 output += key[keyIndex];
102
103 keyIndex = static_cast<size_t>(first & 0x03) << 4;
104 keyIndex += static_cast<size_t>(second & 0xF0) >> 4;
105 output += key[keyIndex];
106
107 keyIndex = static_cast<size_t>(second & 0x0F) << 2;
108 keyIndex += static_cast<size_t>(third & 0xC0) >> 6;
109 output += key[keyIndex];
110
111 keyIndex = static_cast<size_t>(third & 0x3F);
112 output += key[keyIndex];
113 }
114
115 public:
116 // Accepts a partial string to encode, and writes the encoded characters to
117 // the output stream. requires subsequently calling finalize to complete
118 // stream.
encode(std::string_view data,std::string & output)119 void encode(std::string_view data, std::string& output)
120 {
121 // Encode the last round of overflow chars first
122 if (overflowCount == 2)
123 {
124 if (!data.empty())
125 {
126 encodeTriple(overflow1, overflow2, data[0], output);
127 overflowCount = 0;
128 data.remove_prefix(1);
129 }
130 }
131 else if (overflowCount == 1)
132 {
133 if (data.size() >= 2)
134 {
135 encodeTriple(overflow1, data[0], data[1], output);
136 overflowCount = 0;
137 data.remove_prefix(2);
138 }
139 }
140
141 while (data.size() >= 3)
142 {
143 encodeTriple(data[0], data[1], data[2], output);
144 data.remove_prefix(3);
145 }
146
147 if (!data.empty() && overflowCount == 0)
148 {
149 overflow1 = data[0];
150 overflowCount++;
151 data.remove_prefix(1);
152 }
153
154 if (!data.empty() && overflowCount == 1)
155 {
156 overflow2 = data[0];
157 overflowCount++;
158 data.remove_prefix(1);
159 }
160 }
161
162 // Completes a base64 output, by writing any MOD(3) characters to the
163 // output, as well as any required trailing =
finalize(std::string & output)164 void finalize(std::string& output)
165 {
166 if (overflowCount == 0)
167 {
168 return;
169 }
170 size_t keyIndex = static_cast<size_t>(overflow1 & 0xFC) >> 2;
171 output += key[keyIndex];
172
173 keyIndex = static_cast<size_t>(overflow1 & 0x03) << 4;
174 if (overflowCount == 2)
175 {
176 keyIndex += static_cast<size_t>(overflow2 & 0xF0) >> 4;
177 output += key[keyIndex];
178 keyIndex = static_cast<size_t>(overflow2 & 0x0F) << 2;
179 output += key[keyIndex];
180 }
181 else
182 {
183 output += key[keyIndex];
184 output += '=';
185 }
186 output += '=';
187 overflowCount = 0;
188 }
189
190 // Returns the required output buffer in characters for an input of size
191 // inputSize
encodedSize(size_t inputSize)192 static size_t constexpr encodedSize(size_t inputSize)
193 {
194 // Base64 encodes 3 character blocks as 4 character blocks
195 // With a possibility of 2 trailing = characters
196 return (inputSize + 2) / 3 * 4;
197 }
198 };
199
base64encode(std::string_view data)200 inline std::string base64encode(std::string_view data)
201 {
202 // Encodes a 3 character stream into a 4 character stream
203 std::string out;
204 Base64Encoder base64;
205 out.reserve(Base64Encoder::encodedSize(data.size()));
206 base64.encode(data, out);
207 base64.finalize(out);
208 return out;
209 }
210
211 // TODO this is temporary and should be deleted once base64 is refactored out of
212 // crow
base64Decode(std::string_view input,std::string & output)213 inline bool base64Decode(std::string_view input, std::string& output)
214 {
215 static const char nop = static_cast<char>(-1);
216 // See note on encoding_data[] in above function
217 static const std::array<char, 256> decodingData = {
218 nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop,
219 nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop,
220 nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop,
221 nop, 62, nop, nop, nop, 63, 52, 53, 54, 55, 56, 57, 58, 59,
222 60, 61, nop, nop, nop, nop, nop, nop, nop, 0, 1, 2, 3, 4,
223 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18,
224 19, 20, 21, 22, 23, 24, 25, nop, nop, nop, nop, nop, nop, 26,
225 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
226 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, nop, nop, nop,
227 nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop,
228 nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop,
229 nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop,
230 nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop,
231 nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop,
232 nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop,
233 nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop,
234 nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop,
235 nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop,
236 nop, nop, nop, nop};
237
238 size_t inputLength = input.size();
239
240 // allocate space for output string
241 output.clear();
242 output.reserve(((inputLength + 2) / 3) * 4);
243
244 auto getCodeValue = [](char c) {
245 auto code = static_cast<unsigned char>(c);
246 // Ensure we cannot index outside the bounds of the decoding array
247 static_assert(std::numeric_limits<decltype(code)>::max() <
248 decodingData.size());
249 return decodingData[code];
250 };
251
252 // for each 4-bytes sequence from the input, extract 4 6-bits sequences by
253 // dropping first two bits
254 // and regenerate into 3 8-bits sequences
255
256 for (size_t i = 0; i < inputLength; i++)
257 {
258 char base64code0 = 0;
259 char base64code1 = 0;
260 char base64code2 = 0; // initialized to 0 to suppress warnings
261
262 base64code0 = getCodeValue(input[i]);
263 if (base64code0 == nop)
264 { // non base64 character
265 return false;
266 }
267 if (!(++i < inputLength))
268 { // we need at least two input bytes for first
269 // byte output
270 return false;
271 }
272 base64code1 = getCodeValue(input[i]);
273 if (base64code1 == nop)
274 { // non base64 character
275 return false;
276 }
277 output +=
278 static_cast<char>((base64code0 << 2) | ((base64code1 >> 4) & 0x3));
279
280 if (++i < inputLength)
281 {
282 char c = input[i];
283 if (c == '=')
284 { // padding , end of input
285 return (base64code1 & 0x0f) == 0;
286 }
287 base64code2 = getCodeValue(input[i]);
288 if (base64code2 == nop)
289 { // non base64 character
290 return false;
291 }
292 output += static_cast<char>(((base64code1 << 4) & 0xf0) |
293 ((base64code2 >> 2) & 0x0f));
294 }
295
296 if (++i < inputLength)
297 {
298 char c = input[i];
299 if (c == '=')
300 { // padding , end of input
301 return (base64code2 & 0x03) == 0;
302 }
303 char base64code3 = getCodeValue(input[i]);
304 if (base64code3 == nop)
305 { // non base64 character
306 return false;
307 }
308 output +=
309 static_cast<char>((((base64code2 << 6) & 0xc0) | base64code3));
310 }
311 }
312
313 return true;
314 }
315
constantTimeStringCompare(std::string_view a,std::string_view b)316 inline bool constantTimeStringCompare(std::string_view a, std::string_view b)
317 {
318 // Important note, this function is ONLY constant time if the two input
319 // sizes are the same
320 if (a.size() != b.size())
321 {
322 return false;
323 }
324 return CRYPTO_memcmp(a.data(), b.data(), a.size()) == 0;
325 }
326
327 struct ConstantTimeCompare
328 {
operator ()crow::utility::ConstantTimeCompare329 bool operator()(std::string_view a, std::string_view b) const
330 {
331 return constantTimeStringCompare(a, b);
332 }
333 };
334
335 namespace details
336 {
337 inline boost::urls::url
appendUrlPieces(boost::urls::url & url,const std::initializer_list<std::string_view> args)338 appendUrlPieces(boost::urls::url& url,
339 const std::initializer_list<std::string_view> args)
340 {
341 for (std::string_view arg : args)
342 {
343 url.segments().push_back(arg);
344 }
345 return url;
346 }
347
348 } // namespace details
349
350 class OrMorePaths
351 {};
352
353 template <typename... AV>
appendUrlPieces(boost::urls::url & url,const AV...args)354 inline void appendUrlPieces(boost::urls::url& url, const AV... args)
355 {
356 details::appendUrlPieces(url, {args...});
357 }
358
359 namespace details
360 {
361
362 // std::reference_wrapper<std::string> - extracts segment to variable
363 // std::string_view - checks if segment is equal to variable
364 using UrlSegment = std::variant<std::reference_wrapper<std::string>,
365 std::string_view, OrMorePaths>;
366
367 enum class UrlParseResult
368 {
369 Continue,
370 Fail,
371 Done,
372 };
373
374 class UrlSegmentMatcherVisitor
375 {
376 public:
operator ()(std::string & output)377 UrlParseResult operator()(std::string& output)
378 {
379 output = segment;
380 return UrlParseResult::Continue;
381 }
382
operator ()(std::string_view expected)383 UrlParseResult operator()(std::string_view expected)
384 {
385 if (segment == expected)
386 {
387 return UrlParseResult::Continue;
388 }
389 return UrlParseResult::Fail;
390 }
391
operator ()(OrMorePaths)392 UrlParseResult operator()(OrMorePaths /*unused*/)
393 {
394 return UrlParseResult::Done;
395 }
396
UrlSegmentMatcherVisitor(std::string_view segmentIn)397 explicit UrlSegmentMatcherVisitor(std::string_view segmentIn) :
398 segment(segmentIn)
399 {}
400
401 private:
402 std::string_view segment;
403 };
404
readUrlSegments(const boost::urls::url_view_base & url,std::initializer_list<UrlSegment> segments)405 inline bool readUrlSegments(const boost::urls::url_view_base& url,
406 std::initializer_list<UrlSegment> segments)
407 {
408 const boost::urls::segments_view& urlSegments = url.segments();
409
410 if (!urlSegments.is_absolute())
411 {
412 return false;
413 }
414
415 boost::urls::segments_view::const_iterator it = urlSegments.begin();
416 boost::urls::segments_view::const_iterator end = urlSegments.end();
417
418 for (const auto& segment : segments)
419 {
420 if (it == end)
421 {
422 // If the request ends with an "any" path, this was successful
423 return std::holds_alternative<OrMorePaths>(segment);
424 }
425 UrlParseResult res = std::visit(UrlSegmentMatcherVisitor(*it), segment);
426 if (res == UrlParseResult::Done)
427 {
428 return true;
429 }
430 if (res == UrlParseResult::Fail)
431 {
432 return false;
433 }
434 it++;
435 }
436
437 // There will be an empty segment at the end if the URI ends with a "/"
438 // e.g. /redfish/v1/Chassis/
439 if ((it != end) && urlSegments.back().empty())
440 {
441 it++;
442 }
443 return it == end;
444 }
445
446 } // namespace details
447
448 template <typename... Args>
readUrlSegments(const boost::urls::url_view_base & url,Args &&...args)449 inline bool readUrlSegments(const boost::urls::url_view_base& url,
450 Args&&... args)
451 {
452 return details::readUrlSegments(url, {std::forward<Args>(args)...});
453 }
454
455 inline boost::urls::url
replaceUrlSegment(const boost::urls::url_view_base & urlView,const uint replaceLoc,std::string_view newSegment)456 replaceUrlSegment(const boost::urls::url_view_base& urlView,
457 const uint replaceLoc, std::string_view newSegment)
458 {
459 const boost::urls::segments_view& urlSegments = urlView.segments();
460 boost::urls::url url("/");
461
462 if (!urlSegments.is_absolute())
463 {
464 return url;
465 }
466
467 boost::urls::segments_view::iterator it = urlSegments.begin();
468 boost::urls::segments_view::iterator end = urlSegments.end();
469
470 for (uint idx = 0; it != end; it++, idx++)
471 {
472 if (idx == replaceLoc)
473 {
474 url.segments().push_back(newSegment);
475 }
476 else
477 {
478 url.segments().push_back(*it);
479 }
480 }
481
482 return url;
483 }
484
setProtocolDefaults(boost::urls::url & url,std::string_view protocol)485 inline void setProtocolDefaults(boost::urls::url& url,
486 std::string_view protocol)
487 {
488 if (url.has_scheme())
489 {
490 return;
491 }
492 if (protocol == "Redfish" || protocol.empty())
493 {
494 if (url.port_number() == 443)
495 {
496 url.set_scheme("https");
497 }
498 if (url.port_number() == 80)
499 {
500 if constexpr (BMCWEB_INSECURE_PUSH_STYLE_NOTIFICATION)
501 {
502 url.set_scheme("http");
503 }
504 }
505 }
506 else if (protocol == "SNMPv2c")
507 {
508 url.set_scheme("snmp");
509 }
510 }
511
setPortDefaults(boost::urls::url & url)512 inline void setPortDefaults(boost::urls::url& url)
513 {
514 uint16_t port = url.port_number();
515 if (port != 0)
516 {
517 return;
518 }
519
520 // If the user hasn't explicitly stated a port, pick one explicitly for them
521 // based on the protocol defaults
522 if (url.scheme() == "http")
523 {
524 url.set_port_number(80);
525 }
526 if (url.scheme() == "https")
527 {
528 url.set_port_number(443);
529 }
530 if (url.scheme() == "snmp")
531 {
532 url.set_port_number(162);
533 }
534 }
535
536 } // namespace utility
537 } // namespace crow
538
539 namespace nlohmann
540 {
541 template <std::derived_from<boost::urls::url_view_base> URL>
542 struct adl_serializer<URL>
543 {
544 // NOLINTNEXTLINE(readability-identifier-naming)
to_jsonnlohmann::adl_serializer545 static void to_json(json& j, const URL& url)
546 {
547 j = url.buffer();
548 }
549 };
550 } // namespace nlohmann
551