17f8f2456SAndreas Gohr<?php 27f8f2456SAndreas Gohr 37f8f2456SAndreas Gohrnamespace IXR\Server; 47f8f2456SAndreas Gohr 57f8f2456SAndreas Gohr 67f8f2456SAndreas Gohruse IXR\DataType\Value; 77f8f2456SAndreas Gohruse IXR\Exception\ServerException; 87f8f2456SAndreas Gohruse IXR\Message\Error; 97f8f2456SAndreas Gohruse IXR\Message\Message; 107f8f2456SAndreas Gohr 117f8f2456SAndreas Gohrclass Server 127f8f2456SAndreas Gohr{ 137f8f2456SAndreas Gohr protected $callbacks = []; 147f8f2456SAndreas Gohr protected $message; 157f8f2456SAndreas Gohr protected $capabilities; 167f8f2456SAndreas Gohr 17*934f970aSAndreas Gohr /** 18*934f970aSAndreas Gohr * @throws ServerException 19*934f970aSAndreas Gohr */ 207f8f2456SAndreas Gohr public function __construct($callbacks = false, $data = false, $wait = false) 217f8f2456SAndreas Gohr { 227f8f2456SAndreas Gohr $this->setCapabilities(); 237f8f2456SAndreas Gohr if ($callbacks) { 247f8f2456SAndreas Gohr $this->callbacks = $callbacks; 257f8f2456SAndreas Gohr } 267f8f2456SAndreas Gohr $this->setCallbacks(); 277f8f2456SAndreas Gohr if (!$wait) { 287f8f2456SAndreas Gohr $this->serve($data); 297f8f2456SAndreas Gohr } 307f8f2456SAndreas Gohr } 317f8f2456SAndreas Gohr 32*934f970aSAndreas Gohr /** 33*934f970aSAndreas Gohr * @throws ServerException 34*934f970aSAndreas Gohr */ 357f8f2456SAndreas Gohr public function serve($data = false) 367f8f2456SAndreas Gohr { 377f8f2456SAndreas Gohr if (!$data) { 387f8f2456SAndreas Gohr if (isset($_SERVER['REQUEST_METHOD']) && $_SERVER['REQUEST_METHOD'] !== 'POST') { 397f8f2456SAndreas Gohr header('Content-Type: text/plain'); // merged from WP #9093 407f8f2456SAndreas Gohr throw new ServerException('XML-RPC server accepts POST requests only.'); 417f8f2456SAndreas Gohr } 427f8f2456SAndreas Gohr 437f8f2456SAndreas Gohr $data = file_get_contents('php://input'); 447f8f2456SAndreas Gohr } 457f8f2456SAndreas Gohr $this->message = new Message($data); 467f8f2456SAndreas Gohr if (!$this->message->parse()) { 477f8f2456SAndreas Gohr $this->error(-32700, 'parse error. not well formed'); 487f8f2456SAndreas Gohr } 497f8f2456SAndreas Gohr if ($this->message->messageType != 'methodCall') { 507f8f2456SAndreas Gohr $this->error(-32600, 'server error. invalid xml-rpc. not conforming to spec. Request must be a methodCall'); 517f8f2456SAndreas Gohr } 527f8f2456SAndreas Gohr $result = $this->call($this->message->methodName, $this->message->params); 537f8f2456SAndreas Gohr 547f8f2456SAndreas Gohr // Is the result an error? 557f8f2456SAndreas Gohr if ($result instanceof Error) { 567f8f2456SAndreas Gohr $this->error($result); 577f8f2456SAndreas Gohr } 587f8f2456SAndreas Gohr 597f8f2456SAndreas Gohr // Encode the result 607f8f2456SAndreas Gohr $r = new Value($result); 617f8f2456SAndreas Gohr $resultxml = $r->getXml(); 627f8f2456SAndreas Gohr 637f8f2456SAndreas Gohr // Create the XML 647f8f2456SAndreas Gohr $xml = <<<EOD 657f8f2456SAndreas Gohr<methodResponse> 667f8f2456SAndreas Gohr <params> 677f8f2456SAndreas Gohr <param> 687f8f2456SAndreas Gohr <value> 697f8f2456SAndreas Gohr $resultxml 707f8f2456SAndreas Gohr </value> 717f8f2456SAndreas Gohr </param> 727f8f2456SAndreas Gohr </params> 737f8f2456SAndreas Gohr</methodResponse> 747f8f2456SAndreas Gohr 757f8f2456SAndreas GohrEOD; 767f8f2456SAndreas Gohr // Send it 777f8f2456SAndreas Gohr $this->output($xml); 787f8f2456SAndreas Gohr } 797f8f2456SAndreas Gohr 807f8f2456SAndreas Gohr protected function call($methodname, $args) 817f8f2456SAndreas Gohr { 827f8f2456SAndreas Gohr if (!$this->hasMethod($methodname)) { 837f8f2456SAndreas Gohr return new Error(-32601, 'server error. requested method ' . $methodname . ' does not exist.'); 847f8f2456SAndreas Gohr } 857f8f2456SAndreas Gohr $method = $this->callbacks[$methodname]; 867f8f2456SAndreas Gohr // Perform the callback and send the response 877f8f2456SAndreas Gohr 887f8f2456SAndreas Gohr if (is_array($args) && count($args) == 1) { 897f8f2456SAndreas Gohr // If only one parameter just send that instead of the whole array 907f8f2456SAndreas Gohr $args = $args[0]; 917f8f2456SAndreas Gohr } 927f8f2456SAndreas Gohr 937f8f2456SAndreas Gohr try { 947f8f2456SAndreas Gohr // Are we dealing with a function or a method? 957f8f2456SAndreas Gohr if (is_string($method) && substr($method, 0, 5) === 'this:') { 967f8f2456SAndreas Gohr // It's a class method - check it exists 977f8f2456SAndreas Gohr $method = substr($method, 5); 987f8f2456SAndreas Gohr 997f8f2456SAndreas Gohr return $this->$method($args); 1007f8f2456SAndreas Gohr } 1017f8f2456SAndreas Gohr 1027f8f2456SAndreas Gohr return call_user_func($method, $args); 1037f8f2456SAndreas Gohr } catch (\BadFunctionCallException $exception) { 1047f8f2456SAndreas Gohr return new Error(-32601, "server error. requested callable '{$method}' does not exist."); 1057f8f2456SAndreas Gohr } 1067f8f2456SAndreas Gohr 1077f8f2456SAndreas Gohr } 1087f8f2456SAndreas Gohr 1097f8f2456SAndreas Gohr public function error($error, $message = false) 1107f8f2456SAndreas Gohr { 1117f8f2456SAndreas Gohr // Accepts either an error object or an error code and message 1127f8f2456SAndreas Gohr if ($message && !is_object($error)) { 1137f8f2456SAndreas Gohr $error = new Error($error, $message); 1147f8f2456SAndreas Gohr } 1157f8f2456SAndreas Gohr $this->output($error->getXml()); 1167f8f2456SAndreas Gohr } 1177f8f2456SAndreas Gohr 1187f8f2456SAndreas Gohr public function output($xml) 1197f8f2456SAndreas Gohr { 1207f8f2456SAndreas Gohr $xml = '<?xml version="1.0"?>' . "\n" . $xml; 1217f8f2456SAndreas Gohr $length = strlen($xml); 1227f8f2456SAndreas Gohr header('Connection: close'); 1237f8f2456SAndreas Gohr header('Content-Length: ' . $length); 1247f8f2456SAndreas Gohr header('Content-Type: text/xml'); 1257f8f2456SAndreas Gohr header('Date: ' . date('r')); 1267f8f2456SAndreas Gohr echo $xml; 1277f8f2456SAndreas Gohr exit; 1287f8f2456SAndreas Gohr } 1297f8f2456SAndreas Gohr 1307f8f2456SAndreas Gohr protected function hasMethod($method) 1317f8f2456SAndreas Gohr { 1327f8f2456SAndreas Gohr return in_array($method, array_keys($this->callbacks)); 1337f8f2456SAndreas Gohr } 1347f8f2456SAndreas Gohr 1357f8f2456SAndreas Gohr protected function setCapabilities() 1367f8f2456SAndreas Gohr { 1377f8f2456SAndreas Gohr // Initialises capabilities array 1387f8f2456SAndreas Gohr $this->capabilities = [ 1397f8f2456SAndreas Gohr 'xmlrpc' => [ 1407f8f2456SAndreas Gohr 'specUrl' => 'http://www.xmlrpc.com/spec', 1417f8f2456SAndreas Gohr 'specVersion' => 1 1427f8f2456SAndreas Gohr ], 1437f8f2456SAndreas Gohr 'faults_interop' => [ 1447f8f2456SAndreas Gohr 'specUrl' => 'http://xmlrpc-epi.sourceforge.net/specs/rfc.fault_codes.php', 1457f8f2456SAndreas Gohr 'specVersion' => 20010516 1467f8f2456SAndreas Gohr ], 1477f8f2456SAndreas Gohr 'system.multicall' => [ 1487f8f2456SAndreas Gohr 'specUrl' => 'http://www.xmlrpc.com/discuss/msgReader$1208', 1497f8f2456SAndreas Gohr 'specVersion' => 1 1507f8f2456SAndreas Gohr ], 1517f8f2456SAndreas Gohr ]; 1527f8f2456SAndreas Gohr } 1537f8f2456SAndreas Gohr 1547f8f2456SAndreas Gohr public function getCapabilities($args) 1557f8f2456SAndreas Gohr { 1567f8f2456SAndreas Gohr return $this->capabilities; 1577f8f2456SAndreas Gohr } 1587f8f2456SAndreas Gohr 1597f8f2456SAndreas Gohr public function setCallbacks() 1607f8f2456SAndreas Gohr { 1617f8f2456SAndreas Gohr $this->callbacks['system.getCapabilities'] = 'this:getCapabilities'; 1627f8f2456SAndreas Gohr $this->callbacks['system.listMethods'] = 'this:listMethods'; 1637f8f2456SAndreas Gohr $this->callbacks['system.multicall'] = 'this:multiCall'; 1647f8f2456SAndreas Gohr } 1657f8f2456SAndreas Gohr 1667f8f2456SAndreas Gohr public function listMethods($args) 1677f8f2456SAndreas Gohr { 1687f8f2456SAndreas Gohr // Returns a list of methods - uses array_reverse to ensure user defined 1697f8f2456SAndreas Gohr // methods are listed before server defined methods 1707f8f2456SAndreas Gohr return array_reverse(array_keys($this->callbacks)); 1717f8f2456SAndreas Gohr } 1727f8f2456SAndreas Gohr 1737f8f2456SAndreas Gohr public function multiCall($methodcalls) 1747f8f2456SAndreas Gohr { 1757f8f2456SAndreas Gohr // See http://www.xmlrpc.com/discuss/msgReader$1208 1767f8f2456SAndreas Gohr $return = []; 1777f8f2456SAndreas Gohr foreach ($methodcalls as $call) { 1787f8f2456SAndreas Gohr $method = $call['methodName']; 1797f8f2456SAndreas Gohr $params = $call['params']; 1807f8f2456SAndreas Gohr if ($method == 'system.multicall') { 1817f8f2456SAndreas Gohr $result = new Error(-32600, 'Recursive calls to system.multicall are forbidden'); 1827f8f2456SAndreas Gohr } else { 1837f8f2456SAndreas Gohr $result = $this->call($method, $params); 1847f8f2456SAndreas Gohr } 1857f8f2456SAndreas Gohr if ($result instanceof Error) { 1867f8f2456SAndreas Gohr $return[] = [ 1877f8f2456SAndreas Gohr 'faultCode' => $result->code, 1887f8f2456SAndreas Gohr 'faultString' => $result->message 1897f8f2456SAndreas Gohr ]; 1907f8f2456SAndreas Gohr } else { 1917f8f2456SAndreas Gohr $return[] = [$result]; 1927f8f2456SAndreas Gohr } 1937f8f2456SAndreas Gohr } 1947f8f2456SAndreas Gohr return $return; 1957f8f2456SAndreas Gohr } 1967f8f2456SAndreas Gohr} 197