├── COPYRIGHT.md ├── src ├── Exception │ ├── ExceptionInterface.php │ ├── RuntimeException.php │ ├── BadMethodCallException.php │ ├── InvalidArgumentException.php │ └── ValueException.php ├── Client │ ├── Exception │ │ ├── RuntimeException.php │ │ ├── InvalidArgumentException.php │ │ ├── HttpException.php │ │ ├── IntrospectException.php │ │ ├── ExceptionInterface.php │ │ └── FaultException.php │ ├── ServerProxy.php │ └── ServerIntrospection.php ├── Server │ ├── Exception │ │ ├── RuntimeException.php │ │ ├── BadMethodCallException.php │ │ ├── InvalidArgumentException.php │ │ └── ExceptionInterface.php │ ├── Cache.php │ ├── System.php │ └── Fault.php ├── Value │ ├── Nil.php │ ├── BigInteger.php │ ├── AbstractScalar.php │ ├── Text.php │ ├── Double.php │ ├── Integer.php │ ├── Boolean.php │ ├── ArrayValue.php │ ├── Base64.php │ ├── Struct.php │ ├── AbstractCollection.php │ └── DateTime.php ├── Response │ └── Http.php ├── Generator │ ├── GeneratorInterface.php │ ├── XmlWriter.php │ ├── DomDocument.php │ └── AbstractGenerator.php ├── Request │ ├── Stdin.php │ └── Http.php ├── Response.php ├── Fault.php ├── Client.php ├── Request.php ├── AbstractValue.php └── Server.php ├── LICENSE.md ├── composer.json └── README.md /COPYRIGHT.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2020 Laminas Project a Series of LF Projects, LLC. (https://getlaminas.org/) 2 | -------------------------------------------------------------------------------- /src/Exception/ExceptionInterface.php: -------------------------------------------------------------------------------- 1 | type = self::XMLRPC_TYPE_NIL; 13 | $this->value = null; 14 | } 15 | 16 | /** 17 | * Return the value of this object, convert the XML-RPC native nill value into a PHP NULL 18 | */ 19 | public function getValue(): void 20 | { 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/Value/BigInteger.php: -------------------------------------------------------------------------------- 1 | value = (string) BigIntegerMath::of($value); 15 | $this->type = self::XMLRPC_TYPE_I8; 16 | } 17 | 18 | /** 19 | * Return bigint value object 20 | * 21 | * @return string 22 | */ 23 | public function getValue() 24 | { 25 | return $this->value; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/Value/AbstractScalar.php: -------------------------------------------------------------------------------- 1 | openElement('value') 20 | ->openElement($this->type, $this->value) 21 | ->closeElement($this->type) 22 | ->closeElement('value'); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/Response/Http.php: -------------------------------------------------------------------------------- 1 | getEncoding())); 23 | } 24 | 25 | return parent::__toString(); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/Value/Text.php: -------------------------------------------------------------------------------- 1 | type = self::XMLRPC_TYPE_STRING; 15 | 16 | // Make sure this value is string and all XML characters are encoded 17 | $this->value = (string) $value; 18 | } 19 | 20 | /** 21 | * Return the value of this object, convert the XML-RPC native string value into a PHP string 22 | * 23 | * @return string 24 | */ 25 | public function getValue() 26 | { 27 | return (string) $this->value; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/Value/Double.php: -------------------------------------------------------------------------------- 1 | type = self::XMLRPC_TYPE_DOUBLE; 19 | $precision = (int) ini_get('precision'); 20 | $formatString = '%1.' . $precision . 'F'; 21 | $this->value = rtrim(sprintf($formatString, (float) $value), '0'); 22 | } 23 | 24 | /** 25 | * Return the value of this object, convert the XML-RPC native double value into a PHP float 26 | * 27 | * @return float 28 | */ 29 | public function getValue() 30 | { 31 | return (float) $this->value; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/Value/Integer.php: -------------------------------------------------------------------------------- 1 | PHP_INT_MAX) { 20 | throw new Exception\ValueException('Overlong integer given'); 21 | } 22 | 23 | $this->type = self::XMLRPC_TYPE_INTEGER; 24 | $this->value = (int) $value; // Make sure this value is integer 25 | } 26 | 27 | /** 28 | * Return the value of this object, convert the XML-RPC native integer value into a PHP integer 29 | * 30 | * @return int 31 | */ 32 | public function getValue() 33 | { 34 | return $this->value; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/Generator/GeneratorInterface.php: -------------------------------------------------------------------------------- 1 | type = self::XMLRPC_TYPE_BOOLEAN; 20 | // Make sure the value is boolean and then convert it into an integer 21 | // The double conversion is because a bug in the LaminasOptimizer in PHP version 5.0.4 22 | $this->value = (int) filter_var($value, FILTER_VALIDATE_BOOLEAN); 23 | } 24 | 25 | /** 26 | * Return the value of this object, convert the XML-RPC native boolean value into a PHP boolean 27 | * 28 | * @return bool 29 | */ 30 | public function getValue() 31 | { 32 | return (bool) $this->value; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/Value/ArrayValue.php: -------------------------------------------------------------------------------- 1 | type = self::XMLRPC_TYPE_ARRAY; 17 | parent::__construct($value); 18 | } 19 | 20 | /** 21 | * Generate the XML code that represent an array native MXL-RPC value 22 | * 23 | * @return void 24 | */ 25 | protected function generate() 26 | { 27 | $generator = static::getGenerator(); 28 | $generator 29 | ->openElement('value') 30 | ->openElement('array') 31 | ->openElement('data'); 32 | 33 | if (is_array($this->value)) { 34 | foreach ($this->value as $val) { 35 | $val->generateXml(); 36 | } 37 | } 38 | 39 | $generator 40 | ->closeElement('data') 41 | ->closeElement('array') 42 | ->closeElement('value'); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/Value/Base64.php: -------------------------------------------------------------------------------- 1 | type = self::XMLRPC_TYPE_BASE64; 20 | 21 | $value = (string) $value; // Make sure this value is string 22 | if (! $alreadyEncoded) { 23 | $value = base64_encode($value); // We encode it in base64 24 | } 25 | $this->value = $value; 26 | } 27 | 28 | /** 29 | * Return the value of this object, convert the XML-RPC native base64 value into a PHP string 30 | * We return this value decoded (a normal string) 31 | * 32 | * @return string 33 | */ 34 | public function getValue() 35 | { 36 | return base64_decode($this->value); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/Value/Struct.php: -------------------------------------------------------------------------------- 1 | type = self::XMLRPC_TYPE_STRUCT; 17 | parent::__construct($value); 18 | } 19 | 20 | /** 21 | * Generate the XML code that represent struct native MXL-RPC value 22 | * 23 | * @return void 24 | */ 25 | protected function generate() 26 | { 27 | $generator = static::getGenerator(); 28 | $generator 29 | ->openElement('value') 30 | ->openElement('struct'); 31 | 32 | if (is_array($this->value)) { 33 | foreach ($this->value as $name => $val) { 34 | $generator 35 | ->openElement('member') 36 | ->openElement('name', $name) 37 | ->closeElement('name'); 38 | $val->generateXml(); 39 | $generator->closeElement('member'); 40 | } 41 | } 42 | 43 | $generator 44 | ->closeElement('struct') 45 | ->closeElement('value'); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/Value/AbstractCollection.php: -------------------------------------------------------------------------------- 1 | $value) { 18 | // If the elements of the given array are not Laminas\XmlRpc\Value objects, 19 | // we need to convert them as such (using auto-detection from PHP value) 20 | if (! $value instanceof parent) { 21 | $value = static::getXmlRpcValue($value, self::AUTO_DETECT_TYPE); 22 | } 23 | $this->value[$key] = $value; 24 | } 25 | } 26 | 27 | /** 28 | * Return the value of this object, convert the XML-RPC native collection values into a PHP array 29 | * 30 | * @return array 31 | */ 32 | public function getValue() 33 | { 34 | $values = (array) $this->value; 35 | foreach ($values as $key => $value) { 36 | $values[$key] = $value->getValue(); 37 | } 38 | return $values; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2020 Laminas Project a Series of LF Projects, LLC. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are met: 5 | 6 | - Redistributions of source code must retain the above copyright notice, this 7 | list of conditions and the following disclaimer. 8 | 9 | - Redistributions in binary form must reproduce the above copyright notice, 10 | this list of conditions and the following disclaimer in the documentation 11 | and/or other materials provided with the distribution. 12 | 13 | - Neither the name of Laminas Foundation nor the names of its contributors may 14 | be used to endorse or promote products derived from this software without 15 | specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 18 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 19 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 20 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 21 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 22 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 23 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 24 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 25 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 26 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 | -------------------------------------------------------------------------------- /src/Request/Stdin.php: -------------------------------------------------------------------------------- 1 | fault = new Fault(630); 41 | return; 42 | } 43 | 44 | $xml = ''; 45 | while (! feof($fh)) { 46 | $xml .= fgets($fh); 47 | } 48 | fclose($fh); 49 | 50 | $this->xml = $xml; 51 | 52 | $this->loadXml($xml); 53 | } 54 | 55 | /** 56 | * Retrieve the raw XML request 57 | * 58 | * @return string 59 | */ 60 | public function getRawRequest() 61 | { 62 | return $this->xml; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/Client/ServerProxy.php: -------------------------------------------------------------------------------- 1 | foo->bar->baz()". 13 | */ 14 | class ServerProxy 15 | { 16 | private XMLRPCClient $client; 17 | 18 | private string $namespace = ''; 19 | 20 | /** @var array of \Laminas\XmlRpc\Client\ServerProxy */ 21 | private array $cache = []; 22 | 23 | /** 24 | * @param string $namespace 25 | */ 26 | public function __construct(XMLRPCClient $client, $namespace = '') 27 | { 28 | $this->client = $client; 29 | $this->namespace = $namespace; 30 | } 31 | 32 | /** 33 | * Get the next successive namespace 34 | * 35 | * @param string $namespace 36 | * @return ServerProxy 37 | */ 38 | public function __get($namespace) 39 | { 40 | $namespace = ltrim("$this->namespace.$namespace", '.'); 41 | if (! isset($this->cache[$namespace])) { 42 | $this->cache[$namespace] = new $this($this->client, $namespace); 43 | } 44 | return $this->cache[$namespace]; 45 | } 46 | 47 | /** 48 | * Call a method in this namespace. 49 | * 50 | * @param string $method 51 | * @param array $args 52 | * @return mixed 53 | */ 54 | public function __call($method, $args) 55 | { 56 | $method = ltrim("{$this->namespace}.{$method}", '.'); 57 | return $this->client->call($method, $args); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/Generator/XmlWriter.php: -------------------------------------------------------------------------------- 1 | xmlWriter = new \XMLWriter(); 25 | $this->xmlWriter->openMemory(); 26 | $this->xmlWriter->startDocument('1.0', $this->encoding); 27 | } 28 | 29 | /** 30 | * Open a new XML element 31 | * 32 | * @param string $name XML element name 33 | * @return void 34 | */ 35 | protected function openXmlElement($name) 36 | { 37 | $this->xmlWriter->startElement($name); 38 | } 39 | 40 | /** 41 | * Write XML text data into the currently opened XML element 42 | * 43 | * @param string $text XML text data 44 | * @return void 45 | */ 46 | protected function writeTextData($text) 47 | { 48 | $this->xmlWriter->text($text); 49 | } 50 | 51 | /** 52 | * Close a previously opened XML element 53 | * 54 | * @param string $name 55 | * @return XmlWriter 56 | */ 57 | protected function closeXmlElement($name) 58 | { 59 | $this->xmlWriter->endElement(); 60 | 61 | return $this; 62 | } 63 | 64 | /** 65 | * Emit XML document 66 | * 67 | * @return string 68 | */ 69 | public function saveXml() 70 | { 71 | return $this->xmlWriter->flush(false); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/Generator/DomDocument.php: -------------------------------------------------------------------------------- 1 | dom->createElement($name); 27 | 28 | $this->currentElement = $this->currentElement->appendChild($newElement); 29 | } 30 | 31 | /** 32 | * Write XML text data into the currently opened XML element 33 | * 34 | * @param string $text 35 | */ 36 | protected function writeTextData($text) 37 | { 38 | $this->currentElement->appendChild($this->dom->createTextNode($text)); 39 | } 40 | 41 | /** 42 | * Close a previously opened XML element 43 | * 44 | * Resets $currentElement to the next parent node in the hierarchy 45 | * 46 | * @param string $name 47 | * @return void 48 | */ 49 | protected function closeXmlElement($name) 50 | { 51 | if (isset($this->currentElement->parentNode)) { 52 | $this->currentElement = $this->currentElement->parentNode; 53 | } 54 | } 55 | 56 | /** 57 | * Save XML as a string 58 | * 59 | * @return string 60 | */ 61 | public function saveXml() 62 | { 63 | return $this->dom->saveXml(); 64 | } 65 | 66 | /** 67 | * Initializes internal objects 68 | * 69 | * @return void 70 | */ 71 | protected function init() 72 | { 73 | $this->dom = new \DOMDocument('1.0', $this->encoding); 74 | $this->currentElement = $this->dom; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/Value/DateTime.php: -------------------------------------------------------------------------------- 1 | type = self::XMLRPC_TYPE_DATETIME; 38 | 39 | if ($value instanceof \DateTime) { 40 | $this->value = $value->format($this->phpFormatString); 41 | } elseif (is_numeric($value)) { // The value is numeric, we make sure it is an integer 42 | $this->value = date($this->phpFormatString, (int) $value); 43 | } else { 44 | try { 45 | $dateTime = new \DateTime($value); 46 | } catch (\Exception $e) { 47 | throw new Exception\ValueException($e->getMessage(), $e->getCode(), $e); 48 | } 49 | 50 | $this->value = $dateTime->format($this->phpFormatString); // Convert the DateTime to iso8601 format 51 | } 52 | } 53 | 54 | /** 55 | * Return the value of this object as iso8601 dateTime value 56 | * 57 | * @return string Formatted datetime 58 | */ 59 | public function getValue() 60 | { 61 | return $this->value; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "laminas/laminas-xmlrpc", 3 | "description": "Fully-featured XML-RPC server and client implementations", 4 | "license": "BSD-3-Clause", 5 | "keywords": [ 6 | "laminas", 7 | "xmlrpc" 8 | ], 9 | "homepage": "https://laminas.dev", 10 | "support": { 11 | "docs": "https://docs.laminas.dev/laminas-xmlrpc/", 12 | "issues": "https://github.com/laminas/laminas-xmlrpc/issues", 13 | "source": "https://github.com/laminas/laminas-xmlrpc", 14 | "rss": "https://github.com/laminas/laminas-xmlrpc/releases.atom", 15 | "chat": "https://laminas.dev/chat", 16 | "forum": "https://discourse.laminas.dev" 17 | }, 18 | "config": { 19 | "sort-packages": true, 20 | "platform": { 21 | "php": "8.1.99" 22 | }, 23 | "allow-plugins": { 24 | "dealerdirect/phpcodesniffer-composer-installer": true 25 | } 26 | }, 27 | "extra": { 28 | }, 29 | "require": { 30 | "php": "~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0", 31 | "ext-simplexml": "*", 32 | "brick/math": "^0.13.1", 33 | "laminas/laminas-code": "^4.4", 34 | "laminas/laminas-http": "^2.15", 35 | "laminas/laminas-server": "^2.11", 36 | "laminas/laminas-stdlib": "^3.10.1", 37 | "laminas/laminas-xml": "^1.4.0" 38 | }, 39 | "require-dev": { 40 | "laminas/laminas-coding-standard": "~3.1.0", 41 | "phpunit/phpunit": "^10.5.36", 42 | "psalm/plugin-phpunit": "^0.19.0", 43 | "vimeo/psalm": "^5.26.1" 44 | }, 45 | "suggest": { 46 | "laminas/laminas-cache": "To support Laminas\\XmlRpc\\Server\\Cache usage" 47 | }, 48 | "autoload": { 49 | "psr-4": { 50 | "Laminas\\XmlRpc\\": "src/" 51 | } 52 | }, 53 | "autoload-dev": { 54 | "psr-4": { 55 | "LaminasTest\\XmlRpc\\": "test/" 56 | }, 57 | "files": [ 58 | "test/TestAsset/functions.php" 59 | ] 60 | }, 61 | "scripts": { 62 | "check": [ 63 | "@cs-check", 64 | "@test" 65 | ], 66 | "cs-check": "phpcs", 67 | "cs-fix": "phpcbf", 68 | "test": "phpunit --colors=always", 69 | "test-coverage": "phpunit --colors=always --coverage-clover clover.xml", 70 | "static-analysis": "psalm --shepherd --stats" 71 | }, 72 | "conflict": { 73 | "zendframework/zend-xmlrpc": "*" 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # laminas-xmlrpc 2 | 3 | [![Build Status](https://github.com/laminas/laminas-xmlrpc/actions/workflows/continuous-integration.yml/badge.svg)](https://github.com/laminas/laminas-xmlrpc/actions/workflows/continuous-integration.yml) 4 | [![type-coverage](https://shepherd.dev/github/laminas/laminas-xmlrpc/coverage.svg)](https://shepherd.dev/github/laminas/laminas-xmlrpc) 5 | [![Psalm level](https://shepherd.dev/github/laminas/laminas-xmlrpc/level.svg)](https://shepherd.dev/github/laminas/laminas-xmlrpc) 6 | 7 | > ## 🇷🇺 Русским гражданам 8 | > 9 | > Мы, участники Laminas, родились и живем в разных странах. У многих из нас есть друзья, родственники и коллеги как в России, так и в Украине. Некоторые из нас родились в России. Некоторые из нас живут в России. У некоторых бабушки и дедушки сражались с фашистами во Второй мировой войне. Здесь никто не поддерживает фашизм. 10 | > 11 | > У одного из нас есть украинская родственница, которая спаслась из дома вместе с сыном. Поезд задержался из-за бомбежки на дороге впереди. У нас есть друзья, которые прячутся в бомбоубежищах. Мы с тревогой ждем весточки от них после воздушных налетов, которые беспорядочно наносят удары и попадают по больницам, школам, детским садам и домам. Мы не берем это из каких-либо СМИ. Мы наблюдаем это напрямую. 12 | > 13 | > Вы доверяете нам достаточно, чтоб использовать наши программы, и мы просим вас довериться нам вновь. Мы нуждаемся в помощи. Выходите и протестуйте против этой бесполезной войны. Остановите кровопролитие. Скажите "Нет войне!" 14 | > 15 | > ## 🇺🇸 To Citizens of Russia 16 | > 17 | > We at Laminas come from all over the world. Many of us have friends, family and colleagues in both Russia and Ukraine. Some of us were born in Russia. Some of us currently live in Russia. Some have grandparents who fought Nazis in World War II. Nobody here supports fascism. 18 | > 19 | > One team member has a Ukrainian relative who fled her home with her son. The train was delayed due to bombing on the road ahead. We have friends who are hiding in bomb shelters. We anxiously follow up on them after the air raids, which indiscriminately fire at hospitals, schools, kindergartens and houses. We're not taking this from any media. These are our actual experiences. 20 | > 21 | > You trust us enough to use our software. We ask that you trust us to say the truth on this. We need your help. Go out and protest this unnecessary war. Stop the bloodshed. Say "stop the war!" 22 | 23 | From its home page, XML-RPC is described as a ”...remote procedure calling using 24 | HTTP as the transport and XML as the encoding. XML-RPC is designed to be as 25 | simple as possible, while allowing complex data structures to be transmitted, 26 | processed and returned.” 27 | 28 | `Laminas\XmlRpc` provides support for both consuming remote XML-RPC services and 29 | building new XML-RPC servers. 30 | 31 | - File issues at https://github.com/laminas/laminas-xmlrpc/issues 32 | - Documentation is at https://docs.laminas.dev/laminas-xmlrpc/ 33 | -------------------------------------------------------------------------------- /src/Request/Http.php: -------------------------------------------------------------------------------- 1 | fault = new Fault(630); 53 | return; 54 | } 55 | 56 | $this->xml = $xml; 57 | 58 | $this->loadXml($xml); 59 | } 60 | 61 | /** 62 | * Retrieve the raw XML request 63 | * 64 | * @return string 65 | */ 66 | public function getRawRequest() 67 | { 68 | return $this->xml; 69 | } 70 | 71 | /** 72 | * Get headers 73 | * 74 | * Gets all headers as key => value pairs and returns them. 75 | * 76 | * @return array 77 | */ 78 | public function getHeaders() 79 | { 80 | if (null === $this->headers) { 81 | $this->headers = []; 82 | foreach ($_SERVER as $key => $value) { 83 | if (str_starts_with($key, 'HTTP_')) { 84 | $header = str_replace( 85 | ' ', 86 | '-', 87 | ucwords(strtolower(str_replace('_', ' ', substr($key, 5)))) 88 | ); 89 | $this->headers[$header] = $value; 90 | } 91 | } 92 | } 93 | 94 | return $this->headers; 95 | } 96 | 97 | /** 98 | * Retrieve the full HTTP request, including headers and XML 99 | * 100 | * @return string 101 | */ 102 | public function getFullRequest() 103 | { 104 | $request = ''; 105 | foreach ($this->getHeaders() as $key => $value) { 106 | $request .= $key . ': ' . $value . "\n"; 107 | } 108 | 109 | $request .= $this->xml; 110 | 111 | return $request; 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /src/Generator/AbstractGenerator.php: -------------------------------------------------------------------------------- 1 | setEncoding($encoding); 27 | $this->init(); 28 | } 29 | 30 | /** 31 | * Initialize internal objects 32 | * 33 | * @return void 34 | */ 35 | abstract protected function init(); 36 | 37 | /** 38 | * Start XML element 39 | * 40 | * Method opens a new XML element with an element name and an optional value 41 | * 42 | * @param string $name XML tag name 43 | * @param string $value Optional value of the XML tag 44 | * @return AbstractGenerator Fluent interface 45 | */ 46 | public function openElement($name, $value = null) 47 | { 48 | $this->openXmlElement($name); 49 | if ($value !== null) { 50 | $this->writeTextData($value); 51 | } 52 | 53 | return $this; 54 | } 55 | 56 | /** 57 | * End of an XML element 58 | * 59 | * Method marks the end of an XML element 60 | * 61 | * @param string $name XML tag name 62 | * @return AbstractGenerator Fluent interface 63 | */ 64 | public function closeElement($name) 65 | { 66 | $this->closeXmlElement($name); 67 | 68 | return $this; 69 | } 70 | 71 | /** 72 | * Return encoding 73 | * 74 | * @return string 75 | */ 76 | public function getEncoding() 77 | { 78 | return $this->encoding; 79 | } 80 | 81 | /** 82 | * Set XML encoding 83 | * 84 | * @param string $encoding 85 | * @return AbstractGenerator 86 | */ 87 | public function setEncoding($encoding) 88 | { 89 | $this->encoding = $encoding; 90 | return $this; 91 | } 92 | 93 | /** 94 | * Returns the XML as a string and flushes all internal buffers 95 | * 96 | * @return string 97 | */ 98 | public function flush() 99 | { 100 | $xml = $this->saveXml(); 101 | $this->init(); 102 | return $xml; 103 | } 104 | 105 | /** 106 | * Returns XML without document declaration 107 | */ 108 | public function __toString(): string 109 | { 110 | return $this->stripDeclaration($this->saveXml()); 111 | } 112 | 113 | /** 114 | * Removes XML declaration from a string 115 | * 116 | * @param string $xml 117 | * @return string 118 | */ 119 | public function stripDeclaration($xml) 120 | { 121 | return preg_replace('/<\?xml version="1.0"( encoding="[^\"]*")?\?>\n/u', '', $xml); 122 | } 123 | 124 | /** 125 | * Start XML element 126 | * 127 | * @param string $name XML element name 128 | */ 129 | abstract protected function openXmlElement($name); 130 | 131 | /** 132 | * Write XML text data into the currently opened XML element 133 | * 134 | * @param string $text 135 | */ 136 | abstract protected function writeTextData($text); 137 | 138 | /** 139 | * End XML element 140 | * 141 | * @param string $name 142 | */ 143 | abstract protected function closeXmlElement($name); 144 | } 145 | -------------------------------------------------------------------------------- /src/Client/ServerIntrospection.php: -------------------------------------------------------------------------------- 1 | system = $client->getProxy('system'); 21 | } 22 | 23 | /** 24 | * Returns the signature for each method on the server, 25 | * autodetecting whether system.multicall() is supported and 26 | * using it if so. 27 | * 28 | * @return array 29 | */ 30 | public function getSignatureForEachMethod() 31 | { 32 | $methods = $this->listMethods(); 33 | 34 | try { 35 | $signatures = $this->getSignatureForEachMethodByMulticall($methods); 36 | } catch (Exception\FaultException) { 37 | // degrade to looping 38 | } 39 | 40 | if (empty($signatures)) { 41 | $signatures = $this->getSignatureForEachMethodByLooping($methods); 42 | } 43 | 44 | return $signatures; 45 | } 46 | 47 | /** 48 | * Attempt to get the method signatures in one request via system.multicall(). 49 | * This is a boxcar feature of XML-RPC and is found on fewer servers. However, 50 | * can significantly improve performance if present. 51 | * 52 | * @param array $methods 53 | * @throws Exception\IntrospectException 54 | * @return array array(array(return, param, param, param...)) 55 | */ 56 | public function getSignatureForEachMethodByMulticall($methods = null) 57 | { 58 | if ($methods === null) { 59 | $methods = $this->listMethods(); 60 | } 61 | 62 | $multicallParams = []; 63 | foreach ($methods as $method) { 64 | $multicallParams[] = [ 65 | 'methodName' => 'system.methodSignature', 66 | 'params' => [$method], 67 | ]; 68 | } 69 | 70 | $serverSignatures = $this->system->multicall($multicallParams); 71 | 72 | if (! is_array($serverSignatures)) { 73 | $type = gettype($serverSignatures); 74 | $error = "Multicall return is malformed. Expected array, got $type"; 75 | throw new Exception\IntrospectException($error); 76 | } 77 | 78 | if (count($serverSignatures) !== count($methods)) { 79 | $error = 'Bad number of signatures received from multicall'; 80 | throw new Exception\IntrospectException($error); 81 | } 82 | 83 | // Create a new signatures array with the methods name as keys and the signature as value 84 | $signatures = []; 85 | foreach ($serverSignatures as $i => $signature) { 86 | $signatures[$methods[$i]] = $signature; 87 | } 88 | 89 | return $signatures; 90 | } 91 | 92 | /** 93 | * Get the method signatures for every method by 94 | * successively calling system.methodSignature 95 | * 96 | * @param array $methods 97 | * @return array 98 | */ 99 | public function getSignatureForEachMethodByLooping($methods = null) 100 | { 101 | if ($methods === null) { 102 | $methods = $this->listMethods(); 103 | } 104 | 105 | $signatures = []; 106 | foreach ($methods as $method) { 107 | $signatures[$method] = $this->getMethodSignature($method); 108 | } 109 | 110 | return $signatures; 111 | } 112 | 113 | /** 114 | * Call system.methodSignature() for the given method 115 | * 116 | * @param array $method 117 | * @throws Exception\IntrospectException 118 | * @return array array(array(return, param, param, param...)) 119 | */ 120 | public function getMethodSignature($method) 121 | { 122 | $signature = $this->system->methodSignature($method); 123 | if (! is_array($signature)) { 124 | $error = 'Invalid signature for method "' . $method . '"'; 125 | throw new Exception\IntrospectException($error); 126 | } 127 | return $signature; 128 | } 129 | 130 | /** 131 | * Call system.listMethods() 132 | * 133 | * @return array array(method, method, method...) 134 | */ 135 | public function listMethods() 136 | { 137 | return $this->system->listMethods(); 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /src/Server/System.php: -------------------------------------------------------------------------------- 1 | server = $server; 29 | } 30 | 31 | /** 32 | * List all available XMLRPC methods 33 | * 34 | * Returns an array of methods. 35 | * 36 | * @return array 37 | */ 38 | public function listMethods() 39 | { 40 | $table = $this->server->getDispatchTable()->getMethods(); 41 | return array_keys($table); 42 | } 43 | 44 | /** 45 | * Display help message for an XMLRPC method 46 | * 47 | * @param string $method 48 | * @throws InvalidArgumentException 49 | * @return string 50 | */ 51 | public function methodHelp($method) 52 | { 53 | $table = $this->server->getDispatchTable(); 54 | if (! $table->hasMethod($method)) { 55 | throw new InvalidArgumentException('Method "' . $method . '" does not exist', 640); 56 | } 57 | 58 | return $table->getMethod($method)->getMethodHelp(); 59 | } 60 | 61 | /** 62 | * Return a method signature 63 | * 64 | * @param string $method 65 | * @throws InvalidArgumentException 66 | * @return array 67 | */ 68 | public function methodSignature($method) 69 | { 70 | $table = $this->server->getDispatchTable(); 71 | if (! $table->hasMethod($method)) { 72 | throw new InvalidArgumentException('Method "' . $method . '" does not exist', 640); 73 | } 74 | $method = $table->getMethod($method)->toArray(); 75 | return $method['prototypes']; 76 | } 77 | 78 | /** 79 | * Multicall - boxcar feature of XML-RPC for calling multiple methods 80 | * in a single request. 81 | * 82 | * Expects an array of structs representing method calls, each element 83 | * having the keys: 84 | * - methodName 85 | * - params 86 | * 87 | * Returns an array of responses, one for each method called, with the value 88 | * returned by the method. If an error occurs for a given method, returns a 89 | * struct with a fault response. 90 | * 91 | * @see http://www.xmlrpc.com/discuss/msgReader$1208 92 | * 93 | * @param array $methods 94 | * @return array 95 | */ 96 | public function multicall($methods) 97 | { 98 | $responses = []; 99 | foreach ($methods as $method) { 100 | $fault = false; 101 | if (! is_array($method)) { 102 | $fault = $this->server->fault('system.multicall expects each method to be a struct', 601); 103 | } elseif (! isset($method['methodName'])) { 104 | $fault = $this->server->fault('Missing methodName: ' . var_export($methods, 1), 602); 105 | } elseif (! isset($method['params'])) { 106 | $fault = $this->server->fault('Missing params', 603); 107 | } elseif (! is_array($method['params'])) { 108 | $fault = $this->server->fault('Params must be an array', 604); 109 | } else { 110 | if ('system.multicall' === $method['methodName']) { 111 | // don't allow recursive calls to multicall 112 | $fault = $this->server->fault('Recursive system.multicall forbidden', 605); 113 | } 114 | } 115 | 116 | if (! $fault) { 117 | try { 118 | $request = new Request(); 119 | $request->setMethod($method['methodName']); 120 | $request->setParams($method['params']); 121 | $response = $this->server->handle($request); 122 | if ( 123 | $response instanceof Fault 124 | || $response->isFault() 125 | ) { 126 | $fault = $response; 127 | } else { 128 | $responses[] = $response->getReturnValue(); 129 | } 130 | } catch (Exception $e) { 131 | $fault = $this->server->fault($e); 132 | } 133 | } 134 | 135 | if ($fault) { 136 | $responses[] = [ 137 | 'faultCode' => $fault->getCode(), 138 | 'faultString' => $fault->getMessage(), 139 | ]; 140 | } 141 | } 142 | 143 | return $responses; 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /src/Server/Fault.php: -------------------------------------------------------------------------------- 1 | true]; 36 | 37 | /** @var array Array of fault observers */ 38 | protected static $observers = []; 39 | 40 | public function __construct(Exception $e) 41 | { 42 | $this->exception = $e; 43 | $code = 404; 44 | $message = 'Unknown error'; 45 | 46 | foreach (array_keys(static::$faultExceptionClasses) as $class) { 47 | if ($e instanceof $class) { 48 | $code = $e->getCode(); 49 | $message = $e->getMessage(); 50 | break; 51 | } 52 | } 53 | 54 | parent::__construct($code, $message); 55 | 56 | // Notify exception observers, if present 57 | if (! empty(static::$observers)) { 58 | foreach (array_keys(static::$observers) as $observer) { 59 | $observer::observe($this); 60 | } 61 | } 62 | } 63 | 64 | /** 65 | * Return Laminas\XmlRpc\Server\Fault instance 66 | * 67 | * @return Fault 68 | */ 69 | public static function getInstance(Exception $e) 70 | { 71 | return new static($e); 72 | } 73 | 74 | /** 75 | * Attach valid exceptions that can be used to define xmlrpc faults 76 | * 77 | * @param string|array $classes Class name or array of class names 78 | * @return void 79 | */ 80 | public static function attachFaultException($classes) 81 | { 82 | if (! is_array($classes)) { 83 | $classes = (array) $classes; 84 | } 85 | 86 | foreach ($classes as $class) { 87 | if (is_string($class) && class_exists($class)) { 88 | static::$faultExceptionClasses[$class] = true; 89 | } 90 | } 91 | } 92 | 93 | /** 94 | * Detach fault exception classes 95 | * 96 | * @param string|array $classes Class name or array of class names 97 | * @return void 98 | */ 99 | public static function detachFaultException($classes) 100 | { 101 | if (! is_array($classes)) { 102 | $classes = (array) $classes; 103 | } 104 | 105 | foreach ($classes as $class) { 106 | if (is_string($class) && isset(static::$faultExceptionClasses[$class])) { 107 | unset(static::$faultExceptionClasses[$class]); 108 | } 109 | } 110 | } 111 | 112 | /** 113 | * Attach an observer class 114 | * 115 | * Allows observation of xmlrpc server faults, thus allowing logging or mail 116 | * notification of fault responses on the xmlrpc server. 117 | * 118 | * Expects a valid class name; that class must have a public static method 119 | * 'observe' that accepts an exception as its sole argument. 120 | * 121 | * @param string $class 122 | * @return bool 123 | */ 124 | public static function attachObserver($class) 125 | { 126 | if (! is_string($class) || ! class_exists($class) || ! is_callable([$class, 'observe'])) { 127 | return false; 128 | } 129 | 130 | if (! isset(static::$observers[$class])) { 131 | static::$observers[$class] = true; 132 | } 133 | 134 | return true; 135 | } 136 | 137 | /** 138 | * Detach an observer 139 | * 140 | * @param string $class 141 | * @return bool 142 | */ 143 | public static function detachObserver($class) 144 | { 145 | if (! isset(static::$observers[$class])) { 146 | return false; 147 | } 148 | 149 | unset(static::$observers[$class]); 150 | return true; 151 | } 152 | 153 | /** 154 | * Retrieve the exception 155 | * 156 | * @access public 157 | * @return Exception 158 | */ 159 | public function getException() 160 | { 161 | return $this->exception; 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /src/Response.php: -------------------------------------------------------------------------------- 1 | setReturnValue($return, $type); 59 | } 60 | 61 | /** 62 | * Set encoding to use in response 63 | * 64 | * @param string $encoding 65 | * @return Response 66 | */ 67 | public function setEncoding($encoding) 68 | { 69 | $this->encoding = $encoding; 70 | AbstractValue::setEncoding($encoding); 71 | return $this; 72 | } 73 | 74 | /** 75 | * Retrieve current response encoding 76 | * 77 | * @return string 78 | */ 79 | public function getEncoding() 80 | { 81 | return $this->encoding; 82 | } 83 | 84 | /** 85 | * Set the return value 86 | * 87 | * Sets the return value, with optional type hinting if provided. 88 | * 89 | * @param mixed $value 90 | * @param string $type 91 | * @return void 92 | */ 93 | public function setReturnValue($value, $type = null) 94 | { 95 | $this->return = $value; 96 | $this->type = (string) $type; 97 | } 98 | 99 | /** 100 | * Retrieve the return value 101 | * 102 | * @return mixed 103 | */ 104 | public function getReturnValue() 105 | { 106 | return $this->return; 107 | } 108 | 109 | /** 110 | * Retrieve the XMLRPC value for the return value 111 | * 112 | * @return AbstractValue 113 | */ 114 | protected function getXmlRpcReturn() 115 | { 116 | return AbstractValue::getXmlRpcValue($this->return); 117 | } 118 | 119 | /** 120 | * Is the response a fault response? 121 | * 122 | * @return bool 123 | */ 124 | public function isFault() 125 | { 126 | return $this->fault instanceof Fault; 127 | } 128 | 129 | /** 130 | * Returns the fault, if any. 131 | * 132 | * @return null|Fault 133 | */ 134 | public function getFault() 135 | { 136 | return $this->fault; 137 | } 138 | 139 | /** 140 | * Load a response from an XML response 141 | * 142 | * Attempts to load a response from an XMLRPC response, autodetecting if it 143 | * is a fault response. 144 | * 145 | * You may optionally pass a bitmask of LIBXML options via the 146 | * $libXmlOptions parameter; as an example, you might use LIBXML_PARSEHUGE. 147 | * See https://www.php.net/manual/en/libxml.constants.php for a full list. 148 | * 149 | * @param string $response 150 | * @param int $libXmlOptions Bitmask of LIBXML options to use for XML * operations 151 | * @throws Exception\ValueException If invalid XML. 152 | * @return bool True if a valid XMLRPC response, false if a fault 153 | * response or invalid input 154 | */ 155 | public function loadXml($response, int $libXmlOptions = 0) 156 | { 157 | if (! is_string($response)) { 158 | $this->fault = new Fault(650); 159 | $this->fault->setEncoding($this->getEncoding()); 160 | return false; 161 | } 162 | 163 | try { 164 | $xml = XmlSecurity::scan($response, null, $libXmlOptions); 165 | } catch (RuntimeException $e) { 166 | $this->fault = new Fault(651); 167 | $this->fault->setEncoding($this->getEncoding()); 168 | return false; 169 | } 170 | 171 | if (isset($xml->fault) && is_object($xml->fault)) { 172 | // fault response 173 | $this->fault = new Fault(); 174 | $this->fault->setEncoding($this->getEncoding()); 175 | $this->fault->loadXml($response); 176 | return false; 177 | } 178 | 179 | if (! isset($xml->params)) { 180 | // Invalid response 181 | $this->fault = new Fault(652); 182 | $this->fault->setEncoding($this->getEncoding()); 183 | return false; 184 | } 185 | 186 | try { 187 | if (! isset($xml->params->param, $xml->params->param->value)) { 188 | throw new Exception\ValueException('Missing XML-RPC value in XML'); 189 | } 190 | $valueXml = $xml->params->param->value->asXML(); 191 | $value = AbstractValue::getXmlRpcValue($valueXml, AbstractValue::XML_STRING, $libXmlOptions); 192 | } catch (Exception\ValueException) { 193 | $this->fault = new Fault(653); 194 | $this->fault->setEncoding($this->getEncoding()); 195 | return false; 196 | } 197 | 198 | $this->setReturnValue($value->getValue()); 199 | return true; 200 | } 201 | 202 | /** 203 | * Return response as XML 204 | * 205 | * @return string 206 | */ 207 | public function saveXml() 208 | { 209 | $value = $this->getXmlRpcReturn(); 210 | $generator = AbstractValue::getGenerator(); 211 | $generator->openElement('methodResponse') 212 | ->openElement('params') 213 | ->openElement('param'); 214 | $value->generateXml(); 215 | $generator->closeElement('param') 216 | ->closeElement('params') 217 | ->closeElement('methodResponse'); 218 | 219 | return $generator->flush(); 220 | } 221 | 222 | /** 223 | * Return XML response 224 | */ 225 | public function __toString(): string 226 | { 227 | return $this->saveXML(); 228 | } 229 | } 230 | -------------------------------------------------------------------------------- /src/Fault.php: -------------------------------------------------------------------------------- 1 | messages 50 | * 51 | * @var array 52 | */ 53 | protected $internal = [ 54 | 404 => 'Unknown Error', 55 | 56 | // 610 - 619 reflection errors 57 | 610 => 'Invalid method class', 58 | 611 => 'Unable to attach function or callback; not callable', 59 | 612 => 'Unable to load array; not an array', 60 | 613 => 'One or more method records are corrupt or otherwise unusable', 61 | 62 | // 620 - 629 dispatch errors 63 | 620 => 'Method does not exist', 64 | 621 => 'Error instantiating class to invoke method', 65 | 622 => 'Method missing implementation', 66 | 623 => 'Calling parameters do not match signature', 67 | 68 | // 630 - 639 request errors 69 | 630 => 'Unable to read request', 70 | 631 => 'Failed to parse request', 71 | 632 => 'Invalid request, no method passed; request must contain a \'methodName\' tag', 72 | 633 => 'Param must contain a value', 73 | 634 => 'Invalid method name', 74 | 635 => 'Invalid XML provided to request', 75 | 636 => 'Error creating xmlrpc value', 76 | 77 | // 640 - 649 system.* errors 78 | 640 => 'Method does not exist', 79 | 80 | // 650 - 659 response errors 81 | 650 => 'Invalid XML provided for response', 82 | 651 => 'Failed to parse response', 83 | 652 => 'Invalid response', 84 | 653 => 'Invalid XMLRPC value in response', 85 | ]; 86 | 87 | /** 88 | * Constructor 89 | * 90 | * @param int $code 91 | * @param string $message 92 | */ 93 | public function __construct($code = 404, $message = '') 94 | { 95 | $this->setCode($code); 96 | $code = $this->getCode(); 97 | 98 | if (empty($message) && isset($this->internal[$code])) { 99 | $message = $this->internal[$code]; 100 | } elseif (empty($message)) { 101 | $message = $this->internal[404]; 102 | } 103 | $this->setMessage($message); 104 | } 105 | 106 | /** 107 | * Set the fault code 108 | * 109 | * @param int $code 110 | * @return Fault 111 | */ 112 | public function setCode($code) 113 | { 114 | $this->code = (int) $code; 115 | return $this; 116 | } 117 | 118 | /** 119 | * Return fault code 120 | * 121 | * @return int 122 | */ 123 | public function getCode() 124 | { 125 | return $this->code; 126 | } 127 | 128 | /** 129 | * Retrieve fault message 130 | * 131 | * @param string $message 132 | * @return Fault 133 | */ 134 | public function setMessage($message) 135 | { 136 | $this->message = (string) $message; 137 | return $this; 138 | } 139 | 140 | /** 141 | * Retrieve fault message 142 | * 143 | * @return string 144 | */ 145 | public function getMessage() 146 | { 147 | return $this->message; 148 | } 149 | 150 | /** 151 | * Set encoding to use in fault response 152 | * 153 | * @param string $encoding 154 | * @return Fault 155 | */ 156 | public function setEncoding($encoding) 157 | { 158 | $this->encoding = $encoding; 159 | AbstractValue::setEncoding($encoding); 160 | return $this; 161 | } 162 | 163 | /** 164 | * Retrieve current fault encoding 165 | * 166 | * @return string 167 | */ 168 | public function getEncoding() 169 | { 170 | return $this->encoding; 171 | } 172 | 173 | /** 174 | * Load an XMLRPC fault from XML 175 | * 176 | * @param string $fault 177 | * @return bool Returns true if successfully loaded fault response, false 178 | * if response was not a fault response 179 | * @throws Exception\ExceptionInterface If no or faulty XML provided, or if fault 180 | * response does not contain either code or message. 181 | */ 182 | public function loadXml($fault) 183 | { 184 | if (! is_string($fault)) { 185 | throw new Exception\InvalidArgumentException('Invalid XML provided to fault'); 186 | } 187 | 188 | $xmlErrorsFlag = libxml_use_internal_errors(true); 189 | try { 190 | $xml = XmlSecurity::scan($fault); 191 | } catch (RuntimeException $e) { 192 | // Unsecure XML 193 | throw new Exception\RuntimeException('Failed to parse XML fault: ' . $e->getMessage(), 500, $e); 194 | } 195 | if (! $xml instanceof SimpleXMLElement) { 196 | $errors = libxml_get_errors(); 197 | $errors = array_reduce($errors, function ($result, $item) { 198 | if (empty($result)) { 199 | return $item->message; 200 | } 201 | return $result . '; ' . $item->message; 202 | }, ''); 203 | libxml_use_internal_errors($xmlErrorsFlag); 204 | throw new Exception\InvalidArgumentException('Failed to parse XML fault: ' . $errors, 500); 205 | } 206 | libxml_use_internal_errors($xmlErrorsFlag); 207 | 208 | // Check for fault 209 | if (! isset($xml->fault)) { 210 | // Not a fault 211 | return false; 212 | } 213 | 214 | if (! isset($xml->fault->value->struct)) { 215 | // not a proper fault 216 | throw new Exception\InvalidArgumentException('Invalid fault structure', 500); 217 | } 218 | 219 | $structXml = $xml->fault->value->asXML(); 220 | $struct = AbstractValue::getXmlRpcValue($structXml, AbstractValue::XML_STRING); 221 | $struct = $struct->getValue(); 222 | 223 | if (isset($struct['faultCode'])) { 224 | $code = $struct['faultCode']; 225 | } 226 | if (isset($struct['faultString'])) { 227 | $message = $struct['faultString']; 228 | } 229 | 230 | if (empty($code) && empty($message)) { 231 | throw new Exception\InvalidArgumentException('Fault code and string required'); 232 | } 233 | 234 | if (empty($code)) { 235 | $code = '404'; 236 | } 237 | 238 | if (empty($message)) { 239 | if (isset($this->internal[$code])) { 240 | $message = $this->internal[$code]; 241 | } else { 242 | $message = $this->internal[404]; 243 | } 244 | } 245 | 246 | $this->setCode($code); 247 | $this->setMessage($message); 248 | 249 | return true; 250 | } 251 | 252 | /** 253 | * Determine if an XML response is an XMLRPC fault 254 | * 255 | * @param string $xml 256 | * @return bool 257 | */ 258 | public static function isFault($xml) 259 | { 260 | $fault = new static(); 261 | try { 262 | $isFault = $fault->loadXml($xml); 263 | } catch (Exception\ExceptionInterface) { 264 | $isFault = false; 265 | } 266 | 267 | return $isFault; 268 | } 269 | 270 | /** 271 | * Serialize fault to XML 272 | * 273 | * @return string 274 | */ 275 | public function saveXml() 276 | { 277 | // Create fault value 278 | $faultStruct = [ 279 | 'faultCode' => $this->getCode(), 280 | 'faultString' => $this->getMessage(), 281 | ]; 282 | $value = AbstractValue::getXmlRpcValue($faultStruct); 283 | 284 | $generator = AbstractValue::getGenerator(); 285 | $generator->openElement('methodResponse') 286 | ->openElement('fault'); 287 | $value->generateXml(); 288 | $generator->closeElement('fault') 289 | ->closeElement('methodResponse'); 290 | 291 | return $generator->flush(); 292 | } 293 | 294 | /** 295 | * Return XML fault response 296 | */ 297 | public function __toString(): string 298 | { 299 | return $this->saveXML(); 300 | } 301 | } 302 | -------------------------------------------------------------------------------- /src/Client.php: -------------------------------------------------------------------------------- 1 | httpClient = new Http\Client(); 94 | } else { 95 | $this->httpClient = $httpClient; 96 | } 97 | 98 | $this->introspector = new ServerIntrospection($this); 99 | $this->serverAddress = $server; 100 | } 101 | 102 | /** 103 | * Sets the HTTP client object to use for connecting the XML-RPC server. 104 | * 105 | * @return \Laminas\Http\Client 106 | */ 107 | public function setHttpClient(Http\Client $httpClient) 108 | { 109 | return $this->httpClient = $httpClient; 110 | } 111 | 112 | /** 113 | * Gets the HTTP client object. 114 | * 115 | * @return \Laminas\Http\Client 116 | */ 117 | public function getHttpClient() 118 | { 119 | return $this->httpClient; 120 | } 121 | 122 | /** 123 | * Sets the object used to introspect remote servers 124 | * 125 | * @return ServerIntrospection 126 | */ 127 | public function setIntrospector(ServerIntrospection $introspector) 128 | { 129 | return $this->introspector = $introspector; 130 | } 131 | 132 | /** 133 | * Gets the introspection object. 134 | * 135 | * @return ServerIntrospection 136 | */ 137 | public function getIntrospector() 138 | { 139 | return $this->introspector; 140 | } 141 | 142 | /** 143 | * The request of the last method call 144 | * 145 | * @return Request 146 | */ 147 | public function getLastRequest() 148 | { 149 | return $this->lastRequest; 150 | } 151 | 152 | /** 153 | * The response received from the last method call 154 | * 155 | * @return Response 156 | */ 157 | public function getLastResponse() 158 | { 159 | return $this->lastResponse; 160 | } 161 | 162 | /** 163 | * Returns a proxy object for more convenient method calls 164 | * 165 | * @param string $namespace Namespace to proxy or empty string for none 166 | * @return ServerProxy 167 | */ 168 | public function getProxy($namespace = '') 169 | { 170 | if (empty($this->proxyCache[$namespace])) { 171 | $proxy = new ServerProxy($this, $namespace); 172 | $this->proxyCache[$namespace] = $proxy; 173 | } 174 | return $this->proxyCache[$namespace]; 175 | } 176 | 177 | /** 178 | * Set skip system lookup flag 179 | * 180 | * @param bool $flag 181 | * @return Client 182 | */ 183 | public function setSkipSystemLookup($flag = true) 184 | { 185 | $this->skipSystemLookup = (bool) $flag; 186 | return $this; 187 | } 188 | 189 | /** 190 | * Skip system lookup when determining if parameter should be array or struct? 191 | * 192 | * @return bool 193 | */ 194 | public function skipSystemLookup() 195 | { 196 | return $this->skipSystemLookup; 197 | } 198 | 199 | /** 200 | * Perform an XML-RPC request and return a response. 201 | * 202 | * You may optionally pass a bitmask of LIBXML options via the 203 | * $libXmlOptions parameter; as an example, you might use LIBXML_PARSEHUGE. 204 | * See https://www.php.net/manual/en/libxml.constants.php for a full list. 205 | * 206 | * @param Request $request 207 | * @param null|Response $response 208 | * @param int $libXmlOptions Bitmask of LIBXML options to use for XML * operations 209 | * @throws InvalidArgumentException 210 | * @throws RuntimeException 211 | * @throws HttpException 212 | * @throws ValueException 213 | * @return void 214 | */ 215 | public function doRequest($request, $response = null, int $libXmlOptions = 0) 216 | { 217 | $this->lastRequest = $request; 218 | 219 | if (PHP_VERSION_ID < 50600) { 220 | iconv_set_encoding('input_encoding', 'UTF-8'); 221 | iconv_set_encoding('output_encoding', 'UTF-8'); 222 | iconv_set_encoding('internal_encoding', 'UTF-8'); 223 | } else { 224 | ini_set('default_charset', 'UTF-8'); 225 | } 226 | 227 | $http = $this->getHttpClient(); 228 | $httpRequest = $http->getRequest(); 229 | if ($httpRequest->getUriString() === null) { 230 | $http->setUri($this->serverAddress); 231 | } 232 | 233 | $headers = $httpRequest->getHeaders(); 234 | 235 | if (! $headers->has('Content-Type')) { 236 | $headers->addHeaderLine('Content-Type', 'text/xml; charset=utf-8'); 237 | } 238 | 239 | if (! $headers->has('Accept')) { 240 | $headers->addHeaderLine('Accept', 'text/xml'); 241 | } 242 | 243 | if (! $headers->has('user-agent')) { 244 | $headers->addHeaderLine('user-agent', 'Laminas_XmlRpc_Client'); 245 | } 246 | 247 | $xml = $this->lastRequest->__toString(); 248 | $http->setRawBody($xml); 249 | $httpResponse = $http->setMethod('POST')->send(); 250 | 251 | if (! $httpResponse->isSuccess()) { 252 | /** 253 | * Exception thrown when an HTTP error occurs 254 | */ 255 | throw new HttpException( 256 | $httpResponse->getReasonPhrase(), 257 | $httpResponse->getStatusCode() 258 | ); 259 | } 260 | 261 | if ($response === null) { 262 | $response = new Response(); 263 | } 264 | 265 | $this->lastResponse = $response; 266 | $this->lastResponse->loadXml(trim($httpResponse->getBody()), $libXmlOptions); 267 | } 268 | 269 | /** 270 | * Send an XML-RPC request to the service (for a specific method) 271 | * 272 | * @param string $method Name of the method we want to call 273 | * @param array $params Array of parameters for the method 274 | * @return mixed 275 | * @throws FaultException 276 | */ 277 | public function call($method, $params = []) 278 | { 279 | if (! $this->skipSystemLookup() && (! str_starts_with($method, 'system.'))) { 280 | // Ensure empty array/struct params are cast correctly 281 | // If system.* methods are not available, bypass. (Laminas-2978) 282 | $success = true; 283 | try { 284 | $signatures = $this->getIntrospector()->getMethodSignature($method); 285 | } catch (ExceptionInterface) { 286 | $success = false; 287 | } 288 | if ($success) { 289 | $validTypes = [ 290 | AbstractValue::XMLRPC_TYPE_ARRAY, 291 | AbstractValue::XMLRPC_TYPE_BASE64, 292 | AbstractValue::XMLRPC_TYPE_BOOLEAN, 293 | AbstractValue::XMLRPC_TYPE_DATETIME, 294 | AbstractValue::XMLRPC_TYPE_DOUBLE, 295 | AbstractValue::XMLRPC_TYPE_I4, 296 | AbstractValue::XMLRPC_TYPE_INTEGER, 297 | AbstractValue::XMLRPC_TYPE_NIL, 298 | AbstractValue::XMLRPC_TYPE_STRING, 299 | AbstractValue::XMLRPC_TYPE_STRUCT, 300 | ]; 301 | 302 | if (! is_array($params)) { 303 | $params = [$params]; 304 | } 305 | foreach ($params as $key => $param) { 306 | if ($param instanceof AbstractValue) { 307 | continue; 308 | } 309 | 310 | assert(isset($signatures)); 311 | 312 | if (count($signatures) > 1) { 313 | $type = AbstractValue::getXmlRpcTypeByValue($param); 314 | foreach ($signatures as $signature) { 315 | if (! is_array($signature)) { 316 | continue; 317 | } 318 | if (isset($signature['parameters'][$key])) { 319 | if ($signature['parameters'][$key] === $type) { 320 | break; 321 | } 322 | } 323 | } 324 | } elseif (isset($signatures[0]['parameters'][$key])) { 325 | $type = $signatures[0]['parameters'][$key]; 326 | } else { 327 | $type = null; 328 | } 329 | 330 | if (! is_string($type) || ! in_array($type, $validTypes)) { 331 | $type = AbstractValue::AUTO_DETECT_TYPE; 332 | } 333 | 334 | /** @psalm-var AbstractValue::XMLRPC_TYPE_* $type */ 335 | 336 | $params[$key] = AbstractValue::getXmlRpcValue($param, $type); 337 | } 338 | } 339 | } 340 | 341 | $request = $this->createRequest($method, $params); 342 | 343 | $this->doRequest($request); 344 | 345 | if ($this->lastResponse->isFault()) { 346 | $fault = $this->lastResponse->getFault(); 347 | /** 348 | * Exception thrown when an XML-RPC fault is returned 349 | */ 350 | throw new FaultException( 351 | $fault->getMessage(), 352 | $fault->getCode() 353 | ); 354 | } 355 | 356 | return $this->lastResponse->getReturnValue(); 357 | } 358 | 359 | /** 360 | * Create request object 361 | * 362 | * @param string $method 363 | * @param array $params 364 | * @return Request 365 | */ 366 | protected function createRequest($method, $params) 367 | { 368 | return new Request($method, $params); 369 | } 370 | } 371 | -------------------------------------------------------------------------------- /src/Request.php: -------------------------------------------------------------------------------- 1 | setMethod($method); 96 | } 97 | 98 | if ($params !== null) { 99 | $this->setParams($params); 100 | } 101 | } 102 | 103 | /** 104 | * Set encoding to use in request 105 | * 106 | * @param string $encoding 107 | * @return Request 108 | */ 109 | public function setEncoding($encoding) 110 | { 111 | $this->encoding = $encoding; 112 | AbstractValue::setEncoding($encoding); 113 | return $this; 114 | } 115 | 116 | /** 117 | * Retrieve current request encoding 118 | * 119 | * @return string 120 | */ 121 | public function getEncoding() 122 | { 123 | return $this->encoding; 124 | } 125 | 126 | /** 127 | * Set method to call 128 | * 129 | * @param string $method 130 | * @return bool Returns true on success, false if method name is invalid 131 | */ 132 | public function setMethod($method) 133 | { 134 | if (! is_string($method) || ! preg_match('/^[a-z0-9_.:\\\\\/]+$/i', $method)) { 135 | $this->fault = new Fault(634, 'Invalid method name ("' . $method . '")'); 136 | $this->fault->setEncoding($this->getEncoding()); 137 | return false; 138 | } 139 | 140 | $this->method = $method; 141 | return true; 142 | } 143 | 144 | /** 145 | * Retrieve call method 146 | * 147 | * @return string 148 | */ 149 | public function getMethod() 150 | { 151 | return $this->method; 152 | } 153 | 154 | /** 155 | * Add a parameter to the parameter stack 156 | * 157 | * Adds a parameter to the parameter stack, associating it with the type 158 | * $type if provided 159 | * 160 | * @param mixed $value 161 | * @param string $type Optional; type hinting 162 | * @return void 163 | */ 164 | public function addParam($value, $type = null) 165 | { 166 | $this->params[] = $value; 167 | if (null === $type) { 168 | // Detect type if not provided explicitly 169 | if ($value instanceof AbstractValue) { 170 | $type = $value->getType(); 171 | } else { 172 | $xmlRpcValue = AbstractValue::getXmlRpcValue($value); 173 | $type = $xmlRpcValue->getType(); 174 | } 175 | } 176 | $this->types[] = $type; 177 | $this->xmlRpcParams[] = ['value' => $value, 'type' => $type]; 178 | } 179 | 180 | /** 181 | * Set the parameters array 182 | * 183 | * If called with a single, array value, that array is used to set the 184 | * parameters stack. If called with multiple values or a single non-array 185 | * value, the arguments are used to set the parameters stack. 186 | * 187 | * Best is to call with array of the format, in order to allow type hinting 188 | * when creating the XMLRPC values for each parameter: 189 | * 190 | * $array = array( 191 | * array( 192 | * 'value' => $value, 193 | * 'type' => $type 194 | * )[, ... ] 195 | * ); 196 | * 197 | * 198 | * @access public 199 | * @return void 200 | */ 201 | public function setParams() 202 | { 203 | $argc = func_num_args(); 204 | $argv = func_get_args(); 205 | if (0 === $argc) { 206 | return; 207 | } 208 | 209 | if ((1 === $argc) && is_array($argv[0])) { 210 | $params = []; 211 | $types = []; 212 | $wellFormed = true; 213 | foreach ($argv[0] as $arg) { 214 | if (! is_array($arg) || ! isset($arg['value'])) { 215 | $wellFormed = false; 216 | break; 217 | } 218 | $params[] = $arg['value']; 219 | 220 | if (! isset($arg['type'])) { 221 | $xmlRpcValue = AbstractValue::getXmlRpcValue($arg['value']); 222 | $arg['type'] = $xmlRpcValue->getType(); 223 | } 224 | $types[] = $arg['type']; 225 | } 226 | if ($wellFormed) { 227 | $this->xmlRpcParams = $argv[0]; 228 | $this->params = $params; 229 | $this->types = $types; 230 | } else { 231 | $this->params = $argv[0]; 232 | $this->types = []; 233 | $xmlRpcParams = []; 234 | foreach ($argv[0] as $arg) { 235 | if ($arg instanceof AbstractValue) { 236 | $type = $arg->getType(); 237 | } else { 238 | $xmlRpcValue = AbstractValue::getXmlRpcValue($arg); 239 | $type = $xmlRpcValue->getType(); 240 | } 241 | $xmlRpcParams[] = ['value' => $arg, 'type' => $type]; 242 | $this->types[] = $type; 243 | } 244 | $this->xmlRpcParams = $xmlRpcParams; 245 | } 246 | return; 247 | } 248 | 249 | $this->params = $argv; 250 | $this->types = []; 251 | $xmlRpcParams = []; 252 | foreach ($argv as $arg) { 253 | if ($arg instanceof AbstractValue) { 254 | $type = $arg->getType(); 255 | } else { 256 | $xmlRpcValue = AbstractValue::getXmlRpcValue($arg); 257 | $type = $xmlRpcValue->getType(); 258 | } 259 | $xmlRpcParams[] = ['value' => $arg, 'type' => $type]; 260 | $this->types[] = $type; 261 | } 262 | $this->xmlRpcParams = $xmlRpcParams; 263 | } 264 | 265 | /** 266 | * Retrieve the array of parameters 267 | * 268 | * @return array 269 | */ 270 | public function getParams() 271 | { 272 | return $this->params; 273 | } 274 | 275 | /** 276 | * Return parameter types 277 | * 278 | * @return array 279 | */ 280 | public function getTypes() 281 | { 282 | return $this->types; 283 | } 284 | 285 | /** 286 | * Load XML and parse into request components 287 | * 288 | * You may optionally pass a bitmask of LIBXML options via the 289 | * $libXmlOptions parameter; as an example, you might use LIBXML_PARSEHUGE. 290 | * See https://www.php.net/manual/en/libxml.constants.php for a full list. 291 | * 292 | * @param string $request 293 | * @param int $libXmlOptions Bitmask of LIBXML options to use for XML * operations 294 | * @throws ValueException If invalid XML. 295 | * @return bool True on success, false if an error occurred. 296 | */ 297 | public function loadXml($request, int $libXmlOptions = 0) 298 | { 299 | if (! is_string($request)) { 300 | $this->fault = new Fault(635); 301 | $this->fault->setEncoding($this->getEncoding()); 302 | return false; 303 | } 304 | 305 | $xmlErrorsFlag = libxml_use_internal_errors(true); 306 | 307 | try { 308 | $dom = new DOMDocument(); 309 | $dom->loadXML($request, $libXmlOptions); 310 | foreach ($dom->childNodes as $child) { 311 | if ($child->nodeType === XML_DOCUMENT_TYPE_NODE) { 312 | throw new ValueException( 313 | 'Invalid XML: Detected use of illegal DOCTYPE' 314 | ); 315 | } 316 | } 317 | ErrorHandler::start(); 318 | $xml = simplexml_import_dom($dom); 319 | $error = ErrorHandler::stop(); 320 | libxml_use_internal_errors($xmlErrorsFlag); 321 | } catch (Exception) { 322 | // Not valid XML 323 | $this->fault = new Fault(631); 324 | $this->fault->setEncoding($this->getEncoding()); 325 | libxml_use_internal_errors($xmlErrorsFlag); 326 | return false; 327 | } 328 | if (! $xml instanceof SimpleXMLElement || $error) { 329 | // Not valid XML 330 | $this->fault = new Fault(631); 331 | $this->fault->setEncoding($this->getEncoding()); 332 | libxml_use_internal_errors($xmlErrorsFlag); 333 | return false; 334 | } 335 | 336 | // Check for method name 337 | if (! isset($xml->methodName) || (string) $xml->methodName === '') { 338 | // Missing method name 339 | $this->fault = new Fault(632); 340 | $this->fault->setEncoding($this->getEncoding()); 341 | return false; 342 | } 343 | 344 | $this->method = (string) $xml->methodName; 345 | 346 | // Check for parameters 347 | if ($xml->params instanceof SimpleXMLElement && $xml->params->count() > 0) { 348 | $types = []; 349 | $argv = []; 350 | $children = $xml->params->children(); 351 | assert($children !== null); 352 | foreach ($children as $param) { 353 | if (! isset($param->value)) { 354 | $this->fault = new Fault(633); 355 | $this->fault->setEncoding($this->getEncoding()); 356 | return false; 357 | } 358 | 359 | try { 360 | $param = AbstractValue::getXmlRpcValue($param->value, AbstractValue::XML_STRING); 361 | $types[] = $param->getType(); 362 | $argv[] = $param->getValue(); 363 | } catch (Exception) { 364 | $this->fault = new Fault(636); 365 | $this->fault->setEncoding($this->getEncoding()); 366 | return false; 367 | } 368 | } 369 | 370 | $this->types = $types; 371 | $this->params = $argv; 372 | } 373 | 374 | $this->xml = $request; 375 | 376 | return true; 377 | } 378 | 379 | /** 380 | * Does the current request contain errors and should it return a fault 381 | * response? 382 | * 383 | * @return bool 384 | */ 385 | public function isFault() 386 | { 387 | return $this->fault instanceof Fault; 388 | } 389 | 390 | /** 391 | * Retrieve the fault response, if any 392 | * 393 | * @return null|Fault 394 | */ 395 | public function getFault() 396 | { 397 | return $this->fault; 398 | } 399 | 400 | /** 401 | * Retrieve method parameters as XMLRPC values 402 | * 403 | * @return array 404 | */ 405 | protected function getXmlRpcParams() 406 | { 407 | $params = []; 408 | if (is_array($this->xmlRpcParams)) { 409 | foreach ($this->xmlRpcParams as $param) { 410 | $value = $param['value']; 411 | $type = $param['type'] ?: AbstractValue::AUTO_DETECT_TYPE; 412 | 413 | if (! $value instanceof AbstractValue) { 414 | $value = AbstractValue::getXmlRpcValue($value, $type); 415 | } 416 | $params[] = $value; 417 | } 418 | } 419 | 420 | return $params; 421 | } 422 | 423 | /** 424 | * Create XML request 425 | * 426 | * @return string 427 | */ 428 | public function saveXml() 429 | { 430 | $args = $this->getXmlRpcParams(); 431 | $method = $this->getMethod(); 432 | 433 | $generator = AbstractValue::getGenerator(); 434 | $generator->openElement('methodCall') 435 | ->openElement('methodName', $method) 436 | ->closeElement('methodName'); 437 | 438 | if (is_array($args) && count($args)) { 439 | $generator->openElement('params'); 440 | 441 | foreach ($args as $arg) { 442 | $generator->openElement('param'); 443 | $arg->generateXml(); 444 | $generator->closeElement('param'); 445 | } 446 | $generator->closeElement('params'); 447 | } 448 | $generator->closeElement('methodCall'); 449 | 450 | return $generator->flush(); 451 | } 452 | 453 | /** 454 | * Return XML request 455 | */ 456 | public function __toString(): string 457 | { 458 | return $this->saveXML(); 459 | } 460 | } 461 | -------------------------------------------------------------------------------- /src/AbstractValue.php: -------------------------------------------------------------------------------- 1 | type; 116 | } 117 | 118 | /** 119 | * Get XML generator instance 120 | * 121 | * @return GeneratorInterface 122 | */ 123 | public static function getGenerator() 124 | { 125 | if (! static::$generator) { 126 | if (extension_loaded('xmlwriter')) { 127 | static::$generator = new Generator\XmlWriter(); 128 | } else { 129 | static::$generator = new Generator\DomDocument(); 130 | } 131 | } 132 | 133 | return static::$generator; 134 | } 135 | 136 | /** 137 | * Sets XML generator instance 138 | * 139 | * @return void 140 | */ 141 | public static function setGenerator(?Generator\GeneratorInterface $generator = null) 142 | { 143 | static::$generator = $generator; 144 | } 145 | 146 | /** 147 | * Changes the encoding of the generator 148 | * 149 | * @param string $encoding 150 | * @return void 151 | */ 152 | public static function setEncoding($encoding) 153 | { 154 | $generator = static::getGenerator(); 155 | $newGenerator = new $generator($encoding); 156 | static::setGenerator($newGenerator); 157 | } 158 | 159 | /** 160 | * Return the value of this object, convert the XML-RPC native value into a PHP variable 161 | * 162 | * @return mixed 163 | */ 164 | abstract public function getValue(); 165 | 166 | /** 167 | * Return the XML code that represent a native MXL-RPC value 168 | * 169 | * @return string 170 | */ 171 | public function saveXml() 172 | { 173 | if (! $this->xml) { 174 | $this->generateXml(); 175 | $this->xml = (string) static::getGenerator(); 176 | } 177 | return $this->xml; 178 | } 179 | 180 | /** 181 | * Generate XML code that represent a native XML/RPC value 182 | * 183 | * @return void 184 | */ 185 | public function generateXml() 186 | { 187 | $this->generate(); 188 | } 189 | 190 | /** 191 | * Creates a Value* object, representing a native XML-RPC value 192 | * A XmlRpcValue object can be created in 3 ways: 193 | * 1. Autodetecting the native type out of a PHP variable 194 | * (if $type is not set or equal to Value::AUTO_DETECT_TYPE) 195 | * 2. By specifying the native type ($type is one of the Value::XMLRPC_TYPE_* constants) 196 | * 3. From a XML string ($type is set to Value::XML_STRING) 197 | * 198 | * By default the value type is autodetected according to it's PHP type 199 | * 200 | * You may optionally pass a bitmask of LIBXML options via the 201 | * $libXmlOptions parameter; as an example, you might use LIBXML_PARSEHUGE. 202 | * See https://www.php.net/manual/en/libxml.constants.php for a full list. 203 | * 204 | * @param mixed $value 205 | * @param self::AUTO_DETECT_TYPE|self::XML* $type 206 | * @param int $libXmlOptions Bitmask of LIBXML options to use for XML * operations 207 | * @throws ValueException 208 | * @return AbstractValue 209 | */ 210 | public static function getXmlRpcValue($value, $type = self::AUTO_DETECT_TYPE, int $libXmlOptions = 0) 211 | { 212 | switch ($type) { 213 | case self::AUTO_DETECT_TYPE: 214 | // Auto detect the XML-RPC native type from the PHP type of $value 215 | return static::phpVarToNativeXmlRpc($value); 216 | 217 | case self::XML_STRING: 218 | // Parse the XML string given in $value and get the XML-RPC value in it 219 | return static::xmlStringToNativeXmlRpc($value, $libXmlOptions); 220 | 221 | case self::XMLRPC_TYPE_I4: 222 | // fall through to the next case 223 | case self::XMLRPC_TYPE_INTEGER: 224 | return new Value\Integer($value); 225 | 226 | case self::XMLRPC_TYPE_I8: 227 | // fall through to the next case 228 | case self::XMLRPC_TYPE_APACHEI8: 229 | return self::$USE_BIGINT_FOR_I8 ? new Value\BigInteger($value) : new Value\Integer($value); 230 | 231 | case self::XMLRPC_TYPE_DOUBLE: 232 | return new Value\Double($value); 233 | 234 | case self::XMLRPC_TYPE_BOOLEAN: 235 | return new Value\Boolean($value); 236 | 237 | case self::XMLRPC_TYPE_STRING: 238 | return new Value\Text($value); 239 | 240 | case self::XMLRPC_TYPE_BASE64: 241 | return new Value\Base64($value); 242 | 243 | case self::XMLRPC_TYPE_NIL: 244 | // fall through to the next case 245 | case self::XMLRPC_TYPE_APACHENIL: 246 | return new Value\Nil(); 247 | 248 | case self::XMLRPC_TYPE_DATETIME: 249 | return new Value\DateTime($value); 250 | 251 | case self::XMLRPC_TYPE_ARRAY: 252 | return new Value\ArrayValue($value); 253 | 254 | case self::XMLRPC_TYPE_STRUCT: 255 | return new Value\Struct($value); 256 | 257 | default: 258 | throw new ValueException('Given type is not a ' . self::class . ' constant'); 259 | } 260 | } 261 | 262 | /** 263 | * Get XML-RPC type for a PHP native variable 264 | * 265 | * @static 266 | * @param mixed $value 267 | * @throws InvalidArgumentException 268 | * @return string 269 | */ 270 | public static function getXmlRpcTypeByValue($value) 271 | { 272 | if (is_object($value)) { 273 | if ($value instanceof AbstractValue) { 274 | return $value->getType(); 275 | } elseif ($value instanceof DateTime) { 276 | return self::XMLRPC_TYPE_DATETIME; 277 | } 278 | return static::getXmlRpcTypeByValue(get_object_vars($value)); 279 | } elseif (is_array($value)) { 280 | if (! empty($value) && is_array($value) && (array_keys($value) !== range(0, count($value) - 1))) { 281 | return self::XMLRPC_TYPE_STRUCT; 282 | } 283 | return self::XMLRPC_TYPE_ARRAY; 284 | } elseif (is_int($value)) { 285 | return $value > PHP_INT_MAX ? self::XMLRPC_TYPE_I8 : self::XMLRPC_TYPE_INTEGER; 286 | } elseif (is_float($value)) { 287 | return self::XMLRPC_TYPE_DOUBLE; 288 | } elseif (is_bool($value)) { 289 | return self::XMLRPC_TYPE_BOOLEAN; 290 | } elseif (null === $value) { 291 | return self::XMLRPC_TYPE_NIL; 292 | } elseif (is_string($value)) { 293 | return self::XMLRPC_TYPE_STRING; 294 | } 295 | throw new InvalidArgumentException(sprintf( 296 | 'No matching XMLRPC type found for php type %s.', 297 | gettype($value) 298 | )); 299 | } 300 | 301 | /** 302 | * Transform a PHP native variable into a XML-RPC native value 303 | * 304 | * @param mixed $value The PHP variable for conversion 305 | * @throws InvalidArgumentException 306 | * @return AbstractValue 307 | * @static 308 | */ 309 | protected static function phpVarToNativeXmlRpc($value) 310 | { 311 | // @see https://getlaminas.org/issues/browse/Laminas-8623 312 | if ($value instanceof AbstractValue) { 313 | return $value; 314 | } 315 | 316 | switch (static::getXmlRpcTypeByValue($value)) { 317 | case self::XMLRPC_TYPE_DATETIME: 318 | /** @psalm-suppress MixedArgument */ 319 | return new Value\DateTime($value); 320 | 321 | case self::XMLRPC_TYPE_ARRAY: 322 | /** @psalm-suppress MixedArgument */ 323 | return new Value\ArrayValue($value); 324 | 325 | case self::XMLRPC_TYPE_STRUCT: 326 | /** @psalm-suppress MixedArgument */ 327 | return new Value\Struct($value); 328 | 329 | case self::XMLRPC_TYPE_INTEGER: 330 | /** @psalm-suppress MixedArgument */ 331 | return new Value\Integer($value); 332 | 333 | case self::XMLRPC_TYPE_DOUBLE: 334 | /** @psalm-suppress MixedArgument */ 335 | return new Value\Double($value); 336 | 337 | case self::XMLRPC_TYPE_BOOLEAN: 338 | /** @psalm-suppress MixedArgument */ 339 | return new Value\Boolean($value); 340 | 341 | case self::XMLRPC_TYPE_NIL: 342 | return new Value\Nil(); 343 | 344 | case self::XMLRPC_TYPE_STRING: 345 | // Fall through to the next case 346 | default: 347 | // If type isn't identified (or identified as string), it treated as string 348 | /** @psalm-suppress MixedArgument */ 349 | return new Value\Text($value); 350 | } 351 | } 352 | 353 | /** 354 | * Transform an XML string into a XML-RPC native value 355 | * 356 | * @param string|SimpleXMLElement $xml A SimpleXMLElement object represent the XML string 357 | * It can be also a valid XML string for conversion 358 | * @throws ValueException 359 | * @return AbstractValue 360 | * @static 361 | */ 362 | protected static function xmlStringToNativeXmlRpc($xml, int $libXmlOptions = 0) 363 | { 364 | static::createSimpleXMLElement($xml, $libXmlOptions); 365 | 366 | static::extractTypeAndValue($xml, $type, $value); 367 | 368 | switch ($type) { 369 | // All valid and known XML-RPC native values 370 | case self::XMLRPC_TYPE_I4: 371 | // Fall through to the next case 372 | case self::XMLRPC_TYPE_INTEGER: 373 | $xmlrpcValue = new Value\Integer($value); 374 | break; 375 | case self::XMLRPC_TYPE_APACHEI8: 376 | // Fall through to the next case 377 | case self::XMLRPC_TYPE_I8: 378 | $xmlrpcValue = self::$USE_BIGINT_FOR_I8 ? new Value\BigInteger($value) : new Value\Integer($value); 379 | break; 380 | case self::XMLRPC_TYPE_DOUBLE: 381 | $xmlrpcValue = new Value\Double($value); 382 | break; 383 | case self::XMLRPC_TYPE_BOOLEAN: 384 | $xmlrpcValue = new Value\Boolean($value); 385 | break; 386 | case self::XMLRPC_TYPE_STRING: 387 | $xmlrpcValue = new Value\Text($value); 388 | break; 389 | case self::XMLRPC_TYPE_DATETIME: // The value should already be in an iso8601 format 390 | $xmlrpcValue = new Value\DateTime($value); 391 | break; 392 | case self::XMLRPC_TYPE_BASE64: // The value should already be base64 encoded 393 | $xmlrpcValue = new Value\Base64($value, true); 394 | break; 395 | case self::XMLRPC_TYPE_NIL: 396 | // Fall through to the next case 397 | case self::XMLRPC_TYPE_APACHENIL: 398 | // The value should always be NULL 399 | $xmlrpcValue = new Value\Nil(); 400 | break; 401 | case self::XMLRPC_TYPE_ARRAY: 402 | // PHP 5.2.4 introduced a regression in how empty($xml->value) 403 | // returns; need to look for the item specifically 404 | $data = null; 405 | foreach ($value->children() as $key => $value) { 406 | if ('data' === $key) { 407 | $data = $value; 408 | break; 409 | } 410 | } 411 | 412 | if (null === $data) { 413 | throw new ValueException( 414 | 'Invalid XML for XML-RPC native ' 415 | . self::XMLRPC_TYPE_ARRAY 416 | . ' type: ARRAY tag must contain DATA tag' 417 | ); 418 | } 419 | $values = []; 420 | // Parse all the elements of the array from the XML string 421 | // (simple xml element) to Value objects 422 | foreach ($data->value as $element) { 423 | $values[] = static::xmlStringToNativeXmlRpc($element, $libXmlOptions); 424 | } 425 | $xmlrpcValue = new Value\ArrayValue($values); 426 | break; 427 | case self::XMLRPC_TYPE_STRUCT: 428 | $values = []; 429 | // Parse all the members of the struct from the XML string 430 | // (simple xml element) to Value objects 431 | foreach ($value->member as $member) { 432 | // @todo? If a member doesn't have a tag, we don't add it to the struct 433 | // Maybe we want to throw an exception here ? 434 | if (! isset($member->value) || ! isset($member->name)) { 435 | continue; 436 | } 437 | $values[(string) $member->name] = static::xmlStringToNativeXmlRpc($member->value, $libXmlOptions); 438 | } 439 | $xmlrpcValue = new Value\Struct($values); 440 | break; 441 | default: 442 | throw new ValueException( 443 | 'Value type \'' . $type . '\' parsed from the XML string is not a known XML-RPC native type' 444 | ); 445 | } 446 | $xmlrpcValue->setXML($xml->asXML()); 447 | 448 | return $xmlrpcValue; 449 | } 450 | 451 | /** 452 | * @param SimpleXMLElement|string $xml 453 | */ 454 | protected static function createSimpleXMLElement(&$xml, int $libXmlOptions = 0) 455 | { 456 | if ($xml instanceof SimpleXMLElement) { 457 | return; 458 | } 459 | 460 | try { 461 | $xml = new SimpleXMLElement($xml, $libXmlOptions); 462 | } catch (Exception $e) { 463 | // The given string is not a valid XML 464 | throw new ValueException( 465 | 'Failed to create XML-RPC value from XML string: ' . $e->getMessage(), 466 | $e->getCode(), 467 | $e 468 | ); 469 | } 470 | } 471 | 472 | /** 473 | * Extract XML/RPC type and value from SimpleXMLElement object 474 | * 475 | * @param string $type Type bind variable 476 | * @param string $value Value bind variable 477 | * @return void 478 | */ 479 | protected static function extractTypeAndValue(SimpleXMLElement $xml, &$type, &$value) 480 | { 481 | // Casting is necessary to work with strict-typed systems 482 | foreach ((array) $xml as $type => $value) { 483 | break; 484 | } 485 | if (! $type && $value === null) { 486 | $namespaces = ['ex' => 'http://ws.apache.org/xmlrpc/namespaces/extensions']; 487 | foreach ($namespaces as $namespaceName => $namespaceUri) { 488 | foreach ((array) $xml->children($namespaceUri) as $type => $value) { 489 | break; 490 | } 491 | if ($type !== null) { 492 | $type = $namespaceName . ':' . $type; 493 | break; 494 | } 495 | } 496 | } 497 | 498 | // If no type was specified, the default is string 499 | if (! is_string($type) || $type === '') { 500 | $type = self::XMLRPC_TYPE_STRING; 501 | if (empty($value) && preg_match('#^.*$#', $xml->asXML())) { 502 | $value = str_replace(['', ''], '', $xml->asXML()); 503 | } 504 | } 505 | } 506 | 507 | /** 508 | * @param string $xml 509 | * @return void 510 | */ 511 | protected function setXML($xml) 512 | { 513 | $this->xml = static::getGenerator()->stripDeclaration($xml); 514 | } 515 | } 516 | -------------------------------------------------------------------------------- /src/Server.php: -------------------------------------------------------------------------------- 1 | 35 | * use Laminas\XmlRpc; 36 | * 37 | * // Instantiate server 38 | * $server = new XmlRpc\Server(); 39 | * 40 | * // Allow some exceptions to report as fault responses: 41 | * XmlRpc\Server\Fault::attachFaultException('My\\Exception'); 42 | * XmlRpc\Server\Fault::attachObserver('My\\Fault\\Observer'); 43 | * 44 | * // Get or build dispatch table: 45 | * if (!XmlRpc\Server\Cache::get($filename, $server)) { 46 | * 47 | * // Attach Some_Service_Class in 'some' namespace 48 | * $server->setClass('Some\\Service\\Class', 'some'); 49 | * 50 | * // Attach Another_Service_Class in 'another' namespace 51 | * $server->setClass('Another\\Service\\Class', 'another'); 52 | * 53 | * // Create dispatch table cache file 54 | * XmlRpc\Server\Cache::save($filename, $server); 55 | * } 56 | * 57 | * $response = $server->handle(); 58 | * echo $response; 59 | * 60 | */ 61 | class Server extends AbstractServer 62 | { 63 | /** 64 | * Character encoding 65 | * 66 | * @var string 67 | */ 68 | protected $encoding = 'UTF-8'; 69 | 70 | /** 71 | * Request processed 72 | * 73 | * @var null|Request 74 | */ 75 | protected $request; 76 | 77 | /** 78 | * Class to use for responses; defaults to {@link Response\Http} 79 | * 80 | * @var string 81 | */ 82 | protected $responseClass = Http::class; 83 | 84 | /** 85 | * Dispatch table of name => method pairs 86 | * 87 | * @var Definition 88 | */ 89 | protected $table; 90 | 91 | /** 92 | * PHP types => XML-RPC types 93 | * 94 | * @var array 95 | */ 96 | protected $typeMap = [ 97 | 'i4' => 'i4', 98 | 'int' => 'int', 99 | 'integer' => 'int', 100 | 'i8' => 'i8', 101 | 'ex:i8' => 'i8', 102 | 'double' => 'double', 103 | 'float' => 'double', 104 | 'real' => 'double', 105 | 'boolean' => 'boolean', 106 | 'bool' => 'boolean', 107 | 'true' => 'boolean', 108 | 'false' => 'boolean', 109 | 'string' => 'string', 110 | 'str' => 'string', 111 | 'base64' => 'base64', 112 | 'dateTime.iso8601' => 'dateTime.iso8601', 113 | 'date' => 'dateTime.iso8601', 114 | 'time' => 'dateTime.iso8601', 115 | DateTime::class => 'dateTime.iso8601', 116 | 'array' => 'array', 117 | 'struct' => 'struct', 118 | 'null' => 'nil', 119 | 'nil' => 'nil', 120 | 'ex:nil' => 'nil', 121 | 'void' => 'void', 122 | 'mixed' => 'struct', 123 | ]; 124 | 125 | /** 126 | * Send arguments to all methods or just constructor? 127 | * 128 | * @var bool 129 | */ 130 | protected $sendArgumentsToAllMethods = true; 131 | 132 | /** 133 | * Flag: whether or not {@link handle()} should return a response instead 134 | * of automatically emitting it. 135 | * 136 | * @var bool 137 | */ 138 | protected $returnResponse = false; 139 | 140 | /** 141 | * Last response results. 142 | * 143 | * @var Response 144 | */ 145 | protected $response; 146 | 147 | /** 148 | * @internal 149 | * 150 | * @var Server\System 151 | */ 152 | public $system; 153 | 154 | /** 155 | * Constructor 156 | * 157 | * Creates system.* methods. 158 | */ 159 | public function __construct() 160 | { 161 | $this->table = new Definition(); 162 | $this->registerSystemMethods(); 163 | } 164 | 165 | /** 166 | * Proxy calls to system object 167 | * 168 | * @param string $method 169 | * @param array $params 170 | * @return mixed 171 | * @throws Server\Exception\BadMethodCallException 172 | */ 173 | public function __call($method, $params) 174 | { 175 | $system = $this->getSystem(); 176 | if (! method_exists($system, $method)) { 177 | throw new Server\Exception\BadMethodCallException('Unknown instance method called on server: ' . $method); 178 | } 179 | return call_user_func_array([$system, $method], $params); 180 | } 181 | 182 | /** 183 | * Attach a callback as an XMLRPC method 184 | * 185 | * Attaches a callback as an XMLRPC method, prefixing the XMLRPC method name 186 | * with $namespace, if provided. Reflection is done on the callback's 187 | * docblock to create the methodHelp for the XMLRPC method. 188 | * 189 | * Additional arguments to pass to the function at dispatch may be passed; 190 | * any arguments following the namespace will be aggregated and passed at 191 | * dispatch time. 192 | * 193 | * @param string|array|callable $function Valid callback 194 | * @param string $namespace Optional namespace prefix 195 | * @throws InvalidArgumentException 196 | * @return void 197 | */ 198 | public function addFunction($function, $namespace = '') 199 | { 200 | if (! is_string($function) && ! is_array($function)) { 201 | throw new InvalidArgumentException('Unable to attach function; invalid', 611); 202 | } 203 | 204 | $argv = null; 205 | if (2 < func_num_args()) { 206 | $argv = func_get_args(); 207 | $argv = array_slice($argv, 2); 208 | } 209 | 210 | $function = (array) $function; 211 | foreach ($function as $func) { 212 | if (! is_string($func) || ! function_exists($func)) { 213 | throw new InvalidArgumentException('Unable to attach function; invalid', 611); 214 | } 215 | $reflection = Reflection::reflectFunction($func, $argv, $namespace); 216 | $this->_buildSignature($reflection); 217 | } 218 | } 219 | 220 | /** 221 | * Attach class methods as XMLRPC method handlers 222 | * 223 | * $class may be either a class name or an object. Reflection is done on the 224 | * class or object to determine the available public methods, and each is 225 | * attached to the server as an available method; if a $namespace has been 226 | * provided, that namespace is used to prefix the XMLRPC method names. 227 | * 228 | * Any additional arguments beyond $namespace will be passed to a method at 229 | * invocation. 230 | * 231 | * @param string|object $class 232 | * @param string $namespace Optional 233 | * @param mixed $argv Optional arguments to pass to methods 234 | * @return void 235 | * @throws InvalidArgumentException On invalid input. 236 | */ 237 | public function setClass($class, $namespace = '', $argv = null) 238 | { 239 | if (is_string($class) && ! class_exists($class)) { 240 | throw new InvalidArgumentException('Invalid method class', 610); 241 | } 242 | 243 | if (2 < func_num_args()) { 244 | $argv = func_get_args(); 245 | $argv = array_slice($argv, 2); 246 | } 247 | 248 | $dispatchable = Reflection::reflectClass($class, $argv, $namespace); 249 | foreach ($dispatchable->getMethods() as $reflection) { 250 | $this->_buildSignature($reflection, $class); 251 | } 252 | } 253 | 254 | /** 255 | * Raise an xmlrpc server fault 256 | * 257 | * @param string|Exception $fault 258 | * @param int $code 259 | * @return Server\Fault 260 | */ 261 | public function fault($fault = null, $code = 404) 262 | { 263 | if (! $fault instanceof Exception) { 264 | $fault = (string) $fault; 265 | if (empty($fault)) { 266 | $fault = 'Unknown Error'; 267 | } 268 | $fault = new Server\Exception\RuntimeException($fault, $code); 269 | } 270 | 271 | return Server\Fault::getInstance($fault); 272 | } 273 | 274 | /** 275 | * Set return response flag 276 | * 277 | * If true, {@link handle()} will return the response instead of 278 | * automatically sending it back to the requesting client. 279 | * 280 | * The response is always available via {@link getResponse()}. 281 | * 282 | * @param bool $flag 283 | * @return Server 284 | */ 285 | public function setReturnResponse($flag = true) 286 | { 287 | $this->returnResponse = (bool) $flag; 288 | return $this; 289 | } 290 | 291 | /** 292 | * Retrieve return response flag 293 | * 294 | * @return bool 295 | */ 296 | public function getReturnResponse() 297 | { 298 | return $this->returnResponse; 299 | } 300 | 301 | /** 302 | * Handle an xmlrpc call 303 | * 304 | * @param Request $request Optional 305 | * @return Response|Fault 306 | */ 307 | public function handle($request = false) 308 | { 309 | // Get request 310 | if ( 311 | (! $request || ! $request instanceof Request) 312 | && (null === ($request = $this->getRequest())) 313 | ) { 314 | $request = new Request\Http(); 315 | $request->setEncoding($this->getEncoding()); 316 | } 317 | 318 | $this->setRequest($request); 319 | 320 | if ($request->isFault()) { 321 | $response = $request->getFault(); 322 | } else { 323 | try { 324 | $response = $this->handleRequest($request); 325 | } catch (Exception $e) { 326 | $response = $this->fault($e); 327 | } 328 | } 329 | 330 | // Set output encoding 331 | $response->setEncoding($this->getEncoding()); 332 | $this->response = $response; 333 | 334 | if (! $this->returnResponse) { 335 | echo $response; 336 | return; 337 | } 338 | 339 | return $response; 340 | } 341 | 342 | /** 343 | * Load methods as returned from {@link getFunctions} 344 | * 345 | * Typically, you will not use this method; it will be called using the 346 | * results pulled from {@link Laminas\XmlRpc\Server\Cache::get()}. 347 | * 348 | * @param array|Definition $definition 349 | * @return void 350 | * @throws InvalidArgumentException On invalid input. 351 | */ 352 | public function loadFunctions($definition) 353 | { 354 | if (! is_array($definition) && ! $definition instanceof Definition) { 355 | if (is_object($definition)) { 356 | $type = $definition::class; 357 | } else { 358 | $type = gettype($definition); 359 | } 360 | throw new InvalidArgumentException( 361 | 'Unable to load server definition; must be an array or Laminas\Server\Definition, received ' . $type, 362 | 612 363 | ); 364 | } 365 | 366 | $this->table->clearMethods(); 367 | $this->registerSystemMethods(); 368 | 369 | if ($definition instanceof Definition) { 370 | $definition = $definition->getMethods(); 371 | } 372 | 373 | foreach ($definition as $key => $method) { 374 | if (str_starts_with($key, 'system.')) { 375 | continue; 376 | } 377 | $this->table->addMethod($method, $key); 378 | } 379 | } 380 | 381 | /** 382 | * Set encoding 383 | * 384 | * @param string $encoding 385 | * @return Server 386 | */ 387 | public function setEncoding($encoding) 388 | { 389 | $this->encoding = $encoding; 390 | AbstractValue::setEncoding($encoding); 391 | return $this; 392 | } 393 | 394 | /** 395 | * Retrieve current encoding 396 | * 397 | * @return string 398 | */ 399 | public function getEncoding() 400 | { 401 | return $this->encoding; 402 | } 403 | 404 | /** 405 | * Do nothing; persistence is handled via {@link Laminas\XmlRpc\Server\Cache} 406 | * 407 | * @param mixed $mode 408 | * @return void 409 | */ 410 | public function setPersistence($mode) 411 | { 412 | } 413 | 414 | /** 415 | * Set the request object 416 | * 417 | * @param string|Request $request 418 | * @return Server 419 | * @throws InvalidArgumentException On invalid request class or object. 420 | */ 421 | public function setRequest($request) 422 | { 423 | if (is_string($request) && class_exists($request)) { 424 | $request = new $request(); 425 | if (! $request instanceof Request) { 426 | throw new InvalidArgumentException('Invalid request class'); 427 | } 428 | $request->setEncoding($this->getEncoding()); 429 | } elseif (! $request instanceof Request) { 430 | throw new InvalidArgumentException('Invalid request object'); 431 | } 432 | 433 | $this->request = $request; 434 | return $this; 435 | } 436 | 437 | /** 438 | * Return currently registered request object 439 | * 440 | * @return null|Request 441 | */ 442 | public function getRequest() 443 | { 444 | return $this->request; 445 | } 446 | 447 | /** 448 | * Last response. 449 | * 450 | * @return Response 451 | */ 452 | public function getResponse() 453 | { 454 | return $this->response; 455 | } 456 | 457 | /** 458 | * Set the class to use for the response 459 | * 460 | * @param string $class 461 | * @throws InvalidArgumentException If invalid response class. 462 | * @return bool True if class was set, false if not 463 | */ 464 | public function setResponseClass($class) 465 | { 466 | if (! class_exists($class) || ! is_subclass_of($class, Response::class)) { 467 | throw new InvalidArgumentException('Invalid response class'); 468 | } 469 | $this->responseClass = $class; 470 | return true; 471 | } 472 | 473 | /** 474 | * Retrieve current response class 475 | * 476 | * @return string 477 | */ 478 | public function getResponseClass() 479 | { 480 | return $this->responseClass; 481 | } 482 | 483 | /** 484 | * Retrieve dispatch table 485 | * 486 | * @return array 487 | */ 488 | public function getDispatchTable() 489 | { 490 | return $this->table; 491 | } 492 | 493 | /** 494 | * Returns a list of registered methods 495 | * 496 | * Returns an array of dispatchables (Laminas\Server\Reflection\ReflectionFunction, 497 | * ReflectionMethod, and ReflectionClass items). 498 | * 499 | * @return array 500 | */ 501 | public function getFunctions() 502 | { 503 | return $this->table->toArray(); 504 | } 505 | 506 | /** 507 | * Retrieve system object 508 | * 509 | * @return Server\System 510 | */ 511 | public function getSystem() 512 | { 513 | return $this->system; 514 | } 515 | 516 | /** 517 | * Send arguments to all methods? 518 | * 519 | * If setClass() is used to add classes to the server, this flag defined 520 | * how to handle arguments. If set to true, all methods including constructor 521 | * will receive the arguments. If set to false, only constructor will receive the 522 | * arguments 523 | * 524 | * @param bool|null $flag 525 | * @return self 526 | */ 527 | public function sendArgumentsToAllMethods($flag = null) 528 | { 529 | if ($flag === null) { 530 | return $this->sendArgumentsToAllMethods; 531 | } 532 | 533 | $this->sendArgumentsToAllMethods = (bool) $flag; 534 | return $this; 535 | } 536 | 537 | // @codingStandardsIgnoreStart 538 | /** 539 | * Map PHP type to XML-RPC type 540 | * 541 | * @param string $type 542 | * @return string 543 | */ 544 | protected function _fixType($type) 545 | { 546 | return $this->typeMap[$type] ?? 'void'; 547 | } 548 | // @codingStandardsIgnoreEnd 549 | 550 | /** 551 | * Handle an xmlrpc call (actual work) 552 | * 553 | * @return Response 554 | * @throws Server\Exception\RuntimeException 555 | * Laminas\XmlRpc\Server\Exceptions are thrown for internal errors; otherwise, 556 | * any other exception may be thrown by the callback 557 | */ 558 | protected function handleRequest(Request $request) 559 | { 560 | $method = $request->getMethod(); 561 | 562 | // Check for valid method 563 | if (! $this->table->hasMethod($method)) { 564 | throw new Server\Exception\RuntimeException('Method "' . $method . '" does not exist', 620); 565 | } 566 | 567 | $info = $this->table->getMethod($method); 568 | $params = $request->getParams(); 569 | $argv = $info->getInvokeArguments(); 570 | if (0 < count($argv) && $this->sendArgumentsToAllMethods()) { 571 | $params = array_merge($params, $argv); 572 | } 573 | 574 | // Check calling parameters against signatures 575 | $matched = false; 576 | $sigCalled = $request->getTypes(); 577 | 578 | $sigLength = count($sigCalled); 579 | $paramsLen = count($params); 580 | if ($sigLength < $paramsLen) { 581 | for ($i = $sigLength; $i < $paramsLen; ++$i) { 582 | $xmlRpcValue = AbstractValue::getXmlRpcValue($params[$i]); 583 | $sigCalled[] = $xmlRpcValue->getType(); 584 | } 585 | } 586 | 587 | $signatures = $info->getPrototypes(); 588 | foreach ($signatures as $signature) { 589 | $sigParams = $signature->getParameters(); 590 | if ($sigCalled === $sigParams) { 591 | $matched = true; 592 | break; 593 | } 594 | } 595 | if (! $matched) { 596 | throw new Server\Exception\RuntimeException('Calling parameters do not match signature', 623); 597 | } 598 | 599 | $return = $this->_dispatch($info, $params); 600 | $responseClass = $this->getResponseClass(); 601 | return new $responseClass($return); 602 | } 603 | 604 | /** 605 | * Register system methods with the server 606 | * 607 | * @return void 608 | */ 609 | protected function registerSystemMethods() 610 | { 611 | $system = new Server\System($this); 612 | $this->system = $system; 613 | $this->setClass($system, 'system'); 614 | } 615 | 616 | /** 617 | * Checks if the object has this class as one of its parents 618 | * 619 | * @deprecated since laminas 2.3 requires PHP >= 5.3.23 620 | * 621 | * @see https://bugs.php.net/bug.php?id=53727 622 | * @see https://github.com/zendframework/zf2/pull/1807 623 | * 624 | * @param string $className 625 | * @param string $type 626 | * @return bool 627 | */ 628 | protected static function isSubclassOf($className, $type) 629 | { 630 | return is_subclass_of($className, $type); 631 | } 632 | } 633 | --------------------------------------------------------------------------------