Commit 4fa10e25136 for php.net

commit 4fa10e251364cb0497efdef3ab6a642b6c784805
Author: Weilin Du <weilindu@php.net>
Date:   Tue Jul 14 04:27:52 2026 +0800

    ext/soap: Improve SOAP scalar decoding error messages (#22706)

    SOAP encoding errors now report the affected type or failing operation in
    the error message instead of the generic "Encoding: Violation of encoding
    rules" message. Code that compares the exact message may need to be
    updated.

diff --git a/NEWS b/NEWS
index fb54cab61c0..702d9d04fe3 100644
--- a/NEWS
+++ b/NEWS
@@ -103,6 +103,8 @@ PHP                                                                        NEWS
     do_request() parameters). (David Carlier)
   . Fixed xsd:hexBinary decoding to reject odd-length values instead of
     silently truncating the last nibble. (Weilin Du)
+  . Made SOAP encoding errors report the affected type or failing operation
+    instead of the generic "Violation of encoding rules" message. (Weilin Du)

 - Standard:
   . Fixed sleep() and usleep() to reject values that overflow the underlying
diff --git a/UPGRADING b/UPGRADING
index 729dfed0363..74a5e066483 100644
--- a/UPGRADING
+++ b/UPGRADING
@@ -147,6 +147,10 @@ PHP 8.6 UPGRADE NOTES
   . WSDL/XML Schema parsing now rejects out-of-range integer values for
     occurrence constraints and integer restriction facets. Negative minOccurs
     and maxOccurs values are rejected as well.
+  . SOAP encoding errors now report the affected type or failing operation in
+    the error message instead of the generic "Encoding: Violation of encoding
+    rules" message. Code that compares the exact message may need to be
+    updated.

 - Sodium:
   . The password-hashing functions sodium_crypto_pwhash(),
