io_http/rfc9112/
chunk_stream.rs1use 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 rfc9112::chunk::HTTP11_MAX_CHUNK_LINE_LEN,
21 scan::{crlf_position, position},
22};
23
24#[derive(Debug, Error)]
26pub enum Http11ChunksReadStreamError {
27 #[error("HTTP/1.1 read chunks failed: invalid chunk size `{0}`")]
29 InvalidChunkSize(String),
30 #[error("HTTP/1.1 read chunks failed: reached unexpected EOF")]
32 Eof,
33 #[error("HTTP/1.1 read chunks failed: chunk size line exceeds {0} bytes")]
35 ChunkLineTooLarge(usize),
36 #[error("HTTP/1.1 read chunks failed: chunk exceeds {0} bytes")]
38 ChunkTooLarge(usize),
39}
40
41#[derive(Debug)]
44pub enum Http11ChunksReadStreamYield {
45 WantsRead,
48 Frame {
50 body: Vec<u8>,
52 },
53}
54
55#[derive(Debug, Default)]
56enum State {
57 #[default]
58 ChunkSize,
59 ChunkData(usize),
60}
61
62#[derive(Debug)]
65pub struct Http11ChunksReadStream {
66 pub max_chunk_len: usize,
71 state: State,
72 wants_read: bool,
73 done: bool,
74 buf: Vec<u8>,
75 start: usize,
77}
78
79impl Default for Http11ChunksReadStream {
80 fn default() -> Self {
81 Self {
82 max_chunk_len: HTTP_DEFAULT_MAX_BODY_LEN,
83 state: State::default(),
84 wants_read: false,
85 done: false,
86 buf: Vec::new(),
87 start: 0,
88 }
89 }
90}
91
92impl HttpCoroutine for Http11ChunksReadStream {
93 type Yield = Http11ChunksReadStreamYield;
94 type Return = Result<Vec<u8>, Http11ChunksReadStreamError>;
95
96 fn resume(&mut self, arg: Option<&[u8]>) -> HttpCoroutineState<Self::Yield, Self::Return> {
97 match arg {
98 Some(&[]) => {
99 return HttpCoroutineState::Complete(Err(Http11ChunksReadStreamError::Eof));
100 }
101 Some(data) => {
102 self.buf.drain(..self.start);
103 self.start = 0;
104 self.buf.extend_from_slice(data);
105 }
106 None => {}
107 }
108
109 loop {
110 if self.wants_read {
111 self.wants_read = false;
112 return HttpCoroutineState::Yielded(Http11ChunksReadStreamYield::WantsRead);
113 }
114
115 if self.done {
116 self.buf.drain(..self.start);
117 self.start = 0;
118
119 let remaining = mem::take(&mut self.buf);
120 return HttpCoroutineState::Complete(Ok(remaining));
121 }
122
123 let rest = &self.buf[self.start..];
124
125 match self.state {
126 State::ChunkSize => {
127 let line_window = rest.len().min(HTTP11_MAX_CHUNK_LINE_LEN + CRLF.len());
128
129 let Some(crlf) = crlf_position(&rest[..line_window]) else {
130 if line_window == HTTP11_MAX_CHUNK_LINE_LEN + CRLF.len() {
131 let limit = HTTP11_MAX_CHUNK_LINE_LEN;
132 let err = Http11ChunksReadStreamError::ChunkLineTooLarge(limit);
133 return HttpCoroutineState::Complete(Err(err));
134 }
135
136 self.wants_read = true;
137 continue;
138 };
139
140 let ext = match position(&rest[..crlf], |b| b == b';') {
141 None => crlf,
142 Some(ext) => {
143 let exts = String::from_utf8_lossy(rest[ext..crlf].trim_ascii());
144 trace!("ignore extension(s) `{exts}`");
145 ext
146 }
147 };
148
149 let chunk_size = String::from_utf8_lossy(rest[..ext].trim_ascii());
150
151 let is_hex = chunk_size.bytes().all(|b| b.is_ascii_hexdigit());
152
153 let Some(n) = is_hex
154 .then(|| usize::from_str_radix(&chunk_size, 16).ok())
155 .flatten()
156 else {
157 let chunk_size = chunk_size.to_string();
158 let err = Http11ChunksReadStreamError::InvalidChunkSize(chunk_size);
159 return HttpCoroutineState::Complete(Err(err));
160 };
161
162 if n > self.max_chunk_len || n.checked_add(CRLF.len()).is_none() {
165 let err = Http11ChunksReadStreamError::ChunkTooLarge(self.max_chunk_len);
166 return HttpCoroutineState::Complete(Err(err));
167 }
168
169 self.start += crlf + CRLF.len();
170 debug!("reading chunk of {n} bytes");
171 self.state = State::ChunkData(n);
172 }
173 State::ChunkData(size) if rest.len() < size + CRLF.len() => {
174 trace!("received incomplete chunk data {}/{size}", rest.len());
175 self.wants_read = true;
176 continue;
177 }
178 State::ChunkData(0) => {
179 self.start += CRLF.len();
180 self.state = State::ChunkSize;
181 self.done = true;
182 }
183 State::ChunkData(size) => {
184 let body = rest[..size].to_vec();
185 self.start += size + CRLF.len();
186 self.state = State::ChunkSize;
187 return HttpCoroutineState::Yielded(Http11ChunksReadStreamYield::Frame {
188 body,
189 });
190 }
191 }
192 }
193 }
194}
195
196#[cfg(test)]
197mod tests {
198 use alloc::vec;
199
200 use crate::rfc9112::chunk_stream::*;
201
202 #[test]
203 fn single_chunk() {
204 let frames = collect_all(b"5\r\nhello\r\n0\r\n\r\n");
205 assert_eq!(frames, vec![b"hello".to_vec()]);
206 }
207
208 #[test]
209 fn two_chunks_yielded_separately() {
210 let frames = collect_all(b"5\r\nhello\r\n6\r\n world\r\n0\r\n\r\n");
211 assert_eq!(frames, vec![b"hello".to_vec(), b" world".to_vec()]);
212 }
213
214 #[test]
215 fn empty_body() {
216 let frames = collect_all(b"0\r\n\r\n");
217 assert!(frames.is_empty());
218 }
219
220 #[test]
221 fn ignored_extension() {
222 let frames = collect_all(b"5;ext\r\nHello\r\n0\r\n\r\n");
223 assert_eq!(frames, vec![b"Hello".to_vec()]);
224 }
225
226 #[test]
227 fn invalid_chunk_size() {
228 let mut coroutine = Http11ChunksReadStream::default();
229 let err = expect_complete_err(&mut coroutine, Some(b":\r\n0\r\n\r\n"));
230 let Http11ChunksReadStreamError::InvalidChunkSize(s) = err else {
231 panic!("expected InvalidChunkSize, got {err:?}");
232 };
233 assert_eq!(s, ":");
234 }
235
236 #[test]
237 fn max_usize_chunk_size_errors() {
238 let mut coroutine = Http11ChunksReadStream {
239 max_chunk_len: usize::MAX,
240 ..Http11ChunksReadStream::default()
241 };
242 let err = expect_complete_err(&mut coroutine, Some(b"FFFFFFFFFFFFFFFF\r\n"));
243 assert!(matches!(err, Http11ChunksReadStreamError::ChunkTooLarge(_)));
244 }
245
246 #[test]
247 fn chunk_over_limit_errors() {
248 let mut coroutine = Http11ChunksReadStream {
249 max_chunk_len: 4,
250 ..Http11ChunksReadStream::default()
251 };
252 let err = expect_complete_err(&mut coroutine, Some(b"5\r\nhello\r\n"));
253 assert!(matches!(err, Http11ChunksReadStreamError::ChunkTooLarge(4)));
254 }
255
256 #[test]
257 fn chunk_line_over_limit_errors() {
258 let mut coroutine = Http11ChunksReadStream::default();
259 let line = [b'0'; HTTP11_MAX_CHUNK_LINE_LEN + 2];
260 let err = expect_complete_err(&mut coroutine, Some(&line));
261 assert!(matches!(
262 err,
263 Http11ChunksReadStreamError::ChunkLineTooLarge(_)
264 ));
265 }
266
267 #[test]
268 fn incomplete_chunk_size_then_resume() {
269 let mut coroutine = Http11ChunksReadStream::default();
270 expect_wants_read(&mut coroutine, Some(b"5\r"));
271 let body = expect_frame(&mut coroutine, Some(b"\nHello\r\n0\r\n\r\n"));
272 assert_eq!(body, b"Hello");
273 let remaining = expect_complete_ok(&mut coroutine, None);
274 assert!(remaining.is_empty());
275 }
276
277 #[test]
278 fn remaining_bytes_after_terminator() {
279 let mut coroutine = Http11ChunksReadStream::default();
280 let body = expect_frame(&mut coroutine, Some(b"5\r\nhello\r\n0\r\n\r\nNEXT"));
281 assert_eq!(body, b"hello");
282 let remaining = expect_complete_ok(&mut coroutine, None);
283 assert_eq!(remaining, b"NEXT");
284 }
285
286 fn collect_all(encoded: &[u8]) -> Vec<Vec<u8>> {
287 let mut coroutine = Http11ChunksReadStream::default();
288 let mut arg: Option<&[u8]> = Some(encoded);
289 let mut frames = Vec::new();
290
291 loop {
292 match coroutine.resume(arg.take()) {
293 HttpCoroutineState::Yielded(Http11ChunksReadStreamYield::Frame { body }) => {
294 frames.push(body);
295 }
296 HttpCoroutineState::Complete(Ok(remaining)) => {
297 assert!(remaining.is_empty(), "unexpected remaining bytes");
298 return frames;
299 }
300 state => panic!("expected Frame or Complete, got {state:?}"),
301 }
302 }
303 }
304
305 fn expect_wants_read(cor: &mut Http11ChunksReadStream, arg: Option<&[u8]>) {
306 match cor.resume(arg) {
307 HttpCoroutineState::Yielded(Http11ChunksReadStreamYield::WantsRead) => {}
308 state => panic!("expected WantsRead, got {state:?}"),
309 }
310 }
311
312 fn expect_frame(cor: &mut Http11ChunksReadStream, arg: Option<&[u8]>) -> Vec<u8> {
313 match cor.resume(arg) {
314 HttpCoroutineState::Yielded(Http11ChunksReadStreamYield::Frame { body }) => body,
315 state => panic!("expected Frame, got {state:?}"),
316 }
317 }
318
319 fn expect_complete_ok(cor: &mut Http11ChunksReadStream, arg: Option<&[u8]>) -> Vec<u8> {
320 match cor.resume(arg) {
321 HttpCoroutineState::Complete(Ok(remaining)) => remaining,
322 state => panic!("expected Complete(Ok), got {state:?}"),
323 }
324 }
325
326 fn expect_complete_err(
327 cor: &mut Http11ChunksReadStream,
328 arg: Option<&[u8]>,
329 ) -> Http11ChunksReadStreamError {
330 match cor.resume(arg) {
331 HttpCoroutineState::Complete(Err(err)) => err,
332 state => panic!("expected Complete(Err), got {state:?}"),
333 }
334 }
335}