Skip to content

Commit 2c11df3

Browse files
committed
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.
1 parent 00c0a9b commit 2c11df3

13 files changed

+359
-47
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: 53 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -575,30 +575,8 @@ 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 */
601-
static bool php_zipobj_close(ze_zip_object *obj) /* {{{ */
579+
static bool php_zipobj_close(ze_zip_object *obj, zend_string **out_str) /* {{{ */
602580
{
603581
struct zip *intern = obj->za;
604582
bool success = false;
@@ -630,9 +608,17 @@ static bool php_zipobj_close(ze_zip_object *obj) /* {{{ */
630608
obj->filename_len = 0;
631609
}
632610

633-
php_zipobj_release_buffers(obj);
611+
if (obj->out_str) {
612+
if (out_str) {
613+
*out_str = obj->out_str;
614+
} else {
615+
zend_string_release(obj->out_str);
616+
}
617+
obj->out_str = NULL;
618+
}
634619

635620
obj->za = NULL;
621+
obj->from_string = false;
636622
return success;
637623
}
638624
/* }}} */
@@ -1084,7 +1070,7 @@ static void php_zip_object_free_storage(zend_object *object) /* {{{ */
10841070
{
10851071
ze_zip_object * intern = php_zip_fetch_object(object);
10861072

1087-
php_zipobj_close(intern);
1073+
php_zipobj_close(intern, NULL);
10881074

10891075
#ifdef HAVE_PROGRESS_CALLBACK
10901076
/* if not properly called by libzip */
@@ -1491,7 +1477,7 @@ PHP_METHOD(ZipArchive, open)
14911477
}
14921478

14931479
/* If we already have an opened zip, free it */
1494-
php_zipobj_close(ze_obj);
1480+
php_zipobj_close(ze_obj, NULL);
14951481

14961482
/* open for write without option to empty the archive */
14971483
if ((flags & (ZIP_TRUNCATE | ZIP_RDONLY)) == 0) {
@@ -1515,26 +1501,34 @@ PHP_METHOD(ZipArchive, open)
15151501
ze_obj->filename = resolved_path;
15161502
ze_obj->filename_len = strlen(resolved_path);
15171503
ze_obj->za = intern;
1504+
ze_obj->from_string = false;
15181505
RETURN_TRUE;
15191506
}
15201507
/* }}} */
15211508

