xref: /openbmc/bmcweb/include/http_utility.hpp (revision f8fe53e7114ab10c9059377541277739ace5c1ff)
1 #pragma once
2 
3 #include <boost/algorithm/string/classification.hpp>
4 #include <boost/algorithm/string/constants.hpp>
5 #include <boost/iterator/iterator_facade.hpp>
6 #include <boost/type_index/type_index_facade.hpp>
7 
8 #include <cctype>
9 #include <iomanip>
10 #include <ostream>
11 #include <span>
12 #include <string>
13 #include <string_view>
14 #include <vector>
15 
16 // IWYU pragma: no_include <ctype.h>
17 
18 namespace http_helpers
19 {
20 
21 enum class ContentType
22 {
23     NoMatch,
24     ANY, // Accepts: */*
25     CBOR,
26     HTML,
27     JSON,
28     OctetStream,
29 };
30 
31 struct ContentTypePair
32 {
33     std::string_view contentTypeString;
34     ContentType contentTypeEnum;
35 };
36 
37 constexpr std::array<ContentTypePair, 4> contentTypes{{
38     {"application/cbor", ContentType::CBOR},
39     {"application/json", ContentType::JSON},
40     {"application/octet-stream", ContentType::OctetStream},
41     {"text/html", ContentType::HTML},
42 }};
43 
44 inline ContentType getPreferedContentType(std::string_view header,
45                                           std::span<ContentType> preferedOrder)
46 {
47     size_t lastIndex = 0;
48     while (lastIndex < header.size() + 1)
49     {
50         size_t index = header.find(',', lastIndex);
51         if (index == std::string_view::npos)
52         {
53             index = header.size();
54         }
55         std::string_view encoding = header.substr(lastIndex, index);
56 
57         if (!header.empty())
58         {
59             header.remove_prefix(1);
60         }
61         lastIndex = index + 1;
62         // ignore any q-factor weighting (;q=)
63         std::size_t separator = encoding.find(";q=");
64 
65         if (separator != std::string_view::npos)
66         {
67             encoding = encoding.substr(0, separator);
68         }
69         // If the client allows any encoding, given them the first one on the
70         // servers list
71         if (encoding == "*/*")
72         {
73             return ContentType::ANY;
74         }
75         const auto* knownContentType =
76             std::find_if(contentTypes.begin(), contentTypes.end(),
77                          [encoding](const ContentTypePair& pair) {
78             return pair.contentTypeString == encoding;
79             });
80 
81         if (knownContentType == contentTypes.end())
82         {
83             // not able to find content type in list
84             continue;
85         }
86 
87         // Not one of the types requested
88         if (std::find(preferedOrder.begin(), preferedOrder.end(),
89                       knownContentType->contentTypeEnum) == preferedOrder.end())
90         {
91             continue;
92         }
93         return knownContentType->contentTypeEnum;
94     }
95     return ContentType::NoMatch;
96 }
97 
98 inline bool isContentTypeAllowed(std::string_view header, ContentType type,
99                                  bool allowWildcard)
100 {
101     auto types = std::to_array({type});
102     ContentType allowed = getPreferedContentType(header, types);
103     if (allowed == ContentType::ANY)
104     {
105         return allowWildcard;
106     }
107 
108     return type == allowed;
109 }
110 
111 inline std::string urlEncode(const std::string_view value)
112 {
113     std::ostringstream escaped;
114     escaped.fill('0');
115     escaped << std::hex;
116 
117     for (const char c : value)
118     {
119         // Keep alphanumeric and other accepted characters intact
120         if ((isalnum(c) != 0) || c == '-' || c == '_' || c == '.' || c == '~')
121         {
122             escaped << c;
123             continue;
124         }
125 
126         // Any other characters are percent-encoded
127         escaped << std::uppercase;
128         escaped << '%' << std::setw(2)
129                 << static_cast<int>(static_cast<unsigned char>(c));
130         escaped << std::nouppercase;
131     }
132 
133     return escaped.str();
134 }
135 } // namespace http_helpers
136