net_wire/http1/
chunked.rs1use super::error::Http1ParseError;
4use super::fields::{Http1Fields, LineEndError, find_line_end, is_token};
5
6#[derive(Clone, Copy, Debug, Eq, PartialEq)]
8pub struct Http1Chunk<'a> {
9 size: usize,
10 data: &'a [u8],
11 extensions: &'a [u8],
12}
13
14impl<'a> Http1Chunk<'a> {
15 pub const fn size(self) -> usize {
17 self.size
18 }
19
20 pub const fn data(self) -> &'a [u8] {
22 self.data
23 }
24
25 pub const fn extensions(self) -> &'a [u8] {
27 self.extensions
28 }
29}
30
31#[derive(Clone, Copy, Debug, Eq, PartialEq)]
33pub struct Http1ChunkedBody<'a> {
34 bytes: &'a [u8],
35 chunks_end: usize,
36 decoded_len: usize,
37 last_chunk_extensions: &'a [u8],
38 trailers: Http1Fields<'a>,
39}
40
41impl<'a> Http1ChunkedBody<'a> {
42 pub fn parse(input: &'a [u8]) -> Result<Self, Http1ParseError> {
47 let mut offset = 0;
48 let mut decoded_len = 0usize;
49 loop {
50 let line_end = chunk_line_end(input, offset)?;
51 let line = &input[offset..line_end];
52 let parsed = parse_chunk_line(line)?;
53 let after_line = line_end + 2;
54 if parsed.size == 0 {
55 let trailer_start = after_line;
56 let mut trailer_offset = trailer_start;
57 loop {
58 let trailer_end = trailer_line_end(input, trailer_offset)?;
59 if trailer_end == trailer_offset {
60 let trailers =
61 Http1Fields::parse(&input[trailer_start..trailer_offset], true)?;
62 return Ok(Self {
63 bytes: &input[..trailer_end + 2],
64 chunks_end: offset,
65 decoded_len,
66 last_chunk_extensions: parsed.extensions,
67 trailers,
68 });
69 }
70 trailer_offset = trailer_end + 2;
71 }
72 }
73
74 decoded_len = decoded_len
75 .checked_add(parsed.size)
76 .ok_or(Http1ParseError::DecodedSizeOverflow)?;
77 let data_end = after_line
78 .checked_add(parsed.size)
79 .ok_or(Http1ParseError::ChunkSizeOverflow)?;
80 let required = data_end
81 .checked_add(2)
82 .ok_or(Http1ParseError::ChunkSizeOverflow)?;
83 if input.len() < required {
84 return Err(Http1ParseError::Incomplete {
85 required,
86 available: input.len(),
87 });
88 }
89 if &input[data_end..required] != b"\r\n" {
90 return Err(Http1ParseError::InvalidChunkTerminator);
91 }
92 offset = required;
93 }
94 }
95
96 pub const fn as_bytes(self) -> &'a [u8] {
98 self.bytes
99 }
100
101 pub const fn decoded_len(self) -> usize {
103 self.decoded_len
104 }
105
106 pub const fn last_chunk_extensions(self) -> &'a [u8] {
108 self.last_chunk_extensions
109 }
110
111 pub const fn trailers(self) -> Http1Fields<'a> {
113 self.trailers
114 }
115
116 pub fn chunks(self) -> Http1Chunks<'a> {
118 Http1Chunks {
119 bytes: &self.bytes[..self.chunks_end],
120 offset: 0,
121 }
122 }
123}
124
125#[derive(Clone, Debug)]
127pub struct Http1Chunks<'a> {
128 bytes: &'a [u8],
129 offset: usize,
130}
131
132impl<'a> Iterator for Http1Chunks<'a> {
133 type Item = Http1Chunk<'a>;
134
135 fn next(&mut self) -> Option<Self::Item> {
136 if self.offset == self.bytes.len() {
137 return None;
138 }
139 let line_end = find_line_end(self.bytes, self.offset).ok()?;
140 let parsed = parse_chunk_line(&self.bytes[self.offset..line_end]).ok()?;
141 let data_start = line_end + 2;
142 let data_end = data_start + parsed.size;
143 self.offset = data_end + 2;
144 Some(Http1Chunk {
145 size: parsed.size,
146 data: &self.bytes[data_start..data_end],
147 extensions: parsed.extensions,
148 })
149 }
150}
151
152struct ParsedChunkLine<'a> {
153 size: usize,
154 extensions: &'a [u8],
155}
156
157fn parse_chunk_line(line: &[u8]) -> Result<ParsedChunkLine<'_>, Http1ParseError> {
158 let mut offset = 0;
159 let mut size = 0usize;
160 while let Some(digit) = line.get(offset).and_then(|byte| hex_value(*byte)) {
161 size = size
162 .checked_mul(16)
163 .and_then(|size| size.checked_add(usize::from(digit)))
164 .ok_or(Http1ParseError::ChunkSizeOverflow)?;
165 offset += 1;
166 }
167 if offset == 0 {
168 return Err(Http1ParseError::InvalidChunkSize);
169 }
170 let extension_start = offset;
171 validate_chunk_extensions(&line[extension_start..])?;
172 Ok(ParsedChunkLine {
173 size,
174 extensions: &line[extension_start..],
175 })
176}
177
178fn validate_chunk_extensions(bytes: &[u8]) -> Result<(), Http1ParseError> {
179 if bytes.is_empty() {
180 return Ok(());
181 }
182 let mut offset = 0;
183 skip_ows(bytes, &mut offset);
184 if offset == bytes.len() {
185 return Err(Http1ParseError::InvalidChunkSize);
186 }
187 while offset < bytes.len() {
188 if bytes[offset] != b';' {
189 return Err(Http1ParseError::InvalidChunkSize);
190 }
191 offset += 1;
192 skip_ows(bytes, &mut offset);
193 parse_token(bytes, &mut offset)?;
194 skip_ows(bytes, &mut offset);
195 if bytes.get(offset) == Some(&b'=') {
196 offset += 1;
197 skip_ows(bytes, &mut offset);
198 parse_token_or_quoted(bytes, &mut offset)?;
199 skip_ows(bytes, &mut offset);
200 }
201 }
202 Ok(())
203}
204
205fn parse_token(bytes: &[u8], offset: &mut usize) -> Result<(), Http1ParseError> {
206 let start = *offset;
207 while bytes.get(*offset).is_some_and(|byte| is_token(*byte)) {
208 *offset += 1;
209 }
210 if *offset == start {
211 return Err(Http1ParseError::InvalidChunkSize);
212 }
213 Ok(())
214}
215
216fn parse_token_or_quoted(bytes: &[u8], offset: &mut usize) -> Result<(), Http1ParseError> {
217 if bytes.get(*offset) != Some(&b'"') {
218 return parse_token(bytes, offset);
219 }
220 *offset += 1;
221 loop {
222 let byte = *bytes
223 .get(*offset)
224 .ok_or(Http1ParseError::InvalidChunkSize)?;
225 match byte {
226 b'"' => {
227 *offset += 1;
228 return Ok(());
229 }
230 b'\\' => {
231 *offset += 1;
232 let escaped = *bytes
233 .get(*offset)
234 .ok_or(Http1ParseError::InvalidChunkSize)?;
235 if !matches!(escaped, b'\t' | b' ' | 0x21..=0x7e | 0x80..=0xff) {
236 return Err(Http1ParseError::InvalidChunkSize);
237 }
238 *offset += 1;
239 }
240 b'\t' | b' ' | 0x21 | 0x23..=0x5b | 0x5d..=0x7e | 0x80..=0xff => *offset += 1,
241 _ => return Err(Http1ParseError::InvalidChunkSize),
242 }
243 }
244}
245
246fn skip_ows(bytes: &[u8], offset: &mut usize) {
247 while matches!(bytes.get(*offset), Some(b' ' | b'\t')) {
248 *offset += 1;
249 }
250}
251
252fn chunk_line_end(input: &[u8], offset: usize) -> Result<usize, Http1ParseError> {
253 match find_line_end(input, offset) {
254 Ok(end) => Ok(end),
255 Err(LineEndError::Incomplete) => Err(Http1ParseError::Incomplete {
256 required: input.len().saturating_add(1),
257 available: input.len(),
258 }),
259 Err(LineEndError::Malformed) => Err(Http1ParseError::InvalidChunkSize),
260 }
261}
262
263fn trailer_line_end(input: &[u8], offset: usize) -> Result<usize, Http1ParseError> {
264 match find_line_end(input, offset) {
265 Ok(end) => Ok(end),
266 Err(LineEndError::Incomplete) => Err(Http1ParseError::Incomplete {
267 required: input.len().saturating_add(1),
268 available: input.len(),
269 }),
270 Err(LineEndError::Malformed) => Err(Http1ParseError::InvalidTrailer),
271 }
272}
273
274const fn hex_value(byte: u8) -> Option<u8> {
275 match byte {
276 b'0'..=b'9' => Some(byte - b'0'),
277 b'a'..=b'f' => Some(byte - b'a' + 10),
278 b'A'..=b'F' => Some(byte - b'A' + 10),
279 _ => None,
280 }
281}