15221509
/* {{{ Create new read-only zip using given string */
15231510
PHP_METHOD(ZipArchive, openString)
15241511
{
1525-
zend_string *buffer;
1512+
zend_string *buffer = NULL;
1513+
zend_long flags = 0;
15261514
zval *self = ZEND_THIS;
15271515

1528-
if (zend_parse_parameters(ZEND_NUM_ARGS(), "S", &buffer) == FAILURE) {
1516+
if (zend_parse_parameters(ZEND_NUM_ARGS(), "|Sl", &buffer, &flags) == FAILURE) {
15291517
RETURN_THROWS();
15301518
}
15311519

1520+
if (!buffer) {
1521+
buffer = ZSTR_EMPTY_ALLOC();
1522+
}
1523+
15321524
ze_zip_object *ze_obj = Z_ZIP_P(self);
15331525

1526+
php_zipobj_close(ze_obj, NULL);
1527+
15341528
zip_error_t err;
15351529
zip_error_init(&err);
15361530

1537-
zip_source_t * zip_source = zip_source_buffer_create(ZSTR_VAL(buffer), ZSTR_LEN(buffer), 0, &err);
1531+
zip_source_t * zip_source = php_zip_create_string_source(buffer, &ze_obj->out_str, &err);
15381532

15391533
if (!zip_source) {
15401534
ze_obj->err_zip = zip_error_code_zip(&err);
@@ -1543,9 +1537,7 @@ PHP_METHOD(ZipArchive, openString)
15431537
RETURN_LONG(ze_obj->err_zip);
15441538
}
15451539

1546-
php_zipobj_close(ze_obj);
1547-
1548-
struct zip *intern = zip_open_from_source(zip_source, ZIP_RDONLY, &err);
1540+
struct zip *intern = zip_open_from_source(zip_source, flags, &err);
15491541
if (!intern) {
15501542
ze_obj->err_zip = zip_error_code_zip(&err);
15511543
ze_obj->err_sys = zip_error_code_system(&err);
@@ -1554,7 +1546,7 @@ PHP_METHOD(ZipArchive, openString)
15541546
RETURN_LONG(ze_obj->err_zip);
15551547
}
15561548

1557-
php_zipobj_add_buffer(ze_obj, buffer);
1549+
ze_obj->from_string = true;
15581550
ze_obj->za = intern;
15591551
zip_error_fini(&err);
15601552
RETURN_TRUE;
@@ -1593,11 +1585,36 @@ PHP_METHOD(ZipArchive, close)
15931585

15941586
ZIP_FROM_OBJECT(intern, self);
15951587

1596-
RETURN_BOOL(php_zipobj_close(Z_ZIP_P(self)));
1588+
RETURN_BOOL(php_zipobj_close(Z_ZIP_P(self), NULL));
15971589
}
15981590
/* }}} */
15991591

1600-
/* {{{ close the zip archive */
1592+
/* {{{ close the zip archive and get the result as a string */
1593+
PHP_METHOD(ZipArchive, closeToString)
1594+
{
1595+
struct zip *intern;
1596+
zval *self = ZEND_THIS;
1597+
1598+
ZEND_PARSE_PARAMETERS_NONE();
1599+
1600+
ZIP_FROM_OBJECT(intern, self);
1601+
1602+
if (!Z_ZIP_P(self)->from_string) {
1603+
zend_throw_error(NULL, "ZipArchive::closeToString can only be called on "
1604+
"an archive opened with ZipArchive::openString");
1605+
RETURN_THROWS();
1606+
}
1607+
1608+
zend_string * ret = NULL;
1609+
bool success = php_zipobj_close(Z_ZIP_P(self), &ret);
1610+
if (success) {
1611+
RETURN_STR(ret ? ret : ZSTR_EMPTY_ALLOC());
1612+
}
1613+
RETURN_FALSE;
1614+
}
1615+
/* }}} */
1616+
1617+
/* {{{ get the number of entries */
16011618
PHP_METHOD(ZipArchive, count)
16021619
{
16031620
struct zip *intern;
@@ -1911,9 +1928,7 @@ PHP_METHOD(ZipArchive, addFromString)
19111928
ZIP_FROM_OBJECT(intern, self);
19121929

19131930
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);
1931+
zs = php_zip_create_string_source(buffer, NULL, NULL);
19171932

19181933
if (zs == NULL) {
19191934
RETURN_FALSE;

ext/zip/php_zip.h

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,11 +68,11 @@ 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;
74+
zend_string * out_str;
75+
bool from_string;
7676
zip_int64_t last_id;
7777
int err_zip;
7878
int err_sys;
@@ -96,6 +96,8 @@ php_stream *php_stream_zip_open(struct zip *arch, struct zip_stat *sb, const cha
9696

9797
extern const php_stream_wrapper php_stream_zip_wrapper;
9898

99+
zip_source_t * php_zip_create_string_source(zend_string *str, zend_string **dest, zip_error_t *err);
100+
99101
#define LIBZIP_ATLEAST(m,n,p) (((m<<16) + (n<<8) + p) <= ((LIBZIP_VERSION_MAJOR<<16) + (LIBZIP_VERSION_MINOR<<8) + LIBZIP_VERSION_MICRO))
100102

101103
#endif /* PHP_ZIP_H */

ext/zip/php_zip.stub.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -646,7 +646,7 @@ class ZipArchive implements Countable
646646
/** @tentative-return-type */
647647
public function open(string $filename, int $flags = 0): bool|int {}
648648

649-
public function openString(string $data): bool|int {}
649+
public function openString(string $data = '', int $flags = 0): bool|int {}
650650

651651
/**
652652
* @tentative-return-type
@@ -656,6 +656,8 @@ public function setPassword(#[\SensitiveParameter] string $password): bool {}
656656
/** @tentative-return-type */
657657
public function close(): bool {}
658658

659+
public function closeToString(): bool|string {}
660+
659661
/** @tentative-return-type */
660662
public function count(): int {}
661663

ext/zip/php_zip_arginfo.h

Lines changed: 9 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
--TEST--
2+
ZipArchive::closeToString() basic
3+
--EXTENSIONS--
4+
zip
5+
--FILE--
6+
<?php
7+
$zip = new ZipArchive();
8+
$zip->openString();
9+
$zip->addFromString('test1', '1');
10+
$zip->addFromString('test2', '2');
11+
$contents = $zip->closeToString();
12+
echo $contents ? "OK\n" : "FAILED\n";
13+
14+
$zip = new ZipArchive();
15+
$zip->openString($contents);
16+
var_dump($zip->getFromName('test1'));
17+
var_dump($zip->getFromName('test2'));
18+
var_dump($zip->getFromName('nonexistent'));
19+
20+
?>
21+
--EXPECT--
22+
OK
23+
string(1) "1"
24+
string(1) "2"
25+
bool(false)
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
--TEST--
2+
ZipArchive::closeToString() error cases
3+
--EXTENSIONS--
4+
zip
5+
--FILE--
6+
<?php
7+
echo "1.\n";
8+
$zip = new ZipArchive();
9+
$zip->openString();
10+
var_dump($zip->open(__DIR__ . '/test.zip'));
11+
try {
12+
$zip->closeToString();
13+
} catch (Error $e) {
14+
echo $e->getMessage() . "\n";
15+
}
16+
17+
echo "2.\n";
18+
$zip = new ZipArchive();
19+
$zip->openString('...');
20+
echo $zip->getStatusString() . "\n";
21+
try {
22+
$zip->closeToString();
23+
} catch (Error $e) {
24+
echo $e->getMessage() . "\n";
25+
}
26+
27+
echo "3.\n";
28+
$input = file_get_contents(__DIR__ . '/test.zip');
29+
$zip = new ZipArchive();
30+
$zip->openString($input);
31+
$zip->addFromString('entry1.txt', '');
32+
$result = $zip->closeToString();
33+
echo gettype($result) . "\n";
34+
var_dump($input !== $result);
35+
?>
36+
--EXPECT--
37+
1.
38+
bool(true)
39+
ZipArchive::closeToString can only be called on an archive opened with ZipArchive::openString
40+
2.
41+
Not a zip archive
42+
Invalid or uninitialized Zip object
43+
3.
44+
string
45+
bool(true)
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
--TEST--
2+
ZipArchive::closeToString() false return
3+
--EXTENSIONS--
4+
zip
5+
--FILE--
6+
<?php
7+
$zip = new ZipArchive();
8+
// The "compressed size" fields are wrong, causing an error when reading the contents.
9+
// The error is reported on close when we rewrite the member with setCompressionIndex().
10+
// The error code is ER_DATA_LENGTH in libzip 1.10.0+ or ER_INCONS otherwise.
11+
$input = file_get_contents(__DIR__ . '/wrong-file-size.zip');
12+
var_dump($zip->openString($input));
13+
$zip->setCompressionIndex(0, ZipArchive::CM_DEFLATE);
14+
var_dump($zip->closeToString());
15+
echo $zip->getStatusString() . "\n";
16+
?>
17+
--EXPECTREGEX--
18+
bool\(true\)
19+
20+
Warning: ZipArchive::closeToString\(\): (Zip archive inconsistent|Unexpected length of data).*
21+
bool\(false\)
22+
(Zip archive inconsistent|Unexpected length of data)
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
--TEST--
2+
ZipArchive::closeToString() variations
3+
--EXTENSIONS--
4+
zip
5+
--FILE--
6+
<?php
7+
$zip = new ZipArchive();
8+
$zip->openString();
9+
var_dump($zip->closeToString());
10+
echo $zip->getStatusString() . "\n";
11+
?>
12+
--EXPECT--
13+
string(0) ""
14+
No error

0 commit comments

Comments
 (0)