diff --git a/ext/soap/php_encoding.c b/ext/soap/php_encoding.c
index 301cdd8588b..d08c5a68318 100644
--- a/ext/soap/php_encoding.c
+++ b/ext/soap/php_encoding.c
@@ -82,6 +82,11 @@ static xmlNodePtr to_xml_any(encodeTypePtr type, zval *data, int style, xmlNodeP
 static zval *guess_zval_convert(zval *ret, encodeTypePtr type, xmlNodePtr data);
 static xmlNodePtr guess_xml_convert(encodeTypePtr type, zval *data, int style, xmlNodePtr parent);

+static zend_always_inline const char *soap_type_name(encodeTypePtr type)
+{
+	return (type && type->type_str) ? type->type_str : "unknown";
+}
+
 static encodePtr get_array_type(xmlNodePtr node, zval *array, smart_str *out_type);

 static xmlNodePtr check_and_resolve_href(xmlNodePtr data);
@@ -660,7 +665,7 @@ static zval *to_zval_string(zval *ret, encodeTypePtr type, xmlNodePtr data)
 		} else if (data->children->type == XML_CDATA_SECTION_NODE && data->children->next == NULL) {
 			ZVAL_STRING(ret, (char*)data->children->content);
 		} else {
-			soap_error0(E_ERROR, "Encoding: Violation of encoding rules");
+			soap_error1(E_ERROR, "Encoding: Type '%s' value must contain a single text or CDATA node", soap_type_name(type));
 		}
 	} else {
 		ZVAL_EMPTY_STRING(ret);
@@ -693,7 +698,7 @@ static zval *to_zval_stringr(zval *ret, encodeTypePtr type, xmlNodePtr data)
 		} else if (data->children->type == XML_CDATA_SECTION_NODE && data->children->next == NULL) {
 			ZVAL_STRING(ret, (char*)data->children->content);
 		} else {
-			soap_error0(E_ERROR, "Encoding: Violation of encoding rules");
+			soap_error1(E_ERROR, "Encoding: Type '%s' value must contain a single text or CDATA node", soap_type_name(type));
 		}
 	} else {
 		ZVAL_EMPTY_STRING(ret);
@@ -726,7 +731,7 @@ static zval *to_zval_stringc(zval *ret, encodeTypePtr type, xmlNodePtr data)
 		} else if (data->children->type == XML_CDATA_SECTION_NODE && data->children->next == NULL) {
 			ZVAL_STRING(ret, (char*)data->children->content);
 		} else {
-			soap_error0(E_ERROR, "Encoding: Violation of encoding rules");
+			soap_error1(E_ERROR, "Encoding: Type '%s' value must contain a single text or CDATA node", soap_type_name(type));
 		}
 	} else {
 		ZVAL_EMPTY_STRING(ret);
@@ -745,17 +750,17 @@ static zval *to_zval_base64(zval *ret, encodeTypePtr type, xmlNodePtr data)
 			whiteSpace_collapse(data->children->content);
 			str = php_base64_decode(data->children->content, strlen((char*)data->children->content));
 			if (!str) {
-				soap_error0(E_ERROR, "Encoding: Violation of encoding rules");
+				soap_error1(E_ERROR, "Encoding: Invalid value for type '%s'", soap_type_name(type));
 			}
 			ZVAL_STR(ret, str);
 		} else if (data->children->type == XML_CDATA_SECTION_NODE && data->children->next == NULL) {
 			str = php_base64_decode(data->children->content, strlen((char*)data->children->content));
 			if (!str) {
-				soap_error0(E_ERROR, "Encoding: Violation of encoding rules");
+				soap_error1(E_ERROR, "Encoding: Invalid value for type '%s'", soap_type_name(type));
 			}
 			ZVAL_STR(ret, str);
 		} else {
-			soap_error0(E_ERROR, "Encoding: Violation of encoding rules");
+			soap_error1(E_ERROR, "Encoding: Type '%s' value must contain a single text or CDATA node", soap_type_name(type));
 		}
 	} else {
 		ZVAL_EMPTY_STRING(ret);
@@ -776,12 +781,12 @@ static zval *to_zval_hexbin(zval *ret, encodeTypePtr type, xmlNodePtr data)
 		if (data->children->type == XML_TEXT_NODE && data->children->next == NULL) {
 			whiteSpace_collapse(data->children->content);
 		} else if (data->children->type != XML_CDATA_SECTION_NODE || data->children->next != NULL) {
-			soap_error0(E_ERROR, "Encoding: Violation of encoding rules");
+			soap_error1(E_ERROR, "Encoding: Type '%s' value must contain a single text or CDATA node", soap_type_name(type));
 			return ret;
 		}
 		content_len = strlen((char*) data->children->content);
 		if (content_len % 2 != 0) {
-			soap_error0(E_ERROR, "Encoding: Violation of encoding rules");
+			soap_error1(E_ERROR, "Encoding: Type '%s' value must contain an even number of hexadecimal digits", soap_type_name(type));
 			return ret;
 		}
 		str = zend_string_alloc(content_len / 2, 0);
@@ -794,7 +799,7 @@ static zval *to_zval_hexbin(zval *ret, encodeTypePtr type, xmlNodePtr data)
 			} else if (c >= 'A' && c <= 'F') {
 				ZSTR_VAL(str)[i] = (c - 'A' + 10) << 4;
 			} else {
-				soap_error0(E_ERROR, "Encoding: Violation of encoding rules");
+				soap_error1(E_ERROR, "Encoding: Invalid value for type '%s'", soap_type_name(type));
 			}
 			c = data->children->content[j++];
 			if (c >= '0' && c <= '9') {
@@ -804,7 +809,7 @@ static zval *to_zval_hexbin(zval *ret, encodeTypePtr type, xmlNodePtr data)
 			} else if (c >= 'A' && c <= 'F') {
 				ZSTR_VAL(str)[i] |= c - 'A' + 10;
 			} else {
-				soap_error0(E_ERROR, "Encoding: Violation of encoding rules");
+				soap_error1(E_ERROR, "Encoding: Invalid value for type '%s'", soap_type_name(type));
 			}
 		}
 		ZSTR_VAL(str)[ZSTR_LEN(str)] = '\0';
