-
Notifications
You must be signed in to change notification settings - Fork 7.9k
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
Closed
Changes from all commits
Commits
Show all changes
4 commits
Select commit
Hold shift + click to select a range
0301243
Introduce get_serialization_string_from_zval() and use it in to_xml_s…
nielsdos 47915dd
Use get_serialization_string_from_zval() in all encoding functions
nielsdos 7de2677
Implement GH-15711: Allow SoapClient to use the backing value during …
nielsdos e0a7710
Implement GH-15711: Allow SoapClient to use the backing value during …
nielsdos File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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); | ||
|
@@ -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) | ||
{ | ||
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); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this guaranteed to be an integer string? There was a problem hiding this comment. Choose a reason for hiding this commentThe 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; | ||
} | ||
|
@@ -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); | ||
|
@@ -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); | ||
|
@@ -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); | ||
|
@@ -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); | ||
} | ||
|
@@ -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)); | ||
|
@@ -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; | ||
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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 🤷