Commit 021acbe980c for php
commit 021acbe980cecbe12bb22a54f9abe3bbc94c42cf
Author: Ilia Alshanetsky <ilia@ilia.ws>
Date: Thu Sep 24 14:12:48 2026 -0400
sapi/cli: Fix built-in server truncating responses after a partial write
php_cli_server_content_sender_send() moved on to the next queued chunk
after a short send() of the current one. When that send() succeeded, its
bytes went out ahead of the current chunk's remainder, and the full-send
branch pointed buffer.first past both, unlinking the partially sent chunk.
The client received later chunks spliced into the body, the connection was
closed short of Content-Length, and the unsent remainder leaked. Stop at
the first partial write and resume from that chunk on the next POLLOUT.
Closes GH-23969
diff --git a/NEWS b/NEWS
index 2f56de0d1b2..3e045040774 100644
--- a/NEWS
+++ b/NEWS
@@ -13,6 +13,8 @@ PHP NEWS
request for a static file). (jakubskopal)
. Fixed crash in the built-in server when a client is reset before being
accepted. (David Carlier)
+ . Fixed the built-in server truncating an error page and leaking its
+ unsent part after a partial socket write. (Ilia Alshanetsky)
- Core
. Fix GH-21999: GC inconsistency with lazy object, var_dump(), and object
diff --git a/sapi/cli/php_cli_server.c b/sapi/cli/php_cli_server.c
index 1b33ceaf70b..6b17f083c12 100644
--- a/sapi/cli/php_cli_server.c
+++ b/sapi/cli/php_cli_server.c
@@ -1067,7 +1067,8 @@ static void php_cli_server_content_sender_ctor(php_cli_server_content_sender *se
static int php_cli_server_content_sender_send(php_cli_server_content_sender *sender, php_socket_t fd, size_t *nbytes_sent_total) /* {{{ */
{
php_cli_server_chunk *chunk, *next;
- size_t _nbytes_sent_total = 0;
+
+ *nbytes_sent_total = 0;
for (chunk = sender->buffer.first; chunk; chunk = next) {
#ifdef PHP_WIN32
@@ -1085,7 +1086,6 @@ static int php_cli_server_content_sender_send(php_cli_server_content_sender *sen
nbytes_sent = send(fd, chunk->data.heap.p, chunk->data.heap.len, 0);
#endif
if (nbytes_sent < 0) {
- *nbytes_sent_total = _nbytes_sent_total;
return php_socket_errno();
#ifdef PHP_WIN32
} else if (nbytes_sent == chunk->data.heap.len) {
@@ -1101,8 +1101,10 @@ static int php_cli_server_content_sender_send(php_cli_server_content_sender *sen
} else {
chunk->data.heap.p += nbytes_sent;
chunk->data.heap.len -= nbytes_sent;
+ *nbytes_sent_total += nbytes_sent;
+ return 0;
}
- _nbytes_sent_total += nbytes_sent;
+ *nbytes_sent_total += nbytes_sent;
break;
case PHP_CLI_SERVER_CHUNK_IMMORTAL:
@@ -1112,7 +1114,6 @@ static int php_cli_server_content_sender_send(php_cli_server_content_sender *sen
nbytes_sent = send(fd, chunk->data.immortal.p, chunk->data.immortal.len, 0);
#endif
if (nbytes_sent < 0) {
- *nbytes_sent_total = _nbytes_sent_total;
return php_socket_errno();
#ifdef PHP_WIN32
} else if (nbytes_sent == chunk->data.immortal.len) {
@@ -1128,12 +1129,13 @@ static int php_cli_server_content_sender_send(php_cli_server_content_sender *sen
} else {
chunk->data.immortal.p += nbytes_sent;
chunk->data.immortal.len -= nbytes_sent;
+ *nbytes_sent_total += nbytes_sent;
+ return 0;
}
- _nbytes_sent_total += nbytes_sent;
+ *nbytes_sent_total += nbytes_sent;
break;
}
}
- *nbytes_sent_total = _nbytes_sent_total;
return 0;
} /* }}} */