aboutsummaryrefslogtreecommitdiffhomepage
path: root/crates/shirabe-php-shim/src
diff options
context:
space:
mode:
Diffstat (limited to 'crates/shirabe-php-shim/src')
-rw-r--r--crates/shirabe-php-shim/src/fs.rs43
-rw-r--r--crates/shirabe-php-shim/src/stream.rs12
-rw-r--r--crates/shirabe-php-shim/src/string.rs11
3 files changed, 64 insertions, 2 deletions
diff --git a/crates/shirabe-php-shim/src/fs.rs b/crates/shirabe-php-shim/src/fs.rs
index bd5e650a..2142b0e7 100644
--- a/crates/shirabe-php-shim/src/fs.rs
+++ b/crates/shirabe-php-shim/src/fs.rs
@@ -43,6 +43,35 @@ pub struct FilesystemIterator;
impl FilesystemIterator {
pub const KEY_AS_PATHNAME: i64 = 256;
pub const CURRENT_AS_FILEINFO: i64 = 0;
+ pub const CURRENT_AS_PATHNAME: i64 = 32;
+ pub const SKIP_DOTS: i64 = 4096;
+}
+
+/// PHP `new \FilesystemIterator($path, $flags)` flattened to the entries it yields: the direct
+/// children of `path`, in readdir order, without descending into subdirectories.
+pub fn filesystem_iterator(
+ path: impl AsRef<std::path::Path>,
+ flags: i64,
+) -> Result<Vec<String>, UnexpectedValueException> {
+ assert!(
+ flags & FilesystemIterator::CURRENT_AS_PATHNAME != 0,
+ "filesystem_iterator yields pathnames, so CURRENT_AS_PATHNAME must be set"
+ );
+ assert!(
+ flags & FilesystemIterator::SKIP_DOTS != 0,
+ "filesystem_iterator does not model the \".\" and \"..\" entries, so SKIP_DOTS must be set"
+ );
+ let base = path.as_ref();
+ let rd = std::fs::read_dir(base).map_err(|_| {
+ UnexpectedValueException::new(format!(
+ "FilesystemIterator::__construct({}): Failed to open directory",
+ base.display()
+ ))
+ })?;
+ Ok(rd
+ .flatten()
+ .map(|entry| entry.path().to_string_lossy().into_owned())
+ .collect())
}
#[derive(Debug, Clone)]
@@ -161,6 +190,11 @@ impl RecursiveIteratorFileInfo {
self.path.to_string_lossy().into_owned()
}
+ // SplFileInfo::getLinkTarget(): readlink() on the entry. None is PHP's false-on-failure.
+ pub fn get_link_target(&self) -> Option<String> {
+ readlink(&self.path)
+ }
+
pub fn get_size(&self) -> i64 {
std::fs::metadata(&self.path)
.map(|m| m.len() as i64)
@@ -818,6 +852,15 @@ pub fn is_dir(path: impl AsRef<std::path::Path>) -> bool {
path.as_ref().is_dir()
}
+/// PHP `readlink()`: the target the link points at, without resolving it further.
+/// `None` is PHP's `false`-on-failure.
+/// TODO(phase-e): byte-string semantics -- PHP returns the raw bytes of the link target.
+pub fn readlink(path: impl AsRef<std::path::Path>) -> Option<String> {
+ std::fs::read_link(path)
+ .ok()
+ .map(|target| target.to_string_lossy().into_owned())
+}
+
pub fn fileatime(_filename: impl AsRef<std::path::Path>) -> Option<i64> {
std::fs::metadata(_filename.as_ref())
.ok()
diff --git a/crates/shirabe-php-shim/src/stream.rs b/crates/shirabe-php-shim/src/stream.rs
index daf4c0af..b699b0ae 100644
--- a/crates/shirabe-php-shim/src/stream.rs
+++ b/crates/shirabe-php-shim/src/stream.rs
@@ -85,6 +85,18 @@ pub fn stream_isatty(stream: PhpResource) -> bool {
stream_isatty_resource(&stream)
}
+/// PHP `stream_is_local()`: true for plain paths and the `file://` wrapper, false for remote
+/// wrappers (`http://`, `ftp://`, ...).
+/// TODO(phase-c): PHP asks the wrapper registered for the path's scheme whether it is flagged
+/// `STREAM_IS_URL`; this classifies by the scheme itself, so a registered custom wrapper claiming to
+/// be local (or vice versa) comes out differently than in PHP.
+pub fn stream_is_local(path: &str) -> bool {
+ match crate::parse_url(path, crate::PHP_URL_SCHEME) {
+ PhpMixed::String(scheme) => scheme.eq_ignore_ascii_case("file"),
+ _ => true,
+ }
+}
+
pub fn stream_get_wrappers() -> Vec<String> {
// The full registered set depends on compiled-in extensions and runtime
// `stream_wrapper_register` calls, which are not modeled. We return the wrappers always
diff --git a/crates/shirabe-php-shim/src/string.rs b/crates/shirabe-php-shim/src/string.rs
index 78688eed..a322e36f 100644
--- a/crates/shirabe-php-shim/src/string.rs
+++ b/crates/shirabe-php-shim/src/string.rs
@@ -179,6 +179,13 @@ pub fn strrpos(_haystack: &str, _needle: &str) -> Option<usize> {
_haystack.rfind(_needle)
}
+// Byte-based, matching PHP: strrev() reverses the bytes, not the characters.
+pub fn strrev(s: &str) -> String {
+ let mut bytes = s.as_bytes().to_vec();
+ bytes.reverse();
+ String::from_utf8_lossy(&bytes).into_owned()
+}
+
pub fn strtolower(_s: &str) -> String {
_s.to_ascii_lowercase()
}
@@ -485,9 +492,9 @@ pub fn urlencode(s: &str) -> String {
out
}
-pub fn base64_encode(_data: &str) -> String {
+pub fn base64_encode(_data: impl AsRef<[u8]>) -> String {
const TABLE: &[u8; 64] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
- let bytes = _data.as_bytes();
+ let bytes = _data.as_ref();
let mut out = String::with_capacity(bytes.len().div_ceil(3) * 4);
for chunk in bytes.chunks(3) {
let b1 = chunk.get(1).copied();