blob: 6a5463d1e8dbaa30740b1abf4d23e1eb6658c2ab [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 Tanous463b2932024-07-16 17:02:42 -07005#include <boost/spirit/home/x3.hpp>
6
Ed Tanous18f8f602023-07-18 10:07:23 -07007#include <algorithm>
Nan Zhoud5c80ad2022-07-11 01:16:31 +00008#include <cctype>
9#include <iomanip>
10#include <ostream>
Ed Tanous3544d2a2023-08-06 18:12:20 -070011#include <ranges>
Ed Tanous99351cd2022-08-07 16:42:51 -070012#include <span>
Nan Zhoud5c80ad2022-07-11 01:16:31 +000013#include <string>
14#include <string_view>
15#include <vector>
16
Ed Tanous1abe55e2018-09-05 08:30:59 -070017namespace http_helpers
18{
George Liu647b3cd2021-07-05 12:43:56 +080019
Ed Tanous99351cd2022-08-07 16:42:51 -070020enum class ContentType
George Liu647b3cd2021-07-05 12:43:56 +080021{
Ed Tanous99351cd2022-08-07 16:42:51 -070022 NoMatch,
Ed Tanous4a0e1a02022-09-21 15:28:04 -070023 ANY, // Accepts: */*
Ed Tanous99351cd2022-08-07 16:42:51 -070024 CBOR,
25 HTML,
26 JSON,
27 OctetStream,
Ed Tanous6fde95f2023-06-01 07:33:34 -070028 EventStream,
Ed Tanous99351cd2022-08-07 16:42:51 -070029};
30
Ed Tanouse4628c82024-12-16 10:57:04 -080031inline ContentType getContentType(std::string_view contentTypeHeader)
Ed Tanous99351cd2022-08-07 16:42:51 -070032{
Ed Tanous463b2932024-07-16 17:02:42 -070033 using boost::spirit::x3::char_;
34 using boost::spirit::x3::lit;
Ed Tanouse4628c82024-12-16 10:57:04 -080035 using boost::spirit::x3::no_case;
36 using boost::spirit::x3::omit;
37 using boost::spirit::x3::parse;
38 using boost::spirit::x3::space;
39 using boost::spirit::x3::symbols;
40 using boost::spirit::x3::uint_;
41
42 const symbols<ContentType> knownMimeType{
43 {"application/cbor", ContentType::CBOR},
44 {"application/json", ContentType::JSON},
45 {"application/octet-stream", ContentType::OctetStream},
46 {"text/event-stream", ContentType::EventStream},
47 {"text/html", ContentType::HTML}};
48
49 ContentType ct = ContentType::NoMatch;
50
51 auto typeCharset = +(char_("a-zA-Z0-9.+-"));
52
53 auto parameters =
54 *(lit(';') >> *space >> typeCharset >> lit("=") >> typeCharset);
55 auto parser = no_case[knownMimeType] >> omit[parameters];
56 std::string_view::iterator begin = contentTypeHeader.begin();
57 if (!parse(begin, contentTypeHeader.end(), parser, ct))
58 {
59 return ContentType::NoMatch;
60 }
61 if (begin != contentTypeHeader.end())
62 {
63 return ContentType::NoMatch;
64 }
65
66 return ct;
67}
68
69inline ContentType getPreferredContentType(
70 std::string_view acceptsHeader, std::span<const ContentType> preferredOrder)
71{
72 using boost::spirit::x3::char_;
73 using boost::spirit::x3::lit;
74 using boost::spirit::x3::no_case;
Ed Tanous463b2932024-07-16 17:02:42 -070075 using boost::spirit::x3::omit;
76 using boost::spirit::x3::parse;
77 using boost::spirit::x3::space;
78 using boost::spirit::x3::symbols;
79 using boost::spirit::x3::uint_;
80
81 const symbols<ContentType> knownMimeType{
82 {"application/cbor", ContentType::CBOR},
83 {"application/json", ContentType::JSON},
84 {"application/octet-stream", ContentType::OctetStream},
85 {"text/html", ContentType::HTML},
86 {"text/event-stream", ContentType::EventStream},
87 {"*/*", ContentType::ANY}};
88
89 std::vector<ContentType> ct;
90
Ed Tanous80e6e252024-12-11 11:28:39 -080091 auto typeCharset = +(char_("a-zA-Z0-9.+-"));
92
93 auto parameters = *(lit(';') >> typeCharset >> lit("=") >> typeCharset);
Ed Tanouse4628c82024-12-16 10:57:04 -080094 auto mimeType = no_case[knownMimeType] |
Ed Tanous463b2932024-07-16 17:02:42 -070095 omit[+typeCharset >> lit('/') >> +typeCharset];
96 auto parser = +(mimeType >> omit[parameters >> -char_(',') >> *space]);
Ed Tanouse4628c82024-12-16 10:57:04 -080097 if (!parse(acceptsHeader.begin(), acceptsHeader.end(), parser, ct))
Ed Tanous1abe55e2018-09-05 08:30:59 -070098 {
Ed Tanous463b2932024-07-16 17:02:42 -070099 return ContentType::NoMatch;
George Liu647b3cd2021-07-05 12:43:56 +0800100 }
Ed Tanous463b2932024-07-16 17:02:42 -0700101
102 for (const ContentType parsedType : ct)
103 {
104 if (parsedType == ContentType::ANY)
105 {
106 return parsedType;
107 }
Ed Tanous276ede52024-08-28 15:57:45 -0700108 auto it = std::ranges::find(preferredOrder, parsedType);
109 if (it != preferredOrder.end())
Ed Tanous463b2932024-07-16 17:02:42 -0700110 {
111 return *it;
112 }
113 }
114
Ed Tanous99351cd2022-08-07 16:42:51 -0700115 return ContentType::NoMatch;
116}
117
Ed Tanous4a0e1a02022-09-21 15:28:04 -0700118inline bool isContentTypeAllowed(std::string_view header, ContentType type,
119 bool allowWildcard)
Ed Tanous99351cd2022-08-07 16:42:51 -0700120{
121 auto types = std::to_array({type});
Ed Tanous8ece0e42024-01-02 13:16:50 -0800122 ContentType allowed = getPreferredContentType(header, types);
Ed Tanous4a0e1a02022-09-21 15:28:04 -0700123 if (allowed == ContentType::ANY)
124 {
125 return allowWildcard;
126 }
127
128 return type == allowed;
George Liu647b3cd2021-07-05 12:43:56 +0800129}
130
Ed Tanous276ede52024-08-28 15:57:45 -0700131enum class Encoding
132{
133 ParseError,
134 NoMatch,
135 UnencodedBytes,
136 GZIP,
137 ZSTD,
138 ANY, // represents *. Never returned. Only used for string matching
139};
140
141inline Encoding
142 getPreferredEncoding(std::string_view acceptEncoding,
143 const std::span<const Encoding> availableEncodings)
144{
145 if (acceptEncoding.empty())
146 {
147 return Encoding::UnencodedBytes;
148 }
149
150 using boost::spirit::x3::char_;
151 using boost::spirit::x3::lit;
152 using boost::spirit::x3::omit;
153 using boost::spirit::x3::parse;
154 using boost::spirit::x3::space;
155 using boost::spirit::x3::symbols;
156 using boost::spirit::x3::uint_;
157
158 const symbols<Encoding> knownAcceptEncoding{{"gzip", Encoding::GZIP},
159 {"zstd", Encoding::ZSTD},
160 {"*", Encoding::ANY}};
161
162 std::vector<Encoding> ct;
163
164 auto parameters = *(lit(';') >> lit("q=") >> uint_ >> -(lit('.') >> uint_));
165 auto typeCharset = char_("a-zA-Z.+-");
166 auto encodeType = knownAcceptEncoding | omit[+typeCharset];
167 auto parser = +(encodeType >> omit[parameters >> -char_(',') >> *space]);
168 if (!parse(acceptEncoding.begin(), acceptEncoding.end(), parser, ct))
169 {
170 return Encoding::ParseError;
171 }
172
173 for (const Encoding parsedType : ct)
174 {
175 if (parsedType == Encoding::ANY)
176 {
177 if (!availableEncodings.empty())
178 {
179 return *availableEncodings.begin();
180 }
181 }
182 auto it = std::ranges::find(availableEncodings, parsedType);
183 if (it != availableEncodings.end())
184 {
185 return *it;
186 }
187 }
188
189 // Fall back to raw bytes if it was allowed
190 auto it = std::ranges::find(availableEncodings, Encoding::UnencodedBytes);
191 if (it != availableEncodings.end())
192 {
193 return *it;
194 }
195
196 return Encoding::NoMatch;
197}
198
Ed Tanous23a21a12020-07-25 04:45:05 +0000199} // namespace http_helpers