LCOV - code coverage report
Current view: top level - http/common - multipart_parser.cpp (source / functions) Coverage Total Hit
Test: coverage_filtered.info Lines: 96.3 % 379 365
Test Date: 2026-11-10 10:38:07 Functions: 100.0 % 26 26

            Line data    Source code
       1              : #include "multipart_parser.hpp"
       2              : #include "../util/utf8.hpp"
       3              : #include "../../util/ascii.hpp"
       4              : #include <optional>
       5              : 
       6              : namespace thinger::http {
       7              : 
       8              : namespace {
       9              : 
      10      5561778 :     bool is_tchar(char c) {
      11      5561778 :         auto u = static_cast<unsigned char>(c);
      12      5561778 :         if ((u >= '0' && u <= '9') || (u >= 'a' && u <= 'z') || (u >= 'A' && u <= 'Z')) return true;
      13       686544 :         return std::string_view("!#$%&'*+-.^_`|~").find(c) != std::string_view::npos;
      14              :     }
      15              : 
      16      2770323 :     bool is_ows(char c) {
      17      2770323 :         return c == ' ' || c == '\t';
      18              :     }
      19              : 
      20              :     // RFC 9110, 5.5: a field value has no control characters but HTAB (obs-text is allowed:
      21              :     // browsers send UTF-8 filenames as they are)
      22      4053525 :     bool is_field_char(char c) {
      23      4053525 :         auto u = static_cast<unsigned char>(c);
      24      4053525 :         return u == '\t' || (u >= 0x20 && u != 0x7F);
      25              :     }
      26              : 
      27      1060737 :     std::string_view trim(std::string_view value) {
      28      1412580 :         while (!value.empty() && is_ows(value.front())) value.remove_prefix(1);
      29      1062987 :         while (!value.empty() && is_ows(value.back())) value.remove_suffix(1);
      30      1060737 :         return value;
      31              :     }
      32              : 
      33       346404 :     std::string_view take_token(std::string_view& input) {
      34       346404 :         size_t size = 0;
      35      2759559 :         while (size < input.size() && is_tchar(input[size])) ++size;
      36       346404 :         auto token = input.substr(0, size);
      37       346404 :         input.remove_prefix(size);
      38       346404 :         return token;
      39              :     }
      40              : 
      41              :     // Characters a backslash escapes in a quoted string (RFC 2045 tspecials): elsewhere a
      42              :     // backslash is kept, as old browsers send Windows paths unescaped ("C:\dev\a.txt"), the
      43              :     // way Go's mime.ParseMediaType reads them
      44         3480 :     bool is_tspecial(char c) {
      45         3480 :         return std::string_view("()<>@,;:\\\"/[]?=").find(c) != std::string_view::npos;
      46              :     }
      47              : 
      48              :     // A token or a quoted string; nullopt if there is neither
      49       192360 :     std::optional<std::string> take_value(std::string_view& input) {
      50       192360 :         if (input.empty() || input.front() != '"') {
      51        13866 :             auto token = take_token(input);
      52        13866 :             if (token.empty()) return std::nullopt;
      53        26808 :             return std::string(token);
      54              :         }
      55       178494 :         std::string value;
      56       675669 :         for (size_t i = 1; i < input.size(); ++i) {
      57       675195 :             char c = input[i];
      58       675195 :             if (c == '"') {
      59       178020 :                 input.remove_prefix(i + 1);
      60       178020 :                 return value;
      61              :             }
      62       497175 :             if (c == '\\' && i + 1 < input.size() && is_tspecial(input[i + 1])) c = input[++i];
      63       497175 :             value += c;
      64              :         }
      65          474 :         return std::nullopt;    // unterminated
      66       178494 :     }
      67              : 
      68        22812 :     int hex_value(char c) {
      69        22812 :         if (c >= '0' && c <= '9') return c - '0';
      70        10758 :         if (c >= 'a' && c <= 'f') return c - 'a' + 10;
      71        10758 :         if (c >= 'A' && c <= 'F') return c - 'A' + 10;
      72         1350 :         return -1;
      73              :     }
      74              : 
      75              :     // RFC 5987 ext-value (charset'language'percent-encoded) in UTF-8; nullopt if it is
      76              :     // malformed or its charset is not UTF-8, ISO-8859-1 or US-ASCII
      77        12360 :     std::optional<std::string> decode_ext_value(std::string_view value) {
      78        12360 :         auto first = value.find('\'');
      79        12360 :         if (first == std::string_view::npos) return std::nullopt;
      80        11022 :         auto second = value.find('\'', first + 1);
      81        11022 :         if (second == std::string_view::npos) return std::nullopt;
      82         9684 :         auto charset = value.substr(0, first);
      83         9684 :         auto encoded = value.substr(second + 1);
      84              : 
      85         9684 :         std::string bytes;
      86        43680 :         for (size_t i = 0; i < encoded.size(); ++i) {
      87        36684 :             if (encoded[i] != '%') {
      88        23940 :                 bytes += encoded[i];
      89        23940 :                 continue;
      90              :             }
      91        12744 :             if (i + 2 >= encoded.size()) return std::nullopt;
      92        11406 :             int high = hex_value(encoded[i + 1]);
      93        11406 :             int low = hex_value(encoded[i + 2]);
      94        11406 :             if (high < 0 || low < 0) return std::nullopt;
      95        10056 :             bytes += static_cast<char>(high * 16 + low);
      96        10056 :             i += 2;
      97              :         }
      98              : 
      99              :         using ::thinger::util::ascii::iequals;
     100         6996 :         if (iequals(charset, "utf-8")) {
     101         2868 :             if (!utf8::is_valid(reinterpret_cast<const uint8_t*>(bytes.data()), bytes.size())) return std::nullopt;
     102         1518 :             return bytes;
     103              :         }
     104         4128 :         if (iequals(charset, "iso-8859-1")) {
     105          702 :             std::string result;
     106         5616 :             for (char c : bytes) {
     107         4914 :                 auto u = static_cast<unsigned char>(c);
     108         4914 :                 if (u < 0x80) {
     109         3510 :                     result += c;
     110              :                 } else {
     111         1404 :                     result += static_cast<char>(0xC0 | (u >> 6));
     112         1404 :                     result += static_cast<char>(0x80 | (u & 0x3F));
     113              :                 }
     114              :             }
     115          702 :             return result;
     116          702 :         }
     117         3426 :         if (iequals(charset, "us-ascii")) {
     118         8166 :             for (char c : bytes) {
     119         7488 :                 if (static_cast<unsigned char>(c) >= 0x80) return std::nullopt;
     120              :             }
     121          678 :             return bytes;
     122              :         }
     123         1362 :         return std::nullopt;
     124         9684 :     }
     125              : 
     126              :     // Content-Disposition (RFC 6266, RFC 7578 4.2): a disposition type and parameters, with
     127              :     // whitespace around ';' and '=' and a trailing ';', as Go reads it. A repeated parameter
     128              :     // is ambiguous: malformed.
     129       139278 :     bool parse_content_disposition(std::string_view value, multipart_part& part) {
     130       139278 :         auto type = take_token(value);
     131       139278 :         if (type.empty()) return false;
     132       138864 :         part.disposition = std::string(type);
     133       138864 :         ::thinger::util::ascii::to_lower(part.disposition);
     134              : 
     135       138864 :         std::vector<std::string> seen;
     136       138864 :         std::optional<std::string> filename;
     137       138864 :         std::optional<std::string> extended_filename;
     138              :         for (;;) {
     139       329160 :             value = trim(value);
     140       329160 :             if (value.empty()) break;
     141       198408 :             if (value.front() != ';') return false;
     142       194010 :             value.remove_prefix(1);
     143       194010 :             value = trim(value);
     144       194010 :             if (value.empty()) break;   // trailing ';'
     145              : 
     146       193260 :             std::string name(take_token(value));
     147       193260 :             if (name.empty()) return false;
     148       192816 :             ::thinger::util::ascii::to_lower(name);
     149       192816 :             value = trim(value);
     150       192816 :             if (value.empty() || value.front() != '=') return false;
     151       192360 :             value.remove_prefix(1);
     152       192360 :             value = trim(value);
     153       192360 :             auto parameter = take_value(value);
     154       192360 :             if (!parameter) return false;
     155              : 
     156       251808 :             for (const auto& other : seen) {
     157        61512 :                 if (other == name) return false;
     158              :             }
     159       190296 :             seen.push_back(name);
     160              : 
     161       190296 :             if (name == "name") {
     162       134829 :                 part.name = std::move(*parameter);
     163        55467 :             } else if (name == "filename") {
     164        43107 :                 filename = std::move(*parameter);
     165        12360 :             } else if (name == "filename*") {
     166              :                 // An unsupported or malformed one is ignored, falling back to filename
     167        12360 :                 extended_filename = decode_ext_value(*parameter);
     168              :             }
     169       385620 :         }
     170              : 
     171              :         // filename* first (RFC 6266, 4.3), as busboy does
     172       134466 :         if (extended_filename) {
     173         2898 :             filename = std::move(extended_filename);
     174              :         }
     175       134466 :         if (filename) {
     176        44547 :             part.filename = std::move(*filename);
     177        44547 :             part.has_filename = true;
     178              :         }
     179       134466 :         return true;
     180       138864 :     }
     181              : 
     182              : }
     183              : 
     184           99 : http_response::status multipart_error_status(multipart_error error) {
     185              :     using status = http_response::status;
     186           99 :     switch (error) {
     187            0 :         case multipart_error::none: return status::ok;
     188           18 :         case multipart_error::not_multipart: return status::unsupported_media_type;
     189           48 :         case multipart_error::invalid_boundary:
     190              :         case multipart_error::malformed:
     191              :         case multipart_error::truncated:
     192           48 :         case multipart_error::invalid_body: return status::bad_request;
     193           30 :         case multipart_error::too_many_parts:
     194              :         case multipart_error::too_many_files:
     195              :         case multipart_error::header_too_large:
     196              :         case multipart_error::filename_too_large:
     197              :         case multipart_error::field_too_large:
     198              :         case multipart_error::fields_too_large:
     199              :         case multipart_error::file_too_large:
     200           30 :         case multipart_error::body_too_large: return status::payload_too_large;
     201            3 :         case multipart_error::file_failed: return status::internal_server_error;
     202              :     }
     203            0 :     return status::bad_request;
     204              : }
     205              : 
     206           78 : const char* multipart_error_message(multipart_error error) {
     207           78 :     switch (error) {
     208            0 :         case multipart_error::none: return "";
     209           15 :         case multipart_error::not_multipart: return "Unsupported Media Type: expected multipart/form-data";
     210           15 :         case multipart_error::invalid_boundary: return "Invalid multipart boundary";
     211           12 :         case multipart_error::malformed: return "Malformed multipart body";
     212           12 :         case multipart_error::truncated: return "Incomplete multipart body";
     213            3 :         case multipart_error::invalid_body: return "Invalid request body";
     214            6 :         case multipart_error::too_many_parts: return "Too many parts";
     215            0 :         case multipart_error::too_many_files: return "Too many files";
     216            0 :         case multipart_error::header_too_large: return "Part header too large";
     217            0 :         case multipart_error::filename_too_large: return "Filename too long";
     218            3 :         case multipart_error::field_too_large: return "Field too large";
     219            0 :         case multipart_error::fields_too_large: return "Fields too large";
     220            6 :         case multipart_error::file_too_large: return "File too large";
     221            6 :         case multipart_error::body_too_large: return "Payload Too Large";
     222            0 :         case multipart_error::file_failed: return "Could not store the file";
     223              :     }
     224            0 :     return "Invalid multipart body";
     225              : }
     226              : 
     227            6 : std::string multipart_part::header(std::string_view name) const {
     228           12 :     for (const auto& [key, value] : headers) {
     229           12 :         if (::thinger::util::ascii::iequals(key, name)) return value;
     230              :     }
     231            0 :     return {};
     232              : }
     233              : 
     234           81 : std::string multipart_part::sanitize_filename(std::string_view filename) {
     235           81 :     auto separator = filename.find_last_of("/\\");
     236           81 :     if (separator != std::string_view::npos) filename.remove_prefix(separator + 1);
     237           81 :     std::string result;
     238          585 :     for (char c : filename) {
     239          504 :         auto u = static_cast<unsigned char>(c);
     240          504 :         if (u < 0x20 || u == 0x7F) continue;
     241          483 :         result += c;
     242              :     }
     243           81 :     if (result == "." || result == "..") result.clear();
     244           81 :     return result;
     245            0 : }
     246              : 
     247        93498 : bool multipart_parser::valid_boundary(std::string_view boundary) {
     248        93498 :     if (boundary.empty() || boundary.size() > 70 || boundary.back() == ' ') return false;
     249       928323 :     for (char c : boundary) {
     250       834876 :         auto u = static_cast<unsigned char>(c);
     251       905385 :         bool valid = (u >= '0' && u <= '9') || (u >= 'a' && u <= 'z') || (u >= 'A' && u <= 'Z') ||
     252        70509 :                      std::string_view("'()+_,-./:=? ").find(c) != std::string_view::npos;
     253       834876 :         if (!valid) return false;
     254              :     }
     255        93447 :     return true;
     256              : }
     257              : 
     258        93429 : multipart_parser::multipart_parser(std::string_view boundary, multipart_limits limits)
     259        93429 :     : limits_(limits) {
     260        93429 :     if (!valid_boundary(boundary)) {
     261           12 :         fail(multipart_error::invalid_boundary);
     262           12 :         return;
     263              :     }
     264        93417 :     delimiter_ = "\r\n--";
     265        93417 :     delimiter_ += boundary;
     266        93417 :     boundary_end_ = delimiter_.size();
     267        93417 :     delimiter_ += "--";
     268              :     // The body may start with its first delimiter, without the CRLF before it
     269        93417 :     start_matching();
     270            0 : }
     271              : 
     272        29034 : multipart_parser::event multipart_parser::fail(multipart_error error) {
     273        29034 :     if (state_ != state::error) {
     274        29034 :         state_ = state::error;
     275        29034 :         error_ = error;
     276              :     }
     277        29034 :     data_ = {};
     278        29034 :     return event::error;
     279              : }
     280              : 
     281       228246 : void multipart_parser::start_matching() {
     282              :     // As if the CRLF of a delimiter were already matched: it is not in the input
     283       228246 :     matched_ = 2;
     284       228246 :     held_from_ = 2;
     285       228246 : }
     286              : 
     287      7180227 : multipart_parser::match multipart_parser::match_delimiter(size_t& matched, std::string_view input, size_t& used) const {
     288      7180227 :     size_t i = 0;
     289     19817187 :     while (matched < boundary_end_) {
     290     19463214 :         if (i == input.size()) {
     291        82143 :             used = i;
     292        82143 :             return match::partial;
     293              :         }
     294     19381071 :         if (input[i] != delimiter_[matched]) return match::mismatch;
     295     12636960 :         ++matched;
     296     12636960 :         ++i;
     297              :     }
     298       353973 :     if (i == input.size()) {
     299         6885 :         used = i;
     300         6885 :         return match::partial;
     301              :     }
     302       347088 :     if (matched == boundary_end_) {
     303       346074 :         char c = input[i];
     304              :         // Transport padding or CRLF: a delimiter (the line is checked after it)
     305       346074 :         if (is_ows(c) || c == '\r') {
     306       202209 :             used = i;
     307       202209 :             return match::delimiter;
     308              :         }
     309       143865 :         if (c != '-') return match::mismatch;
     310       137571 :         ++matched;
     311       137571 :         if (++i == input.size()) {
     312         1512 :             used = i;
     313         1512 :             return match::partial;
     314              :         }
     315              :     }
     316              :     // After the boundary and a dash: another one closes the body
     317       137073 :     if (input[i] != '-') return match::mismatch;
     318       131673 :     ++matched;
     319       131673 :     used = i + 1;
     320       131673 :     return match::close;
     321              : }
     322              : 
     323       337070 : multipart_parser::event multipart_parser::content(std::string_view data) {
     324       337070 :     if (data.empty() || state_ != state::content) return event::need_more;    // (the preamble is dropped)
     325              : 
     326              :     // Over a limit: the one crossed first, as if the content came byte by byte
     327       196760 :     std::uint64_t limit = part_.has_filename ? limits_.max_file_size : limits_.max_field_size;
     328       196760 :     std::uint64_t allowed = limit - part_size_;
     329       196760 :     std::uint64_t fields_allowed = part_.has_filename ? allowed : limits_.max_fields_size - fields_size_;
     330       196760 :     if (data.size() > allowed || data.size() > fields_allowed) {
     331        11721 :         if (allowed <= fields_allowed) {
     332         9999 :             return fail(part_.has_filename ? multipart_error::file_too_large : multipart_error::field_too_large);
     333              :         }
     334         1722 :         return fail(multipart_error::fields_too_large);
     335              :     }
     336       185039 :     part_size_ += data.size();
     337       185039 :     if (!part_.has_filename) fields_size_ += data.size();
     338       185039 :     data_ = data;
     339       185039 :     return event::data;
     340              : }
     341              : 
     342       215877 : multipart_parser::event multipart_parser::on_delimiter(match found) {
     343       215877 :     bool in_part = state_ == state::content;
     344       215877 :     state_ = found == match::close ? state::close_line : state::delimiter_line;
     345       215877 :     return in_part ? event::part_end : event::need_more;
     346              : }
     347              : 
     348       560089 : multipart_parser::event multipart_parser::scan(std::string_view& input) {
     349              :     for (;;) {
     350              :         // A delimiter matched in part by the input consumed before: go on with it
     351       698563 :         if (matched_ > 0) {
     352       306996 :             size_t matched = matched_;
     353       306996 :             size_t used = 0;
     354       306996 :             auto found = match_delimiter(matched, input, used);
     355       306996 :             if (found == match::partial) {
     356        62607 :                 matched_ = matched;
     357        62607 :                 input.remove_prefix(used);
     358       170379 :                 return event::need_more;
     359              :             }
     360       244389 :             if (found == match::mismatch) {
     361              :                 // Not a delimiter: the bytes held back are content, and the input is
     362              :                 // scanned from its start (it has no CR within those bytes)
     363       138822 :                 auto held = std::string_view(delimiter_).substr(held_from_, matched_ - held_from_);
     364       138822 :                 matched_ = 0;
     365       138822 :                 auto result = content(held);
     366       138822 :                 if (result != event::need_more) return result;
     367       136617 :                 continue;
     368       136617 :             }
     369       105567 :             matched_ = 0;
     370       105567 :             input.remove_prefix(used);
     371       105567 :             return on_delimiter(found);
     372              :         }
     373              : 
     374       391567 :         if (input.empty()) return event::need_more;
     375              : 
     376              :         // Content up to the first CR that starts a delimiter, or a part of one at the end of
     377              :         // the input. A delimiter has no CR but its first byte, so a mismatch at a CR moves on
     378              :         // to the next one.
     379       324881 :         size_t position = 0;
     380              :         for (;;) {
     381      6941864 :             auto cr = input.find('\r', position);
     382      6941864 :             if (cr == std::string_view::npos) {
     383        68633 :                 auto data = input;
     384        68633 :                 input = {};
     385        68633 :                 return content(data);
     386              :             }
     387      6873231 :             size_t matched = 0;
     388      6873231 :             size_t used = 0;
     389      6873231 :             auto found = match_delimiter(matched, input.substr(cr), used);
     390      6873231 :             if (found == match::mismatch) {
     391      6616983 :                 position = cr + 1;
     392      6616983 :                 continue;
     393              :             }
     394       256248 :             if (cr > 0) {
     395       129615 :                 auto data = input.substr(0, cr);
     396       129615 :                 input.remove_prefix(cr);
     397       129615 :                 auto result = content(data);
     398       129615 :                 if (result != event::need_more) return result;
     399         1857 :                 break;
     400              :             }
     401       126633 :             input.remove_prefix(used);
     402       126633 :             if (found == match::partial) {
     403        16323 :                 matched_ = matched;
     404        16323 :                 held_from_ = 0;
     405        16323 :                 return event::need_more;
     406              :             }
     407       110310 :             return on_delimiter(found);
     408      6616983 :         }
     409       138474 :     }
     410              : }
     411              : 
     412       155910 : multipart_parser::event multipart_parser::header_line(std::string_view line) {
     413              :     // A field line: "name: value" CRLF, with a token as its name and no folding
     414       155910 :     if (line.size() < 2 || line[line.size() - 2] != '\r') return fail(multipart_error::malformed);
     415       155034 :     line.remove_suffix(2);
     416       155034 :     auto colon = line.find(':');
     417       155034 :     if (colon == std::string_view::npos || colon == 0) return fail(multipart_error::malformed);
     418       153357 :     auto name = line.substr(0, colon);
     419      2967822 :     for (char c : name) {
     420      2815431 :         if (!is_tchar(c)) return fail(multipart_error::malformed);
     421              :     }
     422       152391 :     auto value = trim(line.substr(colon + 1));
     423      4204818 :     for (char c : value) {
     424      4053525 :         if (!is_field_char(c)) return fail(multipart_error::malformed);
     425              :     }
     426       605172 :     headers_.emplace_back(std::string(name), std::string(value));
     427       151293 :     return event::need_more;
     428              : }
     429              : 
     430       142365 : multipart_parser::event multipart_parser::header_section_end() {
     431       142365 :     multipart_part part;
     432       142365 :     part.headers = std::move(headers_);
     433       142365 :     headers_.clear();
     434              : 
     435       142365 :     bool disposition = false;
     436       142365 :     bool content_type = false;
     437       285825 :     for (const auto& [name, value] : part.headers) {
     438       149418 :         if (::thinger::util::ascii::iequals(name, "content-disposition")) {
     439       140424 :             if (disposition || !parse_content_disposition(value, part)) return fail(multipart_error::malformed);
     440       134466 :             disposition = true;
     441         9528 :         } else if (::thinger::util::ascii::iequals(name, "content-type")) {
     442         8778 :             if (content_type) return fail(multipart_error::malformed);
     443         8244 :             content_type = true;
     444         8244 :             part.content_type = value;
     445              :         }
     446              :     }
     447              : 
     448       136407 :     if (part.has_filename) {
     449        44547 :         if (part.filename.size() > limits_.max_filename_size) return fail(multipart_error::filename_too_large);
     450        43983 :         if (++files_ > limits_.max_files) return fail(multipart_error::too_many_files);
     451              :     }
     452              : 
     453       134829 :     part_ = std::move(part);
     454       134829 :     part_size_ = 0;
     455       134829 :     state_ = state::content;
     456              :     // The content may start with the delimiter (an empty part): the CRLF before it ended
     457              :     // the header section
     458       134829 :     start_matching();
     459       134829 :     return event::part;
     460       142365 : }
     461              : 
     462       952048 : multipart_parser::event multipart_parser::parse(std::string_view& input) {
     463       952048 :     data_ = {};
     464              :     for (;;) {
     465      1542838 :         event result = event::need_more;
     466      1542838 :         switch (state_) {
     467       560089 :             case state::preamble:
     468              :             case state::content:
     469       560089 :                 result = scan(input);
     470       560089 :                 break;
     471              : 
     472       216771 :             case state::delimiter_line:
     473              :             case state::close_line:
     474              :                 // Transport padding, then CRLF
     475       221871 :                 while (!input.empty() && is_ows(input.front())) input.remove_prefix(1);
     476       216771 :                 if (input.empty()) break;
     477       214350 :                 if (input.front() != '\r') return fail(multipart_error::malformed);
     478       213456 :                 input.remove_prefix(1);
     479       213456 :                 state_ = state_ == state::delimiter_line ? state::delimiter_lf : state::close_lf;
     480       213456 :                 break;
     481              : 
     482       150399 :             case state::delimiter_lf:
     483       150399 :                 if (input.empty()) break;
     484       150399 :                 if (input.front() != '\n') return fail(multipart_error::malformed);
     485       150195 :                 input.remove_prefix(1);
     486       150195 :                 if (++parts_ > limits_.max_parts) return fail(multipart_error::too_many_parts);
     487       148572 :                 state_ = state::headers;
     488       148572 :                 header_size_ = 0;
     489       148572 :                 line_.clear();
     490       148572 :                 break;
     491              : 
     492       523503 :             case state::headers: {
     493       523503 :                 if (input.empty()) break;
     494              :                 // Up to the end of the line, counted against the limit before it is checked,
     495              :                 // so the error is the same however the section is split
     496       523503 :                 auto lf = input.find('\n');
     497       523503 :                 size_t size = lf == std::string_view::npos ? input.size() : lf + 1;
     498       523503 :                 if (size > limits_.max_header_size - header_size_) return fail(multipart_error::header_too_large);
     499       522231 :                 header_size_ += size;
     500       522231 :                 line_.append(input.data(), size);
     501       522231 :                 input.remove_prefix(size);
     502       522231 :                 if (lf == std::string_view::npos) break;
     503       298275 :                 if (line_ == "\r\n") {
     504       142365 :                     line_.clear();
     505       142365 :                     return header_section_end();
     506              :                 }
     507       155910 :                 if (header_line(line_) == event::error) return event::error;
     508       151293 :                 line_.clear();
     509       151293 :                 break;
     510              :             }
     511              : 
     512        63048 :             case state::close_lf:
     513        63048 :                 if (input.empty()) break;
     514        63048 :                 if (input.front() != '\n') return fail(multipart_error::malformed);
     515        62856 :                 input.remove_prefix(1);
     516        62856 :                 state_ = state::done;
     517        62856 :                 return event::end;
     518              : 
     519           39 :             case state::done:
     520              :                 // The epilogue
     521           39 :                 input = {};
     522           39 :                 return event::end;
     523              : 
     524        28989 :             case state::error:
     525        28989 :                 return event::error;
     526              :         }
     527      1299787 :         if (result != event::need_more) return result;
     528       980072 :         if (input.empty() && state_ != state::done) return event::need_more;
     529       590790 :     }
     530              : }
     531              : 
     532        31473 : multipart_parser::event multipart_parser::finish() {
     533        31473 :     data_ = {};
     534        31473 :     switch (state_) {
     535            0 :         case state::done:
     536            0 :             return event::end;
     537         1524 :         case state::close_line:
     538              :             // The closing delimiter may end the body, without CRLF
     539         1524 :             state_ = state::done;
     540         1524 :             return event::end;
     541        28986 :         case state::error:
     542        28986 :             return event::error;
     543          963 :         default:
     544          963 :             return fail(multipart_error::truncated);
     545              :     }
     546              : }
     547              : 
     548              : }
        

Generated by: LCOV version 2.0-1