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)