1use std::cell::RefCell;
6use std::io::{Read, Seek, Write};
7use std::path::PathBuf;
8use std::sync::Arc;
9
10use crate::storage::{self, Storage};
11use crate::{decrypt, overwrite};
12use corecrypto::protected::Protected;
13
14#[derive(Debug)]
15pub enum Error {
16 WriteData,
17 OpenArchive,
18 OpenArchivedFile,
19 ResetCursorPosition,
20 Storage(storage::Error),
21 Decrypt(decrypt::Error),
22}
23
24impl std::fmt::Display for Error {
25 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
26 match self {
27 Error::WriteData => f.write_str("Unable to write data"),
28 Error::OpenArchive => f.write_str("Unable to open archive"),
29 Error::OpenArchivedFile => f.write_str("Unable to open archived file"),
30 Error::ResetCursorPosition => f.write_str("Unable to reset cursor position"),
31 Error::Storage(inner) => write!(f, "Storage error: {inner}"),
32 Error::Decrypt(inner) => write!(f, "Decrypt error: {inner}"),
33 }
34 }
35}
36
37impl std::error::Error for Error {}
38
39type OnArchiveInfo = Box<dyn FnOnce(usize)>;
40type OnZipFileFn = Box<dyn Fn(PathBuf) -> bool>;
41
42pub struct Request<'a, R>
43where
44 R: Read,
45{
46 pub reader: &'a RefCell<R>,
47 pub header_reader: Option<&'a RefCell<R>>,
48 pub raw_key: Protected<Vec<u8>>,
49 pub output_dir_path: PathBuf,
50 pub on_decrypted_header: Option<decrypt::OnDecryptedHeaderFn>,
51 pub on_archive_info: Option<OnArchiveInfo>,
52 pub on_zip_file: Option<OnZipFileFn>,
53}
54
55pub fn execute<RW: Read + Write + Seek>(
56 stor: Arc<impl Storage<RW> + 'static>,
57 req: Request<'_, RW>,
58) -> Result<(), Error> {
59 let tmp_file = stor.create_temp_file().map_err(Error::Storage)?;
61
62 decrypt::execute(decrypt::Request {
64 header_reader: req.header_reader,
65 reader: req.reader,
66 writer: tmp_file
67 .try_writer()
68 .expect("We sure that file in write mode"),
69 raw_key: req.raw_key,
70 on_decrypted_header: req.on_decrypted_header,
71 })
72 .map_err(Error::Decrypt)?;
73
74 let buf_capacity = stor.file_len(&tmp_file).map_err(Error::Storage)?;
75
76 {
78 let mut reader = tmp_file
79 .try_reader()
80 .expect("We sure that file in read mode")
81 .borrow_mut();
82
83 reader.rewind().map_err(|_| Error::ResetCursorPosition)?;
84
85 let mut archive = zip::ZipArchive::new(&mut *reader).map_err(|_| Error::OpenArchive)?;
86
87 let output_dir = req.output_dir_path.clone();
88
89 let entities = (0..archive.len())
91 .filter_map(|i| {
92 let zip_file = archive.by_index(i).ok()?;
93 let mut full_path = output_dir.clone();
94
95 zip_file.enclosed_name().map(|path| {
99 full_path.push(path);
100
101 (full_path, i, zip_file.is_dir())
102 })
103 })
104 .filter(|(full_path, ..)| {
105 if let Some(on_zip_file) = req.on_zip_file.as_ref() {
106 on_zip_file(full_path.clone())
107 } else {
108 true
109 }
110 })
111 .collect::<Vec<_>>();
112
113 let files_count = entities.len();
114 if let Some(on_archive_info) = req.on_archive_info {
115 on_archive_info(files_count);
116 }
117
118 #[allow(clippy::needless_collect)]
120 let create_dirs_jobs = entities
121 .iter()
122 .filter(|(_, _, is_dir)| *is_dir)
123 .map(|(fp, ..)| fp)
124 .chain([&output_dir])
125 .map(|full_path| {
126 let stor = stor.clone();
127 let full_path = full_path.clone();
128 std::thread::spawn(move || stor.create_dir_all(full_path).map_err(Error::Storage))
129 })
130 .collect::<Vec<_>>();
131
132 create_dirs_jobs
133 .into_iter()
134 .try_for_each(|th| th.join().unwrap())?;
135
136 entities
138 .iter()
139 .filter(|(_, _, is_dir)| !*is_dir)
140 .try_for_each(|(full_path, i, _)| {
141 let mut zip_file = archive.by_index(*i).map_err(|_| Error::OpenArchivedFile)?;
142 let file = stor
143 .create_file(full_path)
144 .or_else(|_| stor.write_file(full_path))
145 .map_err(Error::Storage)?;
146 std::io::copy(
147 &mut zip_file,
148 &mut *file.try_writer().map_err(Error::Storage)?.borrow_mut(),
149 )
150 .map_err(|_| Error::WriteData)?;
151 Ok(())
152 })?;
153 }
154
155 overwrite::execute(overwrite::Request {
157 buf_capacity,
158 writer: tmp_file
159 .try_writer()
160 .expect("We sure that file in write mode"),
161 passes: 1,
162 })
163 .ok();
164
165 stor.remove_file(tmp_file).ok();
166
167 Ok(())
168}
169
170#[cfg(test)]
171mod tests {
172 #[test]
173 #[ignore = "not yet implemented"]
174 fn should_unpack_encrypted_archive() {
175 todo!()
176 }
177}