From a851d86b4546dae5fec9e0f046c29d2f2bedb857 Mon Sep 17 00:00:00 2001 From: nsfisis Date: Sun, 5 Jul 2026 14:51:32 +0900 Subject: feat(remote-filesystem): support file:// URLs in get_remote_contents get_remote_contents was a full stub always returning None, so any file:// download raised a TransportException. Read local files directly for the file scheme, mirroring PHP's file_get_contents transparently handling the file:// stream wrapper. Also fixes file_get_contents5 to strip the file:// prefix like the 0-arg variant already did. --- crates/shirabe-php-shim/src/fs.rs | 4 +++- crates/shirabe/src/util/remote_filesystem.rs | 24 ++++++++++++++-------- .../shirabe/tests/util/remote_filesystem_test.rs | 2 -- 3 files changed, 19 insertions(+), 11 deletions(-) diff --git a/crates/shirabe-php-shim/src/fs.rs b/crates/shirabe-php-shim/src/fs.rs index e58623a..a20e956 100644 --- a/crates/shirabe-php-shim/src/fs.rs +++ b/crates/shirabe-php-shim/src/fs.rs @@ -881,7 +881,9 @@ pub fn file_get_contents5( ) -> Option { // TODO(phase-d): the stream $context and FILE_USE_INCLUDE_PATH are ignored; only $offset and // $length are applied (to the file read from the local filesystem). - let bytes = std::fs::read(_path).ok()?; + // PHP supports the file:// stream wrapper; strip it to read the local file. + let path = _path.strip_prefix("file://").unwrap_or(_path); + let bytes = std::fs::read(path).ok()?; let len = bytes.len() as i64; let start = if _offset < 0 { (len + _offset).max(0) diff --git a/crates/shirabe/src/util/remote_filesystem.rs b/crates/shirabe/src/util/remote_filesystem.rs index aa42f6b..fc61b16 100644 --- a/crates/shirabe/src/util/remote_filesystem.rs +++ b/crates/shirabe/src/util/remote_filesystem.rs @@ -17,10 +17,10 @@ use shirabe_external_packages::composer::pcre::{CaptureKey, Preg}; use shirabe_php_shim::{ PHP_URL_HOST, PHP_URL_PATH, PHP_URL_SCHEME, PHP_VERSION_ID, PhpMixed, RuntimeException, STREAM_NOTIFY_FAILURE, STREAM_NOTIFY_FILE_SIZE_IS, STREAM_NOTIFY_PROGRESS, - array_replace_recursive, base64_encode, explode, extension_loaded, file_put_contents, - filter_var_boolean, gethostbyname, http_clear_last_response_headers, - http_get_last_response_headers, ini_get, json_decode, parse_url, preg_quote, strpos, - strtolower, strtr, substr, trim, zlib_decode, + array_replace_recursive, base64_encode, explode, extension_loaded, file_get_contents, + file_get_contents5, file_put_contents, filter_var_boolean, gethostbyname, + http_clear_last_response_headers, http_get_last_response_headers, ini_get, json_decode, + parse_url, preg_quote, strpos, strtolower, strtr, substr, trim, zlib_decode, }; /// Result of `RemoteFilesystem::get` — string content, `true` (for copy), or `false`. @@ -699,7 +699,7 @@ impl RemoteFilesystem { fn get_remote_contents( &self, _origin_url: &str, - _file_url: &str, + file_url: &str, _context: &PhpMixed, response_headers: &mut Vec, max_file_size: Option, @@ -711,9 +711,17 @@ impl RemoteFilesystem { } let mut caught_e: Option = None; - // TODO(phase-c): wrap PHP's `file_get_contents` with stream context and error capture; - // depends on the unmodeled PHP stream-context layer. - let outer: Result, anyhow::Error> = Ok(None); + let outer: Result, anyhow::Error> = if self.scheme == "file" { + Ok(match max_file_size { + Some(max) => file_get_contents5(file_url, false, PhpMixed::Null, 0, Some(max)), + None => file_get_contents(file_url), + }) + } else { + // TODO(phase-c): wrap PHP's `file_get_contents` with stream context and error capture + // for http(s) and other network schemes; depends on the unmodeled PHP stream-context + // layer. + Ok(None) + }; match outer { Ok(v) => result = v, Err(e) => caught_e = Some(e), diff --git a/crates/shirabe/tests/util/remote_filesystem_test.rs b/crates/shirabe/tests/util/remote_filesystem_test.rs index 829e58c..c699b94 100644 --- a/crates/shirabe/tests/util/remote_filesystem_test.rs +++ b/crates/shirabe/tests/util/remote_filesystem_test.rs @@ -225,7 +225,6 @@ fn this_file() -> String { } #[test] -#[ignore = "get_remote_contents has no stream/file layer (TODO(phase-c)) and returns None, so getContents raises a TransportException instead of reading the file:// URL"] fn test_get_contents() { let io: Rc> = Rc::new(RefCell::new(IOStub::new())); let mut fs = RemoteFilesystem::new(io, config_mock(), IndexMap::new(), false, None); @@ -242,7 +241,6 @@ fn test_get_contents() { } #[test] -#[ignore = "get_remote_contents has no stream/file layer (TODO(phase-c)) and returns None, so copy raises a TransportException instead of reading the file:// URL"] fn test_copy() { let io: Rc> = Rc::new(RefCell::new(IOStub::new())); let mut fs = RemoteFilesystem::new(io, config_mock(), IndexMap::new(), false, None); -- cgit v1.3.1