diff --git a/ext/xmlrpc/Module.php b/ext/xmlrpc/Module.php
index 88393aa567e..ca8295e92ca 100644
--- a/ext/xmlrpc/Module.php
+++ b/ext/xmlrpc/Module.php
@@ -10,7 +10,7 @@
* xmlrpc extension module entry (php-src ext/xmlrpc/xmlrpc.c; issue #6579, #18503).
*
* PHP-in-PHP XML-RPC encode/decode — no runtime/*.c growth. Advertise logical {@code xmlrpc}
- * extension and xmlrpc_encode()/xmlrpc_decode() when
+ * extension and xmlrpc_encode()/xmlrpc_decode() plus request/type helpers when
* {@see XmlrpcExtensionPolicy::advertisesExtension()} — withheld on reference profile
* (Zend 8.2 has no ext/xmlrpc).
*/
@@ -33,6 +33,11 @@ public function getFunctions(): array
return [
new xmlrpc_encode(),
new xmlrpc_decode(),
+ new xmlrpc_encode_request(),
+ new xmlrpc_decode_request(),
+ new xmlrpc_is_fault(),
+ new xmlrpc_get_type(),
+ new xmlrpc_set_type(),
];
}
}
diff --git a/ext/xmlrpc/VmXmlrpc.php b/ext/xmlrpc/VmXmlrpc.php
index a5be3ba9d6d..462a6598b2e 100644
--- a/ext/xmlrpc/VmXmlrpc.php
+++ b/ext/xmlrpc/VmXmlrpc.php
@@ -8,12 +8,15 @@
use PHPCompiler\VM\Variable;
/**
- * XML-RPC encode/decode (php-src ext/xmlrpc/xmlrpc-epi-php.c; #6579).
+ * XML-RPC encode/decode + request/type helpers (php-src ext/xmlrpc/xmlrpc-epi-php.c; #6579, #22254).
*
* PHP-in-PHP value serialization — string XML parser (no host DOMDocument; #19048 AOT).
*/
final class VmXmlrpc
{
+ /** Struct key used by {@see setType()} / {@see getType()} / encode for base64|datetime. */
+ public const TYPED_VALUE_KEY = '__xmlrpc_type__';
+
private static ?string $lastError = null;
public static function getLastError(): string
@@ -66,24 +69,271 @@ public static function decode(string $xml)
}
}
+ /**
+ * xmlrpc_encode_request() — methodCall / fault response XML (#22254).
+ *
+ * php-src: ext/xmlrpc/xmlrpc-epi-php.c — PHP_FUNCTION(xmlrpc_encode_request)
+ */
+ public static function encodeRequest(?string $method, Variable $params): string
+ {
+ self::$lastError = null;
+ $resolved = $params->resolveIndirect();
+ if (null === $method || '' === $method) {
+ if (Variable::TYPE_ARRAY === $resolved->type && self::isFaultArray($resolved->toArray())) {
+ return self::encodeFaultResponse($resolved);
+ }
+
+ return self::encodeMethodResponse($resolved);
+ }
+
+ $paramValues = self::requestParamList($resolved);
+ $out = ''."\n"
+ .''."\n"
+ .''.self::escapeXml($method).''."\n"
+ .''."\n";
+ foreach ($paramValues as $param) {
+ $out .= ''.self::encodeValue($param).''."\n";
+ }
+
+ return $out.''."\n".'';
+ }
+
+ /**
+ * xmlrpc_decode_request() — methodCall/methodResponse → PHP value (#22254).
+ *
+ * @param-out string $method
+ *
+ * @return mixed|false
+ */
+ public static function decodeRequest(string $xml, ?string &$method)
+ {
+ self::$lastError = null;
+ $method = '';
+ $xml = trim($xml);
+ if ('' === $xml || !str_contains($xml, '<')) {
+ self::$lastError = 'Invalid XML';
+
+ return false;
+ }
+
+ $callInner = self::extractBalancedElementInner($xml, 'methodCall');
+ if (null !== $callInner) {
+ $nameInner = self::extractBalancedElementInner($callInner, 'methodName');
+ $method = null !== $nameInner ? trim($nameInner) : '';
+ $paramsInner = self::extractBalancedElementInner($callInner, 'params');
+ if (null === $paramsInner) {
+ return [];
+ }
+
+ return self::decodeParamsList($paramsInner);
+ }
+
+ $responseInner = self::extractBalancedElementInner($xml, 'methodResponse');
+ if (null !== $responseInner) {
+ $faultInner = self::extractBalancedElementInner($responseInner, 'fault');
+ if (null !== $faultInner) {
+ $valueInner = self::extractBalancedElementInner($faultInner, 'value');
+ if (null === $valueInner) {
+ self::$lastError = 'Invalid XML-RPC fault';
+
+ return false;
+ }
+
+ return self::decodeValueString($valueInner);
+ }
+ $paramsInner = self::extractBalancedElementInner($responseInner, 'params');
+ if (null !== $paramsInner) {
+ $list = self::decodeParamsList($paramsInner);
+
+ return 1 === \count($list) ? $list[0] : $list;
+ }
+ $valueInner = self::extractBalancedElementInner($responseInner, 'value');
+ if (null !== $valueInner) {
+ return self::decodeValueString($valueInner);
+ }
+ }
+
+ return self::decode($xml);
+ }
+
+ /**
+ * xmlrpc_is_fault() — faultCode + faultString array (#22254).
+ */
+ public static function isFault(Variable $arg): bool
+ {
+ $arg = $arg->resolveIndirect();
+ if (Variable::TYPE_ARRAY !== $arg->type) {
+ return false;
+ }
+
+ return self::isFaultArray($arg->toArray());
+ }
+
+ /**
+ * xmlrpc_get_type() — XML-RPC type name for a PHP value (#22254).
+ */
+ public static function getType(Variable $value): string
+ {
+ $value = $value->resolveIndirect();
+ if (Variable::TYPE_OBJECT === $value->type) {
+ $object = $value->toObject();
+ if ($object->hasProperty('xmlrpc_type')) {
+ $typeProp = $object->getProperty('xmlrpc_type')->resolveIndirect();
+ if (Variable::TYPE_STRING === $typeProp->type) {
+ $typed = strtolower($typeProp->toString(null));
+ if ('base64' === $typed || 'datetime' === $typed) {
+ return $typed;
+ }
+ }
+ }
+
+ return 'struct';
+ }
+ if (Variable::TYPE_ARRAY === $value->type) {
+ $typed = self::typedArrayKind($value->toArray());
+ if (null !== $typed) {
+ return $typed;
+ }
+
+ return $value->toArray()->isPackedList() ? 'array' : 'struct';
+ }
+
+ return match ($value->type) {
+ Variable::TYPE_NULL => 'null',
+ Variable::TYPE_BOOLEAN => 'boolean',
+ Variable::TYPE_INTEGER => 'int',
+ Variable::TYPE_FLOAT => 'double',
+ Variable::TYPE_STRING => 'string',
+ default => 'unknown',
+ };
+ }
+
+ /**
+ * xmlrpc_set_type() — mark string as base64/datetime for encode (#22254).
+ *
+ * Stored as a struct array with {@see TYPED_VALUE_KEY} so by-ref call sites keep
+ * the payload (stdClass dynamic props were cleared across builtin return).
+ */
+ public static function setType(Variable $valueSlot, string $type): bool
+ {
+ $type = strtolower(trim($type));
+ if ('datetime.iso8601' === $type) {
+ $type = 'datetime';
+ }
+ if ('base64' !== $type && 'datetime' !== $type) {
+ return false;
+ }
+
+ $resolved = $valueSlot->resolveIndirect();
+ $scalar = match ($resolved->type) {
+ Variable::TYPE_STRING => $resolved->toString(null),
+ Variable::TYPE_INTEGER => (string) $resolved->toInt(null),
+ Variable::TYPE_FLOAT => self::formatDouble($resolved->toFloat(null)),
+ Variable::TYPE_BOOLEAN => $resolved->toBool(null) ? '1' : '0',
+ Variable::TYPE_NULL => '',
+ default => (string) $resolved->toString(null),
+ };
+
+ $ht = new HashTable();
+ $typeVar = new Variable();
+ $typeVar->string($type);
+ $ht->add(self::TYPED_VALUE_KEY, $typeVar);
+ $scalarVar = new Variable();
+ $scalarVar->string($scalar);
+ $ht->add('scalar', $scalarVar);
+ if ('datetime' === $type) {
+ $ts = strtotime($scalar);
+ $tsVar = new Variable();
+ $tsVar->int(false === $ts ? 0 : $ts);
+ $ht->add('timestamp', $tsVar);
+ }
+ $resolved->array($ht);
+
+ return true;
+ }
+
private static function encodeValue(Variable $value): string
{
- switch ($value->type) {
- case Variable::TYPE_NULL:
- return '';
- case Variable::TYPE_BOOLEAN:
- return ''.($value->toBool(null) ? '1' : '0').'';
- case Variable::TYPE_INTEGER:
- return ''.$value->toInt(null).'';
- case Variable::TYPE_FLOAT:
- return ''.self::formatDouble($value->toFloat(null)).'';
- case Variable::TYPE_STRING:
- return ''.self::escapeXml($value->toString(null)).'';
- case Variable::TYPE_ARRAY:
- return self::encodeArray($value->toArray());
- default:
- throw new \Exception('Cannot xmlrpc_encode() value of type '.$value->type);
+ $value = $value->resolveIndirect();
+ if (Variable::TYPE_OBJECT === $value->type) {
+ $object = $value->toObject();
+ if ($object->hasProperty('xmlrpc_type')) {
+ $typeProp = $object->getProperty('xmlrpc_type')->resolveIndirect();
+ if (Variable::TYPE_STRING === $typeProp->type) {
+ $typed = strtolower($typeProp->toString(null));
+ $scalar = '';
+ if ($object->hasProperty('scalar')) {
+ $scalarProp = $object->getProperty('scalar')->resolveIndirect();
+ if (Variable::TYPE_STRING === $scalarProp->type) {
+ $scalar = $scalarProp->toString(null);
+ }
+ }
+ if ('base64' === $typed) {
+ return ''.base64_encode($scalar).'';
+ }
+ if ('datetime' === $typed) {
+ return ''.self::escapeXml($scalar).'';
+ }
+ }
+ }
+
+ throw new \Exception('Cannot xmlrpc_encode() value of type object');
+ }
+ if (Variable::TYPE_ARRAY === $value->type) {
+ $table = $value->toArray();
+ $typed = self::typedArrayKind($table);
+ if (null !== $typed) {
+ $scalar = self::typedArrayScalar($table);
+ if ('base64' === $typed) {
+ return ''.base64_encode($scalar).'';
+ }
+
+ return ''.self::escapeXml($scalar).'';
+ }
+
+ return self::encodeArray($table);
}
+
+ return match ($value->type) {
+ Variable::TYPE_NULL => '',
+ Variable::TYPE_BOOLEAN => ''.($value->toBool(null) ? '1' : '0').'',
+ Variable::TYPE_INTEGER => ''.$value->toInt(null).'',
+ Variable::TYPE_FLOAT => ''.self::formatDouble($value->toFloat(null)).'',
+ Variable::TYPE_STRING => ''.self::escapeXml($value->toString(null)).'',
+ default => throw new \Exception('Cannot xmlrpc_encode() value of type '.$value->type),
+ };
+ }
+
+ private static function typedArrayKind(HashTable $table): ?string
+ {
+ foreach (iterator_to_array($table->iterateKeyed(true), false) as [$key, $element]) {
+ if (self::TYPED_VALUE_KEY !== self::arrayKeyToString($key->resolveIndirect())) {
+ continue;
+ }
+ $element = $element->resolveIndirect();
+ if (Variable::TYPE_STRING !== $element->type) {
+ return null;
+ }
+ $typed = strtolower($element->toString(null));
+
+ return ('base64' === $typed || 'datetime' === $typed) ? $typed : null;
+ }
+
+ return null;
+ }
+
+ private static function typedArrayScalar(HashTable $table): string
+ {
+ foreach (iterator_to_array($table->iterateKeyed(true), false) as [$key, $element]) {
+ if ('scalar' !== self::arrayKeyToString($key->resolveIndirect())) {
+ continue;
+ }
+ $element = $element->resolveIndirect();
+
+ return Variable::TYPE_STRING === $element->type ? $element->toString(null) : '';
+ }
+
+ return '';
}
private static function encodeArray(HashTable $table): string
@@ -108,6 +358,93 @@ private static function encodeArray(HashTable $table): string
return $out.'';
}
+ /**
+ * @return list
+ */
+ private static function requestParamList(Variable $params): array
+ {
+ if (Variable::TYPE_ARRAY !== $params->type) {
+ return [$params];
+ }
+ $table = $params->toArray();
+ if (!$table->isPackedList()) {
+ return [$params];
+ }
+ $out = [];
+ foreach (iterator_to_array($table->iterateKeyed(true), false) as [, $element]) {
+ $out[] = $element;
+ }
+
+ return $out;
+ }
+
+ private static function encodeMethodResponse(Variable $value): string
+ {
+ return ''."\n"
+ .''."\n"
+ .''."\n"
+ .''.self::encodeValue($value).''."\n"
+ .''."\n"
+ .'';
+ }
+
+ private static function encodeFaultResponse(Variable $fault): string
+ {
+ return ''."\n"
+ .''."\n"
+ .''."\n"
+ .''.self::encodeValue($fault).''."\n"
+ .''."\n"
+ .'';
+ }
+
+ private static function isFaultArray(HashTable $table): bool
+ {
+ $hasCode = false;
+ $hasString = false;
+ foreach (iterator_to_array($table->iterateKeyed(true), false) as [$key, $element]) {
+ $name = strtolower(self::arrayKeyToString($key->resolveIndirect()));
+ if ('faultcode' === $name) {
+ $hasCode = true;
+ } elseif ('faultstring' === $name) {
+ $hasString = true;
+ }
+ }
+
+ return $hasCode && $hasString;
+ }
+
+ /**
+ * @return list
+ */
+ private static function decodeParamsList(string $paramsInner): array
+ {
+ $out = [];
+ $pos = 0;
+ $len = \strlen($paramsInner);
+ while ($pos < $len) {
+ if (!preg_match('/]*)?>/i', $paramsInner, $match, PREG_OFFSET_CAPTURE, $pos)) {
+ break;
+ }
+ $sliceStart = $match[0][1];
+ $paramInner = self::extractBalancedElementInner(\substr($paramsInner, $sliceStart), 'param');
+ if (null === $paramInner) {
+ break;
+ }
+ $valueInner = self::extractBalancedElementInner($paramInner, 'value');
+ if (null !== $valueInner) {
+ $out[] = self::decodeValueString($valueInner);
+ }
+ $closePos = stripos($paramsInner, '', $sliceStart);
+ if (false === $closePos) {
+ break;
+ }
+ $pos = $closePos + 8;
+ }
+
+ return $out;
+ }
+
private static function arrayKeyToString(Variable $key): string
{
return match ($key->type) {
@@ -179,11 +516,11 @@ private static function decodeValueString(string $valueInner)
if ('<' !== $valueInner[0]) {
return $valueInner;
}
- if (!preg_match('/^<([a-zA-Z0-9]+)/', $valueInner, $tagMatch)) {
+ if (!preg_match('/^<([a-zA-Z0-9.]+)/', $valueInner, $tagMatch)) {
return $valueInner;
}
$tag = strtolower($tagMatch[1]);
- $typedInner = self::extractBalancedElementInner($valueInner, $tag);
+ $typedInner = self::extractBalancedElementInner($valueInner, $tagMatch[1]);
if (null === $typedInner) {
return trim($valueInner);
}
@@ -195,6 +532,7 @@ private static function decodeValueString(string $valueInner)
'double', 'float' => self::parseDouble($typedInner),
'string' => $typedInner,
'base64' => self::decodeBase64($typedInner),
+ 'datetime.iso8601' => $typedInner,
'array' => self::decodeArrayString($typedInner),
'struct' => self::decodeStructString($typedInner),
default => $typedInner,
diff --git a/ext/xmlrpc/xmlrpc_decode_request.php b/ext/xmlrpc/xmlrpc_decode_request.php
new file mode 100644
index 00000000000..0c2b01312f7
--- /dev/null
+++ b/ext/xmlrpc/xmlrpc_decode_request.php
@@ -0,0 +1,57 @@
+calledArgs);
+ if ($argc < 2 || $argc > 3) {
+ throw new \ArgumentCountError(\sprintf(
+ 'xmlrpc_decode_request() expects 2 or 3 arguments, %d given',
+ $argc
+ ));
+ }
+ if (null === $frame->returnVar) {
+ return;
+ }
+ $xml = VmString::coerceStringBuiltinArg(
+ $frame->calledArgs[0],
+ 'xmlrpc_decode_request',
+ 0,
+ 'xml'
+ );
+ if (isset($frame->calledArgs[2])) {
+ VmString::coerceStringBuiltinArg(
+ $frame->calledArgs[2],
+ 'xmlrpc_decode_request',
+ 2,
+ 'encoding'
+ );
+ }
+ $method = '';
+ $decoded = VmXmlrpc::decodeRequest($xml, $method);
+ $methodSlot = $frame->calledArgs[1]->resolveIndirect();
+ $methodSlot->string($method);
+ if (false === $decoded) {
+ $frame->returnVar->bool(false);
+
+ return;
+ }
+ $frame->returnVar->copyFrom(VmJson::import($decoded));
+ }
+}
diff --git a/ext/xmlrpc/xmlrpc_encode_request.php b/ext/xmlrpc/xmlrpc_encode_request.php
new file mode 100644
index 00000000000..5ff0c9b8306
--- /dev/null
+++ b/ext/xmlrpc/xmlrpc_encode_request.php
@@ -0,0 +1,45 @@
+calledArgs);
+ if ($argc < 2 || $argc > 3) {
+ throw new \ArgumentCountError(\sprintf(
+ 'xmlrpc_encode_request() expects 2 or 3 arguments, %d given',
+ $argc
+ ));
+ }
+ if (null === $frame->returnVar) {
+ return;
+ }
+ $methodArg = $frame->calledArgs[0]->resolveIndirect();
+ $method = null;
+ if (Variable::TYPE_NULL !== $methodArg->type) {
+ $method = VmString::coerceStringBuiltinArg(
+ $frame->calledArgs[0],
+ 'xmlrpc_encode_request',
+ 0,
+ 'method'
+ );
+ }
+ // Optional $output_options accepted for signature parity; ignored in v1.
+ $xml = VmXmlrpc::encodeRequest($method, $frame->calledArgs[1]);
+ $frame->returnVar->string($xml);
+ }
+}
diff --git a/ext/xmlrpc/xmlrpc_get_type.php b/ext/xmlrpc/xmlrpc_get_type.php
new file mode 100644
index 00000000000..cfe17cd2755
--- /dev/null
+++ b/ext/xmlrpc/xmlrpc_get_type.php
@@ -0,0 +1,25 @@
+requireExactArgCount($frame, 'xmlrpc_get_type', 1);
+ if (null === $frame->returnVar) {
+ return;
+ }
+ $frame->returnVar->string(VmXmlrpc::getType($frame->calledArgs[0]));
+ }
+}
diff --git a/ext/xmlrpc/xmlrpc_is_fault.php b/ext/xmlrpc/xmlrpc_is_fault.php
new file mode 100644
index 00000000000..0a3a2070a7d
--- /dev/null
+++ b/ext/xmlrpc/xmlrpc_is_fault.php
@@ -0,0 +1,25 @@
+requireExactArgCount($frame, 'xmlrpc_is_fault', 1);
+ if (null === $frame->returnVar) {
+ return;
+ }
+ $frame->returnVar->bool(VmXmlrpc::isFault($frame->calledArgs[0]));
+ }
+}
diff --git a/ext/xmlrpc/xmlrpc_set_type.php b/ext/xmlrpc/xmlrpc_set_type.php
new file mode 100644
index 00000000000..e9193fe8cc3
--- /dev/null
+++ b/ext/xmlrpc/xmlrpc_set_type.php
@@ -0,0 +1,33 @@
+requireExactArgCount($frame, 'xmlrpc_set_type', 2);
+ $type = VmString::coerceStringBuiltinArg(
+ $frame->calledArgs[1],
+ 'xmlrpc_set_type',
+ 1,
+ 'type'
+ );
+ $ok = VmXmlrpc::setType($frame->calledArgs[0], $type);
+ if (null === $frame->returnVar) {
+ return;
+ }
+ $frame->returnVar->bool($ok);
+ }
+}
diff --git a/lib/BuiltinByRefParams.php b/lib/BuiltinByRefParams.php
index 8fa6ba4e2cf..7cfb3757d18 100644
--- a/lib/BuiltinByRefParams.php
+++ b/lib/BuiltinByRefParams.php
@@ -257,6 +257,12 @@ public static function forFunction(string $name): array
case 'curl_multi_info_read':
// php-src ext/curl/multi.c / curl.stub.php — &$queued_messages (#20495)
return [1];
+ case 'xmlrpc_decode_request':
+ // php-src ext/xmlrpc — &$method (#22254)
+ return [1];
+ case 'xmlrpc_set_type':
+ // php-src ext/xmlrpc — &$value (#22254)
+ return [0];
case 'enchant_dict_quick_check':
// php-src ext/enchant/enchant.stub.php — &$suggestions (#20613)
return [2];
diff --git a/test/compliance/cases/stdlib/xmlrpc_request_type_helpers.phpt b/test/compliance/cases/stdlib/xmlrpc_request_type_helpers.phpt
new file mode 100644
index 00000000000..5be413412cd
--- /dev/null
+++ b/test/compliance/cases/stdlib/xmlrpc_request_type_helpers.phpt
@@ -0,0 +1,52 @@
+--TEST--
+stdlib xmlrpc encode_request/decode_request/is_fault/get_type/set_type (#22254, ext/xmlrpc)
+--SKIPIF--
+ 1, 'faultString' => 'boom'];
+echo 'is_fault=', xmlrpc_is_fault($fault) ? '1' : '0', "\n";
+echo 'not_fault=', xmlrpc_is_fault(['x' => 1]) ? '1' : '0', "\n";
+
+echo 'type_int=', xmlrpc_get_type(42), "\n";
+echo 'type_arr=', xmlrpc_get_type([1, 2]), "\n";
+echo 'type_struct=', xmlrpc_get_type(['a' => 1]), "\n";
+
+$bin = 'hi';
+echo 'set=', xmlrpc_set_type($bin, 'base64') ? '1' : '0', "\n";
+echo 'type_b64=', xmlrpc_get_type($bin), "\n";
+$enc = xmlrpc_encode($bin);
+echo 'b64_xml=', str_contains($enc, '') ? '1' : '0', "\n";
+--EXPECT--
+xmlrpc_encode_request=1
+xmlrpc_decode_request=1
+xmlrpc_is_fault=1
+xmlrpc_get_type=1
+xmlrpc_set_type=1
+method=demo.add
+params=ok
+is_fault=1
+not_fault=0
+type_int=int
+type_arr=array
+type_struct=struct
+set=1
+type_b64=base64
+b64_xml=1
diff --git a/test/repro/issue_22254_xmlrpc_request_types.php b/test/repro/issue_22254_xmlrpc_request_types.php
new file mode 100644
index 00000000000..7ff19bb1b5c
--- /dev/null
+++ b/test/repro/issue_22254_xmlrpc_request_types.php
@@ -0,0 +1,14 @@
+ 1, 'faultString' => 'x']) ? '1' : '0', PHP_EOL;
diff --git a/test/unit/XmlrpcModuleTest.php b/test/unit/XmlrpcModuleTest.php
index e92c8605bde..43d52e25c37 100644
--- a/test/unit/XmlrpcModuleTest.php
+++ b/test/unit/XmlrpcModuleTest.php
@@ -8,7 +8,7 @@
use PHPUnit\Framework\TestCase;
/**
- * xmlrpc extension encode/decode (issue #6579, #18503).
+ * xmlrpc extension encode/decode + request/type helpers (issue #6579, #18503, #22254).
*
* @group xmlrpc
*/
@@ -21,7 +21,15 @@ public function test_xmlrpc_withheld_on_reference_profile(): void
$runtime = new Runtime();
$ctx = $runtime->vmContext;
- foreach (['xmlrpc_encode', 'xmlrpc_decode'] as $fn) {
+ foreach ([
+ 'xmlrpc_encode',
+ 'xmlrpc_decode',
+ 'xmlrpc_encode_request',
+ 'xmlrpc_decode_request',
+ 'xmlrpc_is_fault',
+ 'xmlrpc_get_type',
+ 'xmlrpc_set_type',
+ ] as $fn) {
self::assertFalse(VmReflection::functionExists($ctx, $fn), $fn);
}
@@ -29,12 +37,13 @@ public function test_xmlrpc_withheld_on_reference_profile(): void
parseAndCompile($code, 'xmlrpc_module.php');
ob_start();
$runtime->run($block);
- self::assertSame('000', ob_get_clean());
+ self::assertSame('0000', ob_get_clean());
}
public function test_xmlrpc_registered_and_extension_loaded_on_forward_profile(): void
@@ -47,7 +56,15 @@ public function test_xmlrpc_registered_and_extension_loaded_on_forward_profile()
$runtime = new Runtime();
$ctx = $runtime->vmContext;
- foreach (['xmlrpc_encode', 'xmlrpc_decode'] as $fn) {
+ foreach ([
+ 'xmlrpc_encode',
+ 'xmlrpc_decode',
+ 'xmlrpc_encode_request',
+ 'xmlrpc_decode_request',
+ 'xmlrpc_is_fault',
+ 'xmlrpc_get_type',
+ 'xmlrpc_set_type',
+ ] as $fn) {
self::assertTrue(VmReflection::functionExists($ctx, $fn), $fn);
}
@@ -55,12 +72,13 @@ public function test_xmlrpc_registered_and_extension_loaded_on_forward_profile()
parseAndCompile($code, 'xmlrpc_module.php');
ob_start();
$runtime->run($block);
- self::assertSame('111', ob_get_clean());
+ self::assertSame('1111', ob_get_clean());
} finally {
if (false === $prev) {
putenv('PHP_COMPILER_PROFILE');
@@ -96,4 +114,28 @@ public function test_xmlrpc_struct_and_scalar_roundtrip_on_forward_profile(): vo
}
}
}
+
+ public function test_xmlrpc_request_type_helpers_roundtrip_on_forward_profile(): void
+ {
+ $prev = getenv('PHP_COMPILER_PROFILE');
+ putenv('PHP_COMPILER_PROFILE=8.4');
+ try {
+ $runtime = new Runtime();
+ $code = file_get_contents(__DIR__.'/../repro/issue_22254_xmlrpc_request_types.php');
+ $block = $runtime->parseAndCompile($code, 'issue_22254_xmlrpc_request_types.php');
+ ob_start();
+ $runtime->run($block);
+ $out = ob_get_clean();
+ self::assertStringContainsString('xmlrpc_encode_request=true', $out);
+ self::assertStringContainsString('method=demo.add', $out);
+ self::assertStringContainsString('params_ok=1', $out);
+ self::assertStringContainsString('is_fault=1', $out);
+ } finally {
+ if (false === $prev) {
+ putenv('PHP_COMPILER_PROFILE');
+ } else {
+ putenv('PHP_COMPILER_PROFILE='.$prev);
+ }
+ }
+ }
}