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