Textpattern | PHP Cross Reference | Content Management Systems |
Description: IXR - The Incutio XML-RPC Library Copyright (c) 2010, Incutio Ltd. All rights reserved.
1 <?php 2 /** 3 * IXR - The Incutio XML-RPC Library 4 * 5 * Copyright (c) 2010, Incutio Ltd. 6 * All rights reserved. 7 * 8 * Redistribution and use in source and binary forms, with or without 9 * modification, are permitted provided that the following conditions are met: 10 * 11 * - Redistributions of source code must retain the above copyright notice, 12 * this list of conditions and the following disclaimer. 13 * - Redistributions in binary form must reproduce the above copyright 14 * notice, this list of conditions and the following disclaimer in the 15 * documentation and/or other materials provided with the distribution. 16 * - Neither the name of Incutio Ltd. nor the names of its contributors 17 * may be used to endorse or promote products derived from this software 18 * without specific prior written permission. 19 * 20 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS 21 * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 22 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 23 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR 24 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 25 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 26 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 27 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY 28 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 29 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE 30 * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 * 32 * @package IXR 33 * @since 1.5.0 34 * 35 * @copyright Incutio Ltd 2010 (http://www.incutio.com) 36 * @version 1.7.4 7th September 2010 (Contains Textpatternish amendments, 2014-08-13) 37 * @author Simon Willison 38 * @link http://scripts.incutio.com/xmlrpc/ Site/manual 39 * @license http://www.opensource.org/licenses/bsd-license.php BSD 40 */ 41 42 /** 43 * IXR_Value 44 * 45 * @package IXR 46 * @since 1.5.0 47 */ 48 class IXR_Value { 49 var $data; 50 var $type; 51 52 function __construct($data, $type = false) 53 { 54 $this->data = $data; 55 if (!$type) { 56 $type = $this->calculateType(); 57 } 58 $this->type = $type; 59 if ($type == 'struct') { 60 // Turn all the values in the array in to new IXR_Value objects 61 foreach ($this->data as $key => $value) { 62 $this->data[$key] = new IXR_Value($value); 63 } 64 } 65 if ($type == 'array') { 66 for ($i = 0, $j = count($this->data); $i < $j; $i++) { 67 $this->data[$i] = new IXR_Value($this->data[$i]); 68 } 69 } 70 } 71 72 function calculateType() 73 { 74 if ($this->data === true || $this->data === false) { 75 return 'boolean'; 76 } 77 if (is_integer($this->data)) { 78 return 'int'; 79 } 80 if (is_double($this->data)) { 81 return 'double'; 82 } 83 84 // Deal with IXR object types base64 and date 85 if (is_object($this->data) && is_a($this->data, 'IXR_Date')) { 86 return 'date'; 87 } 88 if (is_object($this->data) && is_a($this->data, 'IXR_Base64')) { 89 return 'base64'; 90 } 91 92 // If it is a normal PHP object convert it in to a struct 93 if (is_object($this->data)) { 94 $this->data = get_object_vars($this->data); 95 return 'struct'; 96 } 97 if (!is_array($this->data)) { 98 return 'string'; 99 } 100 101 // We have an array - is it an array or a struct? 102 if ($this->isStruct($this->data)) { 103 return 'struct'; 104 } else { 105 return 'array'; 106 } 107 } 108 109 function getXml() 110 { 111 // Return XML for this value 112 switch ($this->type) { 113 case 'boolean': 114 return '<boolean>'.(($this->data) ? '1' : '0').'</boolean>'; 115 break; 116 case 'int': 117 return '<int>'.$this->data.'</int>'; 118 break; 119 case 'double': 120 return '<double>'.$this->data.'</double>'; 121 break; 122 case 'string': 123 return '<string>'.htmlspecialchars($this->data).'</string>'; 124 break; 125 case 'array': 126 $return = '<array><data>'."\n"; 127 foreach ($this->data as $item) { 128 $return .= ' <value>'.$item->getXml()."</value>\n"; 129 } 130 $return .= '</data></array>'; 131 return $return; 132 break; 133 case 'struct': 134 $return = '<struct>'."\n"; 135 foreach ($this->data as $name => $value) { 136 $name = htmlspecialchars($name); 137 $return .= " <member><name>$name</name><value>"; 138 $return .= $value->getXml()."</value></member>\n"; 139 } 140 $return .= '</struct>'; 141 return $return; 142 break; 143 case 'date': 144 case 'base64': 145 return $this->data->getXml(); 146 break; 147 } 148 return false; 149 } 150 151 /** 152 * Checks whether or not the supplied array is a struct or not 153 * 154 * @param unknown_type $array 155 * @return boolean 156 */ 157 function isStruct($array) 158 { 159 $expected = 0; 160 foreach ($array as $key => $value) { 161 if ((string)$key != (string)$expected) { 162 return true; 163 } 164 $expected++; 165 } 166 return false; 167 } 168 } 169 170 /** 171 * IXR_MESSAGE 172 * 173 * @package IXR 174 * @since 1.5.0 175 * 176 */ 177 class IXR_Message 178 { 179 var $message; 180 var $messageType; // methodCall / methodResponse / fault 181 var $faultCode; 182 var $faultString; 183 var $methodName; 184 var $params; 185 186 // Current variable stacks 187 var $_arraystructs = array(); // The stack used to keep track of the current array/struct 188 var $_arraystructstypes = array(); // Stack keeping track of if things are structs or array 189 var $_currentStructName = array(); // A stack as well 190 var $_param; 191 var $_value; 192 var $_currentTag; 193 var $_currentTagContents; 194 // The XML parser 195 var $_parser; 196 197 function __construct($message) 198 { 199 $this->message =& $message; 200 } 201 202 function parse() 203 { 204 // first remove the XML declaration 205 // merged from WP #10698 - this method avoids the RAM usage of preg_replace on very large messages 206 $header = preg_replace( '/<\?xml.*?\?'.'>/s', '', substr( $this->message, 0, 100 ), 1 ); 207 $this->message = trim( substr_replace( $this->message, $header, 0, 100 ) ); 208 if ( '' == $this->message ) { 209 return false; 210 } 211 212 // Then remove the DOCTYPE 213 $header = preg_replace( '/^<!DOCTYPE[^>]*+>/i', '', substr( $this->message, 0, 200 ), 1 ); 214 $this->message = trim( substr_replace( $this->message, $header, 0, 200 ) ); 215 if ( '' == $this->message ) { 216 return false; 217 } 218 219 // Check that the root tag is valid 220 $root_tag = substr( $this->message, 0, strcspn( substr( $this->message, 0, 20 ), "> \t\r\n" ) ); 221 if ( '<!DOCTYPE' === strtoupper( $root_tag ) ) { 222 return false; 223 } 224 if ( ! in_array( $root_tag, array( '<methodCall', '<methodResponse', '<fault' ) ) ) { 225 return false; 226 } 227 228 // Bail if there are too many elements to parse 229 $element_limit = 30000; 230 if ( function_exists( 'apply_filters' ) ) { 231 $element_limit = apply_filters( 'xmlrpc_element_limit', $element_limit ); 232 } 233 if ( $element_limit && 2 * $element_limit < substr_count( $this->message, '<' ) ) { 234 return false; 235 } 236 237 $this->_parser = xml_parser_create(); 238 // Set XML parser to take the case of tags in to account 239 xml_parser_set_option($this->_parser, XML_OPTION_CASE_FOLDING, false); 240 // Set XML parser callback functions 241 xml_set_object($this->_parser, $this); 242 xml_set_element_handler($this->_parser, 'tag_open', 'tag_close'); 243 xml_set_character_data_handler($this->_parser, 'cdata'); 244 $chunk_size = 262144; // 256Kb, parse in chunks to avoid the RAM usage on very large messages 245 $final = false; 246 do { 247 if (strlen($this->message) <= $chunk_size) { 248 $final = true; 249 } 250 $part = substr($this->message, 0, $chunk_size); 251 $this->message = substr($this->message, $chunk_size); 252 if (!xml_parse($this->_parser, $part, $final)) { 253 return false; 254 } 255 if ($final) { 256 break; 257 } 258 } while (true); 259 xml_parser_free($this->_parser); 260 261 // Grab the error messages, if any 262 if ($this->messageType == 'fault') { 263 $this->faultCode = $this->params[0]['faultCode']; 264 $this->faultString = $this->params[0]['faultString']; 265 } 266 return true; 267 } 268 269 function tag_open($parser, $tag, $attr) 270 { 271 $this->_currentTagContents = ''; 272 $this->currentTag = $tag; 273 switch($tag) { 274 case 'methodCall': 275 case 'methodResponse': 276 case 'fault': 277 $this->messageType = $tag; 278 break; 279 /* Deal with stacks of arrays and structs */ 280 case 'data': // data is to all intents and puposes more interesting than array 281 $this->_arraystructstypes[] = 'array'; 282 $this->_arraystructs[] = array(); 283 break; 284 case 'struct': 285 $this->_arraystructstypes[] = 'struct'; 286 $this->_arraystructs[] = array(); 287 break; 288 } 289 } 290 291 function cdata($parser, $cdata) 292 { 293 $this->_currentTagContents .= $cdata; 294 } 295 296 function tag_close($parser, $tag) 297 { 298 $valueFlag = false; 299 switch($tag) { 300 case 'int': 301 case 'i4': 302 $value = (int)trim($this->_currentTagContents); 303 $valueFlag = true; 304 break; 305 case 'double': 306 $value = (double)trim($this->_currentTagContents); 307 $valueFlag = true; 308 break; 309 case 'string': 310 $value = (string)trim($this->_currentTagContents); 311 $valueFlag = true; 312 break; 313 case 'dateTime.iso8601': 314 $value = new IXR_Date(trim($this->_currentTagContents)); 315 $valueFlag = true; 316 break; 317 case 'value': 318 // "If no type is indicated, the type is string." 319 if (trim($this->_currentTagContents) != '') { 320 $value = (string)$this->_currentTagContents; 321 $valueFlag = true; 322 } 323 break; 324 case 'boolean': 325 $value = (boolean)trim($this->_currentTagContents); 326 $valueFlag = true; 327 break; 328 case 'base64': 329 $value = base64_decode($this->_currentTagContents); 330 $valueFlag = true; 331 break; 332 /* Deal with stacks of arrays and structs */ 333 case 'data': 334 case 'struct': 335 $value = array_pop($this->_arraystructs); 336 array_pop($this->_arraystructstypes); 337 $valueFlag = true; 338 break; 339 case 'member': 340 array_pop($this->_currentStructName); 341 break; 342 case 'name': 343 $this->_currentStructName[] = trim($this->_currentTagContents); 344 break; 345 case 'methodName': 346 $this->methodName = trim($this->_currentTagContents); 347 break; 348 } 349 350 if ($valueFlag) { 351 if (count($this->_arraystructs) > 0) { 352 // Add value to struct or array 353 if ($this->_arraystructstypes[count($this->_arraystructstypes)-1] == 'struct') { 354 // Add to struct 355 $this->_arraystructs[count($this->_arraystructs)-1][$this->_currentStructName[count($this->_currentStructName)-1]] = $value; 356 } else { 357 // Add to array 358 $this->_arraystructs[count($this->_arraystructs)-1][] = $value; 359 } 360 } else { 361 // Just add as a parameter 362 $this->params[] = $value; 363 } 364 } 365 $this->_currentTagContents = ''; 366 } 367 } 368 369 /** 370 * IXR_Server 371 * 372 * @package IXR 373 * @since 1.5.0 374 */ 375 class IXR_Server 376 { 377 var $data; 378 var $callbacks = array(); 379 var $message; 380 var $capabilities; 381 382 function __construct($callbacks = false, $data = false, $wait = false) 383 { 384 $this->setCapabilities(); 385 if ($callbacks) { 386 $this->callbacks = $callbacks; 387 } 388 $this->setCallbacks(); 389 if (!$wait) { 390 $this->serve($data); 391 } 392 } 393 394 function serve($data = false) 395 { 396 if (!$data) { 397 if (isset($_SERVER['REQUEST_METHOD']) && $_SERVER['REQUEST_METHOD'] !== 'POST') { 398 header('Content-Type: text/plain'); // merged from WP #9093 399 die('XML-RPC server accepts POST requests only.'); 400 } 401 402 global $HTTP_RAW_POST_DATA; 403 if (empty($HTTP_RAW_POST_DATA)) { 404 // workaround for a bug in PHP 5.2.2 - https://bugs.php.net/bug.php?id=41293. 405 $data = file_get_contents('php://input'); 406 } else { 407 $data =& $HTTP_RAW_POST_DATA; 408 } 409 } 410 $this->message = new IXR_Message($data); 411 if (!$this->message->parse()) { 412 $this->error(-32700, 'parse error. not well formed'); 413 } 414 if ($this->message->messageType != 'methodCall') { 415 $this->error(-32600, 'server error. invalid xml-rpc. not conforming to spec. Request must be a methodCall'); 416 } 417 $result = $this->call($this->message->methodName, $this->message->params); 418 419 // Is the result an error? 420 if (is_a($result, 'IXR_Error')) { 421 $this->error($result); 422 } 423 424 // Encode the result 425 $r = new IXR_Value($result); 426 $resultxml = $r->getXml(); 427 428 // Create the XML 429 $xml = <<<EOD 430 <methodResponse> 431 <params> 432 <param> 433 <value> 434 $resultxml 435 </value> 436 </param> 437 </params> 438 </methodResponse> 439 440 EOD; 441 // Send it 442 $this->output($xml); 443 } 444 445 function call($methodname, $args) 446 { 447 if (!$this->hasMethod($methodname)) { 448 return new IXR_Error(-32601, 'server error. requested method '.$methodname.' does not exist.'); 449 } 450 $method = $this->callbacks[$methodname]; 451 452 // Perform the callback and send the response 453 if (count($args) == 1) { 454 // If only one parameter just send that instead of the whole array 455 $args = $args[0]; 456 } 457 458 // Are we dealing with a function or a method? 459 if (is_string($method) && substr($method, 0, 5) == 'this:') { 460 // It's a class method - check it exists 461 $method = substr($method, 5); 462 if (!method_exists($this, $method)) { 463 return new IXR_Error(-32601, 'server error. requested class method "'.$method.'" does not exist.'); 464 } 465 466 //Call the method 467 $result = $this->$method($args); 468 } else { 469 // It's a function - does it exist? 470 if (is_array($method)) { 471 if (!is_callable(array($method[0], $method[1]))) { 472 return new IXR_Error(-32601, 'server error. requested object method "'.$method[1].'" does not exist.'); 473 } 474 } else if (!function_exists($method)) { 475 return new IXR_Error(-32601, 'server error. requested function "'.$method.'" does not exist.'); 476 } 477 478 // Call the function 479 $result = call_user_func($method, $args); 480 } 481 return $result; 482 } 483 484 function error($error, $message = false) 485 { 486 // Accepts either an error object or an error code and message 487 if ($message && !is_object($error)) { 488 $error = new IXR_Error($error, $message); 489 } 490 $this->output($error->getXml()); 491 } 492 493 function output($xml) 494 { 495 $xml = '<?xml version="1.0" encoding="utf-8"?>'."\n".$xml; 496 if ( (@strpos($_SERVER["HTTP_ACCEPT_ENCODING"],'gzip') !== false) && extension_loaded('zlib') && 497 ini_get("zlib.output_compression") == 0 && ini_get('output_handler') != 'ob_gzhandler' && !headers_sent()) 498 { 499 $xml = gzencode($xml,7,FORCE_GZIP); 500 header("Content-Encoding: gzip"); 501 } 502 $length = strlen($xml); 503 header('Connection: close'); 504 header('Content-Length: '.$length); 505 header('Content-Type: text/xml'); 506 header('Date: '.date('r')); 507 echo $xml; 508 exit; 509 } 510 511 function hasMethod($method) 512 { 513 return in_array($method, array_keys($this->callbacks)); 514 } 515 516 function setCapabilities() 517 { 518 // Initialises capabilities array 519 $this->capabilities = array( 520 'xmlrpc' => array( 521 'specUrl' => 'http://www.xmlrpc.com/spec', 522 'specVersion' => 1 523 ), 524 'faults_interop' => array( 525 'specUrl' => 'http://xmlrpc-epi.sourceforge.net/specs/rfc.fault_codes.php', 526 'specVersion' => 20010516 527 ), 528 'system.multicall' => array( 529 'specUrl' => 'http://www.xmlrpc.com/discuss/msgReader$1208', 530 'specVersion' => 1 531 ), 532 ); 533 } 534 535 function getCapabilities($args) 536 { 537 return $this->capabilities; 538 } 539 540 function setCallbacks() 541 { 542 $this->callbacks['system.getCapabilities'] = 'this:getCapabilities'; 543 $this->callbacks['system.listMethods'] = 'this:listMethods'; 544 $this->callbacks['system.multicall'] = 'this:multiCall'; 545 } 546 547 function listMethods($args) 548 { 549 // Returns a list of methods - uses array_reverse to ensure user defined 550 // methods are listed before server defined methods 551 return array_reverse(array_keys($this->callbacks)); 552 } 553 554 function multiCall($methodcalls) 555 { 556 // See http://www.xmlrpc.com/discuss/msgReader$1208 557 $return = array(); 558 foreach ($methodcalls as $call) { 559 $method = $call['methodName']; 560 $params = $call['params']; 561 if ($method == 'system.multicall') { 562 $result = new IXR_Error(-32600, 'Recursive calls to system.multicall are forbidden'); 563 } else { 564 $result = $this->call($method, $params); 565 } 566 if (is_a($result, 'IXR_Error')) { 567 $return[] = array( 568 'faultCode' => $result->code, 569 'faultString' => $result->message 570 ); 571 } else { 572 $return[] = array($result); 573 } 574 } 575 return $return; 576 } 577 } 578 579 /** 580 * IXR_Request 581 * 582 * @package IXR 583 * @since 1.5.0 584 */ 585 class IXR_Request 586 { 587 var $method; 588 var $args; 589 var $xml; 590 591 function __construct($method, $args) 592 { 593 $this->method = $method; 594 $this->args = $args; 595 $this->xml = <<<EOD 596 <?xml version="1.0"?> 597 <methodCall> 598 <methodName>{$this->method}</methodName> 599 <params> 600 601 EOD; 602 foreach ($this->args as $arg) { 603 $this->xml .= '<param><value>'; 604 $v = new IXR_Value($arg); 605 $this->xml .= $v->getXml(); 606 $this->xml .= "</value></param>\n"; 607 } 608 $this->xml .= '</params></methodCall>'; 609 } 610 611 function getLength() 612 { 613 return strlen($this->xml); 614 } 615 616 function getXml() 617 { 618 return $this->xml; 619 } 620 } 621 622 /** 623 * IXR_Client 624 * 625 * @package IXR 626 * @since 1.5.0 627 * 628 */ 629 class IXR_Client 630 { 631 var $server; 632 var $port; 633 var $path; 634 var $useragent; 635 var $response; 636 var $message = false; 637 var $debug = false; 638 var $timeout; 639 var $headers = array(); 640 641 // Storage place for an error message 642 var $error = false; 643 644 function __construct($server, $path = false, $port = 80, $timeout = 45) 645 { 646 if (!$path) { 647 // Assume we have been given a URL instead 648 $bits = parse_url($server); 649 $this->server = $bits['host']; 650 $this->port = isset($bits['port']) ? $bits['port'] : 80; 651 $this->path = isset($bits['path']) ? $bits['path'] : '/'; 652 653 // Make absolutely sure we have a path 654 if (!$this->path) { 655 $this->path = '/'; 656 } 657 658 if ( ! empty( $bits['query'] ) ) { 659 $this->path .= '?' . $bits['query']; 660 } 661 } else { 662 $this->server = $server; 663 $this->path = $path; 664 $this->port = $port; 665 } 666 $this->useragent = 'The Incutio XML-RPC PHP Library'; 667 $this->timeout = $timeout; 668 } 669 670 function query() 671 { 672 $args = func_get_args(); 673 $method = array_shift($args); 674 $request = new IXR_Request($method, $args); 675 $length = $request->getLength(); 676 $xml = $request->getXml(); 677 $r = "\r\n"; 678 $request = "POST {$this->path} HTTP/1.0$r"; 679 680 // Merged from WP #8145 - allow custom headers 681 $this->headers['Host'] = $this->server; 682 $this->headers['Content-Type'] = 'text/xml'; 683 $this->headers['User-Agent'] = $this->useragent; 684 $this->headers['Content-Length']= $length; 685 686 // Accept gzipped response if zlib and if php4.3+ (fgets turned binary safe) 687 if ( extension_loaded('zlib') && preg_match('#^(4\.[3-9])|([5-9])#',phpversion()) ) 688 $this->headers['Accept-Encoding'] = 'gzip'; 689 690 foreach( $this->headers as $header => $value ) { 691 $request .= "{$header}: {$value}{$r}"; 692 } 693 $request .= $r; 694 695 $request .= $xml; 696 697 // Now send the request 698 if ($this->debug) { 699 echo '<pre class="ixr_request">'.htmlspecialchars($request)."\n</pre>\n\n"; 700 } 701 702 if ($this->timeout) { 703 $fp = (!is_disabled('fsockopen')) ? fsockopen($this->server, $this->port, $errno, $errstr, $this->timeout) : false; 704 } else { 705 $fp = (!is_disabled('fsockopen')) ? fsockopen($this->server, $this->port, $errno, $errstr) : false; 706 } 707 if (!$fp) { 708 $this->error = new IXR_Error(-32300, 'transport error - could not open socket ('.$errstr.')'); 709 return false; 710 } 711 fputs($fp, $request); 712 $contents = ''; 713 $debugContents = ''; 714 $gotFirstLine = false; 715 $gettingHeaders = true; 716 $is_gzipped = false; 717 while (!feof($fp)) { 718 $line = fgets($fp, 4096); 719 if (!$gotFirstLine) { 720 // Check line for '200' 721 if (strstr($line, '200') === false) { 722 $this->error = new IXR_Error(-32300, 'transport error - HTTP status code was not 200'); 723 return false; 724 } 725 $gotFirstLine = true; 726 } 727 if ($gettingHeaders && trim($line) == '') { 728 $gettingHeaders = false; 729 continue; 730 } 731 if (!$gettingHeaders) { 732 // We do a binary comparison of the first two bytes, see 733 // rfc1952, to check wether the content is gzipped. 734 if ( ($contents=='') && (strncmp($line,"\x1F\x8B",2)===0)) 735 $is_gzipped = true; 736 // merged from WP #12559 - remove trim 737 $contents .= $line; 738 } 739 if ($this->debug) { 740 $debugContents .= $line; 741 } 742 } 743 // if gzipped, strip the 10 byte header, and pass it to gzinflate (rfc1952) 744 if ($is_gzipped) 745 { 746 $contents = gzinflate(substr($contents, 10)); 747 //simulate trim() for each line; don't know why, but it won't work otherwise 748 $contents = preg_replace('#^[\x20\x09\x0A\x0D\x00\x0B]*(.*)[\x20\x09\x0A\x0D\x00\x0B]*$#m','\\1',$contents); 749 } 750 if ($this->debug) { 751 echo '<pre class="ixr_response">'.htmlspecialchars($debugContents)."\n</pre>\n\n"; 752 } 753 754 // Now parse what we've got back 755 $this->message = new IXR_Message($contents); 756 if (!$this->message->parse()) { 757 // XML error 758 $this->error = new IXR_Error(-32700, 'parse error. not well formed'); 759 return false; 760 } 761 762 // Is the message a fault? 763 if ($this->message->messageType == 'fault') { 764 $this->error = new IXR_Error($this->message->faultCode, $this->message->faultString); 765 return false; 766 } 767 768 // Message must be OK 769 return true; 770 } 771 772 function getResponse() 773 { 774 // methodResponses can only have one param - return that 775 return $this->message->params[0]; 776 } 777 778 function isError() 779 { 780 return (is_object($this->error)); 781 } 782 783 function getErrorCode() 784 { 785 return $this->error->code; 786 } 787 788 function getErrorMessage() 789 { 790 return $this->error->message; 791 } 792 } 793 794 795 /** 796 * IXR_Error 797 * 798 * @package IXR 799 * @since 1.5.0 800 */ 801 class IXR_Error 802 { 803 var $code; 804 var $message; 805 806 function __construct($code, $message) 807 { 808 $this->code = $code; 809 $this->message = htmlspecialchars($message); 810 } 811 812 function getXml() 813 { 814 $xml = <<<EOD 815 <methodResponse> 816 <fault> 817 <value> 818 <struct> 819 <member> 820 <name>faultCode</name> 821 <value><int>{$this->code}</int></value> 822 </member> 823 <member> 824 <name>faultString</name> 825 <value><string>{$this->message}</string></value> 826 </member> 827 </struct> 828 </value> 829 </fault> 830 </methodResponse> 831 832 EOD; 833 return $xml; 834 } 835 } 836 837 /** 838 * IXR_Date 839 * 840 * @package IXR 841 * @since 1.5.0 842 */ 843 class IXR_Date { 844 var $year; 845 var $month; 846 var $day; 847 var $hour; 848 var $minute; 849 var $second; 850 var $timezone; 851 852 function __construct($time) 853 { 854 // $time can be a PHP timestamp or an ISO one 855 if (is_numeric($time)) { 856 $this->parseTimestamp($time); 857 } else { 858 $this->parseIso($time); 859 } 860 } 861 862 function parseTimestamp($timestamp) 863 { 864 $this->year = date('Y', $timestamp); 865 $this->month = date('m', $timestamp); 866 $this->day = date('d', $timestamp); 867 $this->hour = date('H', $timestamp); 868 $this->minute = date('i', $timestamp); 869 $this->second = date('s', $timestamp); 870 $this->timezone = ''; 871 } 872 873 function parseIso($iso) 874 { 875 $this->year = substr($iso, 0, 4); 876 $this->month = substr($iso, 4, 2); 877 $this->day = substr($iso, 6, 2); 878 $this->hour = substr($iso, 9, 2); 879 $this->minute = substr($iso, 12, 2); 880 $this->second = substr($iso, 15, 2); 881 $this->timezone = substr($iso, 17); 882 } 883 884 function getIso() 885 { 886 return $this->year.$this->month.$this->day.'T'.$this->hour.':'.$this->minute.':'.$this->second.$this->timezone; 887 } 888 889 function getXml() 890 { 891 return '<dateTime.iso8601>'.$this->getIso().'</dateTime.iso8601>'; 892 } 893 894 function getTimestamp() 895 { 896 return mktime($this->hour, $this->minute, $this->second, $this->month, $this->day, $this->year); 897 } 898 } 899 900 /** 901 * IXR_Base64 902 * 903 * @package IXR 904 * @since 1.5.0 905 */ 906 class IXR_Base64 907 { 908 var $data; 909 910 function __construct($data) 911 { 912 $this->data = $data; 913 } 914 915 function getXml() 916 { 917 return '<base64>'.base64_encode($this->data).'</base64>'; 918 } 919 } 920 921 /** 922 * IXR_IntrospectionServer 923 * 924 * @package IXR 925 * @since 1.5.0 926 */ 927 class IXR_IntrospectionServer extends IXR_Server 928 { 929 var $signatures; 930 var $help; 931 932 function __construct() 933 { 934 $this->setCallbacks(); 935 $this->setCapabilities(); 936 $this->capabilities['introspection'] = array( 937 'specUrl' => 'http://xmlrpc.usefulinc.com/doc/reserved.html', 938 'specVersion' => 1 939 ); 940 $this->addCallback( 941 'system.methodSignature', 942 'this:methodSignature', 943 array('array', 'string'), 944 'Returns an array describing the return type and required parameters of a method' 945 ); 946 $this->addCallback( 947 'system.getCapabilities', 948 'this:getCapabilities', 949 array('struct'), 950 'Returns a struct describing the XML-RPC specifications supported by this server' 951 ); 952 $this->addCallback( 953 'system.listMethods', 954 'this:listMethods', 955 array('array'), 956 'Returns an array of available methods on this server' 957 ); 958 $this->addCallback( 959 'system.methodHelp', 960 'this:methodHelp', 961 array('string', 'string'), 962 'Returns a documentation string for the specified method' 963 ); 964 } 965 966 function addCallback($method, $callback, $args, $help) 967 { 968 $this->callbacks[$method] = $callback; 969 $this->signatures[$method] = $args; 970 $this->help[$method] = $help; 971 } 972 973 function call($methodname, $args) 974 { 975 // Make sure it's in an array 976 if ($args && !is_array($args)) { 977 $args = array($args); 978 } 979 980 // Over-rides default call method, adds signature check 981 if (!$this->hasMethod($methodname)) { 982 return new IXR_Error(-32601, 'server error. requested method "'.$this->message->methodName.'" not specified.'); 983 } 984 $method = $this->callbacks[$methodname]; 985 $signature = $this->signatures[$methodname]; 986 $returnType = array_shift($signature); 987 988 // Check the number of arguments 989 if (count($args) != count($signature)) { 990 return new IXR_Error(-32602, 'server error. wrong number of method parameters'); 991 } 992 993 // Check the argument types 994 $ok = true; 995 $argsbackup = $args; 996 for ($i = 0, $j = count($args); $i < $j; $i++) { 997 $arg = array_shift($args); 998 $type = array_shift($signature); 999 switch ($type) { 1000 case 'int': 1001 case 'i4': 1002 if (is_array($arg) || !is_int($arg)) { 1003 $ok = false; 1004 } 1005 break; 1006 case 'base64': 1007 case 'string': 1008 if (!is_string($arg)) { 1009 $ok = false; 1010 } 1011 break; 1012 case 'boolean': 1013 if ($arg !== false && $arg !== true) { 1014 $ok = false; 1015 } 1016 break; 1017 case 'float': 1018 case 'double': 1019 if (!is_float($arg)) { 1020 $ok = false; 1021 } 1022 break; 1023 case 'date': 1024 case 'dateTime.iso8601': 1025 if (!is_a($arg, 'IXR_Date')) { 1026 $ok = false; 1027 } 1028 break; 1029 } 1030 if (!$ok) { 1031 return new IXR_Error(-32602, 'server error. invalid method parameters'); 1032 } 1033 } 1034 // It passed the test - run the "real" method call 1035 return parent::call($methodname, $argsbackup); 1036 } 1037 1038 function methodSignature($method) 1039 { 1040 if (!$this->hasMethod($method)) { 1041 return new IXR_Error(-32601, 'server error. requested method "'.$method.'" not specified.'); 1042 } 1043 // We should be returning an array of types 1044 $types = $this->signatures[$method]; 1045 $return = array(); 1046 foreach ($types as $type) { 1047 switch ($type) { 1048 case 'string': 1049 $return[] = 'string'; 1050 break; 1051 case 'int': 1052 case 'i4': 1053 $return[] = 42; 1054 break; 1055 case 'double': 1056 $return[] = 3.1415; 1057 break; 1058 case 'dateTime.iso8601': 1059 $return[] = new IXR_Date(time()); 1060 break; 1061 case 'boolean': 1062 $return[] = true; 1063 break; 1064 case 'base64': 1065 $return[] = new IXR_Base64('base64'); 1066 break; 1067 case 'array': 1068 $return[] = array('array'); 1069 break; 1070 case 'struct': 1071 $return[] = array('struct' => 'struct'); 1072 break; 1073 } 1074 } 1075 return $return; 1076 } 1077 1078 function methodHelp($method) 1079 { 1080 return $this->help[$method]; 1081 } 1082 } 1083 1084 /** 1085 * IXR_ClientMulticall 1086 * 1087 * @package IXR 1088 * @since 1.5.0 1089 */ 1090 class IXR_ClientMulticall extends IXR_Client 1091 { 1092 var $calls = array(); 1093 1094 function __construct($server, $path = false, $port = 80) 1095 { 1096 parent::__construct($server, $path, $port); 1097 $this->useragent = 'The Incutio XML-RPC PHP Library (multicall client)'; 1098 } 1099 1100 function addCall() 1101 { 1102 $args = func_get_args(); 1103 $methodName = array_shift($args); 1104 $struct = array( 1105 'methodName' => $methodName, 1106 'params' => $args 1107 ); 1108 $this->calls[] = $struct; 1109 } 1110 1111 function query() 1112 { 1113 // Prepare multicall, then call the parent::query() method 1114 return parent::query('system.multicall', $this->calls); 1115 } 1116 } 1117 1118 /** 1119 * Client for communicating with a XML-RPC Server over HTTPS. 1120 * 1121 * @author Jason Stirk <jstirk@gmm.com.au> (@link http://blog.griffin.homelinux.org/projects/xmlrpc/) 1122 * @version 0.2.0 26May2005 08:34 +0800 1123 * @copyright (c) 2004-2005 Jason Stirk 1124 * @package IXR 1125 */ 1126 class IXR_ClientSSL extends IXR_Client 1127 { 1128 /** 1129 * Filename of the SSL Client Certificate 1130 * @access private 1131 * @since 0.1.0 1132 * @var string 1133 */ 1134 var $_certFile; 1135 1136 /** 1137 * Filename of the SSL CA Certificate 1138 * @access private 1139 * @since 0.1.0 1140 * @var string 1141 */ 1142 var $_caFile; 1143 1144 /** 1145 * Filename of the SSL Client Private Key 1146 * @access private 1147 * @since 0.1.0 1148 * @var string 1149 */ 1150 var $_keyFile; 1151 1152 /** 1153 * Passphrase to unlock the private key 1154 * @access private 1155 * @since 0.1.0 1156 * @var string 1157 */ 1158 var $_passphrase; 1159 1160 /** 1161 * Constructor 1162 * @param string $server URL of the Server to connect to 1163 * @since 0.1.0 1164 */ 1165 function __construct($server, $path = false, $port = 443, $timeout = false) 1166 { 1167 parent::__construct($server, $path, $port, $timeout); 1168 $this->useragent = 'The Incutio XML-RPC PHP Library for SSL'; 1169 1170 // Set class fields 1171 $this->_certFile=false; 1172 $this->_caFile=false; 1173 $this->_keyFile=false; 1174 $this->_passphrase=''; 1175 } 1176 1177 /** 1178 * Set the client side certificates to communicate with the server. 1179 * 1180 * @since 0.1.0 1181 * @param string $certificateFile Filename of the client side certificate to use 1182 * @param string $keyFile Filename of the client side certificate's private key 1183 * @param string $keyPhrase Passphrase to unlock the private key 1184 */ 1185 function setCertificate($certificateFile, $keyFile, $keyPhrase='') 1186 { 1187 // Check the files all exist 1188 if (is_file($certificateFile)) { 1189 $this->_certFile = $certificateFile; 1190 } else { 1191 die('Could not open certificate: ' . $certificateFile); 1192 } 1193 1194 if (is_file($keyFile)) { 1195 $this->_keyFile = $keyFile; 1196 } else { 1197 die('Could not open private key: ' . $keyFile); 1198 } 1199 1200 $this->_passphrase=(string)$keyPhrase; 1201 } 1202 1203 function setCACertificate($caFile) 1204 { 1205 if (is_file($caFile)) { 1206 $this->_caFile = $caFile; 1207 } else { 1208 die('Could not open CA certificate: ' . $caFile); 1209 } 1210 } 1211 1212 /** 1213 * Sets the connection timeout (in seconds) 1214 * @param int $newTimeOut Timeout in seconds 1215 * @returns void 1216 * @since 0.1.2 1217 */ 1218 function setTimeOut($newTimeOut) 1219 { 1220 $this->timeout = (int)$newTimeOut; 1221 } 1222 1223 /** 1224 * Returns the connection timeout (in seconds) 1225 * @returns int 1226 * @since 0.1.2 1227 */ 1228 function getTimeOut() 1229 { 1230 return $this->timeout; 1231 } 1232 1233 /** 1234 * Set the query to send to the XML-RPC Server 1235 * @since 0.1.0 1236 */ 1237 function query() 1238 { 1239 $args = func_get_args(); 1240 $method = array_shift($args); 1241 $request = new IXR_Request($method, $args); 1242 $length = $request->getLength(); 1243 $xml = $request->getXml(); 1244 1245 if ($this->debug) { 1246 echo '<pre>'.htmlspecialchars($xml)."\n</pre>\n\n"; 1247 } 1248 1249 //This is where we deviate from the normal query() 1250 //Rather than open a normal sock, we will actually use the cURL 1251 //extensions to make the calls, and handle the SSL stuff. 1252 1253 //Since 04Aug2004 (0.1.3) - Need to include the port (duh...) 1254 //Since 06Oct2004 (0.1.4) - Need to include the colon!!! 1255 // (I swear I've fixed this before... ESP in live... But anyhu...) 1256 $curl=curl_init('https://' . $this->server . ':' . $this->port . $this->path); 1257 curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); 1258 1259 //Since 23Jun2004 (0.1.2) - Made timeout a class field 1260 curl_setopt($curl, CURLOPT_TIMEOUT, $this->timeout); 1261 1262 if ($this->debug) { 1263 curl_setopt($curl, CURLOPT_VERBOSE, 1); 1264 } 1265 1266 curl_setopt($curl, CURLOPT_HEADER, 1); 1267 curl_setopt($curl, CURLOPT_POST, 1); 1268 curl_setopt($curl, CURLOPT_POSTFIELDS, $xml); 1269 curl_setopt($curl, CURLOPT_PORT, $this->port); 1270 curl_setopt($curl, CURLOPT_HTTPHEADER, array( 1271 "Content-Type: text/xml", 1272 "Content-length: {$length}")); 1273 1274 // Process the SSL certificates, etc. to use 1275 if (!($this->_certFile === false)) { 1276 // We have a certificate file set, so add these to the cURL handler 1277 curl_setopt($curl, CURLOPT_SSLCERT, $this->_certFile); 1278 curl_setopt($curl, CURLOPT_SSLKEY, $this->_keyFile); 1279 1280 if ($this->debug) { 1281 echo "SSL Cert at : " . $this->_certFile . "\n"; 1282 echo "SSL Key at : " . $this->_keyFile . "\n"; 1283 } 1284 1285 // See if we need to give a passphrase 1286 if (!($this->_passphrase === '')) { 1287 curl_setopt($curl, CURLOPT_SSLCERTPASSWD, $this->_passphrase); 1288 } 1289 1290 if ($this->_caFile === false) { 1291 // Don't verify their certificate, as we don't have a CA to verify against 1292 curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, 0); 1293 } else { 1294 // Verify against a CA 1295 curl_setopt($curl, CURLOPT_CAINFO, $this->_caFile); 1296 } 1297 } 1298 1299 // Call cURL to do it's stuff and return us the content 1300 $contents = curl_exec($curl); 1301 curl_close($curl); 1302 1303 // Check for 200 Code in $contents 1304 if (!strstr($contents, '200 OK')) { 1305 //There was no "200 OK" returned - we failed 1306 $this->error = new IXR_Error(-32300, 'transport error - HTTP status code was not 200'); 1307 return false; 1308 } 1309 1310 if ($this->debug) { 1311 echo '<pre>'.htmlspecialchars($contents)."\n</pre>\n\n"; 1312 } 1313 // Now parse what we've got back 1314 // Since 20Jun2004 (0.1.1) - We need to remove the headers first 1315 // Why I have only just found this, I will never know... 1316 // So, remove everything before the first < 1317 $contents = substr($contents,strpos($contents, '<')); 1318 1319 $this->message = new IXR_Message($contents); 1320 if (!$this->message->parse()) { 1321 // XML error 1322 $this->error = new IXR_Error(-32700, 'parse error. not well formed'); 1323 return false; 1324 } 1325 // Is the message a fault? 1326 if ($this->message->messageType == 'fault') { 1327 $this->error = new IXR_Error($this->message->faultCode, $this->message->faultString); 1328 return false; 1329 } 1330 1331 // Message must be OK 1332 return true; 1333 } 1334 } 1335 1336 /** 1337 * Extension of the {@link IXR_Server} class to easily wrap objects. 1338 * 1339 * Class is designed to extend the existing XML-RPC server to allow the 1340 * presentation of methods from a variety of different objects via an 1341 * XML-RPC server. 1342 * It is intended to assist in organization of your XML-RPC methods by allowing 1343 * you to "write once" in your existing model classes and present them. 1344 * 1345 * @author Jason Stirk <jstirk@gmm.com.au> 1346 * @version 1.0.1 19Apr2005 17:40 +0800 1347 * @copyright Copyright (c) 2005 Jason Stirk 1348 * @package IXR 1349 */ 1350 class IXR_ClassServer extends IXR_Server 1351 { 1352 var $_objects; 1353 var $_delim; 1354 1355 function __construct($delim = '.', $wait = false) 1356 { 1357 $this->IXR_Server(array(), false, $wait); 1358 $this->_delimiter = $delim; 1359 $this->_objects = array(); 1360 } 1361 1362 function addMethod($rpcName, $functionName) 1363 { 1364 $this->callbacks[$rpcName] = $functionName; 1365 } 1366 1367 function registerObject($object, $methods, $prefix=null) 1368 { 1369 if (is_null($prefix)) 1370 { 1371 $prefix = get_class($object); 1372 } 1373 $this->_objects[$prefix] = $object; 1374 1375 // Add to our callbacks array 1376 foreach($methods as $method) 1377 { 1378 if (is_array($method)) 1379 { 1380 $targetMethod = $method[0]; 1381 $method = $method[1]; 1382 } 1383 else 1384 { 1385 $targetMethod = $method; 1386 } 1387 $this->callbacks[$prefix . $this->_delimiter . $method]=array($prefix, $targetMethod); 1388 } 1389 } 1390 1391 function call($methodname, $args) 1392 { 1393 if (!$this->hasMethod($methodname)) { 1394 return new IXR_Error(-32601, 'server error. requested method '.$methodname.' does not exist.'); 1395 } 1396 $method = $this->callbacks[$methodname]; 1397 1398 // Perform the callback and send the response 1399 if (count($args) == 1) { 1400 // If only one parameter just send that instead of the whole array 1401 $args = $args[0]; 1402 } 1403 1404 // See if this method comes from one of our objects or maybe self 1405 if (is_array($method) || (substr($method, 0, 5) == 'this:')) { 1406 if (is_array($method)) { 1407 $object=$this->_objects[$method[0]]; 1408 $method=$method[1]; 1409 } else { 1410 $object=$this; 1411 $method = substr($method, 5); 1412 } 1413 1414 // It's a class method - check it exists 1415 if (!method_exists($object, $method)) { 1416 return new IXR_Error(-32601, 'server error. requested class method "'.$method.'" does not exist.'); 1417 } 1418 1419 // Call the method 1420 $result = $object->$method($args); 1421 } else { 1422 // It's a function - does it exist? 1423 if (!function_exists($method)) { 1424 return new IXR_Error(-32601, 'server error. requested function "'.$method.'" does not exist.'); 1425 } 1426 1427 // Call the function 1428 $result = $method($args); 1429 } 1430 return $result; 1431 } 1432 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
title