Commit 5332ab0c291 for php.net
commit 5332ab0c291361abe3d12db21dd55e5dc7baa829
Author: Weilin Du <weilindu@php.net>
Date: Sat Aug 15 03:00:26 2026 +0800
ext/phar: only `.phar` extensions in file names trigger automatic archive detection (#23260)
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.
diff --git a/NEWS b/NEWS
index b7259e81f68..1bba6a7b6c2 100644
--- a/NEWS
+++ b/NEWS
@@ -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)
diff --git a/UPGRADING b/UPGRADING
index 390012a8070..1cc6d66f375 100644
--- a/UPGRADING
+++ b/UPGRADING
@@ -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.
diff --git a/ext/phar/phar.c b/ext/phar/phar.c
index 9f0ccc62727..af3a4992d6d 100644
--- a/ext/phar/phar.c
+++ b/ext/phar/phar.c
@@ -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)
+{
+ 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);
@@ -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;
diff --git a/ext/phar/tests/include_file_extension.phpt b/ext/phar/tests/include_file_extension.phpt
new file mode 100644
index 00000000000..2e95c0dc324
--- /dev/null
+++ b/ext/phar/tests/include_file_extension.phpt
@@ -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)