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 { // non base64 character
260 return false;
261 }
262 if (!(++i < inputLength))
263 { // we need at least two input bytes for first
264 // byte output
265 return false;
266 }
267 base64code1 = getCodeValue(input[i]);
268 if (base64code1 == nop)
269 { // non base64 character
270 return false;
271 }
272 output +=
273 static_cast<char>((base64code0 << 2) | ((base64code1 >> 4) & 0x3));
274
275 if (++i < inputLength)
276 {
277 char c = input[i];
278 if (c == '=')
279 { // padding , end of input
280 return (base64code1 & 0x0f) == 0;
281 }
282 base64code2 = getCodeValue(input[i]);
283 if (base64code2 == nop)
284 { // non base64 character
285 return false;
286 }
287 output += static_cast<char>(
288 ((base64code1 << 4) & 0xf0) | ((base64code2 >> 2) & 0x0f));
289 }
290
291 if (++i < inputLength)
292 {
293 char c = input[i];
294 if (c == '=')
295 { // padding , end of input
296 return (base64code2 & 0x03) == 0;
297 }
298 char base64code3 = getCodeValue(input[i]);
299 if (base64code3 == nop)
300 { // non base64 character
301 return false;
302 }
303 output +=
304 static_cast<char>((((base64code2 << 6) & 0xc0) | base64code3));
305 }
306 }
307
308 return true;
309 }
310
311 namespace details
312 {
appendUrlPieces(boost::urls::url & url,const std::initializer_list<std::string_view> args)313 inline boost::urls::url appendUrlPieces(
314 boost::urls::url& url, const std::initializer_list<std::string_view> args)
315 {
316 for (std::string_view arg : args)
317 {
318 url.segments().push_back(arg);
319 }
320 return url;
321 }
322
323 } // namespace details
324
325 class OrMorePaths
326 {};
327
328 template <typename... AV>
appendUrlPieces(boost::urls::url & url,const AV...args)329 inline void appendUrlPieces(boost::urls::url& url, const AV... args)
330 {
331 details::appendUrlPieces(url, {args...});
332 }
333
334 namespace details
335 {
336
337 // std::reference_wrapper<std::string> - extracts segment to variable
338 // std::string_view - checks if segment is equal to variable
339 using UrlSegment = std::variant<std::reference_wrapper<std::string>,
340 std::string_view, OrMorePaths>;
341
342 enum class UrlParseResult
343 {
344 Continue,
345 Fail,
346 Done,
347 };
348
349 class UrlSegmentMatcherVisitor
350 {
351 public:
operator ()(std::string & output)352 UrlParseResult operator()(std::string& output)
353 {
354 output = segment;
355 return UrlParseResult::Continue;
356 }
357
operator ()(std::string_view expected)358 UrlParseResult operator()(std::string_view expected)
359 {
360 if (segment == expected)
361 {
362 return UrlParseResult::Continue;
363 }
364 return UrlParseResult::Fail;
365 }
366
operator ()(OrMorePaths)367 UrlParseResult operator()(OrMorePaths /*unused*/)
368 {
369 return UrlParseResult::Done;
370 }
371
UrlSegmentMatcherVisitor(std::string_view segmentIn)372 explicit UrlSegmentMatcherVisitor(std::string_view segmentIn) :
373 segment(segmentIn)
374 {}
375
376 private:
377 std::string_view segment;
378 };
379
readUrlSegments(const boost::urls::url_view_base & url,std::initializer_list<UrlSegment> segments)380 inline bool readUrlSegments(const boost::urls::url_view_base& url,
381 std::initializer_list<UrlSegment> segments)
382 {
383 const boost::urls::segments_view& urlSegments = url.segments();
384
385 if (!urlSegments.is_absolute())
386 {
387 return false;
388 }
389
390 boost::urls::segments_view::const_iterator it = urlSegments.begin();
391 boost::urls::segments_view::const_iterator end = urlSegments.end();
392
393 for (const auto& segment : segments)
394 {
395 if (it == end)
396 {
397 // If the request ends with an "any" path, this was successful
398 return std::holds_alternative<OrMorePaths>(segment);
399 }
400 UrlParseResult res = std::visit(UrlSegmentMatcherVisitor(*it), segment);
401 if (res == UrlParseResult::Done)
402 {
403 return true;
404 }
405 if (res == UrlParseResult::Fail)
406 {
407 return false;
408 }
409 it++;
410 }
411
412 // There will be an empty segment at the end if the URI ends with a "/"
413 // e.g. /redfish/v1/Chassis/
414 if ((it != end) && urlSegments.back().empty())
415 {
416 it++;
417 }
418 return it == end;
419 }
420
421 } // namespace details
422
423 template <typename... Args>
readUrlSegments(const boost::urls::url_view_base & url,Args &&...args)424 inline bool readUrlSegments(const boost::urls::url_view_base& url,
425 Args&&... args)
426 {
427 return details::readUrlSegments(url, {std::forward<Args>(args)...});
428 }
429
430 inline boost::urls::url
replaceUrlSegment(const boost::urls::url_view_base & urlView,const uint replaceLoc,std::string_view newSegment)431 replaceUrlSegment(const boost::urls::url_view_base& urlView,
432 const uint replaceLoc, std::string_view newSegment)
433 {
434 const boost::urls::segments_view& urlSegments = urlView.segments();
435 boost::urls::url url("/");
436
437 if (!urlSegments.is_absolute())
438 {
439 return url;
440 }
441
442 boost::urls::segments_view::iterator it = urlSegments.begin();
443 boost::urls::segments_view::iterator end = urlSegments.end();
444
445 for (uint idx = 0; it != end; it++, idx++)
446 {
447 if (idx == replaceLoc)
448 {
449 url.segments().push_back(newSegment);
450 }
451 else
452 {
453 url.segments().push_back(*it);
454 }
455 }
456
457 return url;
458 }
459
setProtocolDefaults(boost::urls::url & url,std::string_view protocol)460 inline void setProtocolDefaults(boost::urls::url& url,
461 std::string_view protocol)
462 {
463 if (url.has_scheme())
464 {
465 return;
466 }
467 if (protocol == "Redfish" || protocol.empty())
468 {
469 if (url.port_number() == 443)
470 {
471 url.set_scheme("https");
472 }
473 if (url.port_number() == 80)
474 {
475 if constexpr (BMCWEB_INSECURE_PUSH_STYLE_NOTIFICATION)
476 {
477 url.set_scheme("http");
478 }
479 }
480 }
481 else if (protocol == "SNMPv2c")
482 {
483 url.set_scheme("snmp");
484 }
485 }
486
setPortDefaults(boost::urls::url & url)487 inline void setPortDefaults(boost::urls::url& url)
488 {
489 uint16_t port = url.port_number();
490 if (port != 0)
491 {
492 return;
493 }
494
495 // If the user hasn't explicitly stated a port, pick one explicitly for them
496 // based on the protocol defaults
497 if (url.scheme() == "http")
498 {
499 url.set_port_number(80);
500 }
501 if (url.scheme() == "https")
502 {
503 url.set_port_number(443);
504 }
505 if (url.scheme() == "snmp")
506 {
507 url.set_port_number(162);
508 }
509 }
510
511 } // namespace utility
512 } // namespace crow
513
514 namespace nlohmann
515 {
516 template <std::derived_from<boost::urls::url_view_base> URL>
517 struct adl_serializer<URL>
518 {
519 // NOLINTNEXTLINE(readability-identifier-naming)
to_jsonnlohmann::adl_serializer520 static void to_json(json& j, const URL& url)
521 {
522 j = url.buffer();
523 }
524 };
525 } // namespace nlohmann
526