1 use byteorder::{LittleEndian, WriteBytesExt};
2 use std::collections::HashSet;
3 use std::io::prelude::*;
4 use std::io::{Cursor, Seek};
5 use std::iter::FromIterator;
6 use zip::write::FileOptions;
7 use zip::{CompressionMethod, SUPPORTED_COMPRESSION_METHODS};
8 
9 // This test asserts that after creating a zip file, then reading its contents back out,
10 // the extracted data will *always* be exactly the same as the original data.
11 #[test]
end_to_end()12 fn end_to_end() {
13     for &method in SUPPORTED_COMPRESSION_METHODS {
14         let file = &mut Cursor::new(Vec::new());
15 
16         println!("Writing file with {method} compression");
17         write_test_archive(file, method).expect("Couldn't write test zip archive");
18 
19         println!("Checking file contents");
20         check_archive_file(file, ENTRY_NAME, Some(method), LOREM_IPSUM);
21     }
22 }
23 
24 // This test asserts that after copying a `ZipFile` to a new `ZipWriter`, then reading its
25 // contents back out, the extracted data will *always* be exactly the same as the original data.
26 #[test]
copy()27 fn copy() {
28     for &method in SUPPORTED_COMPRESSION_METHODS {
29         let src_file = &mut Cursor::new(Vec::new());
30         write_test_archive(src_file, method).expect("Couldn't write to test file");
31 
32         let mut tgt_file = &mut Cursor::new(Vec::new());
33 
34         {
35             let mut src_archive = zip::ZipArchive::new(src_file).unwrap();
36             let mut zip = zip::ZipWriter::new(&mut tgt_file);
37 
38             {
39                 let file = src_archive
40                     .by_name(ENTRY_NAME)
41                     .expect("Missing expected file");
42 
43                 zip.raw_copy_file(file).expect("Couldn't copy file");
44             }
45 
46             {
47                 let file = src_archive
48                     .by_name(ENTRY_NAME)
49                     .expect("Missing expected file");
50 
51                 zip.raw_copy_file_rename(file, COPY_ENTRY_NAME)
52                     .expect("Couldn't copy and rename file");
53             }
54         }
55 
56         let mut tgt_archive = zip::ZipArchive::new(tgt_file).unwrap();
57 
58         check_archive_file_contents(&mut tgt_archive, ENTRY_NAME, LOREM_IPSUM);
59         check_archive_file_contents(&mut tgt_archive, COPY_ENTRY_NAME, LOREM_IPSUM);
60     }
61 }
62 
63 // This test asserts that after appending to a `ZipWriter`, then reading its contents back out,
64 // both the prior data and the appended data will be exactly the same as their originals.
65 #[test]
append()66 fn append() {
67     for &method in SUPPORTED_COMPRESSION_METHODS {
68         let mut file = &mut Cursor::new(Vec::new());
69         write_test_archive(file, method).expect("Couldn't write to test file");
70 
71         {
72             let mut zip = zip::ZipWriter::new_append(&mut file).unwrap();
73             zip.start_file(
74                 COPY_ENTRY_NAME,
75                 FileOptions::default().compression_method(method),
76             )
77             .unwrap();
78             zip.write_all(LOREM_IPSUM).unwrap();
79             zip.finish().unwrap();
80         }
81 
82         let mut zip = zip::ZipArchive::new(&mut file).unwrap();
83         check_archive_file_contents(&mut zip, ENTRY_NAME, LOREM_IPSUM);
84         check_archive_file_contents(&mut zip, COPY_ENTRY_NAME, LOREM_IPSUM);
85     }
86 }
87 
88 // Write a test zip archive to buffer.
write_test_archive( file: &mut Cursor<Vec<u8>>, method: CompressionMethod, ) -> zip::result::ZipResult<()>89 fn write_test_archive(
90     file: &mut Cursor<Vec<u8>>,
91     method: CompressionMethod,
92 ) -> zip::result::ZipResult<()> {
93     let mut zip = zip::ZipWriter::new(file);
94 
95     zip.add_directory("test/", Default::default())?;
96 
97     let options = FileOptions::default()
98         .compression_method(method)
99         .unix_permissions(0o755);
100 
101     zip.start_file("test/☃.txt", options)?;
102     zip.write_all(b"Hello, World!\n")?;
103 
104     zip.start_file_with_extra_data("test_with_extra_data/��.txt", options)?;
105     zip.write_u16::<LittleEndian>(0xbeef)?;
106     zip.write_u16::<LittleEndian>(EXTRA_DATA.len() as u16)?;
107     zip.write_all(EXTRA_DATA)?;
108     zip.end_extra_data()?;
109     zip.write_all(b"Hello, World! Again.\n")?;
110 
111     zip.start_file(ENTRY_NAME, options)?;
112     zip.write_all(LOREM_IPSUM)?;
113 
114     zip.finish()?;
115     Ok(())
116 }
117 
118 // Load an archive from buffer and check for test data.
check_test_archive<R: Read + Seek>(zip_file: R) -> zip::result::ZipResult<zip::ZipArchive<R>>119 fn check_test_archive<R: Read + Seek>(zip_file: R) -> zip::result::ZipResult<zip::ZipArchive<R>> {
120     let mut archive = zip::ZipArchive::new(zip_file).unwrap();
121 
122     // Check archive contains expected file names.
123     {
124         let expected_file_names = [
125             "test/",
126             "test/☃.txt",
127             "test_with_extra_data/��.txt",
128             ENTRY_NAME,
129         ];
130         let expected_file_names = HashSet::from_iter(expected_file_names.iter().copied());
131         let file_names = archive.file_names().collect::<HashSet<_>>();
132         assert_eq!(file_names, expected_file_names);
133     }
134 
135     // Check an archive file for extra data field contents.
136     {
137         let file_with_extra_data = archive.by_name("test_with_extra_data/��.txt")?;
138         let mut extra_data = Vec::new();
139         extra_data.write_u16::<LittleEndian>(0xbeef)?;
140         extra_data.write_u16::<LittleEndian>(EXTRA_DATA.len() as u16)?;
141         extra_data.write_all(EXTRA_DATA)?;
142         assert_eq!(file_with_extra_data.extra_data(), extra_data.as_slice());
143     }
144 
145     Ok(archive)
146 }
147 
148 // Read a file in the archive as a string.
read_archive_file<R: Read + Seek>( archive: &mut zip::ZipArchive<R>, name: &str, ) -> zip::result::ZipResult<String>149 fn read_archive_file<R: Read + Seek>(
150     archive: &mut zip::ZipArchive<R>,
151     name: &str,
152 ) -> zip::result::ZipResult<String> {
153     let mut file = archive.by_name(name)?;
154 
155     let mut contents = String::new();
156     file.read_to_string(&mut contents).unwrap();
157 
158     Ok(contents)
159 }
160 
161 // Check a file in the archive contains expected data and properties.
check_archive_file( zip_file: &mut Cursor<Vec<u8>>, name: &str, expected_method: Option<CompressionMethod>, expected_data: &[u8], )162 fn check_archive_file(
163     zip_file: &mut Cursor<Vec<u8>>,
164     name: &str,
165     expected_method: Option<CompressionMethod>,
166     expected_data: &[u8],
167 ) {
168     let mut archive = check_test_archive(zip_file).unwrap();
169 
170     if let Some(expected_method) = expected_method {
171         // Check the file's compression method.
172         let file = archive.by_name(name).unwrap();
173         let real_method = file.compression();
174 
175         assert_eq!(
176             expected_method, real_method,
177             "File does not have expected compression method"
178         );
179     }
180 
181     check_archive_file_contents(&mut archive, name, expected_data);
182 }
183 
184 // Check a file in the archive contains the given data.
check_archive_file_contents<R: Read + Seek>( archive: &mut zip::ZipArchive<R>, name: &str, expected: &[u8], )185 fn check_archive_file_contents<R: Read + Seek>(
186     archive: &mut zip::ZipArchive<R>,
187     name: &str,
188     expected: &[u8],
189 ) {
190     let file_contents: String = read_archive_file(archive, name).unwrap();
191     assert_eq!(file_contents.as_bytes(), expected);
192 }
193 
194 const LOREM_IPSUM : &[u8] = b"Lorem ipsum dolor sit amet, consectetur adipiscing elit. In tellus elit, tristique vitae mattis egestas, ultricies vitae risus. Quisque sit amet quam ut urna aliquet
195 molestie. Proin blandit ornare dui, a tempor nisl accumsan in. Praesent a consequat felis. Morbi metus diam, auctor in auctor vel, feugiat id odio. Curabitur ex ex,
196 dictum quis auctor quis, suscipit id lorem. Aliquam vestibulum dolor nec enim vehicula, porta tristique augue tincidunt. Vivamus ut gravida est. Sed pellentesque, dolor
197 vitae tristique consectetur, neque lectus pulvinar dui, sed feugiat purus diam id lectus. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per
198 inceptos himenaeos. Maecenas feugiat velit in ex ultrices scelerisque id id neque.
199 ";
200 
201 const EXTRA_DATA: &[u8] = b"Extra Data";
202 
203 const ENTRY_NAME: &str = "test/lorem_ipsum.txt";
204 
205 const COPY_ENTRY_NAME: &str = "test/lorem_ipsum_renamed.txt";
206