diff --git a/inc/IXR_Library.php b/inc/IXR_Library.php index 699f8baa82ffa4cc260490ee44879aae895cf87c..8392986806e171b1122b9b4016ea5133465b671f 100644 --- a/inc/IXR_Library.php +++ b/inc/IXR_Library.php @@ -1,21 +1,46 @@ <?php + /** - * IXR - The Inutio XML-RPC Library - (c) Incutio Ltd 2002 + * IXR - The Incutio XML-RPC Library + * + * Copyright (c) 2010, Incutio Ltd. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * - Neither the name of Incutio Ltd. nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. * - * @version 1.61 - * @author Simon Willison - * @date 11th July 2003 - * @link http://scripts.incutio.com/xmlrpc/ - * @link http://scripts.incutio.com/xmlrpc/manual.php - * @license Artistic License http://www.opensource.org/licenses/artistic-license.php + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE + * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * @package IXR + * @since 1.5 + * + * @copyright Incutio Ltd 2010 (http://www.incutio.com) + * @version 1.7.4 7th September 2010 + * @author Simon Willison + * @link http://scripts.incutio.com/xmlrpc/ Site/manual * * Modified for DokuWiki * @author Andreas Gohr <andi@splitbrain.org> */ - -/** - * Class IXR_Value - */ class IXR_Value { /** @var IXR_Value[]|IXR_Date|IXR_Base64|int|bool|double|string */ @@ -34,7 +59,7 @@ class IXR_Value { } $this->type = $type; if($type == 'struct') { - /* Turn all the values in the array in to new IXR_Value objects */ + // Turn all the values in the array in to new IXR_Value objects foreach($this->data as $key => $value) { $this->data[$key] = new IXR_Value($value); } @@ -59,6 +84,7 @@ class IXR_Value { if(is_double($this->data)) { return 'double'; } + // Deal with IXR object types base64 and date if(is_object($this->data) && is_a($this->data, 'IXR_Date')) { return 'date'; @@ -66,16 +92,17 @@ class IXR_Value { if(is_object($this->data) && is_a($this->data, 'IXR_Base64')) { return 'base64'; } + // If it is a normal PHP object convert it in to a struct if(is_object($this->data)) { - $this->data = get_object_vars($this->data); return 'struct'; } if(!is_array($this->data)) { return 'string'; } - /* We have an array - is it an array or a struct ? */ + + // We have an array - is it an array or a struct? if($this->isStruct($this->data)) { return 'struct'; } else { @@ -87,7 +114,7 @@ class IXR_Value { * @return bool|string */ function getXml() { - /* Return XML for this value */ + // Return XML for this value switch($this->type) { case 'boolean': return '<boolean>' . (($this->data) ? '1' : '0') . '</boolean>'; @@ -127,11 +154,12 @@ class IXR_Value { } /** + * Checks whether or not the supplied array is a struct or not + * * @param array $array - * @return bool + * @return boolean */ function isStruct($array) { - /* Nasty function to check if an array is a struct or not */ $expected = 0; foreach($array as $key => $value) { if((string) $key != (string) $expected) { @@ -144,7 +172,11 @@ class IXR_Value { } /** - * Class IXR_Message + * IXR_MESSAGE + * + * @package IXR + * @since 1.5 + * */ class IXR_Message { var $message; @@ -153,6 +185,7 @@ class IXR_Message { var $faultString; var $methodName; var $params; + // Current variable stacks var $_arraystructs = array(); // The stack used to keep track of the current array/struct var $_arraystructstypes = array(); // Stack keeping track of if things are structs or array @@ -169,7 +202,7 @@ class IXR_Message { * @param string $message */ function IXR_Message($message) { - $this->message = $message; + $this->message =& $message; } /** @@ -177,7 +210,10 @@ class IXR_Message { */ function parse() { // first remove the XML declaration - $this->message = preg_replace('/<\?xml(.*)?\?' . '>/', '', $this->message); + // merged from WP #10698 - this method avoids the RAM usage of preg_replace on very large messages + $header = preg_replace('/<\?xml.*?\?' . '>/', '', substr($this->message, 0, 100), 1); + $this->message = substr_replace($this->message, $header, 0, 100); + // workaround for a bug in PHP/libxml2, see http://bugs.php.net/bug.php?id=45996 $this->message = str_replace('<', '<', $this->message); $this->message = str_replace('>', '>', $this->message); @@ -195,13 +231,23 @@ class IXR_Message { xml_set_object($this->_parser, $this); xml_set_element_handler($this->_parser, 'tag_open', 'tag_close'); xml_set_character_data_handler($this->_parser, 'cdata'); - if(!xml_parse($this->_parser, $this->message)) { - /* die(sprintf('XML error: %s at line %d', - xml_error_string(xml_get_error_code($this->_parser)), - xml_get_current_line_number($this->_parser))); */ - return false; - } + $chunk_size = 262144; // 256Kb, parse in chunks to avoid the RAM usage on very large messages + $final = false; + do { + if(strlen($this->message) <= $chunk_size) { + $final = true; + } + $part = substr($this->message, 0, $chunk_size); + $this->message = substr($this->message, $chunk_size); + if(!xml_parse($this->_parser, $part, $final)) { + return false; + } + if($final) { + break; + } + } while(true); xml_parser_free($this->_parser); + // Grab the error messages, if any if($this->messageType == 'fault') { $this->faultCode = $this->params[0]['faultCode']; @@ -216,8 +262,9 @@ class IXR_Message { * @param $attr */ function tag_open($parser, $tag, $attr) { - $this->_currentTag = $tag; $this->_currentTagContents = ''; + $this->_currentTag = $tag; + switch($tag) { case 'methodCall': case 'methodResponse': @@ -225,7 +272,7 @@ class IXR_Message { $this->messageType = $tag; break; /* Deal with stacks of arrays and structs */ - case 'data': // data is to all intents and puposes more interesting than array + case 'data': // data is to all intents and purposes more interesting than array $this->_arraystructstypes[] = 'array'; $this->_arraystructs[] = array(); break; @@ -255,41 +302,33 @@ class IXR_Message { case 'int': case 'i4': $value = (int) trim($this->_currentTagContents); - $this->_currentTagContents = ''; $valueFlag = true; break; case 'double': $value = (double) trim($this->_currentTagContents); - $this->_currentTagContents = ''; $valueFlag = true; break; case 'string': $value = (string) $this->_currentTagContents; - $this->_currentTagContents = ''; $valueFlag = true; break; case 'dateTime.iso8601': $value = new IXR_Date(trim($this->_currentTagContents)); - // $value = $iso->getTimestamp(); - $this->_currentTagContents = ''; $valueFlag = true; break; case 'value': // "If no type is indicated, the type is string." if($this->_lastseen == 'value') { $value = (string) $this->_currentTagContents; - $this->_currentTagContents = ''; $valueFlag = true; } break; case 'boolean': $value = (boolean) trim($this->_currentTagContents); - $this->_currentTagContents = ''; $valueFlag = true; break; case 'base64': $value = base64_decode($this->_currentTagContents); - $this->_currentTagContents = ''; $valueFlag = true; break; /* Deal with stacks of arrays and structs */ @@ -304,19 +343,13 @@ class IXR_Message { break; case 'name': $this->_currentStructName[] = trim($this->_currentTagContents); - $this->_currentTagContents = ''; break; case 'methodName': $this->methodName = trim($this->_currentTagContents); - $this->_currentTagContents = ''; break; } + if($valueFlag) { - /* - if (!is_array($value) && !is_object($value)) { - $value = trim($value); - } - */ if(count($this->_arraystructs) > 0) { // Add value to struct or array if($this->_arraystructstypes[count($this->_arraystructstypes) - 1] == 'struct') { @@ -327,16 +360,20 @@ class IXR_Message { $this->_arraystructs[count($this->_arraystructs) - 1][] = $value; } } else { - // Just add as a paramater + // Just add as a parameter $this->params[] = $value; } } + $this->_currentTagContents = ''; $this->_lastseen = $tag; } } /** - * Class IXR_Server + * IXR_Server + * + * @package IXR + * @since 1.5 */ class IXR_Server { var $data; @@ -349,14 +386,18 @@ class IXR_Server { /** * @param array|bool $callbacks * @param bool $data + * @param bool $wait */ - function IXR_Server($callbacks = false, $data = false) { + function IXR_Server($callbacks = false, $data = false, $wait = false) { $this->setCapabilities(); if($callbacks) { $this->callbacks = $callbacks; } $this->setCallbacks(); - $this->serve($data); + + if(!$wait) { + $this->serve($data); + } } /** @@ -367,6 +408,7 @@ class IXR_Server { $postData = trim(http_get_raw_post_data()); if(!$postData) { + header('Content-Type: text/plain'); // merged from WP #9093 die('XML-RPC server accepts POST requests only.'); } $data = $postData; @@ -379,13 +421,16 @@ class IXR_Server { $this->error(-32600, 'server error. invalid xml-rpc. not conforming to spec. Request must be a methodCall'); } $result = $this->call($this->message->methodName, $this->message->params); + // Is the result an error? if(is_a($result, 'IXR_Error')) { $this->error($result); } + // Encode the result $r = new IXR_Value($result); $resultxml = $r->getXml(); + // Create the XML $xml = <<<EOD <methodResponse> @@ -413,11 +458,12 @@ EOD; return new IXR_Error(-32601, 'server error. requested method ' . $methodname . ' does not exist.'); } $method = $this->callbacks[$methodname]; + // Perform the callback and send the response # Removed for DokuWiki to have a more consistent interface # if (count($args) == 1) { - # // If only one paramater just send that instead of the whole array + # // If only one parameter just send that instead of the whole array # $args = $args[0]; # } @@ -427,7 +473,7 @@ EOD; $args = (array) $args; // Are we dealing with a function or a method? - if(substr($method, 0, 5) == 'this:') { + if(is_string($method) && substr($method, 0, 5) == 'this:') { // It's a class method - check it exists $method = substr($method, 5); if(!method_exists($this, $method)) { @@ -446,12 +492,16 @@ EOD; } } else { // It's a function - does it exist? - if(!function_exists($method)) { + if(is_array($method)) { + if(!is_callable(array($method[0], $method[1]))) { + return new IXR_Error(-32601, 'server error. requested object method "' . $method[1] . '" does not exist.'); + } + } else if(!function_exists($method)) { return new IXR_Error(-32601, 'server error. requested function "' . $method . '" does not exist.'); } + // Call the function - #$result = $method($args); - $result = call_user_func_array($method, $args); + $result = call_user_func($method, $args); } return $result; } @@ -469,7 +519,7 @@ EOD; } /** - * @param $xml + * @param string $xml */ function output($xml) { header('Content-Type: text/xml; charset=utf-8'); @@ -554,7 +604,10 @@ EOD; } /** - * Class IXR_Request + * IXR_Request + * + * @package IXR + * @since 1.5 */ class IXR_Request { /** @var string */ @@ -603,6 +656,11 @@ EOD; } /** + * IXR_Client + * + * @package IXR + * @since 1.5 + * * Changed for DokuWiki to use DokuHTTPClient * * This should be compatible to the original class, but uses DokuWiki's @@ -615,6 +673,8 @@ class IXR_Client extends DokuHTTPClient { var $posturl = ''; /** @var IXR_Message|bool */ var $message = false; + + // Storage place for an error message /** @var IXR_Error|bool */ var $xmlerror = false; @@ -622,8 +682,9 @@ class IXR_Client extends DokuHTTPClient { * @param string $server * @param string|bool $path * @param int $port + * @param int $timeout */ - function IXR_Client($server, $path = false, $port = 80) { + function IXR_Client($server, $path = false, $port = 80, $timeout = 15) { parent::__construct(); if(!$path) { // Assume we have been given a URL instead @@ -631,6 +692,7 @@ class IXR_Client extends DokuHTTPClient { } else { $this->posturl = 'http://' . $server . ':' . $port . $path; } + $this->timeout = $timeout; } /** @@ -662,11 +724,13 @@ class IXR_Client extends DokuHTTPClient { $this->xmlerror = new IXR_Error(-32700, 'parse error. not well formed'); return false; } + // Is the message a fault? if($this->message->messageType == 'fault') { $this->xmlerror = new IXR_Error($this->message->faultCode, $this->message->faultString); return false; } + // Message must be OK return true; } @@ -702,7 +766,10 @@ class IXR_Client extends DokuHTTPClient { } /** - * Class IXR_Error + * IXR_Error + * + * @package IXR + * @since 1.5 */ class IXR_Error { var $code; @@ -714,7 +781,7 @@ class IXR_Error { */ function IXR_Error($code, $message) { $this->code = $code; - $this->message = $message; + $this->message = htmlspecialchars($message); } /** @@ -745,7 +812,10 @@ EOD; } /** - * Class IXR_Date + * IXR_Date + * + * @package IXR + * @since 1.5 */ class IXR_Date { var $year; @@ -754,6 +824,7 @@ class IXR_Date { var $hour; var $minute; var $second; + var $timezone; /** * @param int|string $time @@ -777,6 +848,7 @@ class IXR_Date { $this->hour = gmdate('H', $timestamp); $this->minute = gmdate('i', $timestamp); $this->second = gmdate('s', $timestamp); + $this->timezone = ''; } /** @@ -797,7 +869,7 @@ class IXR_Date { * @return string */ function getIso() { - return $this->year . $this->month . $this->day . 'T' . $this->hour . ':' . $this->minute . ':' . $this->second; + return $this->year . $this->month . $this->day . 'T' . $this->hour . ':' . $this->minute . ':' . $this->second . $this->timezone; } /** @@ -816,7 +888,10 @@ class IXR_Date { } /** - * Class IXR_Base64 + * IXR_Base64 + * + * @package IXR + * @since 1.5 */ class IXR_Base64 { var $data; @@ -837,12 +912,15 @@ class IXR_Base64 { } /** - * Class IXR_IntrospectionServer + * IXR_IntrospectionServer + * + * @package IXR + * @since 1.5 */ class IXR_IntrospectionServer extends IXR_Server { /** @var array[] */ - var $signatures/** @var string[] */ - ; + var $signatures; + /** @var string[] */ var $help; function IXR_IntrospectionServer() { @@ -900,6 +978,7 @@ class IXR_IntrospectionServer extends IXR_Server { if($args && !is_array($args)) { $args = array($args); } + // Over-rides default call method, adds signature check if(!$this->hasMethod($methodname)) { return new IXR_Error(-32601, 'server error. requested method "' . $this->message->methodName . '" not specified.'); @@ -913,6 +992,7 @@ class IXR_IntrospectionServer extends IXR_Server { // print 'Num of args: '.count($args).' Num in signature: '.count($signature); return new IXR_Error(-32602, 'server error. wrong number of method parameters'); } + // Check the argument types $ok = true; $argsbackup = $args; @@ -1011,7 +1091,10 @@ class IXR_IntrospectionServer extends IXR_Server { } /** - * Class IXR_ClientMulticall + * IXR_ClientMulticall + * + * @package IXR + * @since 1.5 */ class IXR_ClientMulticall extends IXR_Client {