From 92fec6f244c19f8804b9d2d8a6c6404928ea3980 Mon Sep 17 00:00:00 2001 From: Ilia Alshanetsky Date: Sat, 26 Sep 2026 18:15:06 -0400 Subject: [PATCH] ext/soap: Reject a response body shorter than Content-Length A short read of a Content-Length body returned the bytes received and left the keep-alive socket cached. Return failure instead, which closes that socket and raises the existing HTTP fault. --- NEWS | 4 + ext/soap/php_http.c | 3 +- .../tests/http_content_length_truncated.phpt | 88 +++++++++++++++++++ 3 files changed, 94 insertions(+), 1 deletion(-) create mode 100644 ext/soap/tests/http_content_length_truncated.phpt diff --git a/NEWS b/NEWS index b0f120744463..413c85c2aed6 100644 --- a/NEWS +++ b/NEWS @@ -10,6 +10,10 @@ PHP NEWS . Fixed Collator attribute and strength methods not rejecting an unconstructed Collator. (Ilia Alshanetsky) +- SOAP: + . Fixed the SOAP client accepting a response body shorter than its + Content-Length and reusing the connection. (Ilia Alshanetsky) + - Standard: . Improved performance of array_splice() when inserting without removing elements. (mehmetcansahin) diff --git a/ext/soap/php_http.c b/ext/soap/php_http.c index e7325ec65b53..f20eccf3d04d 100644 --- a/ext/soap/php_http.c +++ b/ext/soap/php_http.c @@ -1576,7 +1576,8 @@ static zend_string* get_http_body(php_stream *stream, bool close, zend_string *h while (http_buf_size < header_length) { ssize_t len_read = php_stream_read(stream, http_buf->val + http_buf_size, header_length - http_buf_size); if (UNEXPECTED(len_read <= 0)) { - break; + zend_string_efree(http_buf); + return NULL; } http_buf_size += len_read; } diff --git a/ext/soap/tests/http_content_length_truncated.phpt b/ext/soap/tests/http_content_length_truncated.phpt new file mode 100644 index 000000000000..dcd63f48c60d --- /dev/null +++ b/ext/soap/tests/http_content_length_truncated.phpt @@ -0,0 +1,88 @@ +--TEST-- +SOAP client rejects a truncated Content-Length response +--EXTENSIONS-- +soap +--SKIPIF-- + +--FILE-- + 0) { + $chunk = fread($connection, $remaining); + if ($chunk === '') { + exit(1); + } + $remaining -= strlen($chunk); +} + +$body = ''; +fwrite($connection, "HTTP/1.1 200 OK\r\n" + . "Content-Type: text/xml; charset=utf-8\r\n" + . 'Content-Length: ' . (strlen($body) + 100) . "\r\n" + . "Connection: keep-alive\r\n" + . "\r\n" + . $body); +fclose($connection); +fclose($server); +PHP; + +$process = proc_open([PHP_BINARY, '-n', '-r', $serverCode], [ + 1 => ['pipe', 'w'], + 2 => ['pipe', 'w'], +], $pipes); +if (!is_resource($process)) { + die('could not start server process'); +} + +$address = fgets($pipes[1]); +if ($address === false) { + die(stream_get_contents($pipes[2])); +} + +try { + $client = new SoapClient(null, [ + 'location' => 'http://' . trim($address), + 'uri' => 'urn:test', + 'keep_alive' => true, + ]); + + try { + $client->test(); + echo "unexpected success\n"; + } catch (SoapFault $e) { + echo $e->faultstring, "\n"; + } +} finally { + fclose($pipes[1]); + fclose($pipes[2]); + proc_close($process); +} +?> +--EXPECT-- +Error Fetching http body, No Content-Length, connection closed or chunked data