Commit 75ad0885c9a for php.net

commit 75ad0885c9acd7735442d7de3c038fb59aa147c8
Merge: 85bd163974f 0c3799a4cc0
Author: Weilin Du <weilindu@php.net>
Date:   Tue Aug 25 00:45:29 2026 +0800

    Merge branch 'PHP-8.5'

    * PHP-8.5:
      Merge branch 'PHP-8.4' into PHP-8.5

diff --cc NEWS
index 022d4897360,247fcb340ef..497293d89e3
--- a/NEWS
+++ b/NEWS
@@@ -56,25 -41,16 +56,29 @@@ PH
    . Fixed a leak when a persistent connection failed a liveness check
      with no other live PDO handle. (iliaal)

 +- PDO_PGSQL:
 +  . Fixed several lazy fetch (PDO::ATTR_PREFETCH => 0) defects: an infinite
 +    loop when cleaning up a fetch left in a COPY, a use-after-free when a
 +    statement with emulated or disabled prepares is destroyed, a connection
 +    left busy for the next fetch, and rows delivered from a result another
 +    statement took over. (KentarouTakeda)
 +
 +- PGSQL:
 +  . Fixed the class name casing of pg_close_stmt()'s connection parameter.
 +    (lacatoire)
 +
 +- Phar:
 +  . Fixed Phar archives being automatically detected when ".phar" only occurs
 +    in a directory name or is not a filename extension in an included file's
 +    path. (Weilin Du)
 +
  - Readline:
 -  . Fixed the interactive shell not waiting for the pager process to exit.
 -    (Weilin Du)
 +  . 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)
+   . Fixed bug GH-23276 (ZipArchive subclass storing its own stream cannot be
+     garbage collected). (Weilin Du, ndossche)
+
  - SAPI:
    . Fixed fuzzer targets failing to build in isolation. (Mrmaxmeier)
    . Fixed returns uninitialized value on LiteSpeed lsapi SAPI (Go Kudo)
