Commit 214ed7eb93c for php.net
commit 214ed7eb93c181cc6f53dc3a4de2d72a38776b5a
Author: David Carlier <devnexen@gmail.com>
Date: Thu Sep 10 22:47:31 2026 +0100
ext/zip: ZipArchive::extractTo() ignores files given in a non-list array
The array form walked the entries by synthetic 0..n-1 indices, so a files
array with non-sequential keys (as returned by array_filter() and similar)
or an integer element extracted nothing while still returning true. It now
iterates the array values, honouring every entry regardless of its keys.
Close GH-23647
diff --git a/NEWS b/NEWS
index 1fd676d2a64..33e9859d219 100644
--- a/NEWS
+++ b/NEWS
@@ -11,6 +11,10 @@ PHP NEWS
registrations are freed while still reachable from the cycle collector.
(Ilia Alshanetsky)
+- Zip:
+ . Fixed ZipArchive::extractTo() ignoring files given in a non-list array.
+ (David Carlier)
+
24 Sep 2026, PHP 8.4.26
diff --git a/ext/zip/php_zip.c b/ext/zip/php_zip.c
index 69b81b88753..682736d4fe7 100644
--- a/ext/zip/php_zip.c
+++ b/ext/zip/php_zip.c
@@ -2963,7 +2963,7 @@ PHP_METHOD(ZipArchive, extractTo)
}
}
- uint32_t nelems, i;
+ uint32_t nelems;
if (files_str) {
if (!php_zip_extract_file(intern, pathto, ZSTR_VAL(files_str), ZSTR_LEN(files_str), -1)) {
@@ -2974,20 +2974,19 @@ PHP_METHOD(ZipArchive, extractTo)
if (nelems == 0 ) {
RETURN_FALSE;
}
- for (i = 0; i < nelems; i++) {
- zval *zval_file;
- if ((zval_file = zend_hash_index_find_deref(files_ht, i)) != NULL) {
- switch (Z_TYPE_P(zval_file)) {
- case IS_LONG:
- break;
- case IS_STRING:
- if (!php_zip_extract_file(intern, pathto, Z_STRVAL_P(zval_file), Z_STRLEN_P(zval_file), -1)) {
- RETURN_FALSE;
- }
- break;
- }
+ zval *zval_file;
+ ZEND_HASH_FOREACH_VAL(files_ht, zval_file) {
+ ZVAL_DEREF(zval_file);
+ switch (Z_TYPE_P(zval_file)) {
+ case IS_LONG:
+ break;
+ case IS_STRING:
+ if (!php_zip_extract_file(intern, pathto, Z_STRVAL_P(zval_file), Z_STRLEN_P(zval_file), -1)) {
+ RETURN_FALSE;
+ }
+ break;
}
- }
+ } ZEND_HASH_FOREACH_END();
} else {
/* Extract all files */
zip_int64_t i, filecount = zip_get_num_entries(intern, 0);
diff --git a/ext/zip/tests/oo_extract_array_keys.phpt b/ext/zip/tests/oo_extract_array_keys.phpt
new file mode 100644
index 00000000000..c825bbefbbb
--- /dev/null
+++ b/ext/zip/tests/oo_extract_array_keys.phpt
@@ -0,0 +1,38 @@
+--TEST--
+ZipArchive::extractTo() with a non-list files array (non-sequential keys)
+--EXTENSIONS--
+zip
+--FILE--
+<?php
+$archive = __DIR__ . "/oo_extract_array_keys.zip";
+
+$zip = new ZipArchive();
+$zip->open($archive, ZipArchive::CREATE | ZipArchive::OVERWRITE);
+$zip->addFromString("file0.txt", "zero");
+$zip->addFromString("file1.txt", "one");
+$zip->close();
+
+$target = __DIR__ . "/oo_extract_array_keys";
+mkdir($target);
+
+// array_filter() (and array_unique/array_diff) preserve keys, so this is [1 => "file1.txt"],
+// a perfectly valid list of entry names that is not a packed 0-based array.
+$files = array_filter(["file0.txt", "file1.txt"], fn($f) => $f === "file1.txt");
+
+$zip = new ZipArchive();
+$zip->open($archive);
+var_dump($zip->extractTo($target, $files));
+$zip->close();
+
+var_dump(is_file("$target/file1.txt"));
+?>
+--EXPECT--
+bool(true)
+bool(true)
+--CLEAN--
+<?php
+@unlink(__DIR__ . "/oo_extract_array_keys.zip");
+$target = __DIR__ . "/oo_extract_array_keys";
+@unlink("$target/file1.txt");
+@rmdir($target);
+?>