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