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