Skip to content

Commit 214ed7e

Browse files
committed
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
1 parent 0275811 commit 214ed7e

3 files changed

Lines changed: 55 additions & 14 deletions

File tree

‎NEWS‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@ PHP NEWS
1111
registrations are freed while still reachable from the cycle collector.
1212
(Ilia Alshanetsky)
1313

14+
- Zip:
15+
. Fixed ZipArchive::extractTo() ignoring files given in a non-list array.
16+
(David Carlier)
17+
1418

1519
24 Sep 2026, PHP 8.4.26
1620

‎ext/zip/php_zip.c‎

Lines changed: 13 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2963,7 +2963,7 @@ PHP_METHOD(ZipArchive, extractTo)
29632963
}
29642964
}
29652965

2966-
uint32_t nelems, i;
2966+
uint32_t nelems;
29672967

29682968
if (files_str) {
29692969
if (!php_zip_extract_file(intern, pathto, ZSTR_VAL(files_str), ZSTR_LEN(files_str), -1)) {
@@ -2974,20 +2974,19 @@ PHP_METHOD(ZipArchive, extractTo)
29742974
if (nelems == 0 ) {
29752975
RETURN_FALSE;
29762976
}
2977-
for (i = 0; i < nelems; i++) {
2978-
zval *zval_file;
2979-
if ((zval_file = zend_hash_index_find_deref(files_ht, i)) != NULL) {
2980-
switch (Z_TYPE_P(zval_file)) {
2981-
case IS_LONG:
2982-
break;
2983-
case IS_STRING:
2984-
if (!php_zip_extract_file(intern, pathto, Z_STRVAL_P(zval_file), Z_STRLEN_P(zval_file), -1)) {
2985-
RETURN_FALSE;
2986-
}
2987-
break;
2988-
}
2977+
zval *zval_file;
2978+
ZEND_HASH_FOREACH_VAL(files_ht, zval_file) {
2979+
ZVAL_DEREF(zval_file);
2980+
switch (Z_TYPE_P(zval_file)) {
2981+
case IS_LONG:
2982+
break;
2983+
case IS_STRING:
2984+
if (!php_zip_extract_file(intern, pathto, Z_STRVAL_P(zval_file), Z_STRLEN_P(zval_file), -1)) {
2985+
RETURN_FALSE;
2986+
}
2987+
break;
29892988
}
2990-
}
2989+
} ZEND_HASH_FOREACH_END();
29912990
} else {
29922991
/* Extract all files */
29932992
zip_int64_t i, filecount = zip_get_num_entries(intern, 0);
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
--TEST--
2+
ZipArchive::extractTo() with a non-list files array (non-sequential keys)
3+
--EXTENSIONS--
4+
zip
5+
--FILE--
6+
<?php
7+
$archive = __DIR__ . "/oo_extract_array_keys.zip";
8+
9+
$zip = new ZipArchive();
10+
$zip->open($archive, ZipArchive::CREATE | ZipArchive::OVERWRITE);
11+
$zip->addFromString("file0.txt", "zero");
12+
$zip->addFromString("file1.txt", "one");
13+
$zip->close();
14+
15+
$target = __DIR__ . "/oo_extract_array_keys";
16+
mkdir($target);
17+
18+
// array_filter() (and array_unique/array_diff) preserve keys, so this is [1 => "file1.txt"],
19+
// a perfectly valid list of entry names that is not a packed 0-based array.
20+
$files = array_filter(["file0.txt", "file1.txt"], fn($f) => $f === "file1.txt");
21+
22+
$zip = new ZipArchive();
23+
$zip->open($archive);
24+
var_dump($zip->extractTo($target, $files));
25+
$zip->close();
26+
27+
var_dump(is_file("$target/file1.txt"));
28+
?>
29+
--EXPECT--
30+
bool(true)
31+
bool(true)
32+
--CLEAN--
33+
<?php
34+
@unlink(__DIR__ . "/oo_extract_array_keys.zip");
35+
$target = __DIR__ . "/oo_extract_array_keys";
36+
@unlink("$target/file1.txt");
37+
@rmdir($target);
38+
?>

0 commit comments

Comments
 (0)