Skip to content

Commit 6bf90e5

Browse files
SoapClient: add new keep_headers option
1 parent 0ec9e41 commit 6bf90e5

20 files changed

Lines changed: 319 additions & 10 deletions

‎ext/soap/php_sdl.c‎

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -358,6 +358,15 @@ void sdl_set_uri_credentials(sdlCtx *ctx, char *uri)
358358
zend_string *lc_headers = zend_string_tolower(flat_headers);
359359
// Fast path: no redaction needed
360360
uint32_t redaction = header_needs_redaction(lc_headers);
361+
if (ctx->headers_to_keep & WSDL_HEADER_KEEP_AUTHORIZATION) {
362+
redaction &= ~REDACT_AUTHORIZATION;
363+
}
364+
if (ctx->headers_to_keep & WSDL_HEADER_KEEP_PROXY_AUTHORIZATION) {
365+
redaction &= ~REDACT_PROXY_AUTH;
366+
}
367+
if (ctx->headers_to_keep & WSDL_HEADER_KEEP_COOKIES) {
368+
redaction &= ~REDACT_COOKIE;
369+
}
361370
if (redaction == REDACT_NONE) {
362371
zend_string_release(lc_headers);
363372
zend_string_release(flat_headers);
@@ -863,6 +872,11 @@ static sdlPtr load_wsdl(zval *this_ptr, char *struri)
863872
zend_hash_init(&ctx.portTypes, 0, NULL, NULL, 0);
864873
zend_hash_init(&ctx.services, 0, NULL, NULL, 0);
865874

875+
ctx.headers_to_keep = 0;
876+
if (instanceof_function(Z_OBJCE_P(this_ptr), soap_class_entry)) {
877+
ctx.headers_to_keep = Z_LVAL_P(Z_CLIENT_KEEP_HEADERS_P(this_ptr));
878+
}
879+
866880
zend_try {
867881
load_wsdl_ex(this_ptr, struri, &ctx, false);
868882
schema_pass2(&ctx);

‎ext/soap/php_sdl.h‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ typedef struct sdlCtx {
7474
HashTable fixupInProgress;
7575
php_stream_context *context;
7676
zval old_header;
77+
zend_long headers_to_keep; /* Bitmask of WSDL_HEADER_KEEP_* from php_soap.h */
7778
} sdlCtx;
7879

7980
struct _sdlBinding {

‎ext/soap/php_soap.h‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,11 @@ struct _soapService {
151151
#define SOAP_MAX_XML_DEPTH 2048
152152
#define SOAP_MAX_DECODE_DEPTH (SOAP_MAX_XML_DEPTH * 2)
153153

154+
/* Some headers are redacted on requests to other hosts unless requested otherwise */
155+
#define WSDL_HEADER_KEEP_AUTHORIZATION (1 << 0)
156+
#define WSDL_HEADER_KEEP_PROXY_AUTHORIZATION (1 << 1)
157+
#define WSDL_HEADER_KEEP_COOKIES (1 << 2)
158+
154159
ZEND_BEGIN_MODULE_GLOBALS(soap)
155160
HashTable *typemap;
156161
int cur_uniq_ns;
@@ -255,6 +260,7 @@ static zend_always_inline zval *php_soap_deref(zval *zv) {
255260
#define Z_CLIENT_LAST_RESPONSE_P(zv) OBJ_PROP_NUM(Z_OBJ_P(zv), 32)
256261
#define Z_CLIENT_LAST_REQUEST_HEADERS_P(zv) OBJ_PROP_NUM(Z_OBJ_P(zv), 33)
257262
#define Z_CLIENT_LAST_RESPONSE_HEADERS_P(zv) OBJ_PROP_NUM(Z_OBJ_P(zv), 34)
263+
#define Z_CLIENT_KEEP_HEADERS_P(zv) OBJ_PROP_NUM(Z_OBJ_P(zv), 35)
258264

259265
typedef struct soap_url_object {
260266
php_uri *uri;

‎ext/soap/soap.c‎

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2106,6 +2106,7 @@ PHP_METHOD(SoapClient, __construct)
21062106
sdlPtr sdl = NULL;
21072107
HashTable *typemap_ht = NULL;
21082108
zval *this_ptr = ZEND_THIS;
2109+
zend_long keep_headers = 0;
21092110

21102111
if (zend_parse_parameters(ZEND_NUM_ARGS(), "S!|a", &wsdl, &options) == FAILURE) {
21112112
RETURN_THROWS();
@@ -2303,6 +2304,26 @@ PHP_METHOD(SoapClient, __construct)
23032304
"The \"ssl_method\" option is deprecated. "
23042305
"Use \"ssl\" stream context options instead");
23052306
}
2307+
2308+
if ((tmp = zend_hash_str_find(ht, "keep_headers", sizeof("keep_headers")-1)) != NULL
2309+
&& Z_TYPE_P(tmp) == IS_ARRAY
2310+
) {
2311+
ZEND_HASH_FOREACH_VAL(Z_ARR_P(tmp), zval *to_keep) {
2312+
if (Z_TYPE_P(to_keep) != IS_STRING) {
2313+
continue;
2314+
}
2315+
zend_string *header_name = Z_STR_P(to_keep);
2316+
if (zend_string_equals_literal_ci(header_name, "authorization")) {
2317+
keep_headers |= WSDL_HEADER_KEEP_AUTHORIZATION;
2318+
} else if (zend_string_equals_literal_ci(header_name, "proxy-authorization")) {
2319+
keep_headers |= WSDL_HEADER_KEEP_PROXY_AUTHORIZATION;
2320+
} else if (zend_string_equals_literal_ci(header_name, "cookie")) {
2321+
keep_headers |= WSDL_HEADER_KEEP_COOKIES;
2322+
}
2323+
} ZEND_HASH_FOREACH_END();
2324+
ZVAL_LONG(Z_CLIENT_KEEP_HEADERS_P(this_ptr), keep_headers);
2325+
2326+
}
23062327
} else if (!wsdl) {
23072328
php_error_docref(NULL, E_ERROR, "'location' and 'uri' options are required in nonWSDL mode");
23082329
}

‎ext/soap/soap.stub.php‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -572,6 +572,7 @@ class SoapClient
572572
private ?string $__last_response = null;
573573
private ?string $__last_request_headers = null;
574574
private ?string $__last_response_headers = null;
575+
private int $_keep_headers = 0;
575576

576577
public function __construct(?string $wsdl, array $options = []) {}
577578

‎ext/soap/soap_arginfo.h‎

Lines changed: 7 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎ext/soap/tests/bugs/gh21421.phpt‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ $client = new SoapClient(NULL, $options);
1515
var_dump($client);
1616
?>
1717
--EXPECTF--
18-
object(SoapClient)#%d (35) {
18+
object(SoapClient)#%d (36) {
1919
["uri":"SoapClient":private]=>
2020
string(26) "http://schemas.nothing.com"
2121
["style":"SoapClient":private]=>
@@ -87,4 +87,6 @@ object(SoapClient)#%d (35) {
8787
NULL
8888
["__last_response_headers":"SoapClient":private]=>
8989
NULL
90+
["_keep_headers":"SoapClient":private]=>
91+
int(0)
9092
}

‎ext/soap/tests/gh23686/check_headers.inc‎

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,12 @@ function start_server(string $code, ?string $entry_point) {
2020
* When an <import> switches to a different server, check the headers that the
2121
* second server recieves
2222
*/
23-
function check_headers_for_import(string $logs_path, $context, bool $verbose_failure = true) {
23+
function check_headers_for_import(
24+
string $logs_path,
25+
$context,
26+
array $options = [],
27+
bool $verbose_failure = true,
28+
) {
2429
$code = file_get_contents(__DIR__ . "/soap-respond-with-headers.inc");
2530
$code = substr($code, strpos($code, "<?php") + strlen("<?php"));
2631
$code = str_replace('"{LOG_FILE}"', var_export($logs_path, true), $code);
@@ -42,6 +47,7 @@ function check_headers_for_import(string $logs_path, $context, bool $verbose_fai
4247
$client = new SoapClient("http://localhost:$wsdl_server_port", [
4348
'soap_version' => $version,
4449
'stream_context' => $context,
50+
...$options,
4551
]);
4652
} catch (SoapFault $e) {
4753
if ($verbose_failure) {
@@ -61,7 +67,12 @@ function check_headers_for_import(string $logs_path, $context, bool $verbose_fai
6167
* When an <import> leads to a <schema> on a different server, check the
6268
* headers that the *third* server recieves
6369
*/
64-
function check_headers_for_schema(string $logs_path, $context, bool $verbose_failure = true) {
70+
function check_headers_for_schema(
71+
string $logs_path,
72+
$context,
73+
array $options = [],
74+
bool $verbose_failure = true
75+
) {
6576
$code = file_get_contents(__DIR__ . "/schema-respond-with-headers.inc");
6677
$code = substr($code, strpos($code, "<?php") + strlen("<?php"));
6778
$code = str_replace('"{LOG_FILE}"', var_export($logs_path, true), $code);
@@ -85,6 +96,7 @@ function check_headers_for_schema(string $logs_path, $context, bool $verbose_fai
8596
$client = new SoapClient("http://localhost:$import_wsdl_server_port", [
8697
'soap_version' => $version,
8798
'stream_context' => $context,
99+
...$options,
88100
]);
89101
} catch (SoapFault $e) {
90102
if ($verbose_failure) {
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
--TEST--
2+
GH-23686: `Authorization` header kept but others removed (`<import>`)
3+
--EXTENSIONS--
4+
soap
5+
--INI--
6+
soap.wsdl_cache_enabled=0
7+
--CLEAN--
8+
<?php
9+
unlink(__DIR__ . "/import-keep-authorization-logs.txt");
10+
?>
11+
--SKIPIF--
12+
<?php
13+
if (!file_exists(__DIR__ . "/../../../../sapi/cli/tests/php_cli_server.inc")) {
14+
echo "skip sapi/cli/tests/php_cli_server.inc required but not found";
15+
}
16+
?>
17+
--FILE--
18+
<?php
19+
20+
include __DIR__ . "/check_headers.inc";
21+
22+
const LOGS_PATH = __DIR__ . "/import-keep-authorization-logs.txt";
23+
24+
$context = stream_context_create([
25+
'http' => ['header' => "Authorization: Bearer secret-token\r\nProxy-Authorization: FooBar\r\nCookie: foo=bar"],
26+
]);
27+
28+
check_headers_for_import(LOGS_PATH, $context, ["keep_headers" => ["authorization"]]);
29+
30+
?>
31+
--EXPECTF--
32+
string(%d) "array (
33+
'Host' => 'localhost:%d',
34+
'Connection' => 'close',
35+
'Authorization' => 'Bearer secret-token',
36+
)"
37+
string(%d) "array (
38+
'Host' => 'localhost:%d',
39+
'Connection' => 'close',
40+
'Authorization' => 'Bearer secret-token',
41+
)"
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
--TEST--
2+
GH-23686: `Cookie` header kept but others removed (`<import>`)
3+
--EXTENSIONS--
4+
soap
5+
--INI--
6+
soap.wsdl_cache_enabled=0
7+
--CLEAN--
8+
<?php
9+
unlink(__DIR__ . "/import-keep-cookies-logs.txt");
10+
?>
11+
--SKIPIF--
12+
<?php
13+
if (!file_exists(__DIR__ . "/../../../../sapi/cli/tests/php_cli_server.inc")) {
14+
echo "skip sapi/cli/tests/php_cli_server.inc required but not found";
15+
}
16+
?>
17+
--FILE--
18+
<?php
19+
20+
include __DIR__ . "/check_headers.inc";
21+
22+
const LOGS_PATH = __DIR__ . "/import-keep-cookies-logs.txt";
23+
24+
$context = stream_context_create([
25+
'http' => ['header' => "Authorization: Bearer secret-token\r\nProxy-Authorization: FooBar\r\nCookie: foo=bar"],
26+
]);
27+
28+
check_headers_for_import(LOGS_PATH, $context, ["keep_headers" => ["cookie"]]);
29+
30+
?>
31+
--EXPECTF--
32+
string(%d) "array (
33+
'Host' => 'localhost:%d',
34+
'Connection' => 'close',
35+
'Cookie' => 'foo=bar',
36+
)"
37+
string(%d) "array (
38+
'Host' => 'localhost:%d',
39+
'Connection' => 'close',
40+
'Cookie' => 'foo=bar',
41+
)"

0 commit comments

Comments
 (0)