Changeset 38389
- Timestamp:
- 08/26/2016 10:07:14 PM (9 years ago)
- Location:
- trunk/src/wp-includes
- Files:
-
- 1 added
- 1 edited
- 10 copied
Legend:
- Unmodified
- Added
- Removed
-
trunk/src/wp-includes/IXR/class-IXR-base64.php
r38384 r38389 1 1 <?php 2 /**3 * IXR - The Incutio XML-RPC Library4 *5 * Copyright (c) 2010, Incutio Ltd.6 * All rights reserved.7 *8 * Redistribution and use in source and binary forms, with or without9 * 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 copyright14 * notice, this list of conditions and the following disclaimer in the15 * documentation and/or other materials provided with the distribution.16 * - Neither the name of Incutio Ltd. nor the names of its contributors17 * may be used to endorse or promote products derived from this software18 * without specific prior written permission.19 *20 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS21 * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,22 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR23 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR24 * 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, OR27 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY28 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT29 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE30 * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.31 *32 * @package IXR33 * @since 1.5.034 *35 * @copyright Incutio Ltd 2010 (http://www.incutio.com)36 * @version 1.7.4 7th September 201037 * @author Simon Willison38 * @link http://scripts.incutio.com/xmlrpc/ Site/manual39 * @license http://www.opensource.org/licenses/bsd-license.php BSD40 */41 42 /**43 * IXR_Value44 *45 * @package IXR46 * @since 1.5.047 */48 class IXR_Value {49 var $data;50 var $type;51 52 /**53 * PHP5 constructor.54 */55 function __construct( $data, $type = false )56 {57 $this->data = $data;58 if (!$type) {59 $type = $this->calculateType();60 }61 $this->type = $type;62 if ($type == 'struct') {63 // Turn all the values in the array in to new IXR_Value objects64 foreach ($this->data as $key => $value) {65 $this->data[$key] = new IXR_Value($value);66 }67 }68 if ($type == 'array') {69 for ($i = 0, $j = count($this->data); $i < $j; $i++) {70 $this->data[$i] = new IXR_Value($this->data[$i]);71 }72 }73 }74 75 /**76 * PHP4 constructor.77 */78 public function IXR_Value( $data, $type = false ) {79 self::__construct( $data, $type );80 }81 82 function calculateType()83 {84 if ($this->data === true || $this->data === false) {85 return 'boolean';86 }87 if (is_integer($this->data)) {88 return 'int';89 }90 if (is_double($this->data)) {91 return 'double';92 }93 94 // Deal with IXR object types base64 and date95 if (is_object($this->data) && is_a($this->data, 'IXR_Date')) {96 return 'date';97 }98 if (is_object($this->data) && is_a($this->data, 'IXR_Base64')) {99 return 'base64';100 }101 102 // If it is a normal PHP object convert it in to a struct103 if (is_object($this->data)) {104 $this->data = get_object_vars($this->data);105 return 'struct';106 }107 if (!is_array($this->data)) {108 return 'string';109 }110 111 // We have an array - is it an array or a struct?112 if ($this->isStruct($this->data)) {113 return 'struct';114 } else {115 return 'array';116 }117 }118 119 function getXml()120 {121 // Return XML for this value122 switch ($this->type) {123 case 'boolean':124 return '<boolean>'.(($this->data) ? '1' : '0').'</boolean>';125 break;126 case 'int':127 return '<int>'.$this->data.'</int>';128 break;129 case 'double':130 return '<double>'.$this->data.'</double>';131 break;132 case 'string':133 return '<string>'.htmlspecialchars($this->data).'</string>';134 break;135 case 'array':136 $return = '<array><data>'."\n";137 foreach ($this->data as $item) {138 $return .= ' <value>'.$item->getXml()."</value>\n";139 }140 $return .= '</data></array>';141 return $return;142 break;143 case 'struct':144 $return = '<struct>'."\n";145 foreach ($this->data as $name => $value) {146 $name = htmlspecialchars($name);147 $return .= " <member><name>$name</name><value>";148 $return .= $value->getXml()."</value></member>\n";149 }150 $return .= '</struct>';151 return $return;152 break;153 case 'date':154 case 'base64':155 return $this->data->getXml();156 break;157 }158 return false;159 }160 161 /**162 * Checks whether or not the supplied array is a struct or not163 *164 * @param array $array165 * @return bool166 */167 function isStruct($array)168 {169 $expected = 0;170 foreach ($array as $key => $value) {171 if ((string)$key !== (string)$expected) {172 return true;173 }174 $expected++;175 }176 return false;177 }178 }179 180 /**181 * IXR_MESSAGE182 *183 * @package IXR184 * @since 1.5.0185 *186 */187 class IXR_Message188 {189 var $message;190 var $messageType; // methodCall / methodResponse / fault191 var $faultCode;192 var $faultString;193 var $methodName;194 var $params;195 196 // Current variable stacks197 var $_arraystructs = array(); // The stack used to keep track of the current array/struct198 var $_arraystructstypes = array(); // Stack keeping track of if things are structs or array199 var $_currentStructName = array(); // A stack as well200 var $_param;201 var $_value;202 var $_currentTag;203 var $_currentTagContents;204 // The XML parser205 var $_parser;206 207 /**208 * PHP5 constructor.209 */210 function __construct( $message )211 {212 $this->message =& $message;213 }214 215 /**216 * PHP4 constructor.217 */218 public function IXR_Message( $message ) {219 self::__construct( $message );220 }221 222 function parse()223 {224 // first remove the XML declaration225 // merged from WP #10698 - this method avoids the RAM usage of preg_replace on very large messages226 $header = preg_replace( '/<\?xml.*?\?'.'>/s', '', substr( $this->message, 0, 100 ), 1 );227 $this->message = trim( substr_replace( $this->message, $header, 0, 100 ) );228 if ( '' == $this->message ) {229 return false;230 }231 232 // Then remove the DOCTYPE233 $header = preg_replace( '/^<!DOCTYPE[^>]*+>/i', '', substr( $this->message, 0, 200 ), 1 );234 $this->message = trim( substr_replace( $this->message, $header, 0, 200 ) );235 if ( '' == $this->message ) {236 return false;237 }238 239 // Check that the root tag is valid240 $root_tag = substr( $this->message, 0, strcspn( substr( $this->message, 0, 20 ), "> \t\r\n" ) );241 if ( '<!DOCTYPE' === strtoupper( $root_tag ) ) {242 return false;243 }244 if ( ! in_array( $root_tag, array( '<methodCall', '<methodResponse', '<fault' ) ) ) {245 return false;246 }247 248 // Bail if there are too many elements to parse249 $element_limit = 30000;250 if ( function_exists( 'apply_filters' ) ) {251 /**252 * Filters the number of elements to parse in an XML-RPC response.253 *254 * @since 4.0.0255 *256 * @param int $element_limit Default elements limit.257 */258 $element_limit = apply_filters( 'xmlrpc_element_limit', $element_limit );259 }260 if ( $element_limit && 2 * $element_limit < substr_count( $this->message, '<' ) ) {261 return false;262 }263 264 $this->_parser = xml_parser_create();265 // Set XML parser to take the case of tags in to account266 xml_parser_set_option($this->_parser, XML_OPTION_CASE_FOLDING, false);267 // Set XML parser callback functions268 xml_set_object($this->_parser, $this);269 xml_set_element_handler($this->_parser, 'tag_open', 'tag_close');270 xml_set_character_data_handler($this->_parser, 'cdata');271 272 // 256Kb, parse in chunks to avoid the RAM usage on very large messages273 $chunk_size = 262144;274 275 /**276 * Filters the chunk size that can be used to parse an XML-RPC reponse message.277 *278 * @since 4.4.0279 *280 * @param int $chunk_size Chunk size to parse in bytes.281 */282 $chunk_size = apply_filters( 'xmlrpc_chunk_parsing_size', $chunk_size );283 284 $final = false;285 do {286 if (strlen($this->message) <= $chunk_size) {287 $final = true;288 }289 $part = substr($this->message, 0, $chunk_size);290 $this->message = substr($this->message, $chunk_size);291 if (!xml_parse($this->_parser, $part, $final)) {292 return false;293 }294 if ($final) {295 break;296 }297 } while (true);298 xml_parser_free($this->_parser);299 300 // Grab the error messages, if any301 if ($this->messageType == 'fault') {302 $this->faultCode = $this->params[0]['faultCode'];303 $this->faultString = $this->params[0]['faultString'];304 }305 return true;306 }307 308 function tag_open($parser, $tag, $attr)309 {310 $this->_currentTagContents = '';311 $this->currentTag = $tag;312 switch($tag) {313 case 'methodCall':314 case 'methodResponse':315 case 'fault':316 $this->messageType = $tag;317 break;318 /* Deal with stacks of arrays and structs */319 case 'data': // data is to all intents and puposes more interesting than array320 $this->_arraystructstypes[] = 'array';321 $this->_arraystructs[] = array();322 break;323 case 'struct':324 $this->_arraystructstypes[] = 'struct';325 $this->_arraystructs[] = array();326 break;327 }328 }329 330 function cdata($parser, $cdata)331 {332 $this->_currentTagContents .= $cdata;333 }334 335 function tag_close($parser, $tag)336 {337 $valueFlag = false;338 switch($tag) {339 case 'int':340 case 'i4':341 $value = (int)trim($this->_currentTagContents);342 $valueFlag = true;343 break;344 case 'double':345 $value = (double)trim($this->_currentTagContents);346 $valueFlag = true;347 break;348 case 'string':349 $value = (string)trim($this->_currentTagContents);350 $valueFlag = true;351 break;352 case 'dateTime.iso8601':353 $value = new IXR_Date(trim($this->_currentTagContents));354 $valueFlag = true;355 break;356 case 'value':357 // "If no type is indicated, the type is string."358 if (trim($this->_currentTagContents) != '') {359 $value = (string)$this->_currentTagContents;360 $valueFlag = true;361 }362 break;363 case 'boolean':364 $value = (boolean)trim($this->_currentTagContents);365 $valueFlag = true;366 break;367 case 'base64':368 $value = base64_decode($this->_currentTagContents);369 $valueFlag = true;370 break;371 /* Deal with stacks of arrays and structs */372 case 'data':373 case 'struct':374 $value = array_pop($this->_arraystructs);375 array_pop($this->_arraystructstypes);376 $valueFlag = true;377 break;378 case 'member':379 array_pop($this->_currentStructName);380 break;381 case 'name':382 $this->_currentStructName[] = trim($this->_currentTagContents);383 break;384 case 'methodName':385 $this->methodName = trim($this->_currentTagContents);386 break;387 }388 389 if ($valueFlag) {390 if (count($this->_arraystructs) > 0) {391 // Add value to struct or array392 if ($this->_arraystructstypes[count($this->_arraystructstypes)-1] == 'struct') {393 // Add to struct394 $this->_arraystructs[count($this->_arraystructs)-1][$this->_currentStructName[count($this->_currentStructName)-1]] = $value;395 } else {396 // Add to array397 $this->_arraystructs[count($this->_arraystructs)-1][] = $value;398 }399 } else {400 // Just add as a parameter401 $this->params[] = $value;402 }403 }404 $this->_currentTagContents = '';405 }406 }407 408 /**409 * IXR_Server410 *411 * @package IXR412 * @since 1.5.0413 */414 class IXR_Server415 {416 var $data;417 var $callbacks = array();418 var $message;419 var $capabilities;420 421 /**422 * PHP5 constructor.423 */424 function __construct( $callbacks = false, $data = false, $wait = false )425 {426 $this->setCapabilities();427 if ($callbacks) {428 $this->callbacks = $callbacks;429 }430 $this->setCallbacks();431 if (!$wait) {432 $this->serve($data);433 }434 }435 436 /**437 * PHP4 constructor.438 */439 public function IXR_Server( $callbacks = false, $data = false, $wait = false ) {440 self::__construct( $callbacks, $data, $wait );441 }442 443 function serve($data = false)444 {445 if (!$data) {446 if (isset($_SERVER['REQUEST_METHOD']) && $_SERVER['REQUEST_METHOD'] !== 'POST') {447 if ( function_exists( 'status_header' ) ) {448 status_header( 405 ); // WP #20986449 header( 'Allow: POST' );450 }451 header('Content-Type: text/plain'); // merged from WP #9093452 die('XML-RPC server accepts POST requests only.');453 }454 455 global $HTTP_RAW_POST_DATA;456 if (empty($HTTP_RAW_POST_DATA)) {457 // workaround for a bug in PHP 5.2.2 - http://bugs.php.net/bug.php?id=41293458 $data = file_get_contents('php://input');459 } else {460 $data =& $HTTP_RAW_POST_DATA;461 }462 }463 $this->message = new IXR_Message($data);464 if (!$this->message->parse()) {465 $this->error(-32700, 'parse error. not well formed');466 }467 if ($this->message->messageType != 'methodCall') {468 $this->error(-32600, 'server error. invalid xml-rpc. not conforming to spec. Request must be a methodCall');469 }470 $result = $this->call($this->message->methodName, $this->message->params);471 472 // Is the result an error?473 if (is_a($result, 'IXR_Error')) {474 $this->error($result);475 }476 477 // Encode the result478 $r = new IXR_Value($result);479 $resultxml = $r->getXml();480 481 // Create the XML482 $xml = <<<EOD483 <methodResponse>484 <params>485 <param>486 <value>487 $resultxml488 </value>489 </param>490 </params>491 </methodResponse>492 493 EOD;494 // Send it495 $this->output($xml);496 }497 498 function call($methodname, $args)499 {500 if (!$this->hasMethod($methodname)) {501 return new IXR_Error(-32601, 'server error. requested method '.$methodname.' does not exist.');502 }503 $method = $this->callbacks[$methodname];504 505 // Perform the callback and send the response506 if (count($args) == 1) {507 // If only one parameter just send that instead of the whole array508 $args = $args[0];509 }510 511 // Are we dealing with a function or a method?512 if (is_string($method) && substr($method, 0, 5) == 'this:') {513 // It's a class method - check it exists514 $method = substr($method, 5);515 if (!method_exists($this, $method)) {516 return new IXR_Error(-32601, 'server error. requested class method "'.$method.'" does not exist.');517 }518 519 //Call the method520 $result = $this->$method($args);521 } else {522 // It's a function - does it exist?523 if (is_array($method)) {524 if (!is_callable(array($method[0], $method[1]))) {525 return new IXR_Error(-32601, 'server error. requested object method "'.$method[1].'" does not exist.');526 }527 } else if (!function_exists($method)) {528 return new IXR_Error(-32601, 'server error. requested function "'.$method.'" does not exist.');529 }530 531 // Call the function532 $result = call_user_func($method, $args);533 }534 return $result;535 }536 537 function error($error, $message = false)538 {539 // Accepts either an error object or an error code and message540 if ($message && !is_object($error)) {541 $error = new IXR_Error($error, $message);542 }543 $this->output($error->getXml());544 }545 546 function output($xml)547 {548 $charset = function_exists('get_option') ? get_option('blog_charset') : '';549 if ($charset)550 $xml = '<?xml version="1.0" encoding="'.$charset.'"?>'."\n".$xml;551 else552 $xml = '<?xml version="1.0"?>'."\n".$xml;553 $length = strlen($xml);554 header('Connection: close');555 if ($charset)556 header('Content-Type: text/xml; charset='.$charset);557 else558 header('Content-Type: text/xml');559 header('Date: '.date('r'));560 echo $xml;561 exit;562 }563 564 function hasMethod($method)565 {566 return in_array($method, array_keys($this->callbacks));567 }568 569 function setCapabilities()570 {571 // Initialises capabilities array572 $this->capabilities = array(573 'xmlrpc' => array(574 'specUrl' => 'http://www.xmlrpc.com/spec',575 'specVersion' => 1576 ),577 'faults_interop' => array(578 'specUrl' => 'http://xmlrpc-epi.sourceforge.net/specs/rfc.fault_codes.php',579 'specVersion' => 20010516580 ),581 'system.multicall' => array(582 'specUrl' => 'http://www.xmlrpc.com/discuss/msgReader$1208',583 'specVersion' => 1584 ),585 );586 }587 588 function getCapabilities($args)589 {590 return $this->capabilities;591 }592 593 function setCallbacks()594 {595 $this->callbacks['system.getCapabilities'] = 'this:getCapabilities';596 $this->callbacks['system.listMethods'] = 'this:listMethods';597 $this->callbacks['system.multicall'] = 'this:multiCall';598 }599 600 function listMethods($args)601 {602 // Returns a list of methods - uses array_reverse to ensure user defined603 // methods are listed before server defined methods604 return array_reverse(array_keys($this->callbacks));605 }606 607 function multiCall($methodcalls)608 {609 // See http://www.xmlrpc.com/discuss/msgReader$1208610 $return = array();611 foreach ($methodcalls as $call) {612 $method = $call['methodName'];613 $params = $call['params'];614 if ($method == 'system.multicall') {615 $result = new IXR_Error(-32600, 'Recursive calls to system.multicall are forbidden');616 } else {617 $result = $this->call($method, $params);618 }619 if (is_a($result, 'IXR_Error')) {620 $return[] = array(621 'faultCode' => $result->code,622 'faultString' => $result->message623 );624 } else {625 $return[] = array($result);626 }627 }628 return $return;629 }630 }631 632 /**633 * IXR_Request634 *635 * @package IXR636 * @since 1.5.0637 */638 class IXR_Request639 {640 var $method;641 var $args;642 var $xml;643 644 /**645 * PHP5 constructor.646 */647 function __construct($method, $args)648 {649 $this->method = $method;650 $this->args = $args;651 $this->xml = <<<EOD652 <?xml version="1.0"?>653 <methodCall>654 <methodName>{$this->method}</methodName>655 <params>656 657 EOD;658 foreach ($this->args as $arg) {659 $this->xml .= '<param><value>';660 $v = new IXR_Value($arg);661 $this->xml .= $v->getXml();662 $this->xml .= "</value></param>\n";663 }664 $this->xml .= '</params></methodCall>';665 }666 667 /**668 * PHP4 constructor.669 */670 public function IXR_Request( $method, $args ) {671 self::__construct( $method, $args );672 }673 674 function getLength()675 {676 return strlen($this->xml);677 }678 679 function getXml()680 {681 return $this->xml;682 }683 }684 685 /**686 * IXR_Client687 *688 * @package IXR689 * @since 1.5.0690 *691 */692 class IXR_Client693 {694 var $server;695 var $port;696 var $path;697 var $useragent;698 var $response;699 var $message = false;700 var $debug = false;701 var $timeout;702 var $headers = array();703 704 // Storage place for an error message705 var $error = false;706 707 /**708 * PHP5 constructor.709 */710 function __construct( $server, $path = false, $port = 80, $timeout = 15 )711 {712 if (!$path) {713 // Assume we have been given a URL instead714 $bits = parse_url($server);715 $this->server = $bits['host'];716 $this->port = isset($bits['port']) ? $bits['port'] : 80;717 $this->path = isset($bits['path']) ? $bits['path'] : '/';718 719 // Make absolutely sure we have a path720 if (!$this->path) {721 $this->path = '/';722 }723 724 if ( ! empty( $bits['query'] ) ) {725 $this->path .= '?' . $bits['query'];726 }727 } else {728 $this->server = $server;729 $this->path = $path;730 $this->port = $port;731 }732 $this->useragent = 'The Incutio XML-RPC PHP Library';733 $this->timeout = $timeout;734 }735 736 /**737 * PHP4 constructor.738 */739 public function IXR_Client( $server, $path = false, $port = 80, $timeout = 15 ) {740 self::__construct( $server, $path, $port, $timeout );741 }742 743 function query()744 {745 $args = func_get_args();746 $method = array_shift($args);747 $request = new IXR_Request($method, $args);748 $length = $request->getLength();749 $xml = $request->getXml();750 $r = "\r\n";751 $request = "POST {$this->path} HTTP/1.0$r";752 753 // Merged from WP #8145 - allow custom headers754 $this->headers['Host'] = $this->server;755 $this->headers['Content-Type'] = 'text/xml';756 $this->headers['User-Agent'] = $this->useragent;757 $this->headers['Content-Length']= $length;758 759 foreach( $this->headers as $header => $value ) {760 $request .= "{$header}: {$value}{$r}";761 }762 $request .= $r;763 764 $request .= $xml;765 766 // Now send the request767 if ($this->debug) {768 echo '<pre class="ixr_request">'.htmlspecialchars($request)."\n</pre>\n\n";769 }770 771 if ($this->timeout) {772 $fp = @fsockopen($this->server, $this->port, $errno, $errstr, $this->timeout);773 } else {774 $fp = @fsockopen($this->server, $this->port, $errno, $errstr);775 }776 if (!$fp) {777 $this->error = new IXR_Error(-32300, 'transport error - could not open socket');778 return false;779 }780 fputs($fp, $request);781 $contents = '';782 $debugContents = '';783 $gotFirstLine = false;784 $gettingHeaders = true;785 while (!feof($fp)) {786 $line = fgets($fp, 4096);787 if (!$gotFirstLine) {788 // Check line for '200'789 if (strstr($line, '200') === false) {790 $this->error = new IXR_Error(-32300, 'transport error - HTTP status code was not 200');791 return false;792 }793 $gotFirstLine = true;794 }795 if (trim($line) == '') {796 $gettingHeaders = false;797 }798 if (!$gettingHeaders) {799 // merged from WP #12559 - remove trim800 $contents .= $line;801 }802 if ($this->debug) {803 $debugContents .= $line;804 }805 }806 if ($this->debug) {807 echo '<pre class="ixr_response">'.htmlspecialchars($debugContents)."\n</pre>\n\n";808 }809 810 // Now parse what we've got back811 $this->message = new IXR_Message($contents);812 if (!$this->message->parse()) {813 // XML error814 $this->error = new IXR_Error(-32700, 'parse error. not well formed');815 return false;816 }817 818 // Is the message a fault?819 if ($this->message->messageType == 'fault') {820 $this->error = new IXR_Error($this->message->faultCode, $this->message->faultString);821 return false;822 }823 824 // Message must be OK825 return true;826 }827 828 function getResponse()829 {830 // methodResponses can only have one param - return that831 return $this->message->params[0];832 }833 834 function isError()835 {836 return (is_object($this->error));837 }838 839 function getErrorCode()840 {841 return $this->error->code;842 }843 844 function getErrorMessage()845 {846 return $this->error->message;847 }848 }849 850 851 /**852 * IXR_Error853 *854 * @package IXR855 * @since 1.5.0856 */857 class IXR_Error858 {859 var $code;860 var $message;861 862 /**863 * PHP5 constructor.864 */865 function __construct( $code, $message )866 {867 $this->code = $code;868 $this->message = htmlspecialchars($message);869 }870 871 /**872 * PHP4 constructor.873 */874 public function IXR_Error( $code, $message ) {875 self::__construct( $code, $message );876 }877 878 function getXml()879 {880 $xml = <<<EOD881 <methodResponse>882 <fault>883 <value>884 <struct>885 <member>886 <name>faultCode</name>887 <value><int>{$this->code}</int></value>888 </member>889 <member>890 <name>faultString</name>891 <value><string>{$this->message}</string></value>892 </member>893 </struct>894 </value>895 </fault>896 </methodResponse>897 898 EOD;899 return $xml;900 }901 }902 903 /**904 * IXR_Date905 *906 * @package IXR907 * @since 1.5.0908 */909 class IXR_Date {910 var $year;911 var $month;912 var $day;913 var $hour;914 var $minute;915 var $second;916 var $timezone;917 918 /**919 * PHP5 constructor.920 */921 function __construct( $time )922 {923 // $time can be a PHP timestamp or an ISO one924 if (is_numeric($time)) {925 $this->parseTimestamp($time);926 } else {927 $this->parseIso($time);928 }929 }930 931 /**932 * PHP4 constructor.933 */934 public function IXR_Date( $time ) {935 self::__construct( $time );936 }937 938 function parseTimestamp($timestamp)939 {940 $this->year = date('Y', $timestamp);941 $this->month = date('m', $timestamp);942 $this->day = date('d', $timestamp);943 $this->hour = date('H', $timestamp);944 $this->minute = date('i', $timestamp);945 $this->second = date('s', $timestamp);946 $this->timezone = '';947 }948 949 function parseIso($iso)950 {951 $this->year = substr($iso, 0, 4);952 $this->month = substr($iso, 4, 2);953 $this->day = substr($iso, 6, 2);954 $this->hour = substr($iso, 9, 2);955 $this->minute = substr($iso, 12, 2);956 $this->second = substr($iso, 15, 2);957 $this->timezone = substr($iso, 17);958 }959 960 function getIso()961 {962 return $this->year.$this->month.$this->day.'T'.$this->hour.':'.$this->minute.':'.$this->second.$this->timezone;963 }964 965 function getXml()966 {967 return '<dateTime.iso8601>'.$this->getIso().'</dateTime.iso8601>';968 }969 970 function getTimestamp()971 {972 return mktime($this->hour, $this->minute, $this->second, $this->month, $this->day, $this->year);973 }974 }975 2 976 3 /** … … 1004 31 } 1005 32 } 1006 1007 /**1008 * IXR_IntrospectionServer1009 *1010 * @package IXR1011 * @since 1.5.01012 */1013 class IXR_IntrospectionServer extends IXR_Server1014 {1015 var $signatures;1016 var $help;1017 1018 /**1019 * PHP5 constructor.1020 */1021 function __construct()1022 {1023 $this->setCallbacks();1024 $this->setCapabilities();1025 $this->capabilities['introspection'] = array(1026 'specUrl' => 'http://xmlrpc.usefulinc.com/doc/reserved.html',1027 'specVersion' => 11028 );1029 $this->addCallback(1030 'system.methodSignature',1031 'this:methodSignature',1032 array('array', 'string'),1033 'Returns an array describing the return type and required parameters of a method'1034 );1035 $this->addCallback(1036 'system.getCapabilities',1037 'this:getCapabilities',1038 array('struct'),1039 'Returns a struct describing the XML-RPC specifications supported by this server'1040 );1041 $this->addCallback(1042 'system.listMethods',1043 'this:listMethods',1044 array('array'),1045 'Returns an array of available methods on this server'1046 );1047 $this->addCallback(1048 'system.methodHelp',1049 'this:methodHelp',1050 array('string', 'string'),1051 'Returns a documentation string for the specified method'1052 );1053 }1054 1055 /**1056 * PHP4 constructor.1057 */1058 public function IXR_IntrospectionServer() {1059 self::__construct();1060 }1061 1062 function addCallback($method, $callback, $args, $help)1063 {1064 $this->callbacks[$method] = $callback;1065 $this->signatures[$method] = $args;1066 $this->help[$method] = $help;1067 }1068 1069 function call($methodname, $args)1070 {1071 // Make sure it's in an array1072 if ($args && !is_array($args)) {1073 $args = array($args);1074 }1075 1076 // Over-rides default call method, adds signature check1077 if (!$this->hasMethod($methodname)) {1078 return new IXR_Error(-32601, 'server error. requested method "'.$this->message->methodName.'" not specified.');1079 }1080 $method = $this->callbacks[$methodname];1081 $signature = $this->signatures[$methodname];1082 $returnType = array_shift($signature);1083 1084 // Check the number of arguments1085 if (count($args) != count($signature)) {1086 return new IXR_Error(-32602, 'server error. wrong number of method parameters');1087 }1088 1089 // Check the argument types1090 $ok = true;1091 $argsbackup = $args;1092 for ($i = 0, $j = count($args); $i < $j; $i++) {1093 $arg = array_shift($args);1094 $type = array_shift($signature);1095 switch ($type) {1096 case 'int':1097 case 'i4':1098 if (is_array($arg) || !is_int($arg)) {1099 $ok = false;1100 }1101 break;1102 case 'base64':1103 case 'string':1104 if (!is_string($arg)) {1105 $ok = false;1106 }1107 break;1108 case 'boolean':1109 if ($arg !== false && $arg !== true) {1110 $ok = false;1111 }1112 break;1113 case 'float':1114 case 'double':1115 if (!is_float($arg)) {1116 $ok = false;1117 }1118 break;1119 case 'date':1120 case 'dateTime.iso8601':1121 if (!is_a($arg, 'IXR_Date')) {1122 $ok = false;1123 }1124 break;1125 }1126 if (!$ok) {1127 return new IXR_Error(-32602, 'server error. invalid method parameters');1128 }1129 }1130 // It passed the test - run the "real" method call1131 return parent::call($methodname, $argsbackup);1132 }1133 1134 function methodSignature($method)1135 {1136 if (!$this->hasMethod($method)) {1137 return new IXR_Error(-32601, 'server error. requested method "'.$method.'" not specified.');1138 }1139 // We should be returning an array of types1140 $types = $this->signatures[$method];1141 $return = array();1142 foreach ($types as $type) {1143 switch ($type) {1144 case 'string':1145 $return[] = 'string';1146 break;1147 case 'int':1148 case 'i4':1149 $return[] = 42;1150 break;1151 case 'double':1152 $return[] = 3.1415;1153 break;1154 case 'dateTime.iso8601':1155 $return[] = new IXR_Date(time());1156 break;1157 case 'boolean':1158 $return[] = true;1159 break;1160 case 'base64':1161 $return[] = new IXR_Base64('base64');1162 break;1163 case 'array':1164 $return[] = array('array');1165 break;1166 case 'struct':1167 $return[] = array('struct' => 'struct');1168 break;1169 }1170 }1171 return $return;1172 }1173 1174 function methodHelp($method)1175 {1176 return $this->help[$method];1177 }1178 }1179 1180 /**1181 * IXR_ClientMulticall1182 *1183 * @package IXR1184 * @since 1.5.01185 */1186 class IXR_ClientMulticall extends IXR_Client1187 {1188 var $calls = array();1189 1190 /**1191 * PHP5 constructor.1192 */1193 function __construct( $server, $path = false, $port = 80 )1194 {1195 parent::IXR_Client($server, $path, $port);1196 $this->useragent = 'The Incutio XML-RPC PHP Library (multicall client)';1197 }1198 1199 /**1200 * PHP4 constructor.1201 */1202 public function IXR_ClientMulticall( $server, $path = false, $port = 80 ) {1203 self::__construct( $server, $path, $port );1204 }1205 1206 function addCall()1207 {1208 $args = func_get_args();1209 $methodName = array_shift($args);1210 $struct = array(1211 'methodName' => $methodName,1212 'params' => $args1213 );1214 $this->calls[] = $struct;1215 }1216 1217 function query()1218 {1219 // Prepare multicall, then call the parent::query() method1220 return parent::query('system.multicall', $this->calls);1221 }1222 } -
trunk/src/wp-includes/IXR/class-IXR-client.php
r38384 r38389 1 1 <?php 2 /**3 * IXR - The Incutio XML-RPC Library4 *5 * Copyright (c) 2010, Incutio Ltd.6 * All rights reserved.7 *8 * Redistribution and use in source and binary forms, with or without9 * 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 copyright14 * notice, this list of conditions and the following disclaimer in the15 * documentation and/or other materials provided with the distribution.16 * - Neither the name of Incutio Ltd. nor the names of its contributors17 * may be used to endorse or promote products derived from this software18 * without specific prior written permission.19 *20 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS21 * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,22 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR23 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR24 * 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, OR27 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY28 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT29 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE30 * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.31 *32 * @package IXR33 * @since 1.5.034 *35 * @copyright Incutio Ltd 2010 (http://www.incutio.com)36 * @version 1.7.4 7th September 201037 * @author Simon Willison38 * @link http://scripts.incutio.com/xmlrpc/ Site/manual39 * @license http://www.opensource.org/licenses/bsd-license.php BSD40 */41 42 /**43 * IXR_Value44 *45 * @package IXR46 * @since 1.5.047 */48 class IXR_Value {49 var $data;50 var $type;51 52 /**53 * PHP5 constructor.54 */55 function __construct( $data, $type = false )56 {57 $this->data = $data;58 if (!$type) {59 $type = $this->calculateType();60 }61 $this->type = $type;62 if ($type == 'struct') {63 // Turn all the values in the array in to new IXR_Value objects64 foreach ($this->data as $key => $value) {65 $this->data[$key] = new IXR_Value($value);66 }67 }68 if ($type == 'array') {69 for ($i = 0, $j = count($this->data); $i < $j; $i++) {70 $this->data[$i] = new IXR_Value($this->data[$i]);71 }72 }73 }74 75 /**76 * PHP4 constructor.77 */78 public function IXR_Value( $data, $type = false ) {79 self::__construct( $data, $type );80 }81 82 function calculateType()83 {84 if ($this->data === true || $this->data === false) {85 return 'boolean';86 }87 if (is_integer($this->data)) {88 return 'int';89 }90 if (is_double($this->data)) {91 return 'double';92 }93 94 // Deal with IXR object types base64 and date95 if (is_object($this->data) && is_a($this->data, 'IXR_Date')) {96 return 'date';97 }98 if (is_object($this->data) && is_a($this->data, 'IXR_Base64')) {99 return 'base64';100 }101 102 // If it is a normal PHP object convert it in to a struct103 if (is_object($this->data)) {104 $this->data = get_object_vars($this->data);105 return 'struct';106 }107 if (!is_array($this->data)) {108 return 'string';109 }110 111 // We have an array - is it an array or a struct?112 if ($this->isStruct($this->data)) {113 return 'struct';114 } else {115 return 'array';116 }117 }118 119 function getXml()120 {121 // Return XML for this value122 switch ($this->type) {123 case 'boolean':124 return '<boolean>'.(($this->data) ? '1' : '0').'</boolean>';125 break;126 case 'int':127 return '<int>'.$this->data.'</int>';128 break;129 case 'double':130 return '<double>'.$this->data.'</double>';131 break;132 case 'string':133 return '<string>'.htmlspecialchars($this->data).'</string>';134 break;135 case 'array':136 $return = '<array><data>'."\n";137 foreach ($this->data as $item) {138 $return .= ' <value>'.$item->getXml()."</value>\n";139 }140 $return .= '</data></array>';141 return $return;142 break;143 case 'struct':144 $return = '<struct>'."\n";145 foreach ($this->data as $name => $value) {146 $name = htmlspecialchars($name);147 $return .= " <member><name>$name</name><value>";148 $return .= $value->getXml()."</value></member>\n";149 }150 $return .= '</struct>';151 return $return;152 break;153 case 'date':154 case 'base64':155 return $this->data->getXml();156 break;157 }158 return false;159 }160 161 /**162 * Checks whether or not the supplied array is a struct or not163 *164 * @param array $array165 * @return bool166 */167 function isStruct($array)168 {169 $expected = 0;170 foreach ($array as $key => $value) {171 if ((string)$key !== (string)$expected) {172 return true;173 }174 $expected++;175 }176 return false;177 }178 }179 180 /**181 * IXR_MESSAGE182 *183 * @package IXR184 * @since 1.5.0185 *186 */187 class IXR_Message188 {189 var $message;190 var $messageType; // methodCall / methodResponse / fault191 var $faultCode;192 var $faultString;193 var $methodName;194 var $params;195 196 // Current variable stacks197 var $_arraystructs = array(); // The stack used to keep track of the current array/struct198 var $_arraystructstypes = array(); // Stack keeping track of if things are structs or array199 var $_currentStructName = array(); // A stack as well200 var $_param;201 var $_value;202 var $_currentTag;203 var $_currentTagContents;204 // The XML parser205 var $_parser;206 207 /**208 * PHP5 constructor.209 */210 function __construct( $message )211 {212 $this->message =& $message;213 }214 215 /**216 * PHP4 constructor.217 */218 public function IXR_Message( $message ) {219 self::__construct( $message );220 }221 222 function parse()223 {224 // first remove the XML declaration225 // merged from WP #10698 - this method avoids the RAM usage of preg_replace on very large messages226 $header = preg_replace( '/<\?xml.*?\?'.'>/s', '', substr( $this->message, 0, 100 ), 1 );227 $this->message = trim( substr_replace( $this->message, $header, 0, 100 ) );228 if ( '' == $this->message ) {229 return false;230 }231 232 // Then remove the DOCTYPE233 $header = preg_replace( '/^<!DOCTYPE[^>]*+>/i', '', substr( $this->message, 0, 200 ), 1 );234 $this->message = trim( substr_replace( $this->message, $header, 0, 200 ) );235 if ( '' == $this->message ) {236 return false;237 }238 239 // Check that the root tag is valid240 $root_tag = substr( $this->message, 0, strcspn( substr( $this->message, 0, 20 ), "> \t\r\n" ) );241 if ( '<!DOCTYPE' === strtoupper( $root_tag ) ) {242 return false;243 }244 if ( ! in_array( $root_tag, array( '<methodCall', '<methodResponse', '<fault' ) ) ) {245 return false;246 }247 248 // Bail if there are too many elements to parse249 $element_limit = 30000;250 if ( function_exists( 'apply_filters' ) ) {251 /**252 * Filters the number of elements to parse in an XML-RPC response.253 *254 * @since 4.0.0255 *256 * @param int $element_limit Default elements limit.257 */258 $element_limit = apply_filters( 'xmlrpc_element_limit', $element_limit );259 }260 if ( $element_limit && 2 * $element_limit < substr_count( $this->message, '<' ) ) {261 return false;262 }263 264 $this->_parser = xml_parser_create();265 // Set XML parser to take the case of tags in to account266 xml_parser_set_option($this->_parser, XML_OPTION_CASE_FOLDING, false);267 // Set XML parser callback functions268 xml_set_object($this->_parser, $this);269 xml_set_element_handler($this->_parser, 'tag_open', 'tag_close');270 xml_set_character_data_handler($this->_parser, 'cdata');271 272 // 256Kb, parse in chunks to avoid the RAM usage on very large messages273 $chunk_size = 262144;274 275 /**276 * Filters the chunk size that can be used to parse an XML-RPC reponse message.277 *278 * @since 4.4.0279 *280 * @param int $chunk_size Chunk size to parse in bytes.281 */282 $chunk_size = apply_filters( 'xmlrpc_chunk_parsing_size', $chunk_size );283 284 $final = false;285 do {286 if (strlen($this->message) <= $chunk_size) {287 $final = true;288 }289 $part = substr($this->message, 0, $chunk_size);290 $this->message = substr($this->message, $chunk_size);291 if (!xml_parse($this->_parser, $part, $final)) {292 return false;293 }294 if ($final) {295 break;296 }297 } while (true);298 xml_parser_free($this->_parser);299 300 // Grab the error messages, if any301 if ($this->messageType == 'fault') {302 $this->faultCode = $this->params[0]['faultCode'];303 $this->faultString = $this->params[0]['faultString'];304 }305 return true;306 }307 308 function tag_open($parser, $tag, $attr)309 {310 $this->_currentTagContents = '';311 $this->currentTag = $tag;312 switch($tag) {313 case 'methodCall':314 case 'methodResponse':315 case 'fault':316 $this->messageType = $tag;317 break;318 /* Deal with stacks of arrays and structs */319 case 'data': // data is to all intents and puposes more interesting than array320 $this->_arraystructstypes[] = 'array';321 $this->_arraystructs[] = array();322 break;323 case 'struct':324 $this->_arraystructstypes[] = 'struct';325 $this->_arraystructs[] = array();326 break;327 }328 }329 330 function cdata($parser, $cdata)331 {332 $this->_currentTagContents .= $cdata;333 }334 335 function tag_close($parser, $tag)336 {337 $valueFlag = false;338 switch($tag) {339 case 'int':340 case 'i4':341 $value = (int)trim($this->_currentTagContents);342 $valueFlag = true;343 break;344 case 'double':345 $value = (double)trim($this->_currentTagContents);346 $valueFlag = true;347 break;348 case 'string':349 $value = (string)trim($this->_currentTagContents);350 $valueFlag = true;351 break;352 case 'dateTime.iso8601':353 $value = new IXR_Date(trim($this->_currentTagContents));354 $valueFlag = true;355 break;356 case 'value':357 // "If no type is indicated, the type is string."358 if (trim($this->_currentTagContents) != '') {359 $value = (string)$this->_currentTagContents;360 $valueFlag = true;361 }362 break;363 case 'boolean':364 $value = (boolean)trim($this->_currentTagContents);365 $valueFlag = true;366 break;367 case 'base64':368 $value = base64_decode($this->_currentTagContents);369 $valueFlag = true;370 break;371 /* Deal with stacks of arrays and structs */372 case 'data':373 case 'struct':374 $value = array_pop($this->_arraystructs);375 array_pop($this->_arraystructstypes);376 $valueFlag = true;377 break;378 case 'member':379 array_pop($this->_currentStructName);380 break;381 case 'name':382 $this->_currentStructName[] = trim($this->_currentTagContents);383 break;384 case 'methodName':385 $this->methodName = trim($this->_currentTagContents);386 break;387 }388 389 if ($valueFlag) {390 if (count($this->_arraystructs) > 0) {391 // Add value to struct or array392 if ($this->_arraystructstypes[count($this->_arraystructstypes)-1] == 'struct') {393 // Add to struct394 $this->_arraystructs[count($this->_arraystructs)-1][$this->_currentStructName[count($this->_currentStructName)-1]] = $value;395 } else {396 // Add to array397 $this->_arraystructs[count($this->_arraystructs)-1][] = $value;398 }399 } else {400 // Just add as a parameter401 $this->params[] = $value;402 }403 }404 $this->_currentTagContents = '';405 }406 }407 408 /**409 * IXR_Server410 *411 * @package IXR412 * @since 1.5.0413 */414 class IXR_Server415 {416 var $data;417 var $callbacks = array();418 var $message;419 var $capabilities;420 421 /**422 * PHP5 constructor.423 */424 function __construct( $callbacks = false, $data = false, $wait = false )425 {426 $this->setCapabilities();427 if ($callbacks) {428 $this->callbacks = $callbacks;429 }430 $this->setCallbacks();431 if (!$wait) {432 $this->serve($data);433 }434 }435 436 /**437 * PHP4 constructor.438 */439 public function IXR_Server( $callbacks = false, $data = false, $wait = false ) {440 self::__construct( $callbacks, $data, $wait );441 }442 443 function serve($data = false)444 {445 if (!$data) {446 if (isset($_SERVER['REQUEST_METHOD']) && $_SERVER['REQUEST_METHOD'] !== 'POST') {447 if ( function_exists( 'status_header' ) ) {448 status_header( 405 ); // WP #20986449 header( 'Allow: POST' );450 }451 header('Content-Type: text/plain'); // merged from WP #9093452 die('XML-RPC server accepts POST requests only.');453 }454 455 global $HTTP_RAW_POST_DATA;456 if (empty($HTTP_RAW_POST_DATA)) {457 // workaround for a bug in PHP 5.2.2 - http://bugs.php.net/bug.php?id=41293458 $data = file_get_contents('php://input');459 } else {460 $data =& $HTTP_RAW_POST_DATA;461 }462 }463 $this->message = new IXR_Message($data);464 if (!$this->message->parse()) {465 $this->error(-32700, 'parse error. not well formed');466 }467 if ($this->message->messageType != 'methodCall') {468 $this->error(-32600, 'server error. invalid xml-rpc. not conforming to spec. Request must be a methodCall');469 }470 $result = $this->call($this->message->methodName, $this->message->params);471 472 // Is the result an error?473 if (is_a($result, 'IXR_Error')) {474 $this->error($result);475 }476 477 // Encode the result478 $r = new IXR_Value($result);479 $resultxml = $r->getXml();480 481 // Create the XML482 $xml = <<<EOD483 <methodResponse>484 <params>485 <param>486 <value>487 $resultxml488 </value>489 </param>490 </params>491 </methodResponse>492 493 EOD;494 // Send it495 $this->output($xml);496 }497 498 function call($methodname, $args)499 {500 if (!$this->hasMethod($methodname)) {501 return new IXR_Error(-32601, 'server error. requested method '.$methodname.' does not exist.');502 }503 $method = $this->callbacks[$methodname];504 505 // Perform the callback and send the response506 if (count($args) == 1) {507 // If only one parameter just send that instead of the whole array508 $args = $args[0];509 }510 511 // Are we dealing with a function or a method?512 if (is_string($method) && substr($method, 0, 5) == 'this:') {513 // It's a class method - check it exists514 $method = substr($method, 5);515 if (!method_exists($this, $method)) {516 return new IXR_Error(-32601, 'server error. requested class method "'.$method.'" does not exist.');517 }518 519 //Call the method520 $result = $this->$method($args);521 } else {522 // It's a function - does it exist?523 if (is_array($method)) {524 if (!is_callable(array($method[0], $method[1]))) {525 return new IXR_Error(-32601, 'server error. requested object method "'.$method[1].'" does not exist.');526 }527 } else if (!function_exists($method)) {528 return new IXR_Error(-32601, 'server error. requested function "'.$method.'" does not exist.');529 }530 531 // Call the function532 $result = call_user_func($method, $args);533 }534 return $result;535 }536 537 function error($error, $message = false)538 {539 // Accepts either an error object or an error code and message540 if ($message && !is_object($error)) {541 $error = new IXR_Error($error, $message);542 }543 $this->output($error->getXml());544 }545 546 function output($xml)547 {548 $charset = function_exists('get_option') ? get_option('blog_charset') : '';549 if ($charset)550 $xml = '<?xml version="1.0" encoding="'.$charset.'"?>'."\n".$xml;551 else552 $xml = '<?xml version="1.0"?>'."\n".$xml;553 $length = strlen($xml);554 header('Connection: close');555 if ($charset)556 header('Content-Type: text/xml; charset='.$charset);557 else558 header('Content-Type: text/xml');559 header('Date: '.date('r'));560 echo $xml;561 exit;562 }563 564 function hasMethod($method)565 {566 return in_array($method, array_keys($this->callbacks));567 }568 569 function setCapabilities()570 {571 // Initialises capabilities array572 $this->capabilities = array(573 'xmlrpc' => array(574 'specUrl' => 'http://www.xmlrpc.com/spec',575 'specVersion' => 1576 ),577 'faults_interop' => array(578 'specUrl' => 'http://xmlrpc-epi.sourceforge.net/specs/rfc.fault_codes.php',579 'specVersion' => 20010516580 ),581 'system.multicall' => array(582 'specUrl' => 'http://www.xmlrpc.com/discuss/msgReader$1208',583 'specVersion' => 1584 ),585 );586 }587 588 function getCapabilities($args)589 {590 return $this->capabilities;591 }592 593 function setCallbacks()594 {595 $this->callbacks['system.getCapabilities'] = 'this:getCapabilities';596 $this->callbacks['system.listMethods'] = 'this:listMethods';597 $this->callbacks['system.multicall'] = 'this:multiCall';598 }599 600 function listMethods($args)601 {602 // Returns a list of methods - uses array_reverse to ensure user defined603 // methods are listed before server defined methods604 return array_reverse(array_keys($this->callbacks));605 }606 607 function multiCall($methodcalls)608 {609 // See http://www.xmlrpc.com/discuss/msgReader$1208610 $return = array();611 foreach ($methodcalls as $call) {612 $method = $call['methodName'];613 $params = $call['params'];614 if ($method == 'system.multicall') {615 $result = new IXR_Error(-32600, 'Recursive calls to system.multicall are forbidden');616 } else {617 $result = $this->call($method, $params);618 }619 if (is_a($result, 'IXR_Error')) {620 $return[] = array(621 'faultCode' => $result->code,622 'faultString' => $result->message623 );624 } else {625 $return[] = array($result);626 }627 }628 return $return;629 }630 }631 632 /**633 * IXR_Request634 *635 * @package IXR636 * @since 1.5.0637 */638 class IXR_Request639 {640 var $method;641 var $args;642 var $xml;643 644 /**645 * PHP5 constructor.646 */647 function __construct($method, $args)648 {649 $this->method = $method;650 $this->args = $args;651 $this->xml = <<<EOD652 <?xml version="1.0"?>653 <methodCall>654 <methodName>{$this->method}</methodName>655 <params>656 657 EOD;658 foreach ($this->args as $arg) {659 $this->xml .= '<param><value>';660 $v = new IXR_Value($arg);661 $this->xml .= $v->getXml();662 $this->xml .= "</value></param>\n";663 }664 $this->xml .= '</params></methodCall>';665 }666 667 /**668 * PHP4 constructor.669 */670 public function IXR_Request( $method, $args ) {671 self::__construct( $method, $args );672 }673 674 function getLength()675 {676 return strlen($this->xml);677 }678 679 function getXml()680 {681 return $this->xml;682 }683 }684 2 685 3 /** … … 847 165 } 848 166 } 849 850 851 /**852 * IXR_Error853 *854 * @package IXR855 * @since 1.5.0856 */857 class IXR_Error858 {859 var $code;860 var $message;861 862 /**863 * PHP5 constructor.864 */865 function __construct( $code, $message )866 {867 $this->code = $code;868 $this->message = htmlspecialchars($message);869 }870 871 /**872 * PHP4 constructor.873 */874 public function IXR_Error( $code, $message ) {875 self::__construct( $code, $message );876 }877 878 function getXml()879 {880 $xml = <<<EOD881 <methodResponse>882 <fault>883 <value>884 <struct>885 <member>886 <name>faultCode</name>887 <value><int>{$this->code}</int></value>888 </member>889 <member>890 <name>faultString</name>891 <value><string>{$this->message}</string></value>892 </member>893 </struct>894 </value>895 </fault>896 </methodResponse>897 898 EOD;899 return $xml;900 }901 }902 903 /**904 * IXR_Date905 *906 * @package IXR907 * @since 1.5.0908 */909 class IXR_Date {910 var $year;911 var $month;912 var $day;913 var $hour;914 var $minute;915 var $second;916 var $timezone;917 918 /**919 * PHP5 constructor.920 */921 function __construct( $time )922 {923 // $time can be a PHP timestamp or an ISO one924 if (is_numeric($time)) {925 $this->parseTimestamp($time);926 } else {927 $this->parseIso($time);928 }929 }930 931 /**932 * PHP4 constructor.933 */934 public function IXR_Date( $time ) {935 self::__construct( $time );936 }937 938 function parseTimestamp($timestamp)939 {940 $this->year = date('Y', $timestamp);941 $this->month = date('m', $timestamp);942 $this->day = date('d', $timestamp);943 $this->hour = date('H', $timestamp);944 $this->minute = date('i', $timestamp);945 $this->second = date('s', $timestamp);946 $this->timezone = '';947 }948 949 function parseIso($iso)950 {951 $this->year = substr($iso, 0, 4);952 $this->month = substr($iso, 4, 2);953 $this->day = substr($iso, 6, 2);954 $this->hour = substr($iso, 9, 2);955 $this->minute = substr($iso, 12, 2);956 $this->second = substr($iso, 15, 2);957 $this->timezone = substr($iso, 17);958 }959 960 function getIso()961 {962 return $this->year.$this->month.$this->day.'T'.$this->hour.':'.$this->minute.':'.$this->second.$this->timezone;963 }964 965 function getXml()966 {967 return '<dateTime.iso8601>'.$this->getIso().'</dateTime.iso8601>';968 }969 970 function getTimestamp()971 {972 return mktime($this->hour, $this->minute, $this->second, $this->month, $this->day, $this->year);973 }974 }975 976 /**977 * IXR_Base64978 *979 * @package IXR980 * @since 1.5.0981 */982 class IXR_Base64983 {984 var $data;985 986 /**987 * PHP5 constructor.988 */989 function __construct( $data )990 {991 $this->data = $data;992 }993 994 /**995 * PHP4 constructor.996 */997 public function IXR_Base64( $data ) {998 self::__construct( $data );999 }1000 1001 function getXml()1002 {1003 return '<base64>'.base64_encode($this->data).'</base64>';1004 }1005 }1006 1007 /**1008 * IXR_IntrospectionServer1009 *1010 * @package IXR1011 * @since 1.5.01012 */1013 class IXR_IntrospectionServer extends IXR_Server1014 {1015 var $signatures;1016 var $help;1017 1018 /**1019 * PHP5 constructor.1020 */1021 function __construct()1022 {1023 $this->setCallbacks();1024 $this->setCapabilities();1025 $this->capabilities['introspection'] = array(1026 'specUrl' => 'http://xmlrpc.usefulinc.com/doc/reserved.html',1027 'specVersion' => 11028 );1029 $this->addCallback(1030 'system.methodSignature',1031 'this:methodSignature',1032 array('array', 'string'),1033 'Returns an array describing the return type and required parameters of a method'1034 );1035 $this->addCallback(1036 'system.getCapabilities',1037 'this:getCapabilities',1038 array('struct'),1039 'Returns a struct describing the XML-RPC specifications supported by this server'1040 );1041 $this->addCallback(1042 'system.listMethods',1043 'this:listMethods',1044 array('array'),1045 'Returns an array of available methods on this server'1046 );1047 $this->addCallback(1048 'system.methodHelp',1049 'this:methodHelp',1050 array('string', 'string'),1051 'Returns a documentation string for the specified method'1052 );1053 }1054 1055 /**1056 * PHP4 constructor.1057 */1058 public function IXR_IntrospectionServer() {1059 self::__construct();1060 }1061 1062 function addCallback($method, $callback, $args, $help)1063 {1064 $this->callbacks[$method] = $callback;1065 $this->signatures[$method] = $args;1066 $this->help[$method] = $help;1067 }1068 1069 function call($methodname, $args)1070 {1071 // Make sure it's in an array1072 if ($args && !is_array($args)) {1073 $args = array($args);1074 }1075 1076 // Over-rides default call method, adds signature check1077 if (!$this->hasMethod($methodname)) {1078 return new IXR_Error(-32601, 'server error. requested method "'.$this->message->methodName.'" not specified.');1079 }1080 $method = $this->callbacks[$methodname];1081 $signature = $this->signatures[$methodname];1082 $returnType = array_shift($signature);1083 1084 // Check the number of arguments1085 if (count($args) != count($signature)) {1086 return new IXR_Error(-32602, 'server error. wrong number of method parameters');1087 }1088 1089 // Check the argument types1090 $ok = true;1091 $argsbackup = $args;1092 for ($i = 0, $j = count($args); $i < $j; $i++) {1093 $arg = array_shift($args);1094 $type = array_shift($signature);1095 switch ($type) {1096 case 'int':1097 case 'i4':1098 if (is_array($arg) || !is_int($arg)) {1099 $ok = false;1100 }1101 break;1102 case 'base64':1103 case 'string':1104 if (!is_string($arg)) {1105 $ok = false;1106 }1107 break;1108 case 'boolean':1109 if ($arg !== false && $arg !== true) {1110 $ok = false;1111 }1112 break;1113 case 'float':1114 case 'double':1115 if (!is_float($arg)) {1116 $ok = false;1117 }1118 break;1119 case 'date':1120 case 'dateTime.iso8601':1121 if (!is_a($arg, 'IXR_Date')) {1122 $ok = false;1123 }1124 break;1125 }1126 if (!$ok) {1127 return new IXR_Error(-32602, 'server error. invalid method parameters');1128 }1129 }1130 // It passed the test - run the "real" method call1131 return parent::call($methodname, $argsbackup);1132 }1133 1134 function methodSignature($method)1135 {1136 if (!$this->hasMethod($method)) {1137 return new IXR_Error(-32601, 'server error. requested method "'.$method.'" not specified.');1138 }1139 // We should be returning an array of types1140 $types = $this->signatures[$method];1141 $return = array();1142 foreach ($types as $type) {1143 switch ($type) {1144 case 'string':1145 $return[] = 'string';1146 break;1147 case 'int':1148 case 'i4':1149 $return[] = 42;1150 break;1151 case 'double':1152 $return[] = 3.1415;1153 break;1154 case 'dateTime.iso8601':1155 $return[] = new IXR_Date(time());1156 break;1157 case 'boolean':1158 $return[] = true;1159 break;1160 case 'base64':1161 $return[] = new IXR_Base64('base64');1162 break;1163 case 'array':1164 $return[] = array('array');1165 break;1166 case 'struct':1167 $return[] = array('struct' => 'struct');1168 break;1169 }1170 }1171 return $return;1172 }1173 1174 function methodHelp($method)1175 {1176 return $this->help[$method];1177 }1178 }1179 1180 /**1181 * IXR_ClientMulticall1182 *1183 * @package IXR1184 * @since 1.5.01185 */1186 class IXR_ClientMulticall extends IXR_Client1187 {1188 var $calls = array();1189 1190 /**1191 * PHP5 constructor.1192 */1193 function __construct( $server, $path = false, $port = 80 )1194 {1195 parent::IXR_Client($server, $path, $port);1196 $this->useragent = 'The Incutio XML-RPC PHP Library (multicall client)';1197 }1198 1199 /**1200 * PHP4 constructor.1201 */1202 public function IXR_ClientMulticall( $server, $path = false, $port = 80 ) {1203 self::__construct( $server, $path, $port );1204 }1205 1206 function addCall()1207 {1208 $args = func_get_args();1209 $methodName = array_shift($args);1210 $struct = array(1211 'methodName' => $methodName,1212 'params' => $args1213 );1214 $this->calls[] = $struct;1215 }1216 1217 function query()1218 {1219 // Prepare multicall, then call the parent::query() method1220 return parent::query('system.multicall', $this->calls);1221 }1222 } -
trunk/src/wp-includes/IXR/class-IXR-clientmulticall.php
r38384 r38389 1 1 <?php 2 /**3 * IXR - The Incutio XML-RPC Library4 *5 * Copyright (c) 2010, Incutio Ltd.6 * All rights reserved.7 *8 * Redistribution and use in source and binary forms, with or without9 * 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 copyright14 * notice, this list of conditions and the following disclaimer in the15 * documentation and/or other materials provided with the distribution.16 * - Neither the name of Incutio Ltd. nor the names of its contributors17 * may be used to endorse or promote products derived from this software18 * without specific prior written permission.19 *20 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS21 * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,22 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR23 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR24 * 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, OR27 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY28 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT29 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE30 * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.31 *32 * @package IXR33 * @since 1.5.034 *35 * @copyright Incutio Ltd 2010 (http://www.incutio.com)36 * @version 1.7.4 7th September 201037 * @author Simon Willison38 * @link http://scripts.incutio.com/xmlrpc/ Site/manual39 * @license http://www.opensource.org/licenses/bsd-license.php BSD40 */41 42 /**43 * IXR_Value44 *45 * @package IXR46 * @since 1.5.047 */48 class IXR_Value {49 var $data;50 var $type;51 52 /**53 * PHP5 constructor.54 */55 function __construct( $data, $type = false )56 {57 $this->data = $data;58 if (!$type) {59 $type = $this->calculateType();60 }61 $this->type = $type;62 if ($type == 'struct') {63 // Turn all the values in the array in to new IXR_Value objects64 foreach ($this->data as $key => $value) {65 $this->data[$key] = new IXR_Value($value);66 }67 }68 if ($type == 'array') {69 for ($i = 0, $j = count($this->data); $i < $j; $i++) {70 $this->data[$i] = new IXR_Value($this->data[$i]);71 }72 }73 }74 75 /**76 * PHP4 constructor.77 */78 public function IXR_Value( $data, $type = false ) {79 self::__construct( $data, $type );80 }81 82 function calculateType()83 {84 if ($this->data === true || $this->data === false) {85 return 'boolean';86 }87 if (is_integer($this->data)) {88 return 'int';89 }90 if (is_double($this->data)) {91 return 'double';92 }93 94 // Deal with IXR object types base64 and date95 if (is_object($this->data) && is_a($this->data, 'IXR_Date')) {96 return 'date';97 }98 if (is_object($this->data) && is_a($this->data, 'IXR_Base64')) {99 return 'base64';100 }101 102 // If it is a normal PHP object convert it in to a struct103 if (is_object($this->data)) {104 $this->data = get_object_vars($this->data);105 return 'struct';106 }107 if (!is_array($this->data)) {108 return 'string';109 }110 111 // We have an array - is it an array or a struct?112 if ($this->isStruct($this->data)) {113 return 'struct';114 } else {115 return 'array';116 }117 }118 119 function getXml()120 {121 // Return XML for this value122 switch ($this->type) {123 case 'boolean':124 return '<boolean>'.(($this->data) ? '1' : '0').'</boolean>';125 break;126 case 'int':127 return '<int>'.$this->data.'</int>';128 break;129 case 'double':130 return '<double>'.$this->data.'</double>';131 break;132 case 'string':133 return '<string>'.htmlspecialchars($this->data).'</string>';134 break;135 case 'array':136 $return = '<array><data>'."\n";137 foreach ($this->data as $item) {138 $return .= ' <value>'.$item->getXml()."</value>\n";139 }140 $return .= '</data></array>';141 return $return;142 break;143 case 'struct':144 $return = '<struct>'."\n";145 foreach ($this->data as $name => $value) {146 $name = htmlspecialchars($name);147 $return .= " <member><name>$name</name><value>";148 $return .= $value->getXml()."</value></member>\n";149 }150 $return .= '</struct>';151 return $return;152 break;153 case 'date':154 case 'base64':155 return $this->data->getXml();156 break;157 }158 return false;159 }160 161 /**162 * Checks whether or not the supplied array is a struct or not163 *164 * @param array $array165 * @return bool166 */167 function isStruct($array)168 {169 $expected = 0;170 foreach ($array as $key => $value) {171 if ((string)$key !== (string)$expected) {172 return true;173 }174 $expected++;175 }176 return false;177 }178 }179 180 /**181 * IXR_MESSAGE182 *183 * @package IXR184 * @since 1.5.0185 *186 */187 class IXR_Message188 {189 var $message;190 var $messageType; // methodCall / methodResponse / fault191 var $faultCode;192 var $faultString;193 var $methodName;194 var $params;195 196 // Current variable stacks197 var $_arraystructs = array(); // The stack used to keep track of the current array/struct198 var $_arraystructstypes = array(); // Stack keeping track of if things are structs or array199 var $_currentStructName = array(); // A stack as well200 var $_param;201 var $_value;202 var $_currentTag;203 var $_currentTagContents;204 // The XML parser205 var $_parser;206 207 /**208 * PHP5 constructor.209 */210 function __construct( $message )211 {212 $this->message =& $message;213 }214 215 /**216 * PHP4 constructor.217 */218 public function IXR_Message( $message ) {219 self::__construct( $message );220 }221 222 function parse()223 {224 // first remove the XML declaration225 // merged from WP #10698 - this method avoids the RAM usage of preg_replace on very large messages226 $header = preg_replace( '/<\?xml.*?\?'.'>/s', '', substr( $this->message, 0, 100 ), 1 );227 $this->message = trim( substr_replace( $this->message, $header, 0, 100 ) );228 if ( '' == $this->message ) {229 return false;230 }231 232 // Then remove the DOCTYPE233 $header = preg_replace( '/^<!DOCTYPE[^>]*+>/i', '', substr( $this->message, 0, 200 ), 1 );234 $this->message = trim( substr_replace( $this->message, $header, 0, 200 ) );235 if ( '' == $this->message ) {236 return false;237 }238 239 // Check that the root tag is valid240 $root_tag = substr( $this->message, 0, strcspn( substr( $this->message, 0, 20 ), "> \t\r\n" ) );241 if ( '<!DOCTYPE' === strtoupper( $root_tag ) ) {242 return false;243 }244 if ( ! in_array( $root_tag, array( '<methodCall', '<methodResponse', '<fault' ) ) ) {245 return false;246 }247 248 // Bail if there are too many elements to parse249 $element_limit = 30000;250 if ( function_exists( 'apply_filters' ) ) {251 /**252 * Filters the number of elements to parse in an XML-RPC response.253 *254 * @since 4.0.0255 *256 * @param int $element_limit Default elements limit.257 */258 $element_limit = apply_filters( 'xmlrpc_element_limit', $element_limit );259 }260 if ( $element_limit && 2 * $element_limit < substr_count( $this->message, '<' ) ) {261 return false;262 }263 264 $this->_parser = xml_parser_create();265 // Set XML parser to take the case of tags in to account266 xml_parser_set_option($this->_parser, XML_OPTION_CASE_FOLDING, false);267 // Set XML parser callback functions268 xml_set_object($this->_parser, $this);269 xml_set_element_handler($this->_parser, 'tag_open', 'tag_close');270 xml_set_character_data_handler($this->_parser, 'cdata');271 272 // 256Kb, parse in chunks to avoid the RAM usage on very large messages273 $chunk_size = 262144;274 275 /**276 * Filters the chunk size that can be used to parse an XML-RPC reponse message.277 *278 * @since 4.4.0279 *280 * @param int $chunk_size Chunk size to parse in bytes.281 */282 $chunk_size = apply_filters( 'xmlrpc_chunk_parsing_size', $chunk_size );283 284 $final = false;285 do {286 if (strlen($this->message) <= $chunk_size) {287 $final = true;288 }289 $part = substr($this->message, 0, $chunk_size);290 $this->message = substr($this->message, $chunk_size);291 if (!xml_parse($this->_parser, $part, $final)) {292 return false;293 }294 if ($final) {295 break;296 }297 } while (true);298 xml_parser_free($this->_parser);299 300 // Grab the error messages, if any301 if ($this->messageType == 'fault') {302 $this->faultCode = $this->params[0]['faultCode'];303 $this->faultString = $this->params[0]['faultString'];304 }305 return true;306 }307 308 function tag_open($parser, $tag, $attr)309 {310 $this->_currentTagContents = '';311 $this->currentTag = $tag;312 switch($tag) {313 case 'methodCall':314 case 'methodResponse':315 case 'fault':316 $this->messageType = $tag;317 break;318 /* Deal with stacks of arrays and structs */319 case 'data': // data is to all intents and puposes more interesting than array320 $this->_arraystructstypes[] = 'array';321 $this->_arraystructs[] = array();322 break;323 case 'struct':324 $this->_arraystructstypes[] = 'struct';325 $this->_arraystructs[] = array();326 break;327 }328 }329 330 function cdata($parser, $cdata)331 {332 $this->_currentTagContents .= $cdata;333 }334 335 function tag_close($parser, $tag)336 {337 $valueFlag = false;338 switch($tag) {339 case 'int':340 case 'i4':341 $value = (int)trim($this->_currentTagContents);342 $valueFlag = true;343 break;344 case 'double':345 $value = (double)trim($this->_currentTagContents);346 $valueFlag = true;347 break;348 case 'string':349 $value = (string)trim($this->_currentTagContents);350 $valueFlag = true;351 break;352 case 'dateTime.iso8601':353 $value = new IXR_Date(trim($this->_currentTagContents));354 $valueFlag = true;355 break;356 case 'value':357 // "If no type is indicated, the type is string."358 if (trim($this->_currentTagContents) != '') {359 $value = (string)$this->_currentTagContents;360 $valueFlag = true;361 }362 break;363 case 'boolean':364 $value = (boolean)trim($this->_currentTagContents);365 $valueFlag = true;366 break;367 case 'base64':368 $value = base64_decode($this->_currentTagContents);369 $valueFlag = true;370 break;371 /* Deal with stacks of arrays and structs */372 case 'data':373 case 'struct':374 $value = array_pop($this->_arraystructs);375 array_pop($this->_arraystructstypes);376 $valueFlag = true;377 break;378 case 'member':379 array_pop($this->_currentStructName);380 break;381 case 'name':382 $this->_currentStructName[] = trim($this->_currentTagContents);383 break;384 case 'methodName':385 $this->methodName = trim($this->_currentTagContents);386 break;387 }388 389 if ($valueFlag) {390 if (count($this->_arraystructs) > 0) {391 // Add value to struct or array392 if ($this->_arraystructstypes[count($this->_arraystructstypes)-1] == 'struct') {393 // Add to struct394 $this->_arraystructs[count($this->_arraystructs)-1][$this->_currentStructName[count($this->_currentStructName)-1]] = $value;395 } else {396 // Add to array397 $this->_arraystructs[count($this->_arraystructs)-1][] = $value;398 }399 } else {400 // Just add as a parameter401 $this->params[] = $value;402 }403 }404 $this->_currentTagContents = '';405 }406 }407 408 /**409 * IXR_Server410 *411 * @package IXR412 * @since 1.5.0413 */414 class IXR_Server415 {416 var $data;417 var $callbacks = array();418 var $message;419 var $capabilities;420 421 /**422 * PHP5 constructor.423 */424 function __construct( $callbacks = false, $data = false, $wait = false )425 {426 $this->setCapabilities();427 if ($callbacks) {428 $this->callbacks = $callbacks;429 }430 $this->setCallbacks();431 if (!$wait) {432 $this->serve($data);433 }434 }435 436 /**437 * PHP4 constructor.438 */439 public function IXR_Server( $callbacks = false, $data = false, $wait = false ) {440 self::__construct( $callbacks, $data, $wait );441 }442 443 function serve($data = false)444 {445 if (!$data) {446 if (isset($_SERVER['REQUEST_METHOD']) && $_SERVER['REQUEST_METHOD'] !== 'POST') {447 if ( function_exists( 'status_header' ) ) {448 status_header( 405 ); // WP #20986449 header( 'Allow: POST' );450 }451 header('Content-Type: text/plain'); // merged from WP #9093452 die('XML-RPC server accepts POST requests only.');453 }454 455 global $HTTP_RAW_POST_DATA;456 if (empty($HTTP_RAW_POST_DATA)) {457 // workaround for a bug in PHP 5.2.2 - http://bugs.php.net/bug.php?id=41293458 $data = file_get_contents('php://input');459 } else {460 $data =& $HTTP_RAW_POST_DATA;461 }462 }463 $this->message = new IXR_Message($data);464 if (!$this->message->parse()) {465 $this->error(-32700, 'parse error. not well formed');466 }467 if ($this->message->messageType != 'methodCall') {468 $this->error(-32600, 'server error. invalid xml-rpc. not conforming to spec. Request must be a methodCall');469 }470 $result = $this->call($this->message->methodName, $this->message->params);471 472 // Is the result an error?473 if (is_a($result, 'IXR_Error')) {474 $this->error($result);475 }476 477 // Encode the result478 $r = new IXR_Value($result);479 $resultxml = $r->getXml();480 481 // Create the XML482 $xml = <<<EOD483 <methodResponse>484 <params>485 <param>486 <value>487 $resultxml488 </value>489 </param>490 </params>491 </methodResponse>492 493 EOD;494 // Send it495 $this->output($xml);496 }497 498 function call($methodname, $args)499 {500 if (!$this->hasMethod($methodname)) {501 return new IXR_Error(-32601, 'server error. requested method '.$methodname.' does not exist.');502 }503 $method = $this->callbacks[$methodname];504 505 // Perform the callback and send the response506 if (count($args) == 1) {507 // If only one parameter just send that instead of the whole array508 $args = $args[0];509 }510 511 // Are we dealing with a function or a method?512 if (is_string($method) && substr($method, 0, 5) == 'this:') {513 // It's a class method - check it exists514 $method = substr($method, 5);515 if (!method_exists($this, $method)) {516 return new IXR_Error(-32601, 'server error. requested class method "'.$method.'" does not exist.');517 }518 519 //Call the method520 $result = $this->$method($args);521 } else {522 // It's a function - does it exist?523 if (is_array($method)) {524 if (!is_callable(array($method[0], $method[1]))) {525 return new IXR_Error(-32601, 'server error. requested object method "'.$method[1].'" does not exist.');526 }527 } else if (!function_exists($method)) {528 return new IXR_Error(-32601, 'server error. requested function "'.$method.'" does not exist.');529 }530 531 // Call the function532 $result = call_user_func($method, $args);533 }534 return $result;535 }536 537 function error($error, $message = false)538 {539 // Accepts either an error object or an error code and message540 if ($message && !is_object($error)) {541 $error = new IXR_Error($error, $message);542 }543 $this->output($error->getXml());544 }545 546 function output($xml)547 {548 $charset = function_exists('get_option') ? get_option('blog_charset') : '';549 if ($charset)550 $xml = '<?xml version="1.0" encoding="'.$charset.'"?>'."\n".$xml;551 else552 $xml = '<?xml version="1.0"?>'."\n".$xml;553 $length = strlen($xml);554 header('Connection: close');555 if ($charset)556 header('Content-Type: text/xml; charset='.$charset);557 else558 header('Content-Type: text/xml');559 header('Date: '.date('r'));560 echo $xml;561 exit;562 }563 564 function hasMethod($method)565 {566 return in_array($method, array_keys($this->callbacks));567 }568 569 function setCapabilities()570 {571 // Initialises capabilities array572 $this->capabilities = array(573 'xmlrpc' => array(574 'specUrl' => 'http://www.xmlrpc.com/spec',575 'specVersion' => 1576 ),577 'faults_interop' => array(578 'specUrl' => 'http://xmlrpc-epi.sourceforge.net/specs/rfc.fault_codes.php',579 'specVersion' => 20010516580 ),581 'system.multicall' => array(582 'specUrl' => 'http://www.xmlrpc.com/discuss/msgReader$1208',583 'specVersion' => 1584 ),585 );586 }587 588 function getCapabilities($args)589 {590 return $this->capabilities;591 }592 593 function setCallbacks()594 {595 $this->callbacks['system.getCapabilities'] = 'this:getCapabilities';596 $this->callbacks['system.listMethods'] = 'this:listMethods';597 $this->callbacks['system.multicall'] = 'this:multiCall';598 }599 600 function listMethods($args)601 {602 // Returns a list of methods - uses array_reverse to ensure user defined603 // methods are listed before server defined methods604 return array_reverse(array_keys($this->callbacks));605 }606 607 function multiCall($methodcalls)608 {609 // See http://www.xmlrpc.com/discuss/msgReader$1208610 $return = array();611 foreach ($methodcalls as $call) {612 $method = $call['methodName'];613 $params = $call['params'];614 if ($method == 'system.multicall') {615 $result = new IXR_Error(-32600, 'Recursive calls to system.multicall are forbidden');616 } else {617 $result = $this->call($method, $params);618 }619 if (is_a($result, 'IXR_Error')) {620 $return[] = array(621 'faultCode' => $result->code,622 'faultString' => $result->message623 );624 } else {625 $return[] = array($result);626 }627 }628 return $return;629 }630 }631 632 /**633 * IXR_Request634 *635 * @package IXR636 * @since 1.5.0637 */638 class IXR_Request639 {640 var $method;641 var $args;642 var $xml;643 644 /**645 * PHP5 constructor.646 */647 function __construct($method, $args)648 {649 $this->method = $method;650 $this->args = $args;651 $this->xml = <<<EOD652 <?xml version="1.0"?>653 <methodCall>654 <methodName>{$this->method}</methodName>655 <params>656 657 EOD;658 foreach ($this->args as $arg) {659 $this->xml .= '<param><value>';660 $v = new IXR_Value($arg);661 $this->xml .= $v->getXml();662 $this->xml .= "</value></param>\n";663 }664 $this->xml .= '</params></methodCall>';665 }666 667 /**668 * PHP4 constructor.669 */670 public function IXR_Request( $method, $args ) {671 self::__construct( $method, $args );672 }673 674 function getLength()675 {676 return strlen($this->xml);677 }678 679 function getXml()680 {681 return $this->xml;682 }683 }684 685 /**686 * IXR_Client687 *688 * @package IXR689 * @since 1.5.0690 *691 */692 class IXR_Client693 {694 var $server;695 var $port;696 var $path;697 var $useragent;698 var $response;699 var $message = false;700 var $debug = false;701 var $timeout;702 var $headers = array();703 704 // Storage place for an error message705 var $error = false;706 707 /**708 * PHP5 constructor.709 */710 function __construct( $server, $path = false, $port = 80, $timeout = 15 )711 {712 if (!$path) {713 // Assume we have been given a URL instead714 $bits = parse_url($server);715 $this->server = $bits['host'];716 $this->port = isset($bits['port']) ? $bits['port'] : 80;717 $this->path = isset($bits['path']) ? $bits['path'] : '/';718 719 // Make absolutely sure we have a path720 if (!$this->path) {721 $this->path = '/';722 }723 724 if ( ! empty( $bits['query'] ) ) {725 $this->path .= '?' . $bits['query'];726 }727 } else {728 $this->server = $server;729 $this->path = $path;730 $this->port = $port;731 }732 $this->useragent = 'The Incutio XML-RPC PHP Library';733 $this->timeout = $timeout;734 }735 736 /**737 * PHP4 constructor.738 */739 public function IXR_Client( $server, $path = false, $port = 80, $timeout = 15 ) {740 self::__construct( $server, $path, $port, $timeout );741 }742 743 function query()744 {745 $args = func_get_args();746 $method = array_shift($args);747 $request = new IXR_Request($method, $args);748 $length = $request->getLength();749 $xml = $request->getXml();750 $r = "\r\n";751 $request = "POST {$this->path} HTTP/1.0$r";752 753 // Merged from WP #8145 - allow custom headers754 $this->headers['Host'] = $this->server;755 $this->headers['Content-Type'] = 'text/xml';756 $this->headers['User-Agent'] = $this->useragent;757 $this->headers['Content-Length']= $length;758 759 foreach( $this->headers as $header => $value ) {760 $request .= "{$header}: {$value}{$r}";761 }762 $request .= $r;763 764 $request .= $xml;765 766 // Now send the request767 if ($this->debug) {768 echo '<pre class="ixr_request">'.htmlspecialchars($request)."\n</pre>\n\n";769 }770 771 if ($this->timeout) {772 $fp = @fsockopen($this->server, $this->port, $errno, $errstr, $this->timeout);773 } else {774 $fp = @fsockopen($this->server, $this->port, $errno, $errstr);775 }776 if (!$fp) {777 $this->error = new IXR_Error(-32300, 'transport error - could not open socket');778 return false;779 }780 fputs($fp, $request);781 $contents = '';782 $debugContents = '';783 $gotFirstLine = false;784 $gettingHeaders = true;785 while (!feof($fp)) {786 $line = fgets($fp, 4096);787 if (!$gotFirstLine) {788 // Check line for '200'789 if (strstr($line, '200') === false) {790 $this->error = new IXR_Error(-32300, 'transport error - HTTP status code was not 200');791 return false;792 }793 $gotFirstLine = true;794 }795 if (trim($line) == '') {796 $gettingHeaders = false;797 }798 if (!$gettingHeaders) {799 // merged from WP #12559 - remove trim800 $contents .= $line;801 }802 if ($this->debug) {803 $debugContents .= $line;804 }805 }806 if ($this->debug) {807 echo '<pre class="ixr_response">'.htmlspecialchars($debugContents)."\n</pre>\n\n";808 }809 810 // Now parse what we've got back811 $this->message = new IXR_Message($contents);812 if (!$this->message->parse()) {813 // XML error814 $this->error = new IXR_Error(-32700, 'parse error. not well formed');815 return false;816 }817 818 // Is the message a fault?819 if ($this->message->messageType == 'fault') {820 $this->error = new IXR_Error($this->message->faultCode, $this->message->faultString);821 return false;822 }823 824 // Message must be OK825 return true;826 }827 828 function getResponse()829 {830 // methodResponses can only have one param - return that831 return $this->message->params[0];832 }833 834 function isError()835 {836 return (is_object($this->error));837 }838 839 function getErrorCode()840 {841 return $this->error->code;842 }843 844 function getErrorMessage()845 {846 return $this->error->message;847 }848 }849 850 851 /**852 * IXR_Error853 *854 * @package IXR855 * @since 1.5.0856 */857 class IXR_Error858 {859 var $code;860 var $message;861 862 /**863 * PHP5 constructor.864 */865 function __construct( $code, $message )866 {867 $this->code = $code;868 $this->message = htmlspecialchars($message);869 }870 871 /**872 * PHP4 constructor.873 */874 public function IXR_Error( $code, $message ) {875 self::__construct( $code, $message );876 }877 878 function getXml()879 {880 $xml = <<<EOD881 <methodResponse>882 <fault>883 <value>884 <struct>885 <member>886 <name>faultCode</name>887 <value><int>{$this->code}</int></value>888 </member>889 <member>890 <name>faultString</name>891 <value><string>{$this->message}</string></value>892 </member>893 </struct>894 </value>895 </fault>896 </methodResponse>897 898 EOD;899 return $xml;900 }901 }902 903 /**904 * IXR_Date905 *906 * @package IXR907 * @since 1.5.0908 */909 class IXR_Date {910 var $year;911 var $month;912 var $day;913 var $hour;914 var $minute;915 var $second;916 var $timezone;917 918 /**919 * PHP5 constructor.920 */921 function __construct( $time )922 {923 // $time can be a PHP timestamp or an ISO one924 if (is_numeric($time)) {925 $this->parseTimestamp($time);926 } else {927 $this->parseIso($time);928 }929 }930 931 /**932 * PHP4 constructor.933 */934 public function IXR_Date( $time ) {935 self::__construct( $time );936 }937 938 function parseTimestamp($timestamp)939 {940 $this->year = date('Y', $timestamp);941 $this->month = date('m', $timestamp);942 $this->day = date('d', $timestamp);943 $this->hour = date('H', $timestamp);944 $this->minute = date('i', $timestamp);945 $this->second = date('s', $timestamp);946 $this->timezone = '';947 }948 949 function parseIso($iso)950 {951 $this->year = substr($iso, 0, 4);952 $this->month = substr($iso, 4, 2);953 $this->day = substr($iso, 6, 2);954 $this->hour = substr($iso, 9, 2);955 $this->minute = substr($iso, 12, 2);956 $this->second = substr($iso, 15, 2);957 $this->timezone = substr($iso, 17);958 }959 960 function getIso()961 {962 return $this->year.$this->month.$this->day.'T'.$this->hour.':'.$this->minute.':'.$this->second.$this->timezone;963 }964 965 function getXml()966 {967 return '<dateTime.iso8601>'.$this->getIso().'</dateTime.iso8601>';968 }969 970 function getTimestamp()971 {972 return mktime($this->hour, $this->minute, $this->second, $this->month, $this->day, $this->year);973 }974 }975 976 /**977 * IXR_Base64978 *979 * @package IXR980 * @since 1.5.0981 */982 class IXR_Base64983 {984 var $data;985 986 /**987 * PHP5 constructor.988 */989 function __construct( $data )990 {991 $this->data = $data;992 }993 994 /**995 * PHP4 constructor.996 */997 public function IXR_Base64( $data ) {998 self::__construct( $data );999 }1000 1001 function getXml()1002 {1003 return '<base64>'.base64_encode($this->data).'</base64>';1004 }1005 }1006 1007 /**1008 * IXR_IntrospectionServer1009 *1010 * @package IXR1011 * @since 1.5.01012 */1013 class IXR_IntrospectionServer extends IXR_Server1014 {1015 var $signatures;1016 var $help;1017 1018 /**1019 * PHP5 constructor.1020 */1021 function __construct()1022 {1023 $this->setCallbacks();1024 $this->setCapabilities();1025 $this->capabilities['introspection'] = array(1026 'specUrl' => 'http://xmlrpc.usefulinc.com/doc/reserved.html',1027 'specVersion' => 11028 );1029 $this->addCallback(1030 'system.methodSignature',1031 'this:methodSignature',1032 array('array', 'string'),1033 'Returns an array describing the return type and required parameters of a method'1034 );1035 $this->addCallback(1036 'system.getCapabilities',1037 'this:getCapabilities',1038 array('struct'),1039 'Returns a struct describing the XML-RPC specifications supported by this server'1040 );1041 $this->addCallback(1042 'system.listMethods',1043 'this:listMethods',1044 array('array'),1045 'Returns an array of available methods on this server'1046 );1047 $this->addCallback(1048 'system.methodHelp',1049 'this:methodHelp',1050 array('string', 'string'),1051 'Returns a documentation string for the specified method'1052 );1053 }1054 1055 /**1056 * PHP4 constructor.1057 */1058 public function IXR_IntrospectionServer() {1059 self::__construct();1060 }1061 1062 function addCallback($method, $callback, $args, $help)1063 {1064 $this->callbacks[$method] = $callback;1065 $this->signatures[$method] = $args;1066 $this->help[$method] = $help;1067 }1068 1069 function call($methodname, $args)1070 {1071 // Make sure it's in an array1072 if ($args && !is_array($args)) {1073 $args = array($args);1074 }1075 1076 // Over-rides default call method, adds signature check1077 if (!$this->hasMethod($methodname)) {1078 return new IXR_Error(-32601, 'server error. requested method "'.$this->message->methodName.'" not specified.');1079 }1080 $method = $this->callbacks[$methodname];1081 $signature = $this->signatures[$methodname];1082 $returnType = array_shift($signature);1083 1084 // Check the number of arguments1085 if (count($args) != count($signature)) {1086 return new IXR_Error(-32602, 'server error. wrong number of method parameters');1087 }1088 1089 // Check the argument types1090 $ok = true;1091 $argsbackup = $args;1092 for ($i = 0, $j = count($args); $i < $j; $i++) {1093 $arg = array_shift($args);1094 $type = array_shift($signature);1095 switch ($type) {1096 case 'int':1097 case 'i4':1098 if (is_array($arg) || !is_int($arg)) {1099 $ok = false;1100 }1101 break;1102 case 'base64':1103 case 'string':1104 if (!is_string($arg)) {1105 $ok = false;1106 }1107 break;1108 case 'boolean':1109 if ($arg !== false && $arg !== true) {1110 $ok = false;1111 }1112 break;1113 case 'float':1114 case 'double':1115 if (!is_float($arg)) {1116 $ok = false;1117 }1118 break;1119 case 'date':1120 case 'dateTime.iso8601':1121 if (!is_a($arg, 'IXR_Date')) {1122 $ok = false;1123 }1124 break;1125 }1126 if (!$ok) {1127 return new IXR_Error(-32602, 'server error. invalid method parameters');1128 }1129 }1130 // It passed the test - run the "real" method call1131 return parent::call($methodname, $argsbackup);1132 }1133 1134 function methodSignature($method)1135 {1136 if (!$this->hasMethod($method)) {1137 return new IXR_Error(-32601, 'server error. requested method "'.$method.'" not specified.');1138 }1139 // We should be returning an array of types1140 $types = $this->signatures[$method];1141 $return = array();1142 foreach ($types as $type) {1143 switch ($type) {1144 case 'string':1145 $return[] = 'string';1146 break;1147 case 'int':1148 case 'i4':1149 $return[] = 42;1150 break;1151 case 'double':1152 $return[] = 3.1415;1153 break;1154 case 'dateTime.iso8601':1155 $return[] = new IXR_Date(time());1156 break;1157 case 'boolean':1158 $return[] = true;1159 break;1160 case 'base64':1161 $return[] = new IXR_Base64('base64');1162 break;1163 case 'array':1164 $return[] = array('array');1165 break;1166 case 'struct':1167 $return[] = array('struct' => 'struct');1168 break;1169 }1170 }1171 return $return;1172 }1173 1174 function methodHelp($method)1175 {1176 return $this->help[$method];1177 }1178 }1179 1180 2 /** 1181 3 * IXR_ClientMulticall -
trunk/src/wp-includes/IXR/class-IXR-date.php
r38384 r38389 1 1 <?php 2 /**3 * IXR - The Incutio XML-RPC Library4 *5 * Copyright (c) 2010, Incutio Ltd.6 * All rights reserved.7 *8 * Redistribution and use in source and binary forms, with or without9 * 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 copyright14 * notice, this list of conditions and the following disclaimer in the15 * documentation and/or other materials provided with the distribution.16 * - Neither the name of Incutio Ltd. nor the names of its contributors17 * may be used to endorse or promote products derived from this software18 * without specific prior written permission.19 *20 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS21 * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,22 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR23 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR24 * 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, OR27 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY28 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT29 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE30 * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.31 *32 * @package IXR33 * @since 1.5.034 *35 * @copyright Incutio Ltd 2010 (http://www.incutio.com)36 * @version 1.7.4 7th September 201037 * @author Simon Willison38 * @link http://scripts.incutio.com/xmlrpc/ Site/manual39 * @license http://www.opensource.org/licenses/bsd-license.php BSD40 */41 42 /**43 * IXR_Value44 *45 * @package IXR46 * @since 1.5.047 */48 class IXR_Value {49 var $data;50 var $type;51 52 /**53 * PHP5 constructor.54 */55 function __construct( $data, $type = false )56 {57 $this->data = $data;58 if (!$type) {59 $type = $this->calculateType();60 }61 $this->type = $type;62 if ($type == 'struct') {63 // Turn all the values in the array in to new IXR_Value objects64 foreach ($this->data as $key => $value) {65 $this->data[$key] = new IXR_Value($value);66 }67 }68 if ($type == 'array') {69 for ($i = 0, $j = count($this->data); $i < $j; $i++) {70 $this->data[$i] = new IXR_Value($this->data[$i]);71 }72 }73 }74 75 /**76 * PHP4 constructor.77 */78 public function IXR_Value( $data, $type = false ) {79 self::__construct( $data, $type );80 }81 82 function calculateType()83 {84 if ($this->data === true || $this->data === false) {85 return 'boolean';86 }87 if (is_integer($this->data)) {88 return 'int';89 }90 if (is_double($this->data)) {91 return 'double';92 }93 94 // Deal with IXR object types base64 and date95 if (is_object($this->data) && is_a($this->data, 'IXR_Date')) {96 return 'date';97 }98 if (is_object($this->data) && is_a($this->data, 'IXR_Base64')) {99 return 'base64';100 }101 102 // If it is a normal PHP object convert it in to a struct103 if (is_object($this->data)) {104 $this->data = get_object_vars($this->data);105 return 'struct';106 }107 if (!is_array($this->data)) {108 return 'string';109 }110 111 // We have an array - is it an array or a struct?112 if ($this->isStruct($this->data)) {113 return 'struct';114 } else {115 return 'array';116 }117 }118 119 function getXml()120 {121 // Return XML for this value122 switch ($this->type) {123 case 'boolean':124 return '<boolean>'.(($this->data) ? '1' : '0').'</boolean>';125 break;126 case 'int':127 return '<int>'.$this->data.'</int>';128 break;129 case 'double':130 return '<double>'.$this->data.'</double>';131 break;132 case 'string':133 return '<string>'.htmlspecialchars($this->data).'</string>';134 break;135 case 'array':136 $return = '<array><data>'."\n";137 foreach ($this->data as $item) {138 $return .= ' <value>'.$item->getXml()."</value>\n";139 }140 $return .= '</data></array>';141 return $return;142 break;143 case 'struct':144 $return = '<struct>'."\n";145 foreach ($this->data as $name => $value) {146 $name = htmlspecialchars($name);147 $return .= " <member><name>$name</name><value>";148 $return .= $value->getXml()."</value></member>\n";149 }150 $return .= '</struct>';151 return $return;152 break;153 case 'date':154 case 'base64':155 return $this->data->getXml();156 break;157 }158 return false;159 }160 161 /**162 * Checks whether or not the supplied array is a struct or not163 *164 * @param array $array165 * @return bool166 */167 function isStruct($array)168 {169 $expected = 0;170 foreach ($array as $key => $value) {171 if ((string)$key !== (string)$expected) {172 return true;173 }174 $expected++;175 }176 return false;177 }178 }179 180 /**181 * IXR_MESSAGE182 *183 * @package IXR184 * @since 1.5.0185 *186 */187 class IXR_Message188 {189 var $message;190 var $messageType; // methodCall / methodResponse / fault191 var $faultCode;192 var $faultString;193 var $methodName;194 var $params;195 196 // Current variable stacks197 var $_arraystructs = array(); // The stack used to keep track of the current array/struct198 var $_arraystructstypes = array(); // Stack keeping track of if things are structs or array199 var $_currentStructName = array(); // A stack as well200 var $_param;201 var $_value;202 var $_currentTag;203 var $_currentTagContents;204 // The XML parser205 var $_parser;206 207 /**208 * PHP5 constructor.209 */210 function __construct( $message )211 {212 $this->message =& $message;213 }214 215 /**216 * PHP4 constructor.217 */218 public function IXR_Message( $message ) {219 self::__construct( $message );220 }221 222 function parse()223 {224 // first remove the XML declaration225 // merged from WP #10698 - this method avoids the RAM usage of preg_replace on very large messages226 $header = preg_replace( '/<\?xml.*?\?'.'>/s', '', substr( $this->message, 0, 100 ), 1 );227 $this->message = trim( substr_replace( $this->message, $header, 0, 100 ) );228 if ( '' == $this->message ) {229 return false;230 }231 232 // Then remove the DOCTYPE233 $header = preg_replace( '/^<!DOCTYPE[^>]*+>/i', '', substr( $this->message, 0, 200 ), 1 );234 $this->message = trim( substr_replace( $this->message, $header, 0, 200 ) );235 if ( '' == $this->message ) {236 return false;237 }238 239 // Check that the root tag is valid240 $root_tag = substr( $this->message, 0, strcspn( substr( $this->message, 0, 20 ), "> \t\r\n" ) );241 if ( '<!DOCTYPE' === strtoupper( $root_tag ) ) {242 return false;243 }244 if ( ! in_array( $root_tag, array( '<methodCall', '<methodResponse', '<fault' ) ) ) {245 return false;246 }247 248 // Bail if there are too many elements to parse249 $element_limit = 30000;250 if ( function_exists( 'apply_filters' ) ) {251 /**252 * Filters the number of elements to parse in an XML-RPC response.253 *254 * @since 4.0.0255 *256 * @param int $element_limit Default elements limit.257 */258 $element_limit = apply_filters( 'xmlrpc_element_limit', $element_limit );259 }260 if ( $element_limit && 2 * $element_limit < substr_count( $this->message, '<' ) ) {261 return false;262 }263 264 $this->_parser = xml_parser_create();265 // Set XML parser to take the case of tags in to account266 xml_parser_set_option($this->_parser, XML_OPTION_CASE_FOLDING, false);267 // Set XML parser callback functions268 xml_set_object($this->_parser, $this);269 xml_set_element_handler($this->_parser, 'tag_open', 'tag_close');270 xml_set_character_data_handler($this->_parser, 'cdata');271 272 // 256Kb, parse in chunks to avoid the RAM usage on very large messages273 $chunk_size = 262144;274 275 /**276 * Filters the chunk size that can be used to parse an XML-RPC reponse message.277 *278 * @since 4.4.0279 *280 * @param int $chunk_size Chunk size to parse in bytes.281 */282 $chunk_size = apply_filters( 'xmlrpc_chunk_parsing_size', $chunk_size );283 284 $final = false;285 do {286 if (strlen($this->message) <= $chunk_size) {287 $final = true;288 }289 $part = substr($this->message, 0, $chunk_size);290 $this->message = substr($this->message, $chunk_size);291 if (!xml_parse($this->_parser, $part, $final)) {292 return false;293 }294 if ($final) {295 break;296 }297 } while (true);298 xml_parser_free($this->_parser);299 300 // Grab the error messages, if any301 if ($this->messageType == 'fault') {302 $this->faultCode = $this->params[0]['faultCode'];303 $this->faultString = $this->params[0]['faultString'];304 }305 return true;306 }307 308 function tag_open($parser, $tag, $attr)309 {310 $this->_currentTagContents = '';311 $this->currentTag = $tag;312 switch($tag) {313 case 'methodCall':314 case 'methodResponse':315 case 'fault':316 $this->messageType = $tag;317 break;318 /* Deal with stacks of arrays and structs */319 case 'data': // data is to all intents and puposes more interesting than array320 $this->_arraystructstypes[] = 'array';321 $this->_arraystructs[] = array();322 break;323 case 'struct':324 $this->_arraystructstypes[] = 'struct';325 $this->_arraystructs[] = array();326 break;327 }328 }329 330 function cdata($parser, $cdata)331 {332 $this->_currentTagContents .= $cdata;333 }334 335 function tag_close($parser, $tag)336 {337 $valueFlag = false;338 switch($tag) {339 case 'int':340 case 'i4':341 $value = (int)trim($this->_currentTagContents);342 $valueFlag = true;343 break;344 case 'double':345 $value = (double)trim($this->_currentTagContents);346 $valueFlag = true;347 break;348 case 'string':349 $value = (string)trim($this->_currentTagContents);350 $valueFlag = true;351 break;352 case 'dateTime.iso8601':353 $value = new IXR_Date(trim($this->_currentTagContents));354 $valueFlag = true;355 break;356 case 'value':357 // "If no type is indicated, the type is string."358 if (trim($this->_currentTagContents) != '') {359 $value = (string)$this->_currentTagContents;360 $valueFlag = true;361 }362 break;363 case 'boolean':364 $value = (boolean)trim($this->_currentTagContents);365 $valueFlag = true;366 break;367 case 'base64':368 $value = base64_decode($this->_currentTagContents);369 $valueFlag = true;370 break;371 /* Deal with stacks of arrays and structs */372 case 'data':373 case 'struct':374 $value = array_pop($this->_arraystructs);375 array_pop($this->_arraystructstypes);376 $valueFlag = true;377 break;378 case 'member':379 array_pop($this->_currentStructName);380 break;381 case 'name':382 $this->_currentStructName[] = trim($this->_currentTagContents);383 break;384 case 'methodName':385 $this->methodName = trim($this->_currentTagContents);386 break;387 }388 389 if ($valueFlag) {390 if (count($this->_arraystructs) > 0) {391 // Add value to struct or array392 if ($this->_arraystructstypes[count($this->_arraystructstypes)-1] == 'struct') {393 // Add to struct394 $this->_arraystructs[count($this->_arraystructs)-1][$this->_currentStructName[count($this->_currentStructName)-1]] = $value;395 } else {396 // Add to array397 $this->_arraystructs[count($this->_arraystructs)-1][] = $value;398 }399 } else {400 // Just add as a parameter401 $this->params[] = $value;402 }403 }404 $this->_currentTagContents = '';405 }406 }407 408 /**409 * IXR_Server410 *411 * @package IXR412 * @since 1.5.0413 */414 class IXR_Server415 {416 var $data;417 var $callbacks = array();418 var $message;419 var $capabilities;420 421 /**422 * PHP5 constructor.423 */424 function __construct( $callbacks = false, $data = false, $wait = false )425 {426 $this->setCapabilities();427 if ($callbacks) {428 $this->callbacks = $callbacks;429 }430 $this->setCallbacks();431 if (!$wait) {432 $this->serve($data);433 }434 }435 436 /**437 * PHP4 constructor.438 */439 public function IXR_Server( $callbacks = false, $data = false, $wait = false ) {440 self::__construct( $callbacks, $data, $wait );441 }442 443 function serve($data = false)444 {445 if (!$data) {446 if (isset($_SERVER['REQUEST_METHOD']) && $_SERVER['REQUEST_METHOD'] !== 'POST') {447 if ( function_exists( 'status_header' ) ) {448 status_header( 405 ); // WP #20986449 header( 'Allow: POST' );450 }451 header('Content-Type: text/plain'); // merged from WP #9093452 die('XML-RPC server accepts POST requests only.');453 }454 455 global $HTTP_RAW_POST_DATA;456 if (empty($HTTP_RAW_POST_DATA)) {457 // workaround for a bug in PHP 5.2.2 - http://bugs.php.net/bug.php?id=41293458 $data = file_get_contents('php://input');459 } else {460 $data =& $HTTP_RAW_POST_DATA;461 }462 }463 $this->message = new IXR_Message($data);464 if (!$this->message->parse()) {465 $this->error(-32700, 'parse error. not well formed');466 }467 if ($this->message->messageType != 'methodCall') {468 $this->error(-32600, 'server error. invalid xml-rpc. not conforming to spec. Request must be a methodCall');469 }470 $result = $this->call($this->message->methodName, $this->message->params);471 472 // Is the result an error?473 if (is_a($result, 'IXR_Error')) {474 $this->error($result);475 }476 477 // Encode the result478 $r = new IXR_Value($result);479 $resultxml = $r->getXml();480 481 // Create the XML482 $xml = <<<EOD483 <methodResponse>484 <params>485 <param>486 <value>487 $resultxml488 </value>489 </param>490 </params>491 </methodResponse>492 493 EOD;494 // Send it495 $this->output($xml);496 }497 498 function call($methodname, $args)499 {500 if (!$this->hasMethod($methodname)) {501 return new IXR_Error(-32601, 'server error. requested method '.$methodname.' does not exist.');502 }503 $method = $this->callbacks[$methodname];504 505 // Perform the callback and send the response506 if (count($args) == 1) {507 // If only one parameter just send that instead of the whole array508 $args = $args[0];509 }510 511 // Are we dealing with a function or a method?512 if (is_string($method) && substr($method, 0, 5) == 'this:') {513 // It's a class method - check it exists514 $method = substr($method, 5);515 if (!method_exists($this, $method)) {516 return new IXR_Error(-32601, 'server error. requested class method "'.$method.'" does not exist.');517 }518 519 //Call the method520 $result = $this->$method($args);521 } else {522 // It's a function - does it exist?523 if (is_array($method)) {524 if (!is_callable(array($method[0], $method[1]))) {525 return new IXR_Error(-32601, 'server error. requested object method "'.$method[1].'" does not exist.');526 }527 } else if (!function_exists($method)) {528 return new IXR_Error(-32601, 'server error. requested function "'.$method.'" does not exist.');529 }530 531 // Call the function532 $result = call_user_func($method, $args);533 }534 return $result;535 }536 537 function error($error, $message = false)538 {539 // Accepts either an error object or an error code and message540 if ($message && !is_object($error)) {541 $error = new IXR_Error($error, $message);542 }543 $this->output($error->getXml());544 }545 546 function output($xml)547 {548 $charset = function_exists('get_option') ? get_option('blog_charset') : '';549 if ($charset)550 $xml = '<?xml version="1.0" encoding="'.$charset.'"?>'."\n".$xml;551 else552 $xml = '<?xml version="1.0"?>'."\n".$xml;553 $length = strlen($xml);554 header('Connection: close');555 if ($charset)556 header('Content-Type: text/xml; charset='.$charset);557 else558 header('Content-Type: text/xml');559 header('Date: '.date('r'));560 echo $xml;561 exit;562 }563 564 function hasMethod($method)565 {566 return in_array($method, array_keys($this->callbacks));567 }568 569 function setCapabilities()570 {571 // Initialises capabilities array572 $this->capabilities = array(573 'xmlrpc' => array(574 'specUrl' => 'http://www.xmlrpc.com/spec',575 'specVersion' => 1576 ),577 'faults_interop' => array(578 'specUrl' => 'http://xmlrpc-epi.sourceforge.net/specs/rfc.fault_codes.php',579 'specVersion' => 20010516580 ),581 'system.multicall' => array(582 'specUrl' => 'http://www.xmlrpc.com/discuss/msgReader$1208',583 'specVersion' => 1584 ),585 );586 }587 588 function getCapabilities($args)589 {590 return $this->capabilities;591 }592 593 function setCallbacks()594 {595 $this->callbacks['system.getCapabilities'] = 'this:getCapabilities';596 $this->callbacks['system.listMethods'] = 'this:listMethods';597 $this->callbacks['system.multicall'] = 'this:multiCall';598 }599 600 function listMethods($args)601 {602 // Returns a list of methods - uses array_reverse to ensure user defined603 // methods are listed before server defined methods604 return array_reverse(array_keys($this->callbacks));605 }606 607 function multiCall($methodcalls)608 {609 // See http://www.xmlrpc.com/discuss/msgReader$1208610 $return = array();611 foreach ($methodcalls as $call) {612 $method = $call['methodName'];613 $params = $call['params'];614 if ($method == 'system.multicall') {615 $result = new IXR_Error(-32600, 'Recursive calls to system.multicall are forbidden');616 } else {617 $result = $this->call($method, $params);618 }619 if (is_a($result, 'IXR_Error')) {620 $return[] = array(621 'faultCode' => $result->code,622 'faultString' => $result->message623 );624 } else {625 $return[] = array($result);626 }627 }628 return $return;629 }630 }631 632 /**633 * IXR_Request634 *635 * @package IXR636 * @since 1.5.0637 */638 class IXR_Request639 {640 var $method;641 var $args;642 var $xml;643 644 /**645 * PHP5 constructor.646 */647 function __construct($method, $args)648 {649 $this->method = $method;650 $this->args = $args;651 $this->xml = <<<EOD652 <?xml version="1.0"?>653 <methodCall>654 <methodName>{$this->method}</methodName>655 <params>656 657 EOD;658 foreach ($this->args as $arg) {659 $this->xml .= '<param><value>';660 $v = new IXR_Value($arg);661 $this->xml .= $v->getXml();662 $this->xml .= "</value></param>\n";663 }664 $this->xml .= '</params></methodCall>';665 }666 667 /**668 * PHP4 constructor.669 */670 public function IXR_Request( $method, $args ) {671 self::__construct( $method, $args );672 }673 674 function getLength()675 {676 return strlen($this->xml);677 }678 679 function getXml()680 {681 return $this->xml;682 }683 }684 685 /**686 * IXR_Client687 *688 * @package IXR689 * @since 1.5.0690 *691 */692 class IXR_Client693 {694 var $server;695 var $port;696 var $path;697 var $useragent;698 var $response;699 var $message = false;700 var $debug = false;701 var $timeout;702 var $headers = array();703 704 // Storage place for an error message705 var $error = false;706 707 /**708 * PHP5 constructor.709 */710 function __construct( $server, $path = false, $port = 80, $timeout = 15 )711 {712 if (!$path) {713 // Assume we have been given a URL instead714 $bits = parse_url($server);715 $this->server = $bits['host'];716 $this->port = isset($bits['port']) ? $bits['port'] : 80;717 $this->path = isset($bits['path']) ? $bits['path'] : '/';718 719 // Make absolutely sure we have a path720 if (!$this->path) {721 $this->path = '/';722 }723 724 if ( ! empty( $bits['query'] ) ) {725 $this->path .= '?' . $bits['query'];726 }727 } else {728 $this->server = $server;729 $this->path = $path;730 $this->port = $port;731 }732 $this->useragent = 'The Incutio XML-RPC PHP Library';733 $this->timeout = $timeout;734 }735 736 /**737 * PHP4 constructor.738 */739 public function IXR_Client( $server, $path = false, $port = 80, $timeout = 15 ) {740 self::__construct( $server, $path, $port, $timeout );741 }742 743 function query()744 {745 $args = func_get_args();746 $method = array_shift($args);747 $request = new IXR_Request($method, $args);748 $length = $request->getLength();749 $xml = $request->getXml();750 $r = "\r\n";751 $request = "POST {$this->path} HTTP/1.0$r";752 753 // Merged from WP #8145 - allow custom headers754 $this->headers['Host'] = $this->server;755 $this->headers['Content-Type'] = 'text/xml';756 $this->headers['User-Agent'] = $this->useragent;757 $this->headers['Content-Length']= $length;758 759 foreach( $this->headers as $header => $value ) {760 $request .= "{$header}: {$value}{$r}";761 }762 $request .= $r;763 764 $request .= $xml;765 766 // Now send the request767 if ($this->debug) {768 echo '<pre class="ixr_request">'.htmlspecialchars($request)."\n</pre>\n\n";769 }770 771 if ($this->timeout) {772 $fp = @fsockopen($this->server, $this->port, $errno, $errstr, $this->timeout);773 } else {774 $fp = @fsockopen($this->server, $this->port, $errno, $errstr);775 }776 if (!$fp) {777 $this->error = new IXR_Error(-32300, 'transport error - could not open socket');778 return false;779 }780 fputs($fp, $request);781 $contents = '';782 $debugContents = '';783 $gotFirstLine = false;784 $gettingHeaders = true;785 while (!feof($fp)) {786 $line = fgets($fp, 4096);787 if (!$gotFirstLine) {788 // Check line for '200'789 if (strstr($line, '200') === false) {790 $this->error = new IXR_Error(-32300, 'transport error - HTTP status code was not 200');791 return false;792 }793 $gotFirstLine = true;794 }795 if (trim($line) == '') {796 $gettingHeaders = false;797 }798 if (!$gettingHeaders) {799 // merged from WP #12559 - remove trim800 $contents .= $line;801 }802 if ($this->debug) {803 $debugContents .= $line;804 }805 }806 if ($this->debug) {807 echo '<pre class="ixr_response">'.htmlspecialchars($debugContents)."\n</pre>\n\n";808 }809 810 // Now parse what we've got back811 $this->message = new IXR_Message($contents);812 if (!$this->message->parse()) {813 // XML error814 $this->error = new IXR_Error(-32700, 'parse error. not well formed');815 return false;816 }817 818 // Is the message a fault?819 if ($this->message->messageType == 'fault') {820 $this->error = new IXR_Error($this->message->faultCode, $this->message->faultString);821 return false;822 }823 824 // Message must be OK825 return true;826 }827 828 function getResponse()829 {830 // methodResponses can only have one param - return that831 return $this->message->params[0];832 }833 834 function isError()835 {836 return (is_object($this->error));837 }838 839 function getErrorCode()840 {841 return $this->error->code;842 }843 844 function getErrorMessage()845 {846 return $this->error->message;847 }848 }849 850 851 /**852 * IXR_Error853 *854 * @package IXR855 * @since 1.5.0856 */857 class IXR_Error858 {859 var $code;860 var $message;861 862 /**863 * PHP5 constructor.864 */865 function __construct( $code, $message )866 {867 $this->code = $code;868 $this->message = htmlspecialchars($message);869 }870 871 /**872 * PHP4 constructor.873 */874 public function IXR_Error( $code, $message ) {875 self::__construct( $code, $message );876 }877 878 function getXml()879 {880 $xml = <<<EOD881 <methodResponse>882 <fault>883 <value>884 <struct>885 <member>886 <name>faultCode</name>887 <value><int>{$this->code}</int></value>888 </member>889 <member>890 <name>faultString</name>891 <value><string>{$this->message}</string></value>892 </member>893 </struct>894 </value>895 </fault>896 </methodResponse>897 898 EOD;899 return $xml;900 }901 }902 2 903 3 /** … … 973 73 } 974 74 } 975 976 /**977 * IXR_Base64978 *979 * @package IXR980 * @since 1.5.0981 */982 class IXR_Base64983 {984 var $data;985 986 /**987 * PHP5 constructor.988 */989 function __construct( $data )990 {991 $this->data = $data;992 }993 994 /**995 * PHP4 constructor.996 */997 public function IXR_Base64( $data ) {998 self::__construct( $data );999 }1000 1001 function getXml()1002 {1003 return '<base64>'.base64_encode($this->data).'</base64>';1004 }1005 }1006 1007 /**1008 * IXR_IntrospectionServer1009 *1010 * @package IXR1011 * @since 1.5.01012 */1013 class IXR_IntrospectionServer extends IXR_Server1014 {1015 var $signatures;1016 var $help;1017 1018 /**1019 * PHP5 constructor.1020 */1021 function __construct()1022 {1023 $this->setCallbacks();1024 $this->setCapabilities();1025 $this->capabilities['introspection'] = array(1026 'specUrl' => 'http://xmlrpc.usefulinc.com/doc/reserved.html',1027 'specVersion' => 11028 );1029 $this->addCallback(1030 'system.methodSignature',1031 'this:methodSignature',1032 array('array', 'string'),1033 'Returns an array describing the return type and required parameters of a method'1034 );1035 $this->addCallback(1036 'system.getCapabilities',1037 'this:getCapabilities',1038 array('struct'),1039 'Returns a struct describing the XML-RPC specifications supported by this server'1040 );1041 $this->addCallback(1042 'system.listMethods',1043 'this:listMethods',1044 array('array'),1045 'Returns an array of available methods on this server'1046 );1047 $this->addCallback(1048 'system.methodHelp',1049 'this:methodHelp',1050 array('string', 'string'),1051 'Returns a documentation string for the specified method'1052 );1053 }1054 1055 /**1056 * PHP4 constructor.1057 */1058 public function IXR_IntrospectionServer() {1059 self::__construct();1060 }1061 1062 function addCallback($method, $callback, $args, $help)1063 {1064 $this->callbacks[$method] = $callback;1065 $this->signatures[$method] = $args;1066 $this->help[$method] = $help;1067 }1068 1069 function call($methodname, $args)1070 {1071 // Make sure it's in an array1072 if ($args && !is_array($args)) {1073 $args = array($args);1074 }1075 1076 // Over-rides default call method, adds signature check1077 if (!$this->hasMethod($methodname)) {1078 return new IXR_Error(-32601, 'server error. requested method "'.$this->message->methodName.'" not specified.');1079 }1080 $method = $this->callbacks[$methodname];1081 $signature = $this->signatures[$methodname];1082 $returnType = array_shift($signature);1083 1084 // Check the number of arguments1085 if (count($args) != count($signature)) {1086 return new IXR_Error(-32602, 'server error. wrong number of method parameters');1087 }1088 1089 // Check the argument types1090 $ok = true;1091 $argsbackup = $args;1092 for ($i = 0, $j = count($args); $i < $j; $i++) {1093 $arg = array_shift($args);1094 $type = array_shift($signature);1095 switch ($type) {1096 case 'int':1097 case 'i4':1098 if (is_array($arg) || !is_int($arg)) {1099 $ok = false;1100 }1101 break;1102 case 'base64':1103 case 'string':1104 if (!is_string($arg)) {1105 $ok = false;1106 }1107 break;1108 case 'boolean':1109 if ($arg !== false && $arg !== true) {1110 $ok = false;1111 }1112 break;1113 case 'float':1114 case 'double':1115 if (!is_float($arg)) {1116 $ok = false;1117 }1118 break;1119 case 'date':1120 case 'dateTime.iso8601':1121 if (!is_a($arg, 'IXR_Date')) {1122 $ok = false;1123 }1124 break;1125 }1126 if (!$ok) {1127 return new IXR_Error(-32602, 'server error. invalid method parameters');1128 }1129 }1130 // It passed the test - run the "real" method call1131 return parent::call($methodname, $argsbackup);1132 }1133 1134 function methodSignature($method)1135 {1136 if (!$this->hasMethod($method)) {1137 return new IXR_Error(-32601, 'server error. requested method "'.$method.'" not specified.');1138 }1139 // We should be returning an array of types1140 $types = $this->signatures[$method];1141 $return = array();1142 foreach ($types as $type) {1143 switch ($type) {1144 case 'string':1145 $return[] = 'string';1146 break;1147 case 'int':1148 case 'i4':1149 $return[] = 42;1150 break;1151 case 'double':1152 $return[] = 3.1415;1153 break;1154 case 'dateTime.iso8601':1155 $return[] = new IXR_Date(time());1156 break;1157 case 'boolean':1158 $return[] = true;1159 break;1160 case 'base64':1161 $return[] = new IXR_Base64('base64');1162 break;1163 case 'array':1164 $return[] = array('array');1165 break;1166 case 'struct':1167 $return[] = array('struct' => 'struct');1168 break;1169 }1170 }1171 return $return;1172 }1173 1174 function methodHelp($method)1175 {1176 return $this->help[$method];1177 }1178 }1179 1180 /**1181 * IXR_ClientMulticall1182 *1183 * @package IXR1184 * @since 1.5.01185 */1186 class IXR_ClientMulticall extends IXR_Client1187 {1188 var $calls = array();1189 1190 /**1191 * PHP5 constructor.1192 */1193 function __construct( $server, $path = false, $port = 80 )1194 {1195 parent::IXR_Client($server, $path, $port);1196 $this->useragent = 'The Incutio XML-RPC PHP Library (multicall client)';1197 }1198 1199 /**1200 * PHP4 constructor.1201 */1202 public function IXR_ClientMulticall( $server, $path = false, $port = 80 ) {1203 self::__construct( $server, $path, $port );1204 }1205 1206 function addCall()1207 {1208 $args = func_get_args();1209 $methodName = array_shift($args);1210 $struct = array(1211 'methodName' => $methodName,1212 'params' => $args1213 );1214 $this->calls[] = $struct;1215 }1216 1217 function query()1218 {1219 // Prepare multicall, then call the parent::query() method1220 return parent::query('system.multicall', $this->calls);1221 }1222 } -
trunk/src/wp-includes/IXR/class-IXR-error.php
r38384 r38389 1 1 <?php 2 /**3 * IXR - The Incutio XML-RPC Library4 *5 * Copyright (c) 2010, Incutio Ltd.6 * All rights reserved.7 *8 * Redistribution and use in source and binary forms, with or without9 * 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 copyright14 * notice, this list of conditions and the following disclaimer in the15 * documentation and/or other materials provided with the distribution.16 * - Neither the name of Incutio Ltd. nor the names of its contributors17 * may be used to endorse or promote products derived from this software18 * without specific prior written permission.19 *20 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS21 * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,22 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR23 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR24 * 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, OR27 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY28 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT29 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE30 * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.31 *32 * @package IXR33 * @since 1.5.034 *35 * @copyright Incutio Ltd 2010 (http://www.incutio.com)36 * @version 1.7.4 7th September 201037 * @author Simon Willison38 * @link http://scripts.incutio.com/xmlrpc/ Site/manual39 * @license http://www.opensource.org/licenses/bsd-license.php BSD40 */41 42 /**43 * IXR_Value44 *45 * @package IXR46 * @since 1.5.047 */48 class IXR_Value {49 var $data;50 var $type;51 52 /**53 * PHP5 constructor.54 */55 function __construct( $data, $type = false )56 {57 $this->data = $data;58 if (!$type) {59 $type = $this->calculateType();60 }61 $this->type = $type;62 if ($type == 'struct') {63 // Turn all the values in the array in to new IXR_Value objects64 foreach ($this->data as $key => $value) {65 $this->data[$key] = new IXR_Value($value);66 }67 }68 if ($type == 'array') {69 for ($i = 0, $j = count($this->data); $i < $j; $i++) {70 $this->data[$i] = new IXR_Value($this->data[$i]);71 }72 }73 }74 75 /**76 * PHP4 constructor.77 */78 public function IXR_Value( $data, $type = false ) {79 self::__construct( $data, $type );80 }81 82 function calculateType()83 {84 if ($this->data === true || $this->data === false) {85 return 'boolean';86 }87 if (is_integer($this->data)) {88 return 'int';89 }90 if (is_double($this->data)) {91 return 'double';92 }93 94 // Deal with IXR object types base64 and date95 if (is_object($this->data) && is_a($this->data, 'IXR_Date')) {96 return 'date';97 }98 if (is_object($this->data) && is_a($this->data, 'IXR_Base64')) {99 return 'base64';100 }101 102 // If it is a normal PHP object convert it in to a struct103 if (is_object($this->data)) {104 $this->data = get_object_vars($this->data);105 return 'struct';106 }107 if (!is_array($this->data)) {108 return 'string';109 }110 111 // We have an array - is it an array or a struct?112 if ($this->isStruct($this->data)) {113 return 'struct';114 } else {115 return 'array';116 }117 }118 119 function getXml()120 {121 // Return XML for this value122 switch ($this->type) {123 case 'boolean':124 return '<boolean>'.(($this->data) ? '1' : '0').'</boolean>';125 break;126 case 'int':127 return '<int>'.$this->data.'</int>';128 break;129 case 'double':130 return '<double>'.$this->data.'</double>';131 break;132 case 'string':133 return '<string>'.htmlspecialchars($this->data).'</string>';134 break;135 case 'array':136 $return = '<array><data>'."\n";137 foreach ($this->data as $item) {138 $return .= ' <value>'.$item->getXml()."</value>\n";139 }140 $return .= '</data></array>';141 return $return;142 break;143 case 'struct':144 $return = '<struct>'."\n";145 foreach ($this->data as $name => $value) {146 $name = htmlspecialchars($name);147 $return .= " <member><name>$name</name><value>";148 $return .= $value->getXml()."</value></member>\n";149 }150 $return .= '</struct>';151 return $return;152 break;153 case 'date':154 case 'base64':155 return $this->data->getXml();156 break;157 }158 return false;159 }160 161 /**162 * Checks whether or not the supplied array is a struct or not163 *164 * @param array $array165 * @return bool166 */167 function isStruct($array)168 {169 $expected = 0;170 foreach ($array as $key => $value) {171 if ((string)$key !== (string)$expected) {172 return true;173 }174 $expected++;175 }176 return false;177 }178 }179 180 /**181 * IXR_MESSAGE182 *183 * @package IXR184 * @since 1.5.0185 *186 */187 class IXR_Message188 {189 var $message;190 var $messageType; // methodCall / methodResponse / fault191 var $faultCode;192 var $faultString;193 var $methodName;194 var $params;195 196 // Current variable stacks197 var $_arraystructs = array(); // The stack used to keep track of the current array/struct198 var $_arraystructstypes = array(); // Stack keeping track of if things are structs or array199 var $_currentStructName = array(); // A stack as well200 var $_param;201 var $_value;202 var $_currentTag;203 var $_currentTagContents;204 // The XML parser205 var $_parser;206 207 /**208 * PHP5 constructor.209 */210 function __construct( $message )211 {212 $this->message =& $message;213 }214 215 /**216 * PHP4 constructor.217 */218 public function IXR_Message( $message ) {219 self::__construct( $message );220 }221 222 function parse()223 {224 // first remove the XML declaration225 // merged from WP #10698 - this method avoids the RAM usage of preg_replace on very large messages226 $header = preg_replace( '/<\?xml.*?\?'.'>/s', '', substr( $this->message, 0, 100 ), 1 );227 $this->message = trim( substr_replace( $this->message, $header, 0, 100 ) );228 if ( '' == $this->message ) {229 return false;230 }231 232 // Then remove the DOCTYPE233 $header = preg_replace( '/^<!DOCTYPE[^>]*+>/i', '', substr( $this->message, 0, 200 ), 1 );234 $this->message = trim( substr_replace( $this->message, $header, 0, 200 ) );235 if ( '' == $this->message ) {236 return false;237 }238 239 // Check that the root tag is valid240 $root_tag = substr( $this->message, 0, strcspn( substr( $this->message, 0, 20 ), "> \t\r\n" ) );241 if ( '<!DOCTYPE' === strtoupper( $root_tag ) ) {242 return false;243 }244 if ( ! in_array( $root_tag, array( '<methodCall', '<methodResponse', '<fault' ) ) ) {245 return false;246 }247 248 // Bail if there are too many elements to parse249 $element_limit = 30000;250 if ( function_exists( 'apply_filters' ) ) {251 /**252 * Filters the number of elements to parse in an XML-RPC response.253 *254 * @since 4.0.0255 *256 * @param int $element_limit Default elements limit.257 */258 $element_limit = apply_filters( 'xmlrpc_element_limit', $element_limit );259 }260 if ( $element_limit && 2 * $element_limit < substr_count( $this->message, '<' ) ) {261 return false;262 }263 264 $this->_parser = xml_parser_create();265 // Set XML parser to take the case of tags in to account266 xml_parser_set_option($this->_parser, XML_OPTION_CASE_FOLDING, false);267 // Set XML parser callback functions268 xml_set_object($this->_parser, $this);269 xml_set_element_handler($this->_parser, 'tag_open', 'tag_close');270 xml_set_character_data_handler($this->_parser, 'cdata');271 272 // 256Kb, parse in chunks to avoid the RAM usage on very large messages273 $chunk_size = 262144;274 275 /**276 * Filters the chunk size that can be used to parse an XML-RPC reponse message.277 *278 * @since 4.4.0279 *280 * @param int $chunk_size Chunk size to parse in bytes.281 */282 $chunk_size = apply_filters( 'xmlrpc_chunk_parsing_size', $chunk_size );283 284 $final = false;285 do {286 if (strlen($this->message) <= $chunk_size) {287 $final = true;288 }289 $part = substr($this->message, 0, $chunk_size);290 $this->message = substr($this->message, $chunk_size);291 if (!xml_parse($this->_parser, $part, $final)) {292 return false;293 }294 if ($final) {295 break;296 }297 } while (true);298 xml_parser_free($this->_parser);299 300 // Grab the error messages, if any301 if ($this->messageType == 'fault') {302 $this->faultCode = $this->params[0]['faultCode'];303 $this->faultString = $this->params[0]['faultString'];304 }305 return true;306 }307 308 function tag_open($parser, $tag, $attr)309 {310 $this->_currentTagContents = '';311 $this->currentTag = $tag;312 switch($tag) {313 case 'methodCall':314 case 'methodResponse':315 case 'fault':316 $this->messageType = $tag;317 break;318 /* Deal with stacks of arrays and structs */319 case 'data': // data is to all intents and puposes more interesting than array320 $this->_arraystructstypes[] = 'array';321 $this->_arraystructs[] = array();322 break;323 case 'struct':324 $this->_arraystructstypes[] = 'struct';325 $this->_arraystructs[] = array();326 break;327 }328 }329 330 function cdata($parser, $cdata)331 {332 $this->_currentTagContents .= $cdata;333 }334 335 function tag_close($parser, $tag)336 {337 $valueFlag = false;338 switch($tag) {339 case 'int':340 case 'i4':341 $value = (int)trim($this->_currentTagContents);342 $valueFlag = true;343 break;344 case 'double':345 $value = (double)trim($this->_currentTagContents);346 $valueFlag = true;347 break;348 case 'string':349 $value = (string)trim($this->_currentTagContents);350 $valueFlag = true;351 break;352 case 'dateTime.iso8601':353 $value = new IXR_Date(trim($this->_currentTagContents));354 $valueFlag = true;355 break;356 case 'value':357 // "If no type is indicated, the type is string."358 if (trim($this->_currentTagContents) != '') {359 $value = (string)$this->_currentTagContents;360 $valueFlag = true;361 }362 break;363 case 'boolean':364 $value = (boolean)trim($this->_currentTagContents);365 $valueFlag = true;366 break;367 case 'base64':368 $value = base64_decode($this->_currentTagContents);369 $valueFlag = true;370 break;371 /* Deal with stacks of arrays and structs */372 case 'data':373 case 'struct':374 $value = array_pop($this->_arraystructs);375 array_pop($this->_arraystructstypes);376 $valueFlag = true;377 break;378 case 'member':379 array_pop($this->_currentStructName);380 break;381 case 'name':382 $this->_currentStructName[] = trim($this->_currentTagContents);383 break;384 case 'methodName':385 $this->methodName = trim($this->_currentTagContents);386 break;387 }388 389 if ($valueFlag) {390 if (count($this->_arraystructs) > 0) {391 // Add value to struct or array392 if ($this->_arraystructstypes[count($this->_arraystructstypes)-1] == 'struct') {393 // Add to struct394 $this->_arraystructs[count($this->_arraystructs)-1][$this->_currentStructName[count($this->_currentStructName)-1]] = $value;395 } else {396 // Add to array397 $this->_arraystructs[count($this->_arraystructs)-1][] = $value;398 }399 } else {400 // Just add as a parameter401 $this->params[] = $value;402 }403 }404 $this->_currentTagContents = '';405 }406 }407 408 /**409 * IXR_Server410 *411 * @package IXR412 * @since 1.5.0413 */414 class IXR_Server415 {416 var $data;417 var $callbacks = array();418 var $message;419 var $capabilities;420 421 /**422 * PHP5 constructor.423 */424 function __construct( $callbacks = false, $data = false, $wait = false )425 {426 $this->setCapabilities();427 if ($callbacks) {428 $this->callbacks = $callbacks;429 }430 $this->setCallbacks();431 if (!$wait) {432 $this->serve($data);433 }434 }435 436 /**437 * PHP4 constructor.438 */439 public function IXR_Server( $callbacks = false, $data = false, $wait = false ) {440 self::__construct( $callbacks, $data, $wait );441 }442 443 function serve($data = false)444 {445 if (!$data) {446 if (isset($_SERVER['REQUEST_METHOD']) && $_SERVER['REQUEST_METHOD'] !== 'POST') {447 if ( function_exists( 'status_header' ) ) {448 status_header( 405 ); // WP #20986449 header( 'Allow: POST' );450 }451 header('Content-Type: text/plain'); // merged from WP #9093452 die('XML-RPC server accepts POST requests only.');453 }454 455 global $HTTP_RAW_POST_DATA;456 if (empty($HTTP_RAW_POST_DATA)) {457 // workaround for a bug in PHP 5.2.2 - http://bugs.php.net/bug.php?id=41293458 $data = file_get_contents('php://input');459 } else {460 $data =& $HTTP_RAW_POST_DATA;461 }462 }463 $this->message = new IXR_Message($data);464 if (!$this->message->parse()) {465 $this->error(-32700, 'parse error. not well formed');466 }467 if ($this->message->messageType != 'methodCall') {468 $this->error(-32600, 'server error. invalid xml-rpc. not conforming to spec. Request must be a methodCall');469 }470 $result = $this->call($this->message->methodName, $this->message->params);471 472 // Is the result an error?473 if (is_a($result, 'IXR_Error')) {474 $this->error($result);475 }476 477 // Encode the result478 $r = new IXR_Value($result);479 $resultxml = $r->getXml();480 481 // Create the XML482 $xml = <<<EOD483 <methodResponse>484 <params>485 <param>486 <value>487 $resultxml488 </value>489 </param>490 </params>491 </methodResponse>492 493 EOD;494 // Send it495 $this->output($xml);496 }497 498 function call($methodname, $args)499 {500 if (!$this->hasMethod($methodname)) {501 return new IXR_Error(-32601, 'server error. requested method '.$methodname.' does not exist.');502 }503 $method = $this->callbacks[$methodname];504 505 // Perform the callback and send the response506 if (count($args) == 1) {507 // If only one parameter just send that instead of the whole array508 $args = $args[0];509 }510 511 // Are we dealing with a function or a method?512 if (is_string($method) && substr($method, 0, 5) == 'this:') {513 // It's a class method - check it exists514 $method = substr($method, 5);515 if (!method_exists($this, $method)) {516 return new IXR_Error(-32601, 'server error. requested class method "'.$method.'" does not exist.');517 }518 519 //Call the method520 $result = $this->$method($args);521 } else {522 // It's a function - does it exist?523 if (is_array($method)) {524 if (!is_callable(array($method[0], $method[1]))) {525 return new IXR_Error(-32601, 'server error. requested object method "'.$method[1].'" does not exist.');526 }527 } else if (!function_exists($method)) {528 return new IXR_Error(-32601, 'server error. requested function "'.$method.'" does not exist.');529 }530 531 // Call the function532 $result = call_user_func($method, $args);533 }534 return $result;535 }536 537 function error($error, $message = false)538 {539 // Accepts either an error object or an error code and message540 if ($message && !is_object($error)) {541 $error = new IXR_Error($error, $message);542 }543 $this->output($error->getXml());544 }545 546 function output($xml)547 {548 $charset = function_exists('get_option') ? get_option('blog_charset') : '';549 if ($charset)550 $xml = '<?xml version="1.0" encoding="'.$charset.'"?>'."\n".$xml;551 else552 $xml = '<?xml version="1.0"?>'."\n".$xml;553 $length = strlen($xml);554 header('Connection: close');555 if ($charset)556 header('Content-Type: text/xml; charset='.$charset);557 else558 header('Content-Type: text/xml');559 header('Date: '.date('r'));560 echo $xml;561 exit;562 }563 564 function hasMethod($method)565 {566 return in_array($method, array_keys($this->callbacks));567 }568 569 function setCapabilities()570 {571 // Initialises capabilities array572 $this->capabilities = array(573 'xmlrpc' => array(574 'specUrl' => 'http://www.xmlrpc.com/spec',575 'specVersion' => 1576 ),577 'faults_interop' => array(578 'specUrl' => 'http://xmlrpc-epi.sourceforge.net/specs/rfc.fault_codes.php',579 'specVersion' => 20010516580 ),581 'system.multicall' => array(582 'specUrl' => 'http://www.xmlrpc.com/discuss/msgReader$1208',583 'specVersion' => 1584 ),585 );586 }587 588 function getCapabilities($args)589 {590 return $this->capabilities;591 }592 593 function setCallbacks()594 {595 $this->callbacks['system.getCapabilities'] = 'this:getCapabilities';596 $this->callbacks['system.listMethods'] = 'this:listMethods';597 $this->callbacks['system.multicall'] = 'this:multiCall';598 }599 600 function listMethods($args)601 {602 // Returns a list of methods - uses array_reverse to ensure user defined603 // methods are listed before server defined methods604 return array_reverse(array_keys($this->callbacks));605 }606 607 function multiCall($methodcalls)608 {609 // See http://www.xmlrpc.com/discuss/msgReader$1208610 $return = array();611 foreach ($methodcalls as $call) {612 $method = $call['methodName'];613 $params = $call['params'];614 if ($method == 'system.multicall') {615 $result = new IXR_Error(-32600, 'Recursive calls to system.multicall are forbidden');616 } else {617 $result = $this->call($method, $params);618 }619 if (is_a($result, 'IXR_Error')) {620 $return[] = array(621 'faultCode' => $result->code,622 'faultString' => $result->message623 );624 } else {625 $return[] = array($result);626 }627 }628 return $return;629 }630 }631 632 /**633 * IXR_Request634 *635 * @package IXR636 * @since 1.5.0637 */638 class IXR_Request639 {640 var $method;641 var $args;642 var $xml;643 644 /**645 * PHP5 constructor.646 */647 function __construct($method, $args)648 {649 $this->method = $method;650 $this->args = $args;651 $this->xml = <<<EOD652 <?xml version="1.0"?>653 <methodCall>654 <methodName>{$this->method}</methodName>655 <params>656 657 EOD;658 foreach ($this->args as $arg) {659 $this->xml .= '<param><value>';660 $v = new IXR_Value($arg);661 $this->xml .= $v->getXml();662 $this->xml .= "</value></param>\n";663 }664 $this->xml .= '</params></methodCall>';665 }666 667 /**668 * PHP4 constructor.669 */670 public function IXR_Request( $method, $args ) {671 self::__construct( $method, $args );672 }673 674 function getLength()675 {676 return strlen($this->xml);677 }678 679 function getXml()680 {681 return $this->xml;682 }683 }684 685 /**686 * IXR_Client687 *688 * @package IXR689 * @since 1.5.0690 *691 */692 class IXR_Client693 {694 var $server;695 var $port;696 var $path;697 var $useragent;698 var $response;699 var $message = false;700 var $debug = false;701 var $timeout;702 var $headers = array();703 704 // Storage place for an error message705 var $error = false;706 707 /**708 * PHP5 constructor.709 */710 function __construct( $server, $path = false, $port = 80, $timeout = 15 )711 {712 if (!$path) {713 // Assume we have been given a URL instead714 $bits = parse_url($server);715 $this->server = $bits['host'];716 $this->port = isset($bits['port']) ? $bits['port'] : 80;717 $this->path = isset($bits['path']) ? $bits['path'] : '/';718 719 // Make absolutely sure we have a path720 if (!$this->path) {721 $this->path = '/';722 }723 724 if ( ! empty( $bits['query'] ) ) {725 $this->path .= '?' . $bits['query'];726 }727 } else {728 $this->server = $server;729 $this->path = $path;730 $this->port = $port;731 }732 $this->useragent = 'The Incutio XML-RPC PHP Library';733 $this->timeout = $timeout;734 }735 736 /**737 * PHP4 constructor.738 */739 public function IXR_Client( $server, $path = false, $port = 80, $timeout = 15 ) {740 self::__construct( $server, $path, $port, $timeout );741 }742 743 function query()744 {745 $args = func_get_args();746 $method = array_shift($args);747 $request = new IXR_Request($method, $args);748 $length = $request->getLength();749 $xml = $request->getXml();750 $r = "\r\n";751 $request = "POST {$this->path} HTTP/1.0$r";752 753 // Merged from WP #8145 - allow custom headers754 $this->headers['Host'] = $this->server;755 $this->headers['Content-Type'] = 'text/xml';756 $this->headers['User-Agent'] = $this->useragent;757 $this->headers['Content-Length']= $length;758 759 foreach( $this->headers as $header => $value ) {760 $request .= "{$header}: {$value}{$r}";761 }762 $request .= $r;763 764 $request .= $xml;765 766 // Now send the request767 if ($this->debug) {768 echo '<pre class="ixr_request">'.htmlspecialchars($request)."\n</pre>\n\n";769 }770 771 if ($this->timeout) {772 $fp = @fsockopen($this->server, $this->port, $errno, $errstr, $this->timeout);773 } else {774 $fp = @fsockopen($this->server, $this->port, $errno, $errstr);775 }776 if (!$fp) {777 $this->error = new IXR_Error(-32300, 'transport error - could not open socket');778 return false;779 }780 fputs($fp, $request);781 $contents = '';782 $debugContents = '';783 $gotFirstLine = false;784 $gettingHeaders = true;785 while (!feof($fp)) {786 $line = fgets($fp, 4096);787 if (!$gotFirstLine) {788 // Check line for '200'789 if (strstr($line, '200') === false) {790 $this->error = new IXR_Error(-32300, 'transport error - HTTP status code was not 200');791 return false;792 }793 $gotFirstLine = true;794 }795 if (trim($line) == '') {796 $gettingHeaders = false;797 }798 if (!$gettingHeaders) {799 // merged from WP #12559 - remove trim800 $contents .= $line;801 }802 if ($this->debug) {803 $debugContents .= $line;804 }805 }806 if ($this->debug) {807 echo '<pre class="ixr_response">'.htmlspecialchars($debugContents)."\n</pre>\n\n";808 }809 810 // Now parse what we've got back811 $this->message = new IXR_Message($contents);812 if (!$this->message->parse()) {813 // XML error814 $this->error = new IXR_Error(-32700, 'parse error. not well formed');815 return false;816 }817 818 // Is the message a fault?819 if ($this->message->messageType == 'fault') {820 $this->error = new IXR_Error($this->message->faultCode, $this->message->faultString);821 return false;822 }823 824 // Message must be OK825 return true;826 }827 828 function getResponse()829 {830 // methodResponses can only have one param - return that831 return $this->message->params[0];832 }833 834 function isError()835 {836 return (is_object($this->error));837 }838 839 function getErrorCode()840 {841 return $this->error->code;842 }843 844 function getErrorMessage()845 {846 return $this->error->message;847 }848 }849 850 2 851 3 /** … … 900 52 } 901 53 } 902 903 /**904 * IXR_Date905 *906 * @package IXR907 * @since 1.5.0908 */909 class IXR_Date {910 var $year;911 var $month;912 var $day;913 var $hour;914 var $minute;915 var $second;916 var $timezone;917 918 /**919 * PHP5 constructor.920 */921 function __construct( $time )922 {923 // $time can be a PHP timestamp or an ISO one924 if (is_numeric($time)) {925 $this->parseTimestamp($time);926 } else {927 $this->parseIso($time);928 }929 }930 931 /**932 * PHP4 constructor.933 */934 public function IXR_Date( $time ) {935 self::__construct( $time );936 }937 938 function parseTimestamp($timestamp)939 {940 $this->year = date('Y', $timestamp);941 $this->month = date('m', $timestamp);942 $this->day = date('d', $timestamp);943 $this->hour = date('H', $timestamp);944 $this->minute = date('i', $timestamp);945 $this->second = date('s', $timestamp);946 $this->timezone = '';947 }948 949 function parseIso($iso)950 {951 $this->year = substr($iso, 0, 4);952 $this->month = substr($iso, 4, 2);953 $this->day = substr($iso, 6, 2);954 $this->hour = substr($iso, 9, 2);955 $this->minute = substr($iso, 12, 2);956 $this->second = substr($iso, 15, 2);957 $this->timezone = substr($iso, 17);958 }959 960 function getIso()961 {962 return $this->year.$this->month.$this->day.'T'.$this->hour.':'.$this->minute.':'.$this->second.$this->timezone;963 }964 965 function getXml()966 {967 return '<dateTime.iso8601>'.$this->getIso().'</dateTime.iso8601>';968 }969 970 function getTimestamp()971 {972 return mktime($this->hour, $this->minute, $this->second, $this->month, $this->day, $this->year);973 }974 }975 976 /**977 * IXR_Base64978 *979 * @package IXR980 * @since 1.5.0981 */982 class IXR_Base64983 {984 var $data;985 986 /**987 * PHP5 constructor.988 */989 function __construct( $data )990 {991 $this->data = $data;992 }993 994 /**995 * PHP4 constructor.996 */997 public function IXR_Base64( $data ) {998 self::__construct( $data );999 }1000 1001 function getXml()1002 {1003 return '<base64>'.base64_encode($this->data).'</base64>';1004 }1005 }1006 1007 /**1008 * IXR_IntrospectionServer1009 *1010 * @package IXR1011 * @since 1.5.01012 */1013 class IXR_IntrospectionServer extends IXR_Server1014 {1015 var $signatures;1016 var $help;1017 1018 /**1019 * PHP5 constructor.1020 */1021 function __construct()1022 {1023 $this->setCallbacks();1024 $this->setCapabilities();1025 $this->capabilities['introspection'] = array(1026 'specUrl' => 'http://xmlrpc.usefulinc.com/doc/reserved.html',1027 'specVersion' => 11028 );1029 $this->addCallback(1030 'system.methodSignature',1031 'this:methodSignature',1032 array('array', 'string'),1033 'Returns an array describing the return type and required parameters of a method'1034 );1035 $this->addCallback(1036 'system.getCapabilities',1037 'this:getCapabilities',1038 array('struct'),1039 'Returns a struct describing the XML-RPC specifications supported by this server'1040 );1041 $this->addCallback(1042 'system.listMethods',1043 'this:listMethods',1044 array('array'),1045 'Returns an array of available methods on this server'1046 );1047 $this->addCallback(1048 'system.methodHelp',1049 'this:methodHelp',1050 array('string', 'string'),1051 'Returns a documentation string for the specified method'1052 );1053 }1054 1055 /**1056 * PHP4 constructor.1057 */1058 public function IXR_IntrospectionServer() {1059 self::__construct();1060 }1061 1062 function addCallback($method, $callback, $args, $help)1063 {1064 $this->callbacks[$method] = $callback;1065 $this->signatures[$method] = $args;1066 $this->help[$method] = $help;1067 }1068 1069 function call($methodname, $args)1070 {1071 // Make sure it's in an array1072 if ($args && !is_array($args)) {1073 $args = array($args);1074 }1075 1076 // Over-rides default call method, adds signature check1077 if (!$this->hasMethod($methodname)) {1078 return new IXR_Error(-32601, 'server error. requested method "'.$this->message->methodName.'" not specified.');1079 }1080 $method = $this->callbacks[$methodname];1081 $signature = $this->signatures[$methodname];1082 $returnType = array_shift($signature);1083 1084 // Check the number of arguments1085 if (count($args) != count($signature)) {1086 return new IXR_Error(-32602, 'server error. wrong number of method parameters');1087 }1088 1089 // Check the argument types1090 $ok = true;1091 $argsbackup = $args;1092 for ($i = 0, $j = count($args); $i < $j; $i++) {1093 $arg = array_shift($args);1094 $type = array_shift($signature);1095 switch ($type) {1096 case 'int':1097 case 'i4':1098 if (is_array($arg) || !is_int($arg)) {1099 $ok = false;1100 }1101 break;1102 case 'base64':1103 case 'string':1104 if (!is_string($arg)) {1105 $ok = false;1106 }1107 break;1108 case 'boolean':1109 if ($arg !== false && $arg !== true) {1110 $ok = false;1111 }1112 break;1113 case 'float':1114 case 'double':1115 if (!is_float($arg)) {1116 $ok = false;1117 }1118 break;1119 case 'date':1120 case 'dateTime.iso8601':1121 if (!is_a($arg, 'IXR_Date')) {1122 $ok = false;1123 }1124 break;1125 }1126 if (!$ok) {1127 return new IXR_Error(-32602, 'server error. invalid method parameters');1128 }1129 }1130 // It passed the test - run the "real" method call1131 return parent::call($methodname, $argsbackup);1132 }1133 1134 function methodSignature($method)1135 {1136 if (!$this->hasMethod($method)) {1137 return new IXR_Error(-32601, 'server error. requested method "'.$method.'" not specified.');1138 }1139 // We should be returning an array of types1140 $types = $this->signatures[$method];1141 $return = array();1142 foreach ($types as $type) {1143 switch ($type) {1144 case 'string':1145 $return[] = 'string';1146 break;1147 case 'int':1148 case 'i4':1149 $return[] = 42;1150 break;1151 case 'double':1152 $return[] = 3.1415;1153 break;1154 case 'dateTime.iso8601':1155 $return[] = new IXR_Date(time());1156 break;1157 case 'boolean':1158 $return[] = true;1159 break;1160 case 'base64':1161 $return[] = new IXR_Base64('base64');1162 break;1163 case 'array':1164 $return[] = array('array');1165 break;1166 case 'struct':1167 $return[] = array('struct' => 'struct');1168 break;1169 }1170 }1171 return $return;1172 }1173 1174 function methodHelp($method)1175 {1176 return $this->help[$method];1177 }1178 }1179 1180 /**1181 * IXR_ClientMulticall1182 *1183 * @package IXR1184 * @since 1.5.01185 */1186 class IXR_ClientMulticall extends IXR_Client1187 {1188 var $calls = array();1189 1190 /**1191 * PHP5 constructor.1192 */1193 function __construct( $server, $path = false, $port = 80 )1194 {1195 parent::IXR_Client($server, $path, $port);1196 $this->useragent = 'The Incutio XML-RPC PHP Library (multicall client)';1197 }1198 1199 /**1200 * PHP4 constructor.1201 */1202 public function IXR_ClientMulticall( $server, $path = false, $port = 80 ) {1203 self::__construct( $server, $path, $port );1204 }1205 1206 function addCall()1207 {1208 $args = func_get_args();1209 $methodName = array_shift($args);1210 $struct = array(1211 'methodName' => $methodName,1212 'params' => $args1213 );1214 $this->calls[] = $struct;1215 }1216 1217 function query()1218 {1219 // Prepare multicall, then call the parent::query() method1220 return parent::query('system.multicall', $this->calls);1221 }1222 } -
trunk/src/wp-includes/IXR/class-IXR-introspectionserver.php
r38384 r38389 1 1 <?php 2 /**3 * IXR - The Incutio XML-RPC Library4 *5 * Copyright (c) 2010, Incutio Ltd.6 * All rights reserved.7 *8 * Redistribution and use in source and binary forms, with or without9 * 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 copyright14 * notice, this list of conditions and the following disclaimer in the15 * documentation and/or other materials provided with the distribution.16 * - Neither the name of Incutio Ltd. nor the names of its contributors17 * may be used to endorse or promote products derived from this software18 * without specific prior written permission.19 *20 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS21 * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,22 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR23 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR24 * 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, OR27 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY28 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT29 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE30 * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.31 *32 * @package IXR33 * @since 1.5.034 *35 * @copyright Incutio Ltd 2010 (http://www.incutio.com)36 * @version 1.7.4 7th September 201037 * @author Simon Willison38 * @link http://scripts.incutio.com/xmlrpc/ Site/manual39 * @license http://www.opensource.org/licenses/bsd-license.php BSD40 */41 42 /**43 * IXR_Value44 *45 * @package IXR46 * @since 1.5.047 */48 class IXR_Value {49 var $data;50 var $type;51 52 /**53 * PHP5 constructor.54 */55 function __construct( $data, $type = false )56 {57 $this->data = $data;58 if (!$type) {59 $type = $this->calculateType();60 }61 $this->type = $type;62 if ($type == 'struct') {63 // Turn all the values in the array in to new IXR_Value objects64 foreach ($this->data as $key => $value) {65 $this->data[$key] = new IXR_Value($value);66 }67 }68 if ($type == 'array') {69 for ($i = 0, $j = count($this->data); $i < $j; $i++) {70 $this->data[$i] = new IXR_Value($this->data[$i]);71 }72 }73 }74 75 /**76 * PHP4 constructor.77 */78 public function IXR_Value( $data, $type = false ) {79 self::__construct( $data, $type );80 }81 82 function calculateType()83 {84 if ($this->data === true || $this->data === false) {85 return 'boolean';86 }87 if (is_integer($this->data)) {88 return 'int';89 }90 if (is_double($this->data)) {91 return 'double';92 }93 94 // Deal with IXR object types base64 and date95 if (is_object($this->data) && is_a($this->data, 'IXR_Date')) {96 return 'date';97 }98 if (is_object($this->data) && is_a($this->data, 'IXR_Base64')) {99 return 'base64';100 }101 102 // If it is a normal PHP object convert it in to a struct103 if (is_object($this->data)) {104 $this->data = get_object_vars($this->data);105 return 'struct';106 }107 if (!is_array($this->data)) {108 return 'string';109 }110 111 // We have an array - is it an array or a struct?112 if ($this->isStruct($this->data)) {113 return 'struct';114 } else {115 return 'array';116 }117 }118 119 function getXml()120 {121 // Return XML for this value122 switch ($this->type) {123 case 'boolean':124 return '<boolean>'.(($this->data) ? '1' : '0').'</boolean>';125 break;126 case 'int':127 return '<int>'.$this->data.'</int>';128 break;129 case 'double':130 return '<double>'.$this->data.'</double>';131 break;132 case 'string':133 return '<string>'.htmlspecialchars($this->data).'</string>';134 break;135 case 'array':136 $return = '<array><data>'."\n";137 foreach ($this->data as $item) {138 $return .= ' <value>'.$item->getXml()."</value>\n";139 }140 $return .= '</data></array>';141 return $return;142 break;143 case 'struct':144 $return = '<struct>'."\n";145 foreach ($this->data as $name => $value) {146 $name = htmlspecialchars($name);147 $return .= " <member><name>$name</name><value>";148 $return .= $value->getXml()."</value></member>\n";149 }150 $return .= '</struct>';151 return $return;152 break;153 case 'date':154 case 'base64':155 return $this->data->getXml();156 break;157 }158 return false;159 }160 161 /**162 * Checks whether or not the supplied array is a struct or not163 *164 * @param array $array165 * @return bool166 */167 function isStruct($array)168 {169 $expected = 0;170 foreach ($array as $key => $value) {171 if ((string)$key !== (string)$expected) {172 return true;173 }174 $expected++;175 }176 return false;177 }178 }179 180 /**181 * IXR_MESSAGE182 *183 * @package IXR184 * @since 1.5.0185 *186 */187 class IXR_Message188 {189 var $message;190 var $messageType; // methodCall / methodResponse / fault191 var $faultCode;192 var $faultString;193 var $methodName;194 var $params;195 196 // Current variable stacks197 var $_arraystructs = array(); // The stack used to keep track of the current array/struct198 var $_arraystructstypes = array(); // Stack keeping track of if things are structs or array199 var $_currentStructName = array(); // A stack as well200 var $_param;201 var $_value;202 var $_currentTag;203 var $_currentTagContents;204 // The XML parser205 var $_parser;206 207 /**208 * PHP5 constructor.209 */210 function __construct( $message )211 {212 $this->message =& $message;213 }214 215 /**216 * PHP4 constructor.217 */218 public function IXR_Message( $message ) {219 self::__construct( $message );220 }221 222 function parse()223 {224 // first remove the XML declaration225 // merged from WP #10698 - this method avoids the RAM usage of preg_replace on very large messages226 $header = preg_replace( '/<\?xml.*?\?'.'>/s', '', substr( $this->message, 0, 100 ), 1 );227 $this->message = trim( substr_replace( $this->message, $header, 0, 100 ) );228 if ( '' == $this->message ) {229 return false;230 }231 232 // Then remove the DOCTYPE233 $header = preg_replace( '/^<!DOCTYPE[^>]*+>/i', '', substr( $this->message, 0, 200 ), 1 );234 $this->message = trim( substr_replace( $this->message, $header, 0, 200 ) );235 if ( '' == $this->message ) {236 return false;237 }238 239 // Check that the root tag is valid240 $root_tag = substr( $this->message, 0, strcspn( substr( $this->message, 0, 20 ), "> \t\r\n" ) );241 if ( '<!DOCTYPE' === strtoupper( $root_tag ) ) {242 return false;243 }244 if ( ! in_array( $root_tag, array( '<methodCall', '<methodResponse', '<fault' ) ) ) {245 return false;246 }247 248 // Bail if there are too many elements to parse249 $element_limit = 30000;250 if ( function_exists( 'apply_filters' ) ) {251 /**252 * Filters the number of elements to parse in an XML-RPC response.253 *254 * @since 4.0.0255 *256 * @param int $element_limit Default elements limit.257 */258 $element_limit = apply_filters( 'xmlrpc_element_limit', $element_limit );259 }260 if ( $element_limit && 2 * $element_limit < substr_count( $this->message, '<' ) ) {261 return false;262 }263 264 $this->_parser = xml_parser_create();265 // Set XML parser to take the case of tags in to account266 xml_parser_set_option($this->_parser, XML_OPTION_CASE_FOLDING, false);267 // Set XML parser callback functions268 xml_set_object($this->_parser, $this);269 xml_set_element_handler($this->_parser, 'tag_open', 'tag_close');270 xml_set_character_data_handler($this->_parser, 'cdata');271 272 // 256Kb, parse in chunks to avoid the RAM usage on very large messages273 $chunk_size = 262144;274 275 /**276 * Filters the chunk size that can be used to parse an XML-RPC reponse message.277 *278 * @since 4.4.0279 *280 * @param int $chunk_size Chunk size to parse in bytes.281 */282 $chunk_size = apply_filters( 'xmlrpc_chunk_parsing_size', $chunk_size );283 284 $final = false;285 do {286 if (strlen($this->message) <= $chunk_size) {287 $final = true;288 }289 $part = substr($this->message, 0, $chunk_size);290 $this->message = substr($this->message, $chunk_size);291 if (!xml_parse($this->_parser, $part, $final)) {292 return false;293 }294 if ($final) {295 break;296 }297 } while (true);298 xml_parser_free($this->_parser);299 300 // Grab the error messages, if any301 if ($this->messageType == 'fault') {302 $this->faultCode = $this->params[0]['faultCode'];303 $this->faultString = $this->params[0]['faultString'];304 }305 return true;306 }307 308 function tag_open($parser, $tag, $attr)309 {310 $this->_currentTagContents = '';311 $this->currentTag = $tag;312 switch($tag) {313 case 'methodCall':314 case 'methodResponse':315 case 'fault':316 $this->messageType = $tag;317 break;318 /* Deal with stacks of arrays and structs */319 case 'data': // data is to all intents and puposes more interesting than array320 $this->_arraystructstypes[] = 'array';321 $this->_arraystructs[] = array();322 break;323 case 'struct':324 $this->_arraystructstypes[] = 'struct';325 $this->_arraystructs[] = array();326 break;327 }328 }329 330 function cdata($parser, $cdata)331 {332 $this->_currentTagContents .= $cdata;333 }334 335 function tag_close($parser, $tag)336 {337 $valueFlag = false;338 switch($tag) {339 case 'int':340 case 'i4':341 $value = (int)trim($this->_currentTagContents);342 $valueFlag = true;343 break;344 case 'double':345 $value = (double)trim($this->_currentTagContents);346 $valueFlag = true;347 break;348 case 'string':349 $value = (string)trim($this->_currentTagContents);350 $valueFlag = true;351 break;352 case 'dateTime.iso8601':353 $value = new IXR_Date(trim($this->_currentTagContents));354 $valueFlag = true;355 break;356 case 'value':357 // "If no type is indicated, the type is string."358 if (trim($this->_currentTagContents) != '') {359 $value = (string)$this->_currentTagContents;360 $valueFlag = true;361 }362 break;363 case 'boolean':364 $value = (boolean)trim($this->_currentTagContents);365 $valueFlag = true;366 break;367 case 'base64':368 $value = base64_decode($this->_currentTagContents);369 $valueFlag = true;370 break;371 /* Deal with stacks of arrays and structs */372 case 'data':373 case 'struct':374 $value = array_pop($this->_arraystructs);375 array_pop($this->_arraystructstypes);376 $valueFlag = true;377 break;378 case 'member':379 array_pop($this->_currentStructName);380 break;381 case 'name':382 $this->_currentStructName[] = trim($this->_currentTagContents);383 break;384 case 'methodName':385 $this->methodName = trim($this->_currentTagContents);386 break;387 }388 389 if ($valueFlag) {390 if (count($this->_arraystructs) > 0) {391 // Add value to struct or array392 if ($this->_arraystructstypes[count($this->_arraystructstypes)-1] == 'struct') {393 // Add to struct394 $this->_arraystructs[count($this->_arraystructs)-1][$this->_currentStructName[count($this->_currentStructName)-1]] = $value;395 } else {396 // Add to array397 $this->_arraystructs[count($this->_arraystructs)-1][] = $value;398 }399 } else {400 // Just add as a parameter401 $this->params[] = $value;402 }403 }404 $this->_currentTagContents = '';405 }406 }407 408 /**409 * IXR_Server410 *411 * @package IXR412 * @since 1.5.0413 */414 class IXR_Server415 {416 var $data;417 var $callbacks = array();418 var $message;419 var $capabilities;420 421 /**422 * PHP5 constructor.423 */424 function __construct( $callbacks = false, $data = false, $wait = false )425 {426 $this->setCapabilities();427 if ($callbacks) {428 $this->callbacks = $callbacks;429 }430 $this->setCallbacks();431 if (!$wait) {432 $this->serve($data);433 }434 }435 436 /**437 * PHP4 constructor.438 */439 public function IXR_Server( $callbacks = false, $data = false, $wait = false ) {440 self::__construct( $callbacks, $data, $wait );441 }442 443 function serve($data = false)444 {445 if (!$data) {446 if (isset($_SERVER['REQUEST_METHOD']) && $_SERVER['REQUEST_METHOD'] !== 'POST') {447 if ( function_exists( 'status_header' ) ) {448 status_header( 405 ); // WP #20986449 header( 'Allow: POST' );450 }451 header('Content-Type: text/plain'); // merged from WP #9093452 die('XML-RPC server accepts POST requests only.');453 }454 455 global $HTTP_RAW_POST_DATA;456 if (empty($HTTP_RAW_POST_DATA)) {457 // workaround for a bug in PHP 5.2.2 - http://bugs.php.net/bug.php?id=41293458 $data = file_get_contents('php://input');459 } else {460 $data =& $HTTP_RAW_POST_DATA;461 }462 }463 $this->message = new IXR_Message($data);464 if (!$this->message->parse()) {465 $this->error(-32700, 'parse error. not well formed');466 }467 if ($this->message->messageType != 'methodCall') {468 $this->error(-32600, 'server error. invalid xml-rpc. not conforming to spec. Request must be a methodCall');469 }470 $result = $this->call($this->message->methodName, $this->message->params);471 472 // Is the result an error?473 if (is_a($result, 'IXR_Error')) {474 $this->error($result);475 }476 477 // Encode the result478 $r = new IXR_Value($result);479 $resultxml = $r->getXml();480 481 // Create the XML482 $xml = <<<EOD483 <methodResponse>484 <params>485 <param>486 <value>487 $resultxml488 </value>489 </param>490 </params>491 </methodResponse>492 493 EOD;494 // Send it495 $this->output($xml);496 }497 498 function call($methodname, $args)499 {500 if (!$this->hasMethod($methodname)) {501 return new IXR_Error(-32601, 'server error. requested method '.$methodname.' does not exist.');502 }503 $method = $this->callbacks[$methodname];504 505 // Perform the callback and send the response506 if (count($args) == 1) {507 // If only one parameter just send that instead of the whole array508 $args = $args[0];509 }510 511 // Are we dealing with a function or a method?512 if (is_string($method) && substr($method, 0, 5) == 'this:') {513 // It's a class method - check it exists514 $method = substr($method, 5);515 if (!method_exists($this, $method)) {516 return new IXR_Error(-32601, 'server error. requested class method "'.$method.'" does not exist.');517 }518 519 //Call the method520 $result = $this->$method($args);521 } else {522 // It's a function - does it exist?523 if (is_array($method)) {524 if (!is_callable(array($method[0], $method[1]))) {525 return new IXR_Error(-32601, 'server error. requested object method "'.$method[1].'" does not exist.');526 }527 } else if (!function_exists($method)) {528 return new IXR_Error(-32601, 'server error. requested function "'.$method.'" does not exist.');529 }530 531 // Call the function532 $result = call_user_func($method, $args);533 }534 return $result;535 }536 537 function error($error, $message = false)538 {539 // Accepts either an error object or an error code and message540 if ($message && !is_object($error)) {541 $error = new IXR_Error($error, $message);542 }543 $this->output($error->getXml());544 }545 546 function output($xml)547 {548 $charset = function_exists('get_option') ? get_option('blog_charset') : '';549 if ($charset)550 $xml = '<?xml version="1.0" encoding="'.$charset.'"?>'."\n".$xml;551 else552 $xml = '<?xml version="1.0"?>'."\n".$xml;553 $length = strlen($xml);554 header('Connection: close');555 if ($charset)556 header('Content-Type: text/xml; charset='.$charset);557 else558 header('Content-Type: text/xml');559 header('Date: '.date('r'));560 echo $xml;561 exit;562 }563 564 function hasMethod($method)565 {566 return in_array($method, array_keys($this->callbacks));567 }568 569 function setCapabilities()570 {571 // Initialises capabilities array572 $this->capabilities = array(573 'xmlrpc' => array(574 'specUrl' => 'http://www.xmlrpc.com/spec',575 'specVersion' => 1576 ),577 'faults_interop' => array(578 'specUrl' => 'http://xmlrpc-epi.sourceforge.net/specs/rfc.fault_codes.php',579 'specVersion' => 20010516580 ),581 'system.multicall' => array(582 'specUrl' => 'http://www.xmlrpc.com/discuss/msgReader$1208',583 'specVersion' => 1584 ),585 );586 }587 588 function getCapabilities($args)589 {590 return $this->capabilities;591 }592 593 function setCallbacks()594 {595 $this->callbacks['system.getCapabilities'] = 'this:getCapabilities';596 $this->callbacks['system.listMethods'] = 'this:listMethods';597 $this->callbacks['system.multicall'] = 'this:multiCall';598 }599 600 function listMethods($args)601 {602 // Returns a list of methods - uses array_reverse to ensure user defined603 // methods are listed before server defined methods604 return array_reverse(array_keys($this->callbacks));605 }606 607 function multiCall($methodcalls)608 {609 // See http://www.xmlrpc.com/discuss/msgReader$1208610 $return = array();611 foreach ($methodcalls as $call) {612 $method = $call['methodName'];613 $params = $call['params'];614 if ($method == 'system.multicall') {615 $result = new IXR_Error(-32600, 'Recursive calls to system.multicall are forbidden');616 } else {617 $result = $this->call($method, $params);618 }619 if (is_a($result, 'IXR_Error')) {620 $return[] = array(621 'faultCode' => $result->code,622 'faultString' => $result->message623 );624 } else {625 $return[] = array($result);626 }627 }628 return $return;629 }630 }631 632 /**633 * IXR_Request634 *635 * @package IXR636 * @since 1.5.0637 */638 class IXR_Request639 {640 var $method;641 var $args;642 var $xml;643 644 /**645 * PHP5 constructor.646 */647 function __construct($method, $args)648 {649 $this->method = $method;650 $this->args = $args;651 $this->xml = <<<EOD652 <?xml version="1.0"?>653 <methodCall>654 <methodName>{$this->method}</methodName>655 <params>656 657 EOD;658 foreach ($this->args as $arg) {659 $this->xml .= '<param><value>';660 $v = new IXR_Value($arg);661 $this->xml .= $v->getXml();662 $this->xml .= "</value></param>\n";663 }664 $this->xml .= '</params></methodCall>';665 }666 667 /**668 * PHP4 constructor.669 */670 public function IXR_Request( $method, $args ) {671 self::__construct( $method, $args );672 }673 674 function getLength()675 {676 return strlen($this->xml);677 }678 679 function getXml()680 {681 return $this->xml;682 }683 }684 685 /**686 * IXR_Client687 *688 * @package IXR689 * @since 1.5.0690 *691 */692 class IXR_Client693 {694 var $server;695 var $port;696 var $path;697 var $useragent;698 var $response;699 var $message = false;700 var $debug = false;701 var $timeout;702 var $headers = array();703 704 // Storage place for an error message705 var $error = false;706 707 /**708 * PHP5 constructor.709 */710 function __construct( $server, $path = false, $port = 80, $timeout = 15 )711 {712 if (!$path) {713 // Assume we have been given a URL instead714 $bits = parse_url($server);715 $this->server = $bits['host'];716 $this->port = isset($bits['port']) ? $bits['port'] : 80;717 $this->path = isset($bits['path']) ? $bits['path'] : '/';718 719 // Make absolutely sure we have a path720 if (!$this->path) {721 $this->path = '/';722 }723 724 if ( ! empty( $bits['query'] ) ) {725 $this->path .= '?' . $bits['query'];726 }727 } else {728 $this->server = $server;729 $this->path = $path;730 $this->port = $port;731 }732 $this->useragent = 'The Incutio XML-RPC PHP Library';733 $this->timeout = $timeout;734 }735 736 /**737 * PHP4 constructor.738 */739 public function IXR_Client( $server, $path = false, $port = 80, $timeout = 15 ) {740 self::__construct( $server, $path, $port, $timeout );741 }742 743 function query()744 {745 $args = func_get_args();746 $method = array_shift($args);747 $request = new IXR_Request($method, $args);748 $length = $request->getLength();749 $xml = $request->getXml();750 $r = "\r\n";751 $request = "POST {$this->path} HTTP/1.0$r";752 753 // Merged from WP #8145 - allow custom headers754 $this->headers['Host'] = $this->server;755 $this->headers['Content-Type'] = 'text/xml';756 $this->headers['User-Agent'] = $this->useragent;757 $this->headers['Content-Length']= $length;758 759 foreach( $this->headers as $header => $value ) {760 $request .= "{$header}: {$value}{$r}";761 }762 $request .= $r;763 764 $request .= $xml;765 766 // Now send the request767 if ($this->debug) {768 echo '<pre class="ixr_request">'.htmlspecialchars($request)."\n</pre>\n\n";769 }770 771 if ($this->timeout) {772 $fp = @fsockopen($this->server, $this->port, $errno, $errstr, $this->timeout);773 } else {774 $fp = @fsockopen($this->server, $this->port, $errno, $errstr);775 }776 if (!$fp) {777 $this->error = new IXR_Error(-32300, 'transport error - could not open socket');778 return false;779 }780 fputs($fp, $request);781 $contents = '';782 $debugContents = '';783 $gotFirstLine = false;784 $gettingHeaders = true;785 while (!feof($fp)) {786 $line = fgets($fp, 4096);787 if (!$gotFirstLine) {788 // Check line for '200'789 if (strstr($line, '200') === false) {790 $this->error = new IXR_Error(-32300, 'transport error - HTTP status code was not 200');791 return false;792 }793 $gotFirstLine = true;794 }795 if (trim($line) == '') {796 $gettingHeaders = false;797 }798 if (!$gettingHeaders) {799 // merged from WP #12559 - remove trim800 $contents .= $line;801 }802 if ($this->debug) {803 $debugContents .= $line;804 }805 }806 if ($this->debug) {807 echo '<pre class="ixr_response">'.htmlspecialchars($debugContents)."\n</pre>\n\n";808 }809 810 // Now parse what we've got back811 $this->message = new IXR_Message($contents);812 if (!$this->message->parse()) {813 // XML error814 $this->error = new IXR_Error(-32700, 'parse error. not well formed');815 return false;816 }817 818 // Is the message a fault?819 if ($this->message->messageType == 'fault') {820 $this->error = new IXR_Error($this->message->faultCode, $this->message->faultString);821 return false;822 }823 824 // Message must be OK825 return true;826 }827 828 function getResponse()829 {830 // methodResponses can only have one param - return that831 return $this->message->params[0];832 }833 834 function isError()835 {836 return (is_object($this->error));837 }838 839 function getErrorCode()840 {841 return $this->error->code;842 }843 844 function getErrorMessage()845 {846 return $this->error->message;847 }848 }849 850 851 /**852 * IXR_Error853 *854 * @package IXR855 * @since 1.5.0856 */857 class IXR_Error858 {859 var $code;860 var $message;861 862 /**863 * PHP5 constructor.864 */865 function __construct( $code, $message )866 {867 $this->code = $code;868 $this->message = htmlspecialchars($message);869 }870 871 /**872 * PHP4 constructor.873 */874 public function IXR_Error( $code, $message ) {875 self::__construct( $code, $message );876 }877 878 function getXml()879 {880 $xml = <<<EOD881 <methodResponse>882 <fault>883 <value>884 <struct>885 <member>886 <name>faultCode</name>887 <value><int>{$this->code}</int></value>888 </member>889 <member>890 <name>faultString</name>891 <value><string>{$this->message}</string></value>892 </member>893 </struct>894 </value>895 </fault>896 </methodResponse>897 898 EOD;899 return $xml;900 }901 }902 903 /**904 * IXR_Date905 *906 * @package IXR907 * @since 1.5.0908 */909 class IXR_Date {910 var $year;911 var $month;912 var $day;913 var $hour;914 var $minute;915 var $second;916 var $timezone;917 918 /**919 * PHP5 constructor.920 */921 function __construct( $time )922 {923 // $time can be a PHP timestamp or an ISO one924 if (is_numeric($time)) {925 $this->parseTimestamp($time);926 } else {927 $this->parseIso($time);928 }929 }930 931 /**932 * PHP4 constructor.933 */934 public function IXR_Date( $time ) {935 self::__construct( $time );936 }937 938 function parseTimestamp($timestamp)939 {940 $this->year = date('Y', $timestamp);941 $this->month = date('m', $timestamp);942 $this->day = date('d', $timestamp);943 $this->hour = date('H', $timestamp);944 $this->minute = date('i', $timestamp);945 $this->second = date('s', $timestamp);946 $this->timezone = '';947 }948 949 function parseIso($iso)950 {951 $this->year = substr($iso, 0, 4);952 $this->month = substr($iso, 4, 2);953 $this->day = substr($iso, 6, 2);954 $this->hour = substr($iso, 9, 2);955 $this->minute = substr($iso, 12, 2);956 $this->second = substr($iso, 15, 2);957 $this->timezone = substr($iso, 17);958 }959 960 function getIso()961 {962 return $this->year.$this->month.$this->day.'T'.$this->hour.':'.$this->minute.':'.$this->second.$this->timezone;963 }964 965 function getXml()966 {967 return '<dateTime.iso8601>'.$this->getIso().'</dateTime.iso8601>';968 }969 970 function getTimestamp()971 {972 return mktime($this->hour, $this->minute, $this->second, $this->month, $this->day, $this->year);973 }974 }975 976 /**977 * IXR_Base64978 *979 * @package IXR980 * @since 1.5.0981 */982 class IXR_Base64983 {984 var $data;985 986 /**987 * PHP5 constructor.988 */989 function __construct( $data )990 {991 $this->data = $data;992 }993 994 /**995 * PHP4 constructor.996 */997 public function IXR_Base64( $data ) {998 self::__construct( $data );999 }1000 1001 function getXml()1002 {1003 return '<base64>'.base64_encode($this->data).'</base64>';1004 }1005 }1006 2 1007 3 /** … … 1177 173 } 1178 174 } 1179 1180 /**1181 * IXR_ClientMulticall1182 *1183 * @package IXR1184 * @since 1.5.01185 */1186 class IXR_ClientMulticall extends IXR_Client1187 {1188 var $calls = array();1189 1190 /**1191 * PHP5 constructor.1192 */1193 function __construct( $server, $path = false, $port = 80 )1194 {1195 parent::IXR_Client($server, $path, $port);1196 $this->useragent = 'The Incutio XML-RPC PHP Library (multicall client)';1197 }1198 1199 /**1200 * PHP4 constructor.1201 */1202 public function IXR_ClientMulticall( $server, $path = false, $port = 80 ) {1203 self::__construct( $server, $path, $port );1204 }1205 1206 function addCall()1207 {1208 $args = func_get_args();1209 $methodName = array_shift($args);1210 $struct = array(1211 'methodName' => $methodName,1212 'params' => $args1213 );1214 $this->calls[] = $struct;1215 }1216 1217 function query()1218 {1219 // Prepare multicall, then call the parent::query() method1220 return parent::query('system.multicall', $this->calls);1221 }1222 } -
trunk/src/wp-includes/IXR/class-IXR-message.php
r38384 r38389 1 1 <?php 2 /**3 * IXR - The Incutio XML-RPC Library4 *5 * Copyright (c) 2010, Incutio Ltd.6 * All rights reserved.7 *8 * Redistribution and use in source and binary forms, with or without9 * 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 copyright14 * notice, this list of conditions and the following disclaimer in the15 * documentation and/or other materials provided with the distribution.16 * - Neither the name of Incutio Ltd. nor the names of its contributors17 * may be used to endorse or promote products derived from this software18 * without specific prior written permission.19 *20 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS21 * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,22 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR23 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR24 * 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, OR27 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY28 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT29 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE30 * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.31 *32 * @package IXR33 * @since 1.5.034 *35 * @copyright Incutio Ltd 2010 (http://www.incutio.com)36 * @version 1.7.4 7th September 201037 * @author Simon Willison38 * @link http://scripts.incutio.com/xmlrpc/ Site/manual39 * @license http://www.opensource.org/licenses/bsd-license.php BSD40 */41 42 /**43 * IXR_Value44 *45 * @package IXR46 * @since 1.5.047 */48 class IXR_Value {49 var $data;50 var $type;51 52 /**53 * PHP5 constructor.54 */55 function __construct( $data, $type = false )56 {57 $this->data = $data;58 if (!$type) {59 $type = $this->calculateType();60 }61 $this->type = $type;62 if ($type == 'struct') {63 // Turn all the values in the array in to new IXR_Value objects64 foreach ($this->data as $key => $value) {65 $this->data[$key] = new IXR_Value($value);66 }67 }68 if ($type == 'array') {69 for ($i = 0, $j = count($this->data); $i < $j; $i++) {70 $this->data[$i] = new IXR_Value($this->data[$i]);71 }72 }73 }74 75 /**76 * PHP4 constructor.77 */78 public function IXR_Value( $data, $type = false ) {79 self::__construct( $data, $type );80 }81 82 function calculateType()83 {84 if ($this->data === true || $this->data === false) {85 return 'boolean';86 }87 if (is_integer($this->data)) {88 return 'int';89 }90 if (is_double($this->data)) {91 return 'double';92 }93 94 // Deal with IXR object types base64 and date95 if (is_object($this->data) && is_a($this->data, 'IXR_Date')) {96 return 'date';97 }98 if (is_object($this->data) && is_a($this->data, 'IXR_Base64')) {99 return 'base64';100 }101 102 // If it is a normal PHP object convert it in to a struct103 if (is_object($this->data)) {104 $this->data = get_object_vars($this->data);105 return 'struct';106 }107 if (!is_array($this->data)) {108 return 'string';109 }110 111 // We have an array - is it an array or a struct?112 if ($this->isStruct($this->data)) {113 return 'struct';114 } else {115 return 'array';116 }117 }118 119 function getXml()120 {121 // Return XML for this value122 switch ($this->type) {123 case 'boolean':124 return '<boolean>'.(($this->data) ? '1' : '0').'</boolean>';125 break;126 case 'int':127 return '<int>'.$this->data.'</int>';128 break;129 case 'double':130 return '<double>'.$this->data.'</double>';131 break;132 case 'string':133 return '<string>'.htmlspecialchars($this->data).'</string>';134 break;135 case 'array':136 $return = '<array><data>'."\n";137 foreach ($this->data as $item) {138 $return .= ' <value>'.$item->getXml()."</value>\n";139 }140 $return .= '</data></array>';141 return $return;142 break;143 case 'struct':144 $return = '<struct>'."\n";145 foreach ($this->data as $name => $value) {146 $name = htmlspecialchars($name);147 $return .= " <member><name>$name</name><value>";148 $return .= $value->getXml()."</value></member>\n";149 }150 $return .= '</struct>';151 return $return;152 break;153 case 'date':154 case 'base64':155 return $this->data->getXml();156 break;157 }158 return false;159 }160 161 /**162 * Checks whether or not the supplied array is a struct or not163 *164 * @param array $array165 * @return bool166 */167 function isStruct($array)168 {169 $expected = 0;170 foreach ($array as $key => $value) {171 if ((string)$key !== (string)$expected) {172 return true;173 }174 $expected++;175 }176 return false;177 }178 }179 2 180 3 /** … … 405 228 } 406 229 } 407 408 /**409 * IXR_Server410 *411 * @package IXR412 * @since 1.5.0413 */414 class IXR_Server415 {416 var $data;417 var $callbacks = array();418 var $message;419 var $capabilities;420 421 /**422 * PHP5 constructor.423 */424 function __construct( $callbacks = false, $data = false, $wait = false )425 {426 $this->setCapabilities();427 if ($callbacks) {428 $this->callbacks = $callbacks;429 }430 $this->setCallbacks();431 if (!$wait) {432 $this->serve($data);433 }434 }435 436 /**437 * PHP4 constructor.438 */439 public function IXR_Server( $callbacks = false, $data = false, $wait = false ) {440 self::__construct( $callbacks, $data, $wait );441 }442 443 function serve($data = false)444 {445 if (!$data) {446 if (isset($_SERVER['REQUEST_METHOD']) && $_SERVER['REQUEST_METHOD'] !== 'POST') {447 if ( function_exists( 'status_header' ) ) {448 status_header( 405 ); // WP #20986449 header( 'Allow: POST' );450 }451 header('Content-Type: text/plain'); // merged from WP #9093452 die('XML-RPC server accepts POST requests only.');453 }454 455 global $HTTP_RAW_POST_DATA;456 if (empty($HTTP_RAW_POST_DATA)) {457 // workaround for a bug in PHP 5.2.2 - http://bugs.php.net/bug.php?id=41293458 $data = file_get_contents('php://input');459 } else {460 $data =& $HTTP_RAW_POST_DATA;461 }462 }463 $this->message = new IXR_Message($data);464 if (!$this->message->parse()) {465 $this->error(-32700, 'parse error. not well formed');466 }467 if ($this->message->messageType != 'methodCall') {468 $this->error(-32600, 'server error. invalid xml-rpc. not conforming to spec. Request must be a methodCall');469 }470 $result = $this->call($this->message->methodName, $this->message->params);471 472 // Is the result an error?473 if (is_a($result, 'IXR_Error')) {474 $this->error($result);475 }476 477 // Encode the result478 $r = new IXR_Value($result);479 $resultxml = $r->getXml();480 481 // Create the XML482 $xml = <<<EOD483 <methodResponse>484 <params>485 <param>486 <value>487 $resultxml488 </value>489 </param>490 </params>491 </methodResponse>492 493 EOD;494 // Send it495 $this->output($xml);496 }497 498 function call($methodname, $args)499 {500 if (!$this->hasMethod($methodname)) {501 return new IXR_Error(-32601, 'server error. requested method '.$methodname.' does not exist.');502 }503 $method = $this->callbacks[$methodname];504 505 // Perform the callback and send the response506 if (count($args) == 1) {507 // If only one parameter just send that instead of the whole array508 $args = $args[0];509 }510 511 // Are we dealing with a function or a method?512 if (is_string($method) && substr($method, 0, 5) == 'this:') {513 // It's a class method - check it exists514 $method = substr($method, 5);515 if (!method_exists($this, $method)) {516 return new IXR_Error(-32601, 'server error. requested class method "'.$method.'" does not exist.');517 }518 519 //Call the method520 $result = $this->$method($args);521 } else {522 // It's a function - does it exist?523 if (is_array($method)) {524 if (!is_callable(array($method[0], $method[1]))) {525 return new IXR_Error(-32601, 'server error. requested object method "'.$method[1].'" does not exist.');526 }527 } else if (!function_exists($method)) {528 return new IXR_Error(-32601, 'server error. requested function "'.$method.'" does not exist.');529 }530 531 // Call the function532 $result = call_user_func($method, $args);533 }534 return $result;535 }536 537 function error($error, $message = false)538 {539 // Accepts either an error object or an error code and message540 if ($message && !is_object($error)) {541 $error = new IXR_Error($error, $message);542 }543 $this->output($error->getXml());544 }545 546 function output($xml)547 {548 $charset = function_exists('get_option') ? get_option('blog_charset') : '';549 if ($charset)550 $xml = '<?xml version="1.0" encoding="'.$charset.'"?>'."\n".$xml;551 else552 $xml = '<?xml version="1.0"?>'."\n".$xml;553 $length = strlen($xml);554 header('Connection: close');555 if ($charset)556 header('Content-Type: text/xml; charset='.$charset);557 else558 header('Content-Type: text/xml');559 header('Date: '.date('r'));560 echo $xml;561 exit;562 }563 564 function hasMethod($method)565 {566 return in_array($method, array_keys($this->callbacks));567 }568 569 function setCapabilities()570 {571 // Initialises capabilities array572 $this->capabilities = array(573 'xmlrpc' => array(574 'specUrl' => 'http://www.xmlrpc.com/spec',575 'specVersion' => 1576 ),577 'faults_interop' => array(578 'specUrl' => 'http://xmlrpc-epi.sourceforge.net/specs/rfc.fault_codes.php',579 'specVersion' => 20010516580 ),581 'system.multicall' => array(582 'specUrl' => 'http://www.xmlrpc.com/discuss/msgReader$1208',583 'specVersion' => 1584 ),585 );586 }587 588 function getCapabilities($args)589 {590 return $this->capabilities;591 }592 593 function setCallbacks()594 {595 $this->callbacks['system.getCapabilities'] = 'this:getCapabilities';596 $this->callbacks['system.listMethods'] = 'this:listMethods';597 $this->callbacks['system.multicall'] = 'this:multiCall';598 }599 600 function listMethods($args)601 {602 // Returns a list of methods - uses array_reverse to ensure user defined603 // methods are listed before server defined methods604 return array_reverse(array_keys($this->callbacks));605 }606 607 function multiCall($methodcalls)608 {609 // See http://www.xmlrpc.com/discuss/msgReader$1208610 $return = array();611 foreach ($methodcalls as $call) {612 $method = $call['methodName'];613 $params = $call['params'];614 if ($method == 'system.multicall') {615 $result = new IXR_Error(-32600, 'Recursive calls to system.multicall are forbidden');616 } else {617 $result = $this->call($method, $params);618 }619 if (is_a($result, 'IXR_Error')) {620 $return[] = array(621 'faultCode' => $result->code,622 'faultString' => $result->message623 );624 } else {625 $return[] = array($result);626 }627 }628 return $return;629 }630 }631 632 /**633 * IXR_Request634 *635 * @package IXR636 * @since 1.5.0637 */638 class IXR_Request639 {640 var $method;641 var $args;642 var $xml;643 644 /**645 * PHP5 constructor.646 */647 function __construct($method, $args)648 {649 $this->method = $method;650 $this->args = $args;651 $this->xml = <<<EOD652 <?xml version="1.0"?>653 <methodCall>654 <methodName>{$this->method}</methodName>655 <params>656 657 EOD;658 foreach ($this->args as $arg) {659 $this->xml .= '<param><value>';660 $v = new IXR_Value($arg);661 $this->xml .= $v->getXml();662 $this->xml .= "</value></param>\n";663 }664 $this->xml .= '</params></methodCall>';665 }666 667 /**668 * PHP4 constructor.669 */670 public function IXR_Request( $method, $args ) {671 self::__construct( $method, $args );672 }673 674 function getLength()675 {676 return strlen($this->xml);677 }678 679 function getXml()680 {681 return $this->xml;682 }683 }684 685 /**686 * IXR_Client687 *688 * @package IXR689 * @since 1.5.0690 *691 */692 class IXR_Client693 {694 var $server;695 var $port;696 var $path;697 var $useragent;698 var $response;699 var $message = false;700 var $debug = false;701 var $timeout;702 var $headers = array();703 704 // Storage place for an error message705 var $error = false;706 707 /**708 * PHP5 constructor.709 */710 function __construct( $server, $path = false, $port = 80, $timeout = 15 )711 {712 if (!$path) {713 // Assume we have been given a URL instead714 $bits = parse_url($server);715 $this->server = $bits['host'];716 $this->port = isset($bits['port']) ? $bits['port'] : 80;717 $this->path = isset($bits['path']) ? $bits['path'] : '/';718 719 // Make absolutely sure we have a path720 if (!$this->path) {721 $this->path = '/';722 }723 724 if ( ! empty( $bits['query'] ) ) {725 $this->path .= '?' . $bits['query'];726 }727 } else {728 $this->server = $server;729 $this->path = $path;730 $this->port = $port;731 }732 $this->useragent = 'The Incutio XML-RPC PHP Library';733 $this->timeout = $timeout;734 }735 736 /**737 * PHP4 constructor.738 */739 public function IXR_Client( $server, $path = false, $port = 80, $timeout = 15 ) {740 self::__construct( $server, $path, $port, $timeout );741 }742 743 function query()744 {745 $args = func_get_args();746 $method = array_shift($args);747 $request = new IXR_Request($method, $args);748 $length = $request->getLength();749 $xml = $request->getXml();750 $r = "\r\n";751 $request = "POST {$this->path} HTTP/1.0$r";752 753 // Merged from WP #8145 - allow custom headers754 $this->headers['Host'] = $this->server;755 $this->headers['Content-Type'] = 'text/xml';756 $this->headers['User-Agent'] = $this->useragent;757 $this->headers['Content-Length']= $length;758 759 foreach( $this->headers as $header => $value ) {760 $request .= "{$header}: {$value}{$r}";761 }762 $request .= $r;763 764 $request .= $xml;765 766 // Now send the request767 if ($this->debug) {768 echo '<pre class="ixr_request">'.htmlspecialchars($request)."\n</pre>\n\n";769 }770 771 if ($this->timeout) {772 $fp = @fsockopen($this->server, $this->port, $errno, $errstr, $this->timeout);773 } else {774 $fp = @fsockopen($this->server, $this->port, $errno, $errstr);775 }776 if (!$fp) {777 $this->error = new IXR_Error(-32300, 'transport error - could not open socket');778 return false;779 }780 fputs($fp, $request);781 $contents = '';782 $debugContents = '';783 $gotFirstLine = false;784 $gettingHeaders = true;785 while (!feof($fp)) {786 $line = fgets($fp, 4096);787 if (!$gotFirstLine) {788 // Check line for '200'789 if (strstr($line, '200') === false) {790 $this->error = new IXR_Error(-32300, 'transport error - HTTP status code was not 200');791 return false;792 }793 $gotFirstLine = true;794 }795 if (trim($line) == '') {796 $gettingHeaders = false;797 }798 if (!$gettingHeaders) {799 // merged from WP #12559 - remove trim800 $contents .= $line;801 }802 if ($this->debug) {803 $debugContents .= $line;804 }805 }806 if ($this->debug) {807 echo '<pre class="ixr_response">'.htmlspecialchars($debugContents)."\n</pre>\n\n";808 }809 810 // Now parse what we've got back811 $this->message = new IXR_Message($contents);812 if (!$this->message->parse()) {813 // XML error814 $this->error = new IXR_Error(-32700, 'parse error. not well formed');815 return false;816 }817 818 // Is the message a fault?819 if ($this->message->messageType == 'fault') {820 $this->error = new IXR_Error($this->message->faultCode, $this->message->faultString);821 return false;822 }823 824 // Message must be OK825 return true;826 }827 828 function getResponse()829 {830 // methodResponses can only have one param - return that831 return $this->message->params[0];832 }833 834 function isError()835 {836 return (is_object($this->error));837 }838 839 function getErrorCode()840 {841 return $this->error->code;842 }843 844 function getErrorMessage()845 {846 return $this->error->message;847 }848 }849 850 851 /**852 * IXR_Error853 *854 * @package IXR855 * @since 1.5.0856 */857 class IXR_Error858 {859 var $code;860 var $message;861 862 /**863 * PHP5 constructor.864 */865 function __construct( $code, $message )866 {867 $this->code = $code;868 $this->message = htmlspecialchars($message);869 }870 871 /**872 * PHP4 constructor.873 */874 public function IXR_Error( $code, $message ) {875 self::__construct( $code, $message );876 }877 878 function getXml()879 {880 $xml = <<<EOD881 <methodResponse>882 <fault>883 <value>884 <struct>885 <member>886 <name>faultCode</name>887 <value><int>{$this->code}</int></value>888 </member>889 <member>890 <name>faultString</name>891 <value><string>{$this->message}</string></value>892 </member>893 </struct>894 </value>895 </fault>896 </methodResponse>897 898 EOD;899 return $xml;900 }901 }902 903 /**904 * IXR_Date905 *906 * @package IXR907 * @since 1.5.0908 */909 class IXR_Date {910 var $year;911 var $month;912 var $day;913 var $hour;914 var $minute;915 var $second;916 var $timezone;917 918 /**919 * PHP5 constructor.920 */921 function __construct( $time )922 {923 // $time can be a PHP timestamp or an ISO one924 if (is_numeric($time)) {925 $this->parseTimestamp($time);926 } else {927 $this->parseIso($time);928 }929 }930 931 /**932 * PHP4 constructor.933 */934 public function IXR_Date( $time ) {935 self::__construct( $time );936 }937 938 function parseTimestamp($timestamp)939 {940 $this->year = date('Y', $timestamp);941 $this->month = date('m', $timestamp);942 $this->day = date('d', $timestamp);943 $this->hour = date('H', $timestamp);944 $this->minute = date('i', $timestamp);945 $this->second = date('s', $timestamp);946 $this->timezone = '';947 }948 949 function parseIso($iso)950 {951 $this->year = substr($iso, 0, 4);952 $this->month = substr($iso, 4, 2);953 $this->day = substr($iso, 6, 2);954 $this->hour = substr($iso, 9, 2);955 $this->minute = substr($iso, 12, 2);956 $this->second = substr($iso, 15, 2);957 $this->timezone = substr($iso, 17);958 }959 960 function getIso()961 {962 return $this->year.$this->month.$this->day.'T'.$this->hour.':'.$this->minute.':'.$this->second.$this->timezone;963 }964 965 function getXml()966 {967 return '<dateTime.iso8601>'.$this->getIso().'</dateTime.iso8601>';968 }969 970 function getTimestamp()971 {972 return mktime($this->hour, $this->minute, $this->second, $this->month, $this->day, $this->year);973 }974 }975 976 /**977 * IXR_Base64978 *979 * @package IXR980 * @since 1.5.0981 */982 class IXR_Base64983 {984 var $data;985 986 /**987 * PHP5 constructor.988 */989 function __construct( $data )990 {991 $this->data = $data;992 }993 994 /**995 * PHP4 constructor.996 */997 public function IXR_Base64( $data ) {998 self::__construct( $data );999 }1000 1001 function getXml()1002 {1003 return '<base64>'.base64_encode($this->data).'</base64>';1004 }1005 }1006 1007 /**1008 * IXR_IntrospectionServer1009 *1010 * @package IXR1011 * @since 1.5.01012 */1013 class IXR_IntrospectionServer extends IXR_Server1014 {1015 var $signatures;1016 var $help;1017 1018 /**1019 * PHP5 constructor.1020 */1021 function __construct()1022 {1023 $this->setCallbacks();1024 $this->setCapabilities();1025 $this->capabilities['introspection'] = array(1026 'specUrl' => 'http://xmlrpc.usefulinc.com/doc/reserved.html',1027 'specVersion' => 11028 );1029 $this->addCallback(1030 'system.methodSignature',1031 'this:methodSignature',1032 array('array', 'string'),1033 'Returns an array describing the return type and required parameters of a method'1034 );1035 $this->addCallback(1036 'system.getCapabilities',1037 'this:getCapabilities',1038 array('struct'),1039 'Returns a struct describing the XML-RPC specifications supported by this server'1040 );1041 $this->addCallback(1042 'system.listMethods',1043 'this:listMethods',1044 array('array'),1045 'Returns an array of available methods on this server'1046 );1047 $this->addCallback(1048 'system.methodHelp',1049 'this:methodHelp',1050 array('string', 'string'),1051 'Returns a documentation string for the specified method'1052 );1053 }1054 1055 /**1056 * PHP4 constructor.1057 */1058 public function IXR_IntrospectionServer() {1059 self::__construct();1060 }1061 1062 function addCallback($method, $callback, $args, $help)1063 {1064 $this->callbacks[$method] = $callback;1065 $this->signatures[$method] = $args;1066 $this->help[$method] = $help;1067 }1068 1069 function call($methodname, $args)1070 {1071 // Make sure it's in an array1072 if ($args && !is_array($args)) {1073 $args = array($args);1074 }1075 1076 // Over-rides default call method, adds signature check1077 if (!$this->hasMethod($methodname)) {1078 return new IXR_Error(-32601, 'server error. requested method "'.$this->message->methodName.'" not specified.');1079 }1080 $method = $this->callbacks[$methodname];1081 $signature = $this->signatures[$methodname];1082 $returnType = array_shift($signature);1083 1084 // Check the number of arguments1085 if (count($args) != count($signature)) {1086 return new IXR_Error(-32602, 'server error. wrong number of method parameters');1087 }1088 1089 // Check the argument types1090 $ok = true;1091 $argsbackup = $args;1092 for ($i = 0, $j = count($args); $i < $j; $i++) {1093 $arg = array_shift($args);1094 $type = array_shift($signature);1095 switch ($type) {1096 case 'int':1097 case 'i4':1098 if (is_array($arg) || !is_int($arg)) {1099 $ok = false;1100 }1101 break;1102 case 'base64':1103 case 'string':1104 if (!is_string($arg)) {1105 $ok = false;1106 }1107 break;1108 case 'boolean':1109 if ($arg !== false && $arg !== true) {1110 $ok = false;1111 }1112 break;1113 case 'float':1114 case 'double':1115 if (!is_float($arg)) {1116 $ok = false;1117 }1118 break;1119 case 'date':1120 case 'dateTime.iso8601':1121 if (!is_a($arg, 'IXR_Date')) {1122 $ok = false;1123 }1124 break;1125 }1126 if (!$ok) {1127 return new IXR_Error(-32602, 'server error. invalid method parameters');1128 }1129 }1130 // It passed the test - run the "real" method call1131 return parent::call($methodname, $argsbackup);1132 }1133 1134 function methodSignature($method)1135 {1136 if (!$this->hasMethod($method)) {1137 return new IXR_Error(-32601, 'server error. requested method "'.$method.'" not specified.');1138 }1139 // We should be returning an array of types1140 $types = $this->signatures[$method];1141 $return = array();1142 foreach ($types as $type) {1143 switch ($type) {1144 case 'string':1145 $return[] = 'string';1146 break;1147 case 'int':1148 case 'i4':1149 $return[] = 42;1150 break;1151 case 'double':1152 $return[] = 3.1415;1153 break;1154 case 'dateTime.iso8601':1155 $return[] = new IXR_Date(time());1156 break;1157 case 'boolean':1158 $return[] = true;1159 break;1160 case 'base64':1161 $return[] = new IXR_Base64('base64');1162 break;1163 case 'array':1164 $return[] = array('array');1165 break;1166 case 'struct':1167 $return[] = array('struct' => 'struct');1168 break;1169 }1170 }1171 return $return;1172 }1173 1174 function methodHelp($method)1175 {1176 return $this->help[$method];1177 }1178 }1179 1180 /**1181 * IXR_ClientMulticall1182 *1183 * @package IXR1184 * @since 1.5.01185 */1186 class IXR_ClientMulticall extends IXR_Client1187 {1188 var $calls = array();1189 1190 /**1191 * PHP5 constructor.1192 */1193 function __construct( $server, $path = false, $port = 80 )1194 {1195 parent::IXR_Client($server, $path, $port);1196 $this->useragent = 'The Incutio XML-RPC PHP Library (multicall client)';1197 }1198 1199 /**1200 * PHP4 constructor.1201 */1202 public function IXR_ClientMulticall( $server, $path = false, $port = 80 ) {1203 self::__construct( $server, $path, $port );1204 }1205 1206 function addCall()1207 {1208 $args = func_get_args();1209 $methodName = array_shift($args);1210 $struct = array(1211 'methodName' => $methodName,1212 'params' => $args1213 );1214 $this->calls[] = $struct;1215 }1216 1217 function query()1218 {1219 // Prepare multicall, then call the parent::query() method1220 return parent::query('system.multicall', $this->calls);1221 }1222 } -
trunk/src/wp-includes/IXR/class-IXR-request.php
r38384 r38389 1 1 <?php 2 /**3 * IXR - The Incutio XML-RPC Library4 *5 * Copyright (c) 2010, Incutio Ltd.6 * All rights reserved.7 *8 * Redistribution and use in source and binary forms, with or without9 * 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 copyright14 * notice, this list of conditions and the following disclaimer in the15 * documentation and/or other materials provided with the distribution.16 * - Neither the name of Incutio Ltd. nor the names of its contributors17 * may be used to endorse or promote products derived from this software18 * without specific prior written permission.19 *20 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS21 * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,22 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR23 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR24 * 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, OR27 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY28 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT29 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE30 * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.31 *32 * @package IXR33 * @since 1.5.034 *35 * @copyright Incutio Ltd 2010 (http://www.incutio.com)36 * @version 1.7.4 7th September 201037 * @author Simon Willison38 * @link http://scripts.incutio.com/xmlrpc/ Site/manual39 * @license http://www.opensource.org/licenses/bsd-license.php BSD40 */41 42 /**43 * IXR_Value44 *45 * @package IXR46 * @since 1.5.047 */48 class IXR_Value {49 var $data;50 var $type;51 52 /**53 * PHP5 constructor.54 */55 function __construct( $data, $type = false )56 {57 $this->data = $data;58 if (!$type) {59 $type = $this->calculateType();60 }61 $this->type = $type;62 if ($type == 'struct') {63 // Turn all the values in the array in to new IXR_Value objects64 foreach ($this->data as $key => $value) {65 $this->data[$key] = new IXR_Value($value);66 }67 }68 if ($type == 'array') {69 for ($i = 0, $j = count($this->data); $i < $j; $i++) {70 $this->data[$i] = new IXR_Value($this->data[$i]);71 }72 }73 }74 75 /**76 * PHP4 constructor.77 */78 public function IXR_Value( $data, $type = false ) {79 self::__construct( $data, $type );80 }81 82 function calculateType()83 {84 if ($this->data === true || $this->data === false) {85 return 'boolean';86 }87 if (is_integer($this->data)) {88 return 'int';89 }90 if (is_double($this->data)) {91 return 'double';92 }93 94 // Deal with IXR object types base64 and date95 if (is_object($this->data) && is_a($this->data, 'IXR_Date')) {96 return 'date';97 }98 if (is_object($this->data) && is_a($this->data, 'IXR_Base64')) {99 return 'base64';100 }101 102 // If it is a normal PHP object convert it in to a struct103 if (is_object($this->data)) {104 $this->data = get_object_vars($this->data);105 return 'struct';106 }107 if (!is_array($this->data)) {108 return 'string';109 }110 111 // We have an array - is it an array or a struct?112 if ($this->isStruct($this->data)) {113 return 'struct';114 } else {115 return 'array';116 }117 }118 119 function getXml()120 {121 // Return XML for this value122 switch ($this->type) {123 case 'boolean':124 return '<boolean>'.(($this->data) ? '1' : '0').'</boolean>';125 break;126 case 'int':127 return '<int>'.$this->data.'</int>';128 break;129 case 'double':130 return '<double>'.$this->data.'</double>';131 break;132 case 'string':133 return '<string>'.htmlspecialchars($this->data).'</string>';134 break;135 case 'array':136 $return = '<array><data>'."\n";137 foreach ($this->data as $item) {138 $return .= ' <value>'.$item->getXml()."</value>\n";139 }140 $return .= '</data></array>';141 return $return;142 break;143 case 'struct':144 $return = '<struct>'."\n";145 foreach ($this->data as $name => $value) {146 $name = htmlspecialchars($name);147 $return .= " <member><name>$name</name><value>";148 $return .= $value->getXml()."</value></member>\n";149 }150 $return .= '</struct>';151 return $return;152 break;153 case 'date':154 case 'base64':155 return $this->data->getXml();156 break;157 }158 return false;159 }160 161 /**162 * Checks whether or not the supplied array is a struct or not163 *164 * @param array $array165 * @return bool166 */167 function isStruct($array)168 {169 $expected = 0;170 foreach ($array as $key => $value) {171 if ((string)$key !== (string)$expected) {172 return true;173 }174 $expected++;175 }176 return false;177 }178 }179 180 /**181 * IXR_MESSAGE182 *183 * @package IXR184 * @since 1.5.0185 *186 */187 class IXR_Message188 {189 var $message;190 var $messageType; // methodCall / methodResponse / fault191 var $faultCode;192 var $faultString;193 var $methodName;194 var $params;195 196 // Current variable stacks197 var $_arraystructs = array(); // The stack used to keep track of the current array/struct198 var $_arraystructstypes = array(); // Stack keeping track of if things are structs or array199 var $_currentStructName = array(); // A stack as well200 var $_param;201 var $_value;202 var $_currentTag;203 var $_currentTagContents;204 // The XML parser205 var $_parser;206 207 /**208 * PHP5 constructor.209 */210 function __construct( $message )211 {212 $this->message =& $message;213 }214 215 /**216 * PHP4 constructor.217 */218 public function IXR_Message( $message ) {219 self::__construct( $message );220 }221 222 function parse()223 {224 // first remove the XML declaration225 // merged from WP #10698 - this method avoids the RAM usage of preg_replace on very large messages226 $header = preg_replace( '/<\?xml.*?\?'.'>/s', '', substr( $this->message, 0, 100 ), 1 );227 $this->message = trim( substr_replace( $this->message, $header, 0, 100 ) );228 if ( '' == $this->message ) {229 return false;230 }231 232 // Then remove the DOCTYPE233 $header = preg_replace( '/^<!DOCTYPE[^>]*+>/i', '', substr( $this->message, 0, 200 ), 1 );234 $this->message = trim( substr_replace( $this->message, $header, 0, 200 ) );235 if ( '' == $this->message ) {236 return false;237 }238 239 // Check that the root tag is valid240 $root_tag = substr( $this->message, 0, strcspn( substr( $this->message, 0, 20 ), "> \t\r\n" ) );241 if ( '<!DOCTYPE' === strtoupper( $root_tag ) ) {242 return false;243 }244 if ( ! in_array( $root_tag, array( '<methodCall', '<methodResponse', '<fault' ) ) ) {245 return false;246 }247 248 // Bail if there are too many elements to parse249 $element_limit = 30000;250 if ( function_exists( 'apply_filters' ) ) {251 /**252 * Filters the number of elements to parse in an XML-RPC response.253 *254 * @since 4.0.0255 *256 * @param int $element_limit Default elements limit.257 */258 $element_limit = apply_filters( 'xmlrpc_element_limit', $element_limit );259 }260 if ( $element_limit && 2 * $element_limit < substr_count( $this->message, '<' ) ) {261 return false;262 }263 264 $this->_parser = xml_parser_create();265 // Set XML parser to take the case of tags in to account266 xml_parser_set_option($this->_parser, XML_OPTION_CASE_FOLDING, false);267 // Set XML parser callback functions268 xml_set_object($this->_parser, $this);269 xml_set_element_handler($this->_parser, 'tag_open', 'tag_close');270 xml_set_character_data_handler($this->_parser, 'cdata');271 272 // 256Kb, parse in chunks to avoid the RAM usage on very large messages273 $chunk_size = 262144;274 275 /**276 * Filters the chunk size that can be used to parse an XML-RPC reponse message.277 *278 * @since 4.4.0279 *280 * @param int $chunk_size Chunk size to parse in bytes.281 */282 $chunk_size = apply_filters( 'xmlrpc_chunk_parsing_size', $chunk_size );283 284 $final = false;285 do {286 if (strlen($this->message) <= $chunk_size) {287 $final = true;288 }289 $part = substr($this->message, 0, $chunk_size);290 $this->message = substr($this->message, $chunk_size);291 if (!xml_parse($this->_parser, $part, $final)) {292 return false;293 }294 if ($final) {295 break;296 }297 } while (true);298 xml_parser_free($this->_parser);299 300 // Grab the error messages, if any301 if ($this->messageType == 'fault') {302 $this->faultCode = $this->params[0]['faultCode'];303 $this->faultString = $this->params[0]['faultString'];304 }305 return true;306 }307 308 function tag_open($parser, $tag, $attr)309 {310 $this->_currentTagContents = '';311 $this->currentTag = $tag;312 switch($tag) {313 case 'methodCall':314 case 'methodResponse':315 case 'fault':316 $this->messageType = $tag;317 break;318 /* Deal with stacks of arrays and structs */319 case 'data': // data is to all intents and puposes more interesting than array320 $this->_arraystructstypes[] = 'array';321 $this->_arraystructs[] = array();322 break;323 case 'struct':324 $this->_arraystructstypes[] = 'struct';325 $this->_arraystructs[] = array();326 break;327 }328 }329 330 function cdata($parser, $cdata)331 {332 $this->_currentTagContents .= $cdata;333 }334 335 function tag_close($parser, $tag)336 {337 $valueFlag = false;338 switch($tag) {339 case 'int':340 case 'i4':341 $value = (int)trim($this->_currentTagContents);342 $valueFlag = true;343 break;344 case 'double':345 $value = (double)trim($this->_currentTagContents);346 $valueFlag = true;347 break;348 case 'string':349 $value = (string)trim($this->_currentTagContents);350 $valueFlag = true;351 break;352 case 'dateTime.iso8601':353 $value = new IXR_Date(trim($this->_currentTagContents));354 $valueFlag = true;355 break;356 case 'value':357 // "If no type is indicated, the type is string."358 if (trim($this->_currentTagContents) != '') {359 $value = (string)$this->_currentTagContents;360 $valueFlag = true;361 }362 break;363 case 'boolean':364 $value = (boolean)trim($this->_currentTagContents);365 $valueFlag = true;366 break;367 case 'base64':368 $value = base64_decode($this->_currentTagContents);369 $valueFlag = true;370 break;371 /* Deal with stacks of arrays and structs */372 case 'data':373 case 'struct':374 $value = array_pop($this->_arraystructs);375 array_pop($this->_arraystructstypes);376 $valueFlag = true;377 break;378 case 'member':379 array_pop($this->_currentStructName);380 break;381 case 'name':382 $this->_currentStructName[] = trim($this->_currentTagContents);383 break;384 case 'methodName':385 $this->methodName = trim($this->_currentTagContents);386 break;387 }388 389 if ($valueFlag) {390 if (count($this->_arraystructs) > 0) {391 // Add value to struct or array392 if ($this->_arraystructstypes[count($this->_arraystructstypes)-1] == 'struct') {393 // Add to struct394 $this->_arraystructs[count($this->_arraystructs)-1][$this->_currentStructName[count($this->_currentStructName)-1]] = $value;395 } else {396 // Add to array397 $this->_arraystructs[count($this->_arraystructs)-1][] = $value;398 }399 } else {400 // Just add as a parameter401 $this->params[] = $value;402 }403 }404 $this->_currentTagContents = '';405 }406 }407 408 /**409 * IXR_Server410 *411 * @package IXR412 * @since 1.5.0413 */414 class IXR_Server415 {416 var $data;417 var $callbacks = array();418 var $message;419 var $capabilities;420 421 /**422 * PHP5 constructor.423 */424 function __construct( $callbacks = false, $data = false, $wait = false )425 {426 $this->setCapabilities();427 if ($callbacks) {428 $this->callbacks = $callbacks;429 }430 $this->setCallbacks();431 if (!$wait) {432 $this->serve($data);433 }434 }435 436 /**437 * PHP4 constructor.438 */439 public function IXR_Server( $callbacks = false, $data = false, $wait = false ) {440 self::__construct( $callbacks, $data, $wait );441 }442 443 function serve($data = false)444 {445 if (!$data) {446 if (isset($_SERVER['REQUEST_METHOD']) && $_SERVER['REQUEST_METHOD'] !== 'POST') {447 if ( function_exists( 'status_header' ) ) {448 status_header( 405 ); // WP #20986449 header( 'Allow: POST' );450 }451 header('Content-Type: text/plain'); // merged from WP #9093452 die('XML-RPC server accepts POST requests only.');453 }454 455 global $HTTP_RAW_POST_DATA;456 if (empty($HTTP_RAW_POST_DATA)) {457 // workaround for a bug in PHP 5.2.2 - http://bugs.php.net/bug.php?id=41293458 $data = file_get_contents('php://input');459 } else {460 $data =& $HTTP_RAW_POST_DATA;461 }462 }463 $this->message = new IXR_Message($data);464 if (!$this->message->parse()) {465 $this->error(-32700, 'parse error. not well formed');466 }467 if ($this->message->messageType != 'methodCall') {468 $this->error(-32600, 'server error. invalid xml-rpc. not conforming to spec. Request must be a methodCall');469 }470 $result = $this->call($this->message->methodName, $this->message->params);471 472 // Is the result an error?473 if (is_a($result, 'IXR_Error')) {474 $this->error($result);475 }476 477 // Encode the result478 $r = new IXR_Value($result);479 $resultxml = $r->getXml();480 481 // Create the XML482 $xml = <<<EOD483 <methodResponse>484 <params>485 <param>486 <value>487 $resultxml488 </value>489 </param>490 </params>491 </methodResponse>492 493 EOD;494 // Send it495 $this->output($xml);496 }497 498 function call($methodname, $args)499 {500 if (!$this->hasMethod($methodname)) {501 return new IXR_Error(-32601, 'server error. requested method '.$methodname.' does not exist.');502 }503 $method = $this->callbacks[$methodname];504 505 // Perform the callback and send the response506 if (count($args) == 1) {507 // If only one parameter just send that instead of the whole array508 $args = $args[0];509 }510 511 // Are we dealing with a function or a method?512 if (is_string($method) && substr($method, 0, 5) == 'this:') {513 // It's a class method - check it exists514 $method = substr($method, 5);515 if (!method_exists($this, $method)) {516 return new IXR_Error(-32601, 'server error. requested class method "'.$method.'" does not exist.');517 }518 519 //Call the method520 $result = $this->$method($args);521 } else {522 // It's a function - does it exist?523 if (is_array($method)) {524 if (!is_callable(array($method[0], $method[1]))) {525 return new IXR_Error(-32601, 'server error. requested object method "'.$method[1].'" does not exist.');526 }527 } else if (!function_exists($method)) {528 return new IXR_Error(-32601, 'server error. requested function "'.$method.'" does not exist.');529 }530 531 // Call the function532 $result = call_user_func($method, $args);533 }534 return $result;535 }536 537 function error($error, $message = false)538 {539 // Accepts either an error object or an error code and message540 if ($message && !is_object($error)) {541 $error = new IXR_Error($error, $message);542 }543 $this->output($error->getXml());544 }545 546 function output($xml)547 {548 $charset = function_exists('get_option') ? get_option('blog_charset') : '';549 if ($charset)550 $xml = '<?xml version="1.0" encoding="'.$charset.'"?>'."\n".$xml;551 else552 $xml = '<?xml version="1.0"?>'."\n".$xml;553 $length = strlen($xml);554 header('Connection: close');555 if ($charset)556 header('Content-Type: text/xml; charset='.$charset);557 else558 header('Content-Type: text/xml');559 header('Date: '.date('r'));560 echo $xml;561 exit;562 }563 564 function hasMethod($method)565 {566 return in_array($method, array_keys($this->callbacks));567 }568 569 function setCapabilities()570 {571 // Initialises capabilities array572 $this->capabilities = array(573 'xmlrpc' => array(574 'specUrl' => 'http://www.xmlrpc.com/spec',575 'specVersion' => 1576 ),577 'faults_interop' => array(578 'specUrl' => 'http://xmlrpc-epi.sourceforge.net/specs/rfc.fault_codes.php',579 'specVersion' => 20010516580 ),581 'system.multicall' => array(582 'specUrl' => 'http://www.xmlrpc.com/discuss/msgReader$1208',583 'specVersion' => 1584 ),585 );586 }587 588 function getCapabilities($args)589 {590 return $this->capabilities;591 }592 593 function setCallbacks()594 {595 $this->callbacks['system.getCapabilities'] = 'this:getCapabilities';596 $this->callbacks['system.listMethods'] = 'this:listMethods';597 $this->callbacks['system.multicall'] = 'this:multiCall';598 }599 600 function listMethods($args)601 {602 // Returns a list of methods - uses array_reverse to ensure user defined603 // methods are listed before server defined methods604 return array_reverse(array_keys($this->callbacks));605 }606 607 function multiCall($methodcalls)608 {609 // See http://www.xmlrpc.com/discuss/msgReader$1208610 $return = array();611 foreach ($methodcalls as $call) {612 $method = $call['methodName'];613 $params = $call['params'];614 if ($method == 'system.multicall') {615 $result = new IXR_Error(-32600, 'Recursive calls to system.multicall are forbidden');616 } else {617 $result = $this->call($method, $params);618 }619 if (is_a($result, 'IXR_Error')) {620 $return[] = array(621 'faultCode' => $result->code,622 'faultString' => $result->message623 );624 } else {625 $return[] = array($result);626 }627 }628 return $return;629 }630 }631 2 632 3 /** … … 682 53 } 683 54 } 684 685 /**686 * IXR_Client687 *688 * @package IXR689 * @since 1.5.0690 *691 */692 class IXR_Client693 {694 var $server;695 var $port;696 var $path;697 var $useragent;698 var $response;699 var $message = false;700 var $debug = false;701 var $timeout;702 var $headers = array();703 704 // Storage place for an error message705 var $error = false;706 707 /**708 * PHP5 constructor.709 */710 function __construct( $server, $path = false, $port = 80, $timeout = 15 )711 {712 if (!$path) {713 // Assume we have been given a URL instead714 $bits = parse_url($server);715 $this->server = $bits['host'];716 $this->port = isset($bits['port']) ? $bits['port'] : 80;717 $this->path = isset($bits['path']) ? $bits['path'] : '/';718 719 // Make absolutely sure we have a path720 if (!$this->path) {721 $this->path = '/';722 }723 724 if ( ! empty( $bits['query'] ) ) {725 $this->path .= '?' . $bits['query'];726 }727 } else {728 $this->server = $server;729 $this->path = $path;730 $this->port = $port;731 }732 $this->useragent = 'The Incutio XML-RPC PHP Library';733 $this->timeout = $timeout;734 }735 736 /**737 * PHP4 constructor.738 */739 public function IXR_Client( $server, $path = false, $port = 80, $timeout = 15 ) {740 self::__construct( $server, $path, $port, $timeout );741 }742 743 function query()744 {745 $args = func_get_args();746 $method = array_shift($args);747 $request = new IXR_Request($method, $args);748 $length = $request->getLength();749 $xml = $request->getXml();750 $r = "\r\n";751 $request = "POST {$this->path} HTTP/1.0$r";752 753 // Merged from WP #8145 - allow custom headers754 $this->headers['Host'] = $this->server;755 $this->headers['Content-Type'] = 'text/xml';756 $this->headers['User-Agent'] = $this->useragent;757 $this->headers['Content-Length']= $length;758 759 foreach( $this->headers as $header => $value ) {760 $request .= "{$header}: {$value}{$r}";761 }762 $request .= $r;763 764 $request .= $xml;765 766 // Now send the request767 if ($this->debug) {768 echo '<pre class="ixr_request">'.htmlspecialchars($request)."\n</pre>\n\n";769 }770 771 if ($this->timeout) {772 $fp = @fsockopen($this->server, $this->port, $errno, $errstr, $this->timeout);773 } else {774 $fp = @fsockopen($this->server, $this->port, $errno, $errstr);775 }776 if (!$fp) {777 $this->error = new IXR_Error(-32300, 'transport error - could not open socket');778 return false;779 }780 fputs($fp, $request);781 $contents = '';782 $debugContents = '';783 $gotFirstLine = false;784 $gettingHeaders = true;785 while (!feof($fp)) {786 $line = fgets($fp, 4096);787 if (!$gotFirstLine) {788 // Check line for '200'789 if (strstr($line, '200') === false) {790 $this->error = new IXR_Error(-32300, 'transport error - HTTP status code was not 200');791 return false;792 }793 $gotFirstLine = true;794 }795 if (trim($line) == '') {796 $gettingHeaders = false;797 }798 if (!$gettingHeaders) {799 // merged from WP #12559 - remove trim800 $contents .= $line;801 }802 if ($this->debug) {803 $debugContents .= $line;804 }805 }806 if ($this->debug) {807 echo '<pre class="ixr_response">'.htmlspecialchars($debugContents)."\n</pre>\n\n";808 }809 810 // Now parse what we've got back811 $this->message = new IXR_Message($contents);812 if (!$this->message->parse()) {813 // XML error814 $this->error = new IXR_Error(-32700, 'parse error. not well formed');815 return false;816 }817 818 // Is the message a fault?819 if ($this->message->messageType == 'fault') {820 $this->error = new IXR_Error($this->message->faultCode, $this->message->faultString);821 return false;822 }823 824 // Message must be OK825 return true;826 }827 828 function getResponse()829 {830 // methodResponses can only have one param - return that831 return $this->message->params[0];832 }833 834 function isError()835 {836 return (is_object($this->error));837 }838 839 function getErrorCode()840 {841 return $this->error->code;842 }843 844 function getErrorMessage()845 {846 return $this->error->message;847 }848 }849 850 851 /**852 * IXR_Error853 *854 * @package IXR855 * @since 1.5.0856 */857 class IXR_Error858 {859 var $code;860 var $message;861 862 /**863 * PHP5 constructor.864 */865 function __construct( $code, $message )866 {867 $this->code = $code;868 $this->message = htmlspecialchars($message);869 }870 871 /**872 * PHP4 constructor.873 */874 public function IXR_Error( $code, $message ) {875 self::__construct( $code, $message );876 }877 878 function getXml()879 {880 $xml = <<<EOD881 <methodResponse>882 <fault>883 <value>884 <struct>885 <member>886 <name>faultCode</name>887 <value><int>{$this->code}</int></value>888 </member>889 <member>890 <name>faultString</name>891 <value><string>{$this->message}</string></value>892 </member>893 </struct>894 </value>895 </fault>896 </methodResponse>897 898 EOD;899 return $xml;900 }901 }902 903 /**904 * IXR_Date905 *906 * @package IXR907 * @since 1.5.0908 */909 class IXR_Date {910 var $year;911 var $month;912 var $day;913 var $hour;914 var $minute;915 var $second;916 var $timezone;917 918 /**919 * PHP5 constructor.920 */921 function __construct( $time )922 {923 // $time can be a PHP timestamp or an ISO one924 if (is_numeric($time)) {925 $this->parseTimestamp($time);926 } else {927 $this->parseIso($time);928 }929 }930 931 /**932 * PHP4 constructor.933 */934 public function IXR_Date( $time ) {935 self::__construct( $time );936 }937 938 function parseTimestamp($timestamp)939 {940 $this->year = date('Y', $timestamp);941 $this->month = date('m', $timestamp);942 $this->day = date('d', $timestamp);943 $this->hour = date('H', $timestamp);944 $this->minute = date('i', $timestamp);945 $this->second = date('s', $timestamp);946 $this->timezone = '';947 }948 949 function parseIso($iso)950 {951 $this->year = substr($iso, 0, 4);952 $this->month = substr($iso, 4, 2);953 $this->day = substr($iso, 6, 2);954 $this->hour = substr($iso, 9, 2);955 $this->minute = substr($iso, 12, 2);956 $this->second = substr($iso, 15, 2);957 $this->timezone = substr($iso, 17);958 }959 960 function getIso()961 {962 return $this->year.$this->month.$this->day.'T'.$this->hour.':'.$this->minute.':'.$this->second.$this->timezone;963 }964 965 function getXml()966 {967 return '<dateTime.iso8601>'.$this->getIso().'</dateTime.iso8601>';968 }969 970 function getTimestamp()971 {972 return mktime($this->hour, $this->minute, $this->second, $this->month, $this->day, $this->year);973 }974 }975 976 /**977 * IXR_Base64978 *979 * @package IXR980 * @since 1.5.0981 */982 class IXR_Base64983 {984 var $data;985 986 /**987 * PHP5 constructor.988 */989 function __construct( $data )990 {991 $this->data = $data;992 }993 994 /**995 * PHP4 constructor.996 */997 public function IXR_Base64( $data ) {998 self::__construct( $data );999 }1000 1001 function getXml()1002 {1003 return '<base64>'.base64_encode($this->data).'</base64>';1004 }1005 }1006 1007 /**1008 * IXR_IntrospectionServer1009 *1010 * @package IXR1011 * @since 1.5.01012 */1013 class IXR_IntrospectionServer extends IXR_Server1014 {1015 var $signatures;1016 var $help;1017 1018 /**1019 * PHP5 constructor.1020 */1021 function __construct()1022 {1023 $this->setCallbacks();1024 $this->setCapabilities();1025 $this->capabilities['introspection'] = array(1026 'specUrl' => 'http://xmlrpc.usefulinc.com/doc/reserved.html',1027 'specVersion' => 11028 );1029 $this->addCallback(1030 'system.methodSignature',1031 'this:methodSignature',1032 array('array', 'string'),1033 'Returns an array describing the return type and required parameters of a method'1034 );1035 $this->addCallback(1036 'system.getCapabilities',1037 'this:getCapabilities',1038 array('struct'),1039 'Returns a struct describing the XML-RPC specifications supported by this server'1040 );1041 $this->addCallback(1042 'system.listMethods',1043 'this:listMethods',1044 array('array'),1045 'Returns an array of available methods on this server'1046 );1047 $this->addCallback(1048 'system.methodHelp',1049 'this:methodHelp',1050 array('string', 'string'),1051 'Returns a documentation string for the specified method'1052 );1053 }1054 1055 /**1056 * PHP4 constructor.1057 */1058 public function IXR_IntrospectionServer() {1059 self::__construct();1060 }1061 1062 function addCallback($method, $callback, $args, $help)1063 {1064 $this->callbacks[$method] = $callback;1065 $this->signatures[$method] = $args;1066 $this->help[$method] = $help;1067 }1068 1069 function call($methodname, $args)1070 {1071 // Make sure it's in an array1072 if ($args && !is_array($args)) {1073 $args = array($args);1074 }1075 1076 // Over-rides default call method, adds signature check1077 if (!$this->hasMethod($methodname)) {1078 return new IXR_Error(-32601, 'server error. requested method "'.$this->message->methodName.'" not specified.');1079 }1080 $method = $this->callbacks[$methodname];1081 $signature = $this->signatures[$methodname];1082 $returnType = array_shift($signature);1083 1084 // Check the number of arguments1085 if (count($args) != count($signature)) {1086 return new IXR_Error(-32602, 'server error. wrong number of method parameters');1087 }1088 1089 // Check the argument types1090 $ok = true;1091 $argsbackup = $args;1092 for ($i = 0, $j = count($args); $i < $j; $i++) {1093 $arg = array_shift($args);1094 $type = array_shift($signature);1095 switch ($type) {1096 case 'int':1097 case 'i4':1098 if (is_array($arg) || !is_int($arg)) {1099 $ok = false;1100 }1101 break;1102 case 'base64':1103 case 'string':1104 if (!is_string($arg)) {1105 $ok = false;1106 }1107 break;1108 case 'boolean':1109 if ($arg !== false && $arg !== true) {1110 $ok = false;1111 }1112 break;1113 case 'float':1114 case 'double':1115 if (!is_float($arg)) {1116 $ok = false;1117 }1118 break;1119 case 'date':1120 case 'dateTime.iso8601':1121 if (!is_a($arg, 'IXR_Date')) {1122 $ok = false;1123 }1124 break;1125 }1126 if (!$ok) {1127 return new IXR_Error(-32602, 'server error. invalid method parameters');1128 }1129 }1130 // It passed the test - run the "real" method call1131 return parent::call($methodname, $argsbackup);1132 }1133 1134 function methodSignature($method)1135 {1136 if (!$this->hasMethod($method)) {1137 return new IXR_Error(-32601, 'server error. requested method "'.$method.'" not specified.');1138 }1139 // We should be returning an array of types1140 $types = $this->signatures[$method];1141 $return = array();1142 foreach ($types as $type) {1143 switch ($type) {1144 case 'string':1145 $return[] = 'string';1146 break;1147 case 'int':1148 case 'i4':1149 $return[] = 42;1150 break;1151 case 'double':1152 $return[] = 3.1415;1153 break;1154 case 'dateTime.iso8601':1155 $return[] = new IXR_Date(time());1156 break;1157 case 'boolean':1158 $return[] = true;1159 break;1160 case 'base64':1161 $return[] = new IXR_Base64('base64');1162 break;1163 case 'array':1164 $return[] = array('array');1165 break;1166 case 'struct':1167 $return[] = array('struct' => 'struct');1168 break;1169 }1170 }1171 return $return;1172 }1173 1174 function methodHelp($method)1175 {1176 return $this->help[$method];1177 }1178 }1179 1180 /**1181 * IXR_ClientMulticall1182 *1183 * @package IXR1184 * @since 1.5.01185 */1186 class IXR_ClientMulticall extends IXR_Client1187 {1188 var $calls = array();1189 1190 /**1191 * PHP5 constructor.1192 */1193 function __construct( $server, $path = false, $port = 80 )1194 {1195 parent::IXR_Client($server, $path, $port);1196 $this->useragent = 'The Incutio XML-RPC PHP Library (multicall client)';1197 }1198 1199 /**1200 * PHP4 constructor.1201 */1202 public function IXR_ClientMulticall( $server, $path = false, $port = 80 ) {1203 self::__construct( $server, $path, $port );1204 }1205 1206 function addCall()1207 {1208 $args = func_get_args();1209 $methodName = array_shift($args);1210 $struct = array(1211 'methodName' => $methodName,1212 'params' => $args1213 );1214 $this->calls[] = $struct;1215 }1216 1217 function query()1218 {1219 // Prepare multicall, then call the parent::query() method1220 return parent::query('system.multicall', $this->calls);1221 }1222 } -
trunk/src/wp-includes/IXR/class-IXR-server.php
r38384 r38389 1 1 <?php 2 /**3 * IXR - The Incutio XML-RPC Library4 *5 * Copyright (c) 2010, Incutio Ltd.6 * All rights reserved.7 *8 * Redistribution and use in source and binary forms, with or without9 * 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 copyright14 * notice, this list of conditions and the following disclaimer in the15 * documentation and/or other materials provided with the distribution.16 * - Neither the name of Incutio Ltd. nor the names of its contributors17 * may be used to endorse or promote products derived from this software18 * without specific prior written permission.19 *20 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS21 * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,22 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR23 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR24 * 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, OR27 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY28 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT29 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE30 * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.31 *32 * @package IXR33 * @since 1.5.034 *35 * @copyright Incutio Ltd 2010 (http://www.incutio.com)36 * @version 1.7.4 7th September 201037 * @author Simon Willison38 * @link http://scripts.incutio.com/xmlrpc/ Site/manual39 * @license http://www.opensource.org/licenses/bsd-license.php BSD40 */41 42 /**43 * IXR_Value44 *45 * @package IXR46 * @since 1.5.047 */48 class IXR_Value {49 var $data;50 var $type;51 52 /**53 * PHP5 constructor.54 */55 function __construct( $data, $type = false )56 {57 $this->data = $data;58 if (!$type) {59 $type = $this->calculateType();60 }61 $this->type = $type;62 if ($type == 'struct') {63 // Turn all the values in the array in to new IXR_Value objects64 foreach ($this->data as $key => $value) {65 $this->data[$key] = new IXR_Value($value);66 }67 }68 if ($type == 'array') {69 for ($i = 0, $j = count($this->data); $i < $j; $i++) {70 $this->data[$i] = new IXR_Value($this->data[$i]);71 }72 }73 }74 75 /**76 * PHP4 constructor.77 */78 public function IXR_Value( $data, $type = false ) {79 self::__construct( $data, $type );80 }81 82 function calculateType()83 {84 if ($this->data === true || $this->data === false) {85 return 'boolean';86 }87 if (is_integer($this->data)) {88 return 'int';89 }90 if (is_double($this->data)) {91 return 'double';92 }93 94 // Deal with IXR object types base64 and date95 if (is_object($this->data) && is_a($this->data, 'IXR_Date')) {96 return 'date';97 }98 if (is_object($this->data) && is_a($this->data, 'IXR_Base64')) {99 return 'base64';100 }101 102 // If it is a normal PHP object convert it in to a struct103 if (is_object($this->data)) {104 $this->data = get_object_vars($this->data);105 return 'struct';106 }107 if (!is_array($this->data)) {108 return 'string';109 }110 111 // We have an array - is it an array or a struct?112 if ($this->isStruct($this->data)) {113 return 'struct';114 } else {115 return 'array';116 }117 }118 119 function getXml()120 {121 // Return XML for this value122 switch ($this->type) {123 case 'boolean':124 return '<boolean>'.(($this->data) ? '1' : '0').'</boolean>';125 break;126 case 'int':127 return '<int>'.$this->data.'</int>';128 break;129 case 'double':130 return '<double>'.$this->data.'</double>';131 break;132 case 'string':133 return '<string>'.htmlspecialchars($this->data).'</string>';134 break;135 case 'array':136 $return = '<array><data>'."\n";137 foreach ($this->data as $item) {138 $return .= ' <value>'.$item->getXml()."</value>\n";139 }140 $return .= '</data></array>';141 return $return;142 break;143 case 'struct':144 $return = '<struct>'."\n";145 foreach ($this->data as $name => $value) {146 $name = htmlspecialchars($name);147 $return .= " <member><name>$name</name><value>";148 $return .= $value->getXml()."</value></member>\n";149 }150 $return .= '</struct>';151 return $return;152 break;153 case 'date':154 case 'base64':155 return $this->data->getXml();156 break;157 }158 return false;159 }160 161 /**162 * Checks whether or not the supplied array is a struct or not163 *164 * @param array $array165 * @return bool166 */167 function isStruct($array)168 {169 $expected = 0;170 foreach ($array as $key => $value) {171 if ((string)$key !== (string)$expected) {172 return true;173 }174 $expected++;175 }176 return false;177 }178 }179 180 /**181 * IXR_MESSAGE182 *183 * @package IXR184 * @since 1.5.0185 *186 */187 class IXR_Message188 {189 var $message;190 var $messageType; // methodCall / methodResponse / fault191 var $faultCode;192 var $faultString;193 var $methodName;194 var $params;195 196 // Current variable stacks197 var $_arraystructs = array(); // The stack used to keep track of the current array/struct198 var $_arraystructstypes = array(); // Stack keeping track of if things are structs or array199 var $_currentStructName = array(); // A stack as well200 var $_param;201 var $_value;202 var $_currentTag;203 var $_currentTagContents;204 // The XML parser205 var $_parser;206 207 /**208 * PHP5 constructor.209 */210 function __construct( $message )211 {212 $this->message =& $message;213 }214 215 /**216 * PHP4 constructor.217 */218 public function IXR_Message( $message ) {219 self::__construct( $message );220 }221 222 function parse()223 {224 // first remove the XML declaration225 // merged from WP #10698 - this method avoids the RAM usage of preg_replace on very large messages226 $header = preg_replace( '/<\?xml.*?\?'.'>/s', '', substr( $this->message, 0, 100 ), 1 );227 $this->message = trim( substr_replace( $this->message, $header, 0, 100 ) );228 if ( '' == $this->message ) {229 return false;230 }231 232 // Then remove the DOCTYPE233 $header = preg_replace( '/^<!DOCTYPE[^>]*+>/i', '', substr( $this->message, 0, 200 ), 1 );234 $this->message = trim( substr_replace( $this->message, $header, 0, 200 ) );235 if ( '' == $this->message ) {236 return false;237 }238 239 // Check that the root tag is valid240 $root_tag = substr( $this->message, 0, strcspn( substr( $this->message, 0, 20 ), "> \t\r\n" ) );241 if ( '<!DOCTYPE' === strtoupper( $root_tag ) ) {242 return false;243 }244 if ( ! in_array( $root_tag, array( '<methodCall', '<methodResponse', '<fault' ) ) ) {245 return false;246 }247 248 // Bail if there are too many elements to parse249 $element_limit = 30000;250 if ( function_exists( 'apply_filters' ) ) {251 /**252 * Filters the number of elements to parse in an XML-RPC response.253 *254 * @since 4.0.0255 *256 * @param int $element_limit Default elements limit.257 */258 $element_limit = apply_filters( 'xmlrpc_element_limit', $element_limit );259 }260 if ( $element_limit && 2 * $element_limit < substr_count( $this->message, '<' ) ) {261 return false;262 }263 264 $this->_parser = xml_parser_create();265 // Set XML parser to take the case of tags in to account266 xml_parser_set_option($this->_parser, XML_OPTION_CASE_FOLDING, false);267 // Set XML parser callback functions268 xml_set_object($this->_parser, $this);269 xml_set_element_handler($this->_parser, 'tag_open', 'tag_close');270 xml_set_character_data_handler($this->_parser, 'cdata');271 272 // 256Kb, parse in chunks to avoid the RAM usage on very large messages273 $chunk_size = 262144;274 275 /**276 * Filters the chunk size that can be used to parse an XML-RPC reponse message.277 *278 * @since 4.4.0279 *280 * @param int $chunk_size Chunk size to parse in bytes.281 */282 $chunk_size = apply_filters( 'xmlrpc_chunk_parsing_size', $chunk_size );283 284 $final = false;285 do {286 if (strlen($this->message) <= $chunk_size) {287 $final = true;288 }289 $part = substr($this->message, 0, $chunk_size);290 $this->message = substr($this->message, $chunk_size);291 if (!xml_parse($this->_parser, $part, $final)) {292 return false;293 }294 if ($final) {295 break;296 }297 } while (true);298 xml_parser_free($this->_parser);299 300 // Grab the error messages, if any301 if ($this->messageType == 'fault') {302 $this->faultCode = $this->params[0]['faultCode'];303 $this->faultString = $this->params[0]['faultString'];304 }305 return true;306 }307 308 function tag_open($parser, $tag, $attr)309 {310 $this->_currentTagContents = '';311 $this->currentTag = $tag;312 switch($tag) {313 case 'methodCall':314 case 'methodResponse':315 case 'fault':316 $this->messageType = $tag;317 break;318 /* Deal with stacks of arrays and structs */319 case 'data': // data is to all intents and puposes more interesting than array320 $this->_arraystructstypes[] = 'array';321 $this->_arraystructs[] = array();322 break;323 case 'struct':324 $this->_arraystructstypes[] = 'struct';325 $this->_arraystructs[] = array();326 break;327 }328 }329 330 function cdata($parser, $cdata)331 {332 $this->_currentTagContents .= $cdata;333 }334 335 function tag_close($parser, $tag)336 {337 $valueFlag = false;338 switch($tag) {339 case 'int':340 case 'i4':341 $value = (int)trim($this->_currentTagContents);342 $valueFlag = true;343 break;344 case 'double':345 $value = (double)trim($this->_currentTagContents);346 $valueFlag = true;347 break;348 case 'string':349 $value = (string)trim($this->_currentTagContents);350 $valueFlag = true;351 break;352 case 'dateTime.iso8601':353 $value = new IXR_Date(trim($this->_currentTagContents));354 $valueFlag = true;355 break;356 case 'value':357 // "If no type is indicated, the type is string."358 if (trim($this->_currentTagContents) != '') {359 $value = (string)$this->_currentTagContents;360 $valueFlag = true;361 }362 break;363 case 'boolean':364 $value = (boolean)trim($this->_currentTagContents);365 $valueFlag = true;366 break;367 case 'base64':368 $value = base64_decode($this->_currentTagContents);369 $valueFlag = true;370 break;371 /* Deal with stacks of arrays and structs */372 case 'data':373 case 'struct':374 $value = array_pop($this->_arraystructs);375 array_pop($this->_arraystructstypes);376 $valueFlag = true;377 break;378 case 'member':379 array_pop($this->_currentStructName);380 break;381 case 'name':382 $this->_currentStructName[] = trim($this->_currentTagContents);383 break;384 case 'methodName':385 $this->methodName = trim($this->_currentTagContents);386 break;387 }388 389 if ($valueFlag) {390 if (count($this->_arraystructs) > 0) {391 // Add value to struct or array392 if ($this->_arraystructstypes[count($this->_arraystructstypes)-1] == 'struct') {393 // Add to struct394 $this->_arraystructs[count($this->_arraystructs)-1][$this->_currentStructName[count($this->_currentStructName)-1]] = $value;395 } else {396 // Add to array397 $this->_arraystructs[count($this->_arraystructs)-1][] = $value;398 }399 } else {400 // Just add as a parameter401 $this->params[] = $value;402 }403 }404 $this->_currentTagContents = '';405 }406 }407 2 408 3 /** … … 629 224 } 630 225 } 631 632 /**633 * IXR_Request634 *635 * @package IXR636 * @since 1.5.0637 */638 class IXR_Request639 {640 var $method;641 var $args;642 var $xml;643 644 /**645 * PHP5 constructor.646 */647 function __construct($method, $args)648 {649 $this->method = $method;650 $this->args = $args;651 $this->xml = <<<EOD652 <?xml version="1.0"?>653 <methodCall>654 <methodName>{$this->method}</methodName>655 <params>656 657 EOD;658 foreach ($this->args as $arg) {659 $this->xml .= '<param><value>';660 $v = new IXR_Value($arg);661 $this->xml .= $v->getXml();662 $this->xml .= "</value></param>\n";663 }664 $this->xml .= '</params></methodCall>';665 }666 667 /**668 * PHP4 constructor.669 */670 public function IXR_Request( $method, $args ) {671 self::__construct( $method, $args );672 }673 674 function getLength()675 {676 return strlen($this->xml);677 }678 679 function getXml()680 {681 return $this->xml;682 }683 }684 685 /**686 * IXR_Client687 *688 * @package IXR689 * @since 1.5.0690 *691 */692 class IXR_Client693 {694 var $server;695 var $port;696 var $path;697 var $useragent;698 var $response;699 var $message = false;700 var $debug = false;701 var $timeout;702 var $headers = array();703 704 // Storage place for an error message705 var $error = false;706 707 /**708 * PHP5 constructor.709 */710 function __construct( $server, $path = false, $port = 80, $timeout = 15 )711 {712 if (!$path) {713 // Assume we have been given a URL instead714 $bits = parse_url($server);715 $this->server = $bits['host'];716 $this->port = isset($bits['port']) ? $bits['port'] : 80;717 $this->path = isset($bits['path']) ? $bits['path'] : '/';718 719 // Make absolutely sure we have a path720 if (!$this->path) {721 $this->path = '/';722 }723 724 if ( ! empty( $bits['query'] ) ) {725 $this->path .= '?' . $bits['query'];726 }727 } else {728 $this->server = $server;729 $this->path = $path;730 $this->port = $port;731 }732 $this->useragent = 'The Incutio XML-RPC PHP Library';733 $this->timeout = $timeout;734 }735 736 /**737 * PHP4 constructor.738 */739 public function IXR_Client( $server, $path = false, $port = 80, $timeout = 15 ) {740 self::__construct( $server, $path, $port, $timeout );741 }742 743 function query()744 {745 $args = func_get_args();746 $method = array_shift($args);747 $request = new IXR_Request($method, $args);748 $length = $request->getLength();749 $xml = $request->getXml();750 $r = "\r\n";751 $request = "POST {$this->path} HTTP/1.0$r";752 753 // Merged from WP #8145 - allow custom headers754 $this->headers['Host'] = $this->server;755 $this->headers['Content-Type'] = 'text/xml';756 $this->headers['User-Agent'] = $this->useragent;757 $this->headers['Content-Length']= $length;758 759 foreach( $this->headers as $header => $value ) {760 $request .= "{$header}: {$value}{$r}";761 }762 $request .= $r;763 764 $request .= $xml;765 766 // Now send the request767 if ($this->debug) {768 echo '<pre class="ixr_request">'.htmlspecialchars($request)."\n</pre>\n\n";769 }770 771 if ($this->timeout) {772 $fp = @fsockopen($this->server, $this->port, $errno, $errstr, $this->timeout);773 } else {774 $fp = @fsockopen($this->server, $this->port, $errno, $errstr);775 }776 if (!$fp) {777 $this->error = new IXR_Error(-32300, 'transport error - could not open socket');778 return false;779 }780 fputs($fp, $request);781 $contents = '';782 $debugContents = '';783 $gotFirstLine = false;784 $gettingHeaders = true;785 while (!feof($fp)) {786 $line = fgets($fp, 4096);787 if (!$gotFirstLine) {788 // Check line for '200'789 if (strstr($line, '200') === false) {790 $this->error = new IXR_Error(-32300, 'transport error - HTTP status code was not 200');791 return false;792 }793 $gotFirstLine = true;794 }795 if (trim($line) == '') {796 $gettingHeaders = false;797 }798 if (!$gettingHeaders) {799 // merged from WP #12559 - remove trim800 $contents .= $line;801 }802 if ($this->debug) {803 $debugContents .= $line;804 }805 }806 if ($this->debug) {807 echo '<pre class="ixr_response">'.htmlspecialchars($debugContents)."\n</pre>\n\n";808 }809 810 // Now parse what we've got back811 $this->message = new IXR_Message($contents);812 if (!$this->message->parse()) {813 // XML error814 $this->error = new IXR_Error(-32700, 'parse error. not well formed');815 return false;816 }817 818 // Is the message a fault?819 if ($this->message->messageType == 'fault') {820 $this->error = new IXR_Error($this->message->faultCode, $this->message->faultString);821 return false;822 }823 824 // Message must be OK825 return true;826 }827 828 function getResponse()829 {830 // methodResponses can only have one param - return that831 return $this->message->params[0];832 }833 834 function isError()835 {836 return (is_object($this->error));837 }838 839 function getErrorCode()840 {841 return $this->error->code;842 }843 844 function getErrorMessage()845 {846 return $this->error->message;847 }848 }849 850 851 /**852 * IXR_Error853 *854 * @package IXR855 * @since 1.5.0856 */857 class IXR_Error858 {859 var $code;860 var $message;861 862 /**863 * PHP5 constructor.864 */865 function __construct( $code, $message )866 {867 $this->code = $code;868 $this->message = htmlspecialchars($message);869 }870 871 /**872 * PHP4 constructor.873 */874 public function IXR_Error( $code, $message ) {875 self::__construct( $code, $message );876 }877 878 function getXml()879 {880 $xml = <<<EOD881 <methodResponse>882 <fault>883 <value>884 <struct>885 <member>886 <name>faultCode</name>887 <value><int>{$this->code}</int></value>888 </member>889 <member>890 <name>faultString</name>891 <value><string>{$this->message}</string></value>892 </member>893 </struct>894 </value>895 </fault>896 </methodResponse>897 898 EOD;899 return $xml;900 }901 }902 903 /**904 * IXR_Date905 *906 * @package IXR907 * @since 1.5.0908 */909 class IXR_Date {910 var $year;911 var $month;912 var $day;913 var $hour;914 var $minute;915 var $second;916 var $timezone;917 918 /**919 * PHP5 constructor.920 */921 function __construct( $time )922 {923 // $time can be a PHP timestamp or an ISO one924 if (is_numeric($time)) {925 $this->parseTimestamp($time);926 } else {927 $this->parseIso($time);928 }929 }930 931 /**932 * PHP4 constructor.933 */934 public function IXR_Date( $time ) {935 self::__construct( $time );936 }937 938 function parseTimestamp($timestamp)939 {940 $this->year = date('Y', $timestamp);941 $this->month = date('m', $timestamp);942 $this->day = date('d', $timestamp);943 $this->hour = date('H', $timestamp);944 $this->minute = date('i', $timestamp);945 $this->second = date('s', $timestamp);946 $this->timezone = '';947 }948 949 function parseIso($iso)950 {951 $this->year = substr($iso, 0, 4);952 $this->month = substr($iso, 4, 2);953 $this->day = substr($iso, 6, 2);954 $this->hour = substr($iso, 9, 2);955 $this->minute = substr($iso, 12, 2);956 $this->second = substr($iso, 15, 2);957 $this->timezone = substr($iso, 17);958 }959 960 function getIso()961 {962 return $this->year.$this->month.$this->day.'T'.$this->hour.':'.$this->minute.':'.$this->second.$this->timezone;963 }964 965 function getXml()966 {967 return '<dateTime.iso8601>'.$this->getIso().'</dateTime.iso8601>';968 }969 970 function getTimestamp()971 {972 return mktime($this->hour, $this->minute, $this->second, $this->month, $this->day, $this->year);973 }974 }975 976 /**977 * IXR_Base64978 *979 * @package IXR980 * @since 1.5.0981 */982 class IXR_Base64983 {984 var $data;985 986 /**987 * PHP5 constructor.988 */989 function __construct( $data )990 {991 $this->data = $data;992 }993 994 /**995 * PHP4 constructor.996 */997 public function IXR_Base64( $data ) {998 self::__construct( $data );999 }1000 1001 function getXml()1002 {1003 return '<base64>'.base64_encode($this->data).'</base64>';1004 }1005 }1006 1007 /**1008 * IXR_IntrospectionServer1009 *1010 * @package IXR1011 * @since 1.5.01012 */1013 class IXR_IntrospectionServer extends IXR_Server1014 {1015 var $signatures;1016 var $help;1017 1018 /**1019 * PHP5 constructor.1020 */1021 function __construct()1022 {1023 $this->setCallbacks();1024 $this->setCapabilities();1025 $this->capabilities['introspection'] = array(1026 'specUrl' => 'http://xmlrpc.usefulinc.com/doc/reserved.html',1027 'specVersion' => 11028 );1029 $this->addCallback(1030 'system.methodSignature',1031 'this:methodSignature',1032 array('array', 'string'),1033 'Returns an array describing the return type and required parameters of a method'1034 );1035 $this->addCallback(1036 'system.getCapabilities',1037 'this:getCapabilities',1038 array('struct'),1039 'Returns a struct describing the XML-RPC specifications supported by this server'1040 );1041 $this->addCallback(1042 'system.listMethods',1043 'this:listMethods',1044 array('array'),1045 'Returns an array of available methods on this server'1046 );1047 $this->addCallback(1048 'system.methodHelp',1049 'this:methodHelp',1050 array('string', 'string'),1051 'Returns a documentation string for the specified method'1052 );1053 }1054 1055 /**1056 * PHP4 constructor.1057 */1058 public function IXR_IntrospectionServer() {1059 self::__construct();1060 }1061 1062 function addCallback($method, $callback, $args, $help)1063 {1064 $this->callbacks[$method] = $callback;1065 $this->signatures[$method] = $args;1066 $this->help[$method] = $help;1067 }1068 1069 function call($methodname, $args)1070 {1071 // Make sure it's in an array1072 if ($args && !is_array($args)) {1073 $args = array($args);1074 }1075 1076 // Over-rides default call method, adds signature check1077 if (!$this->hasMethod($methodname)) {1078 return new IXR_Error(-32601, 'server error. requested method "'.$this->message->methodName.'" not specified.');1079 }1080 $method = $this->callbacks[$methodname];1081 $signature = $this->signatures[$methodname];1082 $returnType = array_shift($signature);1083 1084 // Check the number of arguments1085 if (count($args) != count($signature)) {1086 return new IXR_Error(-32602, 'server error. wrong number of method parameters');1087 }1088 1089 // Check the argument types1090 $ok = true;1091 $argsbackup = $args;1092 for ($i = 0, $j = count($args); $i < $j; $i++) {1093 $arg = array_shift($args);1094 $type = array_shift($signature);1095 switch ($type) {1096 case 'int':1097 case 'i4':1098 if (is_array($arg) || !is_int($arg)) {1099 $ok = false;1100 }1101 break;1102 case 'base64':1103 case 'string':1104 if (!is_string($arg)) {1105 $ok = false;1106 }1107 break;1108 case 'boolean':1109 if ($arg !== false && $arg !== true) {1110 $ok = false;1111 }1112 break;1113 case 'float':1114 case 'double':1115 if (!is_float($arg)) {1116 $ok = false;1117 }1118 break;1119 case 'date':1120 case 'dateTime.iso8601':1121 if (!is_a($arg, 'IXR_Date')) {1122 $ok = false;1123 }1124 break;1125 }1126 if (!$ok) {1127 return new IXR_Error(-32602, 'server error. invalid method parameters');1128 }1129 }1130 // It passed the test - run the "real" method call1131 return parent::call($methodname, $argsbackup);1132 }1133 1134 function methodSignature($method)1135 {1136 if (!$this->hasMethod($method)) {1137 return new IXR_Error(-32601, 'server error. requested method "'.$method.'" not specified.');1138 }1139 // We should be returning an array of types1140 $types = $this->signatures[$method];1141 $return = array();1142 foreach ($types as $type) {1143 switch ($type) {1144 case 'string':1145 $return[] = 'string';1146 break;1147 case 'int':1148 case 'i4':1149 $return[] = 42;1150 break;1151 case 'double':1152 $return[] = 3.1415;1153 break;1154 case 'dateTime.iso8601':1155 $return[] = new IXR_Date(time());1156 break;1157 case 'boolean':1158 $return[] = true;1159 break;1160 case 'base64':1161 $return[] = new IXR_Base64('base64');1162 break;1163 case 'array':1164 $return[] = array('array');1165 break;1166 case 'struct':1167 $return[] = array('struct' => 'struct');1168 break;1169 }1170 }1171 return $return;1172 }1173 1174 function methodHelp($method)1175 {1176 return $this->help[$method];1177 }1178 }1179 1180 /**1181 * IXR_ClientMulticall1182 *1183 * @package IXR1184 * @since 1.5.01185 */1186 class IXR_ClientMulticall extends IXR_Client1187 {1188 var $calls = array();1189 1190 /**1191 * PHP5 constructor.1192 */1193 function __construct( $server, $path = false, $port = 80 )1194 {1195 parent::IXR_Client($server, $path, $port);1196 $this->useragent = 'The Incutio XML-RPC PHP Library (multicall client)';1197 }1198 1199 /**1200 * PHP4 constructor.1201 */1202 public function IXR_ClientMulticall( $server, $path = false, $port = 80 ) {1203 self::__construct( $server, $path, $port );1204 }1205 1206 function addCall()1207 {1208 $args = func_get_args();1209 $methodName = array_shift($args);1210 $struct = array(1211 'methodName' => $methodName,1212 'params' => $args1213 );1214 $this->calls[] = $struct;1215 }1216 1217 function query()1218 {1219 // Prepare multicall, then call the parent::query() method1220 return parent::query('system.multicall', $this->calls);1221 }1222 } -
trunk/src/wp-includes/IXR/class-IXR-value.php
r38384 r38389 1 1 <?php 2 /**3 * IXR - The Incutio XML-RPC Library4 *5 * Copyright (c) 2010, Incutio Ltd.6 * All rights reserved.7 *8 * Redistribution and use in source and binary forms, with or without9 * 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 copyright14 * notice, this list of conditions and the following disclaimer in the15 * documentation and/or other materials provided with the distribution.16 * - Neither the name of Incutio Ltd. nor the names of its contributors17 * may be used to endorse or promote products derived from this software18 * without specific prior written permission.19 *20 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS21 * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,22 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR23 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR24 * 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, OR27 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY28 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT29 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE30 * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.31 *32 * @package IXR33 * @since 1.5.034 *35 * @copyright Incutio Ltd 2010 (http://www.incutio.com)36 * @version 1.7.4 7th September 201037 * @author Simon Willison38 * @link http://scripts.incutio.com/xmlrpc/ Site/manual39 * @license http://www.opensource.org/licenses/bsd-license.php BSD40 */41 42 2 /** 43 3 * IXR_Value … … 177 137 } 178 138 } 179 180 /**181 * IXR_MESSAGE182 *183 * @package IXR184 * @since 1.5.0185 *186 */187 class IXR_Message188 {189 var $message;190 var $messageType; // methodCall / methodResponse / fault191 var $faultCode;192 var $faultString;193 var $methodName;194 var $params;195 196 // Current variable stacks197 var $_arraystructs = array(); // The stack used to keep track of the current array/struct198 var $_arraystructstypes = array(); // Stack keeping track of if things are structs or array199 var $_currentStructName = array(); // A stack as well200 var $_param;201 var $_value;202 var $_currentTag;203 var $_currentTagContents;204 // The XML parser205 var $_parser;206 207 /**208 * PHP5 constructor.209 */210 function __construct( $message )211 {212 $this->message =& $message;213 }214 215 /**216 * PHP4 constructor.217 */218 public function IXR_Message( $message ) {219 self::__construct( $message );220 }221 222 function parse()223 {224 // first remove the XML declaration225 // merged from WP #10698 - this method avoids the RAM usage of preg_replace on very large messages226 $header = preg_replace( '/<\?xml.*?\?'.'>/s', '', substr( $this->message, 0, 100 ), 1 );227 $this->message = trim( substr_replace( $this->message, $header, 0, 100 ) );228 if ( '' == $this->message ) {229 return false;230 }231 232 // Then remove the DOCTYPE233 $header = preg_replace( '/^<!DOCTYPE[^>]*+>/i', '', substr( $this->message, 0, 200 ), 1 );234 $this->message = trim( substr_replace( $this->message, $header, 0, 200 ) );235 if ( '' == $this->message ) {236 return false;237 }238 239 // Check that the root tag is valid240 $root_tag = substr( $this->message, 0, strcspn( substr( $this->message, 0, 20 ), "> \t\r\n" ) );241 if ( '<!DOCTYPE' === strtoupper( $root_tag ) ) {242 return false;243 }244 if ( ! in_array( $root_tag, array( '<methodCall', '<methodResponse', '<fault' ) ) ) {245 return false;246 }247 248 // Bail if there are too many elements to parse249 $element_limit = 30000;250 if ( function_exists( 'apply_filters' ) ) {251 /**252 * Filters the number of elements to parse in an XML-RPC response.253 *254 * @since 4.0.0255 *256 * @param int $element_limit Default elements limit.257 */258 $element_limit = apply_filters( 'xmlrpc_element_limit', $element_limit );259 }260 if ( $element_limit && 2 * $element_limit < substr_count( $this->message, '<' ) ) {261 return false;262 }263 264 $this->_parser = xml_parser_create();265 // Set XML parser to take the case of tags in to account266 xml_parser_set_option($this->_parser, XML_OPTION_CASE_FOLDING, false);267 // Set XML parser callback functions268 xml_set_object($this->_parser, $this);269 xml_set_element_handler($this->_parser, 'tag_open', 'tag_close');270 xml_set_character_data_handler($this->_parser, 'cdata');271 272 // 256Kb, parse in chunks to avoid the RAM usage on very large messages273 $chunk_size = 262144;274 275 /**276 * Filters the chunk size that can be used to parse an XML-RPC reponse message.277 *278 * @since 4.4.0279 *280 * @param int $chunk_size Chunk size to parse in bytes.281 */282 $chunk_size = apply_filters( 'xmlrpc_chunk_parsing_size', $chunk_size );283 284 $final = false;285 do {286 if (strlen($this->message) <= $chunk_size) {287 $final = true;288 }289 $part = substr($this->message, 0, $chunk_size);290 $this->message = substr($this->message, $chunk_size);291 if (!xml_parse($this->_parser, $part, $final)) {292 return false;293 }294 if ($final) {295 break;296 }297 } while (true);298 xml_parser_free($this->_parser);299 300 // Grab the error messages, if any301 if ($this->messageType == 'fault') {302 $this->faultCode = $this->params[0]['faultCode'];303 $this->faultString = $this->params[0]['faultString'];304 }305 return true;306 }307 308 function tag_open($parser, $tag, $attr)309 {310 $this->_currentTagContents = '';311 $this->currentTag = $tag;312 switch($tag) {313 case 'methodCall':314 case 'methodResponse':315 case 'fault':316 $this->messageType = $tag;317 break;318 /* Deal with stacks of arrays and structs */319 case 'data': // data is to all intents and puposes more interesting than array320 $this->_arraystructstypes[] = 'array';321 $this->_arraystructs[] = array();322 break;323 case 'struct':324 $this->_arraystructstypes[] = 'struct';325 $this->_arraystructs[] = array();326 break;327 }328 }329 330 function cdata($parser, $cdata)331 {332 $this->_currentTagContents .= $cdata;333 }334 335 function tag_close($parser, $tag)336 {337 $valueFlag = false;338 switch($tag) {339 case 'int':340 case 'i4':341 $value = (int)trim($this->_currentTagContents);342 $valueFlag = true;343 break;344 case 'double':345 $value = (double)trim($this->_currentTagContents);346 $valueFlag = true;347 break;348 case 'string':349 $value = (string)trim($this->_currentTagContents);350 $valueFlag = true;351 break;352 case 'dateTime.iso8601':353 $value = new IXR_Date(trim($this->_currentTagContents));354 $valueFlag = true;355 break;356 case 'value':357 // "If no type is indicated, the type is string."358 if (trim($this->_currentTagContents) != '') {359 $value = (string)$this->_currentTagContents;360 $valueFlag = true;361 }362 break;363 case 'boolean':364 $value = (boolean)trim($this->_currentTagContents);365 $valueFlag = true;366 break;367 case 'base64':368 $value = base64_decode($this->_currentTagContents);369 $valueFlag = true;370 break;371 /* Deal with stacks of arrays and structs */372 case 'data':373 case 'struct':374 $value = array_pop($this->_arraystructs);375 array_pop($this->_arraystructstypes);376 $valueFlag = true;377 break;378 case 'member':379 array_pop($this->_currentStructName);380 break;381 case 'name':382 $this->_currentStructName[] = trim($this->_currentTagContents);383 break;384 case 'methodName':385 $this->methodName = trim($this->_currentTagContents);386 break;387 }388 389 if ($valueFlag) {390 if (count($this->_arraystructs) > 0) {391 // Add value to struct or array392 if ($this->_arraystructstypes[count($this->_arraystructstypes)-1] == 'struct') {393 // Add to struct394 $this->_arraystructs[count($this->_arraystructs)-1][$this->_currentStructName[count($this->_currentStructName)-1]] = $value;395 } else {396 // Add to array397 $this->_arraystructs[count($this->_arraystructs)-1][] = $value;398 }399 } else {400 // Just add as a parameter401 $this->params[] = $value;402 }403 }404 $this->_currentTagContents = '';405 }406 }407 408 /**409 * IXR_Server410 *411 * @package IXR412 * @since 1.5.0413 */414 class IXR_Server415 {416 var $data;417 var $callbacks = array();418 var $message;419 var $capabilities;420 421 /**422 * PHP5 constructor.423 */424 function __construct( $callbacks = false, $data = false, $wait = false )425 {426 $this->setCapabilities();427 if ($callbacks) {428 $this->callbacks = $callbacks;429 }430 $this->setCallbacks();431 if (!$wait) {432 $this->serve($data);433 }434 }435 436 /**437 * PHP4 constructor.438 */439 public function IXR_Server( $callbacks = false, $data = false, $wait = false ) {440 self::__construct( $callbacks, $data, $wait );441 }442 443 function serve($data = false)444 {445 if (!$data) {446 if (isset($_SERVER['REQUEST_METHOD']) && $_SERVER['REQUEST_METHOD'] !== 'POST') {447 if ( function_exists( 'status_header' ) ) {448 status_header( 405 ); // WP #20986449 header( 'Allow: POST' );450 }451 header('Content-Type: text/plain'); // merged from WP #9093452 die('XML-RPC server accepts POST requests only.');453 }454 455 global $HTTP_RAW_POST_DATA;456 if (empty($HTTP_RAW_POST_DATA)) {457 // workaround for a bug in PHP 5.2.2 - http://bugs.php.net/bug.php?id=41293458 $data = file_get_contents('php://input');459 } else {460 $data =& $HTTP_RAW_POST_DATA;461 }462 }463 $this->message = new IXR_Message($data);464 if (!$this->message->parse()) {465 $this->error(-32700, 'parse error. not well formed');466 }467 if ($this->message->messageType != 'methodCall') {468 $this->error(-32600, 'server error. invalid xml-rpc. not conforming to spec. Request must be a methodCall');469 }470 $result = $this->call($this->message->methodName, $this->message->params);471 472 // Is the result an error?473 if (is_a($result, 'IXR_Error')) {474 $this->error($result);475 }476 477 // Encode the result478 $r = new IXR_Value($result);479 $resultxml = $r->getXml();480 481 // Create the XML482 $xml = <<<EOD483 <methodResponse>484 <params>485 <param>486 <value>487 $resultxml488 </value>489 </param>490 </params>491 </methodResponse>492 493 EOD;494 // Send it495 $this->output($xml);496 }497 498 function call($methodname, $args)499 {500 if (!$this->hasMethod($methodname)) {501 return new IXR_Error(-32601, 'server error. requested method '.$methodname.' does not exist.');502 }503 $method = $this->callbacks[$methodname];504 505 // Perform the callback and send the response506 if (count($args) == 1) {507 // If only one parameter just send that instead of the whole array508 $args = $args[0];509 }510 511 // Are we dealing with a function or a method?512 if (is_string($method) && substr($method, 0, 5) == 'this:') {513 // It's a class method - check it exists514 $method = substr($method, 5);515 if (!method_exists($this, $method)) {516 return new IXR_Error(-32601, 'server error. requested class method "'.$method.'" does not exist.');517 }518 519 //Call the method520 $result = $this->$method($args);521 } else {522 // It's a function - does it exist?523 if (is_array($method)) {524 if (!is_callable(array($method[0], $method[1]))) {525 return new IXR_Error(-32601, 'server error. requested object method "'.$method[1].'" does not exist.');526 }527 } else if (!function_exists($method)) {528 return new IXR_Error(-32601, 'server error. requested function "'.$method.'" does not exist.');529 }530 531 // Call the function532 $result = call_user_func($method, $args);533 }534 return $result;535 }536 537 function error($error, $message = false)538 {539 // Accepts either an error object or an error code and message540 if ($message && !is_object($error)) {541 $error = new IXR_Error($error, $message);542 }543 $this->output($error->getXml());544 }545 546 function output($xml)547 {548 $charset = function_exists('get_option') ? get_option('blog_charset') : '';549 if ($charset)550 $xml = '<?xml version="1.0" encoding="'.$charset.'"?>'."\n".$xml;551 else552 $xml = '<?xml version="1.0"?>'."\n".$xml;553 $length = strlen($xml);554 header('Connection: close');555 if ($charset)556 header('Content-Type: text/xml; charset='.$charset);557 else558 header('Content-Type: text/xml');559 header('Date: '.date('r'));560 echo $xml;561 exit;562 }563 564 function hasMethod($method)565 {566 return in_array($method, array_keys($this->callbacks));567 }568 569 function setCapabilities()570 {571 // Initialises capabilities array572 $this->capabilities = array(573 'xmlrpc' => array(574 'specUrl' => 'http://www.xmlrpc.com/spec',575 'specVersion' => 1576 ),577 'faults_interop' => array(578 'specUrl' => 'http://xmlrpc-epi.sourceforge.net/specs/rfc.fault_codes.php',579 'specVersion' => 20010516580 ),581 'system.multicall' => array(582 'specUrl' => 'http://www.xmlrpc.com/discuss/msgReader$1208',583 'specVersion' => 1584 ),585 );586 }587 588 function getCapabilities($args)589 {590 return $this->capabilities;591 }592 593 function setCallbacks()594 {595 $this->callbacks['system.getCapabilities'] = 'this:getCapabilities';596 $this->callbacks['system.listMethods'] = 'this:listMethods';597 $this->callbacks['system.multicall'] = 'this:multiCall';598 }599 600 function listMethods($args)601 {602 // Returns a list of methods - uses array_reverse to ensure user defined603 // methods are listed before server defined methods604 return array_reverse(array_keys($this->callbacks));605 }606 607 function multiCall($methodcalls)608 {609 // See http://www.xmlrpc.com/discuss/msgReader$1208610 $return = array();611 foreach ($methodcalls as $call) {612 $method = $call['methodName'];613 $params = $call['params'];614 if ($method == 'system.multicall') {615 $result = new IXR_Error(-32600, 'Recursive calls to system.multicall are forbidden');616 } else {617 $result = $this->call($method, $params);618 }619 if (is_a($result, 'IXR_Error')) {620 $return[] = array(621 'faultCode' => $result->code,622 'faultString' => $result->message623 );624 } else {625 $return[] = array($result);626 }627 }628 return $return;629 }630 }631 632 /**633 * IXR_Request634 *635 * @package IXR636 * @since 1.5.0637 */638 class IXR_Request639 {640 var $method;641 var $args;642 var $xml;643 644 /**645 * PHP5 constructor.646 */647 function __construct($method, $args)648 {649 $this->method = $method;650 $this->args = $args;651 $this->xml = <<<EOD652 <?xml version="1.0"?>653 <methodCall>654 <methodName>{$this->method}</methodName>655 <params>656 657 EOD;658 foreach ($this->args as $arg) {659 $this->xml .= '<param><value>';660 $v = new IXR_Value($arg);661 $this->xml .= $v->getXml();662 $this->xml .= "</value></param>\n";663 }664 $this->xml .= '</params></methodCall>';665 }666 667 /**668 * PHP4 constructor.669 */670 public function IXR_Request( $method, $args ) {671 self::__construct( $method, $args );672 }673 674 function getLength()675 {676 return strlen($this->xml);677 }678 679 function getXml()680 {681 return $this->xml;682 }683 }684 685 /**686 * IXR_Client687 *688 * @package IXR689 * @since 1.5.0690 *691 */692 class IXR_Client693 {694 var $server;695 var $port;696 var $path;697 var $useragent;698 var $response;699 var $message = false;700 var $debug = false;701 var $timeout;702 var $headers = array();703 704 // Storage place for an error message705 var $error = false;706 707 /**708 * PHP5 constructor.709 */710 function __construct( $server, $path = false, $port = 80, $timeout = 15 )711 {712 if (!$path) {713 // Assume we have been given a URL instead714 $bits = parse_url($server);715 $this->server = $bits['host'];716 $this->port = isset($bits['port']) ? $bits['port'] : 80;717 $this->path = isset($bits['path']) ? $bits['path'] : '/';718 719 // Make absolutely sure we have a path720 if (!$this->path) {721 $this->path = '/';722 }723 724 if ( ! empty( $bits['query'] ) ) {725 $this->path .= '?' . $bits['query'];726 }727 } else {728 $this->server = $server;729 $this->path = $path;730 $this->port = $port;731 }732 $this->useragent = 'The Incutio XML-RPC PHP Library';733 $this->timeout = $timeout;734 }735 736 /**737 * PHP4 constructor.738 */739 public function IXR_Client( $server, $path = false, $port = 80, $timeout = 15 ) {740 self::__construct( $server, $path, $port, $timeout );741 }742 743 function query()744 {745 $args = func_get_args();746 $method = array_shift($args);747 $request = new IXR_Request($method, $args);748 $length = $request->getLength();749 $xml = $request->getXml();750 $r = "\r\n";751 $request = "POST {$this->path} HTTP/1.0$r";752 753 // Merged from WP #8145 - allow custom headers754 $this->headers['Host'] = $this->server;755 $this->headers['Content-Type'] = 'text/xml';756 $this->headers['User-Agent'] = $this->useragent;757 $this->headers['Content-Length']= $length;758 759 foreach( $this->headers as $header => $value ) {760 $request .= "{$header}: {$value}{$r}";761 }762 $request .= $r;763 764 $request .= $xml;765 766 // Now send the request767 if ($this->debug) {768 echo '<pre class="ixr_request">'.htmlspecialchars($request)."\n</pre>\n\n";769 }770 771 if ($this->timeout) {772 $fp = @fsockopen($this->server, $this->port, $errno, $errstr, $this->timeout);773 } else {774 $fp = @fsockopen($this->server, $this->port, $errno, $errstr);775 }776 if (!$fp) {777 $this->error = new IXR_Error(-32300, 'transport error - could not open socket');778 return false;779 }780 fputs($fp, $request);781 $contents = '';782 $debugContents = '';783 $gotFirstLine = false;784 $gettingHeaders = true;785 while (!feof($fp)) {786 $line = fgets($fp, 4096);787 if (!$gotFirstLine) {788 // Check line for '200'789 if (strstr($line, '200') === false) {790 $this->error = new IXR_Error(-32300, 'transport error - HTTP status code was not 200');791 return false;792 }793 $gotFirstLine = true;794 }795 if (trim($line) == '') {796 $gettingHeaders = false;797 }798 if (!$gettingHeaders) {799 // merged from WP #12559 - remove trim800 $contents .= $line;801 }802 if ($this->debug) {803 $debugContents .= $line;804 }805 }806 if ($this->debug) {807 echo '<pre class="ixr_response">'.htmlspecialchars($debugContents)."\n</pre>\n\n";808 }809 810 // Now parse what we've got back811 $this->message = new IXR_Message($contents);812 if (!$this->message->parse()) {813 // XML error814 $this->error = new IXR_Error(-32700, 'parse error. not well formed');815 return false;816 }817 818 // Is the message a fault?819 if ($this->message->messageType == 'fault') {820 $this->error = new IXR_Error($this->message->faultCode, $this->message->faultString);821 return false;822 }823 824 // Message must be OK825 return true;826 }827 828 function getResponse()829 {830 // methodResponses can only have one param - return that831 return $this->message->params[0];832 }833 834 function isError()835 {836 return (is_object($this->error));837 }838 839 function getErrorCode()840 {841 return $this->error->code;842 }843 844 function getErrorMessage()845 {846 return $this->error->message;847 }848 }849 850 851 /**852 * IXR_Error853 *854 * @package IXR855 * @since 1.5.0856 */857 class IXR_Error858 {859 var $code;860 var $message;861 862 /**863 * PHP5 constructor.864 */865 function __construct( $code, $message )866 {867 $this->code = $code;868 $this->message = htmlspecialchars($message);869 }870 871 /**872 * PHP4 constructor.873 */874 public function IXR_Error( $code, $message ) {875 self::__construct( $code, $message );876 }877 878 function getXml()879 {880 $xml = <<<EOD881 <methodResponse>882 <fault>883 <value>884 <struct>885 <member>886 <name>faultCode</name>887 <value><int>{$this->code}</int></value>888 </member>889 <member>890 <name>faultString</name>891 <value><string>{$this->message}</string></value>892 </member>893 </struct>894 </value>895 </fault>896 </methodResponse>897 898 EOD;899 return $xml;900 }901 }902 903 /**904 * IXR_Date905 *906 * @package IXR907 * @since 1.5.0908 */909 class IXR_Date {910 var $year;911 var $month;912 var $day;913 var $hour;914 var $minute;915 var $second;916 var $timezone;917 918 /**919 * PHP5 constructor.920 */921 function __construct( $time )922 {923 // $time can be a PHP timestamp or an ISO one924 if (is_numeric($time)) {925 $this->parseTimestamp($time);926 } else {927 $this->parseIso($time);928 }929 }930 931 /**932 * PHP4 constructor.933 */934 public function IXR_Date( $time ) {935 self::__construct( $time );936 }937 938 function parseTimestamp($timestamp)939 {940 $this->year = date('Y', $timestamp);941 $this->month = date('m', $timestamp);942 $this->day = date('d', $timestamp);943 $this->hour = date('H', $timestamp);944 $this->minute = date('i', $timestamp);945 $this->second = date('s', $timestamp);946 $this->timezone = '';947 }948 949 function parseIso($iso)950 {951 $this->year = substr($iso, 0, 4);952 $this->month = substr($iso, 4, 2);953 $this->day = substr($iso, 6, 2);954 $this->hour = substr($iso, 9, 2);955 $this->minute = substr($iso, 12, 2);956 $this->second = substr($iso, 15, 2);957 $this->timezone = substr($iso, 17);958 }959 960 function getIso()961 {962 return $this->year.$this->month.$this->day.'T'.$this->hour.':'.$this->minute.':'.$this->second.$this->timezone;963 }964 965 function getXml()966 {967 return '<dateTime.iso8601>'.$this->getIso().'</dateTime.iso8601>';968 }969 970 function getTimestamp()971 {972 return mktime($this->hour, $this->minute, $this->second, $this->month, $this->day, $this->year);973 }974 }975 976 /**977 * IXR_Base64978 *979 * @package IXR980 * @since 1.5.0981 */982 class IXR_Base64983 {984 var $data;985 986 /**987 * PHP5 constructor.988 */989 function __construct( $data )990 {991 $this->data = $data;992 }993 994 /**995 * PHP4 constructor.996 */997 public function IXR_Base64( $data ) {998 self::__construct( $data );999 }1000 1001 function getXml()1002 {1003 return '<base64>'.base64_encode($this->data).'</base64>';1004 }1005 }1006 1007 /**1008 * IXR_IntrospectionServer1009 *1010 * @package IXR1011 * @since 1.5.01012 */1013 class IXR_IntrospectionServer extends IXR_Server1014 {1015 var $signatures;1016 var $help;1017 1018 /**1019 * PHP5 constructor.1020 */1021 function __construct()1022 {1023 $this->setCallbacks();1024 $this->setCapabilities();1025 $this->capabilities['introspection'] = array(1026 'specUrl' => 'http://xmlrpc.usefulinc.com/doc/reserved.html',1027 'specVersion' => 11028 );1029 $this->addCallback(1030 'system.methodSignature',1031 'this:methodSignature',1032 array('array', 'string'),1033 'Returns an array describing the return type and required parameters of a method'1034 );1035 $this->addCallback(1036 'system.getCapabilities',1037 'this:getCapabilities',1038 array('struct'),1039 'Returns a struct describing the XML-RPC specifications supported by this server'1040 );1041 $this->addCallback(1042 'system.listMethods',1043 'this:listMethods',1044 array('array'),1045 'Returns an array of available methods on this server'1046 );1047 $this->addCallback(1048 'system.methodHelp',1049 'this:methodHelp',1050 array('string', 'string'),1051 'Returns a documentation string for the specified method'1052 );1053 }1054 1055 /**1056 * PHP4 constructor.1057 */1058 public function IXR_IntrospectionServer() {1059 self::__construct();1060 }1061 1062 function addCallback($method, $callback, $args, $help)1063 {1064 $this->callbacks[$method] = $callback;1065 $this->signatures[$method] = $args;1066 $this->help[$method] = $help;1067 }1068 1069 function call($methodname, $args)1070 {1071 // Make sure it's in an array1072 if ($args && !is_array($args)) {1073 $args = array($args);1074 }1075 1076 // Over-rides default call method, adds signature check1077 if (!$this->hasMethod($methodname)) {1078 return new IXR_Error(-32601, 'server error. requested method "'.$this->message->methodName.'" not specified.');1079 }1080 $method = $this->callbacks[$methodname];1081 $signature = $this->signatures[$methodname];1082 $returnType = array_shift($signature);1083 1084 // Check the number of arguments1085 if (count($args) != count($signature)) {1086 return new IXR_Error(-32602, 'server error. wrong number of method parameters');1087 }1088 1089 // Check the argument types1090 $ok = true;1091 $argsbackup = $args;1092 for ($i = 0, $j = count($args); $i < $j; $i++) {1093 $arg = array_shift($args);1094 $type = array_shift($signature);1095 switch ($type) {1096 case 'int':1097 case 'i4':1098 if (is_array($arg) || !is_int($arg)) {1099 $ok = false;1100 }1101 break;1102 case 'base64':1103 case 'string':1104 if (!is_string($arg)) {1105 $ok = false;1106 }1107 break;1108 case 'boolean':1109 if ($arg !== false && $arg !== true) {1110 $ok = false;1111 }1112 break;1113 case 'float':1114 case 'double':1115 if (!is_float($arg)) {1116 $ok = false;1117 }1118 break;1119 case 'date':1120 case 'dateTime.iso8601':1121 if (!is_a($arg, 'IXR_Date')) {1122 $ok = false;1123 }1124 break;1125 }1126 if (!$ok) {1127 return new IXR_Error(-32602, 'server error. invalid method parameters');1128 }1129 }1130 // It passed the test - run the "real" method call1131 return parent::call($methodname, $argsbackup);1132 }1133 1134 function methodSignature($method)1135 {1136 if (!$this->hasMethod($method)) {1137 return new IXR_Error(-32601, 'server error. requested method "'.$method.'" not specified.');1138 }1139 // We should be returning an array of types1140 $types = $this->signatures[$method];1141 $return = array();1142 foreach ($types as $type) {1143 switch ($type) {1144 case 'string':1145 $return[] = 'string';1146 break;1147 case 'int':1148 case 'i4':1149 $return[] = 42;1150 break;1151 case 'double':1152 $return[] = 3.1415;1153 break;1154 case 'dateTime.iso8601':1155 $return[] = new IXR_Date(time());1156 break;1157 case 'boolean':1158 $return[] = true;1159 break;1160 case 'base64':1161 $return[] = new IXR_Base64('base64');1162 break;1163 case 'array':1164 $return[] = array('array');1165 break;1166 case 'struct':1167 $return[] = array('struct' => 'struct');1168 break;1169 }1170 }1171 return $return;1172 }1173 1174 function methodHelp($method)1175 {1176 return $this->help[$method];1177 }1178 }1179 1180 /**1181 * IXR_ClientMulticall1182 *1183 * @package IXR1184 * @since 1.5.01185 */1186 class IXR_ClientMulticall extends IXR_Client1187 {1188 var $calls = array();1189 1190 /**1191 * PHP5 constructor.1192 */1193 function __construct( $server, $path = false, $port = 80 )1194 {1195 parent::IXR_Client($server, $path, $port);1196 $this->useragent = 'The Incutio XML-RPC PHP Library (multicall client)';1197 }1198 1199 /**1200 * PHP4 constructor.1201 */1202 public function IXR_ClientMulticall( $server, $path = false, $port = 80 ) {1203 self::__construct( $server, $path, $port );1204 }1205 1206 function addCall()1207 {1208 $args = func_get_args();1209 $methodName = array_shift($args);1210 $struct = array(1211 'methodName' => $methodName,1212 'params' => $args1213 );1214 $this->calls[] = $struct;1215 }1216 1217 function query()1218 {1219 // Prepare multicall, then call the parent::query() method1220 return parent::query('system.multicall', $this->calls);1221 }1222 } -
trunk/src/wp-includes/class-IXR.php
r37518 r38389 40 40 */ 41 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; 42 require_once( ABSPATH . WPINC . '/IXR/class-IXR-value.php' ); 51 43 52 /** 53 * PHP5 constructor. 54 */ 55 function __construct( $data, $type = false ) 56 { 57 $this->data = $data; 58 if (!$type) { 59 $type = $this->calculateType(); 60 } 61 $this->type = $type; 62 if ($type == 'struct') { 63 // Turn all the values in the array in to new IXR_Value objects 64 foreach ($this->data as $key => $value) { 65 $this->data[$key] = new IXR_Value($value); 66 } 67 } 68 if ($type == 'array') { 69 for ($i = 0, $j = count($this->data); $i < $j; $i++) { 70 $this->data[$i] = new IXR_Value($this->data[$i]); 71 } 72 } 73 } 44 require_once( ABSPATH . WPINC . '/IXR/class-IXR-message.php' ); 74 45 75 /** 76 * PHP4 constructor. 77 */ 78 public function IXR_Value( $data, $type = false ) { 79 self::__construct( $data, $type ); 80 } 46 require_once( ABSPATH . WPINC . '/IXR/class-IXR-server.php' ); 81 47 82 function calculateType() 83 { 84 if ($this->data === true || $this->data === false) { 85 return 'boolean'; 86 } 87 if (is_integer($this->data)) { 88 return 'int'; 89 } 90 if (is_double($this->data)) { 91 return 'double'; 92 } 48 require_once( ABSPATH . WPINC . '/IXR/class-IXR-request.php' ); 93 49 94 // Deal with IXR object types base64 and date 95 if (is_object($this->data) && is_a($this->data, 'IXR_Date')) { 96 return 'date'; 97 } 98 if (is_object($this->data) && is_a($this->data, 'IXR_Base64')) { 99 return 'base64'; 100 } 50 require_once( ABSPATH . WPINC . '/IXR/class-IXR-client.php' ); 101 51 102 // If it is a normal PHP object convert it in to a struct 103 if (is_object($this->data)) { 104 $this->data = get_object_vars($this->data); 105 return 'struct'; 106 } 107 if (!is_array($this->data)) { 108 return 'string'; 109 } 52 require_once( ABSPATH . WPINC . '/IXR/class-IXR-error.php' ); 110 53 111 // We have an array - is it an array or a struct? 112 if ($this->isStruct($this->data)) { 113 return 'struct'; 114 } else { 115 return 'array'; 116 } 117 } 54 require_once( ABSPATH . WPINC . '/IXR/class-IXR-date.php' ); 118 55 119 function getXml() 120 { 121 // Return XML for this value 122 switch ($this->type) { 123 case 'boolean': 124 return '<boolean>'.(($this->data) ? '1' : '0').'</boolean>'; 125 break; 126 case 'int': 127 return '<int>'.$this->data.'</int>'; 128 break; 129 case 'double': 130 return '<double>'.$this->data.'</double>'; 131 break; 132 case 'string': 133 return '<string>'.htmlspecialchars($this->data).'</string>'; 134 break; 135 case 'array': 136 $return = '<array><data>'."\n"; 137 foreach ($this->data as $item) { 138 $return .= ' <value>'.$item->getXml()."</value>\n"; 139 } 140 $return .= '</data></array>'; 141 return $return; 142 break; 143 case 'struct': 144 $return = '<struct>'."\n"; 145 foreach ($this->data as $name => $value) { 146 $name = htmlspecialchars($name); 147 $return .= " <member><name>$name</name><value>"; 148 $return .= $value->getXml()."</value></member>\n"; 149 } 150 $return .= '</struct>'; 151 return $return; 152 break; 153 case 'date': 154 case 'base64': 155 return $this->data->getXml(); 156 break; 157 } 158 return false; 159 } 56 require_once( ABSPATH . WPINC . '/IXR/class-IXR-base64.php' ); 160 57 161 /** 162 * Checks whether or not the supplied array is a struct or not 163 * 164 * @param array $array 165 * @return bool 166 */ 167 function isStruct($array) 168 { 169 $expected = 0; 170 foreach ($array as $key => $value) { 171 if ((string)$key !== (string)$expected) { 172 return true; 173 } 174 $expected++; 175 } 176 return false; 177 } 178 } 58 require_once( ABSPATH . WPINC . '/IXR/class-IXR-introspectionserver.php' ); 179 59 180 /** 181 * IXR_MESSAGE 182 * 183 * @package IXR 184 * @since 1.5.0 185 * 186 */ 187 class IXR_Message 188 { 189 var $message; 190 var $messageType; // methodCall / methodResponse / fault 191 var $faultCode; 192 var $faultString; 193 var $methodName; 194 var $params; 195 196 // Current variable stacks 197 var $_arraystructs = array(); // The stack used to keep track of the current array/struct 198 var $_arraystructstypes = array(); // Stack keeping track of if things are structs or array 199 var $_currentStructName = array(); // A stack as well 200 var $_param; 201 var $_value; 202 var $_currentTag; 203 var $_currentTagContents; 204 // The XML parser 205 var $_parser; 206 207 /** 208 * PHP5 constructor. 209 */ 210 function __construct( $message ) 211 { 212 $this->message =& $message; 213 } 214 215 /** 216 * PHP4 constructor. 217 */ 218 public function IXR_Message( $message ) { 219 self::__construct( $message ); 220 } 221 222 function parse() 223 { 224 // first remove the XML declaration 225 // merged from WP #10698 - this method avoids the RAM usage of preg_replace on very large messages 226 $header = preg_replace( '/<\?xml.*?\?'.'>/s', '', substr( $this->message, 0, 100 ), 1 ); 227 $this->message = trim( substr_replace( $this->message, $header, 0, 100 ) ); 228 if ( '' == $this->message ) { 229 return false; 230 } 231 232 // Then remove the DOCTYPE 233 $header = preg_replace( '/^<!DOCTYPE[^>]*+>/i', '', substr( $this->message, 0, 200 ), 1 ); 234 $this->message = trim( substr_replace( $this->message, $header, 0, 200 ) ); 235 if ( '' == $this->message ) { 236 return false; 237 } 238 239 // Check that the root tag is valid 240 $root_tag = substr( $this->message, 0, strcspn( substr( $this->message, 0, 20 ), "> \t\r\n" ) ); 241 if ( '<!DOCTYPE' === strtoupper( $root_tag ) ) { 242 return false; 243 } 244 if ( ! in_array( $root_tag, array( '<methodCall', '<methodResponse', '<fault' ) ) ) { 245 return false; 246 } 247 248 // Bail if there are too many elements to parse 249 $element_limit = 30000; 250 if ( function_exists( 'apply_filters' ) ) { 251 /** 252 * Filters the number of elements to parse in an XML-RPC response. 253 * 254 * @since 4.0.0 255 * 256 * @param int $element_limit Default elements limit. 257 */ 258 $element_limit = apply_filters( 'xmlrpc_element_limit', $element_limit ); 259 } 260 if ( $element_limit && 2 * $element_limit < substr_count( $this->message, '<' ) ) { 261 return false; 262 } 263 264 $this->_parser = xml_parser_create(); 265 // Set XML parser to take the case of tags in to account 266 xml_parser_set_option($this->_parser, XML_OPTION_CASE_FOLDING, false); 267 // Set XML parser callback functions 268 xml_set_object($this->_parser, $this); 269 xml_set_element_handler($this->_parser, 'tag_open', 'tag_close'); 270 xml_set_character_data_handler($this->_parser, 'cdata'); 271 272 // 256Kb, parse in chunks to avoid the RAM usage on very large messages 273 $chunk_size = 262144; 274 275 /** 276 * Filters the chunk size that can be used to parse an XML-RPC reponse message. 277 * 278 * @since 4.4.0 279 * 280 * @param int $chunk_size Chunk size to parse in bytes. 281 */ 282 $chunk_size = apply_filters( 'xmlrpc_chunk_parsing_size', $chunk_size ); 283 284 $final = false; 285 do { 286 if (strlen($this->message) <= $chunk_size) { 287 $final = true; 288 } 289 $part = substr($this->message, 0, $chunk_size); 290 $this->message = substr($this->message, $chunk_size); 291 if (!xml_parse($this->_parser, $part, $final)) { 292 return false; 293 } 294 if ($final) { 295 break; 296 } 297 } while (true); 298 xml_parser_free($this->_parser); 299 300 // Grab the error messages, if any 301 if ($this->messageType == 'fault') { 302 $this->faultCode = $this->params[0]['faultCode']; 303 $this->faultString = $this->params[0]['faultString']; 304 } 305 return true; 306 } 307 308 function tag_open($parser, $tag, $attr) 309 { 310 $this->_currentTagContents = ''; 311 $this->currentTag = $tag; 312 switch($tag) { 313 case 'methodCall': 314 case 'methodResponse': 315 case 'fault': 316 $this->messageType = $tag; 317 break; 318 /* Deal with stacks of arrays and structs */ 319 case 'data': // data is to all intents and puposes more interesting than array 320 $this->_arraystructstypes[] = 'array'; 321 $this->_arraystructs[] = array(); 322 break; 323 case 'struct': 324 $this->_arraystructstypes[] = 'struct'; 325 $this->_arraystructs[] = array(); 326 break; 327 } 328 } 329 330 function cdata($parser, $cdata) 331 { 332 $this->_currentTagContents .= $cdata; 333 } 334 335 function tag_close($parser, $tag) 336 { 337 $valueFlag = false; 338 switch($tag) { 339 case 'int': 340 case 'i4': 341 $value = (int)trim($this->_currentTagContents); 342 $valueFlag = true; 343 break; 344 case 'double': 345 $value = (double)trim($this->_currentTagContents); 346 $valueFlag = true; 347 break; 348 case 'string': 349 $value = (string)trim($this->_currentTagContents); 350 $valueFlag = true; 351 break; 352 case 'dateTime.iso8601': 353 $value = new IXR_Date(trim($this->_currentTagContents)); 354 $valueFlag = true; 355 break; 356 case 'value': 357 // "If no type is indicated, the type is string." 358 if (trim($this->_currentTagContents) != '') { 359 $value = (string)$this->_currentTagContents; 360 $valueFlag = true; 361 } 362 break; 363 case 'boolean': 364 $value = (boolean)trim($this->_currentTagContents); 365 $valueFlag = true; 366 break; 367 case 'base64': 368 $value = base64_decode($this->_currentTagContents); 369 $valueFlag = true; 370 break; 371 /* Deal with stacks of arrays and structs */ 372 case 'data': 373 case 'struct': 374 $value = array_pop($this->_arraystructs); 375 array_pop($this->_arraystructstypes); 376 $valueFlag = true; 377 break; 378 case 'member': 379 array_pop($this->_currentStructName); 380 break; 381 case 'name': 382 $this->_currentStructName[] = trim($this->_currentTagContents); 383 break; 384 case 'methodName': 385 $this->methodName = trim($this->_currentTagContents); 386 break; 387 } 388 389 if ($valueFlag) { 390 if (count($this->_arraystructs) > 0) { 391 // Add value to struct or array 392 if ($this->_arraystructstypes[count($this->_arraystructstypes)-1] == 'struct') { 393 // Add to struct 394 $this->_arraystructs[count($this->_arraystructs)-1][$this->_currentStructName[count($this->_currentStructName)-1]] = $value; 395 } else { 396 // Add to array 397 $this->_arraystructs[count($this->_arraystructs)-1][] = $value; 398 } 399 } else { 400 // Just add as a parameter 401 $this->params[] = $value; 402 } 403 } 404 $this->_currentTagContents = ''; 405 } 406 } 407 408 /** 409 * IXR_Server 410 * 411 * @package IXR 412 * @since 1.5.0 413 */ 414 class IXR_Server 415 { 416 var $data; 417 var $callbacks = array(); 418 var $message; 419 var $capabilities; 420 421 /** 422 * PHP5 constructor. 423 */ 424 function __construct( $callbacks = false, $data = false, $wait = false ) 425 { 426 $this->setCapabilities(); 427 if ($callbacks) { 428 $this->callbacks = $callbacks; 429 } 430 $this->setCallbacks(); 431 if (!$wait) { 432 $this->serve($data); 433 } 434 } 435 436 /** 437 * PHP4 constructor. 438 */ 439 public function IXR_Server( $callbacks = false, $data = false, $wait = false ) { 440 self::__construct( $callbacks, $data, $wait ); 441 } 442 443 function serve($data = false) 444 { 445 if (!$data) { 446 if (isset($_SERVER['REQUEST_METHOD']) && $_SERVER['REQUEST_METHOD'] !== 'POST') { 447 if ( function_exists( 'status_header' ) ) { 448 status_header( 405 ); // WP #20986 449 header( 'Allow: POST' ); 450 } 451 header('Content-Type: text/plain'); // merged from WP #9093 452 die('XML-RPC server accepts POST requests only.'); 453 } 454 455 global $HTTP_RAW_POST_DATA; 456 if (empty($HTTP_RAW_POST_DATA)) { 457 // workaround for a bug in PHP 5.2.2 - http://bugs.php.net/bug.php?id=41293 458 $data = file_get_contents('php://input'); 459 } else { 460 $data =& $HTTP_RAW_POST_DATA; 461 } 462 } 463 $this->message = new IXR_Message($data); 464 if (!$this->message->parse()) { 465 $this->error(-32700, 'parse error. not well formed'); 466 } 467 if ($this->message->messageType != 'methodCall') { 468 $this->error(-32600, 'server error. invalid xml-rpc. not conforming to spec. Request must be a methodCall'); 469 } 470 $result = $this->call($this->message->methodName, $this->message->params); 471 472 // Is the result an error? 473 if (is_a($result, 'IXR_Error')) { 474 $this->error($result); 475 } 476 477 // Encode the result 478 $r = new IXR_Value($result); 479 $resultxml = $r->getXml(); 480 481 // Create the XML 482 $xml = <<<EOD 483 <methodResponse> 484 <params> 485 <param> 486 <value> 487 $resultxml 488 </value> 489 </param> 490 </params> 491 </methodResponse> 492 493 EOD; 494 // Send it 495 $this->output($xml); 496 } 497 498 function call($methodname, $args) 499 { 500 if (!$this->hasMethod($methodname)) { 501 return new IXR_Error(-32601, 'server error. requested method '.$methodname.' does not exist.'); 502 } 503 $method = $this->callbacks[$methodname]; 504 505 // Perform the callback and send the response 506 if (count($args) == 1) { 507 // If only one parameter just send that instead of the whole array 508 $args = $args[0]; 509 } 510 511 // Are we dealing with a function or a method? 512 if (is_string($method) && substr($method, 0, 5) == 'this:') { 513 // It's a class method - check it exists 514 $method = substr($method, 5); 515 if (!method_exists($this, $method)) { 516 return new IXR_Error(-32601, 'server error. requested class method "'.$method.'" does not exist.'); 517 } 518 519 //Call the method 520 $result = $this->$method($args); 521 } else { 522 // It's a function - does it exist? 523 if (is_array($method)) { 524 if (!is_callable(array($method[0], $method[1]))) { 525 return new IXR_Error(-32601, 'server error. requested object method "'.$method[1].'" does not exist.'); 526 } 527 } else if (!function_exists($method)) { 528 return new IXR_Error(-32601, 'server error. requested function "'.$method.'" does not exist.'); 529 } 530 531 // Call the function 532 $result = call_user_func($method, $args); 533 } 534 return $result; 535 } 536 537 function error($error, $message = false) 538 { 539 // Accepts either an error object or an error code and message 540 if ($message && !is_object($error)) { 541 $error = new IXR_Error($error, $message); 542 } 543 $this->output($error->getXml()); 544 } 545 546 function output($xml) 547 { 548 $charset = function_exists('get_option') ? get_option('blog_charset') : ''; 549 if ($charset) 550 $xml = '<?xml version="1.0" encoding="'.$charset.'"?>'."\n".$xml; 551 else 552 $xml = '<?xml version="1.0"?>'."\n".$xml; 553 $length = strlen($xml); 554 header('Connection: close'); 555 if ($charset) 556 header('Content-Type: text/xml; charset='.$charset); 557 else 558 header('Content-Type: text/xml'); 559 header('Date: '.date('r')); 560 echo $xml; 561 exit; 562 } 563 564 function hasMethod($method) 565 { 566 return in_array($method, array_keys($this->callbacks)); 567 } 568 569 function setCapabilities() 570 { 571 // Initialises capabilities array 572 $this->capabilities = array( 573 'xmlrpc' => array( 574 'specUrl' => 'http://www.xmlrpc.com/spec', 575 'specVersion' => 1 576 ), 577 'faults_interop' => array( 578 'specUrl' => 'http://xmlrpc-epi.sourceforge.net/specs/rfc.fault_codes.php', 579 'specVersion' => 20010516 580 ), 581 'system.multicall' => array( 582 'specUrl' => 'http://www.xmlrpc.com/discuss/msgReader$1208', 583 'specVersion' => 1 584 ), 585 ); 586 } 587 588 function getCapabilities($args) 589 { 590 return $this->capabilities; 591 } 592 593 function setCallbacks() 594 { 595 $this->callbacks['system.getCapabilities'] = 'this:getCapabilities'; 596 $this->callbacks['system.listMethods'] = 'this:listMethods'; 597 $this->callbacks['system.multicall'] = 'this:multiCall'; 598 } 599 600 function listMethods($args) 601 { 602 // Returns a list of methods - uses array_reverse to ensure user defined 603 // methods are listed before server defined methods 604 return array_reverse(array_keys($this->callbacks)); 605 } 606 607 function multiCall($methodcalls) 608 { 609 // See http://www.xmlrpc.com/discuss/msgReader$1208 610 $return = array(); 611 foreach ($methodcalls as $call) { 612 $method = $call['methodName']; 613 $params = $call['params']; 614 if ($method == 'system.multicall') { 615 $result = new IXR_Error(-32600, 'Recursive calls to system.multicall are forbidden'); 616 } else { 617 $result = $this->call($method, $params); 618 } 619 if (is_a($result, 'IXR_Error')) { 620 $return[] = array( 621 'faultCode' => $result->code, 622 'faultString' => $result->message 623 ); 624 } else { 625 $return[] = array($result); 626 } 627 } 628 return $return; 629 } 630 } 631 632 /** 633 * IXR_Request 634 * 635 * @package IXR 636 * @since 1.5.0 637 */ 638 class IXR_Request 639 { 640 var $method; 641 var $args; 642 var $xml; 643 644 /** 645 * PHP5 constructor. 646 */ 647 function __construct($method, $args) 648 { 649 $this->method = $method; 650 $this->args = $args; 651 $this->xml = <<<EOD 652 <?xml version="1.0"?> 653 <methodCall> 654 <methodName>{$this->method}</methodName> 655 <params> 656 657 EOD; 658 foreach ($this->args as $arg) { 659 $this->xml .= '<param><value>'; 660 $v = new IXR_Value($arg); 661 $this->xml .= $v->getXml(); 662 $this->xml .= "</value></param>\n"; 663 } 664 $this->xml .= '</params></methodCall>'; 665 } 666 667 /** 668 * PHP4 constructor. 669 */ 670 public function IXR_Request( $method, $args ) { 671 self::__construct( $method, $args ); 672 } 673 674 function getLength() 675 { 676 return strlen($this->xml); 677 } 678 679 function getXml() 680 { 681 return $this->xml; 682 } 683 } 684 685 /** 686 * IXR_Client 687 * 688 * @package IXR 689 * @since 1.5.0 690 * 691 */ 692 class IXR_Client 693 { 694 var $server; 695 var $port; 696 var $path; 697 var $useragent; 698 var $response; 699 var $message = false; 700 var $debug = false; 701 var $timeout; 702 var $headers = array(); 703 704 // Storage place for an error message 705 var $error = false; 706 707 /** 708 * PHP5 constructor. 709 */ 710 function __construct( $server, $path = false, $port = 80, $timeout = 15 ) 711 { 712 if (!$path) { 713 // Assume we have been given a URL instead 714 $bits = parse_url($server); 715 $this->server = $bits['host']; 716 $this->port = isset($bits['port']) ? $bits['port'] : 80; 717 $this->path = isset($bits['path']) ? $bits['path'] : '/'; 718 719 // Make absolutely sure we have a path 720 if (!$this->path) { 721 $this->path = '/'; 722 } 723 724 if ( ! empty( $bits['query'] ) ) { 725 $this->path .= '?' . $bits['query']; 726 } 727 } else { 728 $this->server = $server; 729 $this->path = $path; 730 $this->port = $port; 731 } 732 $this->useragent = 'The Incutio XML-RPC PHP Library'; 733 $this->timeout = $timeout; 734 } 735 736 /** 737 * PHP4 constructor. 738 */ 739 public function IXR_Client( $server, $path = false, $port = 80, $timeout = 15 ) { 740 self::__construct( $server, $path, $port, $timeout ); 741 } 742 743 function query() 744 { 745 $args = func_get_args(); 746 $method = array_shift($args); 747 $request = new IXR_Request($method, $args); 748 $length = $request->getLength(); 749 $xml = $request->getXml(); 750 $r = "\r\n"; 751 $request = "POST {$this->path} HTTP/1.0$r"; 752 753 // Merged from WP #8145 - allow custom headers 754 $this->headers['Host'] = $this->server; 755 $this->headers['Content-Type'] = 'text/xml'; 756 $this->headers['User-Agent'] = $this->useragent; 757 $this->headers['Content-Length']= $length; 758 759 foreach( $this->headers as $header => $value ) { 760 $request .= "{$header}: {$value}{$r}"; 761 } 762 $request .= $r; 763 764 $request .= $xml; 765 766 // Now send the request 767 if ($this->debug) { 768 echo '<pre class="ixr_request">'.htmlspecialchars($request)."\n</pre>\n\n"; 769 } 770 771 if ($this->timeout) { 772 $fp = @fsockopen($this->server, $this->port, $errno, $errstr, $this->timeout); 773 } else { 774 $fp = @fsockopen($this->server, $this->port, $errno, $errstr); 775 } 776 if (!$fp) { 777 $this->error = new IXR_Error(-32300, 'transport error - could not open socket'); 778 return false; 779 } 780 fputs($fp, $request); 781 $contents = ''; 782 $debugContents = ''; 783 $gotFirstLine = false; 784 $gettingHeaders = true; 785 while (!feof($fp)) { 786 $line = fgets($fp, 4096); 787 if (!$gotFirstLine) { 788 // Check line for '200' 789 if (strstr($line, '200') === false) { 790 $this->error = new IXR_Error(-32300, 'transport error - HTTP status code was not 200'); 791 return false; 792 } 793 $gotFirstLine = true; 794 } 795 if (trim($line) == '') { 796 $gettingHeaders = false; 797 } 798 if (!$gettingHeaders) { 799 // merged from WP #12559 - remove trim 800 $contents .= $line; 801 } 802 if ($this->debug) { 803 $debugContents .= $line; 804 } 805 } 806 if ($this->debug) { 807 echo '<pre class="ixr_response">'.htmlspecialchars($debugContents)."\n</pre>\n\n"; 808 } 809 810 // Now parse what we've got back 811 $this->message = new IXR_Message($contents); 812 if (!$this->message->parse()) { 813 // XML error 814 $this->error = new IXR_Error(-32700, 'parse error. not well formed'); 815 return false; 816 } 817 818 // Is the message a fault? 819 if ($this->message->messageType == 'fault') { 820 $this->error = new IXR_Error($this->message->faultCode, $this->message->faultString); 821 return false; 822 } 823 824 // Message must be OK 825 return true; 826 } 827 828 function getResponse() 829 { 830 // methodResponses can only have one param - return that 831 return $this->message->params[0]; 832 } 833 834 function isError() 835 { 836 return (is_object($this->error)); 837 } 838 839 function getErrorCode() 840 { 841 return $this->error->code; 842 } 843 844 function getErrorMessage() 845 { 846 return $this->error->message; 847 } 848 } 849 850 851 /** 852 * IXR_Error 853 * 854 * @package IXR 855 * @since 1.5.0 856 */ 857 class IXR_Error 858 { 859 var $code; 860 var $message; 861 862 /** 863 * PHP5 constructor. 864 */ 865 function __construct( $code, $message ) 866 { 867 $this->code = $code; 868 $this->message = htmlspecialchars($message); 869 } 870 871 /** 872 * PHP4 constructor. 873 */ 874 public function IXR_Error( $code, $message ) { 875 self::__construct( $code, $message ); 876 } 877 878 function getXml() 879 { 880 $xml = <<<EOD 881 <methodResponse> 882 <fault> 883 <value> 884 <struct> 885 <member> 886 <name>faultCode</name> 887 <value><int>{$this->code}</int></value> 888 </member> 889 <member> 890 <name>faultString</name> 891 <value><string>{$this->message}</string></value> 892 </member> 893 </struct> 894 </value> 895 </fault> 896 </methodResponse> 897 898 EOD; 899 return $xml; 900 } 901 } 902 903 /** 904 * IXR_Date 905 * 906 * @package IXR 907 * @since 1.5.0 908 */ 909 class IXR_Date { 910 var $year; 911 var $month; 912 var $day; 913 var $hour; 914 var $minute; 915 var $second; 916 var $timezone; 917 918 /** 919 * PHP5 constructor. 920 */ 921 function __construct( $time ) 922 { 923 // $time can be a PHP timestamp or an ISO one 924 if (is_numeric($time)) { 925 $this->parseTimestamp($time); 926 } else { 927 $this->parseIso($time); 928 } 929 } 930 931 /** 932 * PHP4 constructor. 933 */ 934 public function IXR_Date( $time ) { 935 self::__construct( $time ); 936 } 937 938 function parseTimestamp($timestamp) 939 { 940 $this->year = date('Y', $timestamp); 941 $this->month = date('m', $timestamp); 942 $this->day = date('d', $timestamp); 943 $this->hour = date('H', $timestamp); 944 $this->minute = date('i', $timestamp); 945 $this->second = date('s', $timestamp); 946 $this->timezone = ''; 947 } 948 949 function parseIso($iso) 950 { 951 $this->year = substr($iso, 0, 4); 952 $this->month = substr($iso, 4, 2); 953 $this->day = substr($iso, 6, 2); 954 $this->hour = substr($iso, 9, 2); 955 $this->minute = substr($iso, 12, 2); 956 $this->second = substr($iso, 15, 2); 957 $this->timezone = substr($iso, 17); 958 } 959 960 function getIso() 961 { 962 return $this->year.$this->month.$this->day.'T'.$this->hour.':'.$this->minute.':'.$this->second.$this->timezone; 963 } 964 965 function getXml() 966 { 967 return '<dateTime.iso8601>'.$this->getIso().'</dateTime.iso8601>'; 968 } 969 970 function getTimestamp() 971 { 972 return mktime($this->hour, $this->minute, $this->second, $this->month, $this->day, $this->year); 973 } 974 } 975 976 /** 977 * IXR_Base64 978 * 979 * @package IXR 980 * @since 1.5.0 981 */ 982 class IXR_Base64 983 { 984 var $data; 985 986 /** 987 * PHP5 constructor. 988 */ 989 function __construct( $data ) 990 { 991 $this->data = $data; 992 } 993 994 /** 995 * PHP4 constructor. 996 */ 997 public function IXR_Base64( $data ) { 998 self::__construct( $data ); 999 } 1000 1001 function getXml() 1002 { 1003 return '<base64>'.base64_encode($this->data).'</base64>'; 1004 } 1005 } 1006 1007 /** 1008 * IXR_IntrospectionServer 1009 * 1010 * @package IXR 1011 * @since 1.5.0 1012 */ 1013 class IXR_IntrospectionServer extends IXR_Server 1014 { 1015 var $signatures; 1016 var $help; 1017 1018 /** 1019 * PHP5 constructor. 1020 */ 1021 function __construct() 1022 { 1023 $this->setCallbacks(); 1024 $this->setCapabilities(); 1025 $this->capabilities['introspection'] = array( 1026 'specUrl' => 'http://xmlrpc.usefulinc.com/doc/reserved.html', 1027 'specVersion' => 1 1028 ); 1029 $this->addCallback( 1030 'system.methodSignature', 1031 'this:methodSignature', 1032 array('array', 'string'), 1033 'Returns an array describing the return type and required parameters of a method' 1034 ); 1035 $this->addCallback( 1036 'system.getCapabilities', 1037 'this:getCapabilities', 1038 array('struct'), 1039 'Returns a struct describing the XML-RPC specifications supported by this server' 1040 ); 1041 $this->addCallback( 1042 'system.listMethods', 1043 'this:listMethods', 1044 array('array'), 1045 'Returns an array of available methods on this server' 1046 ); 1047 $this->addCallback( 1048 'system.methodHelp', 1049 'this:methodHelp', 1050 array('string', 'string'), 1051 'Returns a documentation string for the specified method' 1052 ); 1053 } 1054 1055 /** 1056 * PHP4 constructor. 1057 */ 1058 public function IXR_IntrospectionServer() { 1059 self::__construct(); 1060 } 1061 1062 function addCallback($method, $callback, $args, $help) 1063 { 1064 $this->callbacks[$method] = $callback; 1065 $this->signatures[$method] = $args; 1066 $this->help[$method] = $help; 1067 } 1068 1069 function call($methodname, $args) 1070 { 1071 // Make sure it's in an array 1072 if ($args && !is_array($args)) { 1073 $args = array($args); 1074 } 1075 1076 // Over-rides default call method, adds signature check 1077 if (!$this->hasMethod($methodname)) { 1078 return new IXR_Error(-32601, 'server error. requested method "'.$this->message->methodName.'" not specified.'); 1079 } 1080 $method = $this->callbacks[$methodname]; 1081 $signature = $this->signatures[$methodname]; 1082 $returnType = array_shift($signature); 1083 1084 // Check the number of arguments 1085 if (count($args) != count($signature)) { 1086 return new IXR_Error(-32602, 'server error. wrong number of method parameters'); 1087 } 1088 1089 // Check the argument types 1090 $ok = true; 1091 $argsbackup = $args; 1092 for ($i = 0, $j = count($args); $i < $j; $i++) { 1093 $arg = array_shift($args); 1094 $type = array_shift($signature); 1095 switch ($type) { 1096 case 'int': 1097 case 'i4': 1098 if (is_array($arg) || !is_int($arg)) { 1099 $ok = false; 1100 } 1101 break; 1102 case 'base64': 1103 case 'string': 1104 if (!is_string($arg)) { 1105 $ok = false; 1106 } 1107 break; 1108 case 'boolean': 1109 if ($arg !== false && $arg !== true) { 1110 $ok = false; 1111 } 1112 break; 1113 case 'float': 1114 case 'double': 1115 if (!is_float($arg)) { 1116 $ok = false; 1117 } 1118 break; 1119 case 'date': 1120 case 'dateTime.iso8601': 1121 if (!is_a($arg, 'IXR_Date')) { 1122 $ok = false; 1123 } 1124 break; 1125 } 1126 if (!$ok) { 1127 return new IXR_Error(-32602, 'server error. invalid method parameters'); 1128 } 1129 } 1130 // It passed the test - run the "real" method call 1131 return parent::call($methodname, $argsbackup); 1132 } 1133 1134 function methodSignature($method) 1135 { 1136 if (!$this->hasMethod($method)) { 1137 return new IXR_Error(-32601, 'server error. requested method "'.$method.'" not specified.'); 1138 } 1139 // We should be returning an array of types 1140 $types = $this->signatures[$method]; 1141 $return = array(); 1142 foreach ($types as $type) { 1143 switch ($type) { 1144 case 'string': 1145 $return[] = 'string'; 1146 break; 1147 case 'int': 1148 case 'i4': 1149 $return[] = 42; 1150 break; 1151 case 'double': 1152 $return[] = 3.1415; 1153 break; 1154 case 'dateTime.iso8601': 1155 $return[] = new IXR_Date(time()); 1156 break; 1157 case 'boolean': 1158 $return[] = true; 1159 break; 1160 case 'base64': 1161 $return[] = new IXR_Base64('base64'); 1162 break; 1163 case 'array': 1164 $return[] = array('array'); 1165 break; 1166 case 'struct': 1167 $return[] = array('struct' => 'struct'); 1168 break; 1169 } 1170 } 1171 return $return; 1172 } 1173 1174 function methodHelp($method) 1175 { 1176 return $this->help[$method]; 1177 } 1178 } 1179 1180 /** 1181 * IXR_ClientMulticall 1182 * 1183 * @package IXR 1184 * @since 1.5.0 1185 */ 1186 class IXR_ClientMulticall extends IXR_Client 1187 { 1188 var $calls = array(); 1189 1190 /** 1191 * PHP5 constructor. 1192 */ 1193 function __construct( $server, $path = false, $port = 80 ) 1194 { 1195 parent::IXR_Client($server, $path, $port); 1196 $this->useragent = 'The Incutio XML-RPC PHP Library (multicall client)'; 1197 } 1198 1199 /** 1200 * PHP4 constructor. 1201 */ 1202 public function IXR_ClientMulticall( $server, $path = false, $port = 80 ) { 1203 self::__construct( $server, $path, $port ); 1204 } 1205 1206 function addCall() 1207 { 1208 $args = func_get_args(); 1209 $methodName = array_shift($args); 1210 $struct = array( 1211 'methodName' => $methodName, 1212 'params' => $args 1213 ); 1214 $this->calls[] = $struct; 1215 } 1216 1217 function query() 1218 { 1219 // Prepare multicall, then call the parent::query() method 1220 return parent::query('system.multicall', $this->calls); 1221 } 1222 } 60 require_once( ABSPATH . WPINC . '/IXR/class-IXR-clientmulticall.php' );
Note: See TracChangeset
for help on using the changeset viewer.