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