| 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 done = inner(self, input, output)?; |
| self.crc.update(&output.written()[prior..]); |
| 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> { |
| // Because of the footer we have to have already flushed all the data out before we get here |
| if let State::Done = self.state { |
| Ok(true) |
| } else { |
| Err(Error::new( |
| ErrorKind::UnexpectedEof, |
| "unexpected end of file", |
| )) |
| } |
| } |
| } |