haprox-rs 0.3.2

A HaProxy v1/v2 protocol parser.
Documentation
use std::io::{self, Cursor, Error, ErrorKind};

use crate::{error::{HaProxErr, HaProxRes}, map_error, protocol_raw, return_error};

#[allow(unused)]
pub(crate)
fn is_printable_utf8(u: &str, err_lbl: &str) -> HaProxRes<()>
{
    for p in u.chars()
    {
        // any character that can be printed except control chars
        if p.is_control() == true || p.is_ascii_whitespace() == true || p.is_ascii_control() == true 
        {
            return_error!(ArgumentEinval,
                "non-printable characters found in: '{}' for '{}'", sanitize_str_unicode(u), err_lbl);
        }
    }

    return Ok(());
}

/// Chekcs if the argument `u` contains only printable, no whitespace characters to comply with US-ASCII.
#[inline]
pub(crate)
fn is_printable_ascii_nowp(u: &str, err_lbl: &str) -> HaProxRes<()>
{
    return is_printable_ascii(u, err_lbl, false);
}

/// Chekcs if the argument `u` contains only printable, characters to comply with US-ASCII.
pub(crate)
fn is_printable_ascii(u: &str, err_lbl: &str, whitesp: bool) -> HaProxRes<()>
{
    for p in u.chars()
    {
        // any character that can be printed except control chars
        if p.is_control() == true || 
            (p.is_ascii_whitespace() == true && whitesp == false) || 
            p.is_ascii_control() == true ||
            p.is_ascii() == false
        {
            return_error!(ArgumentEinval,
                "non-printable characters found in: '{}' for '{}'", sanitize_str_unicode(u), err_lbl);
        }
    }

    return Ok(());
}

/// Chekcs if the argument `u` contains only printable, characters to comply with US-ASCII.
pub(crate)
fn check_printable_ascii_single_wp<'a>(u: &'a str, err_lbl: &str) -> HaProxRes<Option<&'a str>>
{
    let mut wpi = 0;

    if u.ends_with(protocol_raw::HEADER_V1_EOM) == false
    {
        return Ok(None);
    }

    let uw_end = &u[0..u.len()-protocol_raw::HEADER_V1_EOM.len()];

    for p in uw_end.chars()
    {
        // any character that can be printed except control chars
        if p.is_control() == true || 
            p.is_ascii() == false
        {
            return_error!(MalformedData,
                "non-printable characters or non-ascii found in: '{}' for '{}'", sanitize_str_unicode(u), err_lbl);
        }
        else if p.is_ascii_whitespace() == true 
        {
            if wpi > 0
            {
                return_error!(MalformedData,
                    "multiple spaces was found in '{}', for '{}'", sanitize_str_unicode(u), err_lbl);
            }

            wpi += 1;
        }
        else if p.is_ascii_control() == true 
        {
            return_error!(MalformedData,
                "control chars in sequence in '{}', for '{}'", sanitize_str_unicode(u), err_lbl);            
        }
        else
        {
            wpi = 0;
        }
    }

    return Ok(Some(uw_end));
}

/// Clears all control characlters leaving onlu pritable chars.
pub(crate)
fn sanitize_str_unicode(st: &str) -> String
{
    let mut out = String::with_capacity(st.len());

    for c in st.chars()
    {
        if c.is_alphanumeric() == true ||
            c.is_ascii_punctuation() == true ||
            c == ' '
        {
            out.push(c);
        }
        else
        {
            let mut buf = [0_u8; 4];
            c.encode_utf8(&mut buf);

            let formatted: String = 
                buf[0..c.len_utf8()].into_iter()
                    .map(|c| format!("\\x{:02x}", c))
                    .collect();

            out.push_str(&formatted);
        }
    }

    return out;
}

pub
fn map_io_err(err: Error) -> HaProxErr
{
    return map_error!(IoError, "{}", err)
}

/// An external realization of borrowing using [Cursor].
pub trait ReadExtZeroCopy<'zc>
{
    fn borrow_exact(&mut self, len: usize) -> std::io::Result<&'zc [u8]>;
}

impl<'zc> ReadExtZeroCopy<'zc> for Cursor<&'zc [u8]>
{
    fn borrow_exact(&mut self, len: usize) -> std::io::Result<&'zc [u8]> 
    {
        let start = self.position() as usize;

        if self.get_ref().len() < start+len
        {
            return Err(
                io::Error::new(ErrorKind::UnexpectedEof, 
                    format!("len: {}, req: {}", self.get_ref().len(), start+len))
            );
        }
        let out = &self.get_ref()[start..(start+len)];

        self.set_position((start+len) as u64);

        return Ok(out);
    }
}

#[test]
fn test_mult_wp()
{
    assert_eq!(
        check_printable_ascii_single_wp("PROXY UNKNOWN ffff:f...f:ffff ffff:f...f:ffff 65535  65535\r\n", "test").is_err(),
        true
    ); 

    assert_eq!(
        check_printable_ascii_single_wp("PROXY UNKNOWN ffff:f...f:ffff ffff:f...f:ffff 65535 65535\r\n", "test"),
        Ok(Some("PROXY UNKNOWN ffff:f...f:ffff ffff:f...f:ffff 65535 65535"))
    ); 

    assert_eq!(
        check_printable_ascii_single_wp("PROXY UNKNOWN ffff:f...f:ffff ffff:f...f:ffff 65535 65535", "test"),
        Ok(None)
    );

    assert_eq!(
        check_printable_ascii_single_wp("PROXY UNKNOWN ffff:f...f:ffff ffff:f...f:ffff 65535 65535\r", "test"),
        Ok(None)
    ); 

  
    assert_eq!(
        check_printable_ascii_single_wp("PROXY UNKNOWN ffff:f...f:ffff ffff:f...f:ffff 65535 65535\r\n\r\n", "test").is_err(),
        true
    ); 
}