Skip to content

Commit 605c075

Browse files
tstarlingshyimGaitholabi
authored andcommitted
ext/zip: add ZipArchive::openString() method
Fixes to phpGH-14078: * Rename ZipArchive::openBuffer() to ::openString(). * For consistency with ::open(), return int|bool, don't throw an exception on error. Provide error information via existing properties and accessors. * Fix memory leak when ::openString() is called but ::close() is not called. Add test. * Fix memory leak when a call to ::open() is followed by a call to ::openString(). Add test. * Let libzip own the source, don't call zip_source_keep(). * Share buffer handling with ZipArchive::addFromString(). Elsewhere: * If there is an error from zip_close() during a call to ZipArchive::open(), emit a warning but proceed to open the archive, don't return early. Add test. * When buffers are saved by ZipArchive::addFromString(), release them in ZipArchive::close() and ::open(), don't accumulate buffers until the free_obj handler is called. * Factor out buffer handling and reuse it in ZipArchive::openString() Closes phpGH-21205. Closes phpGH-14078. Co-authored-by: Soner Sayakci <s.sayakci@shopware.com> Co-authored-by: Ghaith Olabi <24876890+Gaitholabi@users.noreply.github.com>
1 parent 006f141 commit 605c075

File tree

9 files changed

+202
-74
lines changed

9 files changed

+202
-74
lines changed

NEWS

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,5 +151,7 @@ PHP NEWS
151151
(ilutov)
152152
. Support minimum version for libzip dependency updated to 1.0.0.
153153
(David Carlier)
154+
. Added ZipArchive::openString() method.
155+
(Tim Starling, Soner Sayakci, Ghaith Olabi)
154156

155157
<<< NOTE: Insert NEWS from last stable release here prior to actual release! >>>

UPGRADING

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,9 @@ PHP 8.6 UPGRADE NOTES
154154
bound.
155155
RFC: https://wiki.php.net/rfc/clamp_v2
156156

157+
- Zip:
158+
. Added ZipArchive::openString() method.
159+
157160
========================================
158161
7. New Classes and Interfaces
159162
========================================

ext/zip/php_zip.c

Lines changed: 109 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -575,6 +575,68 @@ 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+
600+
/* Close and free the zip_t */
601+
static bool php_zipobj_close(ze_zip_object *obj) /* {{{ */
602+
{
603+
struct zip *intern = obj->za;
604+
bool success = false;
605+
606+
if (intern) {
607+
int err = zip_close(intern);
608+
if (err) {
609+
php_error_docref(NULL, E_WARNING, "%s", zip_strerror(intern));
610+
/* Save error for property reader */
611+
zip_error_t *ziperr = zip_get_error(intern);
612+
obj->err_zip = zip_error_code_zip(ziperr);
613+
obj->err_sys = zip_error_code_system(ziperr);
614+
zip_error_fini(ziperr);
615+
zip_discard(intern);
616+
} else {
617+
obj->err_zip = 0;
618+
obj->err_sys = 0;
619+
}
620+
success = !err;
621+
}
622+
623+
/* if we have a filename, we need to free it */
624+
if (obj->filename) {
625+
/* clear cache as empty zip are not created but deleted */
626+
php_clear_stat_cache(1, obj->filename, obj->filename_len);
627+
628+
efree(obj->filename);
629+
obj->filename = NULL;
630+
obj->filename_len = 0;
631+
}
632+
633+
php_zipobj_release_buffers(obj);
634+
635+
obj->za = NULL;
636+
return success;
637+
}
638+
/* }}} */
639+
578640
int php_zip_glob(zend_string *spattern, zend_long flags, zval *return_value) /* {{{ */
579641
{
580642
int cwd_skip = 0;
@@ -1021,21 +1083,8 @@ static void php_zip_object_dtor(zend_object *object)
10211083
static void php_zip_object_free_storage(zend_object *object) /* {{{ */
10221084
{
10231085
ze_zip_object * intern = php_zip_fetch_object(object);
1024-
int i;
1025-
1026-
if (intern->za) {
1027-
if (zip_close(intern->za) != 0) {
1028-
php_error_docref(NULL, E_WARNING, "Cannot destroy the zip context: %s", zip_strerror(intern->za));
1029-
zip_discard(intern->za);
1030-
}
1031-
}
10321086

1033-
if (intern->buffers_cnt>0) {
1034-
for (i=0; i<intern->buffers_cnt; i++) {
1035-
zend_string_release(intern->buffers[i]);
1036-
}
1037-
efree(intern->buffers);
1038-
}
1087+
php_zipobj_close(intern);
10391088

10401089
#ifdef HAVE_PROGRESS_CALLBACK
10411090
/* if not properly called by libzip */
@@ -1047,12 +1096,7 @@ static void php_zip_object_free_storage(zend_object *object) /* {{{ */
10471096
php_zip_cancel_callback_free(intern);
10481097
#endif
10491098

1050-
intern->za = NULL;
10511099
zend_object_std_dtor(&intern->zo);
1052-
1053-
if (intern->filename) {
1054-
efree(intern->filename);
1055-
}
10561100
}
10571101
/* }}} */
10581102

@@ -1448,19 +1492,8 @@ PHP_METHOD(ZipArchive, open)
14481492
RETURN_FALSE;
14491493
}
14501494

