Skip to content

Commit e124d04

Browse files
committed
test: Fix: new tests don't work with zopfli and no flate2
1 parent 18b8948 commit e124d04

File tree

2 files changed

+171
-148
lines changed

2 files changed

+171
-148
lines changed

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,8 @@ The features available are:
3939
* `deflate-flate2`: Combine this with any `flate2` feature flag that enables a back-end, to support deflate compression
4040
at quality 1..=9.
4141
* `deflate-zopfli`: Enables deflating files with the `zopfli` library (used when compression quality is 10..=264). This
42-
is the most effective `deflate` implementation available, but also among the slowest.
42+
is the most effective `deflate` implementation available, but also among the slowest. If `flate2` isn't also enabled,
43+
only compression will be supported and not decompression.
4344
* `deflate64`: Enables the deflate64 compression algorithm. Only decompression is supported.
4445
* `lzma`: Enables the LZMA compression algorithm. Only decompression is supported.
4546
* `bzip2`: Enables the BZip2 compression algorithm.

tests/invalid_path.rs

Lines changed: 169 additions & 147 deletions
Original file line numberDiff line numberDiff line change
@@ -1,158 +1,180 @@
1-
use std::io::Write;
2-
use zip::write::SimpleFileOptions;
3-
use zip::{ZipArchive, ZipWriter};
4-
5-
/// Create a ZIP file with entries that have absolute paths (starting with /)
6-
/// This simulates the problematic ZIP file mentioned in the bug report
7-
fn create_zip_with_absolute_paths() -> Vec<u8> {
8-
let buf = Vec::new();
9-
let mut writer = ZipWriter::new(std::io::Cursor::new(buf));
10-
let options = SimpleFileOptions::default();
11-
12-
// Create entries with absolute paths - this should cause "Invalid file path" error
13-
writer.add_directory("/_/", options).unwrap();
14-
writer.start_file("/_/file1.txt", options).unwrap();
15-
writer.write_all(b"File 1 content").unwrap();
16-
writer.start_file("/_/subdir/file2.txt", options).unwrap();
17-
writer.write_all(b"File 2 content").unwrap();
18-
19-
writer.finish().unwrap().into_inner()
20-
}
1+
#[cfg(all(
2+
test,
3+
not(all(feature = "deflate-zopfli", not(feature = "deflate-flate2")))
4+
))]
5+
pub mod tests {
6+
use std::io::Write;
7+
use zip::write::SimpleFileOptions;
8+
use zip::{ZipArchive, ZipWriter};
9+
10+
/// Create a ZIP file with entries that have absolute paths (starting with /)
11+
/// This simulates the problematic ZIP file mentioned in the bug report
12+
fn create_zip_with_absolute_paths() -> Vec<u8> {
13+
let buf = Vec::new();
14+
let mut writer = ZipWriter::new(std::io::Cursor::new(buf));
15+
let options = SimpleFileOptions::default();
16+
17+
// Create entries with absolute paths - this should cause "Invalid file path" error
18+
writer.add_directory("/_/", options).unwrap();
19+
writer.start_file("/_/file1.txt", options).unwrap();
20+
writer.write_all(b"File 1 content").unwrap();
21+
writer.start_file("/_/subdir/file2.txt", options).unwrap();
22+
writer.write_all(b"File 2 content").unwrap();
23+
24+
writer.finish().unwrap().into_inner()
25+
}
2126

22-
/// Create a ZIP file with entries that have Windows-style absolute paths
23-
fn create_zip_with_windows_absolute_paths() -> Vec<u8> {
24-
let buf = Vec::new();
25-
let mut writer = ZipWriter::new(std::io::Cursor::new(buf));
26-
let options = SimpleFileOptions::default();
27+
/// Create a ZIP file with entries that have Windows-style absolute paths
28+
fn create_zip_with_windows_absolute_paths() -> Vec<u8> {
29+
let buf = Vec::new();
30+
let mut writer = ZipWriter::new(std::io::Cursor::new(buf));
31+
let options = SimpleFileOptions::default();
2732

28-
// Create entries with Windows absolute paths
29-
writer.add_directory("C:\\temp\\", options).unwrap();
30-
writer.start_file("C:\\temp\\file1.txt", options).unwrap();
31-
writer.write_all(b"File 1 content").unwrap();
33+
// Create entries with Windows absolute paths
34+
writer.add_directory("C:\\temp\\", options).unwrap();
35+
writer.start_file("C:\\temp\\file1.txt", options).unwrap();
36+
writer.write_all(b"File 1 content").unwrap();
3237

33-
writer.finish().unwrap().into_inner()
34-
}
38+
writer.finish().unwrap().into_inner()
39+
}
3540

36-
/// Create a ZIP file that more closely simulates the soldeer registry issue
37-
/// with an underscore directory at the root with absolute path
38-
fn create_zip_like_soldeer_issue() -> Vec<u8> {
39-
let buf = Vec::new();
40-
let mut writer = ZipWriter::new(std::io::Cursor::new(buf));
41-
let options = SimpleFileOptions::default();
42-
43-
// Simulate the soldeer registry structure with absolute paths
44-
writer.add_directory("/_/", options).unwrap();
45-
writer.add_directory("/_/forge-std/", options).unwrap();
46-
writer.start_file("/_/forge-std/src/Test.sol", options).unwrap();
47-
writer.write_all(b"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n").unwrap();
48-
writer.start_file("/_/forge-std/lib/ds-test/src/test.sol", options).unwrap();
49-
writer.write_all(b"// Test contract\n").unwrap();
50-
51-
writer.finish().unwrap().into_inner()
52-
}
41+
/// Create a ZIP file that more closely simulates the soldeer registry issue
42+
/// with an underscore directory at the root with absolute path
43+
fn create_zip_like_soldeer_issue() -> Vec<u8> {
44+
let buf = Vec::new();
45+
let mut writer = ZipWriter::new(std::io::Cursor::new(buf));
46+
let options = SimpleFileOptions::default();
47+
48+
// Simulate the soldeer registry structure with absolute paths
49+
writer.add_directory("/_/", options).unwrap();
50+
writer.add_directory("/_/forge-std/", options).unwrap();
51+
writer
52+
.start_file("/_/forge-std/src/Test.sol", options)
53+
.unwrap();
54+
writer
55+
.write_all(b"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n")
56+
.unwrap();
57+
writer
58+
.start_file("/_/forge-std/lib/ds-test/src/test.sol", options)
59+
.unwrap();
60+
writer.write_all(b"// Test contract\n").unwrap();
61+
62+
writer.finish().unwrap().into_inner()
63+
}
5364

54-
#[test]
55-
fn test_extract_zip_with_absolute_paths() {
56-
let zip_data = create_zip_with_absolute_paths();
57-
let mut archive = ZipArchive::new(std::io::Cursor::new(zip_data)).unwrap();
58-
59-
// After fix: should extract successfully, stripping the leading /
60-
let temp_dir = tempfile::TempDir::new().unwrap();
61-
archive.extract(temp_dir.path()).unwrap();
62-
63-
// Files should be extracted with the absolute path prefix stripped
64-
assert!(temp_dir.path().join("_").exists());
65-
assert!(temp_dir.path().join("_/file1.txt").exists());
66-
assert!(temp_dir.path().join("_/subdir/file2.txt").exists());
67-
68-
// Verify file contents
69-
let content = std::fs::read_to_string(temp_dir.path().join("_/file1.txt")).unwrap();
70-
assert_eq!(content, "File 1 content");
71-
}
65+
#[test]
66+
fn test_extract_zip_with_absolute_paths() {
67+
let zip_data = create_zip_with_absolute_paths();
68+
let mut archive = ZipArchive::new(std::io::Cursor::new(zip_data)).unwrap();
7269

73-
#[test]
74-
fn test_extract_zip_with_windows_absolute_paths() {
75-
let zip_data = create_zip_with_windows_absolute_paths();
76-
let mut archive = ZipArchive::new(std::io::Cursor::new(zip_data)).unwrap();
77-
78-
// After fix: should extract successfully, stripping the C:\ prefix
79-
let temp_dir = tempfile::TempDir::new().unwrap();
80-
archive.extract(temp_dir.path()).unwrap();
81-
82-
// Files should be extracted with the Windows absolute path prefix stripped
83-
assert!(temp_dir.path().join("temp").exists());
84-
assert!(temp_dir.path().join("temp/file1.txt").exists());
85-
86-
// Verify file contents
87-
let content = std::fs::read_to_string(temp_dir.path().join("temp/file1.txt")).unwrap();
88-
assert_eq!(content, "File 1 content");
89-
}
70+
// After fix: should extract successfully, stripping the leading /
71+
let temp_dir = tempfile::TempDir::new().unwrap();
72+
archive.extract(temp_dir.path()).unwrap();
9073

91-
#[test]
92-
fn test_extract_soldeer_like_zip() {
93-
let zip_data = create_zip_like_soldeer_issue();
94-
let mut archive = ZipArchive::new(std::io::Cursor::new(zip_data)).unwrap();
95-
96-
// This should now work without "Invalid file path" error
97-
let temp_dir = tempfile::TempDir::new().unwrap();
98-
archive.extract(temp_dir.path()).unwrap();
99-
100-
// Verify the structure is extracted correctly with absolute prefix stripped
101-
assert!(temp_dir.path().join("_").exists());
102-
assert!(temp_dir.path().join("_/forge-std").exists());
103-
assert!(temp_dir.path().join("_/forge-std/src/Test.sol").exists());
104-
assert!(temp_dir.path().join("_/forge-std/lib/ds-test/src/test.sol").exists());
105-
106-
// Verify file contents
107-
let content = std::fs::read_to_string(temp_dir.path().join("_/forge-std/src/Test.sol")).unwrap();
108-
assert!(content.contains("SPDX-License-Identifier"));
109-
}
74+
// Files should be extracted with the absolute path prefix stripped
75+
assert!(temp_dir.path().join("_").exists());
76+
assert!(temp_dir.path().join("_/file1.txt").exists());
77+
assert!(temp_dir.path().join("_/subdir/file2.txt").exists());
11078

111-
#[test]
112-
fn test_individual_file_access_with_absolute_paths() {
113-
let zip_data = create_zip_with_absolute_paths();
114-
let mut archive = ZipArchive::new(std::io::Cursor::new(zip_data)).unwrap();
115-
116-
// Test accessing individual files
117-
for i in 0..archive.len() {
118-
let file = archive.by_index(i).unwrap();
119-
println!("File name: {}", file.name());
120-
121-
// After our fix, enclosed_name should return a safe relative path
122-
let enclosed_name = file.enclosed_name();
123-
println!("Enclosed name: {:?}", enclosed_name);
124-
125-
// Should now return Some with the absolute prefix stripped
126-
assert!(enclosed_name.is_some());
127-
let path = enclosed_name.unwrap();
128-
129-
// Verify the path doesn't start with / or contain absolute components
130-
assert!(!path.is_absolute());
131-
assert!(!path.to_string_lossy().starts_with('/'));
79+
// Verify file contents
80+
let content = std::fs::read_to_string(temp_dir.path().join("_/file1.txt")).unwrap();
81+
assert_eq!(content, "File 1 content");
13282
}
133-
}
13483

135-
#[test]
136-
fn test_security_still_prevents_directory_traversal() {
137-
let buf = Vec::new();
138-
let mut writer = ZipWriter::new(std::io::Cursor::new(buf));
139-
let options = SimpleFileOptions::default();
140-
141-
// Create a ZIP with directory traversal attempts
142-
writer.start_file("../../../etc/passwd", options).unwrap();
143-
writer.write_all(b"malicious content").unwrap();
144-
writer.start_file("foo/../../../etc/shadow", options).unwrap();
145-
writer.write_all(b"more malicious content").unwrap();
146-
147-
let zip_data = writer.finish().unwrap().into_inner();
148-
let mut archive = ZipArchive::new(std::io::Cursor::new(zip_data)).unwrap();
149-
150-
// These should still fail due to directory traversal protection
151-
for i in 0..archive.len() {
152-
let file = archive.by_index(i).unwrap();
153-
let enclosed_name = file.enclosed_name();
154-
155-
// Directory traversal attempts should still return None
156-
assert!(enclosed_name.is_none(), "Directory traversal should still be blocked for: {}", file.name());
84+
#[test]
85+
fn test_extract_zip_with_windows_absolute_paths() {
86+
let zip_data = create_zip_with_windows_absolute_paths();
87+
let mut archive = ZipArchive::new(std::io::Cursor::new(zip_data)).unwrap();
88+
89+
// After fix: should extract successfully, stripping the C:\ prefix
90+
let temp_dir = tempfile::TempDir::new().unwrap();
91+
archive.extract(temp_dir.path()).unwrap();
92+
93+
// Files should be extracted with the Windows absolute path prefix stripped
94+
assert!(temp_dir.path().join("temp").exists());
95+
assert!(temp_dir.path().join("temp/file1.txt").exists());
96+
97+
// Verify file contents
98+
let content = std::fs::read_to_string(temp_dir.path().join("temp/file1.txt")).unwrap();
99+
assert_eq!(content, "File 1 content");
100+
}
101+
102+
#[test]
103+
fn test_extract_soldeer_like_zip() {
104+
let zip_data = create_zip_like_soldeer_issue();
105+
let mut archive = ZipArchive::new(std::io::Cursor::new(zip_data)).unwrap();
106+
107+
// This should now work without "Invalid file path" error
108+
let temp_dir = tempfile::TempDir::new().unwrap();
109+
archive.extract(temp_dir.path()).unwrap();
110+
111+
// Verify the structure is extracted correctly with absolute prefix stripped
112+
assert!(temp_dir.path().join("_").exists());
113+
assert!(temp_dir.path().join("_/forge-std").exists());
114+
assert!(temp_dir.path().join("_/forge-std/src/Test.sol").exists());
115+
assert!(temp_dir
116+
.path()
117+
.join("_/forge-std/lib/ds-test/src/test.sol")
118+
.exists());
119+
120+
// Verify file contents
121+
let content =
122+
std::fs::read_to_string(temp_dir.path().join("_/forge-std/src/Test.sol")).unwrap();
123+
assert!(content.contains("SPDX-License-Identifier"));
124+
}
125+
126+
#[test]
127+
fn test_individual_file_access_with_absolute_paths() {
128+
let zip_data = create_zip_with_absolute_paths();
129+
let mut archive = ZipArchive::new(std::io::Cursor::new(zip_data)).unwrap();
130+
131+
// Test accessing individual files
132+
for i in 0..archive.len() {
133+
let file = archive.by_index(i).unwrap();
134+
println!("File name: {}", file.name());
135+
136+
// After our fix, enclosed_name should return a safe relative path
137+
let enclosed_name = file.enclosed_name();
138+
println!("Enclosed name: {:?}", enclosed_name);
139+
140+
// Should now return Some with the absolute prefix stripped
141+
assert!(enclosed_name.is_some());
142+
let path = enclosed_name.unwrap();
143+
144+
// Verify the path doesn't start with / or contain absolute components
145+
assert!(!path.is_absolute());
146+
assert!(!path.to_string_lossy().starts_with('/'));
147+
}
157148
}
158-
}
149+
150+
#[test]
151+
fn test_security_still_prevents_directory_traversal() {
152+
let buf = Vec::new();
153+
let mut writer = ZipWriter::new(std::io::Cursor::new(buf));
154+
let options = SimpleFileOptions::default();
155+
156+
// Create a ZIP with directory traversal attempts
157+
writer.start_file("../../../etc/passwd", options).unwrap();
158+
writer.write_all(b"malicious content").unwrap();
159+
writer
160+
.start_file("foo/../../../etc/shadow", options)
161+
.unwrap();
162+
writer.write_all(b"more malicious content").unwrap();
163+
164+
let zip_data = writer.finish().unwrap().into_inner();
165+
let mut archive = ZipArchive::new(std::io::Cursor::new(zip_data)).unwrap();
166+
167+
// These should still fail due to directory traversal protection
168+
for i in 0..archive.len() {
169+
let file = archive.by_index(i).unwrap();
170+
let enclosed_name = file.enclosed_name();
171+
172+
// Directory traversal attempts should still return None
173+
assert!(
174+
enclosed_name.is_none(),
175+
"Directory traversal should still be blocked for: {}",
176+
file.name()
177+
);
178+
}
179+
}
180+
}

0 commit comments

Comments
 (0)