use std::ffi::CString;
use std::io::prelude::*;
use std::time;
use bufreader::BufReader;
use Compression;
pub static FHCRC: u8 = 1 << 1;
pub static FEXTRA: u8 = 1 << 2;
pub static FNAME: u8 = 1 << 3;
pub static FCOMMENT: u8 = 1 << 4;
pub mod bufread;
pub mod read;
pub mod write;
#[derive(PartialEq, Clone, Debug, Default)]
pub struct GzHeader {
extra: Option<Vec<u8>>,
filename: Option<Vec<u8>>,
comment: Option<Vec<u8>>,
operating_system: u8,
mtime: u32,
}
impl GzHeader {
pub fn filename(&self) -> Option<&[u8]> {
self.filename.as_ref().map(|s| &s[..])
}
pub fn extra(&self) -> Option<&[u8]> {
self.extra.as_ref().map(|s| &s[..])
}
pub fn comment(&self) -> Option<&[u8]> {
self.comment.as_ref().map(|s| &s[..])
}
pub fn operating_system(&self) -> u8 {
self.operating_system
}
pub fn mtime(&self) -> u32 {
self.mtime
}
pub fn mtime_as_datetime(&self) -> Option<time::SystemTime> {
if self.mtime == 0 {
None
} else {
let duration = time::Duration::new(u64::from(self.mtime), 0);
let datetime = time::UNIX_EPOCH + duration;
Some(datetime)
}
}
}
#[derive(Debug)]
pub struct GzBuilder {
extra: Option<Vec<u8>>,
filename: Option<CString>,
comment: Option<CString>,
operating_system: Option<u8>,
mtime: u32,
}
impl GzBuilder {
pub fn new() -> GzBuilder {
GzBuilder {
extra: None,
filename: None,
comment: None,
operating_system: None,
mtime: 0,
}
}
pub fn mtime(mut self, mtime: u32) -> GzBuilder {
self.mtime = mtime;
self
}
pub fn operating_system(mut self, os: u8) -> GzBuilder {
self.operating_system = Some(os);
self
}
pub fn extra<T: Into<Vec<u8>>>(mut self, extra: T) -> GzBuilder {
self.extra = Some(extra.into());
self
}
pub fn filename<T: Into<Vec<u8>>>(mut self, filename: T) -> GzBuilder {
self.filename = Some(CString::new(filename.into()).unwrap());
self
}
pub fn comment<T: Into<Vec<u8>>>(mut self, comment: T) -> GzBuilder {
self.comment = Some(CString::new(comment.into()).unwrap());
self
}
pub fn write<W: Write>(self, w: W, lvl: Compression) -> write::GzEncoder<W> {
write::gz_encoder(self.into_header(lvl), w, lvl)
}
pub fn read<R: Read>(self, r: R, lvl: Compression) -> read::GzEncoder<R> {
read::gz_encoder(self.buf_read(BufReader::new(r), lvl))
}
pub fn buf_read<R>(self, r: R, lvl: Compression) -> bufread::GzEncoder<R>
where
R: BufRead,
{
bufread::gz_encoder(self.into_header(lvl), r, lvl)
}
fn into_header(self, lvl: Compression) -> Vec<u8> {
let GzBuilder {
extra,
filename,
comment,
operating_system,
mtime,
} = self;
let mut flg = 0;
let mut header = vec![0u8; 10];
match extra {
Some(v) => {
flg |= FEXTRA;
header.push((v.len() >> 0) as u8);
header.push((v.len() >> 8) as u8);
header.extend(v);
}
None => {}
}
match filename {
Some(filename) => {
flg |= FNAME;
header.extend(filename.as_bytes_with_nul().iter().map(|x| *x));
}
None => {}
}
match comment {
Some(comment) => {
flg |= FCOMMENT;
header.extend(comment.as_bytes_with_nul().iter().map(|x| *x));
}
None => {}
}
header[0] = 0x1f;
header[1] = 0x8b;
header[2] = 8;
header[3] = flg;
header[4] = (mtime >> 0) as u8;
header[5] = (mtime >> 8) as u8;
header[6] = (mtime >> 16) as u8;
header[7] = (mtime >> 24) as u8;
header[8] = if lvl.0 >= Compression::best().0 {
2
} else if lvl.0 <= Compression::fast().0 {
4
} else {
0
};
header[9] = operating_system.unwrap_or(255);
return header;
}
}
#[cfg(test)]
mod tests {
use std::io::prelude::*;
use super::{read, write, GzBuilder};
use rand::{thread_rng, Rng};
use Compression;
#[test]
fn roundtrip() {
let mut e = write::GzEncoder::new(Vec::new(), Compression::default());
e.write_all(b"foo bar baz").unwrap();
let inner = e.finish().unwrap();
let mut d = read::GzDecoder::new(&inner[..]);
let mut s = String::new();
d.read_to_string(&mut s).unwrap();
assert_eq!(s, "foo bar baz");
}
#[test]
fn roundtrip_zero() {
let e = write::GzEncoder::new(Vec::new(), Compression::default());
let inner = e.finish().unwrap();
let mut d = read::GzDecoder::new(&inner[..]);
let mut s = String::new();
d.read_to_string(&mut s).unwrap();
assert_eq!(s, "");
}
#[test]
fn roundtrip_big() {
let mut real = Vec::new();
let mut w = write::GzEncoder::new(Vec::new(), Compression::default());
let v = ::random_bytes().take(1024).collect::<Vec<_>>();
for _ in 0..200 {
let to_write = &v[..thread_rng().gen_range(0, v.len())];
real.extend(to_write.iter().map(|x| *x));
w.write_all(to_write).unwrap();
}
let result = w.finish().unwrap();
let mut r = read::GzDecoder::new(&result[..]);
let mut v = Vec::new();
r.read_to_end(&mut v).unwrap();
assert!(v == real);
}
#[test]
fn roundtrip_big2() {
let v = ::random_bytes().take(1024 * 1024).collect::<Vec<_>>();
let mut r = read::GzDecoder::new(read::GzEncoder::new(&v[..], Compression::default()));
let mut res = Vec::new();
r.read_to_end(&mut res).unwrap();
assert!(res == v);
}
#[test]
fn fields() {
let r = vec![0, 2, 4, 6];
let e = GzBuilder::new()
.filename("foo.rs")
.comment("bar")
.extra(vec![0, 1, 2, 3])
.read(&r[..], Compression::default());
let mut d = read::GzDecoder::new(e);
assert_eq!(d.header().unwrap().filename(), Some(&b"foo.rs"[..]));
assert_eq!(d.header().unwrap().comment(), Some(&b"bar"[..]));
assert_eq!(d.header().unwrap().extra(), Some(&b"\x00\x01\x02\x03"[..]));
let mut res = Vec::new();
d.read_to_end(&mut res).unwrap();
assert_eq!(res, vec![0, 2, 4, 6]);
}
#[test]
fn keep_reading_after_end() {
let mut e = write::GzEncoder::new(Vec::new(), Compression::default());
e.write_all(b"foo bar baz").unwrap();
let inner = e.finish().unwrap();
let mut d = read::GzDecoder::new(&inner[..]);
let mut s = String::new();
d.read_to_string(&mut s).unwrap();
assert_eq!(s, "foo bar baz");
d.read_to_string(&mut s).unwrap();
assert_eq!(s, "foo bar baz");
}
#[test]
fn qc_reader() {
::quickcheck::quickcheck(test as fn(_) -> _);
fn test(v: Vec<u8>) -> bool {
let r = read::GzEncoder::new(&v[..], Compression::default());
let mut r = read::GzDecoder::new(r);
let mut v2 = Vec::new();
r.read_to_end(&mut v2).unwrap();
v == v2
}
}
#[test]
fn flush_after_write() {
let mut f = write::GzEncoder::new(Vec::new(), Compression::default());
write!(f, "Hello world").unwrap();
f.flush().unwrap();
}
}