diff --cc ext/zip/php_zip.c
index f7a294425e6,74c8db3fefd..a7a3e340ecf
--- a/ext/zip/php_zip.c
+++ b/ext/zip/php_zip.c
@@@ -361,10 -357,10 +362,10 @@@ static zend_result php_zip_add_file(ze_
  		FILE *fd;
  		fd = fopen(resolved_path, "rb");
  		if (!fd) {
 -			return -1;
 +			return FAILURE;
  		}
  		flags ^= ZIP_FL_OPEN_FILE_NOW;
- 		zs = zip_source_filep(obj->za, fd, offset_start, offset_len);
+ 		zs = zip_source_filep(za, fd, offset_start, offset_len);
  		if (!zs) {
  			fclose(fd);
  			return FAILURE;
@@@ -377,21 -373,21 +378,21 @@@
  	}
  	/* Replace */
  	if (replace >= 0) {
- 		if (zip_file_replace(obj->za, replace, zs, flags) < 0) {
+ 		if (zip_file_replace(za, replace, zs, flags) < 0) {
  			zip_source_free(zs);
 -			return -1;
 +			return FAILURE;
  		}
- 		zip_error_clear(obj->za);
+ 		zip_error_clear(za);
 -		return 1;
 +		return SUCCESS;
  	}
  	/* Add */
- 	obj->last_id = zip_file_add(obj->za, entry_name, zs, flags);
+ 	obj->last_id = zip_file_add(za, entry_name, zs, flags);
  	if (obj->last_id < 0) {
  		zip_source_free(zs);
 -		return -1;
 +		return FAILURE;
  	}
- 	zip_error_clear(obj->za);
+ 	zip_error_clear(za);
 -	return 1;
 +	return SUCCESS;
  }
  /* }}} */

@@@ -565,14 -551,21 +566,15 @@@ static zend_result php_zip_parse_option

  static zend_long php_zip_status(ze_zip_object *obj) /* {{{ */
  {
+ 	struct zip *za = php_zip_object_za(obj);
 -	int zep = obj->err_zip; /* saved err if closed */
 +	zend_long zep = (zend_long)obj->err_zip; /* saved err if closed */

- 	if (obj->za) {
+ 	if (za) {
 -#if LIBZIP_VERSION_MAJOR < 1
 -		int syp;
 -
 -		zip_error_get(za, &zep, &syp);
 -#else
  		zip_error_t *err;

- 		err = zip_get_error(obj->za);
+ 		err = zip_get_error(za);
 -		zep = zip_error_code_zip(err);
 +		zep = (zend_long)zip_error_code_zip(err);
  		zip_error_fini(err);
 -#endif
  	}
  	return zep;
  }
@@@ -586,14 -579,21 +588,15 @@@ static zend_long php_zip_last_id(ze_zip

  static zend_long php_zip_status_sys(ze_zip_object *obj) /* {{{ */
  {
+ 	struct zip *za = php_zip_object_za(obj);
 -	int syp = obj->err_sys;  /* saved err if closed */
 +	zend_long syp = (zend_long)obj->err_sys;  /* saved err if closed */

- 	if (obj->za) {
+ 	if (za) {
 -#if LIBZIP_VERSION_MAJOR < 1
 -		int zep;
 -
 -		zip_error_get(za, &zep, &syp);
 -#else
  		zip_error_t *err;

- 		err = zip_get_error(obj->za);
+ 		err = zip_get_error(za);
 -		syp = zip_error_code_system(err);
 +		syp = (zend_long)zip_error_code_system(err);
  		zip_error_fini(err);
 -#endif
  	}
  	return syp;
  }
@@@ -628,67 -632,7 +635,73 @@@ static char * php_zipobj_get_zip_commen
  }
  /* }}} */

 -int php_zip_glob(char *pattern, int pattern_len, zend_long flags, zval *return_value) /* {{{ */
 +/* Close and free the zip_t. If the archive was opened as a string, the
 + * final contents of the archive will be assigned to *out_str and that
 + * string will afterwards be owned by the caller.
 + *
 + * If out_str is NULL, the final string contents, if any, will be discarded. */
 +static bool php_zipobj_close(ze_zip_object *obj, zend_string **out_str) /* {{{ */
 +{
- 	struct zip *intern = obj->za;
++	php_zip_archive *archive = obj->archive;
++	struct zip *intern = archive ? archive->za : NULL;
++	bool bailout = false;
 +	bool success = false;
 +
 +	if (intern) {
 +		int err = zip_close(intern);
 +		if (err) {
 +			php_error_docref(NULL, E_WARNING, "%s", zip_strerror(intern));
 +			/* Save error for property reader */
 +			zip_error_t *ziperr = zip_get_error(intern);
 +			obj->err_zip = zip_error_code_zip(ziperr);
 +			obj->err_sys = zip_error_code_system(ziperr);
 +			zip_error_fini(ziperr);
 +			zip_discard(intern);
 +		} else {
 +			obj->err_zip = 0;
 +			obj->err_sys = 0;
 +		}
 +		success = !err;
 +	}
 +
 +	/* if we have a filename, we need to free it */
 +	if (obj->filename) {
 +		/* clear cache as empty zip are not created but deleted */
 +		php_clear_stat_cache(1, obj->filename, obj->filename_len);
 +
 +		efree(obj->filename);
 +		obj->filename = NULL;
 +		obj->filename_len = 0;
 +	}
 +
- 	if (obj->out_str) {
++	if (archive && archive->out_str) {
 +		if (out_str) {
- 			*out_str = obj->out_str;
++			*out_str = archive->out_str;
 +		} else {
- 			zend_string_release(obj->out_str);
++			zend_string_release(archive->out_str);
 +		}
- 		obj->out_str = NULL;
++		archive->out_str = NULL;
 +	} else {
 +		ZEND_ASSERT(!out_str);
 +	}
 +
- 	obj->za = NULL;
- 	obj->from_string = false;
++	if (archive) {
++		archive->za = NULL;
++		bailout = archive->bailout_callback;
++		archive->bailout_callback = false;
++		obj->archive = NULL;
++		bailout |= php_zip_archive_release(archive);
++	}
 +
- 	if (obj->bailout_callback) {
- 		obj->bailout_callback = false;
++	if (bailout) {
 +		zend_bailout();
 +	}
 +
 +	return success;
 +}
 +/* }}} */
 +
 +static int php_zip_glob(zend_string *spattern, zend_long flags, zval *return_value) /* {{{ */
  {
  	int cwd_skip = 0;
  #ifdef ZTS
@@@ -1121,44 -1063,79 +1134,79 @@@ static void php_zip_cancel_callback_fre
  }
  #endif

+ static php_zip_archive *php_zip_archive_create(struct zip *za)
+ {
+ 	php_zip_archive *archive = ecalloc(1, sizeof(php_zip_archive));
+
+ 	archive->za = za;
+ 	archive->refcount = 1;
+
+ 	return archive;
+ }
+
+ void php_zip_archive_addref(php_zip_archive *archive)
+ {
+ 	ZEND_ASSERT(archive->refcount > 0);
+ 	archive->refcount++;
+ }
+
 -void php_zip_archive_release(php_zip_archive *archive)
++bool php_zip_archive_release(php_zip_archive *archive)
+ {
+ 	ZEND_ASSERT(archive->refcount > 0);
+ 	if (--archive->refcount != 0) {
 -		return;
++		return false;
+ 	}
+
+ 	if (archive->za) {
+ 		if (zip_close(archive->za) != 0) {
 -			php_error_docref(NULL, E_WARNING, "Cannot destroy the zip context: %s", zip_strerror(archive->za));
++			if (!archive->bailout_callback) {
++				php_error_docref(NULL, E_WARNING, "Cannot destroy the zip context: %s", zip_strerror(archive->za));
++			}
+ 			zip_discard(archive->za);
+ 		}
++		archive->za = NULL;
+ 	}
+
+ #ifdef HAVE_PROGRESS_CALLBACK
+ 	/* In case libzip did not invoke the callback state destructor. */
+ 	php_zip_progress_callback_free(archive);
+ #endif
+
+ #ifdef HAVE_CANCEL_CALLBACK
+ 	/* In case libzip did not invoke the callback state destructor. */
+ 	php_zip_cancel_callback_free(archive);
+ #endif
+
 -	if (archive->buffers) {
 -		for (int i = 0; i < archive->buffers_cnt; i++) {
 -			efree(archive->buffers[i]);
 -		}
 -		efree(archive->buffers);
++	if (archive->out_str) {
++		zend_string_release(archive->out_str);
+ 	}
+
++	bool bailout = archive->bailout_callback;
+ 	efree(archive);
++	return bailout;
+ }
+
 -/* The caller must close or discard released_za before detaching it. */
 -static void php_zip_object_detach_archive(ze_zip_object *ze_obj, struct zip *released_za)
 +static void php_zip_object_dtor(zend_object *object)
  {
 -	ZEND_ASSERT(ze_obj->archive != NULL);
 -	ZEND_ASSERT(ze_obj->archive->za == released_za);
 -	ze_obj->archive->za = NULL;
 -	php_zip_archive_release(ze_obj->archive);
 -	ze_obj->archive = NULL;
 +	zend_objects_destroy_object(object);
 +
 +	ze_zip_object *intern = php_zip_fetch_object(object);
 +
- 	if (intern->za) {
- 		if (zip_close(intern->za) != 0) {
- 			if (!intern->bailout_callback) {
- 				php_error_docref(NULL, E_WARNING, "Cannot destroy the zip context: %s", zip_strerror(intern->za));
- 			}
- 			zip_discard(intern->za);
- 		}
- 		intern->za = NULL;
- 		if (intern->bailout_callback) {
- 			intern->bailout_callback = false;
++	if (intern->archive) {
++		bool bailout = php_zip_archive_release(intern->archive);
++		intern->archive = NULL;
++		if (bailout) {
 +			zend_bailout();
 +		}
 +	}
  }

  static void php_zip_object_free_storage(zend_object *object) /* {{{ */
  {
  	ze_zip_object * intern = php_zip_fetch_object(object);

 -	if (intern->archive) {
 -		php_zip_archive_release(intern->archive);
 -		intern->archive = NULL;
 -	}
 +	php_zipobj_close(intern, NULL);
-
- #ifdef HAVE_PROGRESS_CALLBACK
- 	/* if not properly called by libzip */
- 	php_zip_progress_callback_free(intern);
- #endif
-
- #ifdef HAVE_CANCEL_CALLBACK
- 	/* if not properly called by libzip */
- 	php_zip_cancel_callback_free(intern);
- #endif
-
  	zend_object_std_dtor(&intern->zo);
 -
 -	if (intern->filename) {
 -		efree(intern->filename);
 -	}
  }
  /* }}} */

@@@ -1582,53 -1580,6 +1629,58 @@@ PHP_METHOD(ZipArchive, open
  }
  /* }}} */

 +/* {{{ Create new zip from a string, or a create an empty zip to be saved to a string */
 +PHP_METHOD(ZipArchive, openString)
 +{
 +	zend_string *buffer = NULL;
 +	zend_long flags = 0;
 +	zval *self = ZEND_THIS;
 +
 +	if (zend_parse_parameters(ZEND_NUM_ARGS(), "|Sl", &buffer, &flags) == FAILURE) {
 +		RETURN_THROWS();
 +	}
 +
 +	if (!buffer) {
 +		buffer = ZSTR_EMPTY_ALLOC();
 +	}
 +
 +	ze_zip_object *ze_obj = Z_ZIP_P(self);
++	php_zip_archive *archive;
 +
 +	php_zipobj_close(ze_obj, NULL);
 +
 +	zip_error_t err;
 +	zip_error_init(&err);
 +
- 	zip_source_t * zip_source = php_zip_create_string_source(buffer, &ze_obj->out_str, &err);
++	archive = php_zip_archive_create(NULL);
++	zip_source_t * zip_source = php_zip_create_string_source(buffer, &archive->out_str, &err);
 +
 +	if (!zip_source) {
 +		ze_obj->err_zip = zip_error_code_zip(&err);
 +		ze_obj->err_sys = zip_error_code_system(&err);
 +		zip_error_fini(&err);
++		php_zip_archive_release(archive);
 +		RETURN_LONG(ze_obj->err_zip);
 +	}
 +
 +	struct zip *intern = zip_open_from_source(zip_source, flags, &err);
 +	if (!intern) {
 +		ze_obj->err_zip = zip_error_code_zip(&err);
 +		ze_obj->err_sys = zip_error_code_system(&err);
 +		zip_error_fini(&err);
 +		zip_source_free(zip_source);
++		php_zip_archive_release(archive);
 +		RETURN_LONG(ze_obj->err_zip);
 +	}
 +
- 	ze_obj->from_string = true;
- 	ze_obj->za = intern;
++	archive->za = intern;
++	archive->from_string = true;
++	ze_obj->archive = archive;
 +	zip_error_fini(&err);
 +	RETURN_TRUE;
 +}
 +/* }}} */
 +
  /* {{{ Set the password for the active archive */
  PHP_METHOD(ZipArchive, setPassword)
  {
@@@ -1661,34 -1621,43 +1713,34 @@@ PHP_METHOD(ZipArchive, close

  	ZIP_FROM_OBJECT(intern, self);

 -	ze_obj = Z_ZIP_P(self);
 +	RETURN_BOOL(php_zipobj_close(Z_ZIP_P(self), NULL));
 +}
 +/* }}} */

 -	err = zip_close(intern);
 -	if (err) {
 -		php_error_docref(NULL, E_WARNING, "%s", zip_strerror(intern));
 -		/* Save error for property reader */
 -		#if LIBZIP_VERSION_MAJOR < 1
 -			zip_error_get(intern, &ze_obj->err_zip, &ze_obj->err_sys);
 -		#else
 -			{
 -			zip_error_t *ziperr;
 -
 -			ziperr = zip_get_error(intern);
 -			ze_obj->err_zip = zip_error_code_zip(ziperr);
 -			ze_obj->err_sys = zip_error_code_system(ziperr);
 -			zip_error_fini(ziperr);
 -			}
 -		#endif
 -		zip_discard(intern);
 -	} else {
 -		ze_obj->err_zip = 0;
 -		ze_obj->err_sys = 0;
 -	}
 +/* {{{ close the zip archive and get the result as a string */
 +PHP_METHOD(ZipArchive, closeString)
 +{
 +	struct zip *intern;
 +	zval *self = ZEND_THIS;

 -	/* clear cache as empty zip are not created but deleted */
 -	php_clear_stat_cache(1, ze_obj->filename, ze_obj->filename_len);
 +	ZEND_PARSE_PARAMETERS_NONE();

 -	efree(ze_obj->filename);
 -	ze_obj->filename = NULL;
 -	ze_obj->filename_len = 0;
 -	php_zip_object_detach_archive(ze_obj, intern);
 +	ZIP_FROM_OBJECT(intern, self);

- 	if (!Z_ZIP_P(self)->from_string) {
 -	if (!err) {
 -		RETURN_TRUE;
 -	} else {
 -		RETURN_FALSE;
++	if (!Z_ZIP_P(self)->archive->from_string) {
 +		zend_throw_error(NULL, "ZipArchive::closeString can only be called on "
 +				"an archive opened with ZipArchive::openString");
 +		RETURN_THROWS();
 +	}
 +
 +	zend_string *ret = NULL;
 +	bool success = php_zipobj_close(Z_ZIP_P(self), &ret);
 +	ZEND_ASSERT(ret);
 +	if (success) {
 +		RETURN_STR(ret);
  	}
 +	zend_string_release(ret);
 +	RETURN_FALSE;
  }
  /* }}} */

@@@ -1737,12 -1684,16 +1789,14 @@@ PHP_METHOD(ZipArchive, clearError
  {
  	zval *self = ZEND_THIS;
  	ze_zip_object *ze_obj;
+ 	struct zip *za;

 -	if (zend_parse_parameters_none() == FAILURE) {
 -		RETURN_THROWS();
 -	}
 +	ZEND_PARSE_PARAMETERS_NONE();

  	ze_obj = Z_ZIP_P(self); /* not ZIP_FROM_OBJECT as we can use saved error after close */
- 	if (ze_obj->za) {
- 		zip_error_clear(ze_obj->za);
+ 	za = php_zip_object_za(ze_obj);
+ 	if (za) {
+ 		zip_error_clear(za);
  	} else {
  		ze_obj->err_zip = 0;
  		ze_obj->err_sys = 0;
@@@ -1754,16 -1705,33 +1808,17 @@@
  PHP_METHOD(ZipArchive, getStatusString)
  {
  	zval *self = ZEND_THIS;
 -#if LIBZIP_VERSION_MAJOR < 1
 -	int zep, syp, len;
 -	char error_string[128];
 -#endif
  	ze_zip_object *ze_obj;
+ 	struct zip *za;

 -	if (zend_parse_parameters_none() == FAILURE) {
 -		RETURN_THROWS();
 -	}
 +	ZEND_PARSE_PARAMETERS_NONE();

  	ze_obj = Z_ZIP_P(self); /* not ZIP_FROM_OBJECT as we can use saved error after close */
-
- 	if (ze_obj->za) {
+ 	za = php_zip_object_za(ze_obj);
 -
 -#if LIBZIP_VERSION_MAJOR < 1
 -	if (za) {
 -		zip_error_get(za, &zep, &syp);
 -		len = zip_error_to_str(error_string, 128, zep, syp);
 -	} else {
 -		len = zip_error_to_str(error_string, 128, ze_obj->err_zip, ze_obj->err_sys);
 -	}
 -	RETVAL_STRINGL(error_string, len);
 -#else
+ 	if (za) {
  		zip_error_t *err;

- 		err = zip_get_error(ze_obj->za);
+ 		err = zip_get_error(za);
  		RETVAL_STRING(zip_error_strerror(err));
  		zip_error_fini(err);
  	} else {
@@@ -1856,10 -1828,18 +1911,11 @@@ static void php_zip_add_from_pattern(IN
  	}

  	if (found > 0) {
 -		int i;
  		zval *zval_file;
 -		ze_zip_object *ze_obj;
 -		struct zip *za;
 -
 -		ze_obj = Z_ZIP_P(self);
 -		za = php_zip_object_za(ze_obj);
 +		ze_zip_object *ze_obj = Z_ZIP_P(self);
++		struct zip *za = php_zip_object_za(ze_obj);

 -		for (i = 0; i < found; i++) {
 -			char *file_stripped, *entry_name;
 -			size_t entry_name_len, file_stripped_len;
 -			char entry_name_buf[MAXPATHLEN];
 +		for (int i = 0; i < found; i++) {
  			zend_string *basename = NULL;

  			if ((zval_file = zend_hash_index_find(Z_ARRVAL_P(return_value), i)) != NULL) {
@@@ -1927,7 -1895,12 +1983,7 @@@
  				}
  #ifdef HAVE_ENCRYPTION
  				if (opts.enc_method >= 0) {
- 					if (!php_zip_file_set_encryption(ze_obj->za, ze_obj->last_id, opts.enc_method, opts.enc_password)) {
 -					if (UNEXPECTED(zip_file_set_encryption(za, ze_obj->last_id, ZIP_EM_NONE, NULL) < 0)) {
 -						zend_array_destroy(Z_ARR_P(return_value));
 -						php_error_docref(NULL, E_WARNING, "password reset failed");
 -						RETURN_FALSE;
 -					}
 -					if (zip_file_set_encryption(za, ze_obj->last_id, opts.enc_method, opts.enc_password)) {
++					if (!php_zip_file_set_encryption(za, ze_obj->last_id, opts.enc_method, opts.enc_password)) {
  						zend_array_destroy(Z_ARR_P(return_value));
  						RETURN_FALSE;
  					}
@@@ -3084,21 -3137,11 +3140,21 @@@ PHP_METHOD(ZipArchive, getStream
  #ifdef HAVE_PROGRESS_CALLBACK
  static void php_zip_progress_callback(zip_t *arch, double state, void *ptr)
  {
- 	ze_zip_object *obj = ptr;
 -	zval cb_args[1];
+ 	php_zip_archive *archive = ptr;

- 	if (UNEXPECTED(!EG(active) || obj->bailout_callback)) {
++	if (UNEXPECTED(!EG(active) || archive->bailout_callback)) {
 +		return;
 +	}
 +
 +	zval cb_args[1];
 +
  	ZVAL_DOUBLE(&cb_args[0], state);
 -	zend_call_known_fcc(&archive->progress_callback, NULL, 1, cb_args, NULL);
 +
 +	zend_try {
- 		zend_call_known_fcc(&obj->progress_callback, NULL, 1, cb_args, NULL);
++		zend_call_known_fcc(&archive->progress_callback, NULL, 1, cb_args, NULL);
 +	} zend_catch {
- 		obj->bailout_callback = true;
++		archive->bailout_callback = true;
 +	} zend_end_try();
  }

  /* {{{ register a progression callback: void callback(double state); */
@@@ -3139,20 -3183,9 +3196,20 @@@ PHP_METHOD(ZipArchive, registerProgress
  static int php_zip_cancel_callback(zip_t *arch, void *ptr)
  {
  	zval cb_retval;
- 	ze_zip_object *obj = ptr;
+ 	php_zip_archive *archive = ptr;

- 	if (UNEXPECTED(!EG(active) || obj->bailout_callback)) {
 -	zend_call_known_fcc(&archive->cancel_callback, &cb_retval, 0, NULL, NULL);
++	if (UNEXPECTED(!EG(active) || archive->bailout_callback)) {
 +		return 0;
 +	}
 +
 +	zend_try {
- 		zend_call_known_fcc(&obj->cancel_callback, &cb_retval, 0, NULL, NULL);
++		zend_call_known_fcc(&archive->cancel_callback, &cb_retval, 0, NULL, NULL);
 +	} zend_catch {
- 		obj->bailout_callback = true;
++		archive->bailout_callback = true;
 +		/* Cancel if a bailout occurs to allow cleanup to happen */
 +		return -1;
 +	} zend_end_try();
 +
  	if (Z_ISUNDEF(cb_retval)) {
  		/* Cancel if an exception has been thrown */
  		return -1;
diff --cc ext/zip/php_zip.h
index a10b1910f2a,e761364d0cc..a67d2042d7c
--- a/ext/zip/php_zip.h
+++ b/ext/zip/php_zip.h
@@@ -64,38 -65,55 +64,55 @@@ typedef struct _ze_zip_read_rsrc
  	zend_long zip_rsrc_handle;
  } zip_read_rsrc;

- /* Extends zend object */
- typedef struct _ze_zip_object {
+ /* Refcounted holder for the native archive state.
+  * Owned by a ZipArchive object and streams opened from it. */
+ typedef struct _php_zip_archive {
  	struct zip *za;
- 	HashTable *prop_handler;
- 	char *filename;
- 	size_t filename_len;
+ 	uint32_t refcount;
 -	/* libzip reads buffers until the archive is closed, can outlive the object. */
 -	char **buffers;
 -	int buffers_cnt;
 +	zend_string *out_str;
 +	bool from_string;
- 	zip_int64_t last_id;
- 	int err_zip;
- 	int err_sys;
 +	bool bailout_callback;
  #ifdef HAVE_PROGRESS_CALLBACK
  	zend_fcall_info_cache progress_callback;
  #endif
  #ifdef HAVE_CANCEL_CALLBACK
  	zend_fcall_info_cache cancel_callback;
  #endif
+ } php_zip_archive;
+
+ /* Extends zend object */
+ typedef struct _ze_zip_object {
+ 	/* NULL when there is no open archive, non-NULL otherwise.
+ 	 * Owns one ref to the struct. */
+ 	php_zip_archive *archive;
+ 	HashTable *prop_handler;
+ 	char *filename;
 -	int filename_len;
++	size_t filename_len;
+ 	zip_int64_t last_id;
+ 	int err_zip;
+ 	int err_sys;
  	zend_object zo;
  } ze_zip_object;

 -static inline ze_zip_object *php_zip_fetch_object(zend_object *obj) {
 -	return (ze_zip_object *)((char*)(obj) - XtOffsetOf(ze_zip_object, zo));
 -}
 +#define php_zip_fetch_object(obj) ZEND_CONTAINER_OF(obj, ze_zip_object, zo)

+ /* The archive an object currently has open, or NULL. */
+ static zend_always_inline struct zip *php_zip_object_za(const ze_zip_object *obj) {
+ 	return obj->archive ? obj->archive->za : NULL;
+ }
+
  #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(ze_zip_object *obj, struct zip_stat *sb, const char *mode, zip_flags_t flags STREAMS_DC);

+ void php_zip_archive_addref(php_zip_archive *archive);
 -void php_zip_archive_release(php_zip_archive *archive);
++bool php_zip_archive_release(php_zip_archive *archive);
+
  extern const php_stream_wrapper php_stream_zip_wrapper;

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

  #endif	/* PHP_ZIP_H */
diff --cc ext/zip/zip_stream.c
index 89c6a46e653,0356863ef7c..a6665630e35
--- a/ext/zip/zip_stream.c
+++ b/ext/zip/zip_stream.c
@@@ -82,6 -91,6 +82,7 @@@ static ssize_t php_zip_ops_write(php_st
  static int php_zip_ops_close(php_stream *stream, int close_handle)
  {
  	STREAM_DATA_FROM_STREAM();
++	bool bailout = false;
  	if (close_handle) {
  		if (self->zf) {
  			zip_fclose(self->zf);
@@@ -94,13 -103,13 +95,16 @@@
  		}
  	}

- 	/* 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;
+ 	/* the archive ref is tied to self, so release it regardless of close_handle */
+ 	if (self->archive) {
 -		php_zip_archive_release(self->archive);
++		bailout = php_zip_archive_release(self->archive);
+ 		self->archive = NULL;
  	}
  	efree(self);
  	stream->abstract = NULL;
++	if (bailout) {
++		zend_bailout();
++	}
  	return EOF;
  }
  /* }}} */