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