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()?;
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)
}
}