Skip to content

Commit de15d91

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 8527d42 commit de15d91

9 files changed

+211
-14
lines changed

ext/zip/php_zip.c

Lines changed: 51 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -576,7 +576,7 @@ static char * php_zipobj_get_zip_comment(ze_zip_object *obj, int *len) /* {{{ */
576576
/* }}} */
577577

578578
/* Close and free the zip_t */
579-
static bool php_zipobj_close(ze_zip_object *obj) /* {{{ */
579+
static bool php_zipobj_close(ze_zip_object *obj, zend_string **out_str) /* {{{ */
580580
{
581581
struct zip *intern = obj->za;
582582
bool success = false;
@@ -608,7 +608,17 @@ static bool php_zipobj_close(ze_zip_object *obj) /* {{{ */
608608
obj->filename_len = 0;
609609
}
610610

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+
611620
obj->za = NULL;
621+
obj->from_string = false;
612622
return success;
613623
}
614624
/* }}} */
@@ -1060,7 +1070,7 @@ static void php_zip_object_free_storage(zend_object *object) /* {{{ */
10601070
{
10611071
ze_zip_object * intern = php_zip_fetch_object(object);
10621072

1063-
php_zipobj_close(intern);
1073+
php_zipobj_close(intern, NULL);
10641074

10651075
#ifdef HAVE_PROGRESS_CALLBACK
10661076
/* if not properly called by libzip */
@@ -1467,7 +1477,7 @@ PHP_METHOD(ZipArchive, open)
14671477
}
14681478

14691479
/* If we already have an opened zip, free it */
1470-
php_zipobj_close(ze_obj);
1480+
php_zipobj_close(ze_obj, NULL);
14711481

14721482
/* open for write without option to empty the archive */
14731483
if ((flags & (ZIP_TRUNCATE | ZIP_RDONLY)) == 0) {
@@ -1491,28 +1501,34 @@ PHP_METHOD(ZipArchive, open)
14911501
ze_obj->filename = resolved_path;
14921502
ze_obj->filename_len = strlen(resolved_path);
14931503
ze_obj->za = intern;
1504+
ze_obj->from_string = false;
14941505
RETURN_TRUE;
14951506
}
14961507
/* }}} */
14971508

14981509
/* {{{ Create new read-only zip using given string */
14991510
PHP_METHOD(ZipArchive, openString)
15001511
{
1501-
zend_string *buffer;
1512+
zend_string *buffer = NULL;
1513+
zend_long flags = 0;
15021514
zval *self = ZEND_THIS;
15031515

1504-
if (zend_parse_parameters(ZEND_NUM_ARGS(), "S", &buffer) == FAILURE) {
1516+
if (zend_parse_parameters(ZEND_NUM_ARGS(), "|Sl", &buffer, &flags) == FAILURE) {
15051517
RETURN_THROWS();
15061518
}
15071519

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

1510-
php_zipobj_close(ze_obj);
1526+
php_zipobj_close(ze_obj, NULL);
15111527

15121528
zip_error_t err;
15131529
zip_error_init(&err);
15141530

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

15171533
if (!zip_source) {
15181534
ze_obj->err_zip = zip_error_code_zip(&err);
@@ -1521,7 +1537,7 @@ PHP_METHOD(ZipArchive, openString)
15211537
RETURN_LONG(ze_obj->err_zip);
15221538
}
15231539

1524-
struct zip *intern = zip_open_from_source(zip_source, ZIP_RDONLY, &err);
1540+
struct zip *intern = zip_open_from_source(zip_source, flags, &err);
15251541
if (!intern) {
15261542
ze_obj->err_zip = zip_error_code_zip(&err);
15271543
ze_obj->err_sys = zip_error_code_system(&err);
@@ -1530,6 +1546,7 @@ PHP_METHOD(ZipArchive, openString)
15301546
RETURN_LONG(ze_obj->err_zip);
15311547
}
15321548

1549+
ze_obj->from_string = true;
15331550
ze_obj->za = intern;
15341551
zip_error_fini(&err);
15351552
RETURN_TRUE;
@@ -1568,7 +1585,32 @@ PHP_METHOD(ZipArchive, close)
15681585

15691586
ZIP_FROM_OBJECT(intern, self);
15701587

1571-
RETURN_BOOL(php_zipobj_close(Z_ZIP_P(self)));
1588+
RETURN_BOOL(php_zipobj_close(Z_ZIP_P(self), NULL));
1589+
}
1590+
/* }}} */
1591+
1592+
/* {{{ close the zip archive and get the result as a string */
1593+
PHP_METHOD(ZipArchive, closeString)
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::closeString 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;
15721614
}
15731615
/* }}} */
15741616

ext/zip/php_zip.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,8 @@ typedef struct _ze_zip_object {
7171
HashTable *prop_handler;
7272
char *filename;
7373
size_t filename_len;
74+
zend_string * out_str;
75+
bool from_string;
7476
zip_int64_t last_id;
7577
int err_zip;
7678
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.\n";
8+
$zip = new ZipArchive();
9+
$zip->openString();
10+
var_dump($zip->closeString());
11+
echo $zip->getStatusString() . "\n";
12+
13+
echo "2.\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.
24+
string(0) ""
25+
No error
26+
2.
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.\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.\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.\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.
2238
foo
2339
bar
2440
foobar/
2541
foobar/baz
2642
bool(false)
2743
bool(false)
2844
bool(true)
45+
2.
46+
bool(true)
47+
int(10)
48+
File already exists
49+
3.
50+
bool(true)
51+
int(%d)

0 commit comments

Comments
 (0)