xref: /openbmc/bmcweb/http/utility.hpp (revision 81ee0e74bd3a575593ee2a21de560936843f526a)
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 
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
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.
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 =
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
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 
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
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 class OrMorePaths
318 {};
319 
320 template <typename... AV>
321 inline void appendUrlPieces(boost::urls::url& url, AV&&... args)
322 {
323     // Unclear the correct fix here.
324     // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-array-to-pointer-decay)
325     for (const std::string_view arg : {args...})
326     {
327         url.segments().push_back(arg);
328     }
329 }
330 
331 namespace details
332 {
333 
334 // std::reference_wrapper<std::string> - extracts segment to variable
335 //                    std::string_view - checks if segment is equal to variable
336 using UrlSegment = std::variant<std::reference_wrapper<std::string>,
337                                 std::string_view, OrMorePaths>;
338 
339 enum class UrlParseResult
340 {
341     Continue,
342     Fail,
343     Done,
344 };
345 
346 class UrlSegmentMatcherVisitor
347 {
348   public:
349     UrlParseResult operator()(std::string& output)
350     {
351         output = segment;
352         return UrlParseResult::Continue;
353     }
354 
355     UrlParseResult operator()(std::string_view expected)
356     {
357         if (segment == expected)
358         {
359             return UrlParseResult::Continue;
360         }
361         return UrlParseResult::Fail;
362     }
363 
364     UrlParseResult operator()(OrMorePaths /*unused*/)
365     {
366         return UrlParseResult::Done;
367     }
368 
369     explicit UrlSegmentMatcherVisitor(std::string_view segmentIn) :
370         segment(segmentIn)
371     {}
372 
373   private:
374     std::string_view segment;
375 };
376 
377 inline bool readUrlSegments(const boost::urls::url_view_base& url,
378                             std::initializer_list<UrlSegment> segments)
379 {
380     const boost::urls::segments_view& urlSegments = url.segments();
381 
382     if (!urlSegments.is_absolute())
383     {
384         return false;
385     }
386 
387     boost::urls::segments_view::const_iterator it = urlSegments.begin();
388     boost::urls::segments_view::const_iterator end = urlSegments.end();
389 
390     for (const auto& segment : segments)
391     {
392         if (it == end)
393         {
394             // If the request ends with an "any" path, this was successful
395             return std::holds_alternative<OrMorePaths>(segment);
396         }
397         UrlParseResult res = std::visit(UrlSegmentMatcherVisitor(*it), segment);
398         if (res == UrlParseResult::Done)
399         {
400             return true;
401         }
402         if (res == UrlParseResult::Fail)
403         {
404             return false;
405         }
406         it++;
407     }
408 
409     // There will be an empty segment at the end if the URI ends with a "/"
410     // e.g. /redfish/v1/Chassis/
411     if ((it != end) && urlSegments.back().empty())
412     {
413         it++;
414     }
415     return it == end;
416 }
417 
418 } // namespace details
419 
420 template <typename... Args>
421 inline bool readUrlSegments(const boost::urls::url_view_base& url,
422                             Args&&... args)
423 {
424     return details::readUrlSegments(url, {std::forward<Args>(args)...});
425 }
426 
427 inline boost::urls::url
428     replaceUrlSegment(const boost::urls::url_view_base& urlView,
429                       const uint replaceLoc, std::string_view newSegment)
430 {
431     const boost::urls::segments_view& urlSegments = urlView.segments();
432     boost::urls::url url("/");
433 
434     if (!urlSegments.is_absolute())
435     {
436         return url;
437     }
438 
439     boost::urls::segments_view::iterator it = urlSegments.begin();
440     boost::urls::segments_view::iterator end = urlSegments.end();
441 
442     for (uint idx = 0; it != end; it++, idx++)
443     {
444         if (idx == replaceLoc)
445         {
446             url.segments().push_back(newSegment);
447         }
448         else
449         {
450             url.segments().push_back(*it);
451         }
452     }
453 
454     return url;
455 }
456 
457 inline void setProtocolDefaults(boost::urls::url& url,
458                                 std::string_view protocol)
459 {
460     if (url.has_scheme())
461     {
462         return;
463     }
464     if (protocol == "Redfish" || protocol.empty())
465     {
466         if (url.port_number() == 443)
467         {
468             url.set_scheme("https");
469         }
470         if (url.port_number() == 80)
471         {
472             if constexpr (BMCWEB_INSECURE_PUSH_STYLE_NOTIFICATION)
473             {
474                 url.set_scheme("http");
475             }
476         }
477     }
478     else if (protocol == "SNMPv2c")
479     {
480         url.set_scheme("snmp");
481     }
482 }
483 
484 inline void setPortDefaults(boost::urls::url& url)
485 {
486     uint16_t port = url.port_number();
487     if (port != 0)
488     {
489         return;
490     }
491 
492     // If the user hasn't explicitly stated a port, pick one explicitly for them
493     // based on the protocol defaults
494     if (url.scheme() == "http")
495     {
496         url.set_port_number(80);
497     }
498     if (url.scheme() == "https")
499     {
500         url.set_port_number(443);
501     }
502     if (url.scheme() == "snmp")
503     {
504         url.set_port_number(162);
505     }
506 }
507 
508 } // namespace utility
509 } // namespace crow
510 
511 namespace nlohmann
512 {
513 template <std::derived_from<boost::urls::url_view_base> URL>
514 struct adl_serializer<URL>
515 {
516     // NOLINTNEXTLINE(readability-identifier-naming)
517     static void to_json(json& j, const URL& url)
518     {
519         j = url.buffer();
520     }
521 };
522 } // namespace nlohmann
523