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