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