Skip to content

Commit 72a2cbc

Browse files
committed
Fix bug #73182: PHP SOAPClient does not support stream context HTTP headers in array form
This code is modelled after how `http_fopen_wrapper.c` does things, which apparently is just looping over the array and handling each string the same way as if we passed a header string directly. Also fixes a potential crash in `php_sdl.c` but without adding support for header arrays there (yet) because the code is untested. Closes GH-15817.
1 parent 5cf045d commit 72a2cbc

File tree

4 files changed

+140
-58
lines changed

4 files changed

+140
-58
lines changed

NEWS

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,10 @@ PHP NEWS
3636
. Fixed bug GH-15658 (Segmentation fault in Zend/zend_vm_execute.h).
3737
(nielsdos)
3838

39+
- SOAP:
40+
. Fixed bug #73182 (PHP SOAPClient does not support stream context HTTP
41+
headers in array form). (nielsdos)
42+
3943
- Standard:
4044
. Fixed bug GH-15552 (Signed integer overflow in ext/standard/scanf.c). (cmb)
4145

ext/soap/php_http.c

Lines changed: 71 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,68 @@ int basic_authentication(zval* this_ptr, smart_str* soap_headers)
8181
return 0;
8282
}
8383

84+
static void http_context_add_header(const char *s,
85+
bool has_authorization,
86+
bool has_proxy_authorization,
87+
bool has_cookies,
88+
smart_str *soap_headers)
89+
{
90+
const char *p;
91+
int name_len;
92+
93+
while (*s) {
94+
/* skip leading newlines and spaces */
95+
while (*s == ' ' || *s == '\t' || *s == '\r' || *s == '\n') {
96+
s++;
97+
}
98+
/* extract header name */
99+
p = s;
100+
name_len = -1;
101+
while (*p) {
102+
if (*p == ':') {
103+
if (name_len < 0) name_len = p - s;
104+
break;
105+
} else if (*p == ' ' || *p == '\t') {
106+
if (name_len < 0) name_len = p - s;
107+
} else if (*p == '\r' || *p == '\n') {
108+
break;
109+
}
110+
p++;
111+
}
112+
if (*p == ':') {
113+
/* extract header value */
114+
while (*p && *p != '\r' && *p != '\n') {
115+
p++;
116+
}
117+
/* skip some predefined headers */
118+
if ((name_len != sizeof("host")-1 ||
119+
strncasecmp(s, "host", sizeof("host")-1) != 0) &&
120+
(name_len != sizeof("connection")-1 ||
121+
strncasecmp(s, "connection", sizeof("connection")-1) != 0) &&
122+
(name_len != sizeof("user-agent")-1 ||
123+
strncasecmp(s, "user-agent", sizeof("user-agent")-1) != 0) &&
124+
(name_len != sizeof("content-length")-1 ||
125+
strncasecmp(s, "content-length", sizeof("content-length")-1) != 0) &&
126+
(name_len != sizeof("content-type")-1 ||
127+
strncasecmp(s, "content-type", sizeof("content-type")-1) != 0) &&
128+
(!has_cookies ||
129+
name_len != sizeof("cookie")-1 ||
130+
strncasecmp(s, "cookie", sizeof("cookie")-1) != 0) &&
131+
(!has_authorization ||
132+
name_len != sizeof("authorization")-1 ||
133+
strncasecmp(s, "authorization", sizeof("authorization")-1) != 0) &&
134+
(!has_proxy_authorization ||
135+
name_len != sizeof("proxy-authorization")-1 ||
136+
strncasecmp(s, "proxy-authorization", sizeof("proxy-authorization")-1) != 0)) {
137+
/* add header */
138+
smart_str_appendl(soap_headers, s, p-s);
139+
smart_str_append_const(soap_headers, "\r\n");
140+
}
141+
}
142+
s = (*p) ? (p + 1) : p;
143+
}
144+
}
145+
84146
/* Additional HTTP headers */
85147
void http_context_headers(php_stream_context* context,
86148
bool has_authorization,
@@ -89,64 +151,16 @@ void http_context_headers(php_stream_context* context,
89151
smart_str* soap_headers)
90152
{
91153
zval *tmp;
92-
93-
if (context &&
94-
(tmp = php_stream_context_get_option(context, "http", "header")) != NULL &&
95-
Z_TYPE_P(tmp) == IS_STRING && Z_STRLEN_P(tmp)) {
96-
char *s = Z_STRVAL_P(tmp);
97-
char *p;
98-
int name_len;
99-
100-
while (*s) {
101-
/* skip leading newlines and spaces */
102-
while (*s == ' ' || *s == '\t' || *s == '\r' || *s == '\n') {
103-
s++;
104-
}
105-
/* extract header name */
106-
p = s;
107-
name_len = -1;
108-
while (*p) {
109-
if (*p == ':') {
110-
if (name_len < 0) name_len = p - s;
111-
break;
112-
} else if (*p == ' ' || *p == '\t') {
113-
if (name_len < 0) name_len = p - s;
114-
} else if (*p == '\r' || *p == '\n') {
115-
break;
154+
if (context && (tmp = php_stream_context_get_option(context, "http", "header")) != NULL) {
155+
if (Z_TYPE_P(tmp) == IS_STRING && Z_STRLEN_P(tmp)) {
156+
http_context_add_header(Z_STRVAL_P(tmp), has_authorization, has_proxy_authorization, has_cookies, soap_headers);
157+
} else if (Z_TYPE_P(tmp) == IS_ARRAY) {
158+
zval *value;
159+
ZEND_HASH_FOREACH_VAL(Z_ARR_P(tmp), value) {
160+
if (Z_TYPE_P(value) == IS_STRING && Z_STRLEN_P(value)) {
161+
http_context_add_header(Z_STRVAL_P(value), has_authorization, has_proxy_authorization, has_cookies, soap_headers);
116162
}
117-
p++;
118-
}
119-
if (*p == ':') {
120-
/* extract header value */
121-
while (*p && *p != '\r' && *p != '\n') {
122-
p++;
123-
}
124-
/* skip some predefined headers */
125-
if ((name_len != sizeof("host")-1 ||
126-
strncasecmp(s, "host", sizeof("host")-1) != 0) &&
127-
(name_len != sizeof("connection")-1 ||
128-
strncasecmp(s, "connection", sizeof("connection")-1) != 0) &&
129-
(name_len != sizeof("user-agent")-1 ||
130-
strncasecmp(s, "user-agent", sizeof("user-agent")-1) != 0) &&
131-
(name_len != sizeof("content-length")-1 ||
132-
strncasecmp(s, "content-length", sizeof("content-length")-1) != 0) &&
133-
(name_len != sizeof("content-type")-1 ||
134-
strncasecmp(s, "content-type", sizeof("content-type")-1) != 0) &&
135-
(!has_cookies ||
136-
name_len != sizeof("cookie")-1 ||
137-
strncasecmp(s, "cookie", sizeof("cookie")-1) != 0) &&
138-
(!has_authorization ||
139-
name_len != sizeof("authorization")-1 ||
140-
strncasecmp(s, "authorization", sizeof("authorization")-1) != 0) &&
141-
(!has_proxy_authorization ||
142-
name_len != sizeof("proxy-authorization")-1 ||
143-
strncasecmp(s, "proxy-authorization", sizeof("proxy-authorization")-1) != 0)) {
144-
/* add header */
145-
smart_str_appendl(soap_headers, s, p-s);
146-
smart_str_append_const(soap_headers, "\r\n");
147-
}
148-
}
149-
s = (*p) ? (p + 1) : p;
163+
} ZEND_HASH_FOREACH_END();
150164
}
151165
}
152166
}

ext/soap/php_sdl.c

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -283,7 +283,9 @@ void sdl_set_uri_credentials(sdlCtx *ctx, char *uri)
283283
ctx->context = php_stream_context_from_zval(context_ptr, 1);
284284

285285
if (ctx->context &&
286-
(header = php_stream_context_get_option(ctx->context, "http", "header")) != NULL) {
286+
(header = php_stream_context_get_option(ctx->context, "http", "header")) != NULL &&
287+
Z_TYPE_P(header) == IS_STRING) {
288+
/* TODO: should support header as an array, but this code path is untested */
287289
s = strstr(Z_STRVAL_P(header), "Authorization: Basic");
288290
if (s && (s == Z_STRVAL_P(header) || *(s-1) == '\n' || *(s-1) == '\r')) {
289291
char *rest = strstr(s, "\r\n");

ext/soap/tests/bugs/bug73182.phpt

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
--TEST--
2+
Bug #73182 (PHP SOAPClient does not support stream context HTTP headers in array form)
3+
--EXTENSIONS--
4+
soap
5+
--SKIPIF--
6+
<?php
7+
if (!file_exists(__DIR__ . "/../../../../sapi/cli/tests/php_cli_server.inc")) {
8+
echo "skip sapi/cli/tests/php_cli_server.inc required but not found";
9+
}
10+
?>
11+
--FILE--
12+
<?php
13+
14+
include __DIR__ . "/../../../../sapi/cli/tests/php_cli_server.inc";
15+
16+
$args = ["-d", "extension_dir=" . ini_get("extension_dir"), "-d", "extension=" . (substr(PHP_OS, 0, 3) == "WIN" ? "php_" : "") . "soap." . PHP_SHLIB_SUFFIX];
17+
if (php_ini_loaded_file()) {
18+
// Necessary such that it works from a development directory in which case extension_dir might not be the real extension dir
19+
$args[] = "-c";
20+
$args[] = php_ini_loaded_file();
21+
}
22+
$code = <<<'PHP'
23+
/* Receive */
24+
$content = trim(file_get_contents("php://input")) . PHP_EOL;
25+
PHP;
26+
27+
php_cli_server_start($code, null, $args);
28+
29+
$client = new soapclient(NULL, [
30+
'location' => 'http://' . PHP_CLI_SERVER_ADDRESS,
31+
'uri' => 'misc-uri',
32+
'trace' => true,
33+
'stream_context' => stream_context_create([
34+
'http' => [
35+
'header' => [
36+
// These 2 must be ignored because the soap http client sets them up
37+
'Connection: close',
38+
'User-Agent: bar',
39+
// The following 2 must be included
40+
'X-custom: foo',
41+
'Content-Description: Foo',
42+
' X-custom2: trim me ',
43+
],
44+
],
45+
]),
46+
]);
47+
48+
$client->__soapCall("foo", []);
49+
echo $client->__getLastRequestHeaders();
50+
51+
?>
52+
--EXPECTF--
53+
POST / HTTP/1.1
54+
Host: localhost:%d
55+
Connection: Keep-Alive
56+
User-Agent: PHP-SOAP/%s
57+
Content-Type: text/xml; charset=utf-8
58+
SOAPAction: "misc-uri#foo"
59+
Content-Length: %d
60+
X-custom: foo
61+
Content-Description: Foo
62+
X-custom2: trim me

0 commit comments

Comments
 (0)