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