aboutsummaryrefslogtreecommitdiffhomepage
path: root/crates/shirabe-php-shim/src
diff options
context:
space:
mode:
authornsfisis <nsfisis@gmail.com>2026-08-06 03:46:41 +0900
committernsfisis <nsfisis@gmail.com>2026-08-06 03:46:41 +0900
commit92d2199afcecd0056e82d2559700769715401ef0 (patch)
treeb9d65620db53bc19f800247660e560f3f8775217 /crates/shirabe-php-shim/src
parent27e81d5e5ca0a89eb176a65b1d9f186658b16f50 (diff)
downloadphp-shirabe-92d2199afcecd0056e82d2559700769715401ef0.tar.gz
php-shirabe-92d2199afcecd0056e82d2559700769715401ef0.tar.zst
php-shirabe-92d2199afcecd0056e82d2559700769715401ef0.zip
refactor(php-shim): take filesystem paths as impl AsRef<Path>
The shim's filesystem entry points took `&str` even though each one resolves to a local path through `std::fs` or a syscall, so callers holding a `PathBuf` had to stringify it at the call site. They now take `impl AsRef<Path>`, the form `file_exists`, `is_dir`, `unlink` and the rest of the already-converted set use. `Phar`, `PharData` and `ZipArchive` keep their archive path as a `PathBuf`. `PharData::compress` names the compressed sibling by appending the suffix to the file name rather than formatting the path into a `String`. Arguments PHP resolves through a stream wrapper (`fopen`, `file_put_contents`, `include`) still take `&str`, as do the byte-string operations (`dirname`, `basename`, `pathinfo`) and archive-internal entry names, which are `/`-joined logical names rather than OS paths. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Diffstat (limited to 'crates/shirabe-php-shim/src')
-rw-r--r--crates/shirabe-php-shim/src/compress.rs4
-rw-r--r--crates/shirabe-php-shim/src/fs.rs102
-rw-r--r--crates/shirabe-php-shim/src/phar.rs128
-rw-r--r--crates/shirabe-php-shim/src/process.rs2
-rw-r--r--crates/shirabe-php-shim/src/rar.rs4
-rw-r--r--crates/shirabe-php-shim/src/stream.rs4
-rw-r--r--crates/shirabe-php-shim/src/string.rs4
-rw-r--r--crates/shirabe-php-shim/src/zip.rs15
8 files changed, 150 insertions, 113 deletions
diff --git a/crates/shirabe-php-shim/src/compress.rs b/crates/shirabe-php-shim/src/compress.rs
index d62be7c6..378045b0 100644
--- a/crates/shirabe-php-shim/src/compress.rs
+++ b/crates/shirabe-php-shim/src/compress.rs
@@ -7,12 +7,12 @@ use std::io::Write as _;
#[derive(Debug, Clone)]
pub struct GzFile(std::rc::Rc<std::cell::RefCell<flate2::read::MultiGzDecoder<std::fs::File>>>);
-pub fn gzopen(file: &str, mode: &str) -> Result<GzFile, std::io::Error> {
+pub fn gzopen(file: impl AsRef<std::path::Path>, mode: &str) -> Result<GzFile, std::io::Error> {
assert!(
mode.starts_with('r'),
"gzopen: only read modes are supported (got {mode:?})"
);
- let f = std::fs::File::open(file)?;
+ let f = std::fs::File::open(file.as_ref())?;
Ok(GzFile(std::rc::Rc::new(std::cell::RefCell::new(
flate2::read::MultiGzDecoder::new(f),
))))
diff --git a/crates/shirabe-php-shim/src/fs.rs b/crates/shirabe-php-shim/src/fs.rs
index 3cc2df36..9303ad21 100644
--- a/crates/shirabe-php-shim/src/fs.rs
+++ b/crates/shirabe-php-shim/src/fs.rs
@@ -178,7 +178,7 @@ pub fn recursive_directory_iterator(
return Err(UnexpectedValueException {
message: format!(
"RecursiveDirectoryIterator::__construct({}): Failed to open directory",
- root.to_string_lossy()
+ root.display()
),
code: 0,
});
@@ -243,13 +243,13 @@ fn rii_walk(
}
pub fn directory_iterator(
- path: &str,
+ path: impl AsRef<std::path::Path>,
) -> Result<Vec<DirectoryIteratorEntry>, UnexpectedValueException> {
- let base = std::path::Path::new(path);
+ let base = path.as_ref();
let rd = std::fs::read_dir(base).map_err(|_| UnexpectedValueException {
message: format!(
"DirectoryIterator::__construct({}): Failed to open directory",
- path
+ base.display()
),
code: 0,
})?;
@@ -681,7 +681,7 @@ pub fn fflush(stream: &PhpResource) -> bool {
}
}
-pub fn lstat(_filename: &str) -> Option<IndexMap<String, PhpMixed>> {
+pub fn lstat(_filename: impl AsRef<std::path::Path>) -> Option<IndexMap<String, PhpMixed>> {
use std::os::unix::fs::MetadataExt;
let m = std::fs::symlink_metadata(_filename).ok()?;
Some(stat_fields_map([
@@ -703,7 +703,8 @@ pub fn lstat(_filename: &str) -> Option<IndexMap<String, PhpMixed>> {
/// PHP `touch($path)`: creates the file when it is missing and stamps mtime/atime with the current
/// time. Returns `false` (PHP failure) on error.
-pub fn touch(path: &str) -> bool {
+pub fn touch(path: impl AsRef<std::path::Path>) -> bool {
+ let path = path.as_ref();
if !touch_create(path) {
return false;
}
@@ -724,8 +725,8 @@ pub fn fwrite_resource(resource: &PhpResource, data: &str) {
/// PHP's `touch` creates the file first if it does not exist. An existing path is never opened, so
/// directories — which `utimes` stamps just as well — go through untouched.
-fn touch_create(path: &str) -> bool {
- if std::path::Path::new(path).exists() {
+fn touch_create(path: &std::path::Path) -> bool {
+ if path.exists() {
return true;
}
std::fs::OpenOptions::new()
@@ -737,7 +738,7 @@ fn touch_create(path: &str) -> bool {
}
// PHP's `touch($path, $mtime, $atime)` passes whole seconds.
-fn touch_impl(path: &str, mtime: i64, atime: i64) -> bool {
+fn touch_impl(path: &std::path::Path, mtime: i64, atime: i64) -> bool {
if !touch_create(path) {
return false;
}
@@ -748,25 +749,25 @@ fn touch_impl(path: &str, mtime: i64, atime: i64) -> bool {
/// PHP `touch($path, $mtime)`: sets the modification time (and access time, per PHP, to the same
/// value). Returns `false` (PHP failure) on error.
-pub fn touch2(path: &str, mtime: i64) -> bool {
- touch_impl(path, mtime, mtime)
+pub fn touch2(path: impl AsRef<std::path::Path>, mtime: i64) -> bool {
+ touch_impl(path.as_ref(), mtime, mtime)
}
/// PHP `touch($path, $mtime, $atime)`.
-pub fn touch3(path: &str, mtime: i64, atime: i64) -> bool {
- touch_impl(path, mtime, atime)
+pub fn touch3(path: impl AsRef<std::path::Path>, mtime: i64, atime: i64) -> bool {
+ touch_impl(path.as_ref(), mtime, atime)
}
-pub fn chmod(_path: &str, _mode: u32) -> bool {
+pub fn chmod(_path: impl AsRef<std::path::Path>, _mode: u32) -> bool {
use std::os::unix::fs::PermissionsExt;
- std::fs::set_permissions(_path, std::fs::Permissions::from_mode(_mode)).is_ok()
+ std::fs::set_permissions(_path.as_ref(), std::fs::Permissions::from_mode(_mode)).is_ok()
}
-pub fn fileperms(_path: &str) -> i64 {
+pub fn fileperms(_path: impl AsRef<std::path::Path>) -> i64 {
use std::os::unix::fs::MetadataExt;
// PHP returns the full st_mode (file type bits included).
// TODO(phase-c): PHP returns false on error; this i64 signature reports 0 instead.
- std::fs::metadata(_path)
+ std::fs::metadata(_path.as_ref())
.map(|m| m.mode() as i64)
.unwrap_or(0)
}
@@ -779,12 +780,12 @@ pub fn file_exists(path: impl AsRef<std::path::Path>) -> bool {
path.as_ref().exists()
}
-pub fn is_writable(_path: &str) -> bool {
- nix::unistd::access(_path, nix::unistd::AccessFlags::W_OK).is_ok()
+pub fn is_writable(_path: impl AsRef<std::path::Path>) -> bool {
+ nix::unistd::access(_path.as_ref(), nix::unistd::AccessFlags::W_OK).is_ok()
}
-pub fn is_readable(_path: &str) -> bool {
- let path = std::path::Path::new(_path);
+pub fn is_readable(_path: impl AsRef<std::path::Path>) -> bool {
+ let path = _path.as_ref();
match std::fs::metadata(path) {
Ok(meta) => {
if meta.is_dir() {
@@ -797,8 +798,8 @@ pub fn is_readable(_path: &str) -> bool {
}
}
-pub fn is_executable(_path: &str) -> bool {
- nix::unistd::access(_path, nix::unistd::AccessFlags::X_OK).is_ok()
+pub fn is_executable(_path: impl AsRef<std::path::Path>) -> bool {
+ nix::unistd::access(_path.as_ref(), nix::unistd::AccessFlags::X_OK).is_ok()
}
pub fn is_file(path: impl AsRef<std::path::Path>) -> bool {
@@ -815,34 +816,36 @@ pub fn is_dir(path: impl AsRef<std::path::Path>) -> bool {
path.as_ref().is_dir()
}
-pub fn fileatime(_filename: &str) -> Option<i64> {
- std::fs::metadata(_filename)
+pub fn fileatime(_filename: impl AsRef<std::path::Path>) -> Option<i64> {
+ std::fs::metadata(_filename.as_ref())
.ok()
.and_then(|m| m.accessed().ok())
.and_then(|t| t.duration_since(std::time::UNIX_EPOCH).ok())
.map(|d| d.as_secs() as i64)
}
-pub fn filemtime(_filename: &str) -> Option<i64> {
- std::fs::metadata(_filename)
+pub fn filemtime(_filename: impl AsRef<std::path::Path>) -> Option<i64> {
+ std::fs::metadata(_filename.as_ref())
.ok()
.and_then(|m| m.modified().ok())
.and_then(|t| t.duration_since(std::time::UNIX_EPOCH).ok())
.map(|d| d.as_secs() as i64)
}
-pub fn fileowner(_filename: &str) -> Option<i64> {
+pub fn fileowner(_filename: impl AsRef<std::path::Path>) -> Option<i64> {
use std::os::unix::fs::MetadataExt;
- std::fs::metadata(_filename).ok().map(|m| m.uid() as i64)
+ std::fs::metadata(_filename.as_ref())
+ .ok()
+ .map(|m| m.uid() as i64)
}
pub fn unlink(path: impl AsRef<std::path::Path>) -> bool {
std::fs::remove_file(path).is_ok()
}
-pub fn unlink_silent(_path: &str) -> bool {
+pub fn unlink_silent(_path: impl AsRef<std::path::Path>) -> bool {
// PHP's `@unlink`: delete the file, suppressing any warning.
- std::fs::remove_file(_path).is_ok()
+ std::fs::remove_file(_path.as_ref()).is_ok()
}
pub fn file_put_contents(_path: &str, _data: &[u8]) -> Option<i64> {
@@ -911,8 +914,8 @@ pub fn getcwd() -> Option<String> {
.map(|p| p.to_string_lossy().into_owned())
}
-pub fn chdir(_path: &str) -> anyhow::Result<()> {
- Ok(std::env::set_current_dir(_path)?)
+pub fn chdir(_path: impl AsRef<std::path::Path>) -> anyhow::Result<()> {
+ Ok(std::env::set_current_dir(_path.as_ref())?)
}
pub fn glob(_pattern: &str) -> Vec<String> {
@@ -966,12 +969,12 @@ pub fn umask() -> u32 {
previous.bits() as u32
}
-pub fn mkdir(_pathname: &str, _mode: u32, _recursive: bool) -> bool {
+pub fn mkdir(_pathname: impl AsRef<std::path::Path>, _mode: u32, _recursive: bool) -> bool {
use std::os::unix::fs::DirBuilderExt;
// DirBuilder::mode passes the mode to mkdir(2), which applies the process umask, matching PHP.
let mut builder = std::fs::DirBuilder::new();
builder.mode(_mode).recursive(_recursive);
- builder.create(_pathname).is_ok()
+ builder.create(_pathname.as_ref()).is_ok()
}
pub fn rmdir(dir: impl AsRef<std::path::Path>) -> bool {
@@ -985,8 +988,8 @@ pub fn rename(
std::fs::rename(old_name, new_name).is_ok()
}
-pub fn copy(_source: &str, _dest: &str) -> bool {
- std::fs::copy(_source, _dest).is_ok()
+pub fn copy(_source: impl AsRef<std::path::Path>, _dest: impl AsRef<std::path::Path>) -> bool {
+ std::fs::copy(_source.as_ref(), _dest.as_ref()).is_ok()
}
pub fn ftruncate(stream: &PhpResource, size: i64) -> bool {
@@ -1015,21 +1018,21 @@ pub fn ftruncate(stream: &PhpResource, size: i64) -> bool {
}
}
-pub fn symlink(_target: &str, _link: &str) -> bool {
- std::os::unix::fs::symlink(_target, _link).is_ok()
+pub fn symlink(_target: impl AsRef<std::path::Path>, _link: impl AsRef<std::path::Path>) -> bool {
+ std::os::unix::fs::symlink(_target.as_ref(), _link.as_ref()).is_ok()
}
pub fn sys_get_temp_dir() -> String {
std::env::temp_dir().to_string_lossy().into_owned()
}
-pub fn tempnam(_dir: &str, _prefix: &str) -> Option<String> {
+pub fn tempnam(_dir: impl AsRef<std::path::Path>, _prefix: &str) -> Option<String> {
use std::os::unix::fs::PermissionsExt;
// TODO(phase-c): PHP falls back to the system temp dir when $dir is not writable; that fallback
// is not implemented here.
for _ in 0..1000 {
let name = format!("{}{:08x}", _prefix, fastrand::u32(..));
- let path = std::path::Path::new(_dir).join(name);
+ let path = _dir.as_ref().join(name);
match std::fs::OpenOptions::new()
.write(true)
.create_new(true)
@@ -1054,11 +1057,12 @@ pub struct PhpDirHandle {
pub path: std::path::PathBuf,
}
-pub fn opendir(path: &str) -> Option<PhpDirHandle> {
+pub fn opendir(path: impl AsRef<std::path::Path>) -> Option<PhpDirHandle> {
+ let path = path.as_ref();
// opendir succeeds iff the path is a readable directory.
std::fs::read_dir(path).ok()?;
Some(PhpDirHandle {
- path: std::path::PathBuf::from(path),
+ path: path.to_path_buf(),
})
}
@@ -1086,9 +1090,9 @@ pub fn pathinfo(path: PhpMixed, option: i64) -> PhpMixed {
PhpMixed::String(component)
}
-// TODO(phase-c): takes &Path and returns Option<PathBuf>
-pub fn realpath(path: &str) -> Option<String> {
- std::path::Path::new(path)
+// TODO(phase-c): returns Option<PathBuf>
+pub fn realpath(path: impl AsRef<std::path::Path>) -> Option<String> {
+ path.as_ref()
.canonicalize()
.ok()
.and_then(|p| p.to_str().map(ToOwned::to_owned))
@@ -1140,15 +1144,15 @@ pub fn clearstatcache() {
// cache to invalidate.
}
-pub fn clearstatcache2(_clear_realpath_cache: bool, _filename: &str) {
+pub fn clearstatcache2(_clear_realpath_cache: bool, _filename: impl AsRef<std::path::Path>) {
// Rust performs a fresh syscall for every metadata query; there is no stat
// cache to invalidate.
}
/// PHP `disk_free_space()`: the number of available bytes on the filesystem containing `directory`,
/// computed via `statvfs(3)` (`f_bavail * f_frsize`). Returns `None` (PHP `false`) on failure.
-pub fn disk_free_space(directory: &str) -> Option<f64> {
- let stat = nix::sys::statvfs::statvfs(directory).ok()?;
+pub fn disk_free_space(directory: impl AsRef<std::path::Path>) -> Option<f64> {
+ let stat = nix::sys::statvfs::statvfs(directory.as_ref()).ok()?;
Some(stat.blocks_available() as f64 * stat.fragment_size() as f64)
}
diff --git a/crates/shirabe-php-shim/src/phar.rs b/crates/shirabe-php-shim/src/phar.rs
index 6e4270ac..0387a0de 100644
--- a/crates/shirabe-php-shim/src/phar.rs
+++ b/crates/shirabe-php-shim/src/phar.rs
@@ -22,16 +22,20 @@ struct PharEntry {
mtime: Option<u64>,
}
-fn corruption_error(path: &str, detail: &str) -> anyhow::Error {
+fn corruption_error(path: &std::path::Path, detail: &str) -> anyhow::Error {
anyhow::anyhow!(UnexpectedValueException {
- message: format!("internal corruption of phar \"{}\" ({})", path, detail),
+ message: format!(
+ "internal corruption of phar \"{}\" ({})",
+ path.display(),
+ detail
+ ),
code: 0,
})
}
/// Reads a tar- or zip-based archive (optionally gzip/bzip2 compressed as a whole)
/// into memory. Returns the entries and the detected `Phar::TAR`/`Phar::ZIP` format.
-fn read_archive_entries(path: &str) -> anyhow::Result<(Vec<PharEntry>, i64)> {
+fn read_archive_entries(path: &std::path::Path) -> anyhow::Result<(Vec<PharEntry>, i64)> {
let bytes = std::fs::read(path)
.map_err(|e| corruption_error(path, &format!("unable to open archive: {}", e)))?;
let bytes = if bytes.starts_with(&[0x1f, 0x8b]) {
@@ -127,6 +131,14 @@ fn read_archive_entries(path: &str) -> anyhow::Result<(Vec<PharEntry>, i64)> {
Ok((entries, Phar::TAR))
}
+/// Appends `suffix` to the file name, as phar does when it names the compressed archive
+/// (`out.tar` + `.gz` = `out.tar.gz`).
+fn sibling_with_suffix(path: &std::path::Path, suffix: &str) -> std::path::PathBuf {
+ let mut name = path.to_path_buf().into_os_string();
+ name.push(suffix);
+ std::path::PathBuf::from(name)
+}
+
fn unix_mtime(time: std::time::SystemTime) -> u64 {
time.duration_since(std::time::UNIX_EPOCH)
.map(|d| d.as_secs())
@@ -134,23 +146,23 @@ fn unix_mtime(time: std::time::SystemTime) -> u64 {
}
fn extract_entries(
- archive_path: &str,
+ archive_path: &std::path::Path,
entries: &[PharEntry],
- directory: &str,
+ directory: &std::path::Path,
overwrite: bool,
) -> anyhow::Result<()> {
let extract_error = |detail: String| {
anyhow::anyhow!(PharException {
message: format!(
"Extracting from phar \"{}\" failed: {}",
- archive_path, detail
+ archive_path.display(),
+ detail
),
code: 0,
})
};
- let base = std::path::Path::new(directory);
- std::fs::create_dir_all(base).map_err(|e| extract_error(e.to_string()))?;
+ std::fs::create_dir_all(directory).map_err(|e| extract_error(e.to_string()))?;
for entry in entries {
let rel = std::path::Path::new(&entry.localname);
if rel.is_absolute()
@@ -163,7 +175,7 @@ fn extract_entries(
entry.localname
)));
}
- let dest = base.join(rel);
+ let dest = directory.join(rel);
match &entry.data {
PharEntryData::Dir => {
std::fs::create_dir_all(&dest).map_err(|e| extract_error(e.to_string()))?;
@@ -245,7 +257,7 @@ const PHAR_HAS_SIGNATURE: u32 = 0x0001_0000;
const PHAR_FILE_COMPRESSED_GZ: u32 = 0x0000_1000;
const PHAR_FILE_COMPRESSED_BZ2: u32 = 0x0000_2000;
-fn verify_phar_signature(path: &str, bytes: &[u8]) -> anyhow::Result<()> {
+fn verify_phar_signature(path: &std::path::Path, bytes: &[u8]) -> anyhow::Result<()> {
let broken = || corruption_error(path, "phar has a broken or missing signature");
let n = bytes.len();
if n < 8 || &bytes[n - 4..] != b"GBMB" {
@@ -278,7 +290,7 @@ fn verify_phar_signature(path: &str, bytes: &[u8]) -> anyhow::Result<()> {
Ok(())
}
-fn parse_native_phar(path: &str) -> anyhow::Result<Vec<PharEntry>> {
+fn parse_native_phar(path: &std::path::Path) -> anyhow::Result<Vec<PharEntry>> {
let bytes = std::fs::read(path)
.map_err(|e| corruption_error(path, &format!("unable to open phar: {}", e)))?;
@@ -401,7 +413,7 @@ fn parse_native_phar(path: &str) -> anyhow::Result<Vec<PharEntry>> {
#[derive(Debug)]
pub struct Phar {
- path: String,
+ path: std::path::PathBuf,
entries: Vec<PharEntry>,
}
@@ -411,18 +423,19 @@ impl Phar {
pub const GZ: i64 = 4096;
pub const BZ2: i64 = 8192;
- pub fn new(path: String) -> anyhow::Result<Self> {
+ pub fn new(path: impl AsRef<std::path::Path>) -> anyhow::Result<Self> {
+ let path = path.as_ref().to_path_buf();
let entries = parse_native_phar(&path)?;
Ok(Self { path, entries })
}
pub fn extract_to(
&self,
- directory: &str,
+ directory: impl AsRef<std::path::Path>,
_files: Option<()>,
overwrite: bool,
) -> anyhow::Result<()> {
- extract_entries(&self.path, &self.entries, directory, overwrite)
+ extract_entries(&self.path, &self.entries, directory.as_ref(), overwrite)
}
}
@@ -467,26 +480,26 @@ impl PharFileInfo {
#[derive(Debug)]
pub struct PharData {
- path: String,
+ path: std::path::PathBuf,
format: i64,
entries: std::cell::RefCell<Vec<PharEntry>>,
}
impl PharData {
- pub fn new(path: String) -> anyhow::Result<Self> {
- Self::open(path, None)
+ pub fn new(path: impl AsRef<std::path::Path>) -> anyhow::Result<Self> {
+ Self::open(path.as_ref().to_path_buf(), None)
}
pub fn new_with_format(
- path: String,
+ path: impl AsRef<std::path::Path>,
_flags: i64,
_alias: &str,
format: i64,
) -> anyhow::Result<Self> {
- Self::open(path, Some(format))
+ Self::open(path.as_ref().to_path_buf(), Some(format))
}
- fn open(path: String, format: Option<i64>) -> anyhow::Result<Self> {
+ fn open(path: std::path::PathBuf, format: Option<i64>) -> anyhow::Result<Self> {
if crate::file_exists(&path) {
let (entries, detected_format) = read_archive_entries(&path)?;
return Ok(Self {
@@ -495,7 +508,7 @@ impl PharData {
entries: std::cell::RefCell::new(entries),
});
}
- let parent_exists = match std::path::Path::new(&path).parent() {
+ let parent_exists = match path.parent() {
Some(parent) if parent.as_os_str().is_empty() => true,
Some(parent) => parent.is_dir(),
None => false,
@@ -504,12 +517,12 @@ impl PharData {
return Err(anyhow::anyhow!(UnexpectedValueException {
message: format!(
"Cannot create phar '{}', file extension (or combination) not recognised or the directory does not exist",
- path
+ path.display()
),
code: 0,
}));
}
- let format = format.unwrap_or(if path.ends_with(".zip") {
+ let format = format.unwrap_or(if path.to_string_lossy().ends_with(".zip") {
Phar::ZIP
} else {
Phar::TAR
@@ -595,11 +608,16 @@ impl PharData {
pub fn extract_to(
&self,
- directory: &str,
+ directory: impl AsRef<std::path::Path>,
_files: Option<()>,
overwrite: bool,
) -> anyhow::Result<()> {
- extract_entries(&self.path, &self.entries.borrow(), directory, overwrite)
+ extract_entries(
+ &self.path,
+ &self.entries.borrow(),
+ directory.as_ref(),
+ overwrite,
+ )
}
pub fn add_empty_dir(&self, dirname: &str) -> anyhow::Result<()> {
@@ -621,8 +639,9 @@ impl PharData {
pub fn build_from_iterator(
&self,
iter: &mut dyn Iterator<Item = std::path::PathBuf>,
- base_directory: &str,
+ base_directory: impl AsRef<std::path::Path>,
) -> anyhow::Result<()> {
+ let base_directory = base_directory.as_ref();
{
let mut entries = self.entries.borrow_mut();
for file in iter {
@@ -633,7 +652,7 @@ impl PharData {
message: format!(
"Iterator returned a path \"{}\" that is not in the base directory \"{}\"",
file.display(),
- base_directory
+ base_directory.display()
),
code: 0,
})
@@ -662,7 +681,11 @@ impl PharData {
let tar_bytes = self.build_tar_bytes()?;
let write_error = |e: std::io::Error| {
anyhow::anyhow!(PharException {
- message: format!("Unable to compress phar archive \"{}\": {}", self.path, e),
+ message: format!(
+ "Unable to compress phar archive \"{}\": {}",
+ self.path.display(),
+ e
+ ),
code: 0,
})
};
@@ -672,7 +695,7 @@ impl PharData {
flate2::write::GzEncoder::new(Vec::new(), flate2::Compression::default());
encoder.write_all(&tar_bytes).map_err(write_error)?;
(
- format!("{}.gz", self.path),
+ sibling_with_suffix(&self.path, ".gz"),
encoder.finish().map_err(write_error)?,
)
}
@@ -681,7 +704,7 @@ impl PharData {
bzip2::write::BzEncoder::new(Vec::new(), bzip2::Compression::new(4));
encoder.write_all(&tar_bytes).map_err(write_error)?;
(
- format!("{}.bz2", self.path),
+ sibling_with_suffix(&self.path, ".bz2"),
encoder.finish().map_err(write_error)?,
)
}
@@ -703,7 +726,11 @@ impl PharData {
let bytes = self.build_tar_bytes()?;
std::fs::write(&self.path, bytes).map_err(|e| {
anyhow::anyhow!(PharException {
- message: format!("Unable to write phar archive \"{}\": {}", self.path, e),
+ message: format!(
+ "Unable to write phar archive \"{}\": {}",
+ self.path.display(),
+ e
+ ),
code: 0,
})
})
@@ -712,7 +739,11 @@ impl PharData {
fn build_tar_bytes(&self) -> anyhow::Result<Vec<u8>> {
let write_error = |e: std::io::Error| {
anyhow::anyhow!(PharException {
- message: format!("Unable to write phar archive \"{}\": {}", self.path, e),
+ message: format!(
+ "Unable to write phar archive \"{}\": {}",
+ self.path.display(),
+ e
+ ),
code: 0,
})
};
@@ -773,7 +804,11 @@ impl PharData {
fn write_zip(&self) -> anyhow::Result<()> {
let write_error = |e: String| {
anyhow::anyhow!(PharException {
- message: format!("Unable to write phar archive \"{}\": {}", self.path, e),
+ message: format!(
+ "Unable to write phar archive \"{}\": {}",
+ self.path.display(),
+ e
+ ),
code: 0,
})
};
@@ -841,14 +876,14 @@ mod tests {
let b = write_file(&src, "sub/b.txt", b"world");
let tar_path = dir.path().join("out.tar");
- let phar = PharData::new(tar_path.to_string_lossy().into_owned()).unwrap();
+ let phar = PharData::new(&tar_path).unwrap();
assert!(!phar.valid());
- phar.build_from_iterator(&mut vec![a, b].into_iter(), src.to_str().unwrap())
+ phar.build_from_iterator(&mut vec![a, b].into_iter(), &src)
.unwrap();
phar.add_empty_dir("emptydir").unwrap();
assert!(tar_path.exists());
- let read_back = PharData::new(tar_path.to_string_lossy().into_owned()).unwrap();
+ let read_back = PharData::new(&tar_path).unwrap();
assert!(read_back.valid());
assert_eq!(read_back.get("a.txt").unwrap().get_content(), b"hello");
assert_eq!(read_back.get("sub/b.txt").unwrap().get_content(), b"world");
@@ -868,9 +903,7 @@ mod tests {
);
let out = dir.path().join("extracted");
- read_back
- .extract_to(out.to_str().unwrap(), None, true)
- .unwrap();
+ read_back.extract_to(&out, None, true).unwrap();
assert_eq!(std::fs::read(out.join("a.txt")).unwrap(), b"hello");
assert_eq!(std::fs::read(out.join("sub/b.txt")).unwrap(), b"world");
assert!(out.join("emptydir").is_dir());
@@ -883,23 +916,22 @@ mod tests {
let a = write_file(&src, "a.txt", b"hello");
let tar_path = dir.path().join("out.tar");
- let phar = PharData::new(tar_path.to_string_lossy().into_owned()).unwrap();
- phar.build_from_iterator(&mut vec![a].into_iter(), src.to_str().unwrap())
+ let phar = PharData::new(&tar_path).unwrap();
+ phar.build_from_iterator(&mut vec![a].into_iter(), &src)
.unwrap();
phar.compress(Phar::GZ).unwrap();
phar.compress(Phar::BZ2).unwrap();
assert!(tar_path.exists());
for compressed in ["out.tar.gz", "out.tar.bz2"] {
- let read_back =
- PharData::new(dir.path().join(compressed).to_string_lossy().into_owned()).unwrap();
+ let read_back = PharData::new(dir.path().join(compressed)).unwrap();
assert_eq!(read_back.get("a.txt").unwrap().get_content(), b"hello");
}
}
#[test]
fn phar_data_missing_file_in_missing_directory_is_rejected() {
- let error = PharData::new("/nonexistent-dir/foo.tar".to_string()).unwrap_err();
+ let error = PharData::new("/nonexistent-dir/foo.tar").unwrap_err();
assert!(
error
.downcast_ref::<UnexpectedValueException>()
@@ -976,9 +1008,9 @@ mod tests {
let phar_path = dir.path().join("test.phar");
std::fs::write(&phar_path, build_native_phar(false)).unwrap();
- let phar = Phar::new(phar_path.to_string_lossy().into_owned()).unwrap();
+ let phar = Phar::new(&phar_path).unwrap();
let out = dir.path().join("extracted");
- phar.extract_to(out.to_str().unwrap(), None, true).unwrap();
+ phar.extract_to(&out, None, true).unwrap();
assert_eq!(
std::fs::read(out.join("dir/hello.txt")).unwrap(),
b"Hello World"
@@ -995,7 +1027,7 @@ mod tests {
let phar_path = dir.path().join("tampered.phar");
std::fs::write(&phar_path, build_native_phar(true)).unwrap();
- let error = Phar::new(phar_path.to_string_lossy().into_owned()).unwrap_err();
+ let error = Phar::new(&phar_path).unwrap_err();
assert!(
error
.downcast_ref::<UnexpectedValueException>()
diff --git a/crates/shirabe-php-shim/src/process.rs b/crates/shirabe-php-shim/src/process.rs
index 1485adf7..38ba9928 100644
--- a/crates/shirabe-php-shim/src/process.rs
+++ b/crates/shirabe-php-shim/src/process.rs
@@ -144,7 +144,7 @@ pub fn proc_open(
command: &str,
descriptorspec: &[Descriptor],
pipes: &mut IndexMap<i64, PhpResource>,
- cwd: Option<&str>,
+ cwd: Option<&std::path::Path>,
env: Option<&[String]>,
options: Option<&IndexMap<String, PhpMixed>>,
) -> std::io::Result<PhpResource> {
diff --git a/crates/shirabe-php-shim/src/rar.rs b/crates/shirabe-php-shim/src/rar.rs
index 10c7e27d..475f6e1d 100644
--- a/crates/shirabe-php-shim/src/rar.rs
+++ b/crates/shirabe-php-shim/src/rar.rs
@@ -2,7 +2,7 @@
pub struct RarEntry;
impl RarEntry {
- pub fn extract(&self, _path: &str) -> bool {
+ pub fn extract(&self, _path: impl AsRef<std::path::Path>) -> bool {
todo!()
}
}
@@ -11,7 +11,7 @@ impl RarEntry {
pub struct RarArchive;
impl RarArchive {
- pub fn open(_file: &str) -> Option<Self> {
+ pub fn open(_file: impl AsRef<std::path::Path>) -> Option<Self> {
todo!()
}
diff --git a/crates/shirabe-php-shim/src/stream.rs b/crates/shirabe-php-shim/src/stream.rs
index e34849ed..50f984b5 100644
--- a/crates/shirabe-php-shim/src/stream.rs
+++ b/crates/shirabe-php-shim/src/stream.rs
@@ -13,10 +13,10 @@ pub fn stream_get_contents(stream: &PhpResource) -> Option<String> {
stream_read_remaining(stream, None)
}
-pub fn stream_resolve_include_path(filename: &str) -> Option<String> {
+pub fn stream_resolve_include_path(filename: impl AsRef<std::path::Path>) -> Option<String> {
// TODO(phase-c): resolution searches the `include_path` ini setting, which the shim does not
// model; checking only the current directory would silently miss configured include paths.
- let _ = filename;
+ let _ = filename.as_ref();
todo!()
}
diff --git a/crates/shirabe-php-shim/src/string.rs b/crates/shirabe-php-shim/src/string.rs
index fcf7baf2..331a4ceb 100644
--- a/crates/shirabe-php-shim/src/string.rs
+++ b/crates/shirabe-php-shim/src/string.rs
@@ -893,14 +893,14 @@ pub fn ucfirst(s: &str) -> String {
}
}
-pub fn php_strip_whitespace(path: &str) -> String {
+pub fn php_strip_whitespace(path: impl AsRef<std::path::Path>) -> String {
// PHP `php_strip_whitespace()` tokenizes the source and re-emits it with comments removed and
// each run of whitespace collapsed to a single space. There is no PHP tokenizer in the shim, so
// this is a hand-written lexer that reproduces the observable effect for the cases the class-map
// generator depends on: it preserves single-quoted, double-quoted, backtick and heredoc/nowdoc
// string contents verbatim while dropping `//`, `#` and `/* */` comments and squeezing
// whitespace. On any read failure it returns an empty string, mirroring `@php_strip_whitespace`.
- let contents = match std::fs::read(path) {
+ let contents = match std::fs::read(path.as_ref()) {
Ok(bytes) => bytes,
Err(_) => return String::new(),
};
diff --git a/crates/shirabe-php-shim/src/zip.rs b/crates/shirabe-php-shim/src/zip.rs
index 83dd70ce..56f02db7 100644
--- a/crates/shirabe-php-shim/src/zip.rs
+++ b/crates/shirabe-php-shim/src/zip.rs
@@ -27,7 +27,7 @@ enum ZipState {
Writer {
writer: zip::ZipWriter<std::fs::File>,
/// The destination path, retained so `close` can confirm the file exists.
- path: String,
+ path: std::path::PathBuf,
status: String,
},
}
@@ -65,10 +65,11 @@ impl ZipArchive {
}
}
- pub fn open(&mut self, filename: &str, flags: i64) -> Result<(), i64> {
+ pub fn open(&mut self, filename: impl AsRef<std::path::Path>, flags: i64) -> Result<(), i64> {
if let Some(mock) = &self.mock {
return mock.open;
}
+ let filename = filename.as_ref();
if flags & Self::CREATE != 0 {
let file = match std::fs::File::create(filename) {
Ok(f) => f,
@@ -76,7 +77,7 @@ impl ZipArchive {
};
*self.state.borrow_mut() = ZipState::Writer {
writer: zip::ZipWriter::new(file),
- path: filename.to_string(),
+ path: filename.to_path_buf(),
status: String::new(),
};
self.num_files = 0;
@@ -143,7 +144,7 @@ impl ZipArchive {
Some(stat)
}
- pub fn extract_to(&self, path: &str) -> Result<bool, ErrorException> {
+ pub fn extract_to(&self, path: impl AsRef<std::path::Path>) -> Result<bool, ErrorException> {
if let Some(mock) = &self.mock {
return mock.extract_to.clone().map_err(|message| ErrorException {
message,
@@ -157,7 +158,7 @@ impl ZipArchive {
let ZipState::Reader(archive) = &mut *state else {
return Ok(false);
};
- Ok(archive.extract(path).is_ok())
+ Ok(archive.extract(path.as_ref()).is_ok())
}
pub fn locate_name(&self, name: &str) -> Option<i64> {
@@ -230,8 +231,8 @@ impl ZipArchive {
.is_ok()
}
- pub fn add_file(&self, filepath: &str, local_name: &str) -> bool {
- let contents = match std::fs::read(filepath) {
+ pub fn add_file(&self, filepath: impl AsRef<std::path::Path>, local_name: &str) -> bool {
+ let contents = match std::fs::read(filepath.as_ref()) {
Ok(c) => c,
Err(_) => return false,
};