zip-core 0.0.4

zip implementation independent structs and helpers
Documentation
use super::{
    super::{LocalFileHeader, LocalFileHeaderFixed},
    preview_u16_from_buf, validate_length, validate_length_fixed, DynamicSizeError, FixedSizeError, Parse, ParseExtend,
};

extern crate alloc;
use alloc::vec;

impl Parse for LocalFileHeaderFixed {
    type Error = FixedSizeError;

    fn from_buf<T: bytes::Buf>(buf: &mut T) -> Result<Self, Self::Error>
    where
        Self: Sized,
    {
        let remaining = buf.remaining();
        validate_length(remaining, Self::SIZE_IN_BYTES, FixedSizeError::UnsufficientExactBytes)?;
        Ok(Self {
            local_file_header_signature: buf.get_u32_le(),
            version_needed_to_extract: buf.get_u16_le(),
            general_purpose_bit_flag: buf.get_u16_le(),
            compression_method: buf.get_u16_le(),
            last_mod_file_time: buf.get_u16_le(),
            last_mod_file_date: buf.get_u16_le(),
            crc_32: buf.get_u32_le(),
            compressed_size: buf.get_u32_le(),
            uncompressed_size: buf.get_u32_le(),
            file_name_length: buf.get_u16_le(),
            extra_field_length: buf.get_u16_le(),
        })
    }

    fn to_buf<T: bytes::BufMut>(&self, buf: &mut T) -> Result<(), Self::Error> {
        let remaining = buf.remaining_mut();
        validate_length(remaining, Self::SIZE_IN_BYTES, FixedSizeError::UnsufficientExactBytes)?;
        buf.put_u32_le(self.local_file_header_signature);
        buf.put_u16_le(self.version_needed_to_extract);
        buf.put_u16_le(self.general_purpose_bit_flag);
        buf.put_u16_le(self.compression_method);
        buf.put_u16_le(self.last_mod_file_time);
        buf.put_u16_le(self.last_mod_file_date);
        buf.put_u32_le(self.crc_32);
        buf.put_u32_le(self.compressed_size);
        buf.put_u32_le(self.uncompressed_size);
        buf.put_u16_le(self.file_name_length);
        buf.put_u16_le(self.extra_field_length);
        Ok(())
    }
}

impl ParseExtend for LocalFileHeader {
    type Error = DynamicSizeError;
    type Fixed = LocalFileHeaderFixed;

    fn from_buf_fixed<T: bytes::Buf>(buf: &mut T, fixed: Self::Fixed) -> Result<Self, (Self::Error, Self::Fixed)>
    where
        Self: Sized,
    {
        let total = fixed.file_name_length as usize + fixed.extra_field_length as usize;
        let fixed = validate_length_fixed(buf.remaining(), total, fixed, DynamicSizeError::UnsufficientExactBytes)?;

        let mut file_name = vec![0; fixed.file_name_length as usize];
        let mut extra_field = vec![0; fixed.extra_field_length as usize];
        buf.copy_to_slice(&mut file_name);
        buf.copy_to_slice(&mut extra_field);

        let s = Self {
            fixed,
            file_name,
            extra_field,
        };
        Ok(s)
    }
}

impl Parse for LocalFileHeader {
    type Error = DynamicSizeError;

    fn from_buf<T: bytes::Buf>(buf: &mut T) -> Result<Self, Self::Error>
    where
        Self: Sized,
    {
        let remaining = buf.remaining();
        const SIZE: usize = LocalFileHeaderFixed::SIZE_IN_BYTES;
        validate_length(remaining, SIZE, DynamicSizeError::UnsufficientAtLeastBytes)?;
        const FILE_NAME_PEEK_START: usize = SIZE - 4;
        const EXTRA_FIELD_PEEK_START: usize = SIZE - 2;
        const ERR: DynamicSizeError = DynamicSizeError::NotContiguous(SIZE);
        let chunk = buf.chunk();
        let file_name_length: u16 = preview_u16_from_buf(chunk, FILE_NAME_PEEK_START).ok_or(ERR)?;
        let extra_field_length: u16 = preview_u16_from_buf(chunk, EXTRA_FIELD_PEEK_START).ok_or(ERR)?;
        let total = SIZE + file_name_length as usize + extra_field_length as usize;
        validate_length(remaining, total, DynamicSizeError::UnsufficientExactBytes)?;

        let fixed = LocalFileHeaderFixed::from_buf(buf).map_err(FixedSizeError::in_dynamic)?;
        Self::from_buf_fixed(buf, fixed).map_err(|e| e.0)
    }

    fn to_buf<T: bytes::BufMut>(&self, buf: &mut T) -> Result<(), Self::Error> {
        let remaining = buf.remaining_mut();
        let total = LocalFileHeaderFixed::SIZE_IN_BYTES + self.file_name.len() + self.extra_field.len();
        validate_length(remaining, total, DynamicSizeError::UnsufficientExactBytes)?;
        self.fixed.to_buf(buf).map_err(FixedSizeError::in_dynamic)?;
        buf.put_slice(&self.file_name);
        buf.put_slice(&self.extra_field);
        Ok(())
    }
}

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

    #[test]
    fn cycle_local_file_header() {
        let file_name = vec![48, 49, 32, 50];
        let extra_field = vec![61, 62, 32, 63, 64];
        let lf = LocalFileHeader {
            fixed:       LocalFileHeaderFixed {
                local_file_header_signature: LocalFileHeaderFixed::LOCAL_FILE_HEADER_SIGNATURE,
                version_needed_to_extract: 1,
                general_purpose_bit_flag: 2,
                compression_method: 3,
                last_mod_file_time: 4,
                last_mod_file_date: 5,
                crc_32: 6,
                compressed_size: 7,
                uncompressed_size: 8,
                file_name_length: file_name.len() as u16,
                extra_field_length: extra_field.len() as u16,
            },
            file_name:   file_name.clone(),
            extra_field: extra_field.clone(),
        };
        let mut buf = vec![];
        lf.to_buf(&mut buf).unwrap();
        assert_eq!(
            buf.len(),
            LocalFileHeaderFixed::SIZE_IN_BYTES + file_name.len() + extra_field.len()
        );
        let mut readbuf = buf.as_slice();
        let lf2 = LocalFileHeader::from_buf(&mut readbuf).unwrap();
        assert_eq!(lf, lf2);
        assert_eq!(lf2.file_name, file_name);
        assert_eq!(lf2.extra_field, extra_field);
    }

    #[test]
    fn parseextend_local_file_header() {
        let buf: Vec<u8> = vec![48, 49, 32, 50];
        let mut fixed = LocalFileHeaderFixed {
            local_file_header_signature: LocalFileHeaderFixed::LOCAL_FILE_HEADER_SIGNATURE,
            version_needed_to_extract: 1,
            general_purpose_bit_flag: 2,
            compression_method: 3,
            last_mod_file_time: 4,
            last_mod_file_date: 5,
            crc_32: 6,
            compressed_size: 7,
            uncompressed_size: 8,
            file_name_length: 3u16,
            extra_field_length: 1u16,
        };
        assert!(LocalFileHeader::from_buf_fixed(&mut buf.as_slice(), fixed.clone()).is_ok());
        fixed.extra_field_length += 1;
        assert!(LocalFileHeader::from_buf_fixed(&mut buf.as_slice(), fixed).is_err());
    }
}