sark 0.2.2

Simple Asynchronous Rust webKit - Server
Documentation
use std::io::{Error, ErrorKind, Read};
use std::net::TcpStream;

#[derive(Debug)]
pub(crate) struct ParsedResponse {
    pub(crate) status: u16,
    #[allow(dead_code)]
    pub(crate) headers: Vec<(String, String)>,
    pub(crate) body: Vec<u8>,
}

impl ParsedResponse {
    #[allow(dead_code)]
    pub(crate) fn header(&self, name: &str) -> Option<&str> {
        self.headers
            .iter()
            .find(|(n, _)| n == name)
            .map(|(_, v)| v.as_str())
    }

    pub(crate) fn body_str(&self) -> &str {
        std::str::from_utf8(&self.body).expect("response body is utf8")
    }
}

pub(crate) fn parse_response_from_buffer(
    buf: &mut Vec<u8>,
) -> std::io::Result<Option<ParsedResponse>> {
    let header_end = match buf.windows(4).position(|w| w == b"\r\n\r\n") {
        Some(pos) => pos + 4,
        None => return Ok(None),
    };

    let head = &buf[..header_end];
    let head_str = std::str::from_utf8(head)
        .map_err(|_| Error::new(ErrorKind::InvalidData, "invalid utf8 in header"))?;

    let mut lines = head_str.split("\r\n");
    let status_line = lines
        .next()
        .ok_or_else(|| Error::new(ErrorKind::InvalidData, "missing status line"))?;
    let status = status_line
        .split_whitespace()
        .nth(1)
        .ok_or_else(|| Error::new(ErrorKind::InvalidData, "missing status code"))?
        .parse::<u16>()
        .map_err(|_| Error::new(ErrorKind::InvalidData, "invalid status code"))?;

    let mut content_length = 0usize;
    let mut headers = Vec::new();
    for line in lines {
        if line.is_empty() {
            break;
        }
        if let Some((name, value)) = line.split_once(':') {
            let name_lc = name.trim().to_ascii_lowercase();
            let value_trimmed = value.trim().to_string();
            if name_lc == "content-length" {
                content_length = value_trimmed.parse().map_err(|_| {
                    Error::new(ErrorKind::InvalidData, "invalid content-length header")
                })?;
            }
            headers.push((name_lc, value_trimmed));
        }
    }

    let total = header_end + content_length;
    if buf.len() < total {
        return Ok(None);
    }

    let body = buf[header_end..total].to_vec();
    buf.drain(..total);

    Ok(Some(ParsedResponse {
        status,
        headers,
        body,
    }))
}

#[allow(dead_code)]
pub(crate) fn parse_status_head_from_buffer(buf: &mut Vec<u8>) -> std::io::Result<Option<u16>> {
    let header_end = match buf.windows(4).position(|w| w == b"\r\n\r\n") {
        Some(pos) => pos + 4,
        None => return Ok(None),
    };

    let head_str = std::str::from_utf8(&buf[..header_end])
        .map_err(|_| Error::new(ErrorKind::InvalidData, "invalid utf8 in header"))?;
    let status_line = head_str
        .split("\r\n")
        .next()
        .ok_or_else(|| Error::new(ErrorKind::InvalidData, "missing status line"))?;
    let status = status_line
        .split_whitespace()
        .nth(1)
        .ok_or_else(|| Error::new(ErrorKind::InvalidData, "missing status code"))?
        .parse::<u16>()
        .map_err(|_| Error::new(ErrorKind::InvalidData, "invalid status code"))?;

    buf.drain(..header_end);
    Ok(Some(status))
}

pub(crate) fn read_response_std_stream(
    stream: &mut TcpStream,
    buf: &mut Vec<u8>,
) -> std::io::Result<ParsedResponse> {
    loop {
        if let Some(parsed) = parse_response_from_buffer(buf)? {
            return Ok(parsed);
        }
        let mut tmp = [0u8; 4096];
        let n = stream.read(&mut tmp).map_err(|err| {
            let sample_len = buf.len().min(256);
            let sample = String::from_utf8_lossy(&buf[..sample_len]);
            Error::new(
                err.kind(),
                format!(
                    "{} (partial_len={}, partial_sample={sample:?})",
                    err,
                    buf.len(),
                ),
            )
        })?;
        if n == 0 {
            let sample_len = buf.len().min(256);
            let sample = String::from_utf8_lossy(&buf[..sample_len]);
            return Err(Error::new(
                ErrorKind::UnexpectedEof,
                format!("eof (partial_len={}, partial_sample={sample:?})", buf.len()),
            ));
        }
        buf.extend_from_slice(&tmp[..n]);
    }
}

#[allow(dead_code)]
pub(crate) fn read_status_head_std_stream(
    stream: &mut TcpStream,
    buf: &mut Vec<u8>,
) -> std::io::Result<u16> {
    loop {
        if let Some(status) = parse_status_head_from_buffer(buf)? {
            return Ok(status);
        }
        let mut tmp = [0u8; 4096];
        let n = stream.read(&mut tmp)?;
        if n == 0 {
            return Err(Error::new(ErrorKind::UnexpectedEof, "eof"));
        }
        buf.extend_from_slice(&tmp[..n]);
    }
}

#[allow(dead_code)]
pub(crate) async fn read_response_dope_stream(
    stream: &mut dope::net::TcpStream,
    buf: &mut Vec<u8>,
) -> std::io::Result<ParsedResponse> {
    loop {
        if let Some(parsed) = parse_response_from_buffer(buf)? {
            return Ok(parsed);
        }

        let (res, chunk) = stream.read(vec![0u8; 4096]).await;
        let n = res?;
        if n == 0 {
            return Err(Error::new(ErrorKind::UnexpectedEof, "eof"));
        }
        buf.extend_from_slice(&chunk[..n]);
    }
}