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