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