/Web/wp-includes/class-IXR.php
PHP | 1063 lines | 812 code | 88 blank | 163 comment | 104 complexity | 1b1285d3ee550c83329c3fce5aa5d407 MD5 | raw file
Possible License(s): GPL-2.0, GPL-3.0, AGPL-1.0, LGPL-2.1
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 34 * 35 * @copyright Incutio Ltd 2010 (http://www.incutio.com) 36 * @version 1.7.4 7th September 2010 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 47 */ 48class IXR_Value { 49 var $data; 50 var $type; 51 52 function IXR_Value($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 175 * 176 */ 177class 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 IXR_Message($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.*?\?'.'>/', '', substr($this->message, 0, 100), 1); 207 $this->message = substr_replace($this->message, $header, 0, 100); 208 if (trim($this->message) == '') { 209 return false; 210 } 211 $this->_parser = xml_parser_create(); 212 // Set XML parser to take the case of tags in to account 213 xml_parser_set_option($this->_parser, XML_OPTION_CASE_FOLDING, false); 214 // Set XML parser callback functions 215 xml_set_object($this->_parser, $this); 216 xml_set_element_handler($this->_parser, 'tag_open', 'tag_close'); 217 xml_set_character_data_handler($this->_parser, 'cdata'); 218 $chunk_size = 262144; // 256Kb, parse in chunks to avoid the RAM usage on very large messages 219 $final = false; 220 do { 221 if (strlen($this->message) <= $chunk_size) { 222 $final = true; 223 } 224 $part = substr($this->message, 0, $chunk_size); 225 $this->message = substr($this->message, $chunk_size); 226 if (!xml_parse($this->_parser, $part, $final)) { 227 return false; 228 } 229 if ($final) { 230 break; 231 } 232 } while (true); 233 xml_parser_free($this->_parser); 234 235 // Grab the error messages, if any 236 if ($this->messageType == 'fault') { 237 $this->faultCode = $this->params[0]['faultCode']; 238 $this->faultString = $this->params[0]['faultString']; 239 } 240 return true; 241 } 242 243 function tag_open($parser, $tag, $attr) 244 { 245 $this->_currentTagContents = ''; 246 $this->currentTag = $tag; 247 switch($tag) { 248 case 'methodCall': 249 case 'methodResponse': 250 case 'fault': 251 $this->messageType = $tag; 252 break; 253 /* Deal with stacks of arrays and structs */ 254 case 'data': // data is to all intents and puposes more interesting than array 255 $this->_arraystructstypes[] = 'array'; 256 $this->_arraystructs[] = array(); 257 break; 258 case 'struct': 259 $this->_arraystructstypes[] = 'struct'; 260 $this->_arraystructs[] = array(); 261 break; 262 } 263 } 264 265 function cdata($parser, $cdata) 266 { 267 $this->_currentTagContents .= $cdata; 268 } 269 270 function tag_close($parser, $tag) 271 { 272 $valueFlag = false; 273 switch($tag) { 274 case 'int': 275 case 'i4': 276 $value = (int)trim($this->_currentTagContents); 277 $valueFlag = true; 278 break; 279 case 'double': 280 $value = (double)trim($this->_currentTagContents); 281 $valueFlag = true; 282 break; 283 case 'string': 284 $value = (string)trim($this->_currentTagContents); 285 $valueFlag = true; 286 break; 287 case 'dateTime.iso8601': 288 $value = new IXR_Date(trim($this->_currentTagContents)); 289 $valueFlag = true; 290 break; 291 case 'value': 292 // "If no type is indicated, the type is string." 293 if (trim($this->_currentTagContents) != '') { 294 $value = (string)$this->_currentTagContents; 295 $valueFlag = true; 296 } 297 break; 298 case 'boolean': 299 $value = (boolean)trim($this->_currentTagContents); 300 $valueFlag = true; 301 break; 302 case 'base64': 303 $value = base64_decode($this->_currentTagContents); 304 $valueFlag = true; 305 break; 306 /* Deal with stacks of arrays and structs */ 307 case 'data': 308 case 'struct': 309 $value = array_pop($this->_arraystructs); 310 array_pop($this->_arraystructstypes); 311 $valueFlag = true; 312 break; 313 case 'member': 314 array_pop($this->_currentStructName); 315 break; 316 case 'name': 317 $this->_currentStructName[] = trim($this->_currentTagContents); 318 break; 319 case 'methodName': 320 $this->methodName = trim($this->_currentTagContents); 321 break; 322 } 323 324 if ($valueFlag) { 325 if (count($this->_arraystructs) > 0) { 326 // Add value to struct or array 327 if ($this->_arraystructstypes[count($this->_arraystructstypes)-1] == 'struct') { 328 // Add to struct 329 $this->_arraystructs[count($this->_arraystructs)-1][$this->_currentStructName[count($this->_currentStructName)-1]] = $value; 330 } else { 331 // Add to array 332 $this->_arraystructs[count($this->_arraystructs)-1][] = $value; 333 } 334 } else { 335 // Just add as a paramater 336 $this->params[] = $value; 337 } 338 } 339 $this->_currentTagContents = ''; 340 } 341} 342 343/** 344 * IXR_Server 345 * 346 * @package IXR 347 * @since 1.5 348 */ 349class IXR_Server 350{ 351 var $data; 352 var $callbacks = array(); 353 var $message; 354 var $capabilities; 355 356 function IXR_Server($callbacks = false, $data = false, $wait = false) 357 { 358 $this->setCapabilities(); 359 if ($callbacks) { 360 $this->callbacks = $callbacks; 361 } 362 $this->setCallbacks(); 363 if (!$wait) { 364 $this->serve($data); 365 } 366 } 367 368 function serve($data = false) 369 { 370 if (!$data) { 371 if (isset($_SERVER['REQUEST_METHOD']) && $_SERVER['REQUEST_METHOD'] !== 'POST') { 372 header('Content-Type: text/plain'); // merged from WP #9093 373 die('XML-RPC server accepts POST requests only.'); 374 } 375 376 global $HTTP_RAW_POST_DATA; 377 if (empty($HTTP_RAW_POST_DATA)) { 378 // workaround for a bug in PHP 5.2.2 - http://bugs.php.net/bug.php?id=41293 379 $data = file_get_contents('php://input'); 380 } else { 381 $data =& $HTTP_RAW_POST_DATA; 382 } 383 } 384 $this->message = new IXR_Message($data); 385 if (!$this->message->parse()) { 386 $this->error(-32700, 'parse error. not well formed'); 387 } 388 if ($this->message->messageType != 'methodCall') { 389 $this->error(-32600, 'server error. invalid xml-rpc. not conforming to spec. Request must be a methodCall'); 390 } 391 $result = $this->call($this->message->methodName, $this->message->params); 392 393 // Is the result an error? 394 if (is_a($result, 'IXR_Error')) { 395 $this->error($result); 396 } 397 398 // Encode the result 399 $r = new IXR_Value($result); 400 $resultxml = $r->getXml(); 401 402 // Create the XML 403 $xml = <<<EOD 404<methodResponse> 405 <params> 406 <param> 407 <value> 408 $resultxml 409 </value> 410 </param> 411 </params> 412</methodResponse> 413 414EOD; 415 // Send it 416 $this->output($xml); 417 } 418 419 function call($methodname, $args) 420 { 421 if (!$this->hasMethod($methodname)) { 422 return new IXR_Error(-32601, 'server error. requested method '.$methodname.' does not exist.'); 423 } 424 $method = $this->callbacks[$methodname]; 425 426 // Perform the callback and send the response 427 if (count($args) == 1) { 428 // If only one paramater just send that instead of the whole array 429 $args = $args[0]; 430 } 431 432 // Are we dealing with a function or a method? 433 if (is_string($method) && substr($method, 0, 5) == 'this:') { 434 // It's a class method - check it exists 435 $method = substr($method, 5); 436 if (!method_exists($this, $method)) { 437 return new IXR_Error(-32601, 'server error. requested class method "'.$method.'" does not exist.'); 438 } 439 440 //Call the method 441 $result = $this->$method($args); 442 } else { 443 // It's a function - does it exist? 444 if (is_array($method)) { 445 if (!is_callable(array($method[0], $method[1]))) { 446 return new IXR_Error(-32601, 'server error. requested object method "'.$method[1].'" does not exist.'); 447 } 448 } else if (!function_exists($method)) { 449 return new IXR_Error(-32601, 'server error. requested function "'.$method.'" does not exist.'); 450 } 451 452 // Call the function 453 $result = call_user_func($method, $args); 454 } 455 return $result; 456 } 457 458 function error($error, $message = false) 459 { 460 // Accepts either an error object or an error code and message 461 if ($message && !is_object($error)) { 462 $error = new IXR_Error($error, $message); 463 } 464 $this->output($error->getXml()); 465 } 466 467 function output($xml) 468 { 469 $xml = '<?xml version="1.0"?>'."\n".$xml; 470 $length = strlen($xml); 471 header('Connection: close'); 472 header('Content-Length: '.$length); 473 header('Content-Type: text/xml'); 474 header('Date: '.date('r')); 475 echo $xml; 476 exit; 477 } 478 479 function hasMethod($method) 480 { 481 return in_array($method, array_keys($this->callbacks)); 482 } 483 484 function setCapabilities() 485 { 486 // Initialises capabilities array 487 $this->capabilities = array( 488 'xmlrpc' => array( 489 'specUrl' => 'http://www.xmlrpc.com/spec', 490 'specVersion' => 1 491 ), 492 'faults_interop' => array( 493 'specUrl' => 'http://xmlrpc-epi.sourceforge.net/specs/rfc.fault_codes.php', 494 'specVersion' => 20010516 495 ), 496 'system.multicall' => array( 497 'specUrl' => 'http://www.xmlrpc.com/discuss/msgReader$1208', 498 'specVersion' => 1 499 ), 500 ); 501 } 502 503 function getCapabilities($args) 504 { 505 return $this->capabilities; 506 } 507 508 function setCallbacks() 509 { 510 $this->callbacks['system.getCapabilities'] = 'this:getCapabilities'; 511 $this->callbacks['system.listMethods'] = 'this:listMethods'; 512 $this->callbacks['system.multicall'] = 'this:multiCall'; 513 } 514 515 function listMethods($args) 516 { 517 // Returns a list of methods - uses array_reverse to ensure user defined 518 // methods are listed before server defined methods 519 return array_reverse(array_keys($this->callbacks)); 520 } 521 522 function multiCall($methodcalls) 523 { 524 // See http://www.xmlrpc.com/discuss/msgReader$1208 525 $return = array(); 526 foreach ($methodcalls as $call) { 527 $method = $call['methodName']; 528 $params = $call['params']; 529 if ($method == 'system.multicall') { 530 $result = new IXR_Error(-32600, 'Recursive calls to system.multicall are forbidden'); 531 } else { 532 $result = $this->call($method, $params); 533 } 534 if (is_a($result, 'IXR_Error')) { 535 $return[] = array( 536 'faultCode' => $result->code, 537 'faultString' => $result->message 538 ); 539 } else { 540 $return[] = array($result); 541 } 542 } 543 return $return; 544 } 545} 546 547/** 548 * IXR_Request 549 * 550 * @package IXR 551 * @since 1.5 552 */ 553class IXR_Request 554{ 555 var $method; 556 var $args; 557 var $xml; 558 559 function IXR_Request($method, $args) 560 { 561 $this->method = $method; 562 $this->args = $args; 563 $this->xml = <<<EOD 564<?xml version="1.0"?> 565<methodCall> 566<methodName>{$this->method}</methodName> 567<params> 568 569EOD; 570 foreach ($this->args as $arg) { 571 $this->xml .= '<param><value>'; 572 $v = new IXR_Value($arg); 573 $this->xml .= $v->getXml(); 574 $this->xml .= "</value></param>\n"; 575 } 576 $this->xml .= '</params></methodCall>'; 577 } 578 579 function getLength() 580 { 581 return strlen($this->xml); 582 } 583 584 function getXml() 585 { 586 return $this->xml; 587 } 588} 589 590/** 591 * IXR_Client 592 * 593 * @package IXR 594 * @since 1.5 595 * 596 */ 597class IXR_Client 598{ 599 var $server; 600 var $port; 601 var $path; 602 var $useragent; 603 var $response; 604 var $message = false; 605 var $debug = false; 606 var $timeout; 607 var $headers = array(); 608 609 // Storage place for an error message 610 var $error = false; 611 612 function IXR_Client($server, $path = false, $port = 80, $timeout = 15) 613 { 614 if (!$path) { 615 // Assume we have been given a URL instead 616 $bits = parse_url($server); 617 $this->server = $bits['host']; 618 $this->port = isset($bits['port']) ? $bits['port'] : 80; 619 $this->path = isset($bits['path']) ? $bits['path'] : '/'; 620 621 // Make absolutely sure we have a path 622 if (!$this->path) { 623 $this->path = '/'; 624 } 625 } else { 626 $this->server = $server; 627 $this->path = $path; 628 $this->port = $port; 629 } 630 $this->useragent = 'The Incutio XML-RPC PHP Library'; 631 $this->timeout = $timeout; 632 } 633 634 function query() 635 { 636 $args = func_get_args(); 637 $method = array_shift($args); 638 $request = new IXR_Request($method, $args); 639 $length = $request->getLength(); 640 $xml = $request->getXml(); 641 $r = "\r\n"; 642 $request = "POST {$this->path} HTTP/1.0$r"; 643 644 // Merged from WP #8145 - allow custom headers 645 $this->headers['Host'] = $this->server; 646 $this->headers['Content-Type'] = 'text/xml'; 647 $this->headers['User-Agent'] = $this->useragent; 648 $this->headers['Content-Length']= $length; 649 650 foreach( $this->headers as $header => $value ) { 651 $request .= "{$header}: {$value}{$r}"; 652 } 653 $request .= $r; 654 655 $request .= $xml; 656 657 // Now send the request 658 if ($this->debug) { 659 echo '<pre class="ixr_request">'.htmlspecialchars($request)."\n</pre>\n\n"; 660 } 661 662 if ($this->timeout) { 663 $fp = @fsockopen($this->server, $this->port, $errno, $errstr, $this->timeout); 664 } else { 665 $fp = @fsockopen($this->server, $this->port, $errno, $errstr); 666 } 667 if (!$fp) { 668 $this->error = new IXR_Error(-32300, 'transport error - could not open socket'); 669 return false; 670 } 671 fputs($fp, $request); 672 $contents = ''; 673 $debugContents = ''; 674 $gotFirstLine = false; 675 $gettingHeaders = true; 676 while (!feof($fp)) { 677 $line = fgets($fp, 4096); 678 if (!$gotFirstLine) { 679 // Check line for '200' 680 if (strstr($line, '200') === false) { 681 $this->error = new IXR_Error(-32300, 'transport error - HTTP status code was not 200'); 682 return false; 683 } 684 $gotFirstLine = true; 685 } 686 if (trim($line) == '') { 687 $gettingHeaders = false; 688 } 689 if (!$gettingHeaders) { 690 // merged from WP #12559 - remove trim 691 $contents .= $line; 692 } 693 if ($this->debug) { 694 $debugContents .= $line; 695 } 696 } 697 if ($this->debug) { 698 echo '<pre class="ixr_response">'.htmlspecialchars($debugContents)."\n</pre>\n\n"; 699 } 700 701 // Now parse what we've got back 702 $this->message = new IXR_Message($contents); 703 if (!$this->message->parse()) { 704 // XML error 705 $this->error = new IXR_Error(-32700, 'parse error. not well formed'); 706 return false; 707 } 708 709 // Is the message a fault? 710 if ($this->message->messageType == 'fault') { 711 $this->error = new IXR_Error($this->message->faultCode, $this->message->faultString); 712 return false; 713 } 714 715 // Message must be OK 716 return true; 717 } 718 719 function getResponse() 720 { 721 // methodResponses can only have one param - return that 722 return $this->message->params[0]; 723 } 724 725 function isError() 726 { 727 return (is_object($this->error)); 728 } 729 730 function getErrorCode() 731 { 732 return $this->error->code; 733 } 734 735 function getErrorMessage() 736 { 737 return $this->error->message; 738 } 739} 740 741 742/** 743 * IXR_Error 744 * 745 * @package IXR 746 * @since 1.5 747 */ 748class IXR_Error 749{ 750 var $code; 751 var $message; 752 753 function IXR_Error($code, $message) 754 { 755 $this->code = $code; 756 $this->message = htmlspecialchars($message); 757 } 758 759 function getXml() 760 { 761 $xml = <<<EOD 762<methodResponse> 763 <fault> 764 <value> 765 <struct> 766 <member> 767 <name>faultCode</name> 768 <value><int>{$this->code}</int></value> 769 </member> 770 <member> 771 <name>faultString</name> 772 <value><string>{$this->message}</string></value> 773 </member> 774 </struct> 775 </value> 776 </fault> 777</methodResponse> 778 779EOD; 780 return $xml; 781 } 782} 783 784/** 785 * IXR_Date 786 * 787 * @package IXR 788 * @since 1.5 789 */ 790class IXR_Date { 791 var $year; 792 var $month; 793 var $day; 794 var $hour; 795 var $minute; 796 var $second; 797 var $timezone; 798 799 function IXR_Date($time) 800 { 801 // $time can be a PHP timestamp or an ISO one 802 if (is_numeric($time)) { 803 $this->parseTimestamp($time); 804 } else { 805 $this->parseIso($time); 806 } 807 } 808 809 function parseTimestamp($timestamp) 810 { 811 $this->year = date('Y', $timestamp); 812 $this->month = date('m', $timestamp); 813 $this->day = date('d', $timestamp); 814 $this->hour = date('H', $timestamp); 815 $this->minute = date('i', $timestamp); 816 $this->second = date('s', $timestamp); 817 $this->timezone = ''; 818 } 819 820 function parseIso($iso) 821 { 822 $this->year = substr($iso, 0, 4); 823 $this->month = substr($iso, 4, 2); 824 $this->day = substr($iso, 6, 2); 825 $this->hour = substr($iso, 9, 2); 826 $this->minute = substr($iso, 12, 2); 827 $this->second = substr($iso, 15, 2); 828 $this->timezone = substr($iso, 17); 829 } 830 831 function getIso() 832 { 833 return $this->year.$this->month.$this->day.'T'.$this->hour.':'.$this->minute.':'.$this->second.$this->timezone; 834 } 835 836 function getXml() 837 { 838 return '<dateTime.iso8601>'.$this->getIso().'</dateTime.iso8601>'; 839 } 840 841 function getTimestamp() 842 { 843 return mktime($this->hour, $this->minute, $this->second, $this->month, $this->day, $this->year); 844 } 845} 846 847/** 848 * IXR_Base64 849 * 850 * @package IXR 851 * @since 1.5 852 */ 853class IXR_Base64 854{ 855 var $data; 856 857 function IXR_Base64($data) 858 { 859 $this->data = $data; 860 } 861 862 function getXml() 863 { 864 return '<base64>'.base64_encode($this->data).'</base64>'; 865 } 866} 867 868/** 869 * IXR_IntrospectionServer 870 * 871 * @package IXR 872 * @since 1.5 873 */ 874class IXR_IntrospectionServer extends IXR_Server 875{ 876 var $signatures; 877 var $help; 878 879 function IXR_IntrospectionServer() 880 { 881 $this->setCallbacks(); 882 $this->setCapabilities(); 883 $this->capabilities['introspection'] = array( 884 'specUrl' => 'http://xmlrpc.usefulinc.com/doc/reserved.html', 885 'specVersion' => 1 886 ); 887 $this->addCallback( 888 'system.methodSignature', 889 'this:methodSignature', 890 array('array', 'string'), 891 'Returns an array describing the return type and required parameters of a method' 892 ); 893 $this->addCallback( 894 'system.getCapabilities', 895 'this:getCapabilities', 896 array('struct'), 897 'Returns a struct describing the XML-RPC specifications supported by this server' 898 ); 899 $this->addCallback( 900 'system.listMethods', 901 'this:listMethods', 902 array('array'), 903 'Returns an array of available methods on this server' 904 ); 905 $this->addCallback( 906 'system.methodHelp', 907 'this:methodHelp', 908 array('string', 'string'), 909 'Returns a documentation string for the specified method' 910 ); 911 } 912 913 function addCallback($method, $callback, $args, $help) 914 { 915 $this->callbacks[$method] = $callback; 916 $this->signatures[$method] = $args; 917 $this->help[$method] = $help; 918 } 919 920 function call($methodname, $args) 921 { 922 // Make sure it's in an array 923 if ($args && !is_array($args)) { 924 $args = array($args); 925 } 926 927 // Over-rides default call method, adds signature check 928 if (!$this->hasMethod($methodname)) { 929 return new IXR_Error(-32601, 'server error. requested method "'.$this->message->methodName.'" not specified.'); 930 } 931 $method = $this->callbacks[$methodname]; 932 $signature = $this->signatures[$methodname]; 933 $returnType = array_shift($signature); 934 935 // Check the number of arguments 936 if (count($args) != count($signature)) { 937 return new IXR_Error(-32602, 'server error. wrong number of method parameters'); 938 } 939 940 // Check the argument types 941 $ok = true; 942 $argsbackup = $args; 943 for ($i = 0, $j = count($args); $i < $j; $i++) { 944 $arg = array_shift($args); 945 $type = array_shift($signature); 946 switch ($type) { 947 case 'int': 948 case 'i4': 949 if (is_array($arg) || !is_int($arg)) { 950 $ok = false; 951 } 952 break; 953 case 'base64': 954 case 'string': 955 if (!is_string($arg)) { 956 $ok = false; 957 } 958 break; 959 case 'boolean': 960 if ($arg !== false && $arg !== true) { 961 $ok = false; 962 } 963 break; 964 case 'float': 965 case 'double': 966 if (!is_float($arg)) { 967 $ok = false; 968 } 969 break; 970 case 'date': 971 case 'dateTime.iso8601': 972 if (!is_a($arg, 'IXR_Date')) { 973 $ok = false; 974 } 975 break; 976 } 977 if (!$ok) { 978 return new IXR_Error(-32602, 'server error. invalid method parameters'); 979 } 980 } 981 // It passed the test - run the "real" method call 982 return parent::call($methodname, $argsbackup); 983 } 984 985 function methodSignature($method) 986 { 987 if (!$this->hasMethod($method)) { 988 return new IXR_Error(-32601, 'server error. requested method "'.$method.'" not specified.'); 989 } 990 // We should be returning an array of types 991 $types = $this->signatures[$method]; 992 $return = array(); 993 foreach ($types as $type) { 994 switch ($type) { 995 case 'string': 996 $return[] = 'string'; 997 break; 998 case 'int': 999 case 'i4': 1000 $return[] = 42; 1001 break; 1002 case 'double': 1003 $return[] = 3.1415; 1004 break; 1005 case 'dateTime.iso8601': 1006 $return[] = new IXR_Date(time()); 1007 break; 1008 case 'boolean': 1009 $return[] = true; 1010 break; 1011 case 'base64': 1012 $return[] = new IXR_Base64('base64'); 1013 break; 1014 case 'array': 1015 $return[] = array('array'); 1016 break; 1017 case 'struct': 1018 $return[] = array('struct' => 'struct'); 1019 break; 1020 } 1021 } 1022 return $return; 1023 } 1024 1025 function methodHelp($method) 1026 { 1027 return $this->help[$method]; 1028 } 1029} 1030 1031/** 1032 * IXR_ClientMulticall 1033 * 1034 * @package IXR 1035 * @since 1.5 1036 */ 1037class IXR_ClientMulticall extends IXR_Client 1038{ 1039 var $calls = array(); 1040 1041 function IXR_ClientMulticall($server, $path = false, $port = 80) 1042 { 1043 parent::IXR_Client($server, $path, $port); 1044 $this->useragent = 'The Incutio XML-RPC PHP Library (multicall client)'; 1045 } 1046 1047 function addCall() 1048 { 1049 $args = func_get_args(); 1050 $methodName = array_shift($args); 1051 $struct = array( 1052 'methodName' => $methodName, 1053 'params' => $args 1054 ); 1055 $this->calls[] = $struct; 1056 } 1057 1058 function query() 1059 { 1060 // Prepare multicall, then call the parent::query() method 1061 return parent::query('system.multicall', $this->calls); 1062 } 1063}