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;
 } /* }}} */