Skip to content

Commit 26e6159

Browse files
committed
Add ZipArchive::closeString()
- 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::closeString() which closes the archive and returns the resulting string. For consistency with openString(), return an empty string if the archive is empty.
1 parent cd2e060 commit 26e6159

11 files changed

Lines changed: 215 additions & 16 deletions

ext/zip/php_zip.c

Lines changed: 55 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -573,8 +573,10 @@ static char * php_zipobj_get_zip_comment(ze_zip_object *obj, int *len) /* {{{ */
573573
}
574574
/* }}} */
575575

576-
/* Close and free the zip_t */
577-
static bool php_zipobj_close(ze_zip_object *obj) /* {{{ */
576+
/* Close and free the zip_t. If out_str is non-null and the archive was opened
577+
* as a string, the final contents of the archive will be assigned to *out_str
578+
* and that string will afterwards be owned by the caller. */
579+
static bool php_zipobj_close(ze_zip_object *obj, zend_string **out_str) /* {{{ */
578580
{
579581
struct zip *intern = obj->za;
580582
bool success = false;
@@ -606,7 +608,17 @@ static bool php_zipobj_close(ze_zip_object *obj) /* {{{ */
606608
obj->filename_len = 0;
607609
}
608610

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+
}
619+
609620
obj->za = NULL;
621+
obj->from_string = false;
610622
return success;
611623
}
612624
/* }}} */
@@ -1060,7 +1072,7 @@ static void php_zip_object_free_storage(zend_object *object) /* {{{ */
10601072
{
10611073
ze_zip_object * intern = php_zip_fetch_object(object);
10621074

1063-
php_zipobj_close(intern);
1075+
php_zipobj_close(intern, NULL);
10641076

10651077
#ifdef HAVE_PROGRESS_CALLBACK
10661078
/* if not properly called by libzip */
@@ -1467,7 +1479,7 @@ PHP_METHOD(ZipArchive, open)
14671479
}
14681480

14691481
/* If we already have an opened zip, free it */
1470-
php_zipobj_close(ze_obj);
1482+
php_zipobj_close(ze_obj, NULL);
14711483

14721484
/* open for write without option to empty the archive */
14731485
if ((flags & (ZIP_TRUNCATE | ZIP_RDONLY)) == 0) {
@@ -1491,28 +1503,34 @@ PHP_METHOD(ZipArchive, open)
14911503
ze_obj->filename = resolved_path;
14921504
ze_obj->filename_len = strlen(resolved_path);
14931505
ze_obj->za = intern;
1506+
ze_obj->from_string = false;
14941507
RETURN_TRUE;
14951508
}
14961509
/* }}} */
14971510

