use crate::{
codec::{
gzip::header::{self, Header},
Decode,
},
util::PartialBuffer,
};
use std::io::{Error, ErrorKind, Result};
use flate2::Crc;
#[derive(Debug)]
enum State {
Header(header::Parser),
Decoding,
Footer(PartialBuffer<Vec<u8>>),
Done,
}
#[derive(Debug)]
pub struct GzipDecoder {
inner: crate::codec::FlateDecoder,
crc: Crc,
state: State,
header: Header,
}
fn check_footer(crc: &Crc, input: &[u8]) -> Result<()> {
if input.len() < 8 {
return Err(Error::new(
ErrorKind::InvalidData,
"Invalid gzip footer length",
));
}
let crc_sum = crc.sum().to_le_bytes();
let bytes_read = crc.amount().to_le_bytes();
if crc_sum != input[0..4] {
return Err(Error::new(
ErrorKind::InvalidData,
"CRC computed does not match",
));
}
if bytes_read != input[4..8] {
return Err(Error::new(
ErrorKind::InvalidData,
"amount of bytes read does not match",
));
}
Ok(())
}
impl GzipDecoder {
pub(crate) fn new() -> Self {
Self {
inner: crate::codec::FlateDecoder::new(false),
crc: Crc::new(),
state: State::Header(header::Parser::default()),
header: Header::default(),
}
}
fn process<I: AsRef<[u8]>, O: AsRef<[u8]> + AsMut<[u8]>>(
&mut self,
input: &mut PartialBuffer<I>,
output: &mut PartialBuffer<O>,
inner: impl Fn(&mut Self, &mut PartialBuffer<I>, &mut PartialBuffer<O>) -> Result<bool>,
) -> Result<bool> {
loop {
match &mut self.state {
State::Header(parser) => {
if let Some(header) = parser.input(input)? {
self.header = header;
self.state = State::Decoding;
}
}
State::Decoding => {
let prior = output.written().len();
let res = inner(self, input, output);
if (output.written().len() > prior) {
self.crc.update(&output.written()[prior..]);
}
let done = res?;
if done {
self.state = State::Footer(vec![0; 8].into())
}
}
State::Footer(footer) => {
footer.copy_unwritten_from(input);
if footer.unwritten().is_empty() {
check_footer(&self.crc, footer.written())?;
self.state = State::Done
}
}
State::Done => {}
};
if let State::Done = self.state {
return Ok(true);
}
if input.unwritten().is_empty() || output.unwritten().is_empty() {
return Ok(false);
}
}
}
}
impl Decode for GzipDecoder {
fn reinit(&mut self) -> Result<()> {
self.inner.reinit()?;
self.crc = Crc::new();
self.state = State::Header(header::Parser::default());
self.header = Header::default();
Ok(())
}
fn decode(
&mut self,
input: &mut PartialBuffer<impl AsRef<[u8]>>,
output: &mut PartialBuffer<impl AsRef<[u8]> + AsMut<[u8]>>,
) -> Result<bool> {
self.process(input, output, |this, input, output| {
this.inner.decode(input, output)
})
}
fn flush(
&mut self,
output: &mut PartialBuffer<impl AsRef<[u8]> + AsMut<[u8]>>,
) -> Result<bool> {
loop {
match self.state {
State::Header(_) | State::Footer(_) | State::Done => return Ok(true),
State::Decoding => {
let prior = output.written().len();
let done = self.inner.flush(output)?;
self.crc.update(&output.written()[prior..]);
if done {
return Ok(true);
}
}
};
if output.unwritten().is_empty() {
return Ok(false);
}
}
}
fn finish(
&mut self,
_output: &mut PartialBuffer<impl AsRef<[u8]> + AsMut<[u8]>>,
) -> Result<bool> {
if let State::Done = self.state {
Ok(true)
} else {
Err(Error::new(
ErrorKind::UnexpectedEof,
"unexpected end of file",
))
}
}
}