zip-core 0.0.4

zip implementation independent structs and helpers
Documentation
//! Parsing functionality from bytes for raw types.
//! Note: this is only enabled with the `parse` feature
extern crate alloc;

mod archive_extra_data_record;
mod central_directory_header;
mod data_descriptor;
mod digital_signature;
mod end_of_central_directory;
mod extensible_data;
mod local_file_header;
mod zip64_end_of_central_directory;

#[cfg(feature = "std")] use std::error::Error;

#[derive(Debug, PartialEq)]
pub enum FixedSizeError {
    /// the buffer has not enough bytes to parse this record.
    /// An exact size is known at should be provided.
    /// It's okay to provide a larger buffer.
    UnsufficientExactBytes(usize),
}

#[derive(Debug, PartialEq)]
pub enum DynamicSizeError {
    /// needed to get the size necessary before parsing. as we cannot undo the
    /// advancing on a Buf. with `std` its possible to get around this
    /// limitation by using bytes::chunks_vectored, however in no_std case we
    /// need this error type.
    /// parameters returns how many bytes AT LEAST need to be contiguous.
    NotContiguous(usize),
    /// see [`FixedSizeError::UnsufficientExactBytes`]
    ///
    /// [`FixedSizeError::UnsufficientExactBytes`]
    /// FixedSizeError::UnsufficientExactBytes
    UnsufficientExactBytes(usize),
    /// This means that the buffer has not enough bytes to read the length of
    /// the dynamic part (e.g. there are not enough bytes to parse the fixed
    /// part). Please catch this, and increase the buffer.
    /// The buffer needs at least this size, hoewever it will prob fail with
    /// a [`Self::UnsufficientExactBytes`] if you just provide `n` bytes and not
    /// enough for the dynmapic part.
    ///
    /// [`Self::UnsufficientExactBytes`] Self::UnsufficientExactBytes
    UnsufficientAtLeastBytes(usize),
}

/// for some records, the zip convention choose to allow specifying the total
/// length in its fixed field, e.g. [`Zip64EndOfCentralDirectory`]. Those fields
/// could specify a value < its fixed size, e.g. 0 which would be totally
/// invalid and unexpected, as it would mean that we shouldn't even concider its
/// signature at this point.
///
/// [`Zip64EndOfCentralDirectory`]: super::Zip64EndOfCentralDirectory
#[derive(Debug, PartialEq)]
pub enum DynamicSizeTotalSizeError {
    Dynamic(DynamicSizeError),
    TotalSizeInFieldIsInvalid,
}

pub(super) fn preview_u16_from_buf(buf: &[u8], pos: usize) -> Option<u16> {
    buf.get(pos..pos + 2)
        .map(|src| unsafe { u16::from_le_bytes(*(src as *const _ as *const [_; 2])) })
}

// checks the length and returns and error in case it is not enough
pub(super) fn validate_length<E, T: Fn(usize) -> E>(have: usize, want: usize, e: T) -> Result<(), E> {
    if have < want {
        return Err(e(want - have));
    }
    Ok(())
}

// checks the length and returns and error in case it is not enough
pub(super) fn validate_length_fixed<E, T: Fn(usize) -> E, T2>(
    have: usize,
    want: usize,
    fixed: T2,
    e: T,
) -> Result<T2, (E, T2)> {
    if have < want {
        return Err((e(want - have), fixed));
    }
    Ok(fixed)
}

pub trait Parse {
    type Error;

    /// parsing this Type from a buf, returning either the Type or an Error.
    /// implementations must not modify the buf in case of an error.
    fn from_buf<T: bytes::Buf>(buf: &mut T) -> Result<Self, Self::Error>
    where
        Self: Sized;

    /// writing self to a buffer.
    /// implementation must not modify the buf in case of an error.
    //TODO: evaluate using a different error type, because its unusual that
    // UnsufficientAtLeastBytes is thrown here.
    fn to_buf<T: bytes::BufMut>(&self, buf: &mut T) -> Result<(), Self::Error>;
}

pub trait ParseExtend {
    type Fixed;
    type Error;

    /// parsing this Type from a buf and a fixed part, returning either the Type
    /// or an Error. implementations must not modify the buf in case of an
    /// error and return the fixed
    fn from_buf_fixed<T: bytes::Buf>(buf: &mut T, fixed: Self::Fixed) -> Result<Self, (Self::Error, Self::Fixed)>
    where
        Self: Sized;
}

impl FixedSizeError {
    pub(super) fn in_dynamic(self) -> DynamicSizeError {
        match self {
            // Nature of this Error type is that there is some additional bytes, so we can only make that AtLeast
            FixedSizeError::UnsufficientExactBytes(b) => DynamicSizeError::UnsufficientAtLeastBytes(b),
        }
    }
}

