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