1use core::mem;
8
9use alloc::{
10 string::{String, ToString},
11 vec::Vec,
12};
13
14use log::{debug, trace};
15use memchr::{memchr, memmem};
16use thiserror::Error;
17
18use crate::{coroutine::*, rfc9110::chars::CRLF};
19
20#[derive(Debug, Error)]
22pub enum Http11ChunksReadError {
23 #[error("HTTP/1.1 read chunks failed: invalid chunk size `{0}`")]
25 InvalidChunkSize(String),
26}
27
28#[derive(Debug)]
30pub struct Http11ChunksReadOutput {
31 pub body: Vec<u8>,
33 pub remaining: Vec<u8>,
35}
36
37#[derive(Debug, Default)]
38enum State {
39 #[default]
40 ChunkSize,
41 ChunkData(usize),
42}
43
44#[derive(Debug, Default)]
47pub struct Http11ChunksRead {
48 state: State,
49 wants_read: bool,
50 last_chunk: bool,
51 buf: Vec<u8>,
52 body: Vec<u8>,
53}
54
55impl HttpCoroutine for Http11ChunksRead {
56 type Yield = HttpYield;
57 type Return = Result<Http11ChunksReadOutput, Http11ChunksReadError>;
58
59 fn resume(&mut self, arg: Option<&[u8]>) -> HttpCoroutineState<Self::Yield, Self::Return> {
60 if let Some(data) = arg {
61 self.buf.extend_from_slice(data);
62 }
63
64 loop {
65 if self.wants_read {
66 self.wants_read = false;
67 return HttpCoroutineState::Yielded(HttpYield::WantsRead);
68 }
69
70 if self.last_chunk {
71 let body = mem::take(&mut self.body);
72 let remaining = mem::take(&mut self.buf);
73 return HttpCoroutineState::Complete(Ok(Http11ChunksReadOutput {
74 body,
75 remaining,
76 }));
77 }
78
79 match self.state {
80 State::ChunkSize => {
81 let Some(crlf) = memmem::find(&self.buf, &CRLF) else {
82 self.wants_read = true;
83 continue;
84 };
85
86 let ext = match memchr(b';', &self.buf[..crlf]) {
87 None => crlf,
88 Some(ext) => {
89 let exts = String::from_utf8_lossy(self.buf[ext..crlf].trim_ascii());
90 trace!("ignore extension(s) `{exts}`");
91 ext
92 }
93 };
94
95 let chunk_size = String::from_utf8_lossy(self.buf[..ext].trim_ascii());
96
97 let Ok(n) = usize::from_str_radix(&chunk_size, 16) else {
98 let chunk_size = chunk_size.to_string();
99 let err = Http11ChunksReadError::InvalidChunkSize(chunk_size);
100 return HttpCoroutineState::Complete(Err(err));
101 };
102
103 self.buf.drain(..crlf + CRLF.len());
104 debug!("reading chunk of {n} bytes");
105 self.state = State::ChunkData(n);
106 }
107 State::ChunkData(size) if self.buf.len() < size + CRLF.len() => {
108 trace!("received incomplete chunk data {}/{size}", self.buf.len());
109 self.wants_read = true;
110 continue;
111 }
112 State::ChunkData(size) => {
113 self.body.extend(self.buf.drain(..size));
114 self.buf.drain(..CRLF.len());
115 self.state = State::ChunkSize;
116 self.last_chunk = size == 0;
117 }
118 }
119 }
120 }
121}
122
123#[cfg(test)]
124mod tests {
125 use crate::{coroutine::*, rfc9112::chunk::*};
126
127 #[test]
128 fn single_chunk() {
129 let mut coroutine = Http11ChunksRead::default();
130 let out = expect_complete_ok(&mut coroutine, Some(b"5\r\nhello\r\n0\r\n\r\n"));
131 assert_eq!(out.body, b"hello");
132 assert_eq!(out.remaining, b"");
133 }
134
135 #[test]
136 fn empty_body() {
137 let mut coroutine = Http11ChunksRead::default();
138 let out = expect_complete_ok(&mut coroutine, Some(b"0\r\n\r\n"));
139 assert!(out.body.is_empty());
140 }
141
142 #[test]
143 fn ignored_extension() {
144 let mut coroutine = Http11ChunksRead::default();
145 let out = expect_complete_ok(&mut coroutine, Some(b"5;ext\r\nHello\r\n0\r\n\r\n"));
146 assert_eq!(out.body, b"Hello");
147 }
148
149 #[test]
150 fn invalid_chunk_size() {
151 let mut coroutine = Http11ChunksRead::default();
152 let err = expect_complete_err(&mut coroutine, Some(b":\r\n0\r\n\r\n"));
153 let Http11ChunksReadError::InvalidChunkSize(s) = err;
154 assert_eq!(s, ":");
155 }
156
157 #[test]
158 fn incomplete_chunk_size_then_resume() {
159 let mut coroutine = Http11ChunksRead::default();
160 expect_wants_read(&mut coroutine, Some(b"5\r"));
161 let out = expect_complete_ok(&mut coroutine, Some(b"\nHello\r\n0\r\n\r\n"));
162 assert_eq!(out.body, b"Hello");
163 }
164
165 #[test]
166 fn incomplete_chunk_data_then_resume() {
167 let mut coroutine = Http11ChunksRead::default();
168 expect_wants_read(&mut coroutine, Some(b"5\r\nHell"));
169 let out = expect_complete_ok(&mut coroutine, Some(b"o\r\n0\r\n\r\n"));
170 assert_eq!(out.body, b"Hello");
171 }
172
173 #[test]
174 fn wiki_ru_multi_chunk() {
175 let encoded = "9\r\nchunk 1, \r\n7\r\nchunk 2\r\n0\r\n\r\n";
176 let mut coroutine = Http11ChunksRead::default();
177 let out = expect_complete_ok(&mut coroutine, Some(encoded.as_bytes()));
178 assert_eq!(out.body, b"chunk 1, chunk 2");
179 }
180
181 #[test]
182 fn github_frewsxcv_test_vector() {
183 let encoded = "3\r\nhel\r\nb\r\nlo world!!!\r\n0\r\n\r\n";
184 let mut coroutine = Http11ChunksRead::default();
185 let out = expect_complete_ok(&mut coroutine, Some(encoded.as_bytes()));
186 assert_eq!(out.body, b"hello world!!!");
187 }
188
189 fn expect_wants_read(cor: &mut Http11ChunksRead, arg: Option<&[u8]>) {
190 match cor.resume(arg) {
191 HttpCoroutineState::Yielded(HttpYield::WantsRead) => {}
192 state => panic!("expected WantsRead, got {state:?}"),
193 }
194 }
195
196 fn expect_complete_ok(
197 cor: &mut Http11ChunksRead,
198 arg: Option<&[u8]>,
199 ) -> Http11ChunksReadOutput {
200 match cor.resume(arg) {
201 HttpCoroutineState::Complete(Ok(out)) => out,
202 state => panic!("expected Complete(Ok), got {state:?}"),
203 }
204 }
205
206 fn expect_complete_err(
207 cor: &mut Http11ChunksRead,
208 arg: Option<&[u8]>,
209 ) -> Http11ChunksReadError {
210 match cor.resume(arg) {
211 HttpCoroutineState::Complete(Err(err)) => err,
212 state => panic!("expected Complete(Err), got {state:?}"),
213 }
214 }
215}