LCOV - code coverage report
Current view: top level - http/common - chunked_decoder.cpp (source / functions) Coverage Total Hit
Test: coverage_filtered.info Lines: 97.0 % 169 164
Test Date: 2026-11-10 10:38:07 Functions: 100.0 % 6 6

            Line data    Source code
       1              : #include "chunked_decoder.hpp"
       2              : #include "syntax.hpp"
       3              : #include <algorithm>
       4              : #include <cstring>
       5              : #include <limits>
       6              : 
       7              : namespace thinger::http {
       8              : 
       9              :     namespace {
      10              : 
      11       124734 :         int hex_value(uint8_t c) {
      12       124734 :             if (c >= '0' && c <= '9') return c - '0';
      13        56556 :             if (c >= 'a' && c <= 'f') return c - 'a' + 10;
      14        49457 :             if (c >= 'A' && c <= 'F') return c - 'A' + 10;
      15        48995 :             return -1;
      16              :         }
      17              : 
      18              :         // qdtext = HTAB / SP / %x21 / %x23-5B / %x5D-7E / obs-text (RFC 9110, section 5.6.4)
      19         6156 :         bool is_qdtext(uint8_t c) {
      20         6156 :             return c == '\t' || c == ' ' || c == 0x21 || (c >= 0x23 && c <= 0x5B) || (c >= 0x5D && c <= 0x7E) || c >= 0x80;
      21              :         }
      22              : 
      23              :     }
      24              : 
      25        64994 :     size_t chunked_decoder::decode(const uint8_t* in, size_t size, uint8_t* out, size_t out_size, size_t& produced) {
      26        64994 :         produced = 0;
      27        64994 :         size_t i = 0;
      28       460735 :         while (i < size && state_ != state::done && state_ != state::error) {
      29       444899 :             if (state_ == state::data) {
      30        60356 :                 if (produced == out_size) break;
      31        25028 :                 size_t bytes = std::min({chunk_size_, size - i, out_size - produced});
      32        25028 :                 std::memcpy(out + produced, in + i, bytes);
      33        25028 :                 produced += bytes;
      34        25028 :                 i += bytes;
      35        25028 :                 data_consumed(bytes);
      36        25028 :                 continue;
      37        25028 :             }
      38       384543 :             if (!consume(in[i])) break;
      39       370713 :             i++;
      40              :         }
      41        64994 :         return i;
      42              :     }
      43              : 
      44        51967 :     size_t chunked_decoder::data_pending() const {
      45        51967 :         return state_ == state::data ? chunk_size_ : 0;
      46              :     }
      47              : 
      48        52085 :     void chunked_decoder::data_consumed(size_t size) {
      49        52085 :         chunk_size_ -= std::min(size, chunk_size_);
      50        52085 :         if (chunk_size_ == 0) state_ = state::data_cr;
      51        52085 :     }
      52              : 
      53       384543 :     bool chunked_decoder::consume(uint8_t c) {
      54              :         // bound the chunk size line: size and extensions (the states up to ext_bws)
      55       384543 :         if (state_ <= state::ext_bws && ++line_size_ > max_line_size) return fail();
      56              : 
      57       384537 :         switch (state_) {
      58              :             // chunk-size = 1*HEXDIG, then [ chunk-ext ] CRLF, with no whitespace between them
      59       124734 :             case state::size_start:
      60              :             case state::size: {
      61       124734 :                 int digit = hex_value(c);
      62       124734 :                 if (digit >= 0) {
      63        75739 :                     if (chunk_size_ > (std::numeric_limits<size_t>::max() >> 4)) return fail();
      64        75352 :                     chunk_size_ = chunk_size_ * 16 + digit;
      65        75352 :                     state_ = state::size;
      66        75352 :                     return true;
      67              :                 }
      68        48995 :                 if (state_ == state::size_start) return fail();
      69        47201 :                 if (c == ';') {
      70         8793 :                     state_ = state::ext_name_start;
      71         8793 :                     return true;
      72              :                 }
      73        38408 :                 if (c == '\r') {
      74        36968 :                     state_ = state::size_lf;
      75        36968 :                     return true;
      76              :                 }
      77         1440 :                 return fail();
      78              :             }
      79              : 
      80              :             // chunk-ext = *( BWS ";" BWS ext-name [ BWS "=" BWS ext-val ] ), ext-val = token /
      81              :             // quoted-string (RFC 9112, section 7.1.1). Extensions are ignored, but must follow
      82              :             // the grammar: anything else could be read differently by another parser.
      83        12378 :             case state::ext_name_start:
      84        12378 :                 if (syntax::is_whitespace(c)) return true;
      85        11010 :                 if (!syntax::is_token_char(c)) return fail();
      86        10296 :                 state_ = state::ext_name;
      87        10296 :                 return true;
      88              : 
      89        37503 :             case state::ext_name:
      90        37503 :                 if (syntax::is_token_char(c)) return true;
      91        10293 :                 if (syntax::is_whitespace(c)) {
      92          816 :                     state_ = state::ext_after_name;
      93          816 :                     return true;
      94              :                 }
      95         9477 :                 if (c == '=') {
      96         6192 :                     state_ = state::ext_value_start;
      97         6192 :                     return true;
      98              :                 }
      99         3285 :                 if (c == ';') {
     100          465 :                     state_ = state::ext_name_start;
     101          465 :                     return true;
     102              :                 }
     103         2820 :                 if (c == '\r') {
     104         1728 :                     state_ = state::size_lf;
     105         1728 :                     return true;
     106              :                 }
     107         1092 :                 return fail();
     108              : 
     109          816 :             case state::ext_after_name:
     110          816 :                 if (syntax::is_whitespace(c)) return true;
     111          816 :                 if (c == '=') {
     112          456 :                     state_ = state::ext_value_start;
     113          456 :                     return true;
     114              :                 }
     115          360 :                 if (c == ';') {
     116            0 :                     state_ = state::ext_name_start;
     117            0 :                     return true;
     118              :                 }
     119          360 :                 return fail();
     120              : 
     121         7104 :             case state::ext_value_start:
     122         7104 :                 if (syntax::is_whitespace(c)) return true;
     123         6648 :                 if (c == '"') {
     124         4047 :                     state_ = state::ext_quoted;
     125         4047 :                     return true;
     126              :                 }
     127         2601 :                 if (!syntax::is_token_char(c)) return fail();
     128         2241 :                 state_ = state::ext_value;
     129         2241 :                 return true;
     130              : 
     131         3981 :             case state::ext_value:
     132         3981 :                 if (syntax::is_token_char(c)) return true;
     133         2241 :                 if (syntax::is_whitespace(c)) {
     134         1215 :                     state_ = state::ext_bws;
     135         1215 :                     return true;
     136              :                 }
     137         1026 :                 if (c == ';') {
     138          420 :                     state_ = state::ext_name_start;
     139          420 :                     return true;
     140              :                 }
     141          606 :                 if (c == '\r') {
     142          606 :                     state_ = state::size_lf;
     143          606 :                     return true;
     144              :                 }
     145            0 :                 return fail();
     146              : 
     147         9897 :             case state::ext_quoted:
     148         9897 :                 if (c == '"') {
     149         2529 :                     state_ = state::ext_after_quoted;
     150         2529 :                     return true;
     151              :                 }
     152         7368 :                 if (c == '\\') {
     153         1212 :                     state_ = state::ext_quoted_pair;
     154         1212 :                     return true;
     155              :                 }
     156         6156 :                 return is_qdtext(c) ? true : fail();
     157              : 
     158              :             // quoted-pair = "\" ( HTAB / SP / VCHAR / obs-text )
     159         1212 :             case state::ext_quoted_pair:
     160         1212 :                 if (!syntax::is_field_value_char(c)) return fail();
     161          828 :                 state_ = state::ext_quoted;
     162          828 :                 return true;
     163              : 
     164         2529 :             case state::ext_after_quoted:
     165         2529 :                 if (syntax::is_whitespace(c)) {
     166          456 :                     state_ = state::ext_bws;
     167          456 :                     return true;
     168              :                 }
     169         2073 :                 if (c == ';') {
     170          420 :                     state_ = state::ext_name_start;
     171          420 :                     return true;
     172              :                 }
     173         1653 :                 if (c == '\r') {
     174         1269 :                     state_ = state::size_lf;
     175         1269 :                     return true;
     176              :                 }
     177          384 :                 return fail();
     178              : 
     179              :             // whitespace after a value: only before another extension (BWS ";")
     180         1671 :             case state::ext_bws:
     181         1671 :                 if (syntax::is_whitespace(c)) return true;
     182         1671 :                 if (c == ';') {
     183          912 :                     state_ = state::ext_name_start;
     184          912 :                     return true;
     185              :                 }
     186          759 :                 return fail();
     187              : 
     188        40571 :             case state::size_lf:
     189        40571 :                 if (c != '\n') return fail();
     190        40235 :                 line_size_ = 0;
     191        40235 :                 state_ = chunk_size_ == 0 ? state::trailer_start : state::data;
     192        40235 :                 return true;
     193              : 
     194        30806 :             case state::data_cr:
     195        30806 :                 if (c != '\r') return fail();
     196        29756 :                 state_ = state::data_lf;
     197        29756 :                 return true;
     198              : 
     199        29756 :             case state::data_lf:
     200        29756 :                 if (c != '\n') return fail();
     201        29756 :                 chunk_size_ = 0;
     202        29756 :                 state_ = state::size_start;
     203        29756 :                 return true;
     204              : 
     205              :             // trailer-section = *( field-line CRLF ), then the final CRLF, with
     206              :             // field-line = field-name ":" OWS field-value OWS (RFC 9112, section 5)
     207        11481 :             case state::trailer_start:
     208        11481 :                 if (++trailer_size_ > max_trailer_size) return fail();
     209        11481 :                 if (c == '\r') {
     210         6051 :                     state_ = state::end_lf;
     211         6051 :                     return true;
     212              :                 }
     213              :                 // a field line starts with its name (no obsolete line folding), so that what
     214              :                 // is not a field line (e.g. a request line) is never taken as a trailer
     215         5430 :                 if (!syntax::is_token_char(c)) return fail();
     216         4476 :                 state_ = state::trailer_name;
     217         4476 :                 return true;
     218              : 
     219        26634 :             case state::trailer_name:
     220        26634 :                 if (++trailer_size_ > max_trailer_size) return fail();
     221        26634 :                 if (c == ':') {
     222         2784 :                     state_ = state::trailer;
     223         2784 :                     return true;
     224              :                 }
     225        23850 :                 return syntax::is_token_char(c) ? true : fail();
     226              : 
     227        35325 :             case state::trailer:
     228        35325 :                 if (++trailer_size_ > max_trailer_size) return fail();
     229        35322 :                 if (c == '\r') {
     230         2088 :                     state_ = state::trailer_lf;
     231         2088 :                     return true;
     232              :                 }
     233        33234 :                 return syntax::is_field_value_char(c) ? true : fail();
     234              : 
     235         2088 :             case state::trailer_lf:
     236         2088 :                 if (c != '\n') return fail();
     237         2088 :                 state_ = state::trailer_start;
     238         2088 :                 return true;
     239              : 
     240         6051 :             case state::end_lf:
     241         6051 :                 if (c != '\n') return fail();
     242         5763 :                 state_ = state::done;
     243         5763 :                 return true;
     244              : 
     245            0 :             default:
     246            0 :                 return fail();
     247              :         }
     248              :     }
     249              : 
     250              : }
        

Generated by: LCOV version 2.0-1