tobaru 0.9.3

Port forwarding CLI utility written in Rust with allowlists, TLS SNI and ALPN routing, HTTP forwarding, load balancing, IP-to-destination mapping, and iptables support.
use std::collections::HashMap;

use tokio::io::AsyncRead;

use super::line_reader::LineReader;

pub struct ParsedHttpData {
    first_line: String,
    headers: HashMap<String, String>,
    line_reader: LineReader,
}

impl ParsedHttpData {
    pub async fn parse<T>(stream: &mut T) -> std::io::Result<Self>
    where
        T: AsyncRead + Unpin,
    {
        let mut line_reader = LineReader::new();
        let mut first_line: Option<String> = None;
        let mut headers: HashMap<String, String> = HashMap::new();

        let mut line_count = 0;
        loop {
            let line = line_reader.read_line(stream).await?;
            if line.is_empty() {
                break;
            }

            if line.len() >= 4096 {
                return Err(std::io::Error::new(
                    std::io::ErrorKind::Other,
                    "http request line is too long",
                ));
            }

            if first_line.is_none() {
                first_line = Some(line.to_string());
            } else {
                let tokens: Vec<&str> = line.splitn(2, ':').collect();
                if tokens.len() != 2 {
                    return Err(std::io::Error::new(
                        std::io::ErrorKind::Other,
                        format!("invalid http request line: {}", line),
                    ));
                }
                let header_key = tokens[0].trim().to_lowercase();
                let header_value = tokens[1].trim().to_string();
                headers.insert(header_key, header_value);
            }

            line_count += 1;
            if line_count >= 40 {
                return Err(std::io::Error::new(
                    std::io::ErrorKind::Other,
                    "http request is too long",
                ));
            }
        }

        let first_line = first_line
            .ok_or_else(|| std::io::Error::new(std::io::ErrorKind::Other, "empty http request"))?;

        Ok(Self {
            first_line,
            headers,
            line_reader,
        })
    }

    pub fn first_line(&self) -> &str {
        self.first_line.as_str()
    }

    pub fn set_first_line(&mut self, first_line: String) {
        self.first_line = first_line;
    }

    pub fn headers(&self) -> &HashMap<String, String> {
        &self.headers
    }

    pub fn headers_mut(&mut self) -> &mut HashMap<String, String> {
        &mut self.headers
    }

    pub fn into_reader(self) -> LineReader {
        self.line_reader
    }
}