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