Skip to content

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

Open
iliaal wants to merge 1 commit intophp:masterfrom
iliaal:fix/gh-14481-realpath-phar
Open

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

Conversation

@iliaal
Copy link
Copy Markdown
Contributor

@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 #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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

realpath() and SplFileInfo::getRealPath inside Phar

1 participant