diff options
| author | nsfisis <nsfisis@gmail.com> | 2026-06-05 01:22:46 +0900 |
|---|---|---|
| committer | nsfisis <nsfisis@gmail.com> | 2026-06-05 01:22:46 +0900 |
| commit | adf14510b00929aaee85ccb8dedf9164878a0164 (patch) | |
| tree | c540e711155ee7bc65d2ac8b1a5ebdcbc33c7320 /crates/shirabe/src | |
| parent | c5bcf222f98d13b104231713bf4a0aa0833c420a (diff) | |
| download | php-shirabe-adf14510b00929aaee85ccb8dedf9164878a0164.tar.gz php-shirabe-adf14510b00929aaee85ccb8dedf9164878a0164.tar.zst php-shirabe-adf14510b00929aaee85ccb8dedf9164878a0164.zip | |
feat(downloader): wire ArchiveDownloader extraction and Path/Zip overrides
Implement ArchiveDownloader for Zip/Tar/Gzip/Xz/Phar/Rar so install()
extracts the archive (extract + rename) instead of doing FileDownloader's
plain file rename, and route install/prepare/cleanup through the mixin.
ArchiveDownloader::extract becomes &mut self to match the concrete
implementations.
Route ZipDownloader's bespoke download() (unzip-command static init) and
PathDownloader's symlink/junction/mirror download/install/remove through
the DownloaderInterface trait (path: String -> &str).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Diffstat (limited to 'crates/shirabe/src')
| -rw-r--r-- | crates/shirabe/src/downloader/archive_downloader.rs | 2 | ||||
| -rw-r--r-- | crates/shirabe/src/downloader/gzip_downloader.rs | 58 | ||||
| -rw-r--r-- | crates/shirabe/src/downloader/path_downloader.rs | 452 | ||||
| -rw-r--r-- | crates/shirabe/src/downloader/phar_downloader.rs | 34 | ||||
| -rw-r--r-- | crates/shirabe/src/downloader/rar_downloader.rs | 30 | ||||
| -rw-r--r-- | crates/shirabe/src/downloader/tar_downloader.rs | 34 | ||||
| -rw-r--r-- | crates/shirabe/src/downloader/xz_downloader.rs | 32 | ||||
| -rw-r--r-- | crates/shirabe/src/downloader/zip_downloader.rs | 317 |
8 files changed, 497 insertions, 462 deletions
diff --git a/crates/shirabe/src/downloader/archive_downloader.rs b/crates/shirabe/src/downloader/archive_downloader.rs index f2ec719..61ecfd1 100644 --- a/crates/shirabe/src/downloader/archive_downloader.rs +++ b/crates/shirabe/src/downloader/archive_downloader.rs @@ -24,7 +24,7 @@ pub trait ArchiveDownloader { fn cleanup_executed_mut(&mut self) -> &mut IndexMap<String, bool>; async fn extract( - &self, + &mut self, package: PackageInterfaceHandle, file: &str, path: &str, diff --git a/crates/shirabe/src/downloader/gzip_downloader.rs b/crates/shirabe/src/downloader/gzip_downloader.rs index 0c47b67..05ac244 100644 --- a/crates/shirabe/src/downloader/gzip_downloader.rs +++ b/crates/shirabe/src/downloader/gzip_downloader.rs @@ -50,7 +50,39 @@ impl GzipDownloader { } } - pub(crate) async fn extract( + fn extract_using_ext(&self, file: &str, target_filepath: &str) { + let archive_file = gzopen(file, "rb"); + let target_file = fopen(target_filepath, "wb"); + loop { + let string = gzread(archive_file.clone(), 4096); + if string.is_empty() { + break; + } + fwrite(target_file.clone(), &string, Platform::strlen(&string)); + } + gzclose(archive_file); + fclose(target_file); + } +} + +impl ArchiveDownloader for GzipDownloader { + fn inner(&self) -> &FileDownloader { + &self.inner + } + + fn inner_mut(&mut self) -> &mut FileDownloader { + &mut self.inner + } + + fn cleanup_executed(&self) -> &IndexMap<String, bool> { + &self.cleanup_executed + } + + fn cleanup_executed_mut(&mut self) -> &mut IndexMap<String, bool> { + &mut self.cleanup_executed + } + + async fn extract( &mut self, package: PackageInterfaceHandle, file: &str, @@ -114,20 +146,6 @@ impl GzipDownloader { Ok(None) } - - fn extract_using_ext(&self, file: &str, target_filepath: &str) { - let archive_file = gzopen(file, "rb"); - let target_file = fopen(target_filepath, "wb"); - loop { - let string = gzread(archive_file.clone(), 4096); - if string.is_empty() { - break; - } - fwrite(target_file.clone(), &string, Platform::strlen(&string)); - } - gzclose(archive_file); - fclose(target_file); - } } impl ChangeReportInterface for GzipDownloader { @@ -171,9 +189,7 @@ impl crate::downloader::DownloaderInterface for GzipDownloader { path: &str, prev_package: Option<PackageInterfaceHandle>, ) -> Result<Option<PhpMixed>> { - self.inner - .prepare(r#type, package, path, prev_package) - .await + <Self as ArchiveDownloader>::prepare(self, r#type, package, path, prev_package).await } async fn install( @@ -182,7 +198,7 @@ impl crate::downloader::DownloaderInterface for GzipDownloader { path: &str, output: bool, ) -> Result<Option<PhpMixed>> { - self.inner.install(package, path, output).await + <Self as ArchiveDownloader>::install(self, package, path, output).await } async fn update( @@ -210,8 +226,6 @@ impl crate::downloader::DownloaderInterface for GzipDownloader { path: &str, prev_package: Option<PackageInterfaceHandle>, ) -> Result<Option<PhpMixed>> { - self.inner - .cleanup(r#type, package, path, prev_package) - .await + <Self as ArchiveDownloader>::cleanup(self, r#type, package, path, prev_package).await } } diff --git a/crates/shirabe/src/downloader/path_downloader.rs b/crates/shirabe/src/downloader/path_downloader.rs index 99d72e7..a215bb3 100644 --- a/crates/shirabe/src/downloader/path_downloader.rs +++ b/crates/shirabe/src/downloader/path_downloader.rs @@ -62,14 +62,191 @@ impl PathDownloader { } } - pub async fn download( + pub fn get_vcs_reference(&self, package: PackageInterfaceHandle, path: &str) -> Option<String> { + let path = Filesystem::trim_trailing_slash(path); + let parser = VersionParser::new(); + let mut guesser = VersionGuesser::new( + self.inner.config.clone(), + self.inner.process.clone(), + parser.clone(), + Some(self.inner.io.clone()), + ); + let dumper = ArrayDumper::new(); + + let package_config = dumper.dump(package.clone()); + let package_version = guesser.guess_version(&package_config, &path); + if let Ok(Some(version)) = package_version { + return version.commit; + } + + None + } + + pub(crate) fn get_install_operation_appendix( + &self, + package: PackageInterfaceHandle, + path: &str, + ) -> Result<String> { + let url = package.get_dist_url().ok_or_else(|| RuntimeException { + message: format!( + "The package {} has no dist url configured, cannot install.", + package.get_pretty_name() + ), + code: 0, + })?; + let real_url = realpath(&url).ok_or_else(|| RuntimeException { + message: format!("Failed to realpath {}", url), + code: 0, + })?; + + if realpath(path).as_deref() == Some(&real_url) { + return Ok(": Source already present".to_string()); + } + + let (current_strategy, _) = + self.compute_allowed_strategies(&package.get_transport_options())?; + + if current_strategy == Self::STRATEGY_SYMLINK { + if Platform::is_windows() { + return Ok(format!( + ": Junctioning from {}", + package.get_dist_url().unwrap_or_default() + )); + } + + return Ok(format!( + ": Symlinking from {}", + package.get_dist_url().unwrap_or_default() + )); + } + + Ok(format!( + ": Mirroring from {}", + package.get_dist_url().unwrap_or_default() + )) + } + + fn compute_allowed_strategies( + &self, + transport_options: &IndexMap<String, PhpMixed>, + ) -> Result<(i64, Vec<i64>)> { + // When symlink transport option is null, both symlink and mirror are allowed + let mut current_strategy = Self::STRATEGY_SYMLINK; + let mut allowed_strategies = vec![Self::STRATEGY_SYMLINK, Self::STRATEGY_MIRROR]; + + let mirror_path_repos = Platform::get_env("COMPOSER_MIRROR_PATH_REPOS"); + if mirror_path_repos.map_or(false, |v| !v.is_empty()) { + current_strategy = Self::STRATEGY_MIRROR; + } + + let symlink_option = transport_options.get("symlink"); + + match symlink_option { + Some(PhpMixed::Bool(true)) => { + current_strategy = Self::STRATEGY_SYMLINK; + allowed_strategies = vec![Self::STRATEGY_SYMLINK]; + } + Some(PhpMixed::Bool(false)) => { + current_strategy = Self::STRATEGY_MIRROR; + allowed_strategies = vec![Self::STRATEGY_MIRROR]; + } + _ => {} + } + + // Check we can use junctions safely if we are on Windows + if Platform::is_windows() + && Self::STRATEGY_SYMLINK == current_strategy + && !self.safe_junctions() + { + if !allowed_strategies.contains(&Self::STRATEGY_MIRROR) { + return Err(RuntimeException { + message: "You are on an old Windows / old PHP combo which does not allow Composer to use junctions/symlinks and this path repository has symlink:true in its options so copying is not allowed".to_string(), + code: 0, + } + .into()); + } + current_strategy = Self::STRATEGY_MIRROR; + allowed_strategies = vec![Self::STRATEGY_MIRROR]; + } + + // Check we can use symlink() otherwise + if !Platform::is_windows() + && Self::STRATEGY_SYMLINK == current_strategy + && !function_exists("symlink") + { + if !allowed_strategies.contains(&Self::STRATEGY_MIRROR) { + return Err(RuntimeException { + message: "Your PHP has the symlink() function disabled which does not allow Composer to use symlinks and this path repository has symlink:true in its options so copying is not allowed".to_string(), + code: 0, + } + .into()); + } + current_strategy = Self::STRATEGY_MIRROR; + allowed_strategies = vec![Self::STRATEGY_MIRROR]; + } + + Ok((current_strategy, allowed_strategies)) + } + + // Returns true if junctions can be created and safely used on Windows. + // + // A PHP bug makes junction detection fragile, leading to possible data loss when removing a + // package. See https://bugs.php.net/bug.php?id=77552 + // + // For safety we require a minimum version of Windows 7, so we can call the system rmdir which + // will preserve target content if given a junction. + // + // The PHP bug was fixed in 7.2.16 and 7.3.3 (requires at least Windows 7). + fn safe_junctions(&self) -> bool { + // We need to call mklink, and rmdir on Windows 7 (version 6.1) + function_exists("proc_open") + && (PHP_WINDOWS_VERSION_MAJOR > 6 + || (PHP_WINDOWS_VERSION_MAJOR == 6 && PHP_WINDOWS_VERSION_MINOR >= 1)) + } +} + +impl VcsCapableDownloaderInterface for PathDownloader { + fn get_vcs_reference(&self, package: PackageInterfaceHandle, path: String) -> Option<String> { + PathDownloader::get_vcs_reference(self, package, &path) + } +} + +impl crate::downloader::ChangeReportInterface for PathDownloader { + fn get_local_changes( + &mut self, + package: PackageInterfaceHandle, + path: &str, + ) -> anyhow::Result<Option<String>> { + self.inner.get_local_changes(package, path) + } +} + +#[async_trait::async_trait(?Send)] +impl DownloaderInterface for PathDownloader { + fn get_installation_source(&self) -> String { + self.inner.get_installation_source() + } + + fn as_change_report_interface( + &mut self, + ) -> Option<&mut dyn crate::downloader::ChangeReportInterface> { + Some(self) + } + + fn as_vcs_capable_downloader_interface( + &self, + ) -> Option<&dyn crate::downloader::VcsCapableDownloaderInterface> { + Some(self) + } + + async fn download( &mut self, package: PackageInterfaceHandle, - path: String, - _prev_package: Option<PackageInterfaceHandle>, - _output: bool, + path: &str, + prev_package: Option<PackageInterfaceHandle>, + output: bool, ) -> Result<Option<PhpMixed>> { - let path = Filesystem::trim_trailing_slash(&path); + let path = Filesystem::trim_trailing_slash(path); let url = package.get_dist_url().ok_or_else(|| RuntimeException { message: format!( "The package {} has no dist url configured, cannot download.", @@ -124,13 +301,25 @@ impl PathDownloader { Ok(None) } - pub async fn install( + async fn prepare( + &mut self, + r#type: &str, + package: PackageInterfaceHandle, + path: &str, + prev_package: Option<PackageInterfaceHandle>, + ) -> Result<Option<PhpMixed>> { + self.inner + .prepare(r#type, package, path, prev_package) + .await + } + + async fn install( &mut self, package: PackageInterfaceHandle, - path: String, + path: &str, output: bool, ) -> Result<Option<PhpMixed>> { - let path = Filesystem::trim_trailing_slash(&path); + let path = Filesystem::trim_trailing_slash(path); let url = package.get_dist_url().ok_or_else(|| RuntimeException { message: format!( "The package {} has no dist url configured, cannot install.", @@ -295,13 +484,22 @@ impl PathDownloader { Ok(None) } - pub async fn remove( + async fn update( + &mut self, + initial: PackageInterfaceHandle, + target: PackageInterfaceHandle, + path: &str, + ) -> Result<Option<PhpMixed>> { + self.inner.update(initial, target, path).await + } + + async fn remove( &mut self, package: PackageInterfaceHandle, - path: String, + path: &str, output: bool, ) -> Result<Option<PhpMixed>> { - let path = Filesystem::trim_trailing_slash(&path); + let path = Filesystem::trim_trailing_slash(path); // realpath() may resolve Windows junctions to the source path, so we'll check for a junction // first to prevent a false positive when checking if the dist and install paths are the same. // See https://bugs.php.net/bug.php?id=77639 @@ -386,238 +584,6 @@ impl PathDownloader { self.inner.remove(package, &path, output).await } - pub fn get_vcs_reference(&self, package: PackageInterfaceHandle, path: &str) -> Option<String> { - let path = Filesystem::trim_trailing_slash(path); - let parser = VersionParser::new(); - let mut guesser = VersionGuesser::new( - self.inner.config.clone(), - self.inner.process.clone(), - parser.clone(), - Some(self.inner.io.clone()), - ); - let dumper = ArrayDumper::new(); - - let package_config = dumper.dump(package.clone()); - let package_version = guesser.guess_version(&package_config, &path); - if let Ok(Some(version)) = package_version { - return version.commit; - } - - None - } - - pub(crate) fn get_install_operation_appendix( - &self, - package: PackageInterfaceHandle, - path: &str, - ) -> Result<String> { - let url = package.get_dist_url().ok_or_else(|| RuntimeException { - message: format!( - "The package {} has no dist url configured, cannot install.", - package.get_pretty_name() - ), - code: 0, - })?; - let real_url = realpath(&url).ok_or_else(|| RuntimeException { - message: format!("Failed to realpath {}", url), - code: 0, - })?; - - if realpath(path).as_deref() == Some(&real_url) { - return Ok(": Source already present".to_string()); - } - - let (current_strategy, _) = - self.compute_allowed_strategies(&package.get_transport_options())?; - - if current_strategy == Self::STRATEGY_SYMLINK { - if Platform::is_windows() { - return Ok(format!( - ": Junctioning from {}", - package.get_dist_url().unwrap_or_default() - )); - } - - return Ok(format!( - ": Symlinking from {}", - package.get_dist_url().unwrap_or_default() - )); - } - - Ok(format!( - ": Mirroring from {}", - package.get_dist_url().unwrap_or_default() - )) - } - - fn compute_allowed_strategies( - &self, - transport_options: &IndexMap<String, PhpMixed>, - ) -> Result<(i64, Vec<i64>)> { - // When symlink transport option is null, both symlink and mirror are allowed - let mut current_strategy = Self::STRATEGY_SYMLINK; - let mut allowed_strategies = vec![Self::STRATEGY_SYMLINK, Self::STRATEGY_MIRROR]; - - let mirror_path_repos = Platform::get_env("COMPOSER_MIRROR_PATH_REPOS"); - if mirror_path_repos.map_or(false, |v| !v.is_empty()) { - current_strategy = Self::STRATEGY_MIRROR; - } - - let symlink_option = transport_options.get("symlink"); - - match symlink_option { - Some(PhpMixed::Bool(true)) => { - current_strategy = Self::STRATEGY_SYMLINK; - allowed_strategies = vec![Self::STRATEGY_SYMLINK]; - } - Some(PhpMixed::Bool(false)) => { - current_strategy = Self::STRATEGY_MIRROR; - allowed_strategies = vec![Self::STRATEGY_MIRROR]; - } - _ => {} - } - - // Check we can use junctions safely if we are on Windows - if Platform::is_windows() - && Self::STRATEGY_SYMLINK == current_strategy - && !self.safe_junctions() - { - if !allowed_strategies.contains(&Self::STRATEGY_MIRROR) { - return Err(RuntimeException { - message: "You are on an old Windows / old PHP combo which does not allow Composer to use junctions/symlinks and this path repository has symlink:true in its options so copying is not allowed".to_string(), - code: 0, - } - .into()); - } - current_strategy = Self::STRATEGY_MIRROR; - allowed_strategies = vec![Self::STRATEGY_MIRROR]; - } - - // Check we can use symlink() otherwise - if !Platform::is_windows() - && Self::STRATEGY_SYMLINK == current_strategy - && !function_exists("symlink") - { - if !allowed_strategies.contains(&Self::STRATEGY_MIRROR) { - return Err(RuntimeException { - message: "Your PHP has the symlink() function disabled which does not allow Composer to use symlinks and this path repository has symlink:true in its options so copying is not allowed".to_string(), - code: 0, - } - .into()); - } - current_strategy = Self::STRATEGY_MIRROR; - allowed_strategies = vec![Self::STRATEGY_MIRROR]; - } - - Ok((current_strategy, allowed_strategies)) - } - - // Returns true if junctions can be created and safely used on Windows. - // - // A PHP bug makes junction detection fragile, leading to possible data loss when removing a - // package. See https://bugs.php.net/bug.php?id=77552 - // - // For safety we require a minimum version of Windows 7, so we can call the system rmdir which - // will preserve target content if given a junction. - // - // The PHP bug was fixed in 7.2.16 and 7.3.3 (requires at least Windows 7). - fn safe_junctions(&self) -> bool { - // We need to call mklink, and rmdir on Windows 7 (version 6.1) - function_exists("proc_open") - && (PHP_WINDOWS_VERSION_MAJOR > 6 - || (PHP_WINDOWS_VERSION_MAJOR == 6 && PHP_WINDOWS_VERSION_MINOR >= 1)) - } -} - -impl VcsCapableDownloaderInterface for PathDownloader { - fn get_vcs_reference(&self, package: PackageInterfaceHandle, path: String) -> Option<String> { - PathDownloader::get_vcs_reference(self, package, &path) - } -} - -impl crate::downloader::ChangeReportInterface for PathDownloader { - fn get_local_changes( - &mut self, - package: PackageInterfaceHandle, - path: &str, - ) -> anyhow::Result<Option<String>> { - self.inner.get_local_changes(package, path) - } -} - -// TODO(phase-b): wire up PathDownloader trait properly. PathDownloader extends FileDownloader and -// overrides download/install/remove with &mut self signatures that diverge from the trait. The -// trait methods here delegate to the inner FileDownloader; the bespoke overrides on the struct -// itself are not yet routed through the trait. -#[async_trait::async_trait(?Send)] -impl DownloaderInterface for PathDownloader { - fn get_installation_source(&self) -> String { - self.inner.get_installation_source() - } - - fn as_change_report_interface( - &mut self, - ) -> Option<&mut dyn crate::downloader::ChangeReportInterface> { - Some(self) - } - - fn as_vcs_capable_downloader_interface( - &self, - ) -> Option<&dyn crate::downloader::VcsCapableDownloaderInterface> { - Some(self) - } - - async fn download( - &mut self, - package: PackageInterfaceHandle, - path: &str, - prev_package: Option<PackageInterfaceHandle>, - output: bool, - ) -> Result<Option<PhpMixed>> { - self.inner - .download(package, path, prev_package, output) - .await - } - - async fn prepare( - &mut self, - r#type: &str, - package: PackageInterfaceHandle, - path: &str, - prev_package: Option<PackageInterfaceHandle>, - ) -> Result<Option<PhpMixed>> { - self.inner - .prepare(r#type, package, path, prev_package) - .await - } - - async fn install( - &mut self, - package: PackageInterfaceHandle, - path: &str, - output: bool, - ) -> Result<Option<PhpMixed>> { - self.inner.install(package, path, output).await - } - - async fn update( - &mut self, - initial: PackageInterfaceHandle, - target: PackageInterfaceHandle, - path: &str, - ) -> Result<Option<PhpMixed>> { - self.inner.update(initial, target, path).await - } - - async fn remove( - &mut self, - package: PackageInterfaceHandle, - path: &str, - output: bool, - ) -> Result<Option<PhpMixed>> { - self.inner.remove(package, path, output).await - } - async fn cleanup( &mut self, r#type: &str, diff --git a/crates/shirabe/src/downloader/phar_downloader.rs b/crates/shirabe/src/downloader/phar_downloader.rs index 8180764..4b3fc51 100644 --- a/crates/shirabe/src/downloader/phar_downloader.rs +++ b/crates/shirabe/src/downloader/phar_downloader.rs @@ -45,10 +45,28 @@ impl PharDownloader { cleanup_executed: IndexMap::new(), } } +} - pub(crate) async fn extract( - &self, - package: PackageInterfaceHandle, +impl ArchiveDownloader for PharDownloader { + fn inner(&self) -> &FileDownloader { + &self.inner + } + + fn inner_mut(&mut self) -> &mut FileDownloader { + &mut self.inner + } + + fn cleanup_executed(&self) -> &IndexMap<String, bool> { + &self.cleanup_executed + } + + fn cleanup_executed_mut(&mut self) -> &mut IndexMap<String, bool> { + &mut self.cleanup_executed + } + + async fn extract( + &mut self, + _package: PackageInterfaceHandle, file: &str, path: &str, ) -> Result<Option<PhpMixed>> { @@ -105,9 +123,7 @@ impl DownloaderInterface for PharDownloader { path: &str, prev_package: Option<PackageInterfaceHandle>, ) -> Result<Option<PhpMixed>> { - self.inner - .prepare(r#type, package, path, prev_package) - .await + <Self as ArchiveDownloader>::prepare(self, r#type, package, path, prev_package).await } async fn install( @@ -116,7 +132,7 @@ impl DownloaderInterface for PharDownloader { path: &str, output: bool, ) -> Result<Option<PhpMixed>> { - self.inner.install(package, path, output).await + <Self as ArchiveDownloader>::install(self, package, path, output).await } async fn update( @@ -144,8 +160,6 @@ impl DownloaderInterface for PharDownloader { path: &str, prev_package: Option<PackageInterfaceHandle>, ) -> Result<Option<PhpMixed>> { - self.inner - .cleanup(r#type, package, path, prev_package) - .await + <Self as ArchiveDownloader>::cleanup(self, r#type, package, path, prev_package).await } } diff --git a/crates/shirabe/src/downloader/rar_downloader.rs b/crates/shirabe/src/downloader/rar_downloader.rs index 5676a96..f2c89c3 100644 --- a/crates/shirabe/src/downloader/rar_downloader.rs +++ b/crates/shirabe/src/downloader/rar_downloader.rs @@ -48,8 +48,26 @@ impl RarDownloader { cleanup_executed: IndexMap::new(), } } +} + +impl ArchiveDownloader for RarDownloader { + fn inner(&self) -> &FileDownloader { + &self.inner + } + + fn inner_mut(&mut self) -> &mut FileDownloader { + &mut self.inner + } + + fn cleanup_executed(&self) -> &IndexMap<String, bool> { + &self.cleanup_executed + } - pub(crate) async fn extract( + fn cleanup_executed_mut(&mut self) -> &mut IndexMap<String, bool> { + &mut self.cleanup_executed + } + + async fn extract( &mut self, _package: PackageInterfaceHandle, file: &str, @@ -185,9 +203,7 @@ impl crate::downloader::DownloaderInterface for RarDownloader { path: &str, prev_package: Option<PackageInterfaceHandle>, ) -> Result<Option<PhpMixed>> { - self.inner - .prepare(r#type, package, path, prev_package) - .await + <Self as ArchiveDownloader>::prepare(self, r#type, package, path, prev_package).await } async fn install( @@ -196,7 +212,7 @@ impl crate::downloader::DownloaderInterface for RarDownloader { path: &str, output: bool, ) -> Result<Option<PhpMixed>> { - self.inner.install(package, path, output).await + <Self as ArchiveDownloader>::install(self, package, path, output).await } async fn update( @@ -224,8 +240,6 @@ impl crate::downloader::DownloaderInterface for RarDownloader { path: &str, prev_package: Option<PackageInterfaceHandle>, ) -> Result<Option<PhpMixed>> { - self.inner - .cleanup(r#type, package, path, prev_package) - .await + <Self as ArchiveDownloader>::cleanup(self, r#type, package, path, prev_package).await } } diff --git a/crates/shirabe/src/downloader/tar_downloader.rs b/crates/shirabe/src/downloader/tar_downloader.rs index 8835e7d..833a958 100644 --- a/crates/shirabe/src/downloader/tar_downloader.rs +++ b/crates/shirabe/src/downloader/tar_downloader.rs @@ -45,10 +45,28 @@ impl TarDownloader { cleanup_executed: IndexMap::new(), } } +} - pub(crate) async fn extract( - &self, - package: PackageInterfaceHandle, +impl ArchiveDownloader for TarDownloader { + fn inner(&self) -> &FileDownloader { + &self.inner + } + + fn inner_mut(&mut self) -> &mut FileDownloader { + &mut self.inner + } + + fn cleanup_executed(&self) -> &IndexMap<String, bool> { + &self.cleanup_executed + } + + fn cleanup_executed_mut(&mut self) -> &mut IndexMap<String, bool> { + &mut self.cleanup_executed + } + + async fn extract( + &mut self, + _package: PackageInterfaceHandle, file: &str, path: &str, ) -> Result<Option<PhpMixed>> { @@ -100,9 +118,7 @@ impl DownloaderInterface for TarDownloader { path: &str, prev_package: Option<PackageInterfaceHandle>, ) -> Result<Option<PhpMixed>> { - self.inner - .prepare(r#type, package, path, prev_package) - .await + <Self as ArchiveDownloader>::prepare(self, r#type, package, path, prev_package).await } async fn install( @@ -111,7 +127,7 @@ impl DownloaderInterface for TarDownloader { path: &str, output: bool, ) -> Result<Option<PhpMixed>> { - self.inner.install(package, path, output).await + <Self as ArchiveDownloader>::install(self, package, path, output).await } async fn update( @@ -139,8 +155,6 @@ impl DownloaderInterface for TarDownloader { path: &str, prev_package: Option<PackageInterfaceHandle>, ) -> Result<Option<PhpMixed>> { - self.inner - .cleanup(r#type, package, path, prev_package) - .await + <Self as ArchiveDownloader>::cleanup(self, r#type, package, path, prev_package).await } } diff --git a/crates/shirabe/src/downloader/xz_downloader.rs b/crates/shirabe/src/downloader/xz_downloader.rs index 10a7edf..1c03f1d 100644 --- a/crates/shirabe/src/downloader/xz_downloader.rs +++ b/crates/shirabe/src/downloader/xz_downloader.rs @@ -44,10 +44,28 @@ impl XzDownloader { cleanup_executed: IndexMap::new(), } } +} + +impl ArchiveDownloader for XzDownloader { + fn inner(&self) -> &FileDownloader { + &self.inner + } + + fn inner_mut(&mut self) -> &mut FileDownloader { + &mut self.inner + } + + fn cleanup_executed(&self) -> &IndexMap<String, bool> { + &self.cleanup_executed + } - pub(crate) async fn extract( + fn cleanup_executed_mut(&mut self) -> &mut IndexMap<String, bool> { + &mut self.cleanup_executed + } + + async fn extract( &mut self, - package: PackageInterfaceHandle, + _package: PackageInterfaceHandle, file: &str, path: &str, ) -> Result<Option<PhpMixed>> { @@ -119,9 +137,7 @@ impl crate::downloader::DownloaderInterface for XzDownloader { path: &str, prev_package: Option<PackageInterfaceHandle>, ) -> Result<Option<PhpMixed>> { - self.inner - .prepare(r#type, package, path, prev_package) - .await + <Self as ArchiveDownloader>::prepare(self, r#type, package, path, prev_package).await } async fn install( @@ -130,7 +146,7 @@ impl crate::downloader::DownloaderInterface for XzDownloader { path: &str, output: bool, ) -> Result<Option<PhpMixed>> { - self.inner.install(package, path, output).await + <Self as ArchiveDownloader>::install(self, package, path, output).await } async fn update( @@ -158,8 +174,6 @@ impl crate::downloader::DownloaderInterface for XzDownloader { path: &str, prev_package: Option<PackageInterfaceHandle>, ) -> Result<Option<PhpMixed>> { - self.inner - .cleanup(r#type, package, path, prev_package) - .await + <Self as ArchiveDownloader>::cleanup(self, r#type, package, path, prev_package).await } } diff --git a/crates/shirabe/src/downloader/zip_downloader.rs b/crates/shirabe/src/downloader/zip_downloader.rs index 90772ce..3875e7e 100644 --- a/crates/shirabe/src/downloader/zip_downloader.rs +++ b/crates/shirabe/src/downloader/zip_downloader.rs @@ -61,146 +61,6 @@ impl ZipDownloader { } } - pub async fn download( - &mut self, - package: PackageInterfaceHandle, - path: &str, - prev_package: Option<PackageInterfaceHandle>, - output: bool, - ) -> Result<Option<PhpMixed>> { - { - let mut unzip_commands = UNZIP_COMMANDS.lock().unwrap(); - if unzip_commands.is_none() { - *unzip_commands = Some(vec![]); - let finder = ExecutableFinder::new(); - let commands = unzip_commands.as_mut().unwrap(); - if Platform::is_windows() { - if let Some(cmd) = - finder.find("7z", None, &[r"C:\Program Files\7-Zip".to_string()]) - { - commands.push(vec![ - "7z".to_string(), - cmd, - "x".to_string(), - "-bb0".to_string(), - "-y".to_string(), - "%file%".to_string(), - "-o%path%".to_string(), - ]); - } - } - if let Some(cmd) = finder.find("unzip", None, &[]) { - commands.push(vec![ - "unzip".to_string(), - cmd, - "-qq".to_string(), - "%file%".to_string(), - "-d".to_string(), - "%path%".to_string(), - ]); - } - if !Platform::is_windows() { - if let Some(cmd) = finder.find("7z", None, &[]) { - // 7z linux/macOS support is only used if unzip is not present - commands.push(vec![ - "7z".to_string(), - cmd, - "x".to_string(), - "-bb0".to_string(), - "-y".to_string(), - "%file%".to_string(), - "-o%path%".to_string(), - ]); - } else if let Some(cmd) = finder.find("7zz", None, &[]) { - // 7zz linux/macOS support is only used if unzip is not present - commands.push(vec![ - "7zz".to_string(), - cmd, - "x".to_string(), - "-bb0".to_string(), - "-y".to_string(), - "%file%".to_string(), - "-o%path%".to_string(), - ]); - } else if let Some(cmd) = finder.find("7za", None, &[]) { - // 7za linux/macOS support is only used if unzip is not present - commands.push(vec![ - "7za".to_string(), - cmd, - "x".to_string(), - "-bb0".to_string(), - "-y".to_string(), - "%file%".to_string(), - "-o%path%".to_string(), - ]); - } - } - } - } - - let proc_open_missing = !function_exists("proc_open"); - if proc_open_missing { - *UNZIP_COMMANDS.lock().unwrap() = Some(vec![]); - } - - { - let mut has_zip_archive = HAS_ZIP_ARCHIVE.lock().unwrap(); - if has_zip_archive.is_none() { - *has_zip_archive = Some(class_exists("ZipArchive")); - } - } - - let has_zip_archive = HAS_ZIP_ARCHIVE.lock().unwrap().unwrap_or(false); - let unzip_commands_empty = UNZIP_COMMANDS - .lock() - .unwrap() - .as_ref() - .map_or(true, |v| v.is_empty()); - - if !has_zip_archive && unzip_commands_empty { - let ini_message = IniHelper::get_message(); - let error = if proc_open_missing { - format!( - "The zip extension is missing and unzip/7z commands cannot be called as proc_open is disabled, skipping.\n{}", - ini_message - ) - } else { - format!( - "The zip extension and unzip/7z commands are both missing, skipping.\n{}", - ini_message - ) - }; - return Err(RuntimeException { - message: error, - code: 0, - } - .into()); - } - - { - let mut is_windows_guard = IS_WINDOWS.lock().unwrap(); - if is_windows_guard.is_none() { - *is_windows_guard = Some(Platform::is_windows()); - - if !is_windows_guard.unwrap() && unzip_commands_empty { - if proc_open_missing { - self.inner.io.write_error("<warning>proc_open is disabled so 'unzip' and '7z' commands cannot be used, zip files are being unpacked using the PHP zip extension.</warning>"); - self.inner.io.write_error("<warning>This may cause invalid reports of corrupted archives. Besides, any UNIX permissions (e.g. executable) defined in the archives will be lost.</warning>"); - self.inner.io.write_error("<warning>Enabling proc_open and installing 'unzip' or '7z' (21.01+) may remediate them.</warning>"); - } else { - self.inner.io.write_error("<warning>As there is no 'unzip' nor '7z' command installed zip files are being unpacked using the PHP zip extension.</warning>"); - self.inner.io.write_error("<warning>This may cause invalid reports of corrupted archives. Besides, any UNIX permissions (e.g. executable) defined in the archives will be lost.</warning>"); - self.inner.io.write_error("<warning>Installing 'unzip' or '7z' (21.01+) may remediate them.</warning>"); - } - } - } - } - - self.inner - .download(package, path, prev_package, output) - .await - } - async fn extract_with_system_unzip( &mut self, package: PackageInterfaceHandle, @@ -522,15 +382,6 @@ impl ZipDownloader { }) } - pub(crate) async fn extract( - &mut self, - package: PackageInterfaceHandle, - file: &str, - path: &str, - ) -> Result<Option<PhpMixed>> { - self.extract_with_system_unzip(package, file, path).await - } - pub fn get_error_message(&self, retval: i64, file: &str) -> String { match retval { ZipArchive::ER_EXISTS => format!("File '{}' already exists.", file), @@ -554,6 +405,33 @@ impl ZipDownloader { } } +impl ArchiveDownloader for ZipDownloader { + fn inner(&self) -> &FileDownloader { + &self.inner + } + + fn inner_mut(&mut self) -> &mut FileDownloader { + &mut self.inner + } + + fn cleanup_executed(&self) -> &IndexMap<String, bool> { + &self.cleanup_executed + } + + fn cleanup_executed_mut(&mut self) -> &mut IndexMap<String, bool> { + &mut self.cleanup_executed + } + + async fn extract( + &mut self, + package: PackageInterfaceHandle, + file: &str, + path: &str, + ) -> Result<Option<PhpMixed>> { + self.extract_with_system_unzip(package, file, path).await + } +} + impl ChangeReportInterface for ZipDownloader { fn get_local_changes( &mut self, @@ -564,9 +442,6 @@ impl ChangeReportInterface for ZipDownloader { } } -// TODO(phase-b): ZipDownloader::download is overridden with extra setup (UNZIP_COMMANDS init, -// etc.). The trait method here delegates straight to the inner FileDownloader; the bespoke -// override on the struct itself takes &mut self and is not yet routed through the trait. #[async_trait::async_trait(?Send)] impl crate::downloader::DownloaderInterface for ZipDownloader { fn get_installation_source(&self) -> String { @@ -586,6 +461,134 @@ impl crate::downloader::DownloaderInterface for ZipDownloader { prev_package: Option<PackageInterfaceHandle>, output: bool, ) -> Result<Option<PhpMixed>> { + { + let mut unzip_commands = UNZIP_COMMANDS.lock().unwrap(); + if unzip_commands.is_none() { + *unzip_commands = Some(vec![]); + let finder = ExecutableFinder::new(); + let commands = unzip_commands.as_mut().unwrap(); + if Platform::is_windows() { + if let Some(cmd) = + finder.find("7z", None, &[r"C:\Program Files\7-Zip".to_string()]) + { + commands.push(vec![ + "7z".to_string(), + cmd, + "x".to_string(), + "-bb0".to_string(), + "-y".to_string(), + "%file%".to_string(), + "-o%path%".to_string(), + ]); + } + } + if let Some(cmd) = finder.find("unzip", None, &[]) { + commands.push(vec![ + "unzip".to_string(), + cmd, + "-qq".to_string(), + "%file%".to_string(), + "-d".to_string(), + "%path%".to_string(), + ]); + } + if !Platform::is_windows() { + if let Some(cmd) = finder.find("7z", None, &[]) { + // 7z linux/macOS support is only used if unzip is not present + commands.push(vec![ + "7z".to_string(), + cmd, + "x".to_string(), + "-bb0".to_string(), + "-y".to_string(), + "%file%".to_string(), + "-o%path%".to_string(), + ]); + } else if let Some(cmd) = finder.find("7zz", None, &[]) { + // 7zz linux/macOS support is only used if unzip is not present + commands.push(vec![ + "7zz".to_string(), + cmd, + "x".to_string(), + "-bb0".to_string(), + "-y".to_string(), + "%file%".to_string(), + "-o%path%".to_string(), + ]); + } else if let Some(cmd) = finder.find("7za", None, &[]) { + // 7za linux/macOS support is only used if unzip is not present + commands.push(vec![ + "7za".to_string(), + cmd, + "x".to_string(), + "-bb0".to_string(), + "-y".to_string(), + "%file%".to_string(), + "-o%path%".to_string(), + ]); + } + } + } + } + + let proc_open_missing = !function_exists("proc_open"); + if proc_open_missing { + *UNZIP_COMMANDS.lock().unwrap() = Some(vec![]); + } + + { + let mut has_zip_archive = HAS_ZIP_ARCHIVE.lock().unwrap(); + if has_zip_archive.is_none() { + *has_zip_archive = Some(class_exists("ZipArchive")); + } + } + + let has_zip_archive = HAS_ZIP_ARCHIVE.lock().unwrap().unwrap_or(false); + let unzip_commands_empty = UNZIP_COMMANDS + .lock() + .unwrap() + .as_ref() + .map_or(true, |v| v.is_empty()); + + if !has_zip_archive && unzip_commands_empty { + let ini_message = IniHelper::get_message(); + let error = if proc_open_missing { + format!( + "The zip extension is missing and unzip/7z commands cannot be called as proc_open is disabled, skipping.\n{}", + ini_message + ) + } else { + format!( + "The zip extension and unzip/7z commands are both missing, skipping.\n{}", + ini_message + ) + }; + return Err(RuntimeException { + message: error, + code: 0, + } + .into()); + } + + { + let mut is_windows_guard = IS_WINDOWS.lock().unwrap(); + if is_windows_guard.is_none() { + *is_windows_guard = Some(Platform::is_windows()); + + if !is_windows_guard.unwrap() && unzip_commands_empty { + if proc_open_missing { + self.inner.io.write_error("<warning>proc_open is disabled so 'unzip' and '7z' commands cannot be used, zip files are being unpacked using the PHP zip extension.</warning>"); + self.inner.io.write_error("<warning>This may cause invalid reports of corrupted archives. Besides, any UNIX permissions (e.g. executable) defined in the archives will be lost.</warning>"); + self.inner.io.write_error("<warning>Enabling proc_open and installing 'unzip' or '7z' (21.01+) may remediate them.</warning>"); + } else { + self.inner.io.write_error("<warning>As there is no 'unzip' nor '7z' command installed zip files are being unpacked using the PHP zip extension.</warning>"); + self.inner.io.write_error("<warning>This may cause invalid reports of corrupted archives. Besides, any UNIX permissions (e.g. executable) defined in the archives will be lost.</warning>"); + self.inner.io.write_error("<warning>Installing 'unzip' or '7z' (21.01+) may remediate them.</warning>"); + } + } + } + } + self.inner .download(package, path, prev_package, output) .await @@ -598,9 +601,7 @@ impl crate::downloader::DownloaderInterface for ZipDownloader { path: &str, prev_package: Option<PackageInterfaceHandle>, ) -> Result<Option<PhpMixed>> { - self.inner - .prepare(r#type, package, path, prev_package) - .await + <Self as ArchiveDownloader>::prepare(self, r#type, package, path, prev_package).await } async fn install( @@ -609,7 +610,7 @@ impl crate::downloader::DownloaderInterface for ZipDownloader { path: &str, output: bool, ) -> Result<Option<PhpMixed>> { - self.inner.install(package, path, output).await + <Self as ArchiveDownloader>::install(self, package, path, output).await } async fn update( @@ -637,8 +638,6 @@ impl crate::downloader::DownloaderInterface for ZipDownloader { path: &str, prev_package: Option<PackageInterfaceHandle>, ) -> Result<Option<PhpMixed>> { - self.inner - .cleanup(r#type, package, path, prev_package) - .await + <Self as ArchiveDownloader>::cleanup(self, r#type, package, path, prev_package).await } } |
