Skip to content

Commit e0e2963

Browse files
authored
Wrap strings passed to libzip with zip_source_function_create() (php#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.
1 parent 715645f commit e0e2963

File tree

5 files changed

+215
-36
lines changed

5 files changed

+215
-36
lines changed

ext/zip/config.m4

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ if test "$PHP_ZIP" != "no"; then
4949
AC_DEFINE([HAVE_ZIP], [1],
5050
[Define to 1 if the PHP extension 'zip' is available.])
5151

52-
PHP_NEW_EXTENSION([zip], [php_zip.c zip_stream.c], [$ext_shared])
52+
PHP_NEW_EXTENSION([zip], [php_zip.c zip_source.c zip_stream.c], [$ext_shared])
5353
PHP_ADD_EXTENSION_DEP(zip, pcre)
5454

5555
PHP_SUBST([ZIP_SHARED_LIBADD])

ext/zip/config.w32

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ if (PHP_ZIP != "no") {
88
(PHP_ZIP_SHARED && CHECK_LIB("libzip.lib", "zip", PHP_ZIP) ||
99
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))
1010
) {
11-
EXTENSION('zip', 'php_zip.c zip_stream.c');
11+
EXTENSION('zip', 'php_zip.c zip_source.c zip_stream.c');
1212
ADD_EXTENSION_DEP('zip', 'pcre');
1313

1414
if (get_define("LIBS_ZIP").match("libzip_a(?:_debug)?\.lib")) {

ext/zip/php_zip.c

Lines changed: 5 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -575,28 +575,6 @@ static char * php_zipobj_get_zip_comment(ze_zip_object *obj, int *len) /* {{{ */
575575
}
576576
/* }}} */
577577

578-
/* Add a string to the list of buffers to be released when the object is destroyed.*/
579-
static void php_zipobj_add_buffer(ze_zip_object *obj, zend_string *str) /* {{{ */
580-
{
581-
size_t pos = obj->buffers_cnt++;
582-
obj->buffers = safe_erealloc(obj->buffers, sizeof(*obj->buffers), obj->buffers_cnt, 0);
583-
obj->buffers[pos] = zend_string_copy(str);
584-
}
585-
/* }}} */
586-
587-
static void php_zipobj_release_buffers(ze_zip_object *obj) /* {{{ */
588-
{
589-
if (obj->buffers_cnt > 0) {
590-
for (size_t i = 0; i < obj->buffers_cnt; i++) {
591-
zend_string_release(obj->buffers[i]);
592-
}
593-
efree(obj->buffers);
594-
obj->buffers = NULL;
595-
}
596-
obj->buffers_cnt = 0;
597-
}
598-
/* }}} */
599-
600578
/* Close and free the zip_t */
601579
static bool php_zipobj_close(ze_zip_object *obj) /* {{{ */
602580
{
@@ -630,8 +608,6 @@ static bool php_zipobj_close(ze_zip_object *obj) /* {{{ */
630608
obj->filename_len = 0;
631609
}
632610

633-
php_zipobj_release_buffers(obj);
634-
635611
obj->za = NULL;
636612
return success;
637613
}
@@ -1531,10 +1507,12 @@ PHP_METHOD(ZipArchive, openString)
15311507

15321508
ze_zip_object *ze_obj = Z_ZIP_P(self);
15331509

1510+
php_zipobj_close(ze_obj);
1511+
15341512
zip_error_t err;
15351513
zip_error_init(&err);
15361514

1537-
zip_source_t * zip_source = zip_source_buffer_create(ZSTR_VAL(buffer), ZSTR_LEN(buffer), 0, &err);
1515+
zip_source_t * zip_source = php_zip_create_string_source(buffer, NULL, &err);
15381516

15391517
if (!zip_source) {
15401518
ze_obj->err_zip = zip_error_code_zip(&err);
@@ -1543,8 +1521,6 @@ PHP_METHOD(ZipArchive, openString)
15431521
RETURN_LONG(ze_obj->err_zip);
15441522
}
15451523

