Skip to content

Commit f6d6ab9

Browse files
Merge pull request #131 from CosmicHorrorDev/self-extracting
add a self-extracting zip example
2 parents 9a1c117 + 5fd95a6 commit f6d6ab9

File tree

2 files changed

+106
-0
lines changed

2 files changed

+106
-0
lines changed
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
use std::{
2+
fs::{self, File},
3+
io::{self, Read},
4+
path::Path,
5+
};
6+
7+
use rc_zip::{
8+
error::{Error, FormatError},
9+
parse::EntryKind,
10+
};
11+
use rc_zip_sync::{ArchiveHandle, ReadZip};
12+
13+
/// The executable side of a self-extracting zip file
14+
///
15+
/// A program starts from the front and will still behave the same if you tack anything on the end.
16+
/// The same is true for trailing zip files, but from the opposite direction. This means that you
17+
/// can create a self-extracting zip file by throwing a zip file on the end of a program that
18+
/// extracts itself as a zip file. It is both a valid executable, and a valid zip file at the same
19+
/// time
20+
///
21+
/// 1. build the executable
22+
/// - `$ cargo build --release --example=self_extracting`
23+
/// 2. combine it with a zip file to make a `self_extracting.zip` file
24+
/// - `$ cat target/release/examples/self_extracting path/to/some.zip > self_extracting.zip`
25+
/// 3. make it executable as well
26+
/// - `$ chmod +x self_extracting.zip`
27+
/// 4. now the zip file can extract itself!
28+
/// - `$ zipinfo self_extracting.zip # still detects the zip file`
29+
/// - `$ ./self_extracting.zip`
30+
fn main() -> Result<(), Error> {
31+
let zip_path = std::env::args_os().next().unwrap();
32+
let zip_file = File::open(&zip_path)?;
33+
let archive = zip_file.read_zip().inspect_err(|err| {
34+
if let Error::Format(FormatError::DirectoryEndSignatureNotFound) = err {
35+
eprintln!("hint: did you forget to append a zip file?");
36+
}
37+
})?;
38+
39+
extract(&archive)?;
40+
41+
Ok(())
42+
}
43+
44+
fn extract(archive: &ArchiveHandle<'_, File>) -> Result<(), Error> {
45+
for entry in archive.entries() {
46+
println!("extracting {}", entry.name);
47+
let Some(entry_name) = entry.sanitized_name() else {
48+
eprintln!("ignoring potentially malicious entry");
49+
continue;
50+
};
51+
52+
let path = Path::new(entry_name);
53+
if let Some(parent) = path.parent() {
54+
fs::create_dir_all(parent)?;
55+
}
56+
match entry.kind() {
57+
EntryKind::Directory => fs::create_dir_all(path)?,
58+
EntryKind::File => {
59+
let mut entry_writer = File::create(path)?;
60+
let mut entry_reader = entry.reader();
61+
io::copy(&mut entry_reader, &mut entry_writer)?;
62+
}
63+
EntryKind::Symlink => {
64+
#[cfg(windows)]
65+
{
66+
// creating a symlink on windows is a privileged action, so instead we create a
67+
// regular file
68+
let mut entry_writer = File::create(path)?;
69+
let mut entry_reader = entry.reader();
70+
io::copy(&mut entry_reader, &mut entry_writer)?;
71+
}
72+
#[cfg(unix)]
73+
{
74+
use std::ffi::OsString;
75+
use std::os::unix::ffi::OsStringExt;
76+
77+
if let Ok(metadata) = fs::symlink_metadata(&path) {
78+
if metadata.is_file() {
79+
fs::remove_file(&path)?;
80+
}
81+
}
82+
83+
let mut src = Vec::new();
84+
entry.reader().read_to_end(&mut src)?;
85+
let src = OsString::from_vec(src);
86+
87+
std::os::unix::fs::symlink(&src, &path)?;
88+
}
89+
#[cfg(not(any(windows, unix)))]
90+
{
91+
eprintln!("ignoring symlink on unsupported platform");
92+
}
93+
}
94+
}
95+
}
96+
97+
Ok(())
98+
}

rc-zip/src/corpus/mod.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,14 @@ pub fn test_cases() -> Vec<Case> {
215215
}]),
216216
..Default::default()
217217
},
218+
Case {
219+
name: "readme.trailingzip",
220+
files: Files::ExhaustiveList(vec![CaseFile {
221+
name: "README",
222+
..Default::default()
223+
}]),
224+
..Default::default()
225+
},
218226
#[cfg(feature = "lzma")]
219227
Case {
220228
name: "found-me-lzma.zip",

0 commit comments

Comments
 (0)