tobaru 0.9.6

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, initial_data: Option<Vec<u8>>) -> std::io::Result<Self>
    where
        T: AsyncRead + Unpin,
    {
        let mut line_reader = if let Some(data) = initial_data {
            LineReader::new_with_data(data)
        } else {
            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::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::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::other("http request is too long"));
            }
        }

        let first_line = first_line.ok_or_else(|| std::io::Error::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
    }
}