@@ -1025,11 +1030,11 @@ static zval *to_zval_double(zval *ret, encodeTypePtr type, xmlNodePtr data)
 					} else if (strncasecmp((char*)data->children->content, "-INF", sizeof("-INF")-1) == 0) {
 						ZVAL_DOUBLE(ret, -php_get_inf());
 					} else {
-						soap_error0(E_ERROR, "Encoding: Violation of encoding rules");
+						soap_error1(E_ERROR, "Encoding: Invalid value for type '%s'", soap_type_name(type));
 					}
 			}
 		} else {
-			soap_error0(E_ERROR, "Encoding: Violation of encoding rules");
+			soap_error1(E_ERROR, "Encoding: Type '%s' value must contain a single text or CDATA node", soap_type_name(type));
 		}
 	} else {
 		ZVAL_NULL(ret);
@@ -1058,10 +1063,10 @@ static zval *to_zval_long(zval *ret, encodeTypePtr type, xmlNodePtr data)
 					ZVAL_DOUBLE(ret, dval);
 					break;
 				default:
-					soap_error0(E_ERROR, "Encoding: Violation of encoding rules");
+					soap_error1(E_ERROR, "Encoding: Invalid value for type '%s'", soap_type_name(type));
 			}
 		} else {
-			soap_error0(E_ERROR, "Encoding: Violation of encoding rules");
+			soap_error1(E_ERROR, "Encoding: Type '%s' value must contain a single text or CDATA node", soap_type_name(type));
 		}
 	} else {
 		ZVAL_NULL(ret);
@@ -1127,7 +1132,7 @@ static zval *to_zval_bool(zval *ret, encodeTypePtr type, xmlNodePtr data)
 	}
 	if (data->children->type != XML_TEXT_NODE || data->children->next != NULL) {
 		// TODO Convert to exception?
-		soap_error0(E_ERROR, "Encoding: Violation of encoding rules");
+		soap_error1(E_ERROR, "Encoding: Type '%s' value must contain a single text or CDATA node", soap_type_name(type));
 	}

 	whiteSpace_collapse(data->children->content);
@@ -3096,7 +3101,9 @@ static xmlNodePtr to_xml_list(encodeTypePtr enc, zval *data, int style, xmlNodeP
 				}
 				smart_str_appends(&list, (char*)dummy->children->content);
 			} else {
-				soap_error0(E_ERROR, "Encoding: Violation of encoding rules");
+				soap_error2(E_ERROR,
+					"Encoding: Failed to encode list item of type '%s' for list type '%s'",
+					soap_type_name(&list_enc->details), soap_type_name(enc));
 			}
 			xmlUnlinkNode(dummy);
 			xmlFreeNode(dummy);
@@ -3138,7 +3145,9 @@ static xmlNodePtr to_xml_list(encodeTypePtr enc, zval *data, int style, xmlNodeP
 				}
 				smart_str_appends(&list, (char*)dummy->children->content);
 			} else {
-				soap_error0(E_ERROR, "Encoding: Violation of encoding rules");
+				soap_error2(E_ERROR,
+					"Encoding: Failed to encode list item of type '%s' for list type '%s'",
+					soap_type_name(&list_enc->details), soap_type_name(enc));
 			}
 			xmlUnlinkNode(dummy);
 			xmlFreeNode(dummy);
