LCOV - code coverage report
Current view: top level - http/server - body_decoders.cpp (source / functions) Coverage Total Hit
Test: coverage_filtered.info Lines: 97.7 % 390 381
Test Date: 2026-11-10 10:38:07 Functions: 100.0 % 76 76

            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
        

Generated by: LCOV version 2.0-1