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 : }
|