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
getContentType(std::string_view contentTypeHeader)40 inline ContentType getContentType(std::string_view contentTypeHeader)
41 {
42 using boost::spirit::x3::char_;
43 using boost::spirit::x3::lit;
44 using boost::spirit::x3::no_case;
45 using boost::spirit::x3::omit;
46 using boost::spirit::x3::parse;
47 using boost::spirit::x3::space;
48 using boost::spirit::x3::symbols;
49
50 const symbols<ContentType> knownMimeType{
51 {"application/cbor", ContentType::CBOR},
52 {"application/json", ContentType::JSON},
53 {"application/octet-stream", ContentType::OctetStream},
54 {"text/event-stream", ContentType::EventStream},
55 {"text/html", ContentType::HTML}};
56
57 ContentType ct = ContentType::NoMatch;
58
59 auto typeCharset = +(char_("a-zA-Z0-9.+-"));
60
61 auto parameters =
62 *(lit(';') >> *space >> typeCharset >> lit("=") >> typeCharset);
63 auto parser = no_case[knownMimeType] >> omit[parameters];
64 std::string_view::iterator begin = contentTypeHeader.begin();
65 if (!parse(begin, contentTypeHeader.end(), parser, ct))
66 {
67 return ContentType::NoMatch;
68 }
69 if (begin != contentTypeHeader.end())
70 {
71 return ContentType::NoMatch;
72 }
73
74 return ct;
75 }
76
getPreferredContentType(std::string_view acceptsHeader,std::span<const ContentType> preferredOrder)77 inline ContentType getPreferredContentType(
78 std::string_view acceptsHeader, std::span<const ContentType> preferredOrder)
79 {
80 using boost::spirit::x3::char_;
81 using boost::spirit::x3::lit;
82 using boost::spirit::x3::no_case;
83 using boost::spirit::x3::omit;
84 using boost::spirit::x3::parse;
85 using boost::spirit::x3::space;
86 using boost::spirit::x3::symbols;
87
88 const symbols<ContentType> knownMimeType{
89 {"application/cbor", ContentType::CBOR},
90 {"application/json", ContentType::JSON},
91 {"application/octet-stream", ContentType::OctetStream},
92 {"text/html", ContentType::HTML},
93 {"text/event-stream", ContentType::EventStream},
94 {"*/*", ContentType::ANY}};
95
96 std::vector<ContentType> ct;
97
98 auto typeCharset = +(char_("a-zA-Z0-9.+-"));
99
100 auto parameters = *(lit(';') >> typeCharset >> lit("=") >> typeCharset);
101 auto mimeType = no_case[knownMimeType] |
102 omit[+typeCharset >> lit('/') >> +typeCharset];
103 auto parser = +(mimeType >> omit[parameters >> -char_(',') >> *space]);
104 if (!parse(acceptsHeader.begin(), acceptsHeader.end(), parser, ct))
105 {
106 return ContentType::NoMatch;
107 }
108
109 for (const ContentType parsedType : ct)
110 {
111 if (parsedType == ContentType::ANY)
112 {
113 return parsedType;
114 }
115 auto it = std::ranges::find(preferredOrder, parsedType);
116 if (it != preferredOrder.end())
117 {
118 return *it;
119 }
120 }
121
122 return ContentType::NoMatch;
123 }
124
isContentTypeAllowed(std::string_view header,ContentType type,bool allowWildcard)125 inline bool isContentTypeAllowed(std::string_view header, ContentType type,
126 bool allowWildcard)
127 {
128 auto types = std::to_array({type});
129 ContentType allowed = getPreferredContentType(header, types);
130 if (allowed == ContentType::ANY)
131 {
132 return allowWildcard;
133 }
134
135 return type == allowed;
136 }
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
getPreferredEncoding(std::string_view acceptEncoding,const std::span<const Encoding> availableEncodings)148 inline Encoding getPreferredEncoding(
149 std::string_view acceptEncoding,
150 const std::span<const Encoding> availableEncodings)
151 {
152 if (acceptEncoding.empty())
153 {
154 return Encoding::UnencodedBytes;
155 }
156
157 using boost::spirit::x3::char_;
158 using boost::spirit::x3::lit;
159 using boost::spirit::x3::omit;
160 using boost::spirit::x3::parse;
161 using boost::spirit::x3::space;
162 using boost::spirit::x3::symbols;
163 using boost::spirit::x3::uint_;
164
165 const symbols<Encoding> knownAcceptEncoding{{"gzip", Encoding::GZIP},
166 {"zstd", Encoding::ZSTD},
167 {"*", Encoding::ANY}};
168
169 std::vector<Encoding> ct;
170
171 auto parameters = *(lit(';') >> lit("q=") >> uint_ >> -(lit('.') >> uint_));
172 auto typeCharset = char_("a-zA-Z.+-");
173 auto encodeType = knownAcceptEncoding | omit[+typeCharset];
174 auto parser = +(encodeType >> omit[parameters >> -char_(',') >> *space]);
175 if (!parse(acceptEncoding.begin(), acceptEncoding.end(), parser, ct))
176 {
177 return Encoding::ParseError;
178 }
179
180 for (const Encoding parsedType : ct)
181 {
182 if (parsedType == Encoding::ANY)
183 {
184 if (!availableEncodings.empty())
185 {
186 return *availableEncodings.begin();
187 }
188 }
189 auto it = std::ranges::find(availableEncodings, parsedType);
190 if (it != availableEncodings.end())
191 {
192 return *it;
193 }
194 }
195
196 // Fall back to raw bytes if it was allowed
197 auto it = std::ranges::find(availableEncodings, Encoding::UnencodedBytes);
198 if (it != availableEncodings.end())
199 {
200 return *it;
201 }
202
203 return Encoding::NoMatch;
204 }
205
206 } // namespace http_helpers
207