[PHP-CVS] [php-src] master: Merge branch 'PHP-8.5'

[email protected] (Weilin Du)
Newsgroups php.cvs
Message-ID <[email protected]>
Author: Weilin Du (LamentXU123)
Date: 2026-08-14T04:12:50+08:00

Commit: https://github.com/php/php-src/commit/93adc39ea8ed0f185f9a2895f49b29d7ee0b8741
Raw diff: https://github.com/php/php-src/commit/93adc39ea8ed0f185f9a2895f49b29d7ee0b8741.diff

Merge branch 'PHP-8.5'

* PHP-8.5:
  Fix GH-17787: ZipArchive stream truncates when the archive is freed (#22555)

Changed paths:
  A  ext/zip/tests/gh17787.phpt
  M  NEWS
  M  ext/zip/php_zip.c
  M  ext/zip/php_zip.h
  M  ext/zip/zip_stream.c


Diff:

diff --git a/NEWS b/NEWS
index d3b9b62ce8b2..0265ad778095 100644
--- a/NEWS
+++ b/NEWS
@@ -19,6 +19,10 @@ PHP                                                                        NEWS
 - Readline:
   . Fixed class constant completion in the interactive shell. (Weilin Du)
 
+- Zip:
+  . Fixed bug GH-17787 (ZipArchive stream stops reading early when the archive
+    is freed while the stream is still open). (Eyüp Can Akman)
+
 13 Aug 2026, PHP 8.6.0beta1
 
 - Core:
diff --git a/ext/zip/php_zip.c b/ext/zip/php_zip.c
index 4b5f4d64c0ad..ff355768e26f 100644
--- a/ext/zip/php_zip.c
+++ b/ext/zip/php_zip.c
@@ -2988,7 +2988,7 @@ static void php_zip_get_stream(INTERNAL_FUNCTION_PARAMETERS, int type, bool acce
 		PHP_ZIP_STAT_INDEX(intern, index, flags, sb);
 	}
 
-	stream = php_stream_zip_open(intern, &sb, mode, flags STREAMS_CC);
+	stream = php_stream_zip_open(Z_ZIP_P(self), &sb, mode, flags STREAMS_CC);
 	if (stream) {
 		php_stream_to_zval(stream, return_value);
 	} else {
diff --git a/ext/zip/php_zip.h b/ext/zip/php_zip.h
index e734c4628f02..a10b1910f2ad 100644
--- a/ext/zip/php_zip.h
+++ b/ext/zip/php_zip.h
@@ -90,7 +90,7 @@ typedef struct _ze_zip_object {
 #define Z_ZIP_P(zv) php_zip_fetch_object(Z_OBJ_P((zv)))
 
 php_stream *php_stream_zip_opener(php_stream_wrapper *wrapper, const char *path, const char *mode, int options, zend_string **opened_path, php_stream_context *context STREAMS_DC);
-php_stream *php_stream_zip_open(struct zip *arch, struct zip_stat *sb, const char *mode, zip_flags_t flags STREAMS_DC);
+php_stream *php_stream_zip_open(ze_zip_object *obj, struct zip_stat *sb, const char *mode, zip_flags_t flags STREAMS_DC);
 
 extern const php_stream_wrapper php_stream_zip_wrapper;
 
diff --git a/ext/zip/tests/gh17787.phpt b/ext/zip/tests/gh17787.phpt
new file mode 100644
index 000000000000..f82cfa1145bd
--- /dev/null
+++ b/ext/zip/tests/gh17787.phpt
@@ -0,0 +1,80 @@
+--TEST--
+GH-17787 (ZipArchive stream stops reading early when the archive is freed while the stream is open)
+--EXTENSIONS--
+zip
+--FILE--
+<?php
+$name = __DIR__ . '/gh17787.zip';
+$data = str_repeat("The quick brown fox jumps over the lazy dog.\n", 4000);
+
+$zip = new ZipArchive;
+$zip->open($name, ZipArchive::CREATE | ZipArchive::OVERWRITE);
+$zip->addFromString('entry.txt', $data);
+$zip->close();
+
+$zip = new ZipArchive;
+$zip->open($name, ZipArchive::RDONLY);
+$stream = $zip->getStreamIndex(0, ZipArchive::FL_UNCHANGED);
+
+// Free the archive while the stream is still open
+$zip = null;
+
+var_dump(stream_get_contents($stream) === $data);
+fclose($stream);
+
+// Same with getStreamName()
+$zip = new ZipArchive;
+$zip->open($name, ZipArchive::RDONLY);
+$stream = $zip->getStreamName('entry.txt', ZipArchive::FL_UNCHANGED);
+$zip = null;
+
+var_dump(stream_get_contents($stream) === $data);
+fclose($stream);
+
+// Same with getStream()
+$zip = new ZipArchive;
+$zip->open($name, ZipArchive::RDONLY);
+$stream = $zip->getStream('entry.txt');
+$zip = null;
+
+var_dump(stream_get_contents($stream) === $data);
+fclose($stream);
+
+// Pending changes are still committed once the last stream is closed
+$name = __DIR__ . '/gh17787_write.zip';
+
+$zip = new ZipArchive;
+var_dump($zip->open($name, ZipArchive::CREATE | ZipArchive::OVERWRITE));
+$zip->addFromString('first.txt', 'first');
+$zip->close();
+
+$zip = new ZipArchive;
+var_dump($zip->open($name));
+$zip->addFromString('second.txt', 'second');
+$stream = $zip->getStreamName('first.txt', ZipArchive::FL_UNCHANGED);
+$zip = null;
+
+var_dump(stream_get_contents($stream));
+fclose($stream);
+
+$zip = new ZipArchive;
+var_dump($zip->open($name, ZipArchive::RDONLY));
+var_dump($zip->numFiles);
+var_dump($zip->getFromName('second.txt'));
+$zip->close();
+?>
+--CLEAN--
+<?php
+@unlink(__DIR__ . '/gh17787.zip');
+@unlink(__DIR__ . '/gh17787_write.zip');
+?>
+--EXPECT--
+bool(true)
+bool(true)
+bool(true)
+bool(true)
+bool(true)
+string(5) "first"
+bool(true)
+int(2)
+string(6) "second"
diff --git a/ext/zip/zip_stream.c b/ext/zip/zip_stream.c
index d4c24bd24e13..89c6a46e653d 100644
--- a/ext/zip/zip_stream.c
+++ b/ext/zip/zip_stream.c
@@ -32,6 +32,7 @@ struct php_zip_stream_data_t {
 	struct zip_file *zf;
 	size_t cursor;
 	php_stream *stream;
+	ze_zip_object *owner;
 };
 
 #define STREAM_DATA_FROM_STREAM() \
@@ -92,6 +93,12 @@ static int php_zip_ops_close(php_stream *stream, int close_handle)
 			self->za = NULL;
 		}
 	}
+
+	/* the pinned object ref is tied to self, so release it regardless of close_handle */
+	if (self->owner) {
+		OBJ_RELEASE(&self->owner->zo);
+		self->owner = NULL;
+	}
 	efree(self);
 	stream->abstract = NULL;
 	return EOF;
@@ -227,8 +234,9 @@ const php_stream_ops php_stream_zipio_ops = {
 };
 
 /* {{{ php_stream_zip_open */
-php_stream *php_stream_zip_open(struct zip *arch, struct zip_stat *sb, const char *mode, zip_flags_t flags STREAMS_DC)
+php_stream *php_stream_zip_open(ze_zip_object *obj, struct zip_stat *sb, const char *mode, zip_flags_t flags STREAMS_DC)
 {
+	struct zip *arch = obj->za;
 	struct zip_file *zf = NULL;
 
 	php_stream *stream = NULL;
@@ -247,6 +255,9 @@ php_stream *php_stream_zip_open(struct zip *arch, struct zip_stat *sb, const cha
 			self->zf = zf;
 			self->stream = NULL;
 			self->cursor = 0;
+			/* keep the archive object alive while the stream borrows its zip_t */
+			self->owner = obj;
+			GC_ADDREF(&obj->zo);
 #if LIBZIP_ATLEAST(1,9,1)
 			if (zip_file_is_seekable(zf) > 0) {
 				stream = php_stream_alloc(&php_stream_zipio_seek_ops, self, NULL, mode);
@@ -332,6 +343,7 @@ php_stream *php_stream_zip_opener(php_stream_wrapper *wrapper,
 			self->zf = zf;
 			self->stream = NULL;
 			self->cursor = 0;
+			self->owner = NULL;
 #if LIBZIP_ATLEAST(1,9,1)
 			if (zip_file_is_seekable(zf) > 0) {
 				stream = php_stream_alloc(&php_stream_zipio_seek_ops, self, NULL, mode);
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.