impl From<DynamicSizeError> for DynamicSizeTotalSizeError {
    fn from(value: DynamicSizeError) -> Self { DynamicSizeTotalSizeError::Dynamic(value) }
}

impl core::fmt::Display for FixedSizeError {
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        match self {
            FixedSizeError::UnsufficientExactBytes(n) => write!(
                f,
                "Unsufficient Bytes to parse structure, the buffer needs to have {n} additional bytes to parse"
            ),
        }
    }
}

impl core::fmt::Display for DynamicSizeError {
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        match self {
            DynamicSizeError::NotContiguous(n) => write!(
                f,
                "Bytes support data not beeing Continous, however to peek data its required that the data is within \
                 the first chunk of data. You need to reorganize the underlying data structure so that the first {n} \
                 bytes are within the first chun."
            ),
            DynamicSizeError::UnsufficientExactBytes(n) => write!(
                f,
                "Unsufficient Bytes to parse structure, the buffer needs to have {n} additional bytes to parse"
            ),
            DynamicSizeError::UnsufficientAtLeastBytes(n) => write!(
                f,
                "Unsufficient Bytes to parse structure, the buffer needs to have at least {n} additional bytes to \
                 parse, probably even more bytes are needed. An exact estimation cannot be made yet"
            ),
        }
    }
}

impl core::fmt::Display for DynamicSizeTotalSizeError {
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        match self {
            DynamicSizeTotalSizeError::Dynamic(e) => write!(f, "{e}"),
            DynamicSizeTotalSizeError::TotalSizeInFieldIsInvalid => write!(
                f,
                "The Record contains an corrupt field stating the total size is lower than the minimum size"
            ),
        }
    }
}

#[cfg(feature = "std")]
impl Error for FixedSizeError {}
#[cfg(feature = "std")]
impl Error for DynamicSizeError {}
#[cfg(feature = "std")]
impl Error for DynamicSizeTotalSizeError {}

/// finds next occurence of a signature in a buffer, in case signature is
/// le-encoded
#[inline(always)]
#[allow(clippy::manual_find)] //performance improvement
pub fn find_next_signature(buf: &[u8], signature: [u8; 4]) -> Option<usize> {
    if buf.len() > 3 {
        for i in 0..buf.len() - 3 {
            if buf[i] == signature[0]
                && buf[i + 1] == signature[1]
                && buf[i + 2] == signature[2]
                && buf[i + 3] == signature[3]
            {
                return Some(i);
            }
        }
    }
    None
    /*
    (0..buf.len() - 4).find(|&i| {
        buf[i] == signature[0] && buf[i + 1] == signature[1] && buf[i + 2] == signature[2] && buf[i + 3] == signature[3]
    })
    */
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn find_next_signature_test() {
        let buf = [1, 2, 3, 4, 5, 6, 7, 8];
        assert_eq!(find_next_signature(&buf, [1, 2, 3, 4]), Some(0));
        assert_eq!(find_next_signature(&buf, [2, 3, 4, 5]), Some(1));
        assert_eq!(find_next_signature(&buf, [3, 4, 5, 6]), Some(2));
        assert_eq!(find_next_signature(&buf, [4, 3, 2, 1]), None);
        assert_eq!(find_next_signature(&buf, [0, 1, 2, 3]), None);
        assert_eq!(find_next_signature(&buf, [6, 7, 8, 9]), None);
        assert_eq!(find_next_signature(&buf, [5, 6, 7, 8]), Some(4));

        let buf = [1, 2, 3];
        assert_eq!(find_next_signature(&buf, [1, 2, 3, 4]), None);

        let buf = [1, 2];
        assert_eq!(find_next_signature(&buf, [1, 2, 3, 4]), None);

        let buf = [];
        assert_eq!(find_next_signature(&buf, [1, 2, 3, 4]), None);
    }

    #[test]
    fn display_dont_panic() {
        let mut buf = vec![0; 20000];
        use std::io::Write;
        write!(buf, "{}", FixedSizeError::UnsufficientExactBytes(1)).unwrap();
        write!(buf, "{}", DynamicSizeError::NotContiguous(2)).unwrap();
        write!(buf, "{}", DynamicSizeError::UnsufficientExactBytes(3)).unwrap();
        write!(buf, "{}", DynamicSizeError::UnsufficientAtLeastBytes(4)).unwrap();
        write!(
            buf,
            "{}",
            DynamicSizeTotalSizeError::Dynamic(DynamicSizeError::NotContiguous(5))
        )
        .unwrap();
        write!(buf, "{}", DynamicSizeTotalSizeError::TotalSizeInFieldIsInvalid).unwrap();
    }
}