1use std::ffi::CString;
2use std::io::prelude::*;
3use std::time;
4
5use crate::bufreader::BufReader;
6use crate::Compression;
7
8pub static FHCRC: u8 = 1 << 1;
9pub static FEXTRA: u8 = 1 << 2;
10pub static FNAME: u8 = 1 << 3;
11pub static FCOMMENT: u8 = 1 << 4;
12
13pub mod bufread;
14pub mod read;
15pub mod write;
16
17#[derive(PartialEq, Clone, Debug, Default)]
22pub struct GzHeader {
23 extra: Option<Vec<u8>>,
24 filename: Option<Vec<u8>>,
25 comment: Option<Vec<u8>>,
26 operating_system: u8,
27 mtime: u32,
28}
29
30impl GzHeader {
31 pub fn filename(&self) -> Option<&[u8]> {
33 self.filename.as_ref().map(|s| &s[..])
34 }
35
36 pub fn extra(&self) -> Option<&[u8]> {
38 self.extra.as_ref().map(|s| &s[..])
39 }
40
41 pub fn comment(&self) -> Option<&[u8]> {
43 self.comment.as_ref().map(|s| &s[..])
44 }
45
46 pub fn operating_system(&self) -> u8 {
51 self.operating_system
52 }
53
54 pub fn mtime(&self) -> u32 {
64 self.mtime
65 }
66
67 pub fn mtime_as_datetime(&self) -> Option<time::SystemTime> {
75 if self.mtime == 0 {
76 None
77 } else {
78 let duration = time::Duration::new(u64::from(self.mtime), 0);
79 let datetime = time::UNIX_EPOCH + duration;
80 Some(datetime)
81 }
82 }
83}
84
85#[derive(Debug)]
112pub struct GzBuilder {
113 extra: Option<Vec<u8>>,
114 filename: Option<CString>,
115 comment: Option<CString>,
116 operating_system: Option<u8>,
117 mtime: u32,
118}
119
120impl GzBuilder {
121 pub fn new() -> GzBuilder {
123 GzBuilder {
124 extra: None,
125 filename: None,
126 comment: None,
127 operating_system: None,
128 mtime: 0,
129 }
130 }
131
132 pub fn mtime(mut self, mtime: u32) -> GzBuilder {
134 self.mtime = mtime;
135 self
136 }
137
138 pub fn operating_system(mut self, os: u8) -> GzBuilder {
140 self.operating_system = Some(os);
141 self
142 }
143
144 pub fn extra<T: Into<Vec<u8>>>(mut self, extra: T) -> GzBuilder {
146 self.extra = Some(extra.into());
147 self
148 }
149
150 pub fn filename<T: Into<Vec<u8>>>(mut self, filename: T) -> GzBuilder {
156 self.filename = Some(CString::new(filename.into()).unwrap());
157 self
158 }
159
160 pub fn comment<T: Into<Vec<u8>>>(mut self, comment: T) -> GzBuilder {
166 self.comment = Some(CString::new(comment.into()).unwrap());
167 self
168 }
169
170 pub fn write<W: Write>(self, w: W, lvl: Compression) -> write::GzEncoder<W> {
175 write::gz_encoder(self.into_header(lvl), w, lvl)
176 }
177
178 pub fn read<R: Read>(self, r: R, lvl: Compression) -> read::GzEncoder<R> {
183 read::gz_encoder(self.buf_read(BufReader::new(r), lvl))
184 }
185
186 pub fn buf_read<R>(self, r: R, lvl: Compression) -> bufread::GzEncoder<R>
191 where
192 R: BufRead,
193 {
194 bufread::gz_encoder(self.into_header(lvl), r, lvl)
195 }
196
197 fn into_header(self, lvl: Compression) -> Vec<u8> {
198 let GzBuilder {
199 extra,
200 filename,
201 comment,
202 operating_system,
203 mtime,
204 } = self;
205 let mut flg = 0;
206 let mut header = vec![0u8; 10];
207 match extra {
208 Some(v) => {
209 flg |= FEXTRA;
210 header.push((v.len() >> 0) as u8);
211 header.push((v.len() >> 8) as u8);
212 header.extend(v);
213 }
214 None => {}
215 }
216 match filename {
217 Some(filename) => {
218 flg |= FNAME;
219 header.extend(filename.as_bytes_with_nul().iter().map(|x| *x));
220 }
221 None => {}
222 }
223 match comment {
224 Some(comment) => {
225 flg |= FCOMMENT;
226 header.extend(comment.as_bytes_with_nul().iter().map(|x| *x));
227 }
228 None => {}
229 }
230 header[0] = 0x1f;
231 header[1] = 0x8b;
232 header[2] = 8;
233 header[3] = flg;
234 header[4] = (mtime >> 0) as u8;
235 header[5] = (mtime >> 8) as u8;
236 header[6] = (mtime >> 16) as u8;
237 header[7] = (mtime >> 24) as u8;
238 header[8] = if lvl.0 >= Compression::best().0 {
239 2
240 } else if lvl.0 <= Compression::fast().0 {
241 4
242 } else {
243 0
244 };
245
246 header[9] = operating_system.unwrap_or(255);
251 return header;
252 }
253}
254
255#[cfg(test)]
256mod tests {
257 use std::io::prelude::*;
258
259 use super::{read, write, GzBuilder};
260 use crate::Compression;
261 use rand::{thread_rng, Rng};
262
263 #[test]
264 fn roundtrip() {
265 let mut e = write::GzEncoder::new(Vec::new(), Compression::default());
266 e.write_all(b"foo bar baz").unwrap();
267 let inner = e.finish().unwrap();
268 let mut d = read::GzDecoder::new(&inner[..]);
269 let mut s = String::new();
270 d.read_to_string(&mut s).unwrap();
271 assert_eq!(s, "foo bar baz");
272 }
273
274 #[test]
275 fn roundtrip_zero() {
276 let e = write::GzEncoder::new(Vec::new(), Compression::default());
277 let inner = e.finish().unwrap();
278 let mut d = read::GzDecoder::new(&inner[..]);
279 let mut s = String::new();
280 d.read_to_string(&mut s).unwrap();
281 assert_eq!(s, "");
282 }
283
284 #[test]
285 fn roundtrip_big() {
286 let mut real = Vec::new();
287 let mut w = write::GzEncoder::new(Vec::new(), Compression::default());
288 let v = crate::random_bytes().take(1024).collect::<Vec<_>>();
289 for _ in 0..200 {
290 let to_write = &v[..thread_rng().gen_range(0, v.len())];
291 real.extend(to_write.iter().map(|x| *x));
292 w.write_all(to_write).unwrap();
293 }
294 let result = w.finish().unwrap();
295 let mut r = read::GzDecoder::new(&result[..]);
296 let mut v = Vec::new();
297 r.read_to_end(&mut v).unwrap();
298 assert!(v == real);
299 }
300
301 #[test]
302 fn roundtrip_big2() {
303 let v = crate::random_bytes().take(1024 * 1024).collect::<Vec<_>>();
304 let mut r = read::GzDecoder::new(read::GzEncoder::new(&v[..], Compression::default()));
305 let mut res = Vec::new();
306 r.read_to_end(&mut res).unwrap();
307 assert!(res == v);
308 }
309
310 #[test]
311 fn fields() {
312 let r = vec![0, 2, 4, 6];
313 let e = GzBuilder::new()
314 .filename("foo.rs")
315 .comment("bar")
316 .extra(vec![0, 1, 2, 3])
317 .read(&r[..], Compression::default());
318 let mut d = read::GzDecoder::new(e);
319 assert_eq!(d.header().unwrap().filename(), Some(&b"foo.rs"[..]));
320 assert_eq!(d.header().unwrap().comment(), Some(&b"bar"[..]));
321 assert_eq!(d.header().unwrap().extra(), Some(&b"\x00\x01\x02\x03"[..]));
322 let mut res = Vec::new();
323 d.read_to_end(&mut res).unwrap();
324 assert_eq!(res, vec![0, 2, 4, 6]);
325 }
326
327 #[test]
328 fn keep_reading_after_end() {
329 let mut e = write::GzEncoder::new(Vec::new(), Compression::default());
330 e.write_all(b"foo bar baz").unwrap();
331 let inner = e.finish().unwrap();
332 let mut d = read::GzDecoder::new(&inner[..]);
333 let mut s = String::new();
334 d.read_to_string(&mut s).unwrap();
335 assert_eq!(s, "foo bar baz");
336 d.read_to_string(&mut s).unwrap();
337 assert_eq!(s, "foo bar baz");
338 }
339
340 #[test]
341 fn qc_reader() {
342 ::quickcheck::quickcheck(test as fn(_) -> _);
343
344 fn test(v: Vec<u8>) -> bool {
345 let r = read::GzEncoder::new(&v[..], Compression::default());
346 let mut r = read::GzDecoder::new(r);
347 let mut v2 = Vec::new();
348 r.read_to_end(&mut v2).unwrap();
349 v == v2
350 }
351 }
352
353 #[test]
354 fn flush_after_write() {
355 let mut f = write::GzEncoder::new(Vec::new(), Compression::default());
356 write!(f, "Hello world").unwrap();
357 f.flush().unwrap();
358 }
359}