use flate2::read::GzDecoder; use flate2::{Compression, GzBuilder}; use std::io::{Read, Write}; /// Gzip-compress data with reproducible output (mtime zeroed). /// /// Uses `GzBuilder::new().mtime(0)` to zero the gzip timestamp, /// ensuring reproducible compressed output for testing. pub fn compress(data: &[u8]) -> anyhow::Result> { let mut encoder = GzBuilder::new() .mtime(0) .write(Vec::new(), Compression::default()); encoder.write_all(data)?; let compressed = encoder.finish()?; Ok(compressed) } /// Gzip-decompress data. pub fn decompress(data: &[u8]) -> anyhow::Result> { let mut decoder = GzDecoder::new(data); let mut decompressed = Vec::new(); decoder.read_to_end(&mut decompressed)?; Ok(decompressed) } /// Determine if a file should be compressed based on filename and exclusion list. /// /// Returns false for: /// - Files matching any entry in `no_compress_list` (by suffix or exact match) /// - Files with known compressed extensions (apk, zip, gz, etc.) /// /// Returns true otherwise. pub fn should_compress(filename: &str, no_compress_list: &[String]) -> bool { // Check explicit exclusion list if no_compress_list .iter() .any(|nc| filename.ends_with(nc) || filename == nc) { return false; } // Check known compressed extensions let ext = filename.rsplit('.').next().unwrap_or("").to_lowercase(); !matches!( ext.as_str(), "apk" | "zip" | "gz" | "bz2" | "xz" | "zst" | "png" | "jpg" | "jpeg" | "gif" | "webp" | "mp4" | "mp3" | "aac" | "ogg" | "flac" | "7z" | "rar" | "jar" ) } #[cfg(test)] mod tests { use super::*; #[test] fn test_compress_decompress_roundtrip() { let data = b"Hello, World! This is test data for compression."; let compressed = compress(data).unwrap(); let decompressed = decompress(&compressed).unwrap(); assert_eq!(decompressed, data); } #[test] fn test_compress_decompress_empty() { let data = b""; let compressed = compress(data).unwrap(); let decompressed = decompress(&compressed).unwrap(); assert_eq!(decompressed, data.as_slice()); } #[test] fn test_compress_decompress_large() { // 10000 bytes of pattern data let data: Vec = (0..10000).map(|i| (i % 256) as u8).collect(); let compressed = compress(&data).unwrap(); let decompressed = decompress(&compressed).unwrap(); assert_eq!(decompressed, data); } #[test] fn test_should_compress_text() { assert!(should_compress("readme.txt", &[])); assert!(should_compress("data.json", &[])); } #[test] fn test_should_not_compress_known_extensions() { assert!(!should_compress("app.apk", &[])); assert!(!should_compress("photo.jpg", &[])); assert!(!should_compress("archive.zip", &[])); } #[test] fn test_should_not_compress_excluded() { assert!(!should_compress("special.dat", &["special.dat".into()])); } }