use super::error::Http1ParseError;
use super::fields::{Http1Fields, LineEndError, find_line_end, is_token};
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct Http1Chunk<'a> {
size: usize,
data: &'a [u8],
extensions: &'a [u8],
}
impl<'a> Http1Chunk<'a> {
pub const fn size(self) -> usize {
self.size
}
pub const fn data(self) -> &'a [u8] {
self.data
}
pub const fn extensions(self) -> &'a [u8] {
self.extensions
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct Http1ChunkedBody<'a> {
bytes: &'a [u8],
chunks_end: usize,
decoded_len: usize,
last_chunk_extensions: &'a [u8],
trailers: Http1Fields<'a>,
}
impl<'a> Http1ChunkedBody<'a> {
pub fn parse(input: &'a [u8]) -> Result<Self, Http1ParseError> {
let mut offset = 0;
let mut decoded_len = 0usize;
loop {
let line_end = chunk_line_end(input, offset)?;
let line = &input[offset..line_end];
let parsed = parse_chunk_line(line)?;
let after_line = line_end + 2;
if parsed.size == 0 {
let trailer_start = after_line;
let mut trailer_offset = trailer_start;
loop {
let trailer_end = trailer_line_end(input, trailer_offset)?;
if trailer_end == trailer_offset {
let trailers =
Http1Fields::parse(&input[trailer_start..trailer_offset], true)?;
return Ok(Self {
bytes: &input[..trailer_end + 2],
chunks_end: offset,
decoded_len,
last_chunk_extensions: parsed.extensions,
trailers,
});
}
trailer_offset = trailer_end + 2;
}
}
decoded_len = decoded_len
.checked_add(parsed.size)
.ok_or(Http1ParseError::DecodedSizeOverflow)?;
let data_end = after_line
.checked_add(parsed.size)
.ok_or(Http1ParseError::ChunkSizeOverflow)?;
let required = data_end
.checked_add(2)
.ok_or(Http1ParseError::ChunkSizeOverflow)?;
if input.len() < required {
return Err(Http1ParseError::Incomplete {
required,
available: input.len(),
});
}
if &input[data_end..required] != b"\r\n" {
return Err(Http1ParseError::InvalidChunkTerminator);
}
offset = required;
}
}
pub const fn as_bytes(self) -> &'a [u8] {
self.bytes
}
pub const fn decoded_len(self) -> usize {
self.decoded_len
}
pub const fn last_chunk_extensions(self) -> &'a [u8] {
self.last_chunk_extensions
}
pub const fn trailers(self) -> Http1Fields<'a> {
self.trailers
}
pub fn chunks(self) -> Http1Chunks<'a> {
Http1Chunks {
bytes: &self.bytes[..self.chunks_end],
offset: 0,
}
}
}
#[derive(Clone, Debug)]
pub struct Http1Chunks<'a> {
bytes: &'a [u8],
offset: usize,
}
impl<'a> Iterator for Http1Chunks<'a> {
type Item = Http1Chunk<'a>;
fn next(&mut self) -> Option<Self::Item> {
if self.offset == self.bytes.len() {
return None;
}
let line_end = find_line_end(self.bytes, self.offset).ok()?;
let parsed = parse_chunk_line(&self.bytes[self.offset..line_end]).ok()?;
let data_start = line_end + 2;
let data_end = data_start + parsed.size;
self.offset = data_end + 2;
Some(Http1Chunk {
size: parsed.size,
data: &self.bytes[data_start..data_end],
extensions: parsed.extensions,
})
}
}
struct ParsedChunkLine<'a> {
size: usize,
extensions: &'a [u8],
}
fn parse_chunk_line(line: &[u8]) -> Result<ParsedChunkLine<'_>, Http1ParseError> {
let mut offset = 0;
let mut size = 0usize;
while let Some(digit) = line.get(offset).and_then(|byte| hex_value(*byte)) {
size = size
.checked_mul(16)
.and_then(|size| size.checked_add(usize::from(digit)))
.ok_or(Http1ParseError::ChunkSizeOverflow)?;
offset += 1;
}
if offset == 0 {
return Err(Http1ParseError::InvalidChunkSize);
}
let extension_start = offset;
validate_chunk_extensions(&line[extension_start..])?;
Ok(ParsedChunkLine {
size,
extensions: &line[extension_start..],
})
}
fn validate_chunk_extensions(bytes: &[u8]) -> Result<(), Http1ParseError> {
if bytes.is_empty() {
return Ok(());
}
let mut offset = 0;
skip_ows(bytes, &mut offset);
if offset == bytes.len() {
return Err(Http1ParseError::InvalidChunkSize);
}
while offset < bytes.len() {
if bytes[offset] != b';' {
return Err(Http1ParseError::InvalidChunkSize);
}
offset += 1;
skip_ows(bytes, &mut offset);
parse_token(bytes, &mut offset)?;
skip_ows(bytes, &mut offset);
if bytes.get(offset) == Some(&b'=') {
offset += 1;
skip_ows(bytes, &mut offset);
parse_token_or_quoted(bytes, &mut offset)?;
skip_ows(bytes, &mut offset);
}
}
Ok(())
}
fn parse_token(bytes: &[u8], offset: &mut usize) -> Result<(), Http1ParseError> {
let start = *offset;
while bytes.get(*offset).is_some_and(|byte| is_token(*byte)) {
*offset += 1;
}
if *offset == start {
return Err(Http1ParseError::InvalidChunkSize);
}
Ok(())
}
fn parse_token_or_quoted(bytes: &[u8], offset: &mut usize) -> Result<(), Http1ParseError> {
if bytes.get(*offset) != Some(&b'"') {
return parse_token(bytes, offset);
}
*offset += 1;
loop {
let byte = *bytes
.get(*offset)
.ok_or(Http1ParseError::InvalidChunkSize)?;
match byte {
b'"' => {
*offset += 1;
return Ok(());
}
b'\\' => {
*offset += 1;
let escaped = *bytes
.get(*offset)
.ok_or(Http1ParseError::InvalidChunkSize)?;
if !matches!(escaped, b'\t' | b' ' | 0x21..=0x7e | 0x80..=0xff) {
return Err(Http1ParseError::InvalidChunkSize);
}
*offset += 1;
}
b'\t' | b' ' | 0x21 | 0x23..=0x5b | 0x5d..=0x7e | 0x80..=0xff => *offset += 1,
_ => return Err(Http1ParseError::InvalidChunkSize),
}
}
}
fn skip_ows(bytes: &[u8], offset: &mut usize) {
while matches!(bytes.get(*offset), Some(b' ' | b'\t')) {
*offset += 1;
}
}
fn chunk_line_end(input: &[u8], offset: usize) -> Result<usize, Http1ParseError> {
match find_line_end(input, offset) {
Ok(end) => Ok(end),
Err(LineEndError::Incomplete) => Err(Http1ParseError::Incomplete {
required: input.len().saturating_add(1),
available: input.len(),
}),
Err(LineEndError::Malformed) => Err(Http1ParseError::InvalidChunkSize),
}
}
fn trailer_line_end(input: &[u8], offset: usize) -> Result<usize, Http1ParseError> {
match find_line_end(input, offset) {
Ok(end) => Ok(end),
Err(LineEndError::Incomplete) => Err(Http1ParseError::Incomplete {
required: input.len().saturating_add(1),
available: input.len(),
}),
Err(LineEndError::Malformed) => Err(Http1ParseError::InvalidTrailer),
}
}
const fn hex_value(byte: u8) -> Option<u8> {
match byte {
b'0'..=b'9' => Some(byte - b'0'),
b'a'..=b'f' => Some(byte - b'a' + 10),
b'A'..=b'F' => Some(byte - b'A' + 10),
_ => None,
}
}