140e9b92eSEd Tanous // SPDX-License-Identifier: Apache-2.0
240e9b92eSEd Tanous // SPDX-FileCopyrightText: Copyright OpenBMC Authors
39bd21fc1SEd Tanous #pragma once
4f00032dbSTanous
5d7857201SEd Tanous #include <boost/spirit/home/x3/char/char.hpp>
6d7857201SEd Tanous #include <boost/spirit/home/x3/char/char_class.hpp>
7d7857201SEd Tanous #include <boost/spirit/home/x3/core/parse.hpp>
8d7857201SEd Tanous #include <boost/spirit/home/x3/directive/no_case.hpp>
9d7857201SEd Tanous #include <boost/spirit/home/x3/directive/omit.hpp>
10d7857201SEd Tanous #include <boost/spirit/home/x3/numeric/uint.hpp>
11d7857201SEd Tanous #include <boost/spirit/home/x3/operator/alternative.hpp>
12d7857201SEd Tanous #include <boost/spirit/home/x3/operator/kleene.hpp>
13d7857201SEd Tanous #include <boost/spirit/home/x3/operator/optional.hpp>
14d7857201SEd Tanous #include <boost/spirit/home/x3/operator/plus.hpp>
15d7857201SEd Tanous #include <boost/spirit/home/x3/operator/sequence.hpp>
16d7857201SEd Tanous #include <boost/spirit/home/x3/string/literal_string.hpp>
17d7857201SEd Tanous #include <boost/spirit/home/x3/string/symbols.hpp>
18463b2934SEd Tanous
1918f8f608SEd Tanous #include <algorithm>
20d7857201SEd Tanous #include <array>
21d5c80ad9SNan Zhou #include <cctype>
223544d2a7SEd Tanous #include <ranges>
2399351cd8SEd Tanous #include <span>
24d5c80ad9SNan Zhou #include <string_view>
25d5c80ad9SNan Zhou #include <vector>
26d5c80ad9SNan Zhou
271abe55efSEd Tanous namespace http_helpers
281abe55efSEd Tanous {
29647b3cdcSGeorge Liu
3099351cd8SEd Tanous enum class ContentType
3199351cd8SEd Tanous {
3299351cd8SEd Tanous NoMatch,
334a0e1a0cSEd Tanous ANY, // Accepts: */*
3499351cd8SEd Tanous CBOR,
3599351cd8SEd Tanous HTML,
3699351cd8SEd Tanous JSON,
3799351cd8SEd Tanous OctetStream,
386fde95faSEd Tanous EventStream,
3999351cd8SEd Tanous };
40647b3cdcSGeorge Liu
getContentType(std::string_view contentTypeHeader)41e4628c81SEd Tanous inline ContentType getContentType(std::string_view contentTypeHeader)
42647b3cdcSGeorge Liu {
43463b2934SEd Tanous using boost::spirit::x3::char_;
44463b2934SEd Tanous using boost::spirit::x3::lit;
45e4628c81SEd Tanous using boost::spirit::x3::no_case;
46e4628c81SEd Tanous using boost::spirit::x3::omit;
47e4628c81SEd Tanous using boost::spirit::x3::parse;
48e4628c81SEd Tanous using boost::spirit::x3::space;
49e4628c81SEd Tanous using boost::spirit::x3::symbols;
50e4628c81SEd Tanous using boost::spirit::x3::uint_;
51e4628c81SEd Tanous
52e4628c81SEd Tanous const symbols<ContentType> knownMimeType{
53e4628c81SEd Tanous {"application/cbor", ContentType::CBOR},
54e4628c81SEd Tanous {"application/json", ContentType::JSON},
55e4628c81SEd Tanous {"application/octet-stream", ContentType::OctetStream},
56e4628c81SEd Tanous {"text/event-stream", ContentType::EventStream},
57e4628c81SEd Tanous {"text/html", ContentType::HTML}};
58e4628c81SEd Tanous
59e4628c81SEd Tanous ContentType ct = ContentType::NoMatch;
60e4628c81SEd Tanous
61e4628c81SEd Tanous auto typeCharset = +(char_("a-zA-Z0-9.+-"));
62e4628c81SEd Tanous
63e4628c81SEd Tanous auto parameters =
64e4628c81SEd Tanous *(lit(';') >> *space >> typeCharset >> lit("=") >> typeCharset);
65e4628c81SEd Tanous auto parser = no_case[knownMimeType] >> omit[parameters];
66e4628c81SEd Tanous std::string_view::iterator begin = contentTypeHeader.begin();
67e4628c81SEd Tanous if (!parse(begin, contentTypeHeader.end(), parser, ct))
68e4628c81SEd Tanous {
69e4628c81SEd Tanous return ContentType::NoMatch;
70e4628c81SEd Tanous }
71e4628c81SEd Tanous if (begin != contentTypeHeader.end())
72e4628c81SEd Tanous {
73e4628c81SEd Tanous return ContentType::NoMatch;
74e4628c81SEd Tanous }
75e4628c81SEd Tanous
76e4628c81SEd Tanous return ct;
77e4628c81SEd Tanous }
78e4628c81SEd Tanous
getPreferredContentType(std::string_view acceptsHeader,std::span<const ContentType> preferredOrder)79e4628c81SEd Tanous inline ContentType getPreferredContentType(
80e4628c81SEd Tanous std::string_view acceptsHeader, std::span<const ContentType> preferredOrder)
81e4628c81SEd Tanous {
82e4628c81SEd Tanous using boost::spirit::x3::char_;
83e4628c81SEd Tanous using boost::spirit::x3::lit;
84e4628c81SEd Tanous using boost::spirit::x3::no_case;
85463b2934SEd Tanous using boost::spirit::x3::omit;
86463b2934SEd Tanous using boost::spirit::x3::parse;
87463b2934SEd Tanous using boost::spirit::x3::space;
88463b2934SEd Tanous using boost::spirit::x3::symbols;
89463b2934SEd Tanous using boost::spirit::x3::uint_;
906b5e77d6SEd Tanous
91463b2934SEd Tanous const symbols<ContentType> knownMimeType{
9299351cd8SEd Tanous {"application/cbor", ContentType::CBOR},
9399351cd8SEd Tanous {"application/json", ContentType::JSON},
9499351cd8SEd Tanous {"application/octet-stream", ContentType::OctetStream},
9599351cd8SEd Tanous {"text/html", ContentType::HTML},
966fde95faSEd Tanous {"text/event-stream", ContentType::EventStream},
97463b2934SEd Tanous {"*/*", ContentType::ANY}};
9899351cd8SEd Tanous
99463b2934SEd Tanous std::vector<ContentType> ct;
10099351cd8SEd Tanous
10180e6e25eSEd Tanous auto typeCharset = +(char_("a-zA-Z0-9.+-"));
10280e6e25eSEd Tanous
10380e6e25eSEd Tanous auto parameters = *(lit(';') >> typeCharset >> lit("=") >> typeCharset);
104e4628c81SEd Tanous auto mimeType = no_case[knownMimeType] |
105463b2934SEd Tanous omit[+typeCharset >> lit('/') >> +typeCharset];
106463b2934SEd Tanous auto parser = +(mimeType >> omit[parameters >> -char_(',') >> *space]);
107e4628c81SEd Tanous if (!parse(acceptsHeader.begin(), acceptsHeader.end(), parser, ct))
10899351cd8SEd Tanous {
109463b2934SEd Tanous return ContentType::NoMatch;
11099351cd8SEd Tanous }
11199351cd8SEd Tanous
112463b2934SEd Tanous for (const ContentType parsedType : ct)
11399351cd8SEd Tanous {
114463b2934SEd Tanous if (parsedType == ContentType::ANY)
115463b2934SEd Tanous {
116463b2934SEd Tanous return parsedType;
11799351cd8SEd Tanous }
118276ede55SEd Tanous auto it = std::ranges::find(preferredOrder, parsedType);
119276ede55SEd Tanous if (it != preferredOrder.end())
120463b2934SEd Tanous {
121463b2934SEd Tanous return *it;
12299351cd8SEd Tanous }
123463b2934SEd Tanous }
124463b2934SEd Tanous
12599351cd8SEd Tanous return ContentType::NoMatch;
12699351cd8SEd Tanous }
12799351cd8SEd Tanous
isContentTypeAllowed(std::string_view header,ContentType type,bool allowWildcard)1284a0e1a0cSEd Tanous inline bool isContentTypeAllowed(std::string_view header, ContentType type,
1294a0e1a0cSEd Tanous bool allowWildcard)
13099351cd8SEd Tanous {
13199351cd8SEd Tanous auto types = std::to_array({type});
1328ece0e45SEd Tanous ContentType allowed = getPreferredContentType(header, types);
1334a0e1a0cSEd Tanous if (allowed == ContentType::ANY)
1344a0e1a0cSEd Tanous {
1354a0e1a0cSEd Tanous return allowWildcard;
1364a0e1a0cSEd Tanous }
1374a0e1a0cSEd Tanous
1384a0e1a0cSEd Tanous return type == allowed;
139647b3cdcSGeorge Liu }
140647b3cdcSGeorge Liu
141276ede55SEd Tanous enum class Encoding
142276ede55SEd Tanous {
143276ede55SEd Tanous ParseError,
144276ede55SEd Tanous NoMatch,
145276ede55SEd Tanous UnencodedBytes,
146276ede55SEd Tanous GZIP,
147276ede55SEd Tanous ZSTD,
148276ede55SEd Tanous ANY, // represents *. Never returned. Only used for string matching
149276ede55SEd Tanous };
150276ede55SEd Tanous
getPreferredEncoding(std::string_view acceptEncoding,const std::span<const Encoding> availableEncodings)151*504af5a0SPatrick Williams inline Encoding getPreferredEncoding(
152*504af5a0SPatrick Williams std::string_view acceptEncoding,
153276ede55SEd Tanous const std::span<const Encoding> availableEncodings)
154276ede55SEd Tanous {
155276ede55SEd Tanous if (acceptEncoding.empty())
156276ede55SEd Tanous {
157276ede55SEd Tanous return Encoding::UnencodedBytes;
158276ede55SEd Tanous }
159276ede55SEd Tanous
160276ede55SEd Tanous using boost::spirit::x3::char_;
161276ede55SEd Tanous using boost::spirit::x3::lit;
162276ede55SEd Tanous using boost::spirit::x3::omit;
163276ede55SEd Tanous using boost::spirit::x3::parse;
164276ede55SEd Tanous using boost::spirit::x3::space;
165276ede55SEd Tanous using boost::spirit::x3::symbols;
166276ede55SEd Tanous using boost::spirit::x3::uint_;
167276ede55SEd Tanous
168276ede55SEd Tanous const symbols<Encoding> knownAcceptEncoding{{"gzip", Encoding::GZIP},
169276ede55SEd Tanous {"zstd", Encoding::ZSTD},
170276ede55SEd Tanous {"*", Encoding::ANY}};
171276ede55SEd Tanous
172276ede55SEd Tanous std::vector<Encoding> ct;
173276ede55SEd Tanous
174276ede55SEd Tanous auto parameters = *(lit(';') >> lit("q=") >> uint_ >> -(lit('.') >> uint_));
175276ede55SEd Tanous auto typeCharset = char_("a-zA-Z.+-");
176276ede55SEd Tanous auto encodeType = knownAcceptEncoding | omit[+typeCharset];
177276ede55SEd Tanous auto parser = +(encodeType >> omit[parameters >> -char_(',') >> *space]);
178276ede55SEd Tanous if (!parse(acceptEncoding.begin(), acceptEncoding.end(), parser, ct))
179276ede55SEd Tanous {
180276ede55SEd Tanous return Encoding::ParseError;
181276ede55SEd Tanous }
182276ede55SEd Tanous
183276ede55SEd Tanous for (const Encoding parsedType : ct)
184276ede55SEd Tanous {
185276ede55SEd Tanous if (parsedType == Encoding::ANY)
186276ede55SEd Tanous {
187276ede55SEd Tanous if (!availableEncodings.empty())
188276ede55SEd Tanous {
189276ede55SEd Tanous return *availableEncodings.begin();
190276ede55SEd Tanous }
191276ede55SEd Tanous }
192276ede55SEd Tanous auto it = std::ranges::find(availableEncodings, parsedType);
193276ede55SEd Tanous if (it != availableEncodings.end())
194276ede55SEd Tanous {
195276ede55SEd Tanous return *it;
196276ede55SEd Tanous }
197276ede55SEd Tanous }
198276ede55SEd Tanous
199276ede55SEd Tanous // Fall back to raw bytes if it was allowed
200276ede55SEd Tanous auto it = std::ranges::find(availableEncodings, Encoding::UnencodedBytes);
201276ede55SEd Tanous if (it != availableEncodings.end())
202276ede55SEd Tanous {
203276ede55SEd Tanous return *it;
204276ede55SEd Tanous }
205276ede55SEd Tanous
206276ede55SEd Tanous return Encoding::NoMatch;
207276ede55SEd Tanous }
208276ede55SEd Tanous
2099bd21fc1SEd Tanous } // namespace http_helpers
210