[php-src] master: Merge branch 'PHP-8.6'

From: 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)

« previous php.cvs (#141931) next »