1498-
/* {{{ Create new read-only zip using given string */
1511+
/* {{{ Create new zip from a string, or a create an empty zip to be saved to a string */
14991512
PHP_METHOD(ZipArchive, openString)
15001513
{
1501-
zend_string *buffer;
1514+
zend_string *buffer = NULL;
1515+
zend_long flags = 0;
15021516
zval *self = ZEND_THIS;
15031517

1504-
if (zend_parse_parameters(ZEND_NUM_ARGS(), "S", &buffer) == FAILURE) {
1518+
if (zend_parse_parameters(ZEND_NUM_ARGS(), "|Sl", &buffer, &flags) == FAILURE) {
15051519
RETURN_THROWS();
15061520
}
15071521

1522+
if (!buffer) {
1523+
buffer = ZSTR_EMPTY_ALLOC();
1524+
}
1525+
15081526
ze_zip_object *ze_obj = Z_ZIP_P(self);
15091527

1510-
php_zipobj_close(ze_obj);
1528+
php_zipobj_close(ze_obj, NULL);
15111529

15121530
zip_error_t err;
15131531
zip_error_init(&err);
15141532

1515-
zip_source_t * zip_source = php_zip_create_string_source(buffer, NULL, &err);
1533+
zip_source_t * zip_source = php_zip_create_string_source(buffer, &ze_obj->out_str, &err);
15161534

15171535
if (!zip_source) {
15181536
ze_obj->err_zip = zip_error_code_zip(&err);
@@ -1521,7 +1539,7 @@ PHP_METHOD(ZipArchive, openString)
15211539
RETURN_LONG(ze_obj->err_zip);
15221540
}
15231541

1524-
struct zip *intern = zip_open_from_source(zip_source, ZIP_RDONLY, &err);
1542+
struct zip *intern = zip_open_from_source(zip_source, flags, &err);
15251543
if (!intern) {
15261544
ze_obj->err_zip = zip_error_code_zip(&err);
15271545
ze_obj->err_sys = zip_error_code_system(&err);
@@ -1530,6 +1548,7 @@ PHP_METHOD(ZipArchive, openString)
15301548
RETURN_LONG(ze_obj->err_zip);
15311549
}
15321550

1551+
ze_obj->from_string = true;
15331552
ze_obj->za = intern;
15341553
zip_error_fini(&err);
15351554
RETURN_TRUE;
@@ -1568,7 +1587,32 @@ PHP_METHOD(ZipArchive, close)
15681587

15691588
ZIP_FROM_OBJECT(intern, self);
15701589

1571-
RETURN_BOOL(php_zipobj_close(Z_ZIP_P(self)));
1590+
RETURN_BOOL(php_zipobj_close(Z_ZIP_P(self), NULL));
1591+
}
1592+
/* }}} */
1593+
1594+
/* {{{ close the zip archive and get the result as a string */
1595+
PHP_METHOD(ZipArchive, closeString)
1596+
{
1597+
struct zip *intern;
1598+
zval *self = ZEND_THIS;
1599+
1600+
ZEND_PARSE_PARAMETERS_NONE();
1601+
1602+
ZIP_FROM_OBJECT(intern, self);
1603+
1604+
if (!Z_ZIP_P(self)->from_string) {
1605+
zend_throw_error(NULL, "ZipArchive::closeString can only be called on "
1606+
"an archive opened with ZipArchive::openString");
1607+
RETURN_THROWS();
1608+
}
1609+
1610+
zend_string *ret = NULL;
1611+
bool success = php_zipobj_close(Z_ZIP_P(self), &ret);
1612+
if (success) {
1613+
RETURN_STR(ret ? ret : ZSTR_EMPTY_ALLOC());
1614+
}
1615+
RETURN_FALSE;
15721616
}
15731617
/* }}} */
15741618

ext/zip/php_zip.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,8 @@ typedef struct _ze_zip_object {
6969
HashTable *prop_handler;
7070
char *filename;
7171
size_t filename_len;
72+
zend_string *out_str;
73+
bool from_string;
7274
zip_int64_t last_id;
7375
int err_zip;
7476
int err_sys;

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 closeString(): string|false {}
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::closeString() 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->closeString();
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: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
--TEST--
2+
ZipArchive::closeString() 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->closeString();
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->closeString();
23+
} catch (Error $e) {
24+
echo $e->getMessage() . "\n";
25+
}
26+
27+
echo "3.\n";
28+
$zip = new ZipArchive();
29+
$zip->openString(file_get_contents(__DIR__ . '/test.zip'));
30+
echo gettype($zip->closeString()) . "\n";
31+
try {
32+
$zip->closeString();
33+
} catch (Error $e) {
34+
echo $e->getMessage() . "\n";
35+
}
36+
37+
?>
38+
--EXPECT--
39+
1.
40+
bool(true)
41+
ZipArchive::closeString can only be called on an archive opened with ZipArchive::openString
42+
2.
43+
Not a zip archive
44+
Invalid or uninitialized Zip object
45+
3.
46+
string
47+
Invalid or uninitialized Zip object
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
--TEST--
2+
ZipArchive::closeString() 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->closeString());
15+
echo $zip->getStatusString() . "\n";
16+
?>
17+
--EXPECTREGEX--
18+
bool\(true\)
19+
20+
Warning: ZipArchive::closeString\(\): (Zip archive inconsistent|Unexpected length of data).*
21+
bool\(false\)
22+
(Zip archive inconsistent|Unexpected length of data)
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
--TEST--
2+
ZipArchive::closeString() variations
3+
--EXTENSIONS--
4+
zip
5+
--FILE--
6+
<?php
7+
echo "1. Empty archive creation\n";
8+
$zip = new ZipArchive();
9+
$zip->openString();
10+
var_dump($zip->closeString());
11+
echo $zip->getStatusString() . "\n";
12+
13+
echo "2. Update existing archive\n";
14+
$input = file_get_contents(__DIR__ . '/test.zip');
15+
$zip = new ZipArchive();
16+
$zip->openString($input);
17+
$zip->addFromString('entry1.txt', '');
18+
$result = $zip->closeString();
19+
echo gettype($result) . "\n";
20+
var_dump($input !== $result);
21+
?>
22+
--EXPECT--
23+
1. Empty archive creation
24+
string(0) ""
25+
No error
26+
2. Update existing archive
27+
string
28+
bool(true)

ext/zip/tests/ZipArchive_openString.phpt

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,10 @@ ZipArchive::openString() method
44
zip
55
--FILE--
66
<?php
7+
echo "1. Open read-only and read directory\n";
8+
$input = file_get_contents(__DIR__."/test_procedural.zip");
79
$zip = new ZipArchive();
8-
$zip->openString(file_get_contents(__DIR__."/test_procedural.zip"));
10+
$zip->openString($input, ZipArchive::RDONLY);
911

1012
for ($i = 0; $i < $zip->numFiles; $i++) {
1113
$stat = $zip->statIndex($i);
@@ -17,12 +19,33 @@ var_dump($zip->addFromString("foobar/baz", "baz"));
1719
var_dump($zip->addEmptyDir("blub"));
1820

1921
var_dump($zip->close());
22+
23+
echo "2. CREATE and EXCL flags\n";
24+
$zip = new ZipArchive();
25+
var_dump($zip->openString($input, ZipArchive::CREATE));
26+
var_dump($zip->openString($input, ZipArchive::EXCL));
27+
echo $zip->getStatusString() . "\n";
28+
29+
echo "3. CHECKCONS flag\n";
30+
$inconsistent = file_get_contents(__DIR__ . '/checkcons.zip');
31+
$zip = new ZipArchive();
32+
var_dump($zip->openString($inconsistent));
33+
var_dump($zip->openString($inconsistent, ZipArchive::CHECKCONS));
34+
2035
?>
2136
--EXPECTF--
37+
1. Open read-only and read directory
2238
foo
2339
bar
2440
foobar/
2541
foobar/baz
2642
bool(false)
2743
bool(false)
2844
bool(true)
45+
2. CREATE and EXCL flags
46+
bool(true)
47+
int(10)
48+
File already exists
49+
3. CHECKCONS flag
50+
bool(true)
51+
int(%d)

ext/zip/tests/checkcons.zip

181 Bytes
Binary file not shown.

0 commit comments

Comments
 (0)