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