1451-
if (ze_obj->za) {
1452-
/* we already have an opened zip, free it */
1453-
if (zip_close(ze_obj->za) != 0) {
1454-
php_error_docref(NULL, E_WARNING, "Empty string as source");
1455-
efree(resolved_path);
1456-
RETURN_FALSE;
1457-
}
1458-
ze_obj->za = NULL;
1459-
}
1460-
if (ze_obj->filename) {
1461-
efree(ze_obj->filename);
1462-
ze_obj->filename = NULL;
1463-
}
1495+
/* If we already have an opened zip, free it */
1496+
php_zipobj_close(ze_obj);
14641497

14651498
/* open for write without option to empty the archive */
14661499
if ((flags & (ZIP_TRUNCATE | ZIP_RDONLY)) == 0) {
@@ -1488,6 +1521,48 @@ PHP_METHOD(ZipArchive, open)
14881521
}
14891522
/* }}} */
14901523

1524+
/* {{{ Create new read-only zip using given string */
1525+
PHP_METHOD(ZipArchive, openString)
1526+
{
1527+
zend_string *buffer;
1528+
zval *self = ZEND_THIS;
1529+
1530+
if (zend_parse_parameters(ZEND_NUM_ARGS(), "S", &buffer) == FAILURE) {
1531+
RETURN_THROWS();
1532+
}
1533+
1534+
ze_zip_object *ze_obj = Z_ZIP_P(self);
1535+
1536+
zip_error_t err;
1537+
zip_error_init(&err);
1538+
1539+
zip_source_t * zip_source = zip_source_buffer_create(ZSTR_VAL(buffer), ZSTR_LEN(buffer), 0, &err);
1540+
1541+
if (!zip_source) {
1542+
ze_obj->err_zip = zip_error_code_zip(&err);
1543+
ze_obj->err_sys = zip_error_code_system(&err);
1544+
zip_error_fini(&err);
1545+
RETURN_LONG(ze_obj->err_zip);
1546+
}
1547+
1548+
php_zipobj_close(ze_obj);
1549+
1550+
struct zip *intern = zip_open_from_source(zip_source, ZIP_RDONLY, &err);
1551+
if (!intern) {
1552+
ze_obj->err_zip = zip_error_code_zip(&err);
1553+
ze_obj->err_sys = zip_error_code_system(&err);
1554+
zip_error_fini(&err);
1555+
zip_source_free(zip_source);
1556+
RETURN_LONG(ze_obj->err_zip);
1557+
}
1558+
1559+
php_zipobj_add_buffer(ze_obj, buffer);
1560+
ze_obj->za = intern;
1561+
zip_error_fini(&err);
1562+
RETURN_TRUE;
1563+
}
1564+
/* }}} */
1565+
14911566
/* {{{ Set the password for the active archive */
14921567
PHP_METHOD(ZipArchive, setPassword)
14931568
{
@@ -1515,42 +1590,14 @@ PHP_METHOD(ZipArchive, close)
15151590
{
15161591
struct zip *intern;
15171592
zval *self = ZEND_THIS;
1518-
ze_zip_object *ze_obj;
1519-
int err;
15201593

15211594
if (zend_parse_parameters_none() == FAILURE) {
15221595
RETURN_THROWS();
15231596
}
15241597

15251598
ZIP_FROM_OBJECT(intern, self);
15261599

1527-
ze_obj = Z_ZIP_P(self);
1528-
1529-
err = zip_close(intern);
1530-
if (err) {
1531-
php_error_docref(NULL, E_WARNING, "%s", zip_strerror(intern));
1532-
/* Save error for property reader */
1533-
zip_error_t *ziperr;
1534-
1535-
ziperr = zip_get_error(intern);
1536-
ze_obj->err_zip = zip_error_code_zip(ziperr);
1537-
ze_obj->err_sys = zip_error_code_system(ziperr);
1538-
zip_error_fini(ziperr);
1539-
zip_discard(intern);
1540-
} else {
1541-
ze_obj->err_zip = 0;
1542-
ze_obj->err_sys = 0;
1543-
}
1544-
1545-
/* clear cache as empty zip are not created but deleted */
1546-
php_clear_stat_cache(1, ze_obj->filename, ze_obj->filename_len);
1547-
1548-
efree(ze_obj->filename);
1549-
ze_obj->filename = NULL;
1550-
ze_obj->filename_len = 0;
1551-
ze_obj->za = NULL;
1552-
1553-
RETURN_BOOL(!err);
1600+
RETURN_BOOL(php_zipobj_close(Z_ZIP_P(self)));
15541601
}
15551602
/* }}} */
15561603

@@ -1864,7 +1911,6 @@ PHP_METHOD(ZipArchive, addFromString)
18641911
size_t name_len;
18651912
ze_zip_object *ze_obj;
18661913
struct zip_source *zs;
1867-
int pos = 0;
18681914
zend_long flags = ZIP_FL_OVERWRITE;
18691915

18701916
if (zend_parse_parameters(ZEND_NUM_ARGS(), "sS|l",
@@ -1875,15 +1921,7 @@ PHP_METHOD(ZipArchive, addFromString)
18751921
ZIP_FROM_OBJECT(intern, self);
18761922

18771923
ze_obj = Z_ZIP_P(self);
1878-
if (ze_obj->buffers_cnt) {
1879-
ze_obj->buffers = safe_erealloc(ze_obj->buffers, sizeof(*ze_obj->buffers), (ze_obj->buffers_cnt + 1), 0);
1880-
pos = ze_obj->buffers_cnt++;
1881-
} else {
1882-
ze_obj->buffers = emalloc(sizeof(*ze_obj->buffers));
1883-
ze_obj->buffers_cnt++;
1884-
pos = 0;
1885-
}
1886-
ze_obj->buffers[pos] = zend_string_copy(buffer);
1924+
php_zipobj_add_buffer(ze_obj, buffer);
18871925

18881926
zs = zip_source_buffer(intern, ZSTR_VAL(buffer), ZSTR_LEN(buffer), 0);
18891927

ext/zip/php_zip.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,8 +71,8 @@ typedef struct _ze_zip_object {
7171
zend_string **buffers;
7272
HashTable *prop_handler;
7373
char *filename;
74-
int filename_len;
75-
int buffers_cnt;
74+
size_t filename_len;
75+
size_t buffers_cnt;
7676
zip_int64_t last_id;
7777
int err_zip;
7878
int err_sys;

ext/zip/php_zip.stub.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -646,6 +646,8 @@ 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 {}
650+
649651
/**
650652
* @tentative-return-type
651653
*/

ext/zip/php_zip_arginfo.h

Lines changed: 7 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
--TEST--
2+
ZipArchive::openString() method
3+
--EXTENSIONS--
4+
zip
5+
--FILE--
6+
<?php
7+
$zip = new ZipArchive();
8+
$zip->openString(file_get_contents(__DIR__."/test_procedural.zip"));
9+
10+
for ($i = 0; $i < $zip->numFiles; $i++) {
11+
$stat = $zip->statIndex($i);
12+
echo $stat['name'] . "\n";
13+
}
14+
15+
// Zip is read-only, not allowed
16+
var_dump($zip->addFromString("foobar/baz", "baz"));
17+
var_dump($zip->addEmptyDir("blub"));
18+
19+
var_dump($zip->close());
20+
?>
21+
--EXPECTF--
22+
foo
23+
bar
24+
foobar/
25+
foobar/baz
26+
bool(false)
27+
bool(false)
28+
bool(true)
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
--TEST--
2+
ZipArchive::openString leak tests
3+
--EXTENSIONS--
4+
zip
5+
--FILE--
6+
<?php
7+
$zip = new ZipArchive;
8+
$zip->openString(file_get_contents(__DIR__ . '/test.zip'));
9+
$zip = null;
10+
11+
$zip = new ZipArchive;
12+
$zip->openString(file_get_contents(__DIR__ . '/test.zip'));
13+
$zip->openString(file_get_contents(__DIR__ . '/test.zip'));
14+
$zip = null;
15+
16+
$zip = new ZipArchive;
17+
$zip->openString(file_get_contents(__DIR__ . '/test.zip'));
18+
$zip->open(__DIR__ . '/test.zip');
19+
$zip = null;
20+
21+
$zip = new ZipArchive;
22+
$zip->open(__DIR__ . '/test.zip');
23+
$zip->openString(file_get_contents(__DIR__ . '/test.zip'));
24+
$zip = null;
25+
?>
26+
--EXPECT--
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
--TEST--
2+
zip_close() failure from ZipArchive::open()
3+
--EXTENSIONS--
4+
zip
5+
--FILE--
6+
<?php
7+
// Try to create an archive
8+
$zip = new ZipArchive();
9+
var_dump($zip->open(__DIR__ . '/close-fail.zip', ZipArchive::CREATE));
10+
file_put_contents(__DIR__.'/close-fail-file', '');
11+
$zip->addFile(__DIR__.'/close-fail-file');
12+
// Delete the file to trigger an error on close
13+
@unlink(__DIR__.'/close-fail-file');
14+
var_dump($zip->open(__DIR__ . '/test.zip', ZipArchive::RDONLY));
15+
var_dump($zip->getFromName('bar'));
16+
?>
17+
--EXPECTF--
18+
bool(true)
19+
20+
Warning: ZipArchive::open(): Can't open file: No such file or directory in %s on line %d
21+
bool(true)
22+
string(4) "bar
23+
"

0 commit comments

Comments
 (0)