Fix extracting password protected archives

Closes: #1157

The fix splits the "canceled" and "failed" states for OperationError. It
also preserves that state because some functions overwrote the state by
rewrapping the error.
This commit is contained in:
Josh Megnauth 2025-09-01 01:28:26 -04:00 committed by Jeremy Soller
parent cf2e2faf3c
commit d8acbd2ce0
7 changed files with 341 additions and 189 deletions

View file

@ -47,53 +47,58 @@ pub fn extract(
controller: &Controller,
) -> Result<(), OperationError> {
let mime = mime_for_path(path, None, false);
let controller = controller.clone();
let password = password.clone();
match mime.essence_str() {
"application/gzip" | "application/x-compressed-tar" => OpReader::new(path, controller)
.map(io::BufReader::new)
.map(flate2::read::GzDecoder::new)
.map(tar::Archive::new)
.and_then(|mut archive| archive.unpack(&new_dir))
.map_err(OperationError::from_str)?,
"application/x-tar" => OpReader::new(path, controller)
"application/gzip" | "application/x-compressed-tar" => {
OpReader::new(path, controller.clone())
.map(io::BufReader::new)
.map(flate2::read::GzDecoder::new)
.map(tar::Archive::new)
.and_then(|mut archive| archive.unpack(new_dir))
.map_err(|e| OperationError::from_err(e, controller))?
}
"application/x-tar" => OpReader::new(path, controller.clone())
.map(io::BufReader::new)
.map(tar::Archive::new)
.and_then(|mut archive| archive.unpack(&new_dir))
.map_err(OperationError::from_str)?,
.and_then(|mut archive| archive.unpack(new_dir))
.map_err(|e| OperationError::from_err(e, controller))?,
"application/zip" => fs::File::open(path)
.map(io::BufReader::new)
.map(zip::ZipArchive::new)
.map_err(OperationError::from_str)?
.and_then(move |mut archive| zip_extract(&mut archive, &new_dir, password, controller))
.map_err(|e| OperationError::from_err(e, controller))?
.and_then(move |mut archive| {
zip_extract(&mut archive, new_dir, password, controller.clone())
})
.map_err(|e| match e {
ZipError::UnsupportedArchive(ZipError::PASSWORD_REQUIRED)
| ZipError::InvalidPassword => OperationError {
kind: OperationErrorType::PasswordRequired,
},
_ => OperationError::from_str(e),
| ZipError::InvalidPassword => {
OperationError::from_kind(OperationErrorType::PasswordRequired, controller)
}
_ => OperationError::from_err(e, controller),
})?,
#[cfg(feature = "bzip2")]
"application/x-bzip"
| "application/x-bzip-compressed-tar"
| "application/x-bzip2"
| "application/x-bzip2-compressed-tar" => OpReader::new(path, controller)
| "application/x-bzip2-compressed-tar" => OpReader::new(path, controller.clone())
.map(io::BufReader::new)
.map(bzip2::read::BzDecoder::new)
.map(tar::Archive::new)
.and_then(|mut archive| archive.unpack(&new_dir))
.map_err(OperationError::from_str)?,
.and_then(|mut archive| archive.unpack(new_dir))
.map_err(|e| OperationError::from_err(e, controller))?,
#[cfg(feature = "xz2")]
"application/x-xz" | "application/x-xz-compressed-tar" => OpReader::new(path, controller)
.map(io::BufReader::new)
.map(xz2::read::XzDecoder::new)
.map(tar::Archive::new)
.and_then(|mut archive| archive.unpack(&new_dir))
.map_err(OperationError::from_str)?,
_ => Err(OperationError::from_str(format!(
"unsupported mime type {:?}",
mime
)))?,
"application/x-xz" | "application/x-xz-compressed-tar" => {
OpReader::new(path, controller.clone())
.map(io::BufReader::new)
.map(xz2::read::XzDecoder::new)
.map(tar::Archive::new)
.and_then(|mut archive| archive.unpack(new_dir))
.map_err(|e| OperationError::from_err(e, controller))?
}
_ => Err(OperationError::from_err(
format!("unsupported mime type {:?}", mime),
controller,
))?,
}
Ok(())
}
@ -135,7 +140,7 @@ fn zip_extract<R: io::Read + io::Seek, P: AsRef<Path>>(
controller
.check()
.await
.map_err(|err| io::Error::new(io::ErrorKind::Other, err))
.map_err(|s| io::Error::other(OperationError::from_state(s, &controller)))
})?;
controller.set_progress((i as f32) / total_files as f32);
@ -143,11 +148,10 @@ fn zip_extract<R: io::Read + io::Seek, P: AsRef<Path>>(
let mut file = match &password {
None => archive.by_index(i),
Some(pwd) => archive.by_index_decrypt(i, pwd.as_bytes()),
}
.map_err(|e| e)?;
}?;
let filepath = file
.enclosed_name()
.ok_or(ZipError::InvalidArchive("Invalid file path".into()))?;
.ok_or(ZipError::InvalidArchive("Invalid file path"))?;
let outpath = directory.as_ref().join(filepath);
@ -206,8 +210,7 @@ fn zip_extract<R: io::Read + io::Seek, P: AsRef<Path>>(
let mut file = match &password {
None => archive.by_index(i),
Some(pwd) => archive.by_index_decrypt(i, pwd.as_bytes()),
}
.map_err(|e| e)?;
}?;
// create all pending dirs
while let Some(pending_dir) = pending_directory_creates.pop_front() {
@ -226,7 +229,7 @@ fn zip_extract<R: io::Read + io::Seek, P: AsRef<Path>>(
controller
.check()
.await
.map_err(|err| io::Error::new(io::ErrorKind::Other, err))
.map_err(|s| io::Error::other(OperationError::from_state(s, &controller)))
})?;
let count = file.read(&mut buffer)?;