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