diff options
Diffstat (limited to 'crates')
| -rw-r--r-- | crates/shirabe-php-shim/src/lib.rs | 56 | ||||
| -rw-r--r-- | crates/shirabe/src/package/archiver/phar_archiver.rs | 146 |
2 files changed, 202 insertions, 0 deletions
diff --git a/crates/shirabe-php-shim/src/lib.rs b/crates/shirabe-php-shim/src/lib.rs index 072d458..ffd6edf 100644 --- a/crates/shirabe-php-shim/src/lib.rs +++ b/crates/shirabe-php-shim/src/lib.rs @@ -240,6 +240,11 @@ pub struct Phar { } impl Phar { + pub const ZIP: i64 = 1; + pub const TAR: i64 = 2; + pub const GZ: i64 = 4096; + pub const BZ2: i64 = 8192; + pub fn new(a: String) -> Self { todo!() } @@ -276,6 +281,14 @@ impl PharData { todo!() } + pub fn new_with_format(path: String, flags: i64, alias: &str, format: i64) -> Self { + todo!() + } + + pub fn can_compress(algo: i64) -> bool { + todo!() + } + pub fn valid(&self) -> bool { todo!() } @@ -296,6 +309,14 @@ impl PharData { pub fn add_empty_dir(&self, a: &str) { todo!() } + + pub fn build_from_iterator(&self, iter: &mut dyn Iterator<Item = std::path::PathBuf>, base: &str) { + todo!() + } + + pub fn compress(&self, algo: i64) { + todo!() + } } #[derive(Debug)] @@ -525,3 +546,38 @@ pub fn hash_file(algo: &str, filename: &str) -> Option<String> { pub fn strnatcasecmp(s1: &str, s2: &str) -> i64 { todo!() } + +pub fn file_exists(path: &str) -> bool { + todo!() +} + +pub fn unlink(path: &str) -> bool { + todo!() +} + +pub fn file_put_contents(path: &str, data: &[u8]) -> Option<i64> { + todo!() +} + +pub fn str_repeat(s: &str, count: usize) -> String { + todo!() +} + +pub fn strrpos(haystack: &str, needle: &str) -> Option<usize> { + todo!() +} + +pub fn gzcompress(data: &[u8]) -> Option<Vec<u8>> { + todo!() +} + +pub fn bzcompress(data: &[u8]) -> Option<Vec<u8>> { + todo!() +} + +pub struct FilesystemIterator; + +impl FilesystemIterator { + pub const KEY_AS_PATHNAME: i64 = 256; + pub const CURRENT_AS_FILEINFO: i64 = 0; +} 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()) + } +} |
