1use super::errors::{wrap, WrappedError};
6use super::io::ReportCopyProgress;
7use std::fs::{self, File};
8use std::io;
9use std::path::Path;
10use std::path::PathBuf;
11use zip::read::ZipFile;
12use zip::{self, ZipArchive};
13
14fn should_skip_first_segment(archive: &mut ZipArchive<File>) -> bool {
19 let first_name = {
20 let file = archive
21 .by_index_raw(0)
22 .expect("expected not to have an empty archive");
23
24 let path = file
25 .enclosed_name()
26 .expect("expected to have path")
27 .iter()
28 .next()
29 .expect("expected to have non-empty name");
30
31 path.to_owned()
32 };
33
34 for i in 1..archive.len() {
35 if let Ok(file) = archive.by_index_raw(i) {
36 if let Some(name) = file.enclosed_name() {
37 if name.iter().next() != Some(&first_name) {
38 return false;
39 }
40 }
41 }
42 }
43
44 archive.len() > 1 }
46
47pub fn unzip_file<T>(path: &Path, parent_path: &Path, mut reporter: T) -> Result<(), WrappedError>
48where
49 T: ReportCopyProgress,
50{
51 let file = fs::File::open(path)
52 .map_err(|e| wrap(e, format!("unable to open file {}", path.display())))?;
53
54 let mut archive = zip::ZipArchive::new(file)
55 .map_err(|e| wrap(e, format!("failed to open zip archive {}", path.display())))?;
56
57 let skip_segments_no = usize::from(should_skip_first_segment(&mut archive));
58 for i in 0..archive.len() {
59 reporter.report_progress(i as u64, archive.len() as u64);
60 let mut file = archive
61 .by_index(i)
62 .map_err(|e| wrap(e, format!("could not open zip entry {}", i)))?;
63
64 let outpath: PathBuf = match file.enclosed_name() {
65 Some(path) => {
66 let mut full_path = PathBuf::from(parent_path);
67 full_path.push(PathBuf::from_iter(path.iter().skip(skip_segments_no)));
68 full_path
69 }
70 None => continue,
71 };
72
73 if file.is_dir() || file.name().ends_with('/') {
74 fs::create_dir_all(&outpath)
75 .map_err(|e| wrap(e, format!("could not create dir for {}", outpath.display())))?;
76 apply_permissions(&file, &outpath)?;
77 continue;
78 }
79
80 if let Some(p) = outpath.parent() {
81 fs::create_dir_all(p)
82 .map_err(|e| wrap(e, format!("could not create dir for {}", outpath.display())))?;
83 }
84
85 #[cfg(unix)]
86 {
87 use libc::S_IFLNK;
88 use std::io::Read;
89 use std::os::unix::ffi::OsStringExt;
90
91 #[cfg(target_os = "macos")]
92 const S_IFLINK_32: u32 = S_IFLNK as u32;
93
94 #[cfg(target_os = "linux")]
95 const S_IFLINK_32: u32 = S_IFLNK;
96
97 if matches!(file.unix_mode(), Some(mode) if mode & S_IFLINK_32 == S_IFLINK_32) {
98 let mut link_to = Vec::new();
99 file.read_to_end(&mut link_to).map_err(|e| {
100 wrap(
101 e,
102 format!("could not read symlink linkpath {}", outpath.display()),
103 )
104 })?;
105
106 let link_path = PathBuf::from(std::ffi::OsString::from_vec(link_to));
107 std::os::unix::fs::symlink(link_path, &outpath).map_err(|e| {
108 wrap(e, format!("could not create symlink {}", outpath.display()))
109 })?;
110 continue;
111 }
112 }
113
114 let mut outfile = fs::File::create(&outpath).map_err(|e| {
115 wrap(
116 e,
117 format!(
118 "unable to open file to write {} (from {:?})",
119 outpath.display(),
120 file.enclosed_name().map(|p| p.to_string_lossy()),
121 ),
122 )
123 })?;
124
125 io::copy(&mut file, &mut outfile)
126 .map_err(|e| wrap(e, format!("error copying file {}", outpath.display())))?;
127
128 apply_permissions(&file, &outpath)?;
129 }
130
131 reporter.report_progress(archive.len() as u64, archive.len() as u64);
132
133 Ok(())
134}
135
136#[cfg(unix)]
137fn apply_permissions(file: &ZipFile, outpath: &Path) -> Result<(), WrappedError> {
138 use std::os::unix::fs::PermissionsExt;
139
140 if let Some(mode) = file.unix_mode() {
141 fs::set_permissions(outpath, fs::Permissions::from_mode(mode)).map_err(|e| {
142 wrap(
143 e,
144 format!("error setting permissions on {}", outpath.display()),
145 )
146 })?;
147 }
148
149 Ok(())
150}
151
152#[cfg(windows)]
153fn apply_permissions(_file: &ZipFile, _outpath: &Path) -> Result<(), WrappedError> {
154 Ok(())
155}