blob: 681afa11f6f31b338134d9c97b7193ba02010a5e [file] [log] [blame]
Ed Tanous9bd21fc2018-04-26 16:08:56 -07001#pragma once
Tanousf00032d2018-11-05 01:18:10 -03002
Ed Tanous463b2932024-07-16 17:02:42 -07003#include <boost/spirit/home/x3.hpp>
4
Ed Tanous18f8f602023-07-18 10:07:23 -07005#include <algorithm>
Nan Zhoud5c80ad2022-07-11 01:16:31 +00006#include <cctype>
7#include <iomanip>
8#include <ostream>
Ed Tanous3544d2a2023-08-06 18:12:20 -07009#include <ranges>
Ed Tanous99351cd2022-08-07 16:42:51 -070010#include <span>
Nan Zhoud5c80ad2022-07-11 01:16:31 +000011#include <string>
12#include <string_view>
13#include <vector>
14
Ed Tanous1abe55e2018-09-05 08:30:59 -070015namespace http_helpers
16{
George Liu647b3cd2021-07-05 12:43:56 +080017
Ed Tanous99351cd2022-08-07 16:42:51 -070018enum class ContentType
George Liu647b3cd2021-07-05 12:43:56 +080019{
Ed Tanous99351cd2022-08-07 16:42:51 -070020 NoMatch,
Ed Tanous4a0e1a02022-09-21 15:28:04 -070021 ANY, // Accepts: */*
Ed Tanous99351cd2022-08-07 16:42:51 -070022 CBOR,
23 HTML,
24 JSON,
25 OctetStream,
Ed Tanous6fde95f2023-06-01 07:33:34 -070026 EventStream,
Ed Tanous99351cd2022-08-07 16:42:51 -070027};
28
Ed Tanouse4628c82024-12-16 10:57:04 -080029inline ContentType getContentType(std::string_view contentTypeHeader)
Ed Tanous99351cd2022-08-07 16:42:51 -070030{
Ed Tanous463b2932024-07-16 17:02:42 -070031 using boost::spirit::x3::char_;
32 using boost::spirit::x3::lit;
Ed Tanouse4628c82024-12-16 10:57:04 -080033 using boost::spirit::x3::no_case;
34 using boost::spirit::x3::omit;
35 using boost::spirit::x3::parse;
36 using boost::spirit::x3::space;
37 using boost::spirit::x3::symbols;
38 using boost::spirit::x3::uint_;
39
40 const symbols<ContentType> knownMimeType{
41 {"application/cbor", ContentType::CBOR},
42 {"application/json", ContentType::JSON},
43 {"application/octet-stream", ContentType::OctetStream},
44 {"text/event-stream", ContentType::EventStream},
45 {"text/html", ContentType::HTML}};
46
47 ContentType ct = ContentType::NoMatch;
48
49 auto typeCharset = +(char_("a-zA-Z0-9.+-"));
50
51 auto parameters =
52 *(lit(';') >> *space >> typeCharset >> lit("=") >> typeCharset);
53 auto parser = no_case[knownMimeType] >> omit[parameters];
54 std::string_view::iterator begin = contentTypeHeader.begin();
55 if (!parse(begin, contentTypeHeader.end(), parser, ct))
56 {
57 return ContentType::NoMatch;
58 }
59 if (begin != contentTypeHeader.end())
60 {
61 return ContentType::NoMatch;
62 }
63
64 return ct;
65}
66
67inline ContentType getPreferredContentType(
68 std::string_view acceptsHeader, std::span<const ContentType> preferredOrder)
69{
70 using boost::spirit::x3::char_;
71 using boost::spirit::x3::lit;
72 using boost::spirit::x3::no_case;
Ed Tanous463b2932024-07-16 17:02:42 -070073 using boost::spirit::x3::omit;
74 using boost::spirit::x3::parse;
75 using boost::spirit::x3::space;
76 using boost::spirit::x3::symbols;
77 using boost::spirit::x3::uint_;
78
79 const symbols<ContentType> knownMimeType{
80 {"application/cbor", ContentType::CBOR},
81 {"application/json", ContentType::JSON},
82 {"application/octet-stream", ContentType::OctetStream},
83 {"text/html", ContentType::HTML},
84 {"text/event-stream", ContentType::EventStream},
85 {"*/*", ContentType::ANY}};
86
87 std::vector<ContentType> ct;
88
Ed Tanous80e6e252024-12-11 11:28:39 -080089 auto typeCharset = +(char_("a-zA-Z0-9.+-"));
90
91 auto parameters = *(lit(';') >> typeCharset >> lit("=") >> typeCharset);
Ed Tanouse4628c82024-12-16 10:57:04 -080092 auto mimeType = no_case[knownMimeType] |
Ed Tanous463b2932024-07-16 17:02:42 -070093 omit[+typeCharset >> lit('/') >> +typeCharset];
94 auto parser = +(mimeType >> omit[parameters >> -char_(',') >> *space]);
Ed Tanouse4628c82024-12-16 10:57:04 -080095 if (!parse(acceptsHeader.begin(), acceptsHeader.end(), parser, ct))
Ed Tanous1abe55e2018-09-05 08:30:59 -070096 {
Ed Tanous463b2932024-07-16 17:02:42 -070097 return ContentType::NoMatch;
George Liu647b3cd2021-07-05 12:43:56 +080098 }
Ed Tanous463b2932024-07-16 17:02:42 -070099
100 for (const ContentType parsedType : ct)
101 {
102 if (parsedType == ContentType::ANY)
103 {
104 return parsedType;
105 }
Ed Tanous276ede52024-08-28 15:57:45 -0700106 auto it = std::ranges::find(preferredOrder, parsedType);
107 if (it != preferredOrder.end())
Ed Tanous463b2932024-07-16 17:02:42 -0700108 {
109 return *it;
110 }
111 }
112
Ed Tanous99351cd2022-08-07 16:42:51 -0700113 return ContentType::NoMatch;
114}
115
Ed Tanous4a0e1a02022-09-21 15:28:04 -0700116inline bool isContentTypeAllowed(std::string_view header, ContentType type,
117 bool allowWildcard)
Ed Tanous99351cd2022-08-07 16:42:51 -0700118{
119 auto types = std::to_array({type});
Ed Tanous8ece0e42024-01-02 13:16:50 -0800120 ContentType allowed = getPreferredContentType(header, types);
Ed Tanous4a0e1a02022-09-21 15:28:04 -0700121 if (allowed == ContentType::ANY)
122 {
123 return allowWildcard;
124 }
125
126 return type == allowed;
George Liu647b3cd2021-07-05 12:43:56 +0800127}
128
Ed Tanous276ede52024-08-28 15:57:45 -0700129enum class Encoding
130{
131 ParseError,
132 NoMatch,
133 UnencodedBytes,
134 GZIP,
135 ZSTD,
136 ANY, // represents *. Never returned. Only used for string matching
137};
138
139inline Encoding
140 getPreferredEncoding(std::string_view acceptEncoding,
141 const std::span<const Encoding> availableEncodings)
142{
143 if (acceptEncoding.empty())
144 {
145 return Encoding::UnencodedBytes;
146 }
147
148 using boost::spirit::x3::char_;
149 using boost::spirit::x3::lit;
150 using boost::spirit::x3::omit;
151 using boost::spirit::x3::parse;
152 using boost::spirit::x3::space;
153 using boost::spirit::x3::symbols;
154 using boost::spirit::x3::uint_;
155
156 const symbols<Encoding> knownAcceptEncoding{{"gzip", Encoding::GZIP},
157 {"zstd", Encoding::ZSTD},
158 {"*", Encoding::ANY}};
159
160 std::vector<Encoding> ct;
161
162 auto parameters = *(lit(';') >> lit("q=") >> uint_ >> -(lit('.') >> uint_));
163 auto typeCharset = char_("a-zA-Z.+-");
164 auto encodeType = knownAcceptEncoding | omit[+typeCharset];
165 auto parser = +(encodeType >> omit[parameters >> -char_(',') >> *space]);
166 if (!parse(acceptEncoding.begin(), acceptEncoding.end(), parser, ct))
167 {
168 return Encoding::ParseError;
169 }
170
171 for (const Encoding parsedType : ct)
172 {
173 if (parsedType == Encoding::ANY)
174 {
175 if (!availableEncodings.empty())
176 {
177 return *availableEncodings.begin();
178 }
179 }
180 auto it = std::ranges::find(availableEncodings, parsedType);
181 if (it != availableEncodings.end())
182 {
183 return *it;
184 }
185 }
186
187 // Fall back to raw bytes if it was allowed
188 auto it = std::ranges::find(availableEncodings, Encoding::UnencodedBytes);
189 if (it != availableEncodings.end())
190 {
191 return *it;
192 }
193
194 return Encoding::NoMatch;
195}
196
Ed Tanous23a21a12020-07-25 04:45:05 +0000197} // namespace http_helpers