From a266aa31250e887599de6d0163b32a9496a1d126 Mon Sep 17 00:00:00 2001 From: Tim Starling Date: Mon, 23 Mar 2026 14:18:44 +1100 Subject: [PATCH] Add ZipArchive::closeToString() - Add a $flags parameter to ZipArchive::openString(), by analogy with ZipArchive::open(). This allows the string to be opened read/write. - Have the $data parameter to ZipArchive::openString() default to an empty string, for convenience of callers that want to create an empty archive. This works on all versions of libzip since the change in 1.6.0 only applied to files, it's opt-in for generic sources. - Add ZipArchive::closeToString() which closes the archive and returns the resulting string. For consistency with openString(), return an empty string if the archive is empty. - Wrap strings passed to libzip with zip_source_function_create() instead of using zip_source_buffer_create(). This allows us to make the string writable, and simplifies memory management. --- ext/zip/config.m4 | 2 +- ext/zip/config.w32 | 2 +- ext/zip/php_zip.c | 91 +++++---- ext/zip/php_zip.h | 6 +- ext/zip/php_zip.stub.php | 4 +- ext/zip/php_zip_arginfo.h | 12 +- .../tests/ZipArchive_closeToString_basic.phpt | 25 +++ .../tests/ZipArchive_closeToString_error.phpt | 45 +++++ .../tests/ZipArchive_closeToString_false.phpt | 22 +++ .../ZipArchive_closeToString_variation.phpt | 14 ++ ext/zip/tests/ZipArchive_openString.phpt | 2 +- ext/zip/tests/wrong-file-size.zip | Bin 0 -> 110 bytes ext/zip/zip_source.c | 181 ++++++++++++++++++ 13 files changed, 359 insertions(+), 47 deletions(-) create mode 100644 ext/zip/tests/ZipArchive_closeToString_basic.phpt create mode 100644 ext/zip/tests/ZipArchive_closeToString_error.phpt create mode 100644 ext/zip/tests/ZipArchive_closeToString_false.phpt create mode 100644 ext/zip/tests/ZipArchive_closeToString_variation.phpt create mode 100644 ext/zip/tests/wrong-file-size.zip create mode 100644 ext/zip/zip_source.c diff --git a/ext/zip/config.m4 b/ext/zip/config.m4 index 29ae030d37b66..590be44f6501d 100644 --- a/ext/zip/config.m4 +++ b/ext/zip/config.m4 @@ -49,7 +49,7 @@ if test "$PHP_ZIP" != "no"; then AC_DEFINE([HAVE_ZIP], [1], [Define to 1 if the PHP extension 'zip' is available.]) - PHP_NEW_EXTENSION([zip], [php_zip.c zip_stream.c], [$ext_shared]) + PHP_NEW_EXTENSION([zip], [php_zip.c zip_source.c zip_stream.c], [$ext_shared]) PHP_ADD_EXTENSION_DEP(zip, pcre) PHP_SUBST([ZIP_SHARED_LIBADD]) diff --git a/ext/zip/config.w32 b/ext/zip/config.w32 index 3f05d8454c1ee..8dd22c7761cfb 100644 --- a/ext/zip/config.w32 +++ b/ext/zip/config.w32 @@ -8,7 +8,7 @@ if (PHP_ZIP != "no") { (PHP_ZIP_SHARED && CHECK_LIB("libzip.lib", "zip", PHP_ZIP) || CHECK_LIB("libzip_a.lib", "zip", PHP_ZIP) && CHECK_LIB("libbz2_a.lib", "zip", PHP_ZIP) && CHECK_LIB("zlib_a.lib", "zip", PHP_ZIP) && CHECK_LIB("liblzma_a.lib", "zip", PHP_ZIP)) ) { - EXTENSION('zip', 'php_zip.c zip_stream.c'); + EXTENSION('zip', 'php_zip.c zip_source.c zip_stream.c'); ADD_EXTENSION_DEP('zip', 'pcre'); if (get_define("LIBS_ZIP").match("libzip_a(?:_debug)?\.lib")) { diff --git a/ext/zip/php_zip.c b/ext/zip/php_zip.c index 77d5dacbff94e..7f77624206d1e 100644 --- a/ext/zip/php_zip.c +++ b/ext/zip/php_zip.c @@ -575,30 +575,8 @@ static char * php_zipobj_get_zip_comment(ze_zip_object *obj, int *len) /* {{{ */ } /* }}} */ -/* Add a string to the list of buffers to be released when the object is destroyed.*/ -static void php_zipobj_add_buffer(ze_zip_object *obj, zend_string *str) /* {{{ */ -{ - size_t pos = obj->buffers_cnt++; - obj->buffers = safe_erealloc(obj->buffers, sizeof(*obj->buffers), obj->buffers_cnt, 0); - obj->buffers[pos] = zend_string_copy(str); -} -/* }}} */ - -static void php_zipobj_release_buffers(ze_zip_object *obj) /* {{{ */ -{ - if (obj->buffers_cnt > 0) { - for (size_t i = 0; i < obj->buffers_cnt; i++) { - zend_string_release(obj->buffers[i]); - } - efree(obj->buffers); - obj->buffers = NULL; - } - obj->buffers_cnt = 0; -} -/* }}} */ - /* 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; @@ -630,9 +608,17 @@ static bool php_zipobj_close(ze_zip_object *obj) /* {{{ */ obj->filename_len = 0; } - php_zipobj_release_buffers(obj); + 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; } /* }}} */ @@ -1084,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 */ @@ -1491,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) { @@ -1515,6 +1501,7 @@ 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; } /* }}} */ @@ -1522,19 +1509,26 @@ PHP_METHOD(ZipArchive, open) /* {{{ Create new read-only zip using given 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, NULL); + zip_error_t err; zip_error_init(&err); - zip_source_t * zip_source = zip_source_buffer_create(ZSTR_VAL(buffer), ZSTR_LEN(buffer), 0, &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); @@ -1543,9 +1537,7 @@ PHP_METHOD(ZipArchive, openString) RETURN_LONG(ze_obj->err_zip); } - php_zipobj_close(ze_obj); - - 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); @@ -1554,7 +1546,7 @@ PHP_METHOD(ZipArchive, openString) RETURN_LONG(ze_obj->err_zip); } - php_zipobj_add_buffer(ze_obj, buffer); + ze_obj->from_string = true; ze_obj->za = intern; zip_error_fini(&err); RETURN_TRUE; @@ -1593,11 +1585,36 @@ 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 */ +/* {{{ close the zip archive and get the result as a string */ +PHP_METHOD(ZipArchive, closeToString) +{ + 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::closeToString 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; +} +/* }}} */ + +/* {{{ get the number of entries */ PHP_METHOD(ZipArchive, count) { struct zip *intern; @@ -1911,9 +1928,7 @@ PHP_METHOD(ZipArchive, addFromString) ZIP_FROM_OBJECT(intern, self); ze_obj = Z_ZIP_P(self); - php_zipobj_add_buffer(ze_obj, buffer); - - zs = zip_source_buffer(intern, ZSTR_VAL(buffer), ZSTR_LEN(buffer), 0); + zs = php_zip_create_string_source(buffer, NULL, NULL); if (zs == NULL) { RETURN_FALSE; diff --git a/ext/zip/php_zip.h b/ext/zip/php_zip.h index 0b96d6ec3eaa4..fdb68092faa13 100644 --- a/ext/zip/php_zip.h +++ b/ext/zip/php_zip.h @@ -68,11 +68,11 @@ typedef struct _ze_zip_read_rsrc { /* Extends zend object */ typedef struct _ze_zip_object { struct zip *za; - zend_string **buffers; HashTable *prop_handler; char *filename; size_t filename_len; - size_t buffers_cnt; + zend_string * out_str; + bool from_string; zip_int64_t last_id; int err_zip; int err_sys; @@ -96,6 +96,8 @@ php_stream *php_stream_zip_open(struct zip *arch, struct zip_stat *sb, const cha extern const php_stream_wrapper php_stream_zip_wrapper; +zip_source_t * php_zip_create_string_source(zend_string *str, zend_string **dest, zip_error_t *err); + #define LIBZIP_ATLEAST(m,n,p) (((m<<16) + (n<<8) + p) <= ((LIBZIP_VERSION_MAJOR<<16) + (LIBZIP_VERSION_MINOR<<8) + LIBZIP_VERSION_MICRO)) #endif /* PHP_ZIP_H */ diff --git a/ext/zip/php_zip.stub.php b/ext/zip/php_zip.stub.php index 42dfb006fc61a..a3f7004e9d373 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 closeToString(): bool|string {} + /** @tentative-return-type */ public function count(): int {} diff --git a/ext/zip/php_zip_arginfo.h b/ext/zip/php_zip_arginfo.h index 89dd33b6f1a03..fbb42bdda3365 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: e04b3e90c42074ac364ea25a0e794815bd4271e5 */ + * Stub hash: 6ecef8cd9a1695fb5a1936188908bb9a7e7c9530 */ 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_closeToString, 0, 0, MAY_BE_BOOL|MAY_BE_STRING) +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, closeToString); 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, closeToString, arginfo_class_ZipArchive_closeToString, 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_closeToString_basic.phpt b/ext/zip/tests/ZipArchive_closeToString_basic.phpt new file mode 100644 index 0000000000000..a7854e410fb2d --- /dev/null +++ b/ext/zip/tests/ZipArchive_closeToString_basic.phpt @@ -0,0 +1,25 @@ +--TEST-- +ZipArchive::closeToString() basic +--EXTENSIONS-- +zip +--FILE-- +openString(); +$zip->addFromString('test1', '1'); +$zip->addFromString('test2', '2'); +$contents = $zip->closeToString(); +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_closeToString_error.phpt b/ext/zip/tests/ZipArchive_closeToString_error.phpt new file mode 100644 index 0000000000000..8b260aa1bc075 --- /dev/null +++ b/ext/zip/tests/ZipArchive_closeToString_error.phpt @@ -0,0 +1,45 @@ +--TEST-- +ZipArchive::closeToString() error cases +--EXTENSIONS-- +zip +--FILE-- +openString(); +var_dump($zip->open(__DIR__ . '/test.zip')); +try { + $zip->closeToString(); +} catch (Error $e) { + echo $e->getMessage() . "\n"; +} + +echo "2.\n"; +$zip = new ZipArchive(); +$zip->openString('...'); +echo $zip->getStatusString() . "\n"; +try { + $zip->closeToString(); +} catch (Error $e) { + echo $e->getMessage() . "\n"; +} + +echo "3.\n"; +$input = file_get_contents(__DIR__ . '/test.zip'); +$zip = new ZipArchive(); +$zip->openString($input); +$zip->addFromString('entry1.txt', ''); +$result = $zip->closeToString(); +echo gettype($result) . "\n"; +var_dump($input !== $result); +?> +--EXPECT-- +1. +bool(true) +ZipArchive::closeToString can only be called on an archive opened with ZipArchive::openString +2. +Not a zip archive +Invalid or uninitialized Zip object +3. +string +bool(true) diff --git a/ext/zip/tests/ZipArchive_closeToString_false.phpt b/ext/zip/tests/ZipArchive_closeToString_false.phpt new file mode 100644 index 0000000000000..5087461c8ffda --- /dev/null +++ b/ext/zip/tests/ZipArchive_closeToString_false.phpt @@ -0,0 +1,22 @@ +--TEST-- +ZipArchive::closeToString() false return +--EXTENSIONS-- +zip +--FILE-- +openString($input)); +$zip->setCompressionIndex(0, ZipArchive::CM_DEFLATE); +var_dump($zip->closeToString()); +echo $zip->getStatusString() . "\n"; +?> +--EXPECTREGEX-- +bool\(true\) + +Warning: ZipArchive::closeToString\(\): (Zip archive inconsistent|Unexpected length of data).* +bool\(false\) +(Zip archive inconsistent|Unexpected length of data) diff --git a/ext/zip/tests/ZipArchive_closeToString_variation.phpt b/ext/zip/tests/ZipArchive_closeToString_variation.phpt new file mode 100644 index 0000000000000..056ef4c20ec27 --- /dev/null +++ b/ext/zip/tests/ZipArchive_closeToString_variation.phpt @@ -0,0 +1,14 @@ +--TEST-- +ZipArchive::closeToString() variations +--EXTENSIONS-- +zip +--FILE-- +openString(); +var_dump($zip->closeToString()); +echo $zip->getStatusString() . "\n"; +?> +--EXPECT-- +string(0) "" +No error diff --git a/ext/zip/tests/ZipArchive_openString.phpt b/ext/zip/tests/ZipArchive_openString.phpt index f787b4a84933d..253ffd23e8e45 100644 --- a/ext/zip/tests/ZipArchive_openString.phpt +++ b/ext/zip/tests/ZipArchive_openString.phpt @@ -5,7 +5,7 @@ zip --FILE-- openString(file_get_contents(__DIR__."/test_procedural.zip")); +$zip->openString(file_get_contents(__DIR__."/test_procedural.zip"), ZipArchive::RDONLY); for ($i = 0; $i < $zip->numFiles; $i++) { $stat = $zip->statIndex($i); diff --git a/ext/zip/tests/wrong-file-size.zip b/ext/zip/tests/wrong-file-size.zip new file mode 100644 index 0000000000000000000000000000000000000000..fc9fa1a434c7ddb3188117f5b125cc3cef92e56b GIT binary patch literal 110 zcmWIWW@Zs#0DP1W^zjtZX1Q NBM=$^X*Cds0RYQL62Jfe literal 0 HcmV?d00001 diff --git a/ext/zip/zip_source.c b/ext/zip/zip_source.c new file mode 100644 index 0000000000000..a21c910fdc6cd --- /dev/null +++ b/ext/zip/zip_source.c @@ -0,0 +1,181 @@ +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif +#include "php.h" +#include "php_zip.h" + +typedef struct _php_zip_string_source { + zend_string *in_str; + size_t in_offset; + time_t mtime; + zend_string *out_str; + size_t out_offset; + zend_string **dest; + zip_error_t error; +} php_zip_string_source; + +static zip_int64_t php_zip_string_cb(void *userdata, void *data, zip_uint64_t len, zip_source_cmd_t cmd) +{ + php_zip_string_source *ctx = userdata; + switch (cmd) { + case ZIP_SOURCE_SUPPORTS: + return zip_source_make_command_bitmap( + ZIP_SOURCE_FREE, +#if defined(LIBZIP_VERSION_MAJOR) && LIBZIP_VERSION_MAJOR >= 1 && LIBZIP_VERSION_MINOR >= 10 + ZIP_SOURCE_SUPPORTS_REOPEN, +#endif + ZIP_SOURCE_OPEN, + ZIP_SOURCE_READ, + ZIP_SOURCE_CLOSE, + ZIP_SOURCE_STAT, + ZIP_SOURCE_ERROR, + ZIP_SOURCE_SEEK, + ZIP_SOURCE_TELL, + ZIP_SOURCE_BEGIN_WRITE, + ZIP_SOURCE_WRITE, + ZIP_SOURCE_COMMIT_WRITE, + ZIP_SOURCE_ROLLBACK_WRITE, + ZIP_SOURCE_SEEK_WRITE, + ZIP_SOURCE_TELL_WRITE, + ZIP_SOURCE_REMOVE, + -1 + ); + + case ZIP_SOURCE_FREE: + zend_string_release(ctx->out_str); + zend_string_release(ctx->in_str); + efree(ctx); + return 0; + + /* Read ops */ + + case ZIP_SOURCE_OPEN: + ctx->in_offset = 0; + return 0; + + case ZIP_SOURCE_READ: { + size_t remaining = ZSTR_LEN(ctx->in_str) - ctx->in_offset; + len = MIN(len, remaining); + if (len) { + memcpy(data, ZSTR_VAL(ctx->in_str) + ctx->in_offset, len); + ctx->in_offset += len; + } + return len; + } + + case ZIP_SOURCE_CLOSE: + return 0; + + case ZIP_SOURCE_STAT: { + zip_stat_t *st; + if (len < sizeof(*st)) { + zip_error_set(&ctx->error, ZIP_ER_INVAL, 0); + return -1; + } + + st = (zip_stat_t *)data; + zip_stat_init(st); + st->mtime = ctx->mtime; + st->size = ZSTR_LEN(ctx->in_str); + st->comp_size = st->size; + st->comp_method = ZIP_CM_STORE; + st->encryption_method = ZIP_EM_NONE; + st->valid = ZIP_STAT_MTIME | ZIP_STAT_SIZE | ZIP_STAT_COMP_SIZE | ZIP_STAT_COMP_METHOD | ZIP_STAT_ENCRYPTION_METHOD; + + return sizeof(*st); + } + + case ZIP_SOURCE_ERROR: + return zip_error_to_data(&ctx->error, data, len); + + /* Seekable read ops */ + + case ZIP_SOURCE_SEEK: { + zip_int64_t new_offset = zip_source_seek_compute_offset( + ctx->in_offset, ZSTR_LEN(ctx->in_str), data, len, &ctx->error); + if (new_offset < 0) { + return -1; + } + ctx->in_offset = (size_t)new_offset; + return 0; + } + + case ZIP_SOURCE_TELL: + if (ctx->in_offset > ZIP_INT64_MAX) { + zip_error_set(&ctx->error, ZIP_ER_TELL, EOVERFLOW); + return -1; + } + return (zip_int64_t)ctx->in_offset; + + /* Write ops */ + + case ZIP_SOURCE_BEGIN_WRITE: + zend_string_release(ctx->out_str); + ctx->out_str = ZSTR_EMPTY_ALLOC(); + return 0; + + case ZIP_SOURCE_WRITE: + if (ctx->out_offset > SIZE_MAX - len) { + zip_error_set(&ctx->error, ZIP_ER_MEMORY, 0); + return -1; + } + if (ctx->out_offset + len > ZSTR_LEN(ctx->out_str)) { + ctx->out_str = zend_string_realloc(ctx->out_str, ctx->out_offset + len, false); + } + memcpy(ZSTR_VAL(ctx->out_str) + ctx->out_offset, data, len); + ctx->out_offset += len; + return len; + + case ZIP_SOURCE_COMMIT_WRITE: + ZSTR_VAL(ctx->out_str)[ZSTR_LEN(ctx->out_str)] = '\0'; + zend_string_release(ctx->in_str); + ctx->in_str = ctx->out_str; + ctx->out_str = ZSTR_EMPTY_ALLOC(); + if (ctx->dest) { + *(ctx->dest) = zend_string_copy(ctx->in_str); + } + return 0; + + case ZIP_SOURCE_ROLLBACK_WRITE: + zend_string_release(ctx->out_str); + ctx->out_str = ZSTR_EMPTY_ALLOC(); + return 0; + + case ZIP_SOURCE_SEEK_WRITE: { + zip_int64_t new_offset = zip_source_seek_compute_offset( + ctx->out_offset, ZSTR_LEN(ctx->out_str), data, len, &ctx->error); + if (new_offset < 0) { + return -1; + } + ctx->out_offset = new_offset; + return 0; + } + + case ZIP_SOURCE_TELL_WRITE: + if (ctx->out_offset > ZIP_INT64_MAX) { + zip_error_set(&ctx->error, ZIP_ER_TELL, EOVERFLOW); + return -1; + } + return (zip_int64_t)ctx->out_offset; + + case ZIP_SOURCE_REMOVE: + zend_string_release(ctx->in_str); + ctx->in_str = ZSTR_EMPTY_ALLOC(); + ctx->in_offset = 0; + return 0; + + default: + zip_error_set(&ctx->error, ZIP_ER_OPNOTSUPP, 0); + return -1; + } +} + +zip_source_t * php_zip_create_string_source(zend_string *str, zend_string **dest, zip_error_t *err) /* {{{ */ +{ + php_zip_string_source *ctx = ecalloc(1, sizeof(php_zip_string_source)); + ctx->in_str = zend_string_copy(str); + ctx->out_str = ZSTR_EMPTY_ALLOC(); + ctx->dest = dest; + ctx->mtime = time(NULL); + return zip_source_function_create(php_zip_string_cb, (void*)ctx, err); +}