Skip to content

Fix GH-14481: realpath() and SplFileInfo::getRealPath inside Phar#49

Closed
iliaal wants to merge 1 commit intomasterfrom
fix/gh-14481-realpath-phar
Closed

Fix GH-14481: realpath() and SplFileInfo::getRealPath inside Phar#49
iliaal wants to merge 1 commit intomasterfrom
fix/gh-14481-realpath-phar

Conversation

@iliaal
Copy link
Copy Markdown
Owner

@iliaal iliaal commented Apr 27, 2026

realpath() and SplFileInfo::getRealPath() route directly through VCWD_REALPATH, which has no notion of stream wrappers. For a phar:// URL the chain treats the URI as relative, prepends CWD, and stat()s a nonsense path, so existing entries inside a Phar return false even though file_exists() / is_file() / include all see them.

Before VCWD_REALPATH, look up the stream wrapper for the path. If a non-plain, non-URL wrapper supplies url_stat and reports SUCCESS, return the input string; on FAILURE, return false. Plain paths (and file:// URLs that resolve back to the plain-files wrapper) keep the existing VCWD_REALPATH semantics: realpath cache, ZTS access guard, and open_basedir check. For phar URLs, open_basedir is enforced by the wrapper's own url_stat, matching how is_file() and file_exists() behave today.

The !wrapper->is_url guard scopes the new path to local-style wrappers. URL-style wrappers (http://, ftp://, data://, user wrappers registered with STREAM_IS_URL) keep returning false, so realpath() does not gain network or third-party url_stat side effects. In-tree, only phar matches the new branch. User wrappers registered without STREAM_IS_URL that implement url_stat will now have url_stat called from realpath(). PharFileInfo, SplFileObject, DirectoryIterator, RecursiveDirectoryIterator, and FilesystemIterator inherit SplFileInfo::getRealPath and pick up the fix.

stream_resolve_include_path() is intentionally untouched; it is an include-path search that uses the realpath cache, not URL canonicalisation.

Fixes php#14481

realpath() and SplFileInfo::getRealPath() route directly through
VCWD_REALPATH, which has no notion of stream wrappers. For a phar://
URL the call chain treats the URI as relative, prepends CWD, and
stat()s a nonsense path, so existing entries inside a Phar return
false even though file_exists()/is_file()/include all see them.

Before VCWD_REALPATH, look up the stream wrapper for the path. If a
non-plain, non-URL wrapper supplies url_stat and reports SUCCESS,
return the input string; on FAILURE, return false. Plain paths (and
file:// URLs that resolve back to the plain-files wrapper) keep the
existing VCWD_REALPATH semantics: realpath cache, ZTS access guard,
and open_basedir check. For phar URLs, open_basedir is enforced by
the wrapper's own url_stat, matching how is_file() and file_exists()
behave today.

The !wrapper->is_url guard keeps URL-style wrappers (http://, ftp://,
data://, user wrappers registered with STREAM_IS_URL) on the existing
false return so realpath() does not gain network or third-party
url_stat side effects. In-tree, only phar matches the new branch.
User wrappers registered without STREAM_IS_URL that implement
url_stat will now have url_stat called from realpath().

PharFileInfo, SplFileObject, DirectoryIterator,
RecursiveDirectoryIterator, and FilesystemIterator inherit
SplFileInfo::getRealPath and pick up the fix.

Closes phpGH-14481
@iliaal
Copy link
Copy Markdown
Owner Author

iliaal commented Apr 27, 2026

Promoted to upstream: php#21883

@iliaal iliaal closed this Apr 27, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

realpath() and SplFileInfo::getRealPath inside Phar

1 participant