Skip to content

Commit 92fec6f

Browse files
committed
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.
1 parent 3a486dd commit 92fec6f

3 files changed

Lines changed: 94 additions & 1 deletion

File tree

‎NEWS‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@ PHP NEWS
1010
. Fixed Collator attribute and strength methods not rejecting an
1111
unconstructed Collator. (Ilia Alshanetsky)
1212

13+
- SOAP:
14+
. Fixed the SOAP client accepting a response body shorter than its
15+
Content-Length and reusing the connection. (Ilia Alshanetsky)
16+
1317
- Standard:
1418
. Improved performance of array_splice() when inserting without removing
1519
elements. (mehmetcansahin)

‎ext/soap/php_http.c‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1576,7 +1576,8 @@ static zend_string* get_http_body(php_stream *stream, bool close, zend_string *h
15761576
while (http_buf_size < header_length) {
15771577
ssize_t len_read = php_stream_read(stream, http_buf->val + http_buf_size, header_length - http_buf_size);
15781578
if (UNEXPECTED(len_read <= 0)) {
1579-
break;
1579+
zend_string_efree(http_buf);
1580+
return NULL;
15801581
}
15811582
http_buf_size += len_read;
15821583
}
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
--TEST--
2+
SOAP client rejects a truncated Content-Length response
3+
--EXTENSIONS--
4+
soap
5+
--SKIPIF--
6+
<?php
7+
if (!function_exists('proc_open')) {
8+
die('skip proc_open() is not available');
9+
}
10+
?>
11+
--FILE--
12+
<?php
13+
$serverCode = <<<'PHP'
14+
$server = stream_socket_server('tcp://127.0.0.1:0', $errno, $errstr);
15+
if (!$server) {
16+
fwrite(STDERR, "could not start server: $errstr\n");
17+
exit(1);
18+
}
19+
echo stream_socket_get_name($server, false), "\n";
20+
21+
$connection = stream_socket_accept($server, 10);
22+
if (!$connection) {
23+
exit(1);
24+
}
25+
26+
$request = '';
27+
while (!str_contains($request, "\r\n\r\n")) {
28+
$chunk = fread($connection, 1);
29+
if ($chunk === '') {
30+
exit(1);
31+
}
32+
$request .= $chunk;
33+
}
34+
preg_match('/Content-Length:\s*(\d+)/i', $request, $matches);
35+
$remaining = (int) $matches[1];
36+
while ($remaining > 0) {
37+
$chunk = fread($connection, $remaining);
38+
if ($chunk === '') {
39+
exit(1);
40+
}
41+
$remaining -= strlen($chunk);
42+
}
43+
44+
$body = '<?xml version="1.0"?><SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"><SOAP-ENV:Body><testResponse xmlns="urn:test"/></SOAP-ENV:Body></SOAP-ENV:Envelope>';
45+
fwrite($connection, "HTTP/1.1 200 OK\r\n"
46+
. "Content-Type: text/xml; charset=utf-8\r\n"
47+
. 'Content-Length: ' . (strlen($body) + 100) . "\r\n"
48+
. "Connection: keep-alive\r\n"
49+
. "\r\n"
50+
. $body);
51+
fclose($connection);
52+
fclose($server);
53+
PHP;
54+
55+
$process = proc_open([PHP_BINARY, '-n', '-r', $serverCode], [
56+
1 => ['pipe', 'w'],
57+
2 => ['pipe', 'w'],
58+
], $pipes);
59+
if (!is_resource($process)) {
60+
die('could not start server process');
61+
}
62+
63+
$address = fgets($pipes[1]);
64+
if ($address === false) {
65+
die(stream_get_contents($pipes[2]));
66+
}
67+
68+
try {
69+
$client = new SoapClient(null, [
70+
'location' => 'http://' . trim($address),
71+
'uri' => 'urn:test',
72+
'keep_alive' => true,
73+
]);
74+
75+
try {
76+
$client->test();
77+
echo "unexpected success\n";
78+
} catch (SoapFault $e) {
79+
echo $e->faultstring, "\n";
80+
}
81+
} finally {
82+
fclose($pipes[1]);
83+
fclose($pipes[2]);
84+
proc_close($process);
85+
}
86+
?>
87+
--EXPECT--
88+
Error Fetching http body, No Content-Length, connection closed or chunked data

0 commit comments

Comments
 (0)