camber 0.2.1

Opinionated async Rust for IO-bound services on top of Tokio
Documentation
use std::io::{self, BufRead, BufReader, Read};
use std::net::TcpStream;

const MAX_CHUNK_LINE_BYTES: usize = 1024;
const MAX_TRAILER_BYTES: usize = 16 * 1024;

pub(crate) fn read_response_head(
    reader: &mut BufReader<TcpStream>,
) -> (u16, Vec<(String, String)>) {
    let mut status_line = String::new();
    reader.read_line(&mut status_line).unwrap();
    let status = status_line
        .split_whitespace()
        .nth(1)
        .and_then(|value| value.parse::<u16>().ok())
        .expect("valid HTTP status");
    let mut headers = Vec::new();
    loop {
        let mut line = String::new();
        reader.read_line(&mut line).unwrap();
        if line == "\r\n" {
            return (status, headers);
        }
        let (name, value) = line.trim_end().split_once(':').expect("valid HTTP header");
        headers.push((name.to_owned(), value.trim().to_owned()));
    }
}

pub(crate) fn read_chunk(
    reader: &mut BufReader<TcpStream>,
    payload_limit: usize,
) -> io::Result<Option<Box<[u8]>>> {
    let size_line = read_crlf_line(reader, MAX_CHUNK_LINE_BYTES)?;
    let size_text = std::str::from_utf8(&size_line[..size_line.len() - 2])
        .map_err(|error| invalid_data(format!("chunk size was not UTF-8: {error}")))?;
    let size_token = size_text
        .split(';')
        .next()
        .ok_or_else(|| invalid_data("chunk size was missing"))?;
    let size = usize::from_str_radix(size_token, 16)
        .map_err(|error| invalid_data(format!("invalid chunk size: {error}")))?;
    if size > payload_limit {
        return Err(invalid_data(format!(
            "chunk payload exceeded {payload_limit}-byte limit"
        )));
    }
    if size == 0 {
        read_trailers(reader)?;
        return Ok(None);
    }

    let mut payload = vec![0_u8; size];
    reader.read_exact(&mut payload)?;
    let mut terminator = [0_u8; 2];
    reader.read_exact(&mut terminator)?;
    if terminator != *b"\r\n" {
        return Err(invalid_data("chunk payload did not end with CRLF"));
    }
    Ok(Some(payload.into_boxed_slice()))
}

pub(crate) fn read_to_eof_bounded(
    reader: &mut BufReader<TcpStream>,
    limit: usize,
) -> io::Result<Box<[u8]>> {
    let mut bytes = Vec::new();
    let mut chunk = [0_u8; 4096];
    loop {
        match reader.read(&mut chunk)? {
            0 => return Ok(bytes.into_boxed_slice()),
            count
                if bytes
                    .len()
                    .checked_add(count)
                    .is_some_and(|length| length <= limit) =>
            {
                bytes.extend_from_slice(&chunk[..count]);
            }
            _ => return Err(invalid_data(format!("body exceeded {limit}-byte limit"))),
        }
    }
}

fn read_trailers(reader: &mut BufReader<TcpStream>) -> io::Result<()> {
    let mut bytes_read = 0_usize;
    loop {
        let remaining = MAX_TRAILER_BYTES
            .checked_sub(bytes_read)
            .ok_or_else(|| invalid_data("chunk trailers exceeded size limit"))?;
        let line = read_crlf_line(reader, remaining)?;
        bytes_read = bytes_read
            .checked_add(line.len())
            .ok_or_else(|| invalid_data("chunk trailer length overflowed"))?;
        if line == b"\r\n" {
            return Ok(());
        }
    }
}

fn read_crlf_line(reader: &mut BufReader<TcpStream>, limit: usize) -> io::Result<Vec<u8>> {
    let mut line = Vec::new();
    loop {
        if line.len() == limit {
            return Err(invalid_data("chunk framing line exceeded size limit"));
        }
        let mut byte = [0_u8; 1];
        reader.read_exact(&mut byte)?;
        line.push(byte[0]);
        if line.ends_with(b"\r\n") {
            return Ok(line);
        }
    }
}

fn invalid_data(message: impl Into<String>) -> io::Error {
    io::Error::new(io::ErrorKind::InvalidData, message.into())
}