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