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