1use core::mem;
8
9use alloc::{
10 string::{String, ToString},
11 vec::Vec,
12};
13
14use log::{debug, trace};
15use thiserror::Error;
16
17use crate::{
18 coroutine::*,
19 rfc9110::{chars::CRLF, send::HTTP_DEFAULT_MAX_BODY_LEN},
20 scan::{crlf_position, position},
21};
22
23pub const HTTP11_MAX_CHUNK_LINE_LEN: usize = 4 * 1024;
26
27#[derive(Debug, Error)]
29pub enum Http11ChunksReadError {
30 #[error("HTTP/1.1 read chunks failed: invalid chunk size `{0}`")]
32 InvalidChunkSize(String),
33 #[error("HTTP/1.1 read chunks failed: reached unexpected EOF")]
35 Eof,
36 #[error("HTTP/1.1 read chunks failed: chunk size line exceeds {0} bytes")]
38 ChunkLineTooLarge(usize),
39 #[error("HTTP/1.1 read chunks failed: body exceeds {0} bytes")]
41 BodyTooLarge(usize),
42}
43
44#[derive(Debug)]
46pub struct Http11ChunksReadOutput {
47 pub body: Vec<u8>,
49 pub remaining: Vec<u8>,
51}
52
53#[derive(Debug, Default)]
54enum State {
55 #[default]
56 ChunkSize,
57 ChunkData(usize),
58}
59
60#[derive(Debug)]
63pub struct Http11ChunksRead {
64 pub max_body_len: usize,
69 state: State,
70 wants_read: bool,
71 last_chunk: bool,
72 buf: Vec<u8>,
73 start: usize,
75 body: Vec<u8>,
76}
77
78impl Default for Http11ChunksRead {
79 fn default() -> Self {
80 Self {
81 max_body_len: HTTP_DEFAULT_MAX_BODY_LEN,
82 state: State::default(),
83 wants_read: false,
84 last_chunk: false,
85 buf: Vec::new(),
86 start: 0,
87 body: Vec::new(),
88 }
89 }
90}
91
92impl HttpCoroutine for Http11ChunksRead {
93 type Yield = HttpYield;
94 type Return = Result<Http11ChunksReadOutput, Http11ChunksReadError>;
95
96 fn resume(&mut self, arg: Option<&[u8]>) -> HttpCoroutineState<Self::Yield, Self::Return> {
97 match arg {
98 Some(&[]) => return HttpCoroutineState::Complete(Err(Http11ChunksReadError::Eof)),
99 Some(data) => {
100 self.buf.drain(..self.start);
101 self.start = 0;
102 self.buf.extend_from_slice(data);
103 }
104 None => {}
105 }
106
107 loop {
108 if self.wants_read {
109 self.wants_read = false;
110 return HttpCoroutineState::Yielded(HttpYield::WantsRead);
111 }
112
113 if self.last_chunk {
114 self.buf.drain(..self.start);
115 self.start = 0;
116
117 let body = mem::take(&mut self.body);
118 let remaining = mem::take(&mut self.buf);
119 return HttpCoroutineState::Complete(Ok(Http11ChunksReadOutput {
120 body,
121 remaining,
122 }));
123 }
124
125 let rest = &self.buf[self.start..];
126
127 match self.state {
128 State::ChunkSize => {
129 let line_window = rest.len().min(HTTP11_MAX_CHUNK_LINE_LEN + CRLF.len());
130
131 let Some(crlf) = crlf_position(&rest[..line_window]) else {
132 if line_window == HTTP11_MAX_CHUNK_LINE_LEN + CRLF.len() {
133 let err =
134 Http11ChunksReadError::ChunkLineTooLarge(HTTP11_MAX_CHUNK_LINE_LEN);
135 return HttpCoroutineState::Complete(Err(err));
136 }
137
138 self.wants_read = true;
139 continue;
140 };
141
142 let ext = match position(&rest[..crlf], |b| b == b';') {
143 None => crlf,
144 Some(ext) => {
145 let exts = String::from_utf8_lossy(rest[ext..crlf].trim_ascii());
146 trace!("ignore extension(s) `{exts}`");
147 ext
148 }
149 };
150
151 let chunk_size = String::from_utf8_lossy(rest[..ext].trim_ascii());
152
153 let is_hex = chunk_size.bytes().all(|b| b.is_ascii_hexdigit());
154
155 let Some(n) = is_hex
156 .then(|| usize::from_str_radix(&chunk_size, 16).ok())
157 .flatten()
158 else {
159 let chunk_size = chunk_size.to_string();
160 let err = Http11ChunksReadError::InvalidChunkSize(chunk_size);
161 return HttpCoroutineState::Complete(Err(err));
162 };
163
164 let room = self.max_body_len.saturating_sub(self.body.len());
167 if n > room || n.checked_add(CRLF.len()).is_none() {
168 let err = Http11ChunksReadError::BodyTooLarge(self.max_body_len);
169 return HttpCoroutineState::Complete(Err(err));
170 }
171
172 self.start += crlf + CRLF.len();
173 debug!("reading chunk of {n} bytes");
174 self.state = State::ChunkData(n);
175 }
176 State::ChunkData(size) if rest.len() < size + CRLF.len() => {
177 trace!("received incomplete chunk data {}/{size}", rest.len());
178 self.wants_read = true;
179 continue;
180 }
181 State::ChunkData(size) => {
182 self.body.extend_from_slice(&rest[..size]);
183 self.start += size + CRLF.len();
184 self.state = State::ChunkSize;
185 self.last_chunk = size == 0;
186 }
187 }
188 }
189 }
190}
191
192#[cfg(test)]
193mod tests {
194 use crate::rfc9112::chunk::*;
195
196 #[test]
197 fn single_chunk() {
198 let mut coroutine = Http11ChunksRead::default();
199 let out = expect_complete_ok(&mut coroutine, Some(b"5\r\nhello\r\n0\r\n\r\n"));
200 assert_eq!(out.body, b"hello");
201 assert_eq!(out.remaining, b"");
202 }
203
204 #[test]
205 fn empty_body() {
206 let mut coroutine = Http11ChunksRead::default();
207 let out = expect_complete_ok(&mut coroutine, Some(b"0\r\n\r\n"));
208 assert!(out.body.is_empty());
209 }
210
211 #[test]
212 fn ignored_extension() {
213 let mut coroutine = Http11ChunksRead::default();
214 let out = expect_complete_ok(&mut coroutine, Some(b"5;ext\r\nHello\r\n0\r\n\r\n"));
215 assert_eq!(out.body, b"Hello");
216 }
217
218 #[test]
219 fn invalid_chunk_size() {
220 let mut coroutine = Http11ChunksRead::default();
221 let err = expect_complete_err(&mut coroutine, Some(b":\r\n0\r\n\r\n"));
222 let Http11ChunksReadError::InvalidChunkSize(s) = err else {
223 panic!("expected InvalidChunkSize, got {err:?}");
224 };
225 assert_eq!(s, ":");
226 }
227
228 #[test]
229 fn signed_chunk_size_errors() {
230 let mut coroutine = Http11ChunksRead::default();
231 let err = expect_complete_err(&mut coroutine, Some(b"+5\r\nhello\r\n0\r\n\r\n"));
232 assert!(matches!(err, Http11ChunksReadError::InvalidChunkSize(_)));
233 }
234
235 #[test]
236 fn max_usize_chunk_size_errors() {
237 let mut coroutine = Http11ChunksRead {
238 max_body_len: usize::MAX,
239 ..Http11ChunksRead::default()
240 };
241 let err = expect_complete_err(&mut coroutine, Some(b"FFFFFFFFFFFFFFFF\r\n"));
242 assert!(matches!(err, Http11ChunksReadError::BodyTooLarge(_)));
243 }
244
245 #[test]
246 fn chunk_over_body_limit_errors() {
247 let mut coroutine = Http11ChunksRead {
248 max_body_len: 8,
249 ..Http11ChunksRead::default()
250 };
251 let err = expect_complete_err(&mut coroutine, Some(b"5\r\nhello\r\n4\r\n"));
252 assert!(matches!(err, Http11ChunksReadError::BodyTooLarge(8)));
253 }
254
255 #[test]
256 fn chunk_line_over_limit_errors() {
257 let mut coroutine = Http11ChunksRead::default();
258 let line = [b'0'; HTTP11_MAX_CHUNK_LINE_LEN + 2];
259 let err = expect_complete_err(&mut coroutine, Some(&line));
260 assert!(matches!(err, Http11ChunksReadError::ChunkLineTooLarge(_)));
261 }
262
263 #[test]
264 fn incomplete_chunk_size_then_resume() {
265 let mut coroutine = Http11ChunksRead::default();
266 expect_wants_read(&mut coroutine, Some(b"5\r"));
267 let out = expect_complete_ok(&mut coroutine, Some(b"\nHello\r\n0\r\n\r\n"));
268 assert_eq!(out.body, b"Hello");
269 }
270
271 #[test]
272 fn incomplete_chunk_data_then_resume() {
273 let mut coroutine = Http11ChunksRead::default();
274 expect_wants_read(&mut coroutine, Some(b"5\r\nHell"));
275 let out = expect_complete_ok(&mut coroutine, Some(b"o\r\n0\r\n\r\n"));
276 assert_eq!(out.body, b"Hello");
277 }
278
279 #[test]
280 fn wiki_ru_multi_chunk() {
281 let encoded = "9\r\nchunk 1, \r\n7\r\nchunk 2\r\n0\r\n\r\n";
282 let mut coroutine = Http11ChunksRead::default();
283 let out = expect_complete_ok(&mut coroutine, Some(encoded.as_bytes()));
284 assert_eq!(out.body, b"chunk 1, chunk 2");
285 }
286
287 #[test]
288 fn github_frewsxcv_test_vector() {
289 let encoded = "3\r\nhel\r\nb\r\nlo world!!!\r\n0\r\n\r\n";
290 let mut coroutine = Http11ChunksRead::default();
291 let out = expect_complete_ok(&mut coroutine, Some(encoded.as_bytes()));
292 assert_eq!(out.body, b"hello world!!!");
293 }
294
295 fn expect_wants_read(cor: &mut Http11ChunksRead, arg: Option<&[u8]>) {
296 match cor.resume(arg) {
297 HttpCoroutineState::Yielded(HttpYield::WantsRead) => {}
298 state => panic!("expected WantsRead, got {state:?}"),
299 }
300 }
301
302 fn expect_complete_ok(
303 cor: &mut Http11ChunksRead,
304 arg: Option<&[u8]>,
305 ) -> Http11ChunksReadOutput {
306 match cor.resume(arg) {
307 HttpCoroutineState::Complete(Ok(out)) => out,
308 state => panic!("expected Complete(Ok), got {state:?}"),
309 }
310 }
311
312 fn expect_complete_err(
313 cor: &mut Http11ChunksRead,
314 arg: Option<&[u8]>,
315 ) -> Http11ChunksReadError {
316 match cor.resume(arg) {
317 HttpCoroutineState::Complete(Err(err)) => err,
318 state => panic!("expected Complete(Err), got {state:?}"),
319 }
320 }
321}