From 3f088d61bf83d3c1fa3d7f0058a4fad655383a72 Mon Sep 17 00:00:00 2001 From: David Carlier Date: Fri, 7 Aug 2026 20:15:13 +0100 Subject: [PATCH 1/2] ext/zip: addGlob() and addPattern() ignore their default options. php_zip_parse_options() held the defaults but only ran for a non-empty options array, so opts stayed zeroed otherwise: comp_method 0 is CM_STORE and flags 0 drops FL_OVERWRITE. Entries were therefore stored uncompressed, and an already present entry name failed the call instead of being replaced. The defaults now live in PHP_ZIP_DEFAULT_OPTIONS, applied at declaration. --- ext/zip/php_zip.c | 15 ++-- ext/zip/tests/addGlob_default_options.phpt | 79 ++++++++++++++++++++++ 2 files changed, 86 insertions(+), 8 deletions(-) create mode 100644 ext/zip/tests/addGlob_default_options.phpt diff --git a/ext/zip/php_zip.c b/ext/zip/php_zip.c index ccc474bc715c..fecb9396ace9 100644 --- a/ext/zip/php_zip.c +++ b/ext/zip/php_zip.c @@ -64,6 +64,12 @@ static int le_zip_entry; } /* }}} */ +#ifdef HAVE_ENCRYPTION +#define PHP_ZIP_DEFAULT_OPTIONS { .enc_method = -1, .comp_method = -1, .flags = ZIP_FL_OVERWRITE } +#else +#define PHP_ZIP_DEFAULT_OPTIONS { .comp_method = -1, .flags = ZIP_FL_OVERWRITE } +#endif + /* {{{ php_zip_set_file_comment */ static bool php_zip_set_file_comment(struct zip *za, zip_uint64_t index, const char *comment, size_t comment_len) { @@ -373,13 +379,6 @@ static int php_zip_parse_options(HashTable *options, zip_options *opts) { zval *option; - /* default values */ - opts->flags = ZIP_FL_OVERWRITE; - opts->comp_method = -1; /* -1 to not change default */ -#ifdef HAVE_ENCRYPTION - opts->enc_method = -1; /* -1 to not change default */ -#endif - if ((option = zend_hash_str_find(options, "remove_all_path", sizeof("remove_all_path") - 1)) != NULL) { if (Z_TYPE_P(option) != IS_FALSE && Z_TYPE_P(option) != IS_TRUE) { php_error_docref(NULL, E_WARNING, "Option \"remove_all_path\" must be of type bool, %s given", @@ -1757,7 +1756,7 @@ static void php_zip_add_from_pattern(INTERNAL_FUNCTION_PARAMETERS, int type) /* size_t path_len = 1; zend_long glob_flags = 0; HashTable *options = NULL; - zip_options opts = {0}; + zip_options opts = PHP_ZIP_DEFAULT_OPTIONS; int found; zend_string *pattern; diff --git a/ext/zip/tests/addGlob_default_options.phpt b/ext/zip/tests/addGlob_default_options.phpt new file mode 100644 index 000000000000..f1951e849e07 --- /dev/null +++ b/ext/zip/tests/addGlob_default_options.phpt @@ -0,0 +1,79 @@ +--TEST-- +ZipArchive::addGlob() uses the default options when none are supplied +--EXTENSIONS-- +zip +--FILE-- +open($archive); + $sb = $zip->statIndex(0); + $zip->close(); + + return $sb; +} + +function add_glob(string $archive, string $pattern, ?array $options): bool +{ + $zip = new ZipArchive(); + $zip->open($archive, ZipArchive::CREATE | ZipArchive::OVERWRITE); + $added = $options === null + ? $zip->addGlob($pattern) + : $zip->addGlob($pattern, 0, $options); + $zip->close(); + + return is_array($added); +} + +/* comp_method defaults to "leave it to libzip", i.e. deflate, not CM_STORE. */ +foreach (['no options' => null, 'empty options' => []] as $label => $options) { + echo "-- $label --", PHP_EOL; + @unlink($archive); + var_dump(add_glob($archive, $dir . '/*.txt', $options)); + $sb = stat_first($archive); + var_dump($sb['comp_method'] === ZipArchive::CM_DEFLATE); + var_dump($sb['comp_size'] < $sb['size']); +} + +/* flags defaults to FL_OVERWRITE, so an existing entry is replaced. */ +echo '-- overwrites an existing entry --', PHP_EOL; +@unlink($archive); +$zip = new ZipArchive(); +$zip->open($archive, ZipArchive::CREATE | ZipArchive::OVERWRITE); +$zip->addFromString($src, 'placeholder'); +var_dump(is_array($zip->addGlob($dir . '/*.txt'))); +$zip->close(); + +$zip = new ZipArchive(); +$zip->open($archive); +var_dump($zip->numFiles); +var_dump($zip->getFromName($src) === file_get_contents($src)); +$zip->close(); +?> +--CLEAN-- + +--EXPECT-- +-- no options -- +bool(true) +bool(true) +bool(true) +-- empty options -- +bool(true) +bool(true) +bool(true) +-- overwrites an existing entry -- +bool(true) +int(1) +bool(true) From f4f5b344822e08e2c95f00d3b989131701157bbd Mon Sep 17 00:00:00 2001 From: David Carlier Date: Sat, 8 Aug 2026 10:23:15 +0100 Subject: [PATCH 2/2] addPattern() default options test coverage. Both entry points share php_zip_add_from_pattern(), only addGlob() was covered. --- ext/zip/tests/addPattern_default_options.phpt | 79 +++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 ext/zip/tests/addPattern_default_options.phpt diff --git a/ext/zip/tests/addPattern_default_options.phpt b/ext/zip/tests/addPattern_default_options.phpt new file mode 100644 index 000000000000..404e47e5291c --- /dev/null +++ b/ext/zip/tests/addPattern_default_options.phpt @@ -0,0 +1,79 @@ +--TEST-- +ZipArchive::addPattern() uses the default options when none are supplied +--EXTENSIONS-- +zip +--FILE-- +open($archive); + $sb = $zip->statIndex(0); + $zip->close(); + + return $sb; +} + +function add_pattern(string $archive, string $pattern, string $path, ?array $options): bool +{ + $zip = new ZipArchive(); + $zip->open($archive, ZipArchive::CREATE | ZipArchive::OVERWRITE); + $added = $options === null + ? $zip->addPattern($pattern, $path) + : $zip->addPattern($pattern, $path, $options); + $zip->close(); + + return is_array($added); +} + +/* comp_method defaults to "leave it to libzip", i.e. deflate, not CM_STORE. */ +foreach (['no options' => null, 'empty options' => []] as $label => $options) { + echo "-- $label --", PHP_EOL; + @unlink($archive); + var_dump(add_pattern($archive, '/^data\.txt$/', $dir, $options)); + $sb = stat_first($archive); + var_dump($sb['comp_method'] === ZipArchive::CM_DEFLATE); + var_dump($sb['comp_size'] < $sb['size']); +} + +/* flags defaults to FL_OVERWRITE, so an existing entry is replaced. */ +echo '-- overwrites an existing entry --', PHP_EOL; +@unlink($archive); +$zip = new ZipArchive(); +$zip->open($archive, ZipArchive::CREATE | ZipArchive::OVERWRITE); +$zip->addFromString($src, 'placeholder'); +var_dump(is_array($zip->addPattern('/^data\.txt$/', $dir))); +$zip->close(); + +$zip = new ZipArchive(); +$zip->open($archive); +var_dump($zip->numFiles); +var_dump($zip->getFromIndex(0) === file_get_contents($src)); +$zip->close(); +?> +--CLEAN-- + +--EXPECT-- +-- no options -- +bool(true) +bool(true) +bool(true) +-- empty options -- +bool(true) +bool(true) +bool(true) +-- overwrites an existing entry -- +bool(true) +int(1) +bool(true)