From 4518b3077d80ea9be7726983f4cdf766312bb504 Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Mon, 9 Sep 2024 22:32:30 +0200 Subject: [PATCH] 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. --- ext/soap/php_http.c | 128 +++++++++++++++++------------- ext/soap/php_sdl.c | 4 +- ext/soap/tests/bugs/bug73182.phpt | 62 +++++++++++++++ 3 files changed, 136 insertions(+), 58 deletions(-) create mode 100644 ext/soap/tests/bugs/bug73182.phpt diff --git a/ext/soap/php_http.c b/ext/soap/php_http.c index 2aefdba0fb84..0c71c4f96316 100644 --- a/ext/soap/php_http.c +++ b/ext/soap/php_http.c @@ -81,6 +81,68 @@ int basic_authentication(zval* this_ptr, smart_str* soap_headers) return 0; } +static void http_context_add_header(const char *s, + bool has_authorization, + bool has_proxy_authorization, + bool has_cookies, + smart_str *soap_headers) +{ + const char *p; + int name_len; + + while (*s) { + /* skip leading newlines and spaces */ + while (*s == ' ' || *s == '\t' || *s == '\r' || *s == '\n') { + s++; + } + /* extract header name */ + p = s; + name_len = -1; + while (*p) { + if (*p == ':') { + if (name_len < 0) name_len = p - s; + break; + } else if (*p == ' ' || *p == '\t') { + if (name_len < 0) name_len = p - s; + } else if (*p == '\r' || *p == '\n') { + break; + } + p++; + } + if (*p == ':') { + /* extract header value */ + while (*p && *p != '\r' && *p != '\n') { + p++; + } + /* skip some predefined headers */ + if ((name_len != sizeof("host")-1 || + strncasecmp(s, "host", sizeof("host")-1) != 0) && + (name_len != sizeof("connection")-1 || + strncasecmp(s, "connection", sizeof("connection")-1) != 0) && + (name_len != sizeof("user-agent")-1 || + strncasecmp(s, "user-agent", sizeof("user-agent")-1) != 0) && + (name_len != sizeof("content-length")-1 || + strncasecmp(s, "content-length", sizeof("content-length")-1) != 0) && + (name_len != sizeof("content-type")-1 || + strncasecmp(s, "content-type", sizeof("content-type")-1) != 0) && + (!has_cookies || + name_len != sizeof("cookie")-1 || + strncasecmp(s, "cookie", sizeof("cookie")-1) != 0) && + (!has_authorization || + name_len != sizeof("authorization")-1 || + strncasecmp(s, "authorization", sizeof("authorization")-1) != 0) && + (!has_proxy_authorization || + name_len != sizeof("proxy-authorization")-1 || + strncasecmp(s, "proxy-authorization", sizeof("proxy-authorization")-1) != 0)) { + /* add header */ + smart_str_appendl(soap_headers, s, p-s); + smart_str_append_const(soap_headers, "\r\n"); + } + } + s = (*p) ? (p + 1) : p; + } +} + /* Additional HTTP headers */ void http_context_headers(php_stream_context* context, bool has_authorization, @@ -89,64 +151,16 @@ void http_context_headers(php_stream_context* context, smart_str* soap_headers) { zval *tmp; - - if (context && - (tmp = php_stream_context_get_option(context, "http", "header")) != NULL && - Z_TYPE_P(tmp) == IS_STRING && Z_STRLEN_P(tmp)) { - char *s = Z_STRVAL_P(tmp); - char *p; - int name_len; - - while (*s) { - /* skip leading newlines and spaces */ - while (*s == ' ' || *s == '\t' || *s == '\r' || *s == '\n') { - s++; - } - /* extract header name */ - p = s; - name_len = -1; - while (*p) { - if (*p == ':') { - if (name_len < 0) name_len = p - s; - break; - } else if (*p == ' ' || *p == '\t') { - if (name_len < 0) name_len = p - s; - } else if (*p == '\r' || *p == '\n') { - break; + if (context && (tmp = php_stream_context_get_option(context, "http", "header")) != NULL) { + if (Z_TYPE_P(tmp) == IS_STRING && Z_STRLEN_P(tmp)) { + http_context_add_header(Z_STRVAL_P(tmp), has_authorization, has_proxy_authorization, has_cookies, soap_headers); + } else if (Z_TYPE_P(tmp) == IS_ARRAY) { + zval *value; + ZEND_HASH_FOREACH_VAL(Z_ARR_P(tmp), value) { + if (Z_TYPE_P(value) == IS_STRING && Z_STRLEN_P(value)) { + http_context_add_header(Z_STRVAL_P(value), has_authorization, has_proxy_authorization, has_cookies, soap_headers); } - p++; - } - if (*p == ':') { - /* extract header value */ - while (*p && *p != '\r' && *p != '\n') { - p++; - } - /* skip some predefined headers */ - if ((name_len != sizeof("host")-1 || - strncasecmp(s, "host", sizeof("host")-1) != 0) && - (name_len != sizeof("connection")-1 || - strncasecmp(s, "connection", sizeof("connection")-1) != 0) && - (name_len != sizeof("user-agent")-1 || - strncasecmp(s, "user-agent", sizeof("user-agent")-1) != 0) && - (name_len != sizeof("content-length")-1 || - strncasecmp(s, "content-length", sizeof("content-length")-1) != 0) && - (name_len != sizeof("content-type")-1 || - strncasecmp(s, "content-type", sizeof("content-type")-1) != 0) && - (!has_cookies || - name_len != sizeof("cookie")-1 || - strncasecmp(s, "cookie", sizeof("cookie")-1) != 0) && - (!has_authorization || - name_len != sizeof("authorization")-1 || - strncasecmp(s, "authorization", sizeof("authorization")-1) != 0) && - (!has_proxy_authorization || - name_len != sizeof("proxy-authorization")-1 || - strncasecmp(s, "proxy-authorization", sizeof("proxy-authorization")-1) != 0)) { - /* add header */ - smart_str_appendl(soap_headers, s, p-s); - smart_str_append_const(soap_headers, "\r\n"); - } - } - s = (*p) ? (p + 1) : p; + } ZEND_HASH_FOREACH_END(); } } } diff --git a/ext/soap/php_sdl.c b/ext/soap/php_sdl.c index b731114775ad..554b8616d964 100644 --- a/ext/soap/php_sdl.c +++ b/ext/soap/php_sdl.c @@ -283,7 +283,9 @@ void sdl_set_uri_credentials(sdlCtx *ctx, char *uri) ctx->context = php_stream_context_from_zval(context_ptr, 1); if (ctx->context && - (header = php_stream_context_get_option(ctx->context, "http", "header")) != NULL) { + (header = php_stream_context_get_option(ctx->context, "http", "header")) != NULL && + Z_TYPE_P(header) == IS_STRING) { + /* TODO: should support header as an array, but this code path is untested */ s = strstr(Z_STRVAL_P(header), "Authorization: Basic"); if (s && (s == Z_STRVAL_P(header) || *(s-1) == '\n' || *(s-1) == '\r')) { char *rest = strstr(s, "\r\n"); diff --git a/ext/soap/tests/bugs/bug73182.phpt b/ext/soap/tests/bugs/bug73182.phpt new file mode 100644 index 000000000000..1e89f262101f --- /dev/null +++ b/ext/soap/tests/bugs/bug73182.phpt @@ -0,0 +1,62 @@ +--TEST-- +Bug #73182 (PHP SOAPClient does not support stream context HTTP headers in array form) +--EXTENSIONS-- +soap +--SKIPIF-- + +--FILE-- + 'http://' . PHP_CLI_SERVER_ADDRESS, + 'uri' => 'misc-uri', + 'trace' => true, + 'stream_context' => stream_context_create([ + 'http' => [ + 'header' => [ + // These 2 must be ignored because the soap http client sets them up + 'Connection: close', + 'User-Agent: bar', + // The following 2 must be included + 'X-custom: foo', + 'Content-Description: Foo', + ' X-custom2: trim me ', + ], + ], + ]), +]); + +$client->__soapCall("foo", []); +echo $client->__getLastRequestHeaders(); + +?> +--EXPECTF-- +POST / HTTP/1.1 +Host: localhost:%d +Connection: Keep-Alive +User-Agent: PHP-SOAP/%s +Content-Type: text/xml; charset=utf-8 +SOAPAction: "misc-uri#foo" +Content-Length: %d +X-custom: foo +Content-Description: Foo +X-custom2: trim me