diff options
| author | nsfisis <nsfisis@gmail.com> | 2026-05-15 23:19:12 +0900 |
|---|---|---|
| committer | nsfisis <nsfisis@gmail.com> | 2026-05-16 10:00:40 +0900 |
| commit | 148586086312fa32887e580f232be30a7125a1a0 (patch) | |
| tree | 0ca9156d06256c605bedab02ea5c8ec5dc422ad3 /crates/shirabe | |
| parent | 7011204a8267137967e55b3e6016b88f2e4a600d (diff) | |
| download | php-shirabe-148586086312fa32887e580f232be30a7125a1a0.tar.gz php-shirabe-148586086312fa32887e580f232be30a7125a1a0.tar.zst php-shirabe-148586086312fa32887e580f232be30a7125a1a0.zip | |
feat(port): port PharArchiver.php
Diffstat (limited to 'crates/shirabe')
| -rw-r--r-- | crates/shirabe/src/package/archiver/phar_archiver.rs | 146 |
1 files changed, 146 insertions, 0 deletions
diff --git a/crates/shirabe/src/package/archiver/phar_archiver.rs b/crates/shirabe/src/package/archiver/phar_archiver.rs index 1ac14bf..24cd738 100644 --- a/crates/shirabe/src/package/archiver/phar_archiver.rs +++ b/crates/shirabe/src/package/archiver/phar_archiver.rs @@ -1 +1,147 @@ //! ref: composer/src/Composer/Package/Archiver/PharArchiver.php + +use indexmap::IndexMap; +use shirabe_php_shim::{ + bzcompress, file_exists, file_put_contents, function_exists, gzcompress, pack, str_repeat, + strrpos, unlink, FilesystemIterator, Phar, PharData, PhpMixed, RuntimeException, +}; + +use crate::package::archiver::archivable_files_filter::ArchivableFilesFilter; +use crate::package::archiver::archivable_files_finder::ArchivableFilesFinder; +use crate::package::archiver::archiver_interface::ArchiverInterface; + +fn formats() -> IndexMap<&'static str, i64> { + let mut m = IndexMap::new(); + m.insert("zip", Phar::ZIP); + m.insert("tar", Phar::TAR); + m.insert("tar.gz", Phar::TAR); + m.insert("tar.bz2", Phar::TAR); + m +} + +fn compress_formats() -> IndexMap<&'static str, i64> { + let mut m = IndexMap::new(); + m.insert("tar.gz", Phar::GZ); + m.insert("tar.bz2", Phar::BZ2); + m +} + +#[derive(Debug)] +pub struct PharArchiver; + +impl ArchiverInterface for PharArchiver { + fn archive( + &self, + sources: String, + target: String, + format: String, + excludes: Vec<String>, + ignore_filters: bool, + ) -> anyhow::Result<String> { + let sources = shirabe_php_shim::realpath(&sources).unwrap_or(sources); + let formats = formats(); + let compress_formats = compress_formats(); + + if file_exists(&target) { + unlink(&target); + } + + let inner = (|| -> anyhow::Result<String> { + let pos = strrpos(&target, &format).unwrap_or(target.len()); + let filename = target[..pos.saturating_sub(1)].to_string(); + + let target = if compress_formats.contains_key(format.as_str()) { + format!("{}.tar", filename) + } else { + target + }; + + let phar = PharData::new_with_format( + target.clone(), + FilesystemIterator::KEY_AS_PATHNAME | FilesystemIterator::CURRENT_AS_FILEINFO, + "", + *formats.get(format.as_str()).unwrap_or(&Phar::TAR), + ); + let files = ArchivableFilesFinder::new(&sources, excludes, ignore_filters)?; + let mut files_only = ArchivableFilesFilter::new(files); + phar.build_from_iterator(&mut files_only, &sources); + files_only.add_empty_dir(&phar, &sources); + + if !file_exists(&target) { + let target = format!("{}.{}", filename, format); + drop(phar); + + if format == "tar" { + // create an empty tar file (=10240 null bytes) if the tar file is empty and PharData thus did not write it to disk + file_put_contents(&target, &str_repeat("\0", 10240).into_bytes()); + } else if format == "zip" { + // create minimal valid ZIP file (Empty Central Directory + End of Central Directory record) + let eocd = pack( + "VvvvvVVv", + &[ + PhpMixed::Int(0x06054b50), // End of central directory signature + PhpMixed::Int(0), // Number of this disk + PhpMixed::Int(0), // Disk where central directory starts + PhpMixed::Int(0), // Number of central directory records on this disk + PhpMixed::Int(0), // Total number of central directory records + PhpMixed::Int(0), // Size of central directory (bytes) + PhpMixed::Int(0), // Offset of start of central directory + PhpMixed::Int(0), // Comment length + ], + ); + file_put_contents(&target, &eocd); + } else if format == "tar.gz" || format == "tar.bz2" { + let compress_algo = *compress_formats.get(format.as_str()).unwrap(); + if !PharData::can_compress(compress_algo) { + return Err(RuntimeException { + message: format!("Can not compress to {} format", format), + code: 0, + } + .into()); + } + if format == "tar.gz" && function_exists("gzcompress") { + let data = gzcompress(&str_repeat("\0", 10240).into_bytes()).unwrap_or_default(); + file_put_contents(&target, &data); + } else if format == "tar.bz2" && function_exists("bzcompress") { + let data = bzcompress(&str_repeat("\0", 10240).into_bytes()).unwrap_or_default(); + file_put_contents(&target, &data); + } + } + + return Ok(target); + } + + if compress_formats.contains_key(format.as_str()) { + let compress_algo = *compress_formats.get(format.as_str()).unwrap(); + if !PharData::can_compress(compress_algo) { + return Err(RuntimeException { + message: format!("Can not compress to {} format", format), + code: 0, + } + .into()); + } + + unlink(&target); + + phar.compress(compress_algo); + + let target = format!("{}.{}", filename, format); + return Ok(target); + } + + Ok(target) + })(); + + inner.map_err(|e| { + let message = format!( + "Could not create archive '{}' from '{}': {}", + target, sources, e + ); + anyhow::anyhow!(RuntimeException { message, code: 0 }) + }) + } + + fn supports(&self, format: String, _source_type: Option<String>) -> bool { + formats().contains_key(format.as_str()) + } +} |
