[php-src] master: Merge branch 'PHP-8.6'
| From: | Weilin Du | Date: | Thu, 08 Oct 2026 16:57:03 +0000 |
| Subject: | [php-src] master: Merge branch 'PHP-8.6' | ||
| Groups: | php.cvs | ||
| Request: | Send a blank email to php-cvs+get-142098@lists.php.net to get a copy of this message | ||
Author: Weilin Du (LamentXU123)
Date: 2026-10-09T00:55:38+08:00
Commit: https://github.com/php/php-src/commit/ccd06d4cc633614a426398d44d4468c0724cbd09
Raw diff: https://github.com/php/php-src/commit/ccd06d4cc633614a426398d44d4468c0724cbd09.diff
Merge branch 'PHP-8.6'
* PHP-8.6:
ext/zip: Reject ZipArchive mutators during close() (#24025)
Changed paths:
A ext/zip/tests/zip_close_mutator_reentry.phpt
A ext/zip/tests/zip_close_mutator_reentry_cancel.phpt
A ext/zip/tests/zip_close_mutator_reentry_open.phpt
M ext/zip/php_zip.c
Diff:
diff --git a/ext/zip/php_zip.c b/ext/zip/php_zip.c
index fc2998a7ca3f..5baecfc18933 100644
--- a/ext/zip/php_zip.c
+++ b/ext/zip/php_zip.c
@@ -1728,6 +1728,10 @@ PHP_METHOD(ZipArchive, setPassword)
ZIP_FROM_OBJECT(intern, self);
+ if (php_zipobj_closing(Z_ZIP_P(self))) {
+ RETURN_THROWS();
+ }
+
if (password_len < 1) {
RETURN_FALSE;
}
@@ -1890,6 +1894,10 @@ PHP_METHOD(ZipArchive, addEmptyDir)
ZIP_FROM_OBJECT(intern, self);
+ if (php_zipobj_closing(Z_ZIP_P(self))) {
+ RETURN_THROWS();
+ }
+
if (dirname_len<1) {
RETURN_FALSE;
}
@@ -1945,6 +1953,10 @@ static void php_zip_add_from_pattern(INTERNAL_FUNCTION_PARAMETERS, int type)
/*
RETURN_THROWS();
}
+ if (php_zipobj_closing(Z_ZIP_P(self))) {
+ RETURN_THROWS();
+ }
+
if (type == 1) {
found = php_zip_glob(pattern, glob_flags, return_value);
} else {
@@ -2079,6 +2091,10 @@ PHP_METHOD(ZipArchive, addFile)
entry_name_len = ZSTR_LEN(filename);
}
+ if (php_zipobj_closing(Z_ZIP_P(self))) {
+ RETURN_THROWS();
+ }
+
RETURN_BOOL(php_zip_add_file(Z_ZIP_P(self), ZSTR_VAL(filename), ZSTR_LEN(filename),
entry_name, entry_name_len, offset_start, offset_len, -1, flags) == SUCCESS);
}
@@ -2108,6 +2124,10 @@ PHP_METHOD(ZipArchive, replaceFile)
RETURN_THROWS();
}
+ if (php_zipobj_closing(Z_ZIP_P(self))) {
+ RETURN_THROWS();
+ }
+
RETURN_BOOL(php_zip_add_file(Z_ZIP_P(self), ZSTR_VAL(filename), ZSTR_LEN(filename),
NULL, 0, offset_start, offset_len, index, flags) == SUCCESS);
}
@@ -2132,6 +2152,10 @@ PHP_METHOD(ZipArchive, addFromString)
ZIP_FROM_OBJECT(intern, self);
+ if (php_zipobj_closing(Z_ZIP_P(self))) {
+ RETURN_THROWS();
+ }
+
ze_obj = Z_ZIP_P(self);
zs = php_zip_create_string_source(buffer, NULL, NULL);
@@ -2262,6 +2286,10 @@ PHP_METHOD(ZipArchive, setArchiveComment)
ZIP_FROM_OBJECT(intern, self);
+ if (php_zipobj_closing(Z_ZIP_P(self))) {
+ RETURN_THROWS();
+ }
+
if (comment_len > 0xffff) {
zend_argument_value_error(1, "must be less than 65535 bytes");
RETURN_THROWS();
@@ -2306,6 +2334,10 @@ PHP_METHOD(ZipArchive, setArchiveFlag)
ZIP_FROM_OBJECT(intern, self);
+ if (php_zipobj_closing(Z_ZIP_P(self))) {
+ RETURN_THROWS();
+ }
+
RETURN_BOOL(zip_set_archive_flag(intern, flag, (int)value) == 0);
}
@@ -2345,6 +2377,10 @@ PHP_METHOD(ZipArchive, setCommentName)
ZIP_FROM_OBJECT(intern, self);
+ if (php_zipobj_closing(Z_ZIP_P(self))) {
+ RETURN_THROWS();
+ }
+
if (comment_len > 0xffff) {
zend_argument_value_error(2, "must be less than 65535 bytes");
RETURN_THROWS();
@@ -2376,6 +2412,10 @@ PHP_METHOD(ZipArchive, setCommentIndex)
ZIP_FROM_OBJECT(intern, self);
+ if (php_zipobj_closing(Z_ZIP_P(self))) {
+ RETURN_THROWS();
+ }
+
if (comment_len > 0xffff) {
zend_argument_value_error(2, "must be less than 65535 bytes");
RETURN_THROWS();
@@ -2408,6 +2448,10 @@ PHP_METHOD(ZipArchive, setExternalAttributesName)
ZIP_FROM_OBJECT(intern, self);
+ if (php_zipobj_closing(Z_ZIP_P(self))) {
+ RETURN_THROWS();
+ }
+
if (name_len == 0) {
zend_argument_must_not_be_empty_error(1);
RETURN_THROWS();
@@ -2438,6 +2482,10 @@ PHP_METHOD(ZipArchive, setExternalAttributesIndex)
ZIP_FROM_OBJECT(intern, self);
+ if (php_zipobj_closing(Z_ZIP_P(self))) {
+ RETURN_THROWS();
+ }
+
PHP_ZIP_STAT_INDEX(intern, index, 0, sb);
RETURN_BOOL(zip_file_set_external_attributes(intern, (zip_uint64_t)index,
(zip_flags_t)flags, (zip_uint8_t)(opsys&0xff), (zip_uint32_t)attr) == 0);
@@ -2527,6 +2575,10 @@ PHP_METHOD(ZipArchive, setEncryptionName)
ZIP_FROM_OBJECT(intern, self);
+ if (php_zipobj_closing(Z_ZIP_P(self))) {
+ RETURN_THROWS();
+ }
+
if (name_len == 0) {
zend_argument_must_not_be_empty_error(1);
RETURN_THROWS();
@@ -2558,6 +2610,10 @@ PHP_METHOD(ZipArchive, setEncryptionIndex)
ZIP_FROM_OBJECT(intern, self);
+ if (php_zipobj_closing(Z_ZIP_P(self))) {
+ RETURN_THROWS();
+ }
+
RETURN_BOOL(php_zip_file_set_encryption(intern, index, method, password));
}
/* }}} */
@@ -2653,6 +2709,11 @@ PHP_METHOD(ZipArchive, setCompressionName)
}
ZIP_FROM_OBJECT(intern, this);
+
+ if (php_zipobj_closing(Z_ZIP_P(this))) {
+ RETURN_THROWS();
+ }
+
idx = zip_name_locate(intern, name, 0);
if (idx < 0) {
@@ -2694,6 +2755,10 @@ PHP_METHOD(ZipArchive, setCompressionIndex)
ZIP_FROM_OBJECT(intern, this);
+ if (php_zipobj_closing(Z_ZIP_P(this))) {
+ RETURN_THROWS();
+ }
+
RETURN_BOOL(zip_set_file_compression(intern, (zip_uint64_t)index,
(zip_int32_t)comp_method, (zip_uint32_t)comp_flags) == 0);
}
@@ -2721,6 +2786,10 @@ PHP_METHOD(ZipArchive, setMtimeName)
ZIP_FROM_OBJECT(intern, this);
+ if (php_zipobj_closing(Z_ZIP_P(this))) {
+ RETURN_THROWS();
+ }
+
idx = zip_name_locate(intern, name, 0);
if (idx < 0) {
@@ -2747,6 +2816,10 @@ PHP_METHOD(ZipArchive, setMtimeIndex)
ZIP_FROM_OBJECT(intern, this);
+ if (php_zipobj_closing(Z_ZIP_P(this))) {
+ RETURN_THROWS();
+ }
+
RETURN_BOOL(zip_file_set_mtime(intern, (zip_uint64_t)index,
(time_t)mtime, (zip_uint32_t)flags) == 0);
}
@@ -2765,6 +2838,10 @@ PHP_METHOD(ZipArchive, deleteIndex)
ZIP_FROM_OBJECT(intern, self);
+ if (php_zipobj_closing(Z_ZIP_P(self))) {
+ RETURN_THROWS();
+ }
+
if (index < 0) {
RETURN_FALSE;
}
@@ -2792,6 +2869,10 @@ PHP_METHOD(ZipArchive, deleteName)
ZIP_FROM_OBJECT(intern, self);
+ if (php_zipobj_closing(Z_ZIP_P(self))) {
+ RETURN_THROWS();
+ }
+
PHP_ZIP_STAT_PATH(intern, name, name_len, 0, sb);
RETURN_BOOL(zip_delete(intern, sb.index) == 0);
@@ -2822,6 +2903,10 @@ PHP_METHOD(ZipArchive, renameIndex)
ZIP_FROM_OBJECT(intern, self);
+ if (php_zipobj_closing(Z_ZIP_P(self))) {
+ RETURN_THROWS();
+ }
+
RETURN_BOOL(zip_file_rename(intern, index, (const char *)new_name, 0) == 0);
}
/* }}} */
@@ -2841,6 +2926,10 @@ PHP_METHOD(ZipArchive, renameName)
ZIP_FROM_OBJECT(intern, self);
+ if (php_zipobj_closing(Z_ZIP_P(self))) {
+ RETURN_THROWS();
+ }
+
if (new_name_len == 0) {
zend_argument_must_not_be_empty_error(2);
RETURN_THROWS();
@@ -2869,6 +2958,10 @@ PHP_METHOD(ZipArchive, unchangeIndex)
ZIP_FROM_OBJECT(intern, self);
+ if (php_zipobj_closing(Z_ZIP_P(self))) {
+ RETURN_THROWS();
+ }
+
RETURN_BOOL(zip_unchange(intern, index) == 0);
}
/* }}} */
@@ -2892,6 +2985,10 @@ PHP_METHOD(ZipArchive, unchangeName)
ZIP_FROM_OBJECT(intern, self);
+ if (php_zipobj_closing(Z_ZIP_P(self))) {
+ RETURN_THROWS();
+ }
+
PHP_ZIP_STAT_PATH(intern, name, name_len, 0, sb);
RETURN_BOOL(zip_unchange(intern, sb.index) == 0);
@@ -2908,6 +3005,10 @@ PHP_METHOD(ZipArchive, unchangeAll)
ZIP_FROM_OBJECT(intern, self);
+ if (php_zipobj_closing(Z_ZIP_P(self))) {
+ RETURN_THROWS();
+ }
+
RETURN_BOOL(zip_unchange_all(intern) == 0);
}
/* }}} */
@@ -2922,6 +3023,10 @@ PHP_METHOD(ZipArchive, unchangeArchive)
ZIP_FROM_OBJECT(intern, self);
+ if (php_zipobj_closing(Z_ZIP_P(self))) {
+ RETURN_THROWS();
+ }
+
RETURN_BOOL(zip_unchange_archive(intern) == 0);
}
/* }}} */
@@ -3222,6 +3327,10 @@ PHP_METHOD(ZipArchive, registerProgressCallback)
zend_release_fcall_info_cache(&fcc);
RETURN_THROWS();
}
+ if (php_zipobj_closing(obj)) {
+ zend_release_fcall_info_cache(&fcc);
+ RETURN_THROWS();
+ }
archive = obj->archive;
/* register */
@@ -3297,6 +3406,10 @@ PHP_METHOD(ZipArchive, registerCancelCallback)
zend_release_fcall_info_cache(&fcc);
RETURN_THROWS();
}
+ if (php_zipobj_closing(obj)) {
+ zend_release_fcall_info_cache(&fcc);
+ RETURN_THROWS();
+ }
archive = obj->archive;
/* register */
diff --git a/ext/zip/tests/zip_close_mutator_reentry.phpt
b/ext/zip/tests/zip_close_mutator_reentry.phpt
new file mode 100644
index 000000000000..98636d9202f0
--- /dev/null
+++ b/ext/zip/tests/zip_close_mutator_reentry.phpt
@@ -0,0 +1,145 @@
+--TEST--
+ZipArchive mutators throw when called from a progress callback during close()
+--EXTENSIONS--
+zip
+--SKIPIF--
+<?php
+if (!method_exists(ZipArchive::class, 'registerProgressCallback')) {
+ die('skip progress callbacks are not supported');
+}
+if (!method_exists(ZipArchive::class, 'registerCancelCallback')) {
+ die('skip cancel callbacks are not supported');
+}
+if (!method_exists(ZipArchive::class, 'setEncryptionName')) {
+ die('skip encryption not supported');
+}
+if (!method_exists(ZipArchive::class, 'setMtimeName')) {
+ die('skip libzip too old');
+}
+?>
+--FILE--
+<?php
+$filename = __DIR__ . '/zip_close_mutator_reentry.zip';
+
+function populate(ZipArchive $zip, string $filename): void {
+ $zip->open($filename, ZipArchive::CREATE | ZipArchive::OVERWRITE);
+ $zip->addFromString('a.txt', str_repeat('a', 100000));
+ $zip->addFromString('b.txt', str_repeat('b', 100000));
+}
+
+$zip = new ZipArchive();
+populate($zip, $filename);
+$zip->registerProgressCallback(0.0, function ($rate) use ($zip) {
+ static $done = false;
+ if ($done || $rate <= 0) {
+ return;
+ }
+ $done = true;
+ try {
+ var_dump($zip->deleteIndex(0));
+ } catch (Error $e) {
+ echo $e::class, ': ', $e->getMessage(), PHP_EOL;
+ }
+});
+var_dump($zip->close());
+
+$zip = new ZipArchive();
+populate($zip, $filename);
+$mutators = [
+ 'deleteIndex' => fn() => $zip->deleteIndex(0),
+ 'deleteName' => fn() => $zip->deleteName('a.txt'),
+ 'unchangeIndex' => fn() => $zip->unchangeIndex(0),
+ 'unchangeName' => fn() => $zip->unchangeName('a.txt'),
+ 'unchangeAll' => fn() => $zip->unchangeAll(),
+ 'unchangeArchive' => fn() => $zip->unchangeArchive(),
+ 'addEmptyDir' => fn() => $zip->addEmptyDir('dir'),
+ 'addFile' => fn() => $zip->addFile(__FILE__, 'file.phpt'),
+ 'addFromString' => fn() => $zip->addFromString('c.txt',
'c'),
+ 'addGlob' => fn() => $zip->addGlob(__FILE__),
+ 'addPattern' => fn() => $zip->addPattern('/\.phpt$/', __DIR__),
+ 'replaceFile' => fn() => $zip->replaceFile(__FILE__, 0),
+ 'renameIndex' => fn() => $zip->renameIndex(0, 'x.txt'),
+ 'renameName' => fn() => $zip->renameName('a.txt',
'x.txt'),
+ 'setArchiveComment' => fn() => $zip->setArchiveComment('comment'),
+ 'setArchiveFlag' => fn() => $zip->setArchiveFlag(ZipArchive::AFL_RDONLY, 1),
+ 'setCommentIndex' => fn() => $zip->setCommentIndex(0, 'comment'),
+ 'setCommentName' => fn() => $zip->setCommentName('a.txt',
'comment'),
+ 'setCompressionIndex' => fn() => $zip->setCompressionIndex(0,
ZipArchive::CM_STORE),
+ 'setCompressionName' => fn() => $zip->setCompressionName('a.txt',
ZipArchive::CM_STORE),
+ 'setEncryptionIndex' => fn() => $zip->setEncryptionIndex(0,
ZipArchive::EM_AES_256, 'secret'),
+ 'setEncryptionName' => fn() => $zip->setEncryptionName('a.txt',
ZipArchive::EM_AES_256, 'secret'),
+ 'setExternalAttributesIndex' => fn() => $zip->setExternalAttributesIndex(0,
ZipArchive::OPSYS_UNIX, 0),
+ 'setExternalAttributesName' => fn() =>
$zip->setExternalAttributesName('a.txt', ZipArchive::OPSYS_UNIX, 0),
+ 'setMtimeIndex' => fn() => $zip->setMtimeIndex(0, 0),
+ 'setMtimeName' => fn() => $zip->setMtimeName('a.txt', 0),
+ 'setPassword' => fn() => $zip->setPassword('secret'),
+ 'registerCancelCallback' => fn() => $zip->registerCancelCallback(fn() =>
0),
+ 'registerProgressCallback' => fn() => $zip->registerProgressCallback(0.5,
function () {}),
+];
+
+$zip->registerProgressCallback(0.0, function ($rate) use ($mutators) {
+ static $done = false;
+ if ($done || $rate <= 0) {
+ return;
+ }
+ $done = true;
+ foreach ($mutators as $name => $mutator) {
+ try {
+ var_dump($mutator());
+ } catch (Error $e) {
+ echo $name, ': ', $e::class, ': ', $e->getMessage(), PHP_EOL;
+ }
+ }
+});
+var_dump($zip->close());
+
+$zip = new ZipArchive();
+var_dump($zip->open($filename, ZipArchive::CHECKCONS));
+var_dump($zip->numFiles);
+var_dump($zip->getFromName('a.txt') === str_repeat('a', 100000));
+var_dump($zip->getFromName('b.txt') === str_repeat('b', 100000));
+var_dump($zip->getArchiveComment());
+$zip->close();
+?>
+--CLEAN--
+<?php
+@unlink(__DIR__ . '/zip_close_mutator_reentry.zip');
+?>
+--EXPECT--
+Error: Already being closed
+bool(true)
+deleteIndex: Error: Already being closed
+deleteName: Error: Already being closed
+unchangeIndex: Error: Already being closed
+unchangeName: Error: Already being closed
+unchangeAll: Error: Already being closed
+unchangeArchive: Error: Already being closed
+addEmptyDir: Error: Already being closed
+addFile: Error: Already being closed
+addFromString: Error: Already being closed
+addGlob: Error: Already being closed
+addPattern: Error: Already being closed
+replaceFile: Error: Already being closed
+renameIndex: Error: Already being closed
+renameName: Error: Already being closed
+setArchiveComment: Error: Already being closed
+setArchiveFlag: Error: Already being closed
+setCommentIndex: Error: Already being closed
+setCommentName: Error: Already being closed
+setCompressionIndex: Error: Already being closed
+setCompressionName: Error: Already being closed
+setEncryptionIndex: Error: Already being closed
+setEncryptionName: Error: Already being closed
+setExternalAttributesIndex: Error: Already being closed
+setExternalAttributesName: Error: Already being closed
+setMtimeIndex: Error: Already being closed
+setMtimeName: Error: Already being closed
+setPassword: Error: Already being closed
+registerCancelCallback: Error: Already being closed
+registerProgressCallback: Error: Already being closed
+bool(true)
+bool(true)
+int(2)
+bool(true)
+bool(true)
+string(0) ""
diff --git a/ext/zip/tests/zip_close_mutator_reentry_cancel.phpt
b/ext/zip/tests/zip_close_mutator_reentry_cancel.phpt
new file mode 100644
index 000000000000..4826c8b6a71c
--- /dev/null
+++ b/ext/zip/tests/zip_close_mutator_reentry_cancel.phpt
@@ -0,0 +1,57 @@
+--TEST--
+ZipArchive mutators throw when called from a cancel callback during close()
+--EXTENSIONS--
+zip
+--SKIPIF--
+<?php
+if (!method_exists(ZipArchive::class, 'registerCancelCallback')) {
+ die('skip cancel callbacks are not supported');
+}
+?>
+--FILE--
+<?php
+$filename = __DIR__ . '/zip_close_mutator_reentry_cancel.zip';
+$zip = new ZipArchive();
+$zip->open($filename, ZipArchive::CREATE | ZipArchive::OVERWRITE);
+$zip->addFromString('a.txt', str_repeat('a', 100000));
+$zip->addFromString('b.txt', str_repeat('b', 100000));
+$zip->registerCancelCallback(function () use ($zip) {
+ static $done = false;
+ if ($done) {
+ return 0;
+ }
+ $done = true;
+ $mutators = [
+ 'deleteIndex' => fn() => $zip->deleteIndex(0),
+ 'addFromString' => fn() => $zip->addFromString('c.txt',
'c'),
+ 'registerCancelCallback' => fn() => $zip->registerCancelCallback(fn()
=> 0),
+ ];
+ foreach ($mutators as $name => $mutator) {
+ try {
+ var_dump($mutator());
+ } catch (Error $e) {
+ echo $name, ': ', $e::class, ': ', $e->getMessage(), PHP_EOL;
+ }
+ }
+ return 0;
+});
+var_dump($zip->close());
+var_dump($zip->open($filename, ZipArchive::CHECKCONS));
+var_dump($zip->numFiles);
+var_dump($zip->getFromName('a.txt') === str_repeat('a', 100000));
+var_dump($zip->getFromName('b.txt') === str_repeat('b', 100000));
+$zip->close();
+?>
+--CLEAN--
+<?php
+@unlink(__DIR__ . '/zip_close_mutator_reentry_cancel.zip');
+?>
+--EXPECT--
+deleteIndex: Error: Already being closed
+addFromString: Error: Already being closed
+registerCancelCallback: Error: Already being closed
+bool(true)
+bool(true)
+int(2)
+bool(true)
+bool(true)
diff --git a/ext/zip/tests/zip_close_mutator_reentry_open.phpt
b/ext/zip/tests/zip_close_mutator_reentry_open.phpt
new file mode 100644
index 000000000000..c169ea9a02d4
--- /dev/null
+++ b/ext/zip/tests/zip_close_mutator_reentry_open.phpt
@@ -0,0 +1,69 @@
+--TEST--
+ZipArchive mutators throw when open() implicitly closes the previous archive
+--EXTENSIONS--
+zip
+--SKIPIF--
+<?php
+if (!method_exists(ZipArchive::class, 'registerProgressCallback')) {
+ die('skip progress callbacks are not supported');
+}
+?>
+--FILE--
+<?php
+$filename = __DIR__ . '/zip_close_mutator_reentry_open.zip';
+$nextFilename = __DIR__ . '/zip_close_mutator_reentry_open_next.zip';
+$zip = new ZipArchive();
+$zip->open($filename, ZipArchive::CREATE | ZipArchive::OVERWRITE);
+$zip->addFromString('a.txt', str_repeat('a', 100000));
+$zip->addFromString('b.txt', str_repeat('b', 100000));
+$zip->registerProgressCallback(0.0, function ($rate) use ($zip) {
+ static $done = false;
+ if ($done || $rate <= 0) {
+ return;
+ }
+ $done = true;
+ $mutators = [
+ 'deleteIndex' => fn() => $zip->deleteIndex(0),
+ 'addFromString' => fn() => $zip->addFromString('c.txt',
'c'),
+ 'registerProgressCallback' => fn() =>
$zip->registerProgressCallback(0.5, function () {}),
+ ];
+ foreach ($mutators as $name => $mutator) {
+ try {
+ var_dump($mutator());
+ } catch (Error $e) {
+ echo $name, ': ', $e::class, ': ', $e->getMessage(), PHP_EOL;
+ }
+ }
+});
+var_dump($zip->open($nextFilename, ZipArchive::CREATE | ZipArchive::OVERWRITE));
+var_dump($zip->addFromString('new.txt', 'new contents'));
+var_dump($zip->close());
+var_dump($zip->open($filename, ZipArchive::CHECKCONS));
+var_dump($zip->numFiles);
+var_dump($zip->getFromName('a.txt') === str_repeat('a', 100000));
+var_dump($zip->getFromName('b.txt') === str_repeat('b', 100000));
+$zip->close();
+var_dump($zip->open($nextFilename, ZipArchive::CHECKCONS));
+var_dump($zip->numFiles);
+var_dump($zip->getFromName('new.txt') === 'new contents');
+$zip->close();
+?>
+--CLEAN--
+<?php
+@unlink(__DIR__ . '/zip_close_mutator_reentry_open.zip');
+@unlink(__DIR__ . '/zip_close_mutator_reentry_open_next.zip');
+?>
+--EXPECT--
+deleteIndex: Error: Already being closed
+addFromString: Error: Already being closed
+registerProgressCallback: Error: Already being closed
+bool(true)
+bool(true)
+bool(true)
+bool(true)
+int(2)
+bool(true)
+bool(true)
+bool(true)
+int(1)
+bool(true)