sark-core 0.11.0

the L7 layer for dope
Documentation
use super::error::ERR_INVALID_HEADER_NAME;

#[derive(Clone, Copy)]
pub struct HeaderLine<'a> {
    bytes: &'a [u8],
}

impl<'a> HeaderLine<'a> {
    pub fn new(bytes: &'a [u8]) -> Self {
        Self { bytes }
    }

    pub fn is_whitespace(byte: u8) -> bool {
        byte == b' ' || byte == b'\t'
    }

    pub fn trimmed_range(&self, start: usize, end: usize) -> Option<(usize, usize)> {
        if start > end || end > self.bytes.len() {
            return None;
        }
        let mut value_start = start;
        let mut value_end = end;
        while value_start < value_end && Self::is_whitespace(self.bytes[value_start]) {
            value_start += 1;
        }
        while value_end > value_start && Self::is_whitespace(self.bytes[value_end - 1]) {
            value_end -= 1;
        }
        Some((value_start, value_end))
    }

    pub fn find_crlf_from(&self, start: usize) -> Option<usize> {
        if self.bytes.len() < 2 || start >= self.bytes.len().saturating_sub(1) {
            return None;
        }
        let mut seek = start;
        while let Some(relative) = memchr::memchr(b'\r', &self.bytes[seek..]) {
            let cr_idx = seek + relative;
            if cr_idx + 1 >= self.bytes.len() {
                return None;
            }
            if self.bytes[cr_idx + 1] == b'\n' {
                return Some(cr_idx);
            }
            seek = cr_idx + 1;
        }
        None
    }

    pub fn find_name_end_valid(
        &self,
        start: usize,
    ) -> Result<Option<(usize, u8)>, crate::error::Error> {
        match crate::http::scan::scan_header_name(self.bytes, start) {
            crate::http::scan::HeaderNameOutcome::Found { pos, byte } => Ok(Some((pos, byte))),
            crate::http::scan::HeaderNameOutcome::Invalid => Err(crate::error::Error::BadRequest(
                ERR_INVALID_HEADER_NAME.into(),
            )),
            crate::http::scan::HeaderNameOutcome::None => Ok(None),
        }
    }

    fn name_value(self) -> Option<(&'a [u8], &'a [u8])> {
        let colon = self.bytes.iter().position(|byte| *byte == b':')?;
        let (name_start, name_end) = self.trimmed_range(0, colon)?;
        let (value_start, value_end) = self.trimmed_range(colon + 1, self.bytes.len())?;
        Some((
            &self.bytes[name_start..name_end],
            &self.bytes[value_start..value_end],
        ))
    }
}

pub struct HeaderLines<'a> {
    remaining: &'a [u8],
    finished: bool,
}

impl<'a> HeaderLines<'a> {
    pub fn new(wire: &'a [u8]) -> Self {
        Self {
            remaining: wire,
            finished: false,
        }
    }
}

impl<'a> Iterator for HeaderLines<'a> {
    type Item = (&'a [u8], &'a [u8]);

    fn next(&mut self) -> Option<Self::Item> {
        while !self.finished && !self.remaining.is_empty() {
            let (line, remaining) = match memchr::memchr(b'\n', self.remaining) {
                Some(newline) => (&self.remaining[..newline], &self.remaining[newline + 1..]),
                None => (self.remaining, &[][..]),
            };
            self.remaining = remaining;
            let line = match line.strip_suffix(b"\r") {
                Some(stripped) => stripped,
                None => line,
            };
            if line.is_empty() {
                self.finished = true;
                return None;
            }
            if let Some(parts) = HeaderLine::new(line).name_value() {
                return Some(parts);
            }
        }
        None
    }
}

pub struct HeaderLineScan {
    pub end: usize,
    pub colon: Option<usize>,
    pub value_start: usize,
    pub value_end: usize,
}

pub trait HeadInput {
    fn len(&self) -> usize;
    fn is_empty(&self) -> bool {
        self.len() == 0
    }
    fn slice_range(&self, range: std::ops::Range<usize>) -> Option<&[u8]>;
    fn copy_range_frame(
        &self,
        range: std::ops::Range<usize>,
    ) -> Option<o3::buffer::Bytes<o3::buffer::Retained>>;
    fn copy_range_into(&self, range: std::ops::Range<usize>, out: &mut [u8]);
    fn for_each_slice<F>(&self, range: std::ops::Range<usize>, f: F)
    where
        F: FnMut(&[u8]);
}

impl HeadInput for [u8] {
    fn len(&self) -> usize {
        <[u8]>::len(self)
    }

    fn slice_range(&self, range: std::ops::Range<usize>) -> Option<&[u8]> {
        self.get(range)
    }

    fn copy_range_into(&self, range: std::ops::Range<usize>, out: &mut [u8]) {
        assert!(
            range.start <= range.end && range.end <= self.len(),
            "head input copy range invariant: range out of bounds",
        );
        let need = range.end - range.start;
        assert!(
            out.len() >= need,
            "head input copy range invariant: output too small",
        );
        out[..need].copy_from_slice(&self[range]);
    }

    fn copy_range_frame(
        &self,
        range: std::ops::Range<usize>,
    ) -> Option<o3::buffer::Bytes<o3::buffer::Retained>> {
        self.get(range)
            .map(o3::buffer::Shared::copy_from_slice)
            .map(o3::buffer::Bytes::<o3::buffer::Retained>::from)
    }

    fn for_each_slice<F>(&self, range: std::ops::Range<usize>, mut f: F)
    where
        F: FnMut(&[u8]),
    {
        if let Some(slice) = self.get(range) {
            f(slice);
        }
    }
}

impl HeaderLineScan {
    pub fn find(bytes: &[u8], start: usize) -> Option<Self> {
        if start >= bytes.len() {
            return None;
        }

        let mut search = start;
        let (colon_pos, value_segment_start) = loop {
            let rel = memchr::memchr2(b':', b'\r', &bytes[search..])?;
            let pos = search + rel;
            if bytes[pos] == b':' {
                break (pos, pos + 1);
            }
            if pos + 1 >= bytes.len() {
                return None;
            }
            if bytes[pos + 1] == b'\n' {
                return Some(HeaderLineScan {
                    end: pos,
                    colon: None,
                    value_start: 0,
                    value_end: 0,
                });
            }
            search = pos + 1;
        };

        let mut search = value_segment_start;
        let cr = loop {
            let rel = memchr::memchr(b'\r', &bytes[search..])?;
            let pos = search + rel;
            if pos + 1 >= bytes.len() {
                return None;
            }
            if bytes[pos + 1] == b'\n' {
                break pos;
            }
            search = pos + 1;
        };

        let segment = &bytes[value_segment_start..cr];
        let leading = segment
            .iter()
            .take_while(|&&b| HeaderLine::is_whitespace(b))
            .count();
        let trailing = segment[leading..]
            .iter()
            .rev()
            .take_while(|&&b| HeaderLine::is_whitespace(b))
            .count();
        Some(HeaderLineScan {
            end: cr,
            colon: Some(colon_pos),
            value_start: value_segment_start + leading,
            value_end: cr - trailing,
        })
    }
}