diff --git a/ext/soap/tests/bugs/bug39832.phpt b/ext/soap/tests/bugs/bug39832.phpt
index f5742c70984..bc8e8e861ab 100644
--- a/ext/soap/tests/bugs/bug39832.phpt
+++ b/ext/soap/tests/bugs/bug39832.phpt
@@ -26,4 +26,4 @@ function Test($x) {
 ?>
 --EXPECT--
 <?xml version="1.0" encoding="UTF-8"?>
-<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"><SOAP-ENV:Body><SOAP-ENV:Fault><faultcode>SOAP-ENV:Server</faultcode><faultstring>SOAP-ERROR: Encoding: Violation of encoding rules</faultstring></SOAP-ENV:Fault></SOAP-ENV:Body></SOAP-ENV:Envelope>
+<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"><SOAP-ENV:Body><SOAP-ENV:Fault><faultcode>SOAP-ENV:Server</faultcode><faultstring>SOAP-ERROR: Encoding: Invalid value for type 'integer'</faultstring></SOAP-ENV:Fault></SOAP-ENV:Body></SOAP-ENV:Envelope>
diff --git a/ext/soap/tests/hexbin_odd_length.phpt b/ext/soap/tests/hexbin_odd_length.phpt
index bfe84ab643d..cc1cdcbab1f 100644
--- a/ext/soap/tests/hexbin_odd_length.phpt
+++ b/ext/soap/tests/hexbin_odd_length.phpt
@@ -34,4 +34,4 @@ public function __doRequest($request, $location, $action, $version, $one_way = f
 }
 ?>
 --EXPECT--
-SOAP-ERROR: Encoding: Violation of encoding rules
+SOAP-ERROR: Encoding: Type 'hexBinary' value must contain an even number of hexadecimal digits
diff --git a/ext/soap/tests/scalar_error_messages.phpt b/ext/soap/tests/scalar_error_messages.phpt
new file mode 100644
index 00000000000..fde4449d6d8
--- /dev/null
+++ b/ext/soap/tests/scalar_error_messages.phpt
@@ -0,0 +1,52 @@
+--TEST--
+SOAP reports specific scalar encoding errors
+--EXTENSIONS--
+soap
+--FILE--
+<?php
+class ScalarErrorClient extends SoapClient {
+    public string $response;
+
+    public function __doRequest($request, $location, $action, $version, $one_way = false, ?string $uriParserClass = null): string {
+        return $this->response;
+    }
+}
+
+function soap_response(string $type, string $value): string {
+    return <<<XML
+<?xml version="1.0" encoding="UTF-8"?>
+<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
+                   xmlns:xsd="http://www.w3.org/2001/XMLSchema"
+                   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+  <SOAP-ENV:Body>
+    <ns1:testResponse xmlns:ns1="urn:test">
+      <return xsi:type="$type">$value</return>
+    </ns1:testResponse>
+  </SOAP-ENV:Body>
+</SOAP-ENV:Envelope>
+XML;
+}
+
+function run_case(string $label, string $type, string $value): void {
+    $client = new ScalarErrorClient(null, [
+        'location' => 'test://',
+        'uri' => 'urn:test',
+        'exceptions' => true,
+    ]);
+    $client->response = soap_response($type, $value);
+
+    try {
+        $client->__soapCall('test', []);
+    } catch (SoapFault $e) {
+        echo $label, ': ', $e->faultstring, "\n";
+    }
+}
+
+run_case('double', 'xsd:double', 'abc');
+run_case('long', 'xsd:long', 'abc');
+run_case('base64Binary node', 'xsd:base64Binary', '<value>abc</value>');
+?>
+--EXPECT--
+double: SOAP-ERROR: Encoding: Invalid value for type 'double'
+long: SOAP-ERROR: Encoding: Invalid value for type 'long'
+base64Binary node: SOAP-ERROR: Encoding: Type 'base64Binary' value must contain a single text or CDATA node
diff --git a/ext/soap/tests/soap12/T27.phpt b/ext/soap/tests/soap12/T27.phpt
index 09591b7faeb..5bb6572e3df 100644
--- a/ext/soap/tests/soap12/T27.phpt
+++ b/ext/soap/tests/soap12/T27.phpt
@@ -25,4 +25,4 @@
 ?>
 --EXPECT--
 <?xml version="1.0" encoding="UTF-8"?>
-<env:Envelope xmlns:env="http://www.w3.org/2003/05/soap-envelope"><env:Body><env:Fault><env:Code><env:Value>env:Receiver</env:Value></env:Code><env:Reason><env:Text xml:lang="en">SOAP-ERROR: Encoding: Violation of encoding rules</env:Text></env:Reason></env:Fault></env:Body></env:Envelope>
+<env:Envelope xmlns:env="http://www.w3.org/2003/05/soap-envelope"><env:Body><env:Fault><env:Code><env:Value>env:Receiver</env:Value></env:Code><env:Reason><env:Text xml:lang="en">SOAP-ERROR: Encoding: Type 'string' value must contain a single text or CDATA node</env:Text></env:Reason></env:Fault></env:Body></env:Envelope>
diff --git a/ext/soap/tests/soap12/T58.phpt b/ext/soap/tests/soap12/T58.phpt
index 69c20a4f867..a8619d20b29 100644
--- a/ext/soap/tests/soap12/T58.phpt
+++ b/ext/soap/tests/soap12/T58.phpt
@@ -24,4 +24,4 @@
 ?>
 --EXPECT--
 <?xml version="1.0" encoding="UTF-8"?>
-<env:Envelope xmlns:env="http://www.w3.org/2003/05/soap-envelope"><env:Body><env:Fault><env:Code><env:Value>env:Receiver</env:Value></env:Code><env:Reason><env:Text xml:lang="en">SOAP-ERROR: Encoding: Violation of encoding rules</env:Text></env:Reason></env:Fault></env:Body></env:Envelope>
+<env:Envelope xmlns:env="http://www.w3.org/2003/05/soap-envelope"><env:Body><env:Fault><env:Code><env:Value>env:Receiver</env:Value></env:Code><env:Reason><env:Text xml:lang="en">SOAP-ERROR: Encoding: Type 'int' value must contain a single text or CDATA node</env:Text></env:Reason></env:Fault></env:Body></env:Envelope>