diff --git a/ext/zip/php_zip.c b/ext/zip/php_zip.c index 1a65a1e87220..fc5cc27454c8 100644 --- a/ext/zip/php_zip.c +++ b/ext/zip/php_zip.c @@ -574,7 +574,7 @@ static char * php_zipobj_get_zip_comment(ze_zip_object *obj, int *len) /* {{{ */ /* }}} */ /* Close and free the zip_t */ -static bool php_zipobj_close(ze_zip_object *obj) /* {{{ */ +static bool php_zipobj_close(ze_zip_object *obj, zend_string **out_str) /* {{{ */ { struct zip *intern = obj->za; bool success = false; @@ -606,7 +606,17 @@ static bool php_zipobj_close(ze_zip_object *obj) /* {{{ */ obj->filename_len = 0; } + if (obj->out_str) { + if (out_str) { + *out_str = obj->out_str; + } else { + zend_string_release(obj->out_str); + } + obj->out_str = NULL; + } + obj->za = NULL; + obj->from_string = false; return success; } /* }}} */ @@ -1060,7 +1070,7 @@ static void php_zip_object_free_storage(zend_object *object) /* {{{ */ { ze_zip_object * intern = php_zip_fetch_object(object); - php_zipobj_close(intern); + php_zipobj_close(intern, NULL); #ifdef HAVE_PROGRESS_CALLBACK /* if not properly called by libzip */ @@ -1467,7 +1477,7 @@ PHP_METHOD(ZipArchive, open) } /* If we already have an opened zip, free it */ - php_zipobj_close(ze_obj); + php_zipobj_close(ze_obj, NULL); /* open for write without option to empty the archive */ if ((flags & (ZIP_TRUNCATE | ZIP_RDONLY)) == 0) { @@ -1491,28 +1501,34 @@ PHP_METHOD(ZipArchive, open) ze_obj->filename = resolved_path; ze_obj->filename_len = strlen(resolved_path); ze_obj->za = intern; + ze_obj->from_string = false; RETURN_TRUE; } /* }}} */ -/* {{{ Create new read-only zip using given string */ +/* {{{ Create new zip from a string, or a create an empty zip to be saved to a string */ PHP_METHOD(ZipArchive, openString) { - zend_string *buffer; + zend_string *buffer = NULL; + zend_long flags = 0; zval *self = ZEND_THIS; - if (zend_parse_parameters(ZEND_NUM_ARGS(), "S", &buffer) == FAILURE) { + if (zend_parse_parameters(ZEND_NUM_ARGS(), "|Sl", &buffer, &flags) == FAILURE) { RETURN_THROWS(); } + if (!buffer) { + buffer = ZSTR_EMPTY_ALLOC(); + } + ze_zip_object *ze_obj = Z_ZIP_P(self); - php_zipobj_close(ze_obj); + php_zipobj_close(ze_obj, NULL); zip_error_t err; zip_error_init(&err); - zip_source_t * zip_source = php_zip_create_string_source(buffer, NULL, &err); + zip_source_t * zip_source = php_zip_create_string_source(buffer, &ze_obj->out_str, &err); if (!zip_source) { ze_obj->err_zip = zip_error_code_zip(&err); @@ -1521,7 +1537,7 @@ PHP_METHOD(ZipArchive, openString) RETURN_LONG(ze_obj->err_zip); } - struct zip *intern = zip_open_from_source(zip_source, ZIP_RDONLY, &err); + struct zip *intern = zip_open_from_source(zip_source, flags, &err); if (!intern) { ze_obj->err_zip = zip_error_code_zip(&err); ze_obj->err_sys = zip_error_code_system(&err); @@ -1530,6 +1546,7 @@ PHP_METHOD(ZipArchive, openString) RETURN_LONG(ze_obj->err_zip); } + ze_obj->from_string = true; ze_obj->za = intern; zip_error_fini(&err); RETURN_TRUE; @@ -1568,7 +1585,32 @@ PHP_METHOD(ZipArchive, close) ZIP_FROM_OBJECT(intern, self); - RETURN_BOOL(php_zipobj_close(Z_ZIP_P(self))); + RETURN_BOOL(php_zipobj_close(Z_ZIP_P(self), NULL)); +} +/* }}} */ + +/* {{{ close the zip archive and get the result as a string */ +PHP_METHOD(ZipArchive, closeString) +{ + struct zip *intern; + zval *self = ZEND_THIS; + + ZEND_PARSE_PARAMETERS_NONE(); + + ZIP_FROM_OBJECT(intern, self); + + if (!Z_ZIP_P(self)->from_string) { + zend_throw_error(NULL, "ZipArchive::closeString can only be called on " + "an archive opened with ZipArchive::openString"); + RETURN_THROWS(); + } + + zend_string * ret = NULL; + bool success = php_zipobj_close(Z_ZIP_P(self), &ret); + if (success) { + RETURN_STR(ret ? ret : ZSTR_EMPTY_ALLOC()); + } + RETURN_FALSE; } /* }}} */ diff --git a/ext/zip/php_zip.h b/ext/zip/php_zip.h index 4c7661256538..d83f93ed0140 100644 --- a/ext/zip/php_zip.h +++ b/ext/zip/php_zip.h @@ -69,6 +69,8 @@ typedef struct _ze_zip_object { HashTable *prop_handler; char *filename; size_t filename_len; + zend_string * out_str; + bool from_string; zip_int64_t last_id; int err_zip; int err_sys; diff --git a/ext/zip/php_zip.stub.php b/ext/zip/php_zip.stub.php index 19ea67e07fba..49dd19e53553 100644 --- a/ext/zip/php_zip.stub.php +++ b/ext/zip/php_zip.stub.php @@ -646,7 +646,7 @@ class ZipArchive implements Countable /** @tentative-return-type */ public function open(string $filename, int $flags = 0): bool|int {} - public function openString(string $data): bool|int {} + public function openString(string $data = '', int $flags = 0): bool|int {} /** * @tentative-return-type @@ -656,6 +656,8 @@ public function setPassword(#[\SensitiveParameter] string $password): bool {} /** @tentative-return-type */ public function close(): bool {} + public function closeString(): string|false {} + /** @tentative-return-type */ public function count(): int {} diff --git a/ext/zip/php_zip_arginfo.h b/ext/zip/php_zip_arginfo.h index ae2569400efe..faa6feb1cb1e 100644 --- a/ext/zip/php_zip_arginfo.h +++ b/ext/zip/php_zip_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit php_zip.stub.php instead. - * Stub hash: bf6706496639628a3287d0026f68f57ecebc4a55 */ + * Stub hash: d623efdfe5ac46f07aebf8fb120050c818f3d793 */ ZEND_BEGIN_ARG_INFO_EX(arginfo_zip_open, 0, 0, 1) ZEND_ARG_TYPE_INFO(0, filename, IS_STRING, 0) @@ -45,8 +45,9 @@ ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_TYPE_MASK_EX(arginfo_class_ZipArchive_open, ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, flags, IS_LONG, 0, "0") ZEND_END_ARG_INFO() -ZEND_BEGIN_ARG_WITH_RETURN_TYPE_MASK_EX(arginfo_class_ZipArchive_openString, 0, 1, MAY_BE_BOOL|MAY_BE_LONG) - ZEND_ARG_TYPE_INFO(0, data, IS_STRING, 0) +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_MASK_EX(arginfo_class_ZipArchive_openString, 0, 0, MAY_BE_BOOL|MAY_BE_LONG) + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, data, IS_STRING, 0, "\'\'") + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, flags, IS_LONG, 0, "0") ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_TYPE_INFO_EX(arginfo_class_ZipArchive_setPassword, 0, 1, _IS_BOOL, 0) @@ -56,6 +57,9 @@ ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_TYPE_INFO_EX(arginfo_class_ZipArchive_close, 0, 0, _IS_BOOL, 0) ZEND_END_ARG_INFO() +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_MASK_EX(arginfo_class_ZipArchive_closeString, 0, 0, MAY_BE_STRING|MAY_BE_FALSE) +ZEND_END_ARG_INFO() + ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_TYPE_INFO_EX(arginfo_class_ZipArchive_count, 0, 0, IS_LONG, 0) ZEND_END_ARG_INFO() @@ -320,6 +324,7 @@ ZEND_METHOD(ZipArchive, open); ZEND_METHOD(ZipArchive, openString); ZEND_METHOD(ZipArchive, setPassword); ZEND_METHOD(ZipArchive, close); +ZEND_METHOD(ZipArchive, closeString); ZEND_METHOD(ZipArchive, count); ZEND_METHOD(ZipArchive, getStatusString); ZEND_METHOD(ZipArchive, clearError); @@ -399,6 +404,7 @@ static const zend_function_entry class_ZipArchive_methods[] = { ZEND_ME(ZipArchive, openString, arginfo_class_ZipArchive_openString, ZEND_ACC_PUBLIC) ZEND_ME(ZipArchive, setPassword, arginfo_class_ZipArchive_setPassword, ZEND_ACC_PUBLIC) ZEND_ME(ZipArchive, close, arginfo_class_ZipArchive_close, ZEND_ACC_PUBLIC) + ZEND_ME(ZipArchive, closeString, arginfo_class_ZipArchive_closeString, ZEND_ACC_PUBLIC) ZEND_ME(ZipArchive, count, arginfo_class_ZipArchive_count, ZEND_ACC_PUBLIC) ZEND_ME(ZipArchive, getStatusString, arginfo_class_ZipArchive_getStatusString, ZEND_ACC_PUBLIC) ZEND_ME(ZipArchive, clearError, arginfo_class_ZipArchive_clearError, ZEND_ACC_PUBLIC) diff --git a/ext/zip/tests/ZipArchive_closeString_basic.phpt b/ext/zip/tests/ZipArchive_closeString_basic.phpt new file mode 100644 index 000000000000..852a7ebf53f3 --- /dev/null +++ b/ext/zip/tests/ZipArchive_closeString_basic.phpt @@ -0,0 +1,25 @@ +--TEST-- +ZipArchive::closeString() basic +--EXTENSIONS-- +zip +--FILE-- +openString(); +$zip->addFromString('test1', '1'); +$zip->addFromString('test2', '2'); +$contents = $zip->closeString(); +echo $contents ? "OK\n" : "FAILED\n"; + +$zip = new ZipArchive(); +$zip->openString($contents); +var_dump($zip->getFromName('test1')); +var_dump($zip->getFromName('test2')); +var_dump($zip->getFromName('nonexistent')); + +?> +--EXPECT-- +OK +string(1) "1" +string(1) "2" +bool(false) diff --git a/ext/zip/tests/ZipArchive_closeString_error.phpt b/ext/zip/tests/ZipArchive_closeString_error.phpt new file mode 100644 index 000000000000..d5dca14a97fc --- /dev/null +++ b/ext/zip/tests/ZipArchive_closeString_error.phpt @@ -0,0 +1,47 @@ +--TEST-- +ZipArchive::closeString() error cases +--EXTENSIONS-- +zip +--FILE-- +openString(); +var_dump($zip->open(__DIR__ . '/test.zip')); +try { + $zip->closeString(); +} catch (Error $e) { + echo $e->getMessage() . "\n"; +} + +echo "2.\n"; +$zip = new ZipArchive(); +$zip->openString('...'); +echo $zip->getStatusString() . "\n"; +try { + $zip->closeString(); +} catch (Error $e) { + echo $e->getMessage() . "\n"; +} + +echo "3.\n"; +$zip = new ZipArchive(); +$zip->openString(file_get_contents(__DIR__ . '/test.zip')); +echo gettype($zip->closeString()) . "\n"; +try { + $zip->closeString(); +} catch (Error $e) { + echo $e->getMessage() . "\n"; +} + +?> +--EXPECT-- +1. +bool(true) +ZipArchive::closeString can only be called on an archive opened with ZipArchive::openString +2. +Not a zip archive +Invalid or uninitialized Zip object +3. +string +Invalid or uninitialized Zip object diff --git a/ext/zip/tests/ZipArchive_closeString_false.phpt b/ext/zip/tests/ZipArchive_closeString_false.phpt new file mode 100644 index 000000000000..8a7d942527f6 --- /dev/null +++ b/ext/zip/tests/ZipArchive_closeString_false.phpt @@ -0,0 +1,22 @@ +--TEST-- +ZipArchive::closeString() false return +--EXTENSIONS-- +zip +--FILE-- +openString($input)); +$zip->setCompressionIndex(0, ZipArchive::CM_DEFLATE); +var_dump($zip->closeString()); +echo $zip->getStatusString() . "\n"; +?> +--EXPECTREGEX-- +bool\(true\) + +Warning: ZipArchive::closeString\(\): (Zip archive inconsistent|Unexpected length of data).* +bool\(false\) +(Zip archive inconsistent|Unexpected length of data) diff --git a/ext/zip/tests/ZipArchive_closeString_variation.phpt b/ext/zip/tests/ZipArchive_closeString_variation.phpt new file mode 100644 index 000000000000..3024d8bc9076 --- /dev/null +++ b/ext/zip/tests/ZipArchive_closeString_variation.phpt @@ -0,0 +1,28 @@ +--TEST-- +ZipArchive::closeString() variations +--EXTENSIONS-- +zip +--FILE-- +openString(); +var_dump($zip->closeString()); +echo $zip->getStatusString() . "\n"; + +echo "2.\n"; +$input = file_get_contents(__DIR__ . '/test.zip'); +$zip = new ZipArchive(); +$zip->openString($input); +$zip->addFromString('entry1.txt', ''); +$result = $zip->closeString(); +echo gettype($result) . "\n"; +var_dump($input !== $result); +?> +--EXPECT-- +1. +string(0) "" +No error +2. +string +bool(true) diff --git a/ext/zip/tests/ZipArchive_openString.phpt b/ext/zip/tests/ZipArchive_openString.phpt index f787b4a84933..f475ab3e77d3 100644 --- a/ext/zip/tests/ZipArchive_openString.phpt +++ b/ext/zip/tests/ZipArchive_openString.phpt @@ -4,8 +4,10 @@ ZipArchive::openString() method zip --FILE-- openString(file_get_contents(__DIR__."/test_procedural.zip")); +$zip->openString($input, ZipArchive::RDONLY); for ($i = 0; $i < $zip->numFiles; $i++) { $stat = $zip->statIndex($i); @@ -17,8 +19,22 @@ var_dump($zip->addFromString("foobar/baz", "baz")); var_dump($zip->addEmptyDir("blub")); var_dump($zip->close()); + +echo "2.\n"; +$zip = new ZipArchive(); +var_dump($zip->openString($input, ZipArchive::CREATE)); +var_dump($zip->openString($input, ZipArchive::EXCL)); +echo $zip->getStatusString() . "\n"; + +echo "3.\n"; +$inconsistent = file_get_contents(__DIR__ . '/checkcons.zip'); +$zip = new ZipArchive(); +var_dump($zip->openString($inconsistent)); +var_dump($zip->openString($inconsistent, ZipArchive::CHECKCONS)); + ?> --EXPECTF-- +1. foo bar foobar/ @@ -26,3 +42,10 @@ foobar/baz bool(false) bool(false) bool(true) +2. +bool(true) +int(10) +File already exists +3. +bool(true) +int(%d) diff --git a/ext/zip/tests/checkcons.zip b/ext/zip/tests/checkcons.zip new file mode 100644 index 000000000000..50bcea128a46 Binary files /dev/null and b/ext/zip/tests/checkcons.zip differ diff --git a/ext/zip/tests/wrong-file-size.zip b/ext/zip/tests/wrong-file-size.zip new file mode 100644 index 000000000000..fc9fa1a434c7 Binary files /dev/null and b/ext/zip/tests/wrong-file-size.zip differ