blob: 97685262514eb35d0a84f69467371f386ed36b2b [file] [log] [blame]
Ed Tanous40e9b922024-09-10 13:50:16 -07001// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: Copyright OpenBMC Authors
Ed Tanous9bd21fc2018-04-26 16:08:56 -07003#pragma once
Tanousf00032d2018-11-05 01:18:10 -03004
Ed Tanousd7857202025-01-28 15:32:26 -08005#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>
Ed Tanous463b2932024-07-16 17:02:42 -070018
Ed Tanous18f8f602023-07-18 10:07:23 -070019#include <algorithm>
Ed Tanousd7857202025-01-28 15:32:26 -080020#include <array>
Nan Zhoud5c80ad2022-07-11 01:16:31 +000021#include <cctype>
Ed Tanous3544d2a2023-08-06 18:12:20 -070022#include <ranges>
Ed Tanous99351cd2022-08-07 16:42:51 -070023#include <span>
Nan Zhoud5c80ad2022-07-11 01:16:31 +000024#include <string_view>
25#include <vector>
26
Ed Tanous1abe55e2018-09-05 08:30:59 -070027namespace http_helpers
28{
George Liu647b3cd2021-07-05 12:43:56 +080029
Ed Tanous99351cd2022-08-07 16:42:51 -070030enum class ContentType
George Liu647b3cd2021-07-05 12:43:56 +080031{
Ed Tanous99351cd2022-08-07 16:42:51 -070032 NoMatch,
Ed Tanous4a0e1a02022-09-21 15:28:04 -070033 ANY, // Accepts: */*
Ed Tanous99351cd2022-08-07 16:42:51 -070034 CBOR,
35 HTML,
36 JSON,
37 OctetStream,
Ed Tanous6fde95f2023-06-01 07:33:34 -070038 EventStream,
Ed Tanous99351cd2022-08-07 16:42:51 -070039};
40
Ed Tanouse4628c82024-12-16 10:57:04 -080041inline ContentType getContentType(std::string_view contentTypeHeader)
Ed Tanous99351cd2022-08-07 16:42:51 -070042{
Ed Tanous463b2932024-07-16 17:02:42 -070043 using boost::spirit::x3::char_;
44 using boost::spirit::x3::lit;
Ed Tanouse4628c82024-12-16 10:57:04 -080045 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;
Ed Tanouse4628c82024-12-16 10:57:04 -080050
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
78inline 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;
Ed Tanous463b2932024-07-16 17:02:42 -070084 using boost::spirit::x3::omit;
85 using boost::spirit::x3::parse;
86 using boost::spirit::x3::space;
87 using boost::spirit::x3::symbols;
Ed Tanous463b2932024-07-16 17:02:42 -070088
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
Ed Tanous80e6e252024-12-11 11:28:39 -080099 auto typeCharset = +(char_("a-zA-Z0-9.+-"));
100
101 auto parameters = *(lit(';') >> typeCharset >> lit("=") >> typeCharset);
Ed Tanouse4628c82024-12-16 10:57:04 -0800102 auto mimeType = no_case[knownMimeType] |
Ed Tanous463b2932024-07-16 17:02:42 -0700103 omit[+typeCharset >> lit('/') >> +typeCharset];
104 auto parser = +(mimeType >> omit[parameters >> -char_(',') >> *space]);
Ed Tanouse4628c82024-12-16 10:57:04 -0800105 if (!parse(acceptsHeader.begin(), acceptsHeader.end(), parser, ct))
Ed Tanous1abe55e2018-09-05 08:30:59 -0700106 {
Ed Tanous463b2932024-07-16 17:02:42 -0700107 return ContentType::NoMatch;
George Liu647b3cd2021-07-05 12:43:56 +0800108 }
Ed Tanous463b2932024-07-16 17:02:42 -0700109
110 for (const ContentType parsedType : ct)
111 {
112 if (parsedType == ContentType::ANY)
113 {
114 return parsedType;
115 }
Ed Tanous276ede52024-08-28 15:57:45 -0700116 auto it = std::ranges::find(preferredOrder, parsedType);
117 if (it != preferredOrder.end())
Ed Tanous463b2932024-07-16 17:02:42 -0700118 {
119 return *it;
120 }
121 }
122
Ed Tanous99351cd2022-08-07 16:42:51 -0700123 return ContentType::NoMatch;
124}
125
Ed Tanous4a0e1a02022-09-21 15:28:04 -0700126inline bool isContentTypeAllowed(std::string_view header, ContentType type,
127 bool allowWildcard)
Ed Tanous99351cd2022-08-07 16:42:51 -0700128{
129 auto types = std::to_array({type});
Ed Tanous8ece0e42024-01-02 13:16:50 -0800130 ContentType allowed = getPreferredContentType(header, types);
Ed Tanous4a0e1a02022-09-21 15:28:04 -0700131 if (allowed == ContentType::ANY)
132 {
133 return allowWildcard;
134 }
135
136 return type == allowed;
George Liu647b3cd2021-07-05 12:43:56 +0800137}
138
Ed Tanous276ede52024-08-28 15:57:45 -0700139enum 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
Patrick Williams504af5a2025-02-03 14:29:03 -0500149inline Encoding getPreferredEncoding(
150 std::string_view acceptEncoding,
151 const std::span<const Encoding> availableEncodings)
Ed Tanous276ede52024-08-28 15:57:45 -0700152{
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
Ed Tanous23a21a12020-07-25 04:45:05 +0000207} // namespace http_helpers