Skip to content
4 changes: 4 additions & 0 deletions NEWS
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
9 changes: 5 additions & 4 deletions ext/com_dotnet/com_com.c
Original file line number Diff line number Diff line change
Expand Up @@ -670,7 +670,8 @@ PHP_FUNCTION(com_create_guid)
/* {{{ Connect events from a COM object to a PHP object */
PHP_FUNCTION(com_event_sink)
{
zval *object, *sinkobject;
zend_object *object;
zend_object *sinkobject;
zend_string *sink_str = NULL;
HashTable *sink_ht = NULL;
zend_string *type_lib_name = NULL;
Expand All @@ -679,16 +680,16 @@ PHP_FUNCTION(com_event_sink)
ITypeInfo *typeinfo = NULL;

ZEND_PARSE_PARAMETERS_START(2, 3)
Z_PARAM_OBJECT_OF_CLASS(object, php_com_variant_class_entry)
Z_PARAM_OBJECT(sinkobject)
Z_PARAM_OBJ_OF_CLASS(object, php_com_variant_class_entry)
Z_PARAM_OBJ(sinkobject)
Z_PARAM_OPTIONAL
Z_PARAM_ARRAY_HT_OR_STR_OR_NULL(sink_ht, sink_str)
ZEND_PARSE_PARAMETERS_END();

RETVAL_FALSE;

php_com_initialize();
obj = CDNO_FETCH(object);
obj = (php_com_dotnet_object*)object;

if (sink_ht) {
/* 0 => typelibname, 1 => dispname */
Expand Down
4 changes: 2 additions & 2 deletions ext/com_dotnet/com_variant.c
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ static void php_com_variant_from_zval_ex(VARIANT *v, zval *z, int codepage, VART
break;

case IS_OBJECT:
if (php_com_is_valid_object(z)) {
if (php_com_is_valid_object(Z_OBJ_P(z))) {
obj = CDNO_FETCH(z);
if (V_VT(&obj->v) == VT_DISPATCH) {
/* pass the underlying object */
Expand All @@ -132,7 +132,7 @@ static void php_com_variant_from_zval_ex(VARIANT *v, zval *z, int codepage, VART
} else {
/* export the PHP object using our COM wrapper */
V_VT(v) = VT_DISPATCH;
V_DISPATCH(v) = php_com_wrapper_export(z);
V_DISPATCH(v) = php_com_wrapper_export(Z_OBJ_P(z));
}
break;

Expand Down
16 changes: 6 additions & 10 deletions ext/com_dotnet/com_wrapper.c
Original file line number Diff line number Diff line change
Expand Up @@ -499,11 +499,11 @@ static void generate_dispids(php_dispatchex *disp)
}
}

static php_dispatchex *disp_constructor(zval *object)
static php_dispatchex *disp_constructor(zend_object *object)
{
php_dispatchex *disp = (php_dispatchex*)CoTaskMemAlloc(sizeof(php_dispatchex));

trace("constructing a COM wrapper for PHP object %p (%s)\n", object, ZSTR_VAL(Z_OBJCE_P(object)->name));
trace("constructing a COM wrapper for PHP object %p (%s)\n", object, ZSTR_VAL(object->ce->name));

if (disp == NULL)
return NULL;
Expand All @@ -516,7 +516,7 @@ static php_dispatchex *disp_constructor(zval *object)


if (object) {
ZVAL_COPY(&disp->object, object);
ZVAL_OBJ_COPY(&disp->object, object);
} else {
ZVAL_UNDEF(&disp->object);
}
Expand All @@ -536,7 +536,7 @@ static void disp_destructor(php_dispatchex *disp)
CoTaskMemFree(disp);
}

PHP_COM_DOTNET_API IDispatch *php_com_wrapper_export_as_sink(zval *val, GUID *sinkid,
PHP_COM_DOTNET_API IDispatch *php_com_wrapper_export_as_sink(zend_object *val, GUID *sinkid,
HashTable *id_to_name)
{
php_dispatchex *disp = disp_constructor(val);
Expand Down Expand Up @@ -572,17 +572,13 @@ PHP_COM_DOTNET_API IDispatch *php_com_wrapper_export_as_sink(zval *val, GUID *si
return (IDispatch*)disp;
}

PHP_COM_DOTNET_API IDispatch *php_com_wrapper_export(zval *val)
PHP_COM_DOTNET_API IDispatch *php_com_wrapper_export(zend_object *val)
{
php_dispatchex *disp = NULL;

if (Z_TYPE_P(val) != IS_OBJECT) {
return NULL;
}

if (php_com_is_valid_object(val)) {
/* pass back its IDispatch directly */
php_com_dotnet_object *obj = CDNO_FETCH(val);
php_com_dotnet_object *obj = (php_com_dotnet_object*)val;

if (obj == NULL)
return NULL;
Expand Down
10 changes: 5 additions & 5 deletions ext/com_dotnet/php_com_dotnet_internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -44,17 +44,17 @@ typedef struct _php_com_dotnet_object {
HashTable *id_of_name_cache;
} php_com_dotnet_object;

static inline bool php_com_is_valid_object(zval *zv)
static inline bool php_com_is_valid_object(zend_object *obj)
{
zend_class_entry *ce = Z_OBJCE_P(zv);
const zend_class_entry *ce = obj->ce;
return zend_string_equals_literal(ce->name, "com") ||
zend_string_equals_literal(ce->name, "dotnet") ||
zend_string_equals_literal(ce->name, "variant");
}

#define CDNO_FETCH(zv) (php_com_dotnet_object*)Z_OBJ_P(zv)
#define CDNO_FETCH_VERIFY(obj, zv) do { \
if (!php_com_is_valid_object(zv)) { \
if (!php_com_is_valid_object(Z_OBJ_P(zv))) { \
php_com_throw_exception(E_UNEXPECTED, "expected a variant object"); \
return; \
} \
Expand Down Expand Up @@ -99,8 +99,8 @@ zend_result php_com_do_invoke_byref(php_com_dotnet_object *obj, zend_internal_fu
WORD flags, VARIANT *v, int nargs, zval *args);

/* com_wrapper.c */
PHP_COM_DOTNET_API IDispatch *php_com_wrapper_export_as_sink(zval *val, GUID *sinkid, HashTable *id_to_name);
PHP_COM_DOTNET_API IDispatch *php_com_wrapper_export(zval *val);
PHP_COM_DOTNET_API IDispatch *php_com_wrapper_export_as_sink(zend_object *val, GUID *sinkid, HashTable *id_to_name);
PHP_COM_DOTNET_API IDispatch *php_com_wrapper_export(zend_object *val);

/* com_persist.c */
void php_com_persist_minit(INIT_FUNC_ARGS);
Expand Down
4 changes: 2 additions & 2 deletions ext/curl/tests/curl_headerfunction_throws_abort.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ curl_setopt($ch, CURLOPT_HEADERFUNCTION,
try {
curl_exec($ch);
} catch (Exception $e) {
echo $e->getMessage(), "\n";
echo $e::class, ': ', $e->getMessage(), "\n";
}

var_dump(curl_errno($ch) === CURLE_WRITE_ERROR);
Expand All @@ -39,7 +39,7 @@ var_dump(curl_errno($ch) === CURLE_OK);
?>
--EXPECTF--
Test: header function throws exception
header exception
Exception: header exception
bool(true)
Test: header function is null
bool(true)
4 changes: 2 additions & 2 deletions ext/curl/tests/curl_prereqfunction_throws_abort.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,12 @@ curl_setopt($ch, CURLOPT_PREREQFUNCTION,
try {
curl_exec($ch);
} catch (Exception $e) {
echo $e->getMessage(), "\n";
echo $e::class, ': ', $e->getMessage(), "\n";
}

var_dump(curl_errno($ch) === CURLE_ABORTED_BY_CALLBACK);

?>
--EXPECTF--
prereq exception
Exception: prereq exception
bool(true)
4 changes: 2 additions & 2 deletions ext/curl/tests/curl_progressfunction_throws_abort.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ curl_setopt($ch, CURLOPT_PROGRESSFUNCTION,
try {
curl_exec($ch);
} catch (Exception $e) {
echo $e->getMessage(), "\n";
echo $e::class, ': ', $e->getMessage(), "\n";
}

var_dump(curl_errno($ch) === CURLE_ABORTED_BY_CALLBACK);
Expand All @@ -40,7 +40,7 @@ var_dump(curl_errno($ch) === CURLE_OK);
?>
--EXPECTF--
Test: progress function throws exception
info exception
Exception: info exception
bool(true)
Test: progress function is null
bool(true)
4 changes: 2 additions & 2 deletions ext/curl/tests/curl_read_function_error_on_int.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,10 @@ curl_setopt($ch, CURLOPT_READFUNCTION, "custom_readfunction" );
try {
curl_exec($ch);
} catch (ValueError $e) {
echo $e->getMessage() . "\n";
echo $e::class, ': ', $e->getMessage(), "\n";
}
var_dump(curl_error($ch));
?>
--EXPECT--
The CURLOPT_READFUNCTION callback must return a string or CURL_READFUNC_ABORT or CURL_READFUNC_PAUSE
ValueError: The CURLOPT_READFUNCTION callback must return a string or CURL_READFUNC_ABORT or CURL_READFUNC_PAUSE
string(29) "operation aborted by callback"
4 changes: 2 additions & 2 deletions ext/curl/tests/curl_readfunction_throws_abort.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ curl_setopt($ch, CURLOPT_READFUNCTION,
try {
curl_exec($ch);
} catch (Exception $e) {
echo $e->getMessage(), "\n";
echo $e::class, ': ', $e->getMessage(), "\n";
}

var_dump(curl_errno($ch) === CURLE_ABORTED_BY_CALLBACK);
Expand All @@ -42,7 +42,7 @@ var_dump(curl_errno($ch) === CURLE_OK);
?>
--EXPECTF--
Test: read function throws exception
read exception
Exception: read exception
bool(true)
Test: read function is null
bool(true)
4 changes: 2 additions & 2 deletions ext/curl/tests/curl_writefunction_throws_abort.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ curl_setopt($ch, CURLOPT_WRITEFUNCTION,
try {
curl_exec($ch);
} catch (Exception $e) {
echo $e->getMessage(), "\n";
echo $e::class, ': ', $e->getMessage(), "\n";
}

var_dump(curl_errno($ch) === CURLE_WRITE_ERROR);
Expand All @@ -38,7 +38,7 @@ var_dump(curl_errno($ch) === CURLE_OK);
?>
--EXPECTF--
Test: write function throws exception
write exception
Exception: write exception
bool(true)
Test: write function is null
Hello World!
Expand Down
4 changes: 2 additions & 2 deletions ext/curl/tests/curl_xferinfofunction_throws_abort.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ curl_setopt($ch, CURLOPT_XFERINFOFUNCTION,
try {
curl_exec($ch);
} catch (Exception $e) {
echo $e->getMessage(), "\n";
echo $e::class, ': ', $e->getMessage(), "\n";
}

var_dump(curl_errno($ch) === CURLE_ABORTED_BY_CALLBACK);
Expand All @@ -40,7 +40,7 @@ var_dump(curl_errno($ch) === CURLE_OK);
?>
--EXPECTF--
Test: xfer info function throws exception
info exception
Exception: info exception
bool(true)
Test: xfer info function is null
bool(true)
2 changes: 1 addition & 1 deletion ext/zip/php_zip.c
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
2 changes: 1 addition & 1 deletion ext/zip/php_zip.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
80 changes: 80 additions & 0 deletions ext/zip/tests/gh17787.phpt
Original file line number Diff line number Diff line change
@@ -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"
Loading