xref: /openbmc/bmcweb/include/http_utility.hpp (revision 504af5a0568171b72caf13234cc81380b261fa21)
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