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