Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions NEWS
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@ PHP NEWS
left busy for the next fetch, and rows delivered from a result another
statement took over. (KentarouTakeda)

- Phar:
. Fixed Phar archives being automatically detected when ".phar" only occurs
in a directory name or is not a filename extension in an included file's
path. (Weilin Du)

- Readline:
. Fixed class constant completion in the interactive shell. (Weilin Du)

Expand Down
6 changes: 6 additions & 0 deletions UPGRADING
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,12 @@ PHP 8.6 UPGRADE NOTES
. imagesetstyle(), imagefilter() and imagecrop() filter their array arguments
types / values and raise a TypeError / ValueError accordingly.

- Phar:
. Files are only automatically interpreted as Phar archives when included
if ".phar" occurs as an extension in the filename component of their
paths. Previously, it could occur in a directory name or as part of an
extension such as ".pharma".

- GMP:
. GMP power and shift operators now throw ValueError when GMP right operands
are outside the unsigned long range, instead of silently truncating them.
Expand Down
22 changes: 21 additions & 1 deletion ext/phar/phar.c
Original file line number Diff line number Diff line change
Expand Up @@ -3167,6 +3167,26 @@ static size_t phar_zend_stream_fsizer(void *handle) /* {{{ */

zend_op_array *(*phar_orig_compile_file)(zend_file_handle *file_handle, int type);

static bool phar_has_marker_in_filename(const zend_string *filename)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think to really properly fix it, we should check if the filename ends with .phar or .phar.zip or .phar.tar etc, one of those supported extensions. I believe somewhere in ext/phar the code already lists the possible extensions, but I may be wrong.
Now, with this code, something like quality.pharma.report.txt would still trigger ;)

@LamentXU123 LamentXU123 Aug 13, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe somewhere in ext/phar the code already lists the possible extensions

Sadly there aren't :( and since the phar extension is very, very old. There are conventions in different code bases all over the world and I couldn't find a place where people finally reach an agreement on what suffix should a valid phar file have.

There are things like .phar.php .phar.zip.php or whatever.

Now, with this code, something like quality.pharma.report.txt would still trigger ;)

Clever catch ;)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are things like .phar.php .phar.zip.php or whatever.

😭

{
const char *path = ZSTR_VAL(filename);
const char *basename = zend_memrchr(path, '/', ZSTR_LEN(filename));
#ifdef PHP_WIN32
const char *backslash = zend_memrchr(path, '\\', ZSTR_LEN(filename));
if (backslash && (!basename || backslash > basename)) {
basename = backslash;
}
#endif
const char *marker = basename ? basename + 1 : path;
while ((marker = strstr(marker, ".phar"))) {
marker += sizeof(".phar") - 1;
if (*marker == '\0' || *marker == '.') {
return true;
}
}
return false;
}

static zend_string *phar_resolve_path(zend_string *filename)
{
zend_string *ret = phar_find_in_include_path(filename);
Expand All @@ -3186,7 +3206,7 @@ static zend_op_array *phar_compile_file(zend_file_handle *file_handle, int type)
if (!file_handle || !file_handle->filename) {
return phar_orig_compile_file(file_handle, type);
}
if (strstr(ZSTR_VAL(file_handle->filename), ".phar") && !strstr(ZSTR_VAL(file_handle->filename), "://")) {
if (phar_has_marker_in_filename(file_handle->filename) && !strstr(ZSTR_VAL(file_handle->filename), "://")) {
if (SUCCESS == phar_open_from_filename(ZSTR_VAL(file_handle->filename), ZSTR_LEN(file_handle->filename), NULL, 0, &phar, NULL)) {
if (phar->is_zip || phar->is_tar) {
zend_file_handle f;
Expand Down
58 changes: 58 additions & 0 deletions ext/phar/tests/include_file_extension.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
--TEST--
Phar: only .phar extensions in file names trigger automatic archive detection
--EXTENSIONS--
phar
zlib
--INI--
phar.readonly=0
phar.require_hash=0
--FILE--
<?php
$base = __DIR__ . '/' . basename(__FILE__, '.php');
$targets = [
$base . '.phar.png',
$base . '.pharabcd/archive.html',
$base . '.pharma.report.txt',
$base . '.pharma.phar.html',
];

if (!is_dir(dirname($targets[1]))) {
mkdir(dirname($targets[1]));
}

foreach ($targets as $i => $target) {
$source = $base . ".source-$i.phar.zip";
$constant = "PHAR_STUB_EXECUTED_$i";

$phar = new Phar($source);
$phar->addFromString('payload', 'payload');
$phar->setStub("<?php define('$constant', true); __HALT_COMPILER();");
$phar->compressFiles(Phar::GZ);
unset($phar);

rename($source, $target);

ob_start();
include $target;
ob_end_clean();

var_dump(defined($constant));
}
?>
--CLEAN--
<?php
$base = __DIR__ . '/' . basename(__FILE__, '.clean.php');
@unlink($base . '.phar.png');
@unlink($base . '.pharabcd/archive.html');
@unlink($base . '.pharma.report.txt');
@unlink($base . '.pharma.phar.html');
for ($i = 0; $i < 4; $i++) {
@unlink($base . ".source-$i.phar.zip");
}
@rmdir($base . '.pharabcd');
?>
--EXPECT--
bool(true)
bool(false)
bool(false)
bool(true)
Loading