xref: /openbmc/bmcweb/http/utility.hpp (revision 8d45b9cb)
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 <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 
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 = url.substr(urlSegmentIndex,
58                                               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
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.
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 =
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
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 
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
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(std::numeric_limits<decltype(code)>::max() <
245                       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         { // non base64 character
262             return false;
263         }
264         if (!(++i < inputLength))
265         { // we need at least two input bytes for first
266           // byte output
267             return false;
268         }
269         base64code1 = getCodeValue(input[i]);
270         if (base64code1 == nop)
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             { // padding , end of input
282                 return (base64code1 & 0x0f) == 0;
283             }
284             base64code2 = getCodeValue(input[i]);
285             if (base64code2 == nop)
286             { // non base64 character
287                 return false;
288             }
289             output += static_cast<char>(((base64code1 << 4) & 0xf0) |
290                                         ((base64code2 >> 2) & 0x0f));
291         }
292 
293         if (++i < inputLength)
294         {
295             char c = input[i];
296             if (c == '=')
297             { // padding , end of input
298                 return (base64code2 & 0x03) == 0;
299             }
300             char base64code3 = getCodeValue(input[i]);
301             if (base64code3 == nop)
302             { // non base64 character
303                 return false;
304             }
305             output +=
306                 static_cast<char>((((base64code2 << 6) & 0xc0) | base64code3));
307         }
308     }
309 
310     return true;
311 }
312 
313 inline bool constantTimeStringCompare(std::string_view a, std::string_view b)
314 {
315     // Important note, this function is ONLY constant time if the two input
316     // sizes are the same
317     if (a.size() != b.size())
318     {
319         return false;
320     }
321     return CRYPTO_memcmp(a.data(), b.data(), a.size()) == 0;
322 }
323 
324 struct ConstantTimeCompare
325 {
326     bool operator()(std::string_view a, std::string_view b) const
327     {
328         return constantTimeStringCompare(a, b);
329     }
330 };
331 
332 namespace details
333 {
334 inline boost::urls::url
335     appendUrlPieces(boost::urls::url& url,
336                     const std::initializer_list<std::string_view> args)
337 {
338     for (std::string_view arg : args)
339     {
340         url.segments().push_back(arg);
341     }
342     return url;
343 }
344 
345 } // namespace details
346 
347 class OrMorePaths
348 {};
349 
350 template <typename... AV>
351 inline void appendUrlPieces(boost::urls::url& url, const AV... args)
352 {
353     details::appendUrlPieces(url, {args...});
354 }
355 
356 namespace details
357 {
358 
359 // std::reference_wrapper<std::string> - extracts segment to variable
360 //                    std::string_view - checks if segment is equal to variable
361 using UrlSegment = std::variant<std::reference_wrapper<std::string>,
362                                 std::string_view, OrMorePaths>;
363 
364 enum class UrlParseResult
365 {
366     Continue,
367     Fail,
368     Done,
369 };
370 
371 class UrlSegmentMatcherVisitor
372 {
373   public:
374     UrlParseResult operator()(std::string& output)
375     {
376         output = segment;
377         return UrlParseResult::Continue;
378     }
379 
380     UrlParseResult operator()(std::string_view expected)
381     {
382         if (segment == expected)
383         {
384             return UrlParseResult::Continue;
385         }
386         return UrlParseResult::Fail;
387     }
388 
389     UrlParseResult operator()(OrMorePaths /*unused*/)
390     {
391         return UrlParseResult::Done;
392     }
393 
394     explicit UrlSegmentMatcherVisitor(std::string_view segmentIn) :
395         segment(segmentIn)
396     {}
397 
398   private:
399     std::string_view segment;
400 };
401 
402 inline bool readUrlSegments(const boost::urls::url_view_base& url,
403                             std::initializer_list<UrlSegment> segments)
404 {
405     const boost::urls::segments_view& urlSegments = url.segments();
406 
407     if (!urlSegments.is_absolute())
408     {
409         return false;
410     }
411 
412     boost::urls::segments_view::const_iterator it = urlSegments.begin();
413     boost::urls::segments_view::const_iterator end = urlSegments.end();
414 
415     for (const auto& segment : segments)
416     {
417         if (it == end)
418         {
419             // If the request ends with an "any" path, this was successful
420             return std::holds_alternative<OrMorePaths>(segment);
421         }
422         UrlParseResult res = std::visit(UrlSegmentMatcherVisitor(*it), segment);
423         if (res == UrlParseResult::Done)
424         {
425             return true;
426         }
427         if (res == UrlParseResult::Fail)
428         {
429             return false;
430         }
431         it++;
432     }
433 
434     // There will be an empty segment at the end if the URI ends with a "/"
435     // e.g. /redfish/v1/Chassis/
436     if ((it != end) && urlSegments.back().empty())
437     {
438         it++;
439     }
440     return it == end;
441 }
442 
443 } // namespace details
444 
445 template <typename... Args>
446 inline bool readUrlSegments(const boost::urls::url_view_base& url,
447                             Args&&... args)
448 {
449     return details::readUrlSegments(url, {std::forward<Args>(args)...});
450 }
451 
452 inline boost::urls::url
453     replaceUrlSegment(const boost::urls::url_view_base& urlView,
454                       const uint replaceLoc, std::string_view newSegment)
455 {
456     const boost::urls::segments_view& urlSegments = urlView.segments();
457     boost::urls::url url("/");
458 
459     if (!urlSegments.is_absolute())
460     {
461         return url;
462     }
463 
464     boost::urls::segments_view::iterator it = urlSegments.begin();
465     boost::urls::segments_view::iterator end = urlSegments.end();
466 
467     for (uint idx = 0; it != end; it++, idx++)
468     {
469         if (idx == replaceLoc)
470         {
471             url.segments().push_back(newSegment);
472         }
473         else
474         {
475             url.segments().push_back(*it);
476         }
477     }
478 
479     return url;
480 }
481 
482 inline void setProtocolDefaults(boost::urls::url& url,
483                                 std::string_view protocol)
484 {
485     if (url.has_scheme())
486     {
487         return;
488     }
489     if (protocol == "Redfish" || protocol.empty())
490     {
491         if (url.port_number() == 443)
492         {
493             url.set_scheme("https");
494         }
495         if (url.port_number() == 80)
496         {
497             if (bmcwebInsecureEnableHttpPushStyleEventing)
498             {
499                 url.set_scheme("http");
500             }
501         }
502     }
503     else if (protocol == "SNMPv2c")
504     {
505         url.set_scheme("snmp");
506     }
507 }
508 
509 inline void setPortDefaults(boost::urls::url& url)
510 {
511     uint16_t port = url.port_number();
512     if (port != 0)
513     {
514         return;
515     }
516 
517     // If the user hasn't explicitly stated a port, pick one explicitly for them
518     // based on the protocol defaults
519     if (url.scheme() == "http")
520     {
521         url.set_port_number(80);
522     }
523     if (url.scheme() == "https")
524     {
525         url.set_port_number(443);
526     }
527     if (url.scheme() == "snmp")
528     {
529         url.set_port_number(162);
530     }
531 }
532 
533 } // namespace utility
534 } // namespace crow
535 
536 namespace nlohmann
537 {
538 template <std::derived_from<boost::urls::url_view_base> URL>
539 struct adl_serializer<URL>
540 {
541     // NOLINTNEXTLINE(readability-identifier-naming)
542     static void to_json(json& j, const URL& url)
543     {
544         j = url.buffer();
545     }
546 };
547 } // namespace nlohmann
548