Line data Source code
1 : #ifndef THINGER_HTTP_COMMON_CHUNKED_DECODER_HPP
2 : #define THINGER_HTTP_COMMON_CHUNKED_DECODER_HPP
3 :
4 : #include <cstddef>
5 : #include <cstdint>
6 :
7 : namespace thinger::http {
8 :
9 : /**
10 : * Strict decoder of the chunked transfer coding (RFC 9112, section 7.1), of request and
11 : * response bodies. It is fed with the raw bytes following the header section and stops
12 : * right after the end of the body (last chunk and trailer section), so whatever follows
13 : * is left for the next message.
14 : * Anything that does not follow the grammar fails the decoding: there is no recovery,
15 : * as a lenient parser would frame the body differently than a proxy in front of it.
16 : */
17 : class chunked_decoder {
18 : public:
19 : // Longest chunk size line (size and extensions) and trailer section accepted
20 : static constexpr size_t max_line_size = 4096;
21 : static constexpr size_t max_trailer_size = 8192;
22 :
23 : /// Decode up to `size` input bytes, writing up to `out_size` body bytes to `out`
24 : /// (their number is stored in `produced`). Returns the input bytes consumed: it stops
25 : /// at the end of the body, on error, or when `out` is full.
26 : size_t decode(const uint8_t* in, size_t size, uint8_t* out, size_t out_size, size_t& produced);
27 :
28 : /// Bytes of the current chunk that can be read straight from the input (0 if the
29 : /// decoder is not within chunk data), and notify that `size` of them were read so
30 : size_t data_pending() const;
31 : void data_consumed(size_t size);
32 :
33 141682 : bool done() const { return state_ == state::done; }
34 115430 : bool failed() const { return state_ == state::error; }
35 :
36 : private:
37 : // The states of the chunk size line (size and extensions) come first, up to ext_bws
38 : enum class state {
39 : size_start, // first hex digit of the chunk size
40 : size, // more hex digits, ';' or CR
41 : ext_name_start, // after ';': whitespace or the first character of a name
42 : ext_name, // within an extension name
43 : ext_after_name, // whitespace after an extension name, then '=' or ';'
44 : ext_value_start, // after '=': whitespace, then a token or a quoted string
45 : ext_value, // within a token value
46 : ext_quoted, // within a quoted string value
47 : ext_quoted_pair, // after a backslash in a quoted string
48 : ext_after_quoted, // after a quoted string: whitespace, ';' or CR
49 : ext_bws, // whitespace after a value, before the next ';'
50 : size_lf,
51 : data,
52 : data_cr,
53 : data_lf,
54 : trailer_start, // start of a trailer field line, or CR of the final CRLF
55 : trailer_name, // within a trailer field name, up to ':'
56 : trailer, // within a trailer field value
57 : trailer_lf,
58 : end_lf, // LF of the final CRLF
59 : done,
60 : error
61 : };
62 :
63 : state state_ = state::size_start;
64 : size_t chunk_size_ = 0; // size being parsed, then data left in the chunk
65 : size_t line_size_ = 0; // bytes of the current size line
66 : size_t trailer_size_ = 0; // bytes of the trailer section
67 :
68 13830 : bool fail() { state_ = state::error; return false; }
69 : bool consume(uint8_t byte);
70 : };
71 :
72 : }
73 :
74 : #endif
|