1546-
php_zipobj_close(ze_obj);
1547-
15481524
struct zip *intern = zip_open_from_source(zip_source, ZIP_RDONLY, &err);
15491525
if (!intern) {
15501526
ze_obj->err_zip = zip_error_code_zip(&err);
@@ -1554,7 +1530,6 @@ PHP_METHOD(ZipArchive, openString)
15541530
RETURN_LONG(ze_obj->err_zip);
15551531
}
15561532

1557-
php_zipobj_add_buffer(ze_obj, buffer);
15581533
ze_obj->za = intern;
15591534
zip_error_fini(&err);
15601535
RETURN_TRUE;
@@ -1597,7 +1572,7 @@ PHP_METHOD(ZipArchive, close)
15971572
}
15981573
/* }}} */
15991574

1600-
/* {{{ close the zip archive */
1575+
/* {{{ get the number of entries */
16011576
PHP_METHOD(ZipArchive, count)
16021577
{
16031578
struct zip *intern;
@@ -1911,9 +1886,7 @@ PHP_METHOD(ZipArchive, addFromString)
19111886
ZIP_FROM_OBJECT(intern, self);
19121887

19131888
ze_obj = Z_ZIP_P(self);
1914-
php_zipobj_add_buffer(ze_obj, buffer);
1915-
1916-
zs = zip_source_buffer(intern, ZSTR_VAL(buffer), ZSTR_LEN(buffer), 0);
1889+
zs = php_zip_create_string_source(buffer, NULL, NULL);
19171890

19181891
if (zs == NULL) {
19191892
RETURN_FALSE;

ext/zip/php_zip.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,11 +68,9 @@ typedef struct _ze_zip_read_rsrc {
6868
/* Extends zend object */
6969
typedef struct _ze_zip_object {
7070
struct zip *za;
71-
zend_string **buffers;
7271
HashTable *prop_handler;
7372
char *filename;
7473
size_t filename_len;
75-
size_t buffers_cnt;
7674
zip_int64_t last_id;
7775
int err_zip;
7876
int err_sys;
@@ -96,6 +94,8 @@ php_stream *php_stream_zip_open(struct zip *arch, struct zip_stat *sb, const cha
9694

9795
extern const php_stream_wrapper php_stream_zip_wrapper;
9896

97+
zip_source_t * php_zip_create_string_source(zend_string *str, zend_string **dest, zip_error_t *err);
98+
9999
#define LIBZIP_ATLEAST(m,n,p) (((m<<16) + (n<<8) + p) <= ((LIBZIP_VERSION_MAJOR<<16) + (LIBZIP_VERSION_MINOR<<8) + LIBZIP_VERSION_MICRO))
100100

101101
#endif /* PHP_ZIP_H */

ext/zip/zip_source.c

Lines changed: 206 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
1+
/*
2+
+----------------------------------------------------------------------+
3+
| Copyright (c) The PHP Group |
4+
+----------------------------------------------------------------------+
5+
| This source file is subject to version 3.01 of the PHP license, |
6+
| that is bundled with this package in the file LICENSE, and is |
7+
| available through the world-wide-web at the following url: |
8+
| https://www.php.net/license/3_01.txt |
9+
| If you did not receive a copy of the PHP license and are unable to |
10+
| obtain it through the world-wide-web, please send a note to |
11+
| license@php.net so we can mail you a copy immediately. |
12+
+----------------------------------------------------------------------+
13+
| Author: Tim Starling <tstarling@wikimedia.org> |
14+
+----------------------------------------------------------------------+
15+
*/
16+
17+
#ifdef HAVE_CONFIG_H
18+
# include "config.h"
19+
#endif
20+
#include "php.h"
21+
#include "php_zip.h"
22+
23+
typedef struct _php_zip_string_source {
24+
/* The current string being read from */
25+
zend_string *in_str;
26+
/* The offset into in_str of the current read position */
27+
size_t in_offset;
28+
/* The modification time returned in stat calls */
29+
time_t mtime;
30+
/* The current string being written to */
31+
zend_string *out_str;
32+
/* The offset into out_str of the current write position */
33+
size_t out_offset;
34+
/* A place to copy the result to when the archive is closed, or NULL */
35+
zend_string **dest;
36+
/* The error to be returned when libzip asks for the last error code */
37+
zip_error_t error;
38+
} php_zip_string_source;
39+
40+
/* The source callback function, see https://libzip.org/documentation/zip_source_function.html
41+
* This is similar to read_data() in libzip's zip_source_buffer.c */
42+
static zip_int64_t php_zip_string_cb(void *userdata, void *data, zip_uint64_t len, zip_source_cmd_t cmd)
43+
{
44+
php_zip_string_source *ctx = userdata;
45+
switch (cmd) {
46+
case ZIP_SOURCE_SUPPORTS:
47+
return zip_source_make_command_bitmap(
48+
ZIP_SOURCE_FREE,
49+
#if LIBZIP_VERSION_MAJOR > 1 || LIBZIP_VERSION_MINOR >= 10
50+
ZIP_SOURCE_SUPPORTS_REOPEN,
51+
#endif
52+
ZIP_SOURCE_OPEN,
53+
ZIP_SOURCE_READ,
54+
ZIP_SOURCE_CLOSE,
55+
ZIP_SOURCE_STAT,
56+
ZIP_SOURCE_ERROR,
57+
ZIP_SOURCE_SEEK,
58+
ZIP_SOURCE_TELL,
59+
ZIP_SOURCE_BEGIN_WRITE,
60+
ZIP_SOURCE_WRITE,
61+
ZIP_SOURCE_COMMIT_WRITE,
62+
ZIP_SOURCE_ROLLBACK_WRITE,
63+
ZIP_SOURCE_SEEK_WRITE,
64+
ZIP_SOURCE_TELL_WRITE,
65+
ZIP_SOURCE_REMOVE,
66+
-1
67+
);
68+
69+
case ZIP_SOURCE_FREE:
70+
zend_string_release(ctx->out_str);
71+
zend_string_release(ctx->in_str);
72+
efree(ctx);
73+
return 0;
74+
75+
/* Read ops */
76+
77+
case ZIP_SOURCE_OPEN:
78+
ctx->in_offset = 0;
79+
return 0;
80+
81+
case ZIP_SOURCE_READ: {
82+
size_t remaining = ZSTR_LEN(ctx->in_str) - ctx->in_offset;
83+
len = MIN(len, remaining);
84+
if (len) {
85+
memcpy(data, ZSTR_VAL(ctx->in_str) + ctx->in_offset, len);
86+
ctx->in_offset += len;
87+
}
88+
return len;
89+
}
90+
91+
case ZIP_SOURCE_CLOSE:
92+
return 0;
93+
94+
case ZIP_SOURCE_STAT: {
95+
zip_stat_t *st;
96+
if (len < sizeof(*st)) {
97+
zip_error_set(&ctx->error, ZIP_ER_INVAL, 0);
98+
return -1;
99+
}
100+
101+
st = (zip_stat_t *)data;
102+
zip_stat_init(st);
103+
st->mtime = ctx->mtime;
104+
st->size = ZSTR_LEN(ctx->in_str);
105+
st->comp_size = st->size;
106+
st->comp_method = ZIP_CM_STORE;
107+
st->encryption_method = ZIP_EM_NONE;
108+
st->valid = ZIP_STAT_MTIME | ZIP_STAT_SIZE | ZIP_STAT_COMP_SIZE | ZIP_STAT_COMP_METHOD | ZIP_STAT_ENCRYPTION_METHOD;
109+
110+
return sizeof(*st);
111+
}
112+
113+
case ZIP_SOURCE_ERROR:
114+
return zip_error_to_data(&ctx->error, data, len);
115+
116+
/* Seekable read ops */
117+
118+
case ZIP_SOURCE_SEEK: {
119+
zip_int64_t new_offset = zip_source_seek_compute_offset(
120+
ctx->in_offset, ZSTR_LEN(ctx->in_str), data, len, &ctx->error);
121+
if (new_offset < 0) {
122+
return -1;
123+
}
124+
ctx->in_offset = (size_t)new_offset;
125+
return 0;
126+
}
127+
128+
case ZIP_SOURCE_TELL:
129+
if (ctx->in_offset > ZIP_INT64_MAX) {
130+
zip_error_set(&ctx->error, ZIP_ER_TELL, EOVERFLOW);
131+
return -1;
132+
}
133+
return (zip_int64_t)ctx->in_offset;
134+
135+
/* Write ops */
136+
137+
case ZIP_SOURCE_BEGIN_WRITE:
138+
zend_string_release(ctx->out_str);
139+
ctx->out_str = ZSTR_EMPTY_ALLOC();
140+
return 0;
141+
142+
case ZIP_SOURCE_WRITE:
143+
if (ctx->out_offset > SIZE_MAX - len) {
144+
zip_error_set(&ctx->error, ZIP_ER_MEMORY, 0);
145+
return -1;
146+
}
147+
if (ctx->out_offset + len > ZSTR_LEN(ctx->out_str)) {
148+
ctx->out_str = zend_string_realloc(ctx->out_str, ctx->out_offset + len, false);
149+
}
150+
memcpy(ZSTR_VAL(ctx->out_str) + ctx->out_offset, data, len);
151+
ctx->out_offset += len;
152+
return len;
153+
154+
case ZIP_SOURCE_COMMIT_WRITE:
155+
ZSTR_VAL(ctx->out_str)[ZSTR_LEN(ctx->out_str)] = '\0';
156+
zend_string_release(ctx->in_str);
157+
ctx->in_str = ctx->out_str;
158+
ctx->out_str = ZSTR_EMPTY_ALLOC();
159+
if (ctx->dest) {
160+
*(ctx->dest) = zend_string_copy(ctx->in_str);
161+
}
162+
return 0;
163+
164+
case ZIP_SOURCE_ROLLBACK_WRITE:
165+
zend_string_release(ctx->out_str);
166+
ctx->out_str = ZSTR_EMPTY_ALLOC();
167+
return 0;
168+
169+
case ZIP_SOURCE_SEEK_WRITE: {
170+
zip_int64_t new_offset = zip_source_seek_compute_offset(
171+
ctx->out_offset, ZSTR_LEN(ctx->out_str), data, len, &ctx->error);
172+
if (new_offset < 0) {
173+
return -1;
174+
}
175+
ctx->out_offset = new_offset;
176+
return 0;
177+
}
178+
179+
case ZIP_SOURCE_TELL_WRITE:
180+
if (ctx->out_offset > ZIP_INT64_MAX) {
181+
zip_error_set(&ctx->error, ZIP_ER_TELL, EOVERFLOW);
182+
return -1;
183+
}
184+
return (zip_int64_t)ctx->out_offset;
185+
186+
case ZIP_SOURCE_REMOVE:
187+
zend_string_release(ctx->in_str);
188+
ctx->in_str = ZSTR_EMPTY_ALLOC();
189+
ctx->in_offset = 0;
190+
return 0;
191+
192+
default:
193+
zip_error_set(&ctx->error, ZIP_ER_OPNOTSUPP, 0);
194+
return -1;
195+
}
196+
}
197+
198+
zip_source_t * php_zip_create_string_source(zend_string *str, zend_string **dest, zip_error_t *err)
199+
{
200+
php_zip_string_source *ctx = ecalloc(1, sizeof(php_zip_string_source));
201+
ctx->in_str = zend_string_copy(str);
202+
ctx->out_str = ZSTR_EMPTY_ALLOC();
203+
ctx->dest = dest;
204+
ctx->mtime = time(NULL);
205+
return zip_source_function_create(php_zip_string_cb, (void*)ctx, err);
206+
}

0 commit comments

Comments
 (0)