1<?php 2 3namespace IXR\Server; 4 5 6use IXR\DataType\Value; 7use IXR\Exception\ServerException; 8use IXR\Message\Error; 9use IXR\Message\Message; 10 11class Server 12{ 13 protected $callbacks = []; 14 protected $message; 15 protected $capabilities; 16 17 /** 18 * @throws ServerException 19 */ 20 public function __construct($callbacks = false, $data = false, $wait = false) 21 { 22 $this->setCapabilities(); 23 if ($callbacks) { 24 $this->callbacks = $callbacks; 25 } 26 $this->setCallbacks(); 27 if (!$wait) { 28 $this->serve($data); 29 } 30 } 31 32 /** 33 * @throws ServerException 34 */ 35 public function serve($data = false) 36 { 37 if (!$data) { 38 if (isset($_SERVER['REQUEST_METHOD']) && $_SERVER['REQUEST_METHOD'] !== 'POST') { 39 header('Content-Type: text/plain'); // merged from WP #9093 40 throw new ServerException('XML-RPC server accepts POST requests only.'); 41 } 42 43 $data = file_get_contents('php://input'); 44 } 45 $this->message = new Message($data); 46 if (!$this->message->parse()) { 47 $this->error(-32700, 'parse error. not well formed'); 48 } 49 if ($this->message->messageType != 'methodCall') { 50 $this->error(-32600, 'server error. invalid xml-rpc. not conforming to spec. Request must be a methodCall'); 51 } 52 $result = $this->call($this->message->methodName, $this->message->params); 53 54 // Is the result an error? 55 if ($result instanceof Error) { 56 $this->error($result); 57 } 58 59 // Encode the result 60 $r = new Value($result); 61 $resultxml = $r->getXml(); 62 63 // Create the XML 64 $xml = <<<EOD 65<methodResponse> 66 <params> 67 <param> 68 <value> 69 $resultxml 70 </value> 71 </param> 72 </params> 73</methodResponse> 74 75EOD; 76 // Send it 77 $this->output($xml); 78 } 79 80 protected function call($methodname, $args) 81 { 82 if (!$this->hasMethod($methodname)) { 83 return new Error(-32601, 'server error. requested method ' . $methodname . ' does not exist.'); 84 } 85 $method = $this->callbacks[$methodname]; 86 // Perform the callback and send the response 87 88 if (is_array($args) && count($args) == 1) { 89 // If only one parameter just send that instead of the whole array 90 $args = $args[0]; 91 } 92 93 try { 94 // Are we dealing with a function or a method? 95 if (is_string($method) && substr($method, 0, 5) === 'this:') { 96 // It's a class method - check it exists 97 $method = substr($method, 5); 98 99 return $this->$method($args); 100 } 101 102 return call_user_func($method, $args); 103 } catch (\BadFunctionCallException $exception) { 104 return new Error(-32601, "server error. requested callable '{$method}' does not exist."); 105 } 106 107 } 108 109 public function error($error, $message = false) 110 { 111 // Accepts either an error object or an error code and message 112 if ($message && !is_object($error)) { 113 $error = new Error($error, $message); 114 } 115 $this->output($error->getXml()); 116 } 117 118 public function output($xml) 119 { 120 $xml = '<?xml version="1.0"?>' . "\n" . $xml; 121 $length = strlen($xml); 122 header('Connection: close'); 123 header('Content-Length: ' . $length); 124 header('Content-Type: text/xml'); 125 header('Date: ' . date('r')); 126 echo $xml; 127 exit; 128 } 129 130 protected function hasMethod($method) 131 { 132 return in_array($method, array_keys($this->callbacks)); 133 } 134 135 protected function setCapabilities() 136 { 137 // Initialises capabilities array 138 $this->capabilities = [ 139 'xmlrpc' => [ 140 'specUrl' => 'http://www.xmlrpc.com/spec', 141 'specVersion' => 1 142 ], 143 'faults_interop' => [ 144 'specUrl' => 'http://xmlrpc-epi.sourceforge.net/specs/rfc.fault_codes.php', 145 'specVersion' => 20010516 146 ], 147 'system.multicall' => [ 148 'specUrl' => 'http://www.xmlrpc.com/discuss/msgReader$1208', 149 'specVersion' => 1 150 ], 151 ]; 152 } 153 154 public function getCapabilities($args) 155 { 156 return $this->capabilities; 157 } 158 159 public function setCallbacks() 160 { 161 $this->callbacks['system.getCapabilities'] = 'this:getCapabilities'; 162 $this->callbacks['system.listMethods'] = 'this:listMethods'; 163 $this->callbacks['system.multicall'] = 'this:multiCall'; 164 } 165 166 public function listMethods($args) 167 { 168 // Returns a list of methods - uses array_reverse to ensure user defined 169 // methods are listed before server defined methods 170 return array_reverse(array_keys($this->callbacks)); 171 } 172 173 public function multiCall($methodcalls) 174 { 175 // See http://www.xmlrpc.com/discuss/msgReader$1208 176 $return = []; 177 foreach ($methodcalls as $call) { 178 $method = $call['methodName']; 179 $params = $call['params']; 180 if ($method == 'system.multicall') { 181 $result = new Error(-32600, 'Recursive calls to system.multicall are forbidden'); 182 } else { 183 $result = $this->call($method, $params); 184 } 185 if ($result instanceof Error) { 186 $return[] = [ 187 'faultCode' => $result->code, 188 'faultString' => $result->message 189 ]; 190 } else { 191 $return[] = [$result]; 192 } 193 } 194 return $return; 195 } 196} 197