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