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