Commit e0e2963b74e for php.net
commit e0e2963b74ed1a5531e4759defacd3b961c93bda
Author: Tim Starling <tstarling@wikimedia.org>
Date: Fri Apr 10 11:46:59 2026 +1000
Wrap strings passed to libzip with zip_source_function_create() (#21659)
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.
diff --git a/ext/zip/config.m4 b/ext/zip/config.m4
index 29ae030d37b..590be44f650 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 3f05d8454c1..8dd22c7761c 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 4749f91cc27..5194eaa0df0 100644
--- a/ext/zip/php_zip.c
+++ b/ext/zip/php_zip.c
@@ -575,28 +575,6 @@ 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) /* {{{ */
{
@@ -630,8 +608,6 @@ static bool php_zipobj_close(ze_zip_object *obj) /* {{{ */
obj->filename_len = 0;
}
- php_zipobj_release_buffers(obj);
-
obj->za = NULL;
return success;
}
@@ -1531,10 +1507,12 @@ PHP_METHOD(ZipArchive, openString)
ze_zip_object *ze_obj = Z_ZIP_P(self);
+ php_zipobj_close(ze_obj);
+
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, NULL, &err);
if (!zip_source) {
ze_obj->err_zip = zip_error_code_zip(&err);
@@ -1543,8 +1521,6 @@ 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);
if (!intern) {
ze_obj->err_zip = zip_error_code_zip(&err);
@@ -1554,7 +1530,6 @@ PHP_METHOD(ZipArchive, openString)
RETURN_LONG(ze_obj->err_zip);
}
- php_zipobj_add_buffer(ze_obj, buffer);
ze_obj->za = intern;
zip_error_fini(&err);
RETURN_TRUE;
@@ -1597,7 +1572,7 @@ PHP_METHOD(ZipArchive, close)
}
/* }}} */
-/* {{{ close the zip archive */
+/* {{{ get the number of entries */
PHP_METHOD(ZipArchive, count)
{
struct zip *intern;
@@ -1911,9 +1886,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 486d117398c..22e257f5087 100644
--- a/ext/zip/php_zip.h
+++ b/ext/zip/php_zip.h
@@ -68,11 +68,9 @@ 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;
zip_int64_t last_id;
int err_zip;
int err_sys;
@@ -96,6 +94,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/zip_source.c b/ext/zip/zip_source.c
new file mode 100644
index 00000000000..f0222bcd78a
--- /dev/null
+++ b/ext/zip/zip_source.c
@@ -0,0 +1,206 @@
+/*
+ +----------------------------------------------------------------------+
+ | Copyright (c) The PHP Group |
+ +----------------------------------------------------------------------+
+ | This source file is subject to version 3.01 of the PHP license, |
+ | that is bundled with this package in the file LICENSE, and is |
+ | available through the world-wide-web at the following url: |
+ | https://www.php.net/license/3_01.txt |
+ | If you did not receive a copy of the PHP license and are unable to |
+ | obtain it through the world-wide-web, please send a note to |
+ | license@php.net so we can mail you a copy immediately. |
+ +----------------------------------------------------------------------+
+ | Author: Tim Starling <tstarling@wikimedia.org> |
+ +----------------------------------------------------------------------+
+*/
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+#include "php.h"
+#include "php_zip.h"
+
+typedef struct _php_zip_string_source {
+ /* The current string being read from */
+ zend_string *in_str;
+ /* The offset into in_str of the current read position */
+ size_t in_offset;
+ /* The modification time returned in stat calls */
+ time_t mtime;
+ /* The current string being written to */
+ zend_string *out_str;
+ /* The offset into out_str of the current write position */
+ size_t out_offset;
+ /* A place to copy the result to when the archive is closed, or NULL */
+ zend_string **dest;
+ /* The error to be returned when libzip asks for the last error code */
+ zip_error_t error;
+} php_zip_string_source;
+
+/* The source callback function, see https://libzip.org/documentation/zip_source_function.html
+ * This is similar to read_data() in libzip's zip_source_buffer.c */
+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 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);
+}