PHP Cross Reference of WordPress Subversion HEAD |
| [ Index ] [ Classes ] [ Functions ] [ Variables ] [ Constants ] |
[Summary view] [Print] [Text view]
1 <?php 2 /* 3 IXR - The Inutio XML-RPC Library - (c) Incutio Ltd 2002-2005 4 Version 1.7 (beta) - Simon Willison, 23rd May 2005 5 Site: http://scripts.incutio.com/xmlrpc/ 6 Manual: http://scripts.incutio.com/xmlrpc/manual.php 7 Made available under the BSD License: http://www.opensource.org/licenses/bsd-license.php 8 */ 9 10 class IXR_Value { 11 var $data; 12 var $type; 13 function IXR_Value ($data, $type = false) { 14 $this->data = $data; 15 if (!$type) { 16 $type = $this->calculateType(); 17 } 18 $this->type = $type; 19 if ($type == 'struct') { 20 /* Turn all the values in the array in to new IXR_Value objects */ 21 foreach ($this->data as $key => $value) { 22 $this->data[$key] = new IXR_Value($value); 23 } 24 } 25 if ($type == 'array') { 26 for ($i = 0, $j = count($this->data); $i < $j; $i++) { 27 $this->data[$i] = new IXR_Value($this->data[$i]); 28 } 29 } 30 } 31 function calculateType() { 32 if ($this->data === true || $this->data === false) { 33 return 'boolean'; 34 } 35 if (is_integer($this->data)) { 36 return 'int'; 37 } 38 if (is_double($this->data)) { 39 return 'double'; 40 } 41 // Deal with IXR object types base64 and date 42 if (is_object($this->data) && is_a($this->data, 'IXR_Date')) { 43 return 'date'; 44 } 45 if (is_object($this->data) && is_a($this->data, 'IXR_Base64')) { 46 return 'base64'; 47 } 48 // If it is a normal PHP object convert it in to a struct 49 if (is_object($this->data)) { 50 51 $this->data = get_object_vars($this->data); 52 return 'struct'; 53 } 54 if (!is_array($this->data)) { 55 return 'string'; 56 } 57 /* We have an array - is it an array or a struct ? */ 58 if ($this->isStruct($this->data)) { 59 return 'struct'; 60 } else { 61 return 'array'; 62 } 63 } 64 function getXml() { 65 /* Return XML for this value */ 66 switch ($this->type) { 67 case 'boolean': 68 return '<boolean>'.(($this->data) ? '1' : '0').'</boolean>'; 69 break; 70 case 'int': 71 return '<int>'.$this->data.'</int>'; 72 break; 73 case 'double': 74 return '<double>'.$this->data.'</double>'; 75 break; 76 case 'string': 77 return '<string>'.htmlspecialchars($this->data).'</string>'; 78 break; 79 case 'array': 80 $return = '<array><data>'."\n"; 81 foreach ($this->data as $item) { 82 $return .= ' <value>'.$item->getXml()."</value>\n"; 83 } 84 $return .= '</data></array>'; 85 return $return; 86 break; 87 case 'struct': 88 $return = '<struct>'."\n"; 89 foreach ($this->data as $name => $value) { 90 $name = htmlspecialchars($name); 91 $return .= " <member><name>$name</name><value>"; 92 $return .= $value->getXml()."</value></member>\n"; 93 } 94 $return .= '</struct>'; 95 return $return; 96 break; 97 case 'date': 98 case 'base64': 99 return $this->data->getXml(); 100 break; 101 } 102 return false; 103 } 104 function isStruct($array) { 105 /* Nasty function to check if an array is a struct or not */ 106 $expected = 0; 107 foreach ($array as $key => $value) { 108 if ((string)$key != (string)$expected) { 109 return true; 110 } 111 $expected++; 112 } 113 return false; 114 } 115 } 116 117 118 class IXR_Message { 119 var $message; 120 var $messageType; // methodCall / methodResponse / fault 121 var $faultCode; 122 var $faultString; 123 var $methodName; 124 var $params; 125 // Current variable stacks 126 var $_arraystructs = array(); // The stack used to keep track of the current array/struct 127 var $_arraystructstypes = array(); // Stack keeping track of if things are structs or array 128 var $_currentStructName = array(); // A stack as well 129 var $_param; 130 var $_value; 131 var $_currentTag; 132 var $_currentTagContents; 133 // The XML parser 134 var $_parser; 135 function IXR_Message ($message) { 136 $this->message = $message; 137 } 138 function parse() { 139 // first remove the XML declaration 140 $this->message = preg_replace('/<\?xml(.*)?\?'.'>/', '', $this->message); 141 if (trim($this->message) == '') { 142 return false; 143 } 144 $this->_parser = xml_parser_create(); 145 // Set XML parser to take the case of tags in to account 146 xml_parser_set_option($this->_parser, XML_OPTION_CASE_FOLDING, false); 147 // Set XML parser callback functions 148 xml_set_object($this->_parser, $this); 149 xml_set_element_handler($this->_parser, 'tag_open', 'tag_close'); 150 xml_set_character_data_handler($this->_parser, 'cdata'); 151 if (!xml_parse($this->_parser, $this->message)) { 152 /* die(sprintf('XML error: %s at line %d', 153 xml_error_string(xml_get_error_code($this->_parser)), 154 xml_get_current_line_number($this->_parser))); */ 155 return false; 156 } 157 xml_parser_free($this->_parser); 158 // Grab the error messages, if any 159 if ($this->messageType == 'fault') { 160 $this->faultCode = $this->params[0]['faultCode']; 161 $this->faultString = $this->params[0]['faultString']; 162 } 163 return true; 164 } 165 function tag_open($parser, $tag, $attr) { 166 $this->_currentTagContents = ''; 167 $this->currentTag = $tag; 168 switch($tag) { 169 case 'methodCall': 170 case 'methodResponse': 171 case 'fault': 172 $this->messageType = $tag; 173 break; 174 /* Deal with stacks of arrays and structs */ 175 case 'data': // data is to all intents and puposes more interesting than array 176 $this->_arraystructstypes[] = 'array'; 177 $this->_arraystructs[] = array(); 178 break; 179 case 'struct': 180 $this->_arraystructstypes[] = 'struct'; 181 $this->_arraystructs[] = array(); 182 break; 183 } 184 } 185 function cdata($parser, $cdata) { 186 $this->_currentTagContents .= $cdata; 187 } 188 function tag_close($parser, $tag) { 189 $valueFlag = false; 190 switch($tag) { 191 case 'int': 192 case 'i4': 193 $value = (int) trim($this->_currentTagContents); 194 $valueFlag = true; 195 break; 196 case 'double': 197 $value = (double) trim($this->_currentTagContents); 198 $valueFlag = true; 199 break; 200 case 'string': 201 $value = $this->_currentTagContents; 202 $valueFlag = true; 203 break; 204 case 'dateTime.iso8601': 205 $value = new IXR_Date(trim($this->_currentTagContents)); 206 // $value = $iso->getTimestamp(); 207 $valueFlag = true; 208 break; 209 case 'value': 210 // "If no type is indicated, the type is string." 211 if (trim($this->_currentTagContents) != '') { 212 $value = (string)$this->_currentTagContents; 213 $valueFlag = true; 214 } 215 break; 216 case 'boolean': 217 $value = (boolean) trim($this->_currentTagContents); 218 $valueFlag = true; 219 break; 220 case 'base64': 221 $value = base64_decode( trim( $this->_currentTagContents ) ); 222 $valueFlag = true; 223 break; 224 /* Deal with stacks of arrays and structs */ 225 case 'data': 226 case 'struct': 227 $value = array_pop($this->_arraystructs); 228 array_pop($this->_arraystructstypes); 229 $valueFlag = true; 230 break; 231 case 'member': 232 array_pop($this->_currentStructName); 233 break; 234 case 'name': 235 $this->_currentStructName[] = trim($this->_currentTagContents); 236 break; 237 case 'methodName': 238 $this->methodName = trim($this->_currentTagContents); 239 break; 240 } 241 if ($valueFlag) { 242 if (count($this->_arraystructs) > 0) { 243 // Add value to struct or array 244 if ($this->_arraystructstypes[count($this->_arraystructstypes)-1] == 'struct') { 245 // Add to struct 246 $this->_arraystructs[count($this->_arraystructs)-1][$this->_currentStructName[count($this->_currentStructName)-1]] = $value; 247 } else { 248 // Add to array 249 $this->_arraystructs[count($this->_arraystructs)-1][] = $value; 250 } 251 } else { 252 // Just add as a paramater 253 $this->params[] = $value; 254 } 255 } 256 $this->_currentTagContents = ''; 257 } 258 } 259 260 261 class IXR_Server { 262 var $data; 263 var $callbacks = array(); 264 var $message; 265 var $capabilities; 266 function IXR_Server($callbacks = false, $data = false) { 267 $this->setCapabilities(); 268 if ($callbacks) { 269 $this->callbacks = $callbacks; 270 } 271 $this->setCallbacks(); 272 $this->serve($data); 273 } 274 function serve($data = false) { 275 if (!$data) { 276 global $HTTP_RAW_POST_DATA; 277 if (!$HTTP_RAW_POST_DATA) { 278 die('XML-RPC server accepts POST requests only.'); 279 } 280 $data = $HTTP_RAW_POST_DATA; 281 } 282 $this->message = new IXR_Message($data); 283 if (!$this->message->parse()) { 284 $this->error(-32700, 'parse error. not well formed'); 285 } 286 if ($this->message->messageType != 'methodCall') { 287 $this->error(-32600, 'server error. invalid xml-rpc. not conforming to spec. Request must be a methodCall'); 288 } 289 $result = $this->call($this->message->methodName, $this->message->params); 290 // Is the result an error? 291 if (is_a($result, 'IXR_Error')) { 292 $this->error($result); 293 } 294 // Encode the result 295 $r = new IXR_Value($result); 296 $resultxml = $r->getXml(); 297 // Create the XML 298 $xml = <<<EOD 299 <methodResponse> 300 <params> 301 <param> 302 <value> 303 $resultxml 304 </value> 305 </param> 306 </params> 307 </methodResponse> 308 309 EOD; 310 // Send it 311 $this->output($xml); 312 } 313 function call($methodname, $args) { 314 if (!$this->hasMethod($methodname)) { 315 return new IXR_Error(-32601, 'server error. requested method '.$methodname.' does not exist.'); 316 } 317 $method = $this->callbacks[$methodname]; 318 // Perform the callback and send the response 319 if (count($args) == 1) { 320 // If only one paramater just send that instead of the whole array 321 $args = $args[0]; 322 } 323 // Are we dealing with a function or a method? 324 if (substr($method, 0, 5) == 'this:') { 325 // It's a class method - check it exists 326 $method = substr($method, 5); 327 if (!method_exists($this, $method)) { 328 return new IXR_Error(-32601, 'server error. requested class method "'.$method.'" does not exist.'); 329 } 330 // Call the method 331 $result = $this->$method($args); 332 } else { 333 // It's a function - does it exist? 334 if (is_array($method)) { 335 if (!method_exists($method[0], $method[1])) { 336 return new IXR_Error(-32601, 'server error. requested object method "'.$method[1].'" does not exist.'); 337 } 338 } else if (!function_exists($method)) { 339 return new IXR_Error(-32601, 'server error. requested function "'.$method.'" does not exist.'); 340 } 341 // Call the function 342 $result = call_user_func($method, $args); 343 } 344 return $result; 345 } 346 347 function error($error, $message = false) { 348 // Accepts either an error object or an error code and message 349 if ($message && !is_object($error)) { 350 $error = new IXR_Error($error, $message); 351 } 352 $this->output($error->getXml()); 353 } 354 function output($xml) { 355 $xml = '<?xml version="1.0"?>'."\n".$xml; 356 $length = strlen($xml); 357 header('Connection: close'); 358 header('Content-Length: '.$length); 359 header('Content-Type: text/xml'); 360 header('Date: '.date('r')); 361 echo $xml; 362 exit; 363 } 364 function hasMethod($method) { 365 return in_array($method, array_keys($this->callbacks)); 366 } 367 function setCapabilities() { 368 // Initialises capabilities array 369 $this->capabilities = array( 370 'xmlrpc' => array( 371 'specUrl' => 'http://www.xmlrpc.com/spec', 372 'specVersion' => 1 373 ), 374 'faults_interop' => array( 375 'specUrl' => 'http://xmlrpc-epi.sourceforge.net/specs/rfc.fault_codes.php', 376 'specVersion' => 20010516 377 ), 378 'system.multicall' => array( 379 'specUrl' => 'http://www.xmlrpc.com/discuss/msgReader$1208', 380 'specVersion' => 1 381 ), 382 ); 383 } 384 function getCapabilities($args) { 385 return $this->capabilities; 386 } 387 function setCallbacks() { 388 $this->callbacks['system.getCapabilities'] = 'this:getCapabilities'; 389 $this->callbacks['system.listMethods'] = 'this:listMethods'; 390 $this->callbacks['system.multicall'] = 'this:multiCall'; 391 } 392 function listMethods($args) { 393 // Returns a list of methods - uses array_reverse to ensure user defined 394 // methods are listed before server defined methods 395 return array_reverse(array_keys($this->callbacks)); 396 } 397 function multiCall($methodcalls) { 398 // See http://www.xmlrpc.com/discuss/msgReader$1208 399 $return = array(); 400 foreach ($methodcalls as $call) { 401 $method = $call['methodName']; 402 $params = $call['params']; 403 if ($method == 'system.multicall') { 404 $result = new IXR_Error(-32600, 'Recursive calls to system.multicall are forbidden'); 405 } else { 406 $result = $this->call($method, $params); 407 } 408 if (is_a($result, 'IXR_Error')) { 409 $return[] = array( 410 'faultCode' => $result->code, 411 'faultString' => $result->message 412 ); 413 } else { 414 $return[] = array($result); 415 } 416 } 417 return $return; 418 } 419 } 420 421 class IXR_Request { 422 var $method; 423 var $args; 424 var $xml; 425 function IXR_Request($method, $args) { 426 $this->method = $method; 427 $this->args = $args; 428 $this->xml = <<<EOD 429 <?xml version="1.0"?> 430 <methodCall> 431 <methodName>{$this->method}</methodName> 432 <params> 433 434 EOD; 435 foreach ($this->args as $arg) { 436 $this->xml .= '<param><value>'; 437 $v = new IXR_Value($arg); 438 $this->xml .= $v->getXml(); 439 $this->xml .= "</value></param>\n"; 440 } 441 $this->xml .= '</params></methodCall>'; 442 } 443 function getLength() { 444 return strlen($this->xml); 445 } 446 function getXml() { 447 return $this->xml; 448 } 449 } 450 451 452 class IXR_Client { 453 var $server; 454 var $port; 455 var $path; 456 var $useragent; 457 var $response; 458 var $message = false; 459 var $debug = false; 460 var $timeout; 461 // Storage place for an error message 462 var $error = false; 463 function IXR_Client($server, $path = false, $port = 80, $timeout = false) { 464 if (!$path) { 465 // Assume we have been given a URL instead 466 $bits = parse_url($server); 467 $this->server = $bits['host']; 468 $this->port = isset($bits['port']) ? $bits['port'] : 80; 469 $this->path = isset($bits['path']) ? $bits['path'] : '/'; 470 // Make absolutely sure we have a path 471 if (!$this->path) { 472 $this->path = '/'; 473 } 474 } else { 475 $this->server = $server; 476 $this->path = $path; 477 $this->port = $port; 478 } 479 $this->useragent = 'Incutio XML-RPC'; 480 $this->timeout =