[php-src] master: Merge branch 'PHP-8.6'
| From: | Jakub Zelenka | Date: | Sun, 04 Oct 2026 17:45:29 +0000 |
| Subject: | [php-src] master: Merge branch 'PHP-8.6' | ||
| Groups: | php.cvs | ||
| Request: | Send a blank email to php-cvs+get-141931@lists.php.net to get a copy of this message | ||
Author: Jakub Zelenka (bukka)
Date: 2026-10-04T19:45:20+02:00
Commit: https://github.com/php/php-src/commit/31aacfb651ad3398dd6610d612bf44122e1a320b
Raw diff: https://github.com/php/php-src/commit/31aacfb651ad3398dd6610d612bf44122e1a320b.diff
Merge branch 'PHP-8.6'
* PHP-8.6:
Fix StreamPollHandle changing unread_bytes of a TLS stream when added
Changed paths:
A ext/openssl/tests/stream_poll_handle_cast.phpt
M ext/openssl/xp_ssl.c
M ext/pgsql/pgsql.c
M ext/standard/io_poll.c
M main/php_streams.h
M main/streams/cast.c
M main/streams/plain_wrapper.c
M main/streams/userspace.c
M main/streams/xp_socket.c
Diff:
diff --git a/ext/openssl/tests/stream_poll_handle_cast.phpt
b/ext/openssl/tests/stream_poll_handle_cast.phpt
new file mode 100644
index 000000000000..5ef35f552bff
--- /dev/null
+++ b/ext/openssl/tests/stream_poll_handle_cast.phpt
@@ -0,0 +1,72 @@
+--TEST--
+A stream poll handle takes the descriptor of a TLS stream without touching its buffers
+--EXTENSIONS--
+openssl
+--SKIPIF--
+<?php
+if (!function_exists("proc_open")) die("skip no proc_open");
+?>
+--FILE--
+<?php
+$certFile = __DIR__ . DIRECTORY_SEPARATOR . 'stream_poll_handle_cast.pem.tmp';
+
+$serverCode = <<<'CODE'
+ $serverCtx = stream_context_create(['ssl' => ['local_cert' =>
'%s']]);
+ $sock = stream_socket_server("tls://127.0.0.1:0", $errno, $errstr,
+ STREAM_SERVER_BIND | STREAM_SERVER_LISTEN, $serverCtx);
+ phpt_notify_server_start($sock);
+
+ $link = stream_socket_accept($sock);
+ /* One record of 100 bytes */
+ fwrite($link, str_repeat("x", 100));
+ phpt_wait();
+ fclose($link);
+CODE;
+$serverCode = sprintf($serverCode, $certFile);
+
+$clientCode = <<<'CODE'
+ $clientCtx = stream_context_create(['ssl' => [
+ 'verify_peer' => false,
+ 'verify_peer_name' => false,
+ ]]);
+ $sock = stream_socket_client("tls://{{ ADDR }}", $errno, $errstr, 2,
STREAM_CLIENT_CONNECT, $clientCtx);
+
+ /* 10 of 100 bytes read, 90 stay inside OpenSSL */
+ stream_set_read_buffer($sock, 0);
+ var_dump(strlen(fread($sock, 10)));
+
+ /* Adding the handle must not move them into the stream buffer */
+ $ctx = new Io\Poll\Context();
+ $w = $ctx->add(new StreamPollHandle($sock), [Io\Poll\Event::Read]);
+ var_dump(stream_get_meta_data($sock)['unread_bytes']);
+
+ /* Not readiness of the socket */
+ var_dump(count($ctx->wait(Time\Duration::fromMilliseconds(100))));
+ var_dump(strlen(fread($sock, 90)));
+
+ /* The peer's close is */
+ phpt_notify();
+ $fired = $ctx->wait(Time\Duration::fromSeconds(2));
+ var_dump(count($fired), $fired[0] === $w);
+ var_dump(fread($sock, 10), feof($sock));
+CODE;
+
+include 'CertificateGenerator.inc';
+(new CertificateGenerator())->saveNewCertAsFileWithKey('stream_poll_handle_cast',
$certFile);
+
+include 'ServerClientTestCase.inc';
+ServerClientTestCase::getInstance()->run($clientCode, $serverCode);
+?>
+--CLEAN--
+<?php
+@unlink(__DIR__ . DIRECTORY_SEPARATOR . 'stream_poll_handle_cast.pem.tmp');
+?>
+--EXPECT--
+int(10)
+int(0)
+int(0)
+int(90)
+int(1)
+bool(true)
+string(0) ""
+bool(true)
diff --git a/ext/openssl/xp_ssl.c b/ext/openssl/xp_ssl.c
index cd439be4a42d..59be6503e00c 100644
--- a/ext/openssl/xp_ssl.c
+++ b/ext/openssl/xp_ssl.c
@@ -3909,6 +3909,13 @@ static int php_openssl_sockop_cast(php_stream *stream, int castas, void
**ret)
}
return SUCCESS;
+ case PHP_STREAM_AS_FD_FOR_POLL:
+ /* Descriptor only, OpenSSL pending bytes stay put */
+ if (ret) {
+ *(php_socket_t *)ret = sslsock->s.socket;
+ }
+ return SUCCESS;
+
case PHP_STREAM_AS_FD:
case PHP_STREAM_AS_SOCKETD:
if (sslsock->ssl_active) {
diff --git a/ext/pgsql/pgsql.c b/ext/pgsql/pgsql.c
index 4501ab0dfd08..90bb203e4a6d 100644
--- a/ext/pgsql/pgsql.c
+++ b/ext/pgsql/pgsql.c
@@ -4417,6 +4417,7 @@ static int php_pgsql_fd_cast(php_stream *stream, int cast_as, void **ret) /*
{{{
switch (cast_as) {
case PHP_STREAM_AS_FD_FOR_SELECT:
+ case PHP_STREAM_AS_FD_FOR_POLL:
case PHP_STREAM_AS_FD:
case PHP_STREAM_AS_SOCKETD: {
int fd_number = PQsocket(pgsql);
diff --git a/ext/standard/io_poll.c b/ext/standard/io_poll.c
index abb7543d8f30..a90a9292bb8d 100644
--- a/ext/standard/io_poll.c
+++ b/ext/standard/io_poll.c
@@ -205,7 +205,7 @@ static php_socket_t php_stream_poll_handle_get_fd(php_poll_handle_object *handle
return SOCK_ERR;
}
- if (php_stream_cast(stream, PHP_STREAM_AS_FD_FOR_SELECT | PHP_STREAM_CAST_INTERNAL,
+ if (php_stream_cast(stream, PHP_STREAM_AS_FD_FOR_POLL | PHP_STREAM_CAST_INTERNAL,
(void *) &fd, 1)
!= SUCCESS
|| fd == -1) {
diff --git a/main/php_streams.h b/main/php_streams.h
index 0111a6a78413..093685c1bd94 100644
--- a/main/php_streams.h
+++ b/main/php_streams.h
@@ -536,6 +536,8 @@ END_EXTERN_C()
#define PHP_STREAM_AS_FD_FOR_SELECT 3
/* cast as fd/socket for copy purposes */
#define PHP_STREAM_AS_FD_FOR_COPY 4
+/* cast as fd/socket for polling, buffers untouched */
+#define PHP_STREAM_AS_FD_FOR_POLL 5
/* try really, really hard to make sure the cast happens (avoid using this flag if possible) */
#define PHP_STREAM_CAST_TRY_HARD 0x80000000
diff --git a/main/streams/cast.c b/main/streams/cast.c
index c93b9d9747f8..269bb9e21784 100644
--- a/main/streams/cast.c
+++ b/main/streams/cast.c
@@ -194,7 +194,8 @@ PHPAPI zend_result php_stream_cast(php_stream *stream, int castas, void **ret, i
castas &= ~PHP_STREAM_CAST_MASK;
/* synchronize our buffer (if possible) */
- if (ret && castas != PHP_STREAM_AS_FD_FOR_SELECT && castas !=
PHP_STREAM_AS_FD_FOR_COPY) {
+ if (ret && castas != PHP_STREAM_AS_FD_FOR_SELECT && castas !=
PHP_STREAM_AS_FD_FOR_COPY
+ && castas != PHP_STREAM_AS_FD_FOR_POLL) {
php_stream_flush(stream);
if (stream->ops->seek && (stream->flags & PHP_STREAM_FLAG_NO_SEEK) == 0) {
zend_off_t dummy;
@@ -304,7 +305,8 @@ PHPAPI zend_result php_stream_cast(php_stream *stream, int castas, void **ret, i
}
}
- if (php_stream_is_filtered(stream) && castas != PHP_STREAM_AS_FD_FOR_SELECT) {
+ if (php_stream_is_filtered(stream) && castas != PHP_STREAM_AS_FD_FOR_SELECT
+ && castas != PHP_STREAM_AS_FD_FOR_POLL) {
if (show_err) {
php_stream_warn(stream, CastNotSupported,
"Cannot cast a filtered stream on this system");
@@ -316,11 +318,13 @@ PHPAPI zend_result php_stream_cast(php_stream *stream, int castas, void **ret,
i
if (show_err) {
/* these names depend on the values of the PHP_STREAM_AS_XXX defines in php_streams.h */
- static const char *cast_names[4] = {
+ static const char *cast_names[6] = {
"STDIO FILE*",
"File Descriptor",
"Socket Descriptor",
- "select()able descriptor"
+ "select()able descriptor",
+ "copyable descriptor",
+ "pollable descriptor"
};
php_stream_warn(stream, CastNotSupported,
diff --git a/main/streams/plain_wrapper.c b/main/streams/plain_wrapper.c
index eb9b81b6e2c5..24427b41b690 100644
--- a/main/streams/plain_wrapper.c
+++ b/main/streams/plain_wrapper.c
@@ -689,6 +689,7 @@ static int php_stdiop_cast(php_stream *stream, int castas, void **ret)
return SUCCESS;
case PHP_STREAM_AS_FD_FOR_SELECT:
+ case PHP_STREAM_AS_FD_FOR_POLL:
PHP_STDIOP_GET_FD(fd, data);
if (SOCK_ERR == fd) {
return FAILURE;
diff --git a/main/streams/userspace.c b/main/streams/userspace.c
index b6177ade8b44..8c4b43f6ce83 100644
--- a/main/streams/userspace.c
+++ b/main/streams/userspace.c
@@ -1434,6 +1434,8 @@ static int php_userstreamop_cast(php_stream *stream, int castas, void
**retptr)
switch(castas) {
case PHP_STREAM_AS_FD_FOR_SELECT:
+ case PHP_STREAM_AS_FD_FOR_POLL:
+ /* Userland only knows STREAM_CAST_FOR_SELECT */
ZVAL_LONG(&args[0], PHP_STREAM_AS_FD_FOR_SELECT);
break;
default:
diff --git a/main/streams/xp_socket.c b/main/streams/xp_socket.c
index 4d022c485713..6c408dab1127 100644
--- a/main/streams/xp_socket.c
+++ b/main/streams/xp_socket.c
@@ -518,6 +518,7 @@ static int php_sockop_cast(php_stream *stream, int castas, void **ret)
}
return SUCCESS;
case PHP_STREAM_AS_FD_FOR_SELECT:
+ case PHP_STREAM_AS_FD_FOR_POLL:
case PHP_STREAM_AS_FD:
case PHP_STREAM_AS_SOCKETD:
if (ret)