Line data Source code
1 : #include "body_decoders.hpp"
2 : #include "../common/multipart_parser.hpp"
3 : #include "../common/syntax.hpp"
4 : #include "../util/url.hpp"
5 : #include "../util/utf8.hpp"
6 : #include "../../util/ascii.hpp"
7 : #include <algorithm>
8 : #include <array>
9 : #include <cstdint>
10 :
11 : namespace thinger::http {
12 :
13 : namespace {
14 :
15 : using json = nlohmann::json;
16 :
17 : // RFC 9110, 5.6.2: token characters (bit 1), in a table, as media types are parsed on
18 : // every request with a body. Bit 2: those of a plain media type (see is_plain_media_type),
19 : // all but uppercase letters and '*'.
20 : constexpr std::array<uint8_t, 256> token_table = [] {
21 : std::array<uint8_t, 256> table{};
22 : for (int c = '0'; c <= '9'; ++c) table[c] = 3;
23 : for (int c = 'a'; c <= 'z'; ++c) table[c] = 3;
24 : for (int c = 'A'; c <= 'Z'; ++c) table[c] = 1;
25 : for (unsigned char c : std::string_view("!#$%&'+-.^_`|~")) table[c] = 3;
26 : table['*'] = 1;
27 : return table;
28 : }();
29 :
30 11343 : bool is_tchar(char c) {
31 11343 : return token_table[static_cast<unsigned char>(c)] & 1;
32 : }
33 :
34 : // "type/subtype" of token characters in lowercase, without wildcards, whitespace nor
35 : // parameters (as most clients send it): media_type::parse() reads it as it is
36 496992 : bool is_plain_media_type(std::string_view value) {
37 496992 : size_t slash = 0; // (none: a slash first is not valid either)
38 10053330 : for (size_t i = 0; i < value.size(); ++i) {
39 9556596 : if (token_table[static_cast<unsigned char>(value[i])] & 2) continue;
40 497205 : if (value[i] != '/' || i == 0 || slash) return false;
41 496947 : slash = i;
42 : }
43 496734 : return slash > 0 && slash + 1 < value.size();
44 : }
45 :
46 : // RFC 9110, 5.6.4: qdtext, and the characters of a quoted-pair after the backslash
47 84 : bool is_qdtext(char c) {
48 84 : auto u = static_cast<unsigned char>(c);
49 84 : return u == '\t' || u == ' ' || u == 0x21 || (u >= 0x23 && u <= 0x5B) || (u >= 0x5D && u <= 0x7E) || u >= 0x80;
50 : }
51 :
52 6 : bool is_quoted_pair_char(char c) {
53 6 : auto u = static_cast<unsigned char>(c);
54 6 : return u == '\t' || u == ' ' || (u >= 0x21 && u <= 0x7E) || u >= 0x80;
55 : }
56 :
57 288660 : std::string lowercase(std::string_view value) {
58 288660 : std::string result(value);
59 288660 : ::thinger::util::ascii::to_lower(result);
60 288660 : return result;
61 : }
62 :
63 : // Cursor over a media type, reading the rules it is made of
64 : class media_type_reader {
65 : public:
66 273 : explicit media_type_reader(std::string_view value) : value_(value) {}
67 :
68 516 : size_t position() const { return position_; }
69 14745 : bool at_end() const { return position_ >= value_.size(); }
70 1194 : bool next_is(char c) const { return !at_end() && value_[position_] == c; }
71 :
72 963 : bool consume(char c) {
73 963 : if (!next_is(c)) return false;
74 729 : ++position_;
75 729 : return true;
76 : }
77 :
78 963 : void skip_ows() {
79 1179 : while (!at_end() && syntax::is_whitespace(value_[position_])) ++position_;
80 963 : }
81 :
82 954 : std::string_view token() {
83 954 : size_t start = position_;
84 11562 : while (!at_end() && is_tchar(value_[position_])) ++position_;
85 954 : return value_.substr(start, position_ - start);
86 : }
87 :
88 : // A parameter value: a token, or a quoted string (unescaped); false if malformed
89 222 : bool parameter_value(std::string& value) {
90 222 : if (consume('"')) return quoted_string(value);
91 201 : auto token_value = token();
92 201 : value = token_value;
93 201 : return !token_value.empty();
94 : }
95 :
96 : private:
97 : // The rest of a quoted string, after its opening quote
98 21 : bool quoted_string(std::string& value) {
99 111 : while (!at_end()) {
100 105 : char c = value_[position_++];
101 105 : if (c == '"') return true;
102 90 : if (c == '\\') {
103 6 : if (at_end() || !is_quoted_pair_char(value_[position_])) return false;
104 6 : c = value_[position_++];
105 84 : } else if (!is_qdtext(c)) {
106 0 : return false;
107 : }
108 90 : value += c;
109 : }
110 6 : return false;
111 : }
112 :
113 : std::string_view value_;
114 : size_t position_ = 0;
115 : };
116 :
117 : // Parameters of a media type (";name=value", RFC 9110, 5.6.6), up to the end; false if
118 : // malformed
119 243 : bool read_parameters(media_type_reader& reader, std::vector<std::pair<std::string, std::string>>& parameters) {
120 243 : reader.skip_ows();
121 459 : while (!reader.at_end()) {
122 267 : if (!reader.consume(';')) return false;
123 234 : reader.skip_ows();
124 234 : if (reader.at_end()) break;
125 231 : if (reader.next_is(';')) continue; // empty parameter
126 228 : auto name = reader.token();
127 228 : if (name.empty() || !reader.consume('=')) return false;
128 222 : std::string value;
129 222 : if (!reader.parameter_value(value)) return false;
130 213 : parameters.emplace_back(lowercase(name), std::move(value));
131 213 : reader.skip_ows();
132 222 : }
133 210 : return true;
134 : }
135 :
136 828 : bool valid_utf8(std::string_view value) {
137 828 : return utf8::is_valid(reinterpret_cast<const uint8_t*>(value.data()), value.size());
138 : }
139 :
140 : // Add a value of a form: a member of the object, or for a repeated name, an array of its
141 : // values in order
142 126 : void add_form_value(json& form, const std::string& name, json value) {
143 126 : auto member = form.find(name);
144 126 : if (member == form.end()) {
145 99 : form.emplace(name, std::move(value));
146 27 : } else if (member->is_array()) {
147 3 : member->push_back(std::move(value));
148 : } else {
149 96 : *member = json::array({std::move(*member), std::move(value)});
150 : }
151 174 : }
152 :
153 : // The boundary of a multipart body: a single one (repeated ones are ambiguous), valid
154 12 : std::string multipart_boundary(const media_type& type) {
155 12 : std::string boundary;
156 12 : size_t boundaries = 0;
157 24 : for (const auto& [name, value] : type.parameters) {
158 12 : if (name != "boundary") continue;
159 12 : boundary = value;
160 12 : ++boundaries;
161 : }
162 12 : if (boundaries != 1 || !multipart_parser::valid_boundary(boundary)) {
163 0 : throw body_decode_error("Invalid multipart body: missing or invalid boundary");
164 : }
165 12 : return boundary;
166 0 : }
167 :
168 : // Value of a multipart form part of `content` (taken): a string, or an object for a file
169 15 : json part_value(const multipart_part& part, std::string& content) {
170 15 : if (!part.is_file()) {
171 18 : if (!valid_utf8(content)) throw body_decode_error("Invalid multipart body: not valid UTF-8");
172 9 : return std::move(content);
173 : }
174 3 : if (!valid_utf8(part.filename) || !valid_utf8(part.content_type)) {
175 0 : throw body_decode_error("Invalid multipart body: not valid UTF-8");
176 : }
177 3 : return {{"filename", part.safe_filename()}, {"content_type", part.content_type},
178 36 : {"content", json::binary(std::vector<uint8_t>(content.begin(), content.end()))}};
179 33 : }
180 :
181 : // Charsets other than UTF-8 are not converted: rejected as an unsupported media type
182 471 : void require_utf8(const media_type& type, const std::string& what, bool allow_ascii = false) {
183 471 : auto charset = type.parameter("charset");
184 471 : if (charset.empty() || ::thinger::util::ascii::iequals(charset, "utf-8")) return;
185 15 : if (allow_ascii && ::thinger::util::ascii::iequals(charset, "us-ascii")) return;
186 24 : throw body_decode_error("Unsupported charset for " + what + " (only UTF-8)",
187 36 : http_response::status::unsupported_media_type);
188 471 : }
189 :
190 : // Builds the decoded value from the SAX events of nlohmann::json (for JSON and the binary
191 : // formats alike), within the limits: nesting depth, number of values (at most one per
192 : // byte of input) and, for the binary formats, UTF-8 strings. Containers are filled
193 : // through a stack, never recursively, and decoding stops at the first event over a limit.
194 : class limited_dom_builder {
195 : public:
196 : enum class failure { none, syntax, depth, values, utf8 };
197 :
198 657 : limited_dom_builder(json& root, size_t max_depth, size_t max_values, bool check_utf8)
199 657 : : root_(root), max_depth_(max_depth), max_values_(max_values), check_utf8_(check_utf8) {}
200 :
201 123 : bool null() { return add(nullptr); }
202 75 : bool boolean(bool value) { return add(value); }
203 26 : bool number_integer(json::number_integer_t value) { return add(value); }
204 143 : bool number_unsigned(json::number_unsigned_t value) { return add(value); }
205 12 : bool number_float(json::number_float_t value, const json::string_t&) { return add(value); }
206 :
207 550 : bool string(json::string_t& value) {
208 550 : if (check_utf8_ && !valid_utf8(value)) return fail(failure::utf8);
209 535 : return add(std::move(value));
210 : }
211 :
212 6 : bool binary(json::binary_t& value) {
213 6 : return add(std::move(value));
214 : }
215 :
216 441 : bool start_object(std::size_t) { return open(json::value_t::object); }
217 :
218 644 : bool key(json::string_t& value) {
219 644 : if (check_utf8_ && !valid_utf8(value)) return fail(failure::utf8);
220 : // a repeated key keeps the last value, as json::parse() does
221 641 : element_ = &stack_.back()->get_ref<json::object_t&>()[value];
222 641 : return true;
223 : }
224 :
225 337 : bool end_object() { stack_.pop_back(); return true; }
226 7572 : bool start_array(std::size_t) { return open(json::value_t::array); }
227 1689 : bool end_array() { stack_.pop_back(); return true; }
228 :
229 185 : bool parse_error(std::size_t, const std::string&, const json::exception&) {
230 185 : return fail(failure::syntax);
231 : }
232 :
233 284 : failure get_failure() const { return failure_; }
234 :
235 : private:
236 284 : bool fail(failure reason) {
237 284 : if (failure_ == failure::none) failure_ = reason;
238 284 : return false;
239 : }
240 :
241 8013 : bool open(json::value_t type) {
242 8013 : if (stack_.size() >= max_depth_) return fail(failure::depth);
243 7947 : if (!add(type)) return false;
244 7947 : stack_.push_back(last_);
245 7947 : return true;
246 : }
247 :
248 : // Add a value where it goes (the root, the open array, or the last key of the open
249 : // object), built there from the parsed value; false if over the limit of values.
250 : // Pointers to open containers stay valid: their parents get no other element while
251 : // they are open.
252 : template<typename Value>
253 8867 : bool add(Value&& value) {
254 8867 : if (++values_ > max_values_) return fail(failure::values);
255 8852 : if (stack_.empty()) {
256 588 : root_ = json(std::forward<Value>(value));
257 588 : last_ = &root_;
258 8264 : } else if (auto* array = stack_.back()->template get_ptr<json::array_t*>()) {
259 7668 : last_ = &array->emplace_back(std::forward<Value>(value));
260 : } else {
261 596 : *element_ = json(std::forward<Value>(value));
262 596 : last_ = element_;
263 : }
264 8852 : return true;
265 : }
266 :
267 : json& root_;
268 : std::vector<json*> stack_;
269 : json* element_ = nullptr;
270 : // the value added last
271 : json* last_ = nullptr;
272 : size_t max_depth_;
273 : size_t max_values_;
274 : size_t values_ = 0;
275 : bool check_utf8_;
276 : failure failure_ = failure::none;
277 : };
278 :
279 657 : json decode_with_limits(std::string_view body, json::input_format_t format, const body_limits& limits,
280 : const char* name) {
281 657 : json result;
282 657 : limited_dom_builder builder(result, limits.max_depth, body.size(), format != json::input_format_t::json);
283 657 : bool decoded = false;
284 : try {
285 657 : decoded = json::sax_parse(body.data(), body.data() + body.size(), &builder, format, true);
286 0 : } catch (const std::exception&) {
287 : // the parser reports errors through the builder, but fail safe on any exception
288 0 : decoded = false;
289 0 : }
290 1030 : if (decoded) return result;
291 :
292 284 : switch (builder.get_failure()) {
293 66 : case limited_dom_builder::failure::depth:
294 132 : throw body_decode_error(std::string("Invalid ") + name + ": nested deeper than " +
295 198 : std::to_string(limits.max_depth) + " levels");
296 15 : case limited_dom_builder::failure::values:
297 45 : throw body_decode_error(std::string("Invalid ") + name + ": more values than bytes");
298 18 : case limited_dom_builder::failure::utf8:
299 54 : throw body_decode_error(std::string("Invalid ") + name + ": string not valid UTF-8");
300 185 : default:
301 555 : throw body_decode_error(std::string("Invalid ") + name);
302 : }
303 941 : }
304 :
305 : }
306 :
307 : // --- media_type ---
308 :
309 729 : std::optional<media_type> media_type::parse(std::string_view value) {
310 : // Parsed on every request with a body: the usual value, such as "application/json",
311 : // needs neither a copy in lowercase nor a parameter list
312 1641 : if (is_plain_media_type(value)) return media_type{std::string(value), {}};
313 :
314 273 : media_type_reader reader(value);
315 273 : reader.skip_ows();
316 273 : size_t start = reader.position();
317 273 : auto type = reader.token();
318 273 : if (type.empty() || !reader.consume('/')) return std::nullopt;
319 252 : auto subtype = reader.token();
320 252 : if (subtype.empty()) return std::nullopt;
321 : // "*/subtype" is not a media range
322 246 : if (type == "*" && subtype != "*") return std::nullopt;
323 :
324 243 : media_type result;
325 243 : result.type.assign(value.substr(start, reader.position() - start));
326 243 : ::thinger::util::ascii::to_lower(result.type);
327 243 : if (!read_parameters(reader, result.parameters)) return std::nullopt;
328 210 : return result;
329 243 : }
330 :
331 486 : std::string media_type::parameter(std::string_view name) const {
332 492 : for (const auto& [key, value] : parameters) {
333 54 : if (::thinger::util::ascii::iequals(key, name)) return value;
334 : }
335 438 : return {};
336 : }
337 :
338 : // --- body_decoder_registry ---
339 :
340 11535 : body_decoder_registry::body_decoder_registry() {
341 57675 : add("json", {"application/json"}, decode_json, "+json");
342 57675 : add("msgpack", {"application/msgpack", "application/x-msgpack"}, decode_msgpack);
343 57675 : add("cbor", {"application/cbor"}, decode_cbor, "+cbor");
344 57675 : add("ubjson", {"application/ubjson"}, decode_ubjson);
345 57675 : add("form", {"application/x-www-form-urlencoded"}, decode_form);
346 57675 : add("multipart", {"multipart/form-data"}, decode_multipart);
347 57675 : add("text", {"text/plain"}, decode_text);
348 57675 : add("binary", {"application/octet-stream"}, decode_binary);
349 11535 : }
350 :
351 92298 : body_decoder_registry& body_decoder_registry::add(const std::string& name, std::vector<std::string> media_types,
352 : body_decoder decoder, std::string suffix) {
353 92298 : body_format format{lowercase(name), {}, lowercase(suffix), std::move(decoder)};
354 196137 : for (auto& type : media_types) {
355 103839 : auto lower = lowercase(type);
356 103839 : if (std::find(format.media_types.begin(), format.media_types.end(), lower) == format.media_types.end()) {
357 103839 : format.media_types.push_back(std::move(lower));
358 : }
359 103839 : }
360 :
361 : // A media type (or suffix) is decoded by one decoder only: the last one added
362 415425 : for (auto& other : formats_) {
363 323127 : if (other.name == format.name) continue;
364 323124 : std::erase_if(other.media_types, [&](const std::string& type) {
365 392352 : return std::find(format.media_types.begin(), format.media_types.end(), type) != format.media_types.end();
366 : });
367 323124 : if (!format.suffix.empty() && other.suffix == format.suffix) other.suffix.clear();
368 : }
369 :
370 92298 : auto existing = std::find_if(formats_.begin(), formats_.end(),
371 323124 : [&](const body_format& f) { return f.name == format.name; });
372 92298 : if (existing != formats_.end()) {
373 3 : *existing = std::move(format);
374 : } else {
375 92295 : formats_.push_back(std::move(format));
376 : }
377 92298 : index_plain_media_types();
378 92298 : return *this;
379 92298 : }
380 :
381 12 : bool body_decoder_registry::remove(const std::string& name) {
382 12 : auto key = lowercase(name);
383 111 : if (std::erase_if(formats_, [&](const body_format& f) { return f.name == key; }) == 0) return false;
384 9 : index_plain_media_types();
385 9 : return true;
386 12 : }
387 :
388 92307 : void body_decoder_registry::index_plain_media_types() {
389 92307 : plain_media_types_.clear();
390 507798 : for (size_t i = 0; i < formats_.size(); ++i) {
391 911754 : for (const auto& type : formats_[i].media_types) {
392 496263 : if (is_plain_media_type(type)) plain_media_types_.emplace_back(media_type{type, {}}, i);
393 : }
394 : }
395 92307 : }
396 :
397 626 : std::pair<const media_type*, const body_format*> body_decoder_registry::find_plain_media_type(std::string_view type) const {
398 2225 : for (const auto& [parsed, index] : plain_media_types_) {
399 2138 : if (parsed.type == type) return {&parsed, &formats_[index]};
400 : }
401 87 : return {nullptr, nullptr};
402 : }
403 :
404 476 : const body_format* body_decoder_registry::find(std::string_view name) const {
405 1520 : for (const auto& format : formats_) {
406 1514 : if (::thinger::util::ascii::iequals(format.name, name)) return &format;
407 : }
408 6 : return nullptr;
409 : }
410 :
411 207 : const body_format* body_decoder_registry::find_media_type(std::string_view type) const {
412 1182 : for (const auto& format : formats_) {
413 2268 : for (const auto& media_type : format.media_types) {
414 1293 : if (media_type == type) return &format;
415 : }
416 : }
417 : // Structured syntax suffix (RFC 6838, 4.2.8): "application/problem+json" is JSON
418 54 : auto plus = type.rfind('+');
419 54 : if (plus == std::string_view::npos || type.find('/') > plus) return nullptr;
420 18 : auto suffix = type.substr(plus);
421 48 : for (const auto& format : formats_) {
422 45 : if (!format.suffix.empty() && format.suffix == suffix) return &format;
423 : }
424 3 : return nullptr;
425 : }
426 :
427 : // --- built-in decoders ---
428 :
429 390 : json body_decoder_registry::decode_json(std::string_view body, const media_type& type, const body_limits& limits) {
430 396 : require_utf8(type, "JSON");
431 : // The message of invalid JSON is kept as it was before the other formats
432 384 : return decode_with_limits(body, json::input_format_t::json, limits, "JSON");
433 : }
434 :
435 120 : json body_decoder_registry::decode_msgpack(std::string_view body, const media_type&, const body_limits& limits) {
436 120 : return decode_with_limits(body, json::input_format_t::msgpack, limits, "MessagePack");
437 : }
438 :
439 78 : json body_decoder_registry::decode_cbor(std::string_view body, const media_type&, const body_limits& limits) {
440 78 : return decode_with_limits(body, json::input_format_t::cbor, limits, "CBOR");
441 : }
442 :
443 75 : json body_decoder_registry::decode_ubjson(std::string_view body, const media_type&, const body_limits& limits) {
444 75 : return decode_with_limits(body, json::input_format_t::ubjson, limits, "UBJSON");
445 : }
446 :
447 60 : json body_decoder_registry::decode_form(std::string_view body, const media_type& type, const body_limits&) {
448 63 : require_utf8(type, "form data");
449 :
450 : // name=value pairs separated by '&': percent-decoded, '+' as a space. Each name is a
451 : // member of the object, with its value as a string; a repeated name, an array of its
452 : // values in order. Empty pairs and pairs without name are skipped; a pair without '='
453 : // has an empty value.
454 57 : json result = json::object();
455 57 : std::string name;
456 57 : std::string value;
457 57 : size_t position = 0;
458 189 : while (position <= body.size()) {
459 147 : size_t end = body.find('&', position);
460 147 : if (end == std::string_view::npos) end = body.size();
461 147 : auto pair = body.substr(position, end - position);
462 147 : position = end + 1;
463 150 : if (pair.empty()) continue;
464 :
465 132 : auto equals = pair.find('=');
466 132 : auto raw_name = pair.substr(0, equals);
467 132 : auto raw_value = equals == std::string_view::npos ? std::string_view{} : pair.substr(equals + 1);
468 132 : if (raw_name.empty()) continue;
469 :
470 645 : if (!util::url::url_decode(std::string(raw_name), name) ||
471 387 : !util::url::url_decode(std::string(raw_value), value)) {
472 27 : throw body_decode_error("Invalid form data: malformed percent-encoding");
473 : }
474 120 : if (!valid_utf8(name) || !valid_utf8(value)) {
475 18 : throw body_decode_error("Invalid form data: not valid UTF-8");
476 : }
477 :
478 114 : add_form_value(result, name, value);
479 : }
480 84 : return result;
481 87 : }
482 :
483 12 : json body_decoder_registry::decode_multipart(std::string_view body, const media_type& type, const body_limits&) {
484 : // As decode_form(): each form-data part with a name is a member, its value a string, or
485 : // an object for a file; a repeated name, an array of its values. Other parts are skipped.
486 12 : json result = json::object();
487 12 : multipart_parser parser(multipart_boundary(type));
488 12 : std::string content;
489 12 : bool skip = true;
490 : for (;;) {
491 63 : auto event = parser.parse(body);
492 63 : if (event == multipart_parser::event::need_more) event = parser.finish();
493 63 : switch (event) {
494 21 : case multipart_parser::event::part: {
495 21 : const auto& part = parser.part();
496 21 : skip = part.disposition != "form-data" || part.name.empty();
497 21 : if (!skip && !valid_utf8(part.name)) throw body_decode_error("Invalid multipart body: not valid UTF-8");
498 21 : content.clear();
499 21 : break;
500 : }
501 18 : case multipart_parser::event::data:
502 18 : if (!skip) content.append(parser.data());
503 18 : break;
504 15 : case multipart_parser::event::part_end:
505 15 : if (!skip) add_form_value(result, parser.part().name, part_value(parser.part(), content));
506 12 : break;
507 3 : case multipart_parser::event::end:
508 6 : return result;
509 6 : default: {
510 6 : auto error = parser.error();
511 : throw body_decode_error(multipart_error_message(error),
512 18 : multipart_error_status(error));
513 : }
514 : }
515 51 : }
516 30 : }
517 :
518 21 : json body_decoder_registry::decode_text(std::string_view body, const media_type& type, const body_limits&) {
519 24 : require_utf8(type, "text", true);
520 30 : if (!valid_utf8(body)) throw body_decode_error("Invalid text: not valid UTF-8");
521 24 : return json(std::string(body));
522 : }
523 :
524 6 : json body_decoder_registry::decode_binary(std::string_view body, const media_type&, const body_limits&) {
525 12 : return json::binary(std::vector<uint8_t>(body.begin(), body.end()));
526 : }
527 :
528 : // --- settings and matching ---
529 :
530 24 : const body_settings& body_settings::defaults() {
531 24 : static const body_settings settings;
532 24 : return settings;
533 : }
534 :
535 156 : body_type_match match_body_type(const body_decoder_registry& decoders,
536 : const std::vector<std::string>& accepted,
537 : std::string_view content_type) {
538 156 : body_type_match match;
539 156 : content_type = syntax::trim(content_type);
540 156 : auto type = media_type::parse(content_type.empty() ? default_body_media_type : content_type);
541 156 : if (!type) {
542 6 : match.error = "Invalid Content-Type";
543 6 : return match;
544 : }
545 150 : match.type = std::move(*type);
546 150 : match.format = decoders.find_media_type(match.type.type);
547 264 : for (const auto& entry : accepted) {
548 168 : if (entry == match.type.type || (match.format && entry == match.format->name)) {
549 54 : match.accepted = true;
550 54 : return match;
551 : }
552 : }
553 96 : match.error = "Unsupported Media Type: " + match.type.type;
554 96 : return match;
555 156 : }
556 :
557 68 : std::vector<std::string> accepted_media_types(const body_decoder_registry& decoders,
558 : const std::vector<std::string>& accepted) {
559 68 : std::vector<std::string> types;
560 152 : auto add = [&types](const std::string& type) {
561 152 : if (std::find(types.begin(), types.end(), type) == types.end()) types.push_back(type);
562 152 : };
563 214 : for (const auto& entry : accepted) {
564 146 : if (entry.find('/') != std::string::npos) {
565 24 : add(entry);
566 122 : } else if (const auto* format = decoders.find(entry)) {
567 247 : for (const auto& type : format->media_types) add(type);
568 : }
569 : }
570 136 : return types;
571 0 : }
572 :
573 : // --- response encoding ---
574 :
575 : namespace {
576 :
577 : // A media range of an Accept header, and its quality in thousandths
578 : struct accepted_range {
579 : media_type range;
580 : int quality;
581 : };
582 :
583 : // Media ranges of an Accept header with their quality (1 unless given); invalid ones are
584 : // skipped
585 63 : std::vector<accepted_range> parse_accept(std::string_view accept) {
586 63 : std::vector<accepted_range> ranges;
587 63 : syntax::for_each_element(accept, [&](std::string_view element) {
588 63 : auto range = media_type::parse(element);
589 63 : if (!range) return;
590 63 : int quality = 1000;
591 63 : for (const auto& [name, value] : range->parameters) {
592 24 : if (name == "q") {
593 24 : quality = syntax::parse_qvalue(value);
594 24 : break;
595 : }
596 : }
597 123 : if (quality >= 0) ranges.push_back({std::move(*range), quality});
598 123 : });
599 63 : return ranges;
600 0 : }
601 :
602 : // How specifically a media range matches a media type: 2 by its type, 1 by "type/*",
603 : // 0 by "*/*"; -1 if it does not
604 300 : int specificity(std::string_view range, std::string_view type) {
605 300 : if (range == type) return 2;
606 255 : if (range == "*/*") return 0;
607 225 : if (range.ends_with("/*") && type.starts_with(range.substr(0, range.size() - 1))) return 1;
608 210 : return -1;
609 : }
610 :
611 : // Quality of a media type: that of the most specific range matching it, 0 if none does
612 315 : int quality_of(std::string_view type, const std::vector<accepted_range>& ranges) {
613 315 : int best_specificity = -1;
614 315 : int quality = 0;
615 615 : for (const auto& [range, range_quality] : ranges) {
616 300 : int match = specificity(range.type, type);
617 300 : if (match > best_specificity) {
618 87 : best_specificity = match;
619 87 : quality = range_quality;
620 : }
621 : }
622 315 : return quality;
623 : }
624 :
625 : }
626 :
627 63 : response_encoding negotiate_response_encoding(std::string_view accept) {
628 : // In order of preference when the client accepts several with the same quality
629 : static const std::array<std::pair<std::string_view, std::string_view>, 5> candidates{{
630 : {"application/json", "json"},
631 : {"application/msgpack", "msgpack"},
632 : {"application/x-msgpack", "msgpack"},
633 : {"application/cbor", "cbor"},
634 : {"application/ubjson", "ubjson"},
635 : }};
636 189 : response_encoding result{"application/json", "json"};
637 63 : auto ranges = parse_accept(accept);
638 63 : int best_quality = 0;
639 378 : for (const auto& [type, format] : candidates) {
640 315 : int quality = quality_of(type, ranges);
641 315 : if (quality > best_quality) {
642 45 : best_quality = quality;
643 45 : result = {std::string(type), std::string(format)};
644 : }
645 : }
646 126 : return result;
647 198 : }
648 :
649 39 : std::string encode_body(const nlohmann::json& value, const std::string& format) {
650 39 : std::vector<uint8_t> encoded;
651 39 : if (format == "msgpack") {
652 6 : encoded = json::to_msgpack(value);
653 33 : } else if (format == "cbor") {
654 6 : encoded = json::to_cbor(value);
655 27 : } else if (format == "ubjson") {
656 6 : encoded = json::to_ubjson(value);
657 : } else {
658 21 : return value.dump();
659 : }
660 54 : return {encoded.begin(), encoded.end()};
661 39 : }
662 :
663 : } // namespace thinger::http
|