Skip to content

Implement GH-15711: Allow SoapClient to use the backing value during response serialization #15803

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
95 changes: 64 additions & 31 deletions ext/soap/php_encoding.c
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
#include <libxml/parserInternals.h>
#include "zend_strtod.h"
#include "zend_interfaces.h"
#include "zend_enum.h"

/* zval type decode */
static zval *to_zval_double(zval* ret, encodeTypePtr type, xmlNodePtr data);
Expand Down Expand Up @@ -830,33 +831,69 @@ static zval *to_zval_hexbin(zval *ret, encodeTypePtr type, xmlNodePtr data)
return ret;
}

static zend_string *get_serialization_string_from_zval(zval *data)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this have a "SOAP" prefix? I don't remember if SOAP uses them or not

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The other methods here don't do that, most methods in soap actually don't 🤷

{
switch (Z_TYPE_P(data)) {
case IS_OBJECT:
if (Z_OBJCE_P(data)->ce_flags & ZEND_ACC_ENUM) {
if (UNEXPECTED(Z_OBJCE_P(data)->enum_backing_type == IS_UNDEF)) {
zend_value_error("Non-backed enums have no default serialization");
return zend_empty_string;
} else {
zval *value = zend_enum_fetch_case_value(Z_OBJ_P(data));
return zval_get_string_func(value);
}
}
ZEND_FALLTHROUGH;
default:
return zval_get_string_func(data);
}
}

static zend_long get_serialization_long_from_zval(zval *data)
{
switch (Z_TYPE_P(data)) {
case IS_OBJECT:
if (Z_OBJCE_P(data)->ce_flags & ZEND_ACC_ENUM) {
if (UNEXPECTED(Z_OBJCE_P(data)->enum_backing_type != IS_LONG)) {
if (Z_OBJCE_P(data)->enum_backing_type == IS_UNDEF) {
zend_value_error("Non-backed enums have no default serialization");
} else {
zend_value_error("String-backed enum cannot be serialized as int");
}
return 0;
} else {
zval *value = zend_enum_fetch_case_value(Z_OBJ_P(data));
ZEND_ASSERT(Z_TYPE_P(value) == IS_LONG);
return Z_LVAL_P(value);
}
}
ZEND_FALLTHROUGH;
default:
return zval_get_long(data);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this guaranteed to be an integer string?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No. It'll throw if that's not the case.

}
}

static xmlNodePtr to_xml_string(encodeTypePtr type, zval *data, int style, xmlNodePtr parent)
{
xmlNodePtr ret, text;
char *str;
int new_len;

ret = xmlNewNode(NULL, BAD_CAST("BOGUS"));
xmlAddChild(parent, ret);
FIND_ZVAL_NULL(data, ret, style);

if (Z_TYPE_P(data) == IS_STRING) {
str = estrndup(Z_STRVAL_P(data), Z_STRLEN_P(data));
new_len = Z_STRLEN_P(data);
} else {
zend_string *tmp = zval_get_string_func(data);
str = estrndup(ZSTR_VAL(tmp), ZSTR_LEN(tmp));
new_len = ZSTR_LEN(tmp);
zend_string_release_ex(tmp, 0);
}
zend_string *serialization = get_serialization_string_from_zval(data);
char *str = ZSTR_VAL(serialization);
size_t new_len = ZSTR_LEN(serialization);

if (SOAP_GLOBAL(encoding) != NULL) {
xmlBufferPtr in = xmlBufferCreateStatic(str, new_len);
xmlBufferPtr out = xmlBufferCreate();
int n = xmlCharEncInFunc(SOAP_GLOBAL(encoding), out, in);

if (n >= 0) {
efree(str);
zend_string_release(serialization);
serialization = NULL;
str = estrdup((char*)xmlBufferContent(out));
new_len = n;
}
Expand Down Expand Up @@ -907,7 +944,11 @@ static xmlNodePtr to_xml_string(encodeTypePtr type, zval *data, int style, xmlNo

text = xmlNewTextLen(BAD_CAST(str), new_len);
xmlAddChild(ret, text);
efree(str);
if (serialization) {
zend_string_release(serialization);
} else {
efree(str);
}

if (style == SOAP_ENCODED) {
set_ns_and_type(ret, type);
Expand All @@ -918,19 +959,14 @@ static xmlNodePtr to_xml_string(encodeTypePtr type, zval *data, int style, xmlNo
static xmlNodePtr to_xml_base64(encodeTypePtr type, zval *data, int style, xmlNodePtr parent)
{
xmlNodePtr ret, text;
zend_string *str;

ret = xmlNewNode(NULL, BAD_CAST("BOGUS"));
xmlAddChild(parent, ret);
FIND_ZVAL_NULL(data, ret, style);

if (Z_TYPE_P(data) == IS_STRING) {
str = php_base64_encode((unsigned char*)Z_STRVAL_P(data), Z_STRLEN_P(data));
} else {
zend_string *tmp = zval_get_string_func(data);
str = php_base64_encode((unsigned char*) ZSTR_VAL(tmp), ZSTR_LEN(tmp));
zend_string_release_ex(tmp, 0);
}
zend_string *serialization = get_serialization_string_from_zval(data);
zend_string *str = php_base64_encode((unsigned char *) ZSTR_VAL(serialization), ZSTR_LEN(serialization));
zend_string_release(serialization);

text = xmlNewTextLen(BAD_CAST(ZSTR_VAL(str)), ZSTR_LEN(str));
xmlAddChild(ret, text);
Expand All @@ -955,7 +991,7 @@ static xmlNodePtr to_xml_hexbin(encodeTypePtr type, zval *data, int style, xmlNo
FIND_ZVAL_NULL(data, ret, style);

if (Z_TYPE_P(data) != IS_STRING) {
ZVAL_STR(&tmp, zval_get_string_func(data));
ZVAL_STR(&tmp, get_serialization_string_from_zval(data));
data = &tmp;
}
str = (unsigned char *) safe_emalloc(Z_STRLEN_P(data) * 2, sizeof(char), 1);
Expand Down Expand Up @@ -1063,7 +1099,7 @@ static xmlNodePtr to_xml_long(encodeTypePtr type, zval *data, int style, xmlNode
snprintf(s, sizeof(s), "%0.0F",floor(Z_DVAL_P(data)));
xmlNodeSetContent(ret, BAD_CAST(s));
} else {
zend_string *str = zend_long_to_str(zval_get_long(data));
zend_string *str = zend_long_to_str(get_serialization_long_from_zval(data));
xmlNodeSetContentLen(ret, BAD_CAST(ZSTR_VAL(str)), ZSTR_LEN(str));
zend_string_release_ex(str, 0);
}
Expand Down Expand Up @@ -3026,7 +3062,7 @@ static xmlNodePtr to_xml_list(encodeTypePtr enc, zval *data, int style, xmlNodeP
smart_str list = {0};

if (Z_TYPE_P(data) != IS_STRING) {
ZVAL_STR(&tmp, zval_get_string_func(data));
ZVAL_STR(&tmp, get_serialization_string_from_zval(data));
data = &tmp;
}
str = estrndup(Z_STRVAL_P(data), Z_STRLEN_P(data));
Expand Down Expand Up @@ -3135,13 +3171,10 @@ static xmlNodePtr to_xml_any(encodeTypePtr type, zval *data, int style, xmlNodeP
} ZEND_HASH_FOREACH_END();
return ret;
}
if (Z_TYPE_P(data) == IS_STRING) {
ret = xmlNewTextLen(BAD_CAST(Z_STRVAL_P(data)), Z_STRLEN_P(data));
} else {
zend_string *tmp = zval_get_string_func(data);
ret = xmlNewTextLen(BAD_CAST(ZSTR_VAL(tmp)), ZSTR_LEN(tmp));
zend_string_release_ex(tmp, 0);
}

zend_string *serialization = get_serialization_string_from_zval(data);
ret = xmlNewTextLen(BAD_CAST(ZSTR_VAL(serialization)), ZSTR_LEN(serialization));
zend_string_release_ex(serialization, false);

ret->name = xmlStringTextNoenc;
ret->parent = parent;
Expand Down
88 changes: 88 additions & 0 deletions ext/soap/tests/gh15711.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
--TEST--
GH-15711 (SoapClient can't convert BackedEnum to scalar value)
--EXTENSIONS--
soap
--INI--
soap.wsdl_cache_enabled=0
--FILE--
<?php

enum StringBackedEnum: string
{
case First = 'BackingValue1';
case Second = 'BackingValue2';
case Third = 'BackingValue3';
case Fourth = 'BackingValue4';
case Fifth = 'BackingValue5';
}

enum IntBackedEnum: int
{
case First = 1;
case Second = 2;
}

enum NonBackedEnum
{
case First;
}

class TestSoapClient extends SoapClient {
function __doRequest($request, $location, $action, $version, $one_way = 0): ?string {
echo $request;
}
}

$client = new TestSoapClient('ext/soap/tests/gh15711.wsdl', ['classmap' => ['book' => 'book']]);

echo "--- Test with backed enum ---\n";

$book = new stdClass();
$book->base64 = StringBackedEnum::First;
$book->string = StringBackedEnum::Second;
$book->any = StringBackedEnum::Third;
$book->hexbin = StringBackedEnum::Fourth;
$book->nmtokens = StringBackedEnum::Fifth;
$book->integer = IntBackedEnum::First;
$book->short = IntBackedEnum::Second;

try {
$client->dotest($book);
} catch (Throwable) {}

echo "--- Test with non-backed enum ---\n";

$book = new stdClass();
$book->base64 = NonBackedEnum::First;
$book->string = NonBackedEnum::First;
$book->any = NonBackedEnum::First;
$book->hexbin = NonBackedEnum::First;
$book->nmtokens = NonBackedEnum::First;
$book->integer = NonBackedEnum::First;
$book->short = NonBackedEnum::First;

try {
$client->dotest($book);
} catch (ValueError $e) {
echo "ValueError: ", $e->getMessage(), "\n";
}

echo "--- Test with mismatched enum backing type ---\n";

$book->integer = StringBackedEnum::First;
$book->short = StringBackedEnum::First;
try {
$client->dotest($book);
} catch (ValueError $e) {
echo "ValueError: ", $e->getMessage(), "\n";
}

?>
--EXPECT--
--- Test with backed enum ---
<?xml version="1.0" encoding="UTF-8"?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns1="http://schemas.nothing.com" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"><SOAP-ENV:Body><ns1:dotest><dotestReturn xsi:type="ns1:book"><base64 xsi:type="xsd:base64Binary">QmFja2luZ1ZhbHVlMQ==</base64><string xsi:type="xsd:string">BackingValue2</string><any xsi:type="xsd:any"><name xsi:type="xsd:string">Third</name><value xsi:type="xsd:string">BackingValue3</value></any><hexbin xsi:type="xsd:hexBinary">4261636B696E6756616C756534</hexbin><nmtokens>BackingValue5</nmtokens><integer xsi:type="xsd:integer">1</integer><short xsi:type="xsd:short">2</short></dotestReturn></ns1:dotest></SOAP-ENV:Body></SOAP-ENV:Envelope>
--- Test with non-backed enum ---
ValueError: Non-backed enums have no default serialization
--- Test with mismatched enum backing type ---
ValueError: String-backed enum cannot be serialized as int
39 changes: 39 additions & 0 deletions ext/soap/tests/gh15711.wsdl
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<wsdl:definitions xmlns="http://schemas.xmlsoap.org/wsdl/" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:tns="http://schemas.nothing.com" targetNamespace="http://schemas.nothing.com">
<wsdl:types>
<xsd:schema targetNamespace="http://schemas.nothing.com">
<xsd:complexType name="book">
<xsd:all>
<xsd:element name="base64" type="xsd:base64Binary"/>
<xsd:element name="string" type="xsd:string"/>
<xsd:element name="any" type="xsd:any"/>
<xsd:element name="hexbin" type="xsd:hexBinary"/>
<xsd:element name="nmtokens" type="xsd:NMTOKENS"/>
<xsd:element name="integer" type="xsd:integer"/>
<xsd:element name="short" type="xsd:short"/>
</xsd:all>
</xsd:complexType>
</xsd:schema>
</wsdl:types>
<message name="dotestRequest">
<part name="dotestReturn" type="tns:book"/>
</message>
<portType name="testPortType">
<operation name="dotest">
<input message="tns:dotestRequest"/>
</operation>
</portType>
<binding name="testBinding" type="tns:testPortType">
<soap:binding style="rpc" transport="http://schemas.xmlsoap.org/soap/http"/>
<operation name="dotest">
<soap:operation soapAction="http://localhost:81/test/interface.php?class=test/dotest" style="rpc"/>
<input>
<soap:body use="encoded" encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" namespace="http://schemas.nothing.com"/>
</input>
</operation>
</binding>
<service name="test">
<port name="testPort" binding="tns:testBinding">
<soap:address location="http://localhost:81/test/interface.php?class=test"/>
</port>
</service>
</wsdl:definitions>
Loading