blob: 96a86b5e17bf03c3c1fa64dc80801bfe48467f80 [file] [log] [blame]
//! The encoder and decoder of the GZIP format.
//!
//! The GZIP format is defined in [RFC-1952](https://tools.ietf.org/html/rfc1952).
//!
//! # Examples
//! ```
//! use std::io::{self, Read};
//! use libflate::gzip::Encoder;
//! use libflate::non_blocking::gzip::Decoder;
//!
//! // Encoding
//! let mut encoder = Encoder::new(Vec::new()).unwrap();
//! io::copy(&mut &b"Hello World!"[..], &mut encoder).unwrap();
//! let encoded_data = encoder.finish().into_result().unwrap();
//!
//! // Decoding
//! let mut decoder = Decoder::new(&encoded_data[..]);
//! let mut decoded_data = Vec::new();
//! decoder.read_to_end(&mut decoded_data).unwrap();
//!
//! assert_eq!(decoded_data, b"Hello World!");
//! ```
use std::io::{self, Read};
use checksum;
use gzip::{Header, Trailer};
use non_blocking::deflate;
/// GZIP decoder which supports non-blocking I/O.
#[derive(Debug)]
pub struct Decoder<R> {
header: Option<Header>,
reader: deflate::Decoder<R>,
crc32: checksum::Crc32,
eos: bool,
}
impl<R: Read> Decoder<R> {
/// Makes a new decoder instance.
///
/// `inner` is to be decoded GZIP stream.
///
/// # Examples
/// ```
/// use std::io::Read;
/// use libflate::non_blocking::gzip::Decoder;
///
/// let encoded_data = [31, 139, 8, 0, 123, 0, 0, 0, 0, 3, 1, 12, 0, 243, 255,
/// 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33,
/// 163, 28, 41, 28, 12, 0, 0, 0];
///
/// let mut decoder = Decoder::new(&encoded_data[..]);
/// let mut buf = Vec::new();
/// decoder.read_to_end(&mut buf).unwrap();
///
/// assert_eq!(buf, b"Hello World!");
/// ```
pub fn new(inner: R) -> Self {
Decoder {
header: None,
reader: deflate::Decoder::new(inner),
crc32: checksum::Crc32::new(),
eos: false,
}
}
/// Returns the header of the GZIP stream.
///
/// # Examples
/// ```
/// use libflate::gzip::Os;
/// use libflate::non_blocking::gzip::Decoder;
///
/// let encoded_data = [31, 139, 8, 0, 123, 0, 0, 0, 0, 3, 1, 12, 0, 243, 255,
/// 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33,
/// 163, 28, 41, 28, 12, 0, 0, 0];
///
/// let mut decoder = Decoder::new(&encoded_data[..]);
/// assert_eq!(decoder.header().unwrap().os(), Os::Unix);
/// ```
pub fn header(&mut self) -> io::Result<&Header> {
if let Some(ref header) = self.header {
Ok(header)
} else {
let header = self.reader.bit_reader_mut().transaction(|r| {
Header::read_from(r.as_inner_mut())
})?;
self.header = Some(header);
self.header()
}
}
/// Unwraps this `Decoder`, returning the underlying reader.
///
/// # Examples
/// ```
/// use std::io::Cursor;
/// use libflate::non_blocking::gzip::Decoder;
///
/// let encoded_data = [31, 139, 8, 0, 123, 0, 0, 0, 0, 3, 1, 12, 0, 243, 255,
/// 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33,
/// 163, 28, 41, 28, 12, 0, 0, 0];
///
/// let decoder = Decoder::new(Cursor::new(&encoded_data[..]));
/// assert_eq!(decoder.into_inner().into_inner(), &encoded_data[..]);
/// ```
pub fn into_inner(self) -> R {
self.reader.into_inner()
}
}
impl<R: Read> Read for Decoder<R> {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
if self.header.is_none() {
self.header()?;
}
if self.eos {
Ok(0)
} else {
let read_size = self.reader.read(buf)?;
self.crc32.update(&buf[..read_size]);
if read_size == 0 {
let trailer = self.reader.bit_reader_mut().transaction(|r| {
Trailer::read_from(r.as_inner_mut())
})?;
self.eos = true;
if trailer.crc32() != self.crc32.value() {
Err(invalid_data_error!(
"CRC32 mismatched: value={}, expected={}",
self.crc32.value(),
trailer.crc32()
))
} else {
Ok(0)
}
} else {
Ok(read_size)
}
}
}
}
#[cfg(test)]
mod test {
use std::io;
use gzip::Encoder;
use util::{nb_read_to_end, WouldBlockReader};
use super::*;
fn decode_all(buf: &[u8]) -> io::Result<Vec<u8>> {
let decoder = Decoder::new(WouldBlockReader::new(buf));
nb_read_to_end(decoder)
}
#[test]
fn encode_works() {
let plain = b"Hello World! Hello GZIP!!";
let mut encoder = Encoder::new(Vec::new()).unwrap();
io::copy(&mut &plain[..], &mut encoder).unwrap();
let encoded = encoder.finish().into_result().unwrap();
assert_eq!(decode_all(&encoded).unwrap(), plain);
}
}