xref: /openbmc/bmcweb/include/http_utility.hpp (revision 56431b29998d58c43b101b5f55401e505c85be5e)
1 #pragma once
2 
3 #include <boost/spirit/home/x3.hpp>
4 
5 #include <algorithm>
6 #include <cctype>
7 #include <iomanip>
8 #include <ostream>
9 #include <ranges>
10 #include <span>
11 #include <string>
12 #include <string_view>
13 #include <vector>
14 
15 namespace http_helpers
16 {
17 
18 enum class ContentType
19 {
20     NoMatch,
21     ANY, // Accepts: */*
22     CBOR,
23     HTML,
24     JSON,
25     OctetStream,
26     EventStream,
27 };
28 
29 inline ContentType getContentType(std::string_view contentTypeHeader)
30 {
31     using boost::spirit::x3::char_;
32     using boost::spirit::x3::lit;
33     using boost::spirit::x3::no_case;
34     using boost::spirit::x3::omit;
35     using boost::spirit::x3::parse;
36     using boost::spirit::x3::space;
37     using boost::spirit::x3::symbols;
38     using boost::spirit::x3::uint_;
39 
40     const symbols<ContentType> knownMimeType{
41         {"application/cbor", ContentType::CBOR},
42         {"application/json", ContentType::JSON},
43         {"application/octet-stream", ContentType::OctetStream},
44         {"text/event-stream", ContentType::EventStream},
45         {"text/html", ContentType::HTML}};
46 
47     ContentType ct = ContentType::NoMatch;
48 
49     auto typeCharset = +(char_("a-zA-Z0-9.+-"));
50 
51     auto parameters =
52         *(lit(';') >> *space >> typeCharset >> lit("=") >> typeCharset);
53     auto parser = no_case[knownMimeType] >> omit[parameters];
54     std::string_view::iterator begin = contentTypeHeader.begin();
55     if (!parse(begin, contentTypeHeader.end(), parser, ct))
56     {
57         return ContentType::NoMatch;
58     }
59     if (begin != contentTypeHeader.end())
60     {
61         return ContentType::NoMatch;
62     }
63 
64     return ct;
65 }
66 
67 inline ContentType getPreferredContentType(
68     std::string_view acceptsHeader, std::span<const ContentType> preferredOrder)
69 {
70     using boost::spirit::x3::char_;
71     using boost::spirit::x3::lit;
72     using boost::spirit::x3::no_case;
73     using boost::spirit::x3::omit;
74     using boost::spirit::x3::parse;
75     using boost::spirit::x3::space;
76     using boost::spirit::x3::symbols;
77     using boost::spirit::x3::uint_;
78 
79     const symbols<ContentType> knownMimeType{
80         {"application/cbor", ContentType::CBOR},
81         {"application/json", ContentType::JSON},
82         {"application/octet-stream", ContentType::OctetStream},
83         {"text/html", ContentType::HTML},
84         {"text/event-stream", ContentType::EventStream},
85         {"*/*", ContentType::ANY}};
86 
87     std::vector<ContentType> ct;
88 
89     auto typeCharset = +(char_("a-zA-Z0-9.+-"));
90 
91     auto parameters = *(lit(';') >> typeCharset >> lit("=") >> typeCharset);
92     auto mimeType = no_case[knownMimeType] |
93                     omit[+typeCharset >> lit('/') >> +typeCharset];
94     auto parser = +(mimeType >> omit[parameters >> -char_(',') >> *space]);
95     if (!parse(acceptsHeader.begin(), acceptsHeader.end(), parser, ct))
96     {
97         return ContentType::NoMatch;
98     }
99 
100     for (const ContentType parsedType : ct)
101     {
102         if (parsedType == ContentType::ANY)
103         {
104             return parsedType;
105         }
106         auto it = std::ranges::find(preferredOrder, parsedType);
107         if (it != preferredOrder.end())
108         {
109             return *it;
110         }
111     }
112 
113     return ContentType::NoMatch;
114 }
115 
116 inline bool isContentTypeAllowed(std::string_view header, ContentType type,
117                                  bool allowWildcard)
118 {
119     auto types = std::to_array({type});
120     ContentType allowed = getPreferredContentType(header, types);
121     if (allowed == ContentType::ANY)
122     {
123         return allowWildcard;
124     }
125 
126     return type == allowed;
127 }
128 
129 enum class Encoding
130 {
131     ParseError,
132     NoMatch,
133     UnencodedBytes,
134     GZIP,
135     ZSTD,
136     ANY, // represents *. Never returned.  Only used for string matching
137 };
138 
139 inline Encoding
140     getPreferredEncoding(std::string_view acceptEncoding,
141                          const std::span<const Encoding> availableEncodings)
142 {
143     if (acceptEncoding.empty())
144     {
145         return Encoding::UnencodedBytes;
146     }
147 
148     using boost::spirit::x3::char_;
149     using boost::spirit::x3::lit;
150     using boost::spirit::x3::omit;
151     using boost::spirit::x3::parse;
152     using boost::spirit::x3::space;
153     using boost::spirit::x3::symbols;
154     using boost::spirit::x3::uint_;
155 
156     const symbols<Encoding> knownAcceptEncoding{{"gzip", Encoding::GZIP},
157                                                 {"zstd", Encoding::ZSTD},
158                                                 {"*", Encoding::ANY}};
159 
160     std::vector<Encoding> ct;
161 
162     auto parameters = *(lit(';') >> lit("q=") >> uint_ >> -(lit('.') >> uint_));
163     auto typeCharset = char_("a-zA-Z.+-");
164     auto encodeType = knownAcceptEncoding | omit[+typeCharset];
165     auto parser = +(encodeType >> omit[parameters >> -char_(',') >> *space]);
166     if (!parse(acceptEncoding.begin(), acceptEncoding.end(), parser, ct))
167     {
168         return Encoding::ParseError;
169     }
170 
171     for (const Encoding parsedType : ct)
172     {
173         if (parsedType == Encoding::ANY)
174         {
175             if (!availableEncodings.empty())
176             {
177                 return *availableEncodings.begin();
178             }
179         }
180         auto it = std::ranges::find(availableEncodings, parsedType);
181         if (it != availableEncodings.end())
182         {
183             return *it;
184         }
185     }
186 
187     // Fall back to raw bytes if it was allowed
188     auto it = std::ranges::find(availableEncodings, Encoding::UnencodedBytes);
189     if (it != availableEncodings.end())
190     {
191         return *it;
192     }
193 
194     return Encoding::NoMatch;
195 }
196 
197 } // namespace http_helpers
198