gzip 0.1.2

A GZIP compression library
Documentation
use std::{io, io::prelude::*, convert::{TryInto}};
use byteorder::{ReadBytesExt, LittleEndian};
use deflate::{deflate_bytes};
use encoding_rs::*;
use crate::types::*;
use crate::error::*;

pub struct GzipReader<S: io::Read> {
    source: S,
}

impl<S: io::Read> GzipReader<S> {
    pub fn new(source: S) -> Self {
        Self { source }
    }

    fn read_extra_data(&mut self, extra: &mut Extra) -> Result<()> {
        self.source.read_exact(&mut extra.subfield_id)?;
        extra.data_length = self.source.read_u16::<LittleEndian>()?;
        extra.data = vec![0u8; extra.data_length.into()];
        self.source.read_exact(&mut extra.data)?;
        Ok(())
    }

    fn read_zero_string(&mut self) -> Result<String> {
        let mut name_bytes = vec![];
        loop {
            let byte = self.source.read_u8()?;
            match byte {
                0 => break,
                _ => name_bytes.push(byte),
            }
        }
        let (name, _) = WINDOWS_1252.decode_without_bom_handling(&name_bytes[..name_bytes.len() - 1]);
        Ok(name.to_string())
    }

    fn read_header_start(&mut self) -> Result<Header> {
        let mut header = Header::default();
        self.source.read_exact(&mut header.id)?;
        if header.id != Header::GZIP_ID {
            return Err(Error(ErrorKind::InvalidMagicHeader, None))
        }
        header.compression_method = self.source.read_u8()?.try_into()?;
        header.flags = self.source.read_u8()?.try_into()
            .or(Err(Error(ErrorKind::InvalidFlags, None)))?;
        header.modification_time = self.source.read_u32::<LittleEndian>()?;
        header.compression_flags = self.source.read_u8()?.try_into()
            .or(Err(Error(ErrorKind::InvalidCompressionFlags, None)))?;
        header.operating_system = self.source.read_u8()?.try_into()?;
        Ok(header)
    }

    pub fn read_header_extra_field(&mut self, header: &mut Header) -> Result<()> {
        if header.flags.contains(Flags::Extra) {
            let mut extra = Extra::default();
            extra.length = self.source.read_u16::<LittleEndian>()?;
            self.read_extra_data(&mut extra)?;
            header.extra = Some(extra);
        }
        Ok(())
    }

    pub fn read_header_optional_fields(&mut self, header: &mut Header) -> Result<()> {
        if header.flags.contains(Flags::Name) {
            header.name = Some(self.read_zero_string()?);
        }
        if header.flags.contains(Flags::Comment) {
            header.comment = Some(self.read_zero_string()?);
        }
        if header.flags.contains(Flags::HeaderCrc) {
            let crc = self.source.read_u16::<LittleEndian>()?;
            header.crc = Some(crc);
        }
        Ok(())
    }

    pub fn read_header(&mut self) -> Result<Header> {
        let mut header = self.read_header_start()?;
        self.read_header_extra_field(&mut header)?;
        self.read_header_optional_fields(&mut header)?;
        Ok(header)
    }

    pub fn read_member(&mut self) -> Result<Member> {
        let mut member = Member::default();
        member.header = self.read_header()?;
        // The following 2 lines will make it impossible to read concatenated gzips
        // Right now it's pretty hard to scan for a next member in concatenated gzips with the
        // way rust streams work, it will be implemented at a later time
        let mut rest_bytes: Vec<u8> = vec!();
        self.source.read_to_end(&mut rest_bytes)?;
        let len = rest_bytes.len();
        let mut cursor = io::Cursor::new(rest_bytes);
        let mut compressed_data_bytes = vec![0u8; len - 8];
        cursor.read_exact(&mut compressed_data_bytes)?;
        member.data = deflate_bytes(&mut compressed_data_bytes);
        member.crc = cursor.read_u32::<LittleEndian>()?;
        member.input_size = cursor.read_u32::<LittleEndian>()?;
        Ok(member)
    }
}

impl<S: io::Read + io::Seek> GzipReader<S> {
    fn read_header_extra_field_fast(&mut self, header: &mut Header) -> Result<()> {
        if header.flags.contains(Flags::Extra) {
            let mut extra = Extra::default();
            extra.length = self.source.read_u16::<LittleEndian>()?;
            self.source.seek(io::SeekFrom::Current(extra.length.into()))?;
        }
        Ok(())
    }

    pub fn read_header_fast(&mut self) -> Result<Header> {
        let mut header = self.read_header_start()?;
        self.read_header_extra_field_fast(&mut header)?;
        self.read_header_optional_fields(&mut header)?;
        Ok(header)
    }
}