1 use std::ffi::CString;
2 use std::io::prelude::*;
3 use std::time;
4 
5 use crate::bufreader::BufReader;
6 use crate::Compression;
7 
8 pub static FHCRC: u8 = 1 << 1;
9 pub static FEXTRA: u8 = 1 << 2;
10 pub static FNAME: u8 = 1 << 3;
11 pub static FCOMMENT: u8 = 1 << 4;
12 
13 pub mod bufread;
14 pub mod read;
15 pub mod write;
16 
17 /// A structure representing the header of a gzip stream.
18 ///
19 /// The header can contain metadata about the file that was compressed, if
20 /// present.
21 #[derive(PartialEq, Clone, Debug, Default)]
22 pub 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 
30 impl GzHeader {
31     /// Returns the `filename` field of this gzip stream's header, if present.
filename(&self) -> Option<&[u8]>32     pub fn filename(&self) -> Option<&[u8]> {
33         self.filename.as_ref().map(|s| &s[..])
34     }
35 
36     /// Returns the `extra` field of this gzip stream's header, if present.
extra(&self) -> Option<&[u8]>37     pub fn extra(&self) -> Option<&[u8]> {
38         self.extra.as_ref().map(|s| &s[..])
39     }
40 
41     /// Returns the `comment` field of this gzip stream's header, if present.
comment(&self) -> Option<&[u8]>42     pub fn comment(&self) -> Option<&[u8]> {
43         self.comment.as_ref().map(|s| &s[..])
44     }
45 
46     /// Returns the `operating_system` field of this gzip stream's header.
47     ///
48     /// There are predefined values for various operating systems.
49     /// 255 means that the value is unknown.
operating_system(&self) -> u850     pub fn operating_system(&self) -> u8 {
51         self.operating_system
52     }
53 
54     /// This gives the most recent modification time of the original file being compressed.
55     ///
56     /// The time is in Unix format, i.e., seconds since 00:00:00 GMT, Jan. 1, 1970.
57     /// (Note that this may cause problems for MS-DOS and other systems that use local
58     /// rather than Universal time.) If the compressed data did not come from a file,
59     /// `mtime` is set to the time at which compression started.
60     /// `mtime` = 0 means no time stamp is available.
61     ///
62     /// The usage of `mtime` is discouraged because of Year 2038 problem.
mtime(&self) -> u3263     pub fn mtime(&self) -> u32 {
64         self.mtime
65     }
66 
67     /// Returns the most recent modification time represented by a date-time type.
68     /// Returns `None` if the value of the underlying counter is 0,
69     /// indicating no time stamp is available.
70     ///
71     ///
72     /// The time is measured as seconds since 00:00:00 GMT, Jan. 1 1970.
73     /// See [`mtime`](#method.mtime) for more detail.
mtime_as_datetime(&self) -> Option<time::SystemTime>74     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 /// A builder structure to create a new gzip Encoder.
86 ///
87 /// This structure controls header configuration options such as the filename.
88 ///
89 /// # Examples
90 ///
91 /// ```
92 /// use std::io::prelude::*;
93 /// # use std::io;
94 /// use std::fs::File;
95 /// use flate2::GzBuilder;
96 /// use flate2::Compression;
97 ///
98 /// // GzBuilder opens a file and writes a sample string using GzBuilder pattern
99 ///
100 /// # fn sample_builder() -> Result<(), io::Error> {
101 /// let f = File::create("examples/hello_world.gz")?;
102 /// let mut gz = GzBuilder::new()
103 ///                 .filename("hello_world.txt")
104 ///                 .comment("test file, please delete")
105 ///                 .write(f, Compression::default());
106 /// gz.write_all(b"hello world")?;
107 /// gz.finish()?;
108 /// # Ok(())
109 /// # }
110 /// ```
111 #[derive(Debug)]
112 pub 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 
120 impl GzBuilder {
121     /// Create a new blank builder with no header by default.
new() -> GzBuilder122     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     /// Configure the `mtime` field in the gzip header.
mtime(mut self, mtime: u32) -> GzBuilder133     pub fn mtime(mut self, mtime: u32) -> GzBuilder {
134         self.mtime = mtime;
135         self
136     }
137 
138     /// Configure the `operating_system` field in the gzip header.
operating_system(mut self, os: u8) -> GzBuilder139     pub fn operating_system(mut self, os: u8) -> GzBuilder {
140         self.operating_system = Some(os);
141         self
142     }
143 
144     /// Configure the `extra` field in the gzip header.
extra<T: Into<Vec<u8>>>(mut self, extra: T) -> GzBuilder145     pub fn extra<T: Into<Vec<u8>>>(mut self, extra: T) -> GzBuilder {
146         self.extra = Some(extra.into());
147         self
148     }
149 
150     /// Configure the `filename` field in the gzip header.
151     ///
152     /// # Panics
153     ///
154     /// Panics if the `filename` slice contains a zero.
filename<T: Into<Vec<u8>>>(mut self, filename: T) -> GzBuilder155     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     /// Configure the `comment` field in the gzip header.
161     ///
162     /// # Panics
163     ///
164     /// Panics if the `comment` slice contains a zero.
comment<T: Into<Vec<u8>>>(mut self, comment: T) -> GzBuilder165     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     /// Consume this builder, creating a writer encoder in the process.
171     ///
172     /// The data written to the returned encoder will be compressed and then
173     /// written out to the supplied parameter `w`.
write<W: Write>(self, w: W, lvl: Compression) -> write::GzEncoder<W>174     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     /// Consume this builder, creating a reader encoder in the process.
179     ///
180     /// Data read from the returned encoder will be the compressed version of
181     /// the data read from the given reader.
read<R: Read>(self, r: R, lvl: Compression) -> read::GzEncoder<R>182     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     /// Consume this builder, creating a reader encoder in the process.
187     ///
188     /// Data read from the returned encoder will be the compressed version of
189     /// the data read from the given reader.
buf_read<R>(self, r: R, lvl: Compression) -> bufread::GzEncoder<R> where R: BufRead,190     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 
into_header(self, lvl: Compression) -> Vec<u8>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         // Typically this byte indicates what OS the gz stream was created on,
247         // but in an effort to have cross-platform reproducible streams just
248         // default this value to 255. I'm not sure that if we "correctly" set
249         // this it'd do anything anyway...
250         header[9] = operating_system.unwrap_or(255);
251         return header;
252     }
253 }
254 
255 #[cfg(test)]
256 mod 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]
roundtrip()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]
roundtrip_zero()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]
roundtrip_big()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]
roundtrip_big2()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]
fields()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]
keep_reading_after_end()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]
qc_reader()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]
flush_after_write()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 }
360