pulse_http 0.2.0

Async HTTP/1.1 framework on Tokio — raw TCP, routing, middleware, no Hyper
Documentation
use std::str::from_utf8;

use crate::helpers::{find_subslice::find_subslice, read_with_timeout::read_with_timeout};
use tokio::net::TcpStream;

pub async fn read_chunked_body(
    stream: &mut TcpStream,
    buffer: &mut Vec<u8>,
    body_start: usize,
    max_body_size: usize,
    read_timeout_sec: u64,
) -> Result<(Vec<u8>, Vec<u8>), ()> {
    let mut cursor = body_start;
    let mut body = Vec::new();
    let mut read_buf = [0u8; 1024];

    loop {
        let size_line_end =
            ensure_crlf(stream, buffer, cursor, read_timeout_sec, &mut read_buf).await?;
        let size_line = from_utf8(&buffer[cursor..size_line_end]).map_err(|_| ())?;
        let size = parse_chunk_size(size_line)?;
        cursor = size_line_end + 2;

        if size == 0 {
            cursor = skip_trailers(stream, buffer, cursor, read_timeout_sec, &mut read_buf).await?;
            return Ok((body, buffer[cursor..].to_vec()));
        }

        let need = size.checked_add(2).ok_or(())?;
        ensure_len(
            stream,
            buffer,
            cursor + need,
            read_timeout_sec,
            &mut read_buf,
        )
        .await?;

        if body.len().saturating_add(size) > max_body_size {
            return Err(());
        }

        body.extend_from_slice(&buffer[cursor..cursor + size]);
        if &buffer[cursor + size..cursor + size + 2] != b"\r\n" {
            return Err(());
        }
        cursor += need;
    }
}

pub fn is_chunked_transfer_encoding(value: &str) -> bool {
    value.trim().eq_ignore_ascii_case("chunked")
}

fn parse_chunk_size(line: &str) -> Result<usize, ()> {
    let hex = line.split(';').next().unwrap_or(line).trim();
    if hex.is_empty() {
        return Err(());
    }
    usize::from_str_radix(hex, 16).map_err(|_| ())
}

async fn ensure_crlf(
    stream: &mut TcpStream,
    buffer: &mut Vec<u8>,
    cursor: usize,
    read_timeout_sec: u64,
    read_buf: &mut [u8],
) -> Result<usize, ()> {
    loop {
        if let Some(rel) = find_subslice(&buffer[cursor..], b"\r\n") {
            return Ok(cursor + rel);
        }
        read_more(stream, buffer, read_timeout_sec, read_buf).await?;
    }
}

async fn ensure_len(
    stream: &mut TcpStream,
    buffer: &mut Vec<u8>,
    needed: usize,
    read_timeout_sec: u64,
    read_buf: &mut [u8],
) -> Result<(), ()> {
    while buffer.len() < needed {
        read_more(stream, buffer, read_timeout_sec, read_buf).await?;
    }
    Ok(())
}

async fn skip_trailers(
    stream: &mut TcpStream,
    buffer: &mut Vec<u8>,
    cursor: usize,
    read_timeout_sec: u64,
    read_buf: &mut [u8],
) -> Result<usize, ()> {
    loop {
        if buffer.len() >= cursor + 2 && &buffer[cursor..cursor + 2] == b"\r\n" {
            return Ok(cursor + 2);
        }
        if let Some(rel) = find_subslice(&buffer[cursor..], b"\r\n\r\n") {
            return Ok(cursor + rel + 4);
        }
        read_more(stream, buffer, read_timeout_sec, read_buf).await?;
    }
}

async fn read_more(
    stream: &mut TcpStream,
    buffer: &mut Vec<u8>,
    read_timeout_sec: u64,
    read_buf: &mut [u8],
) -> Result<(), ()> {
    match read_with_timeout(stream, read_buf, read_timeout_sec).await {
        Ok(0) => Err(()),
        Ok(n) => {
            buffer.extend_from_slice(&read_buf[..n]);
            Ok(())
        }
        Err(()) => Err(()),
    }
}