Newer
Older
* 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.
* 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 {
/** @var IXR_Value[]|IXR_Date|IXR_Base64|int|bool|double|string */
function __construct($data, $type = false) {
$type = $this->calculateType();
}
$this->type = $type;
// Turn all the values in the array in to new IXR_Value objects
$this->data[$key] = new IXR_Value($value);
}
}
if($type == 'array') {
for($i = 0, $j = count($this->data); $i < $j; $i++) {
$this->data[$i] = new IXR_Value($this->data[$i]);
}
}
}
if($this->data === true || $this->data === false) {
if(is_object($this->data) && is_a($this->data, 'IXR_Date')) {
if(is_object($this->data) && is_a($this->data, 'IXR_Base64')) {
// If it is a normal PHP object convert it in to a struct
$this->data = get_object_vars($this->data);
return 'struct';
}
// We have an array - is it an array or a struct?
return 'struct';
} else {
return 'array';
}
}
// Return XML for this value
return '<boolean>' . (($this->data) ? '1' : '0') . '</boolean>';
return '<double>' . $this->data . '</double>';
return '<string>' . htmlspecialchars($this->data) . '</string>';
$return = '<array><data>' . "\n";
foreach($this->data as $item) {
$return .= ' <value>' . $item->getXml() . "</value>\n";
}
$return .= '</data></array>';
return $return;
break;
case 'struct':
$return = '<struct>' . "\n";
foreach($this->data as $name => $value) {
$return .= " <member><name>$name</name><value>";
$return .= $value->getXml() . "</value></member>\n";
}
$return .= '</struct>';
return $return;
break;
case 'date':
case 'base64':
return $this->data->getXml();
break;
}
return false;
}
* Checks whether or not the supplied array is a struct or not
*
function isStruct($array) {
$expected = 0;
foreach($array as $key => $value) {
if((string) $key != (string) $expected) {
return true;
}
$expected++;
}
return false;
}
}
* IXR_MESSAGE
*
* @package IXR
* @since 1.5
*
var $messageType; // methodCall / methodResponse / fault
var $faultCode;
var $faultString;
var $methodName;
var $params;
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
var $_currentStructName = array(); // A stack as well
var $_param;
var $_value;
var $_currentTag;
var $_currentTagContents;
/**
* @param string $message
*/
$this->message =& $message;
function parse() {
// first remove the XML declaration
// 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);
$this->message = str_replace('&', '&', $this->message);
$this->message = str_replace(''', ''', $this->message);
$this->message = str_replace('"', '"', $this->message);
$this->message = str_replace("\x0b", ' ', $this->message); //vertical tab
return false;
}
$this->_parser = xml_parser_create();
// Set XML parser to take the case of tags in to account
xml_parser_set_option($this->_parser, XML_OPTION_CASE_FOLDING, false);
// Set XML parser callback functions
xml_set_object($this->_parser, $this);
xml_set_element_handler($this->_parser, 'tag_open', 'tag_close');
xml_set_character_data_handler($this->_parser, 'cdata');
$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);
$this->faultCode = $this->params[0]['faultCode'];
$this->faultString = $this->params[0]['faultString'];
}
return true;
}
/**
* @param $parser
* @param string $tag
* @param $attr
*/
$this->_currentTag = $tag;
switch($tag) {
case 'methodCall':
case 'methodResponse':
case 'fault':
$this->messageType = $tag;
break;
/* Deal with stacks of arrays and structs */
case 'data': // data is to all intents and purposes more interesting than array
$this->_arraystructstypes[] = 'array';
$this->_arraystructs[] = array();
break;
case 'struct':
$this->_arraystructstypes[] = 'struct';
$this->_arraystructs[] = array();
break;
}
function cdata($parser, $cdata) {
$this->_currentTagContents .= $cdata;
}
$valueFlag = false;
switch($tag) {
case 'int':
case 'i4':
$value = (int) trim($this->_currentTagContents);
$valueFlag = true;
break;
case 'double':
$value = (double) trim($this->_currentTagContents);
$valueFlag = true;
break;
case 'string':
$valueFlag = true;
break;
case 'dateTime.iso8601':
$value = new IXR_Date(trim($this->_currentTagContents));
$valueFlag = true;
break;
case 'value':
// "If no type is indicated, the type is string."
if($this->_lastseen == 'value') {
$value = (string) $this->_currentTagContents;
$value = (boolean) trim($this->_currentTagContents);
$valueFlag = true;
break;
case 'base64':
$value = base64_decode($this->_currentTagContents);
$valueFlag = true;
break;
/* Deal with stacks of arrays and structs */
case 'data':
case 'struct':
$value = array_pop($this->_arraystructs);
array_pop($this->_arraystructstypes);
$valueFlag = true;
break;
case 'member':
array_pop($this->_currentStructName);
break;
case 'name':
$this->_currentStructName[] = trim($this->_currentTagContents);
break;
case 'methodName':
$this->methodName = trim($this->_currentTagContents);
break;
}
if($valueFlag) {
if(count($this->_arraystructs) > 0) {
if($this->_arraystructstypes[count($this->_arraystructstypes) - 1] == 'struct') {
$this->_arraystructs[count($this->_arraystructs) - 1][$this->_currentStructName[count($this->_currentStructName) - 1]] = $value;
$this->_arraystructs[count($this->_arraystructs) - 1][] = $value;
// Just add as a parameter
$this->_currentTagContents = '';
* IXR_Server
*
* @package IXR
* @since 1.5
/**
* @param array|bool $callbacks
* @param bool $data
* @param bool $wait
function __construct($callbacks = false, $data = false, $wait = false) {
$this->callbacks = $callbacks;
}
$this->setCallbacks();
if(!$wait) {
$this->serve($data);
}
$postData = trim(http_get_raw_post_data());
header('Content-Type: text/plain'); // merged from WP #9093
die('XML-RPC server accepts POST requests only.');
$data = $postData;
}
$this->message = new IXR_Message($data);
$this->error(-32700, 'parse error. not well formed');
}
if($this->message->messageType != 'methodCall') {
$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);
// Encode the result
$r = new IXR_Value($result);
$resultxml = $r->getXml();
// Create the XML
$xml = <<<EOD
<methodResponse>
<params>
<param>
<value>
$resultxml
</value>
</param>
</params>
</methodResponse>
EOD;
// Send it
$this->output($xml);
}
if(!$this->hasMethod($methodname)) {
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 parameter just send that instead of the whole array
# Adjusted for DokuWiki to use call_user_func_array
// args need to be an array
$args = (array) $args;
// Are we dealing with a function or a method?
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)) {
return new IXR_Error(-32601, 'server error. requested class method "' . $method . '" does not exist.');
}
// Call the method
#$result = $this->$method($args);
$result = call_user_func_array(array(&$this, $method), $args);
} elseif(substr($method, 0, 7) == 'plugin:') {
list($pluginname, $callback) = explode(':', substr($method, 7), 2);
if(!plugin_isdisabled($pluginname)) {
$plugin = plugin_load('action', $pluginname);
return call_user_func_array(array($plugin, $callback), $args);
} else {
return new IXR_Error(-99999, 'server error');
}
} else {
// It's a function - does it exist?
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.');
$result = call_user_func($method, $args);
/**
* @param int $error
* @param string|bool $message
*/
function error($error, $message = false) {
// Accepts either an error object or an error code and message
$error = new IXR_Error($error, $message);
}
$this->output($error->getXml());
}
* @param string $xml
header('Content-Type: text/xml; charset=utf-8');
echo '<?xml version="1.0"?>', "\n", $xml;
function hasMethod($method) {
return in_array($method, array_keys($this->callbacks));
}
function setCapabilities() {
// Initialises capabilities array
$this->capabilities = array(
'xmlrpc' => array(
'specUrl' => 'http://www.xmlrpc.com/spec',
'specVersion' => 1
),
'faults_interop' => array(
'specUrl' => 'http://xmlrpc-epi.sourceforge.net/specs/rfc.fault_codes.php',
'specVersion' => 20010516
),
'system.multicall' => array(
'specUrl' => 'http://www.xmlrpc.com/discuss/msgReader$1208',
'specVersion' => 1
),
);
}
function getCapabilities() {
return $this->capabilities;
}
function setCallbacks() {
$this->callbacks['system.getCapabilities'] = 'this:getCapabilities';
$this->callbacks['system.listMethods'] = 'this:listMethods';
$this->callbacks['system.multicall'] = 'this:multiCall';
}
function listMethods() {
// Returns a list of methods - uses array_reverse to ensure user defined
// methods are listed before server defined methods
return array_reverse(array_keys($this->callbacks));
}
/**
* @param array $methodcalls
* @return array
*/
function multiCall($methodcalls) {
// See http://www.xmlrpc.com/discuss/msgReader$1208
$return = array();
$method = $call['methodName'];
$params = $call['params'];
$result = new IXR_Error(-32800, 'Recursive calls to system.multicall are forbidden');
} else {
$result = $this->call($method, $params);
}
$return[] = array(
'faultCode' => $result->code,
'faultString' => $result->message
);
} else {
$return[] = array($result);
}
}
return $return;
}
}
* IXR_Request
*
* @package IXR
* @since 1.5
function __construct($method, $args) {
$this->method = $method;
$this->args = $args;
$this->xml = <<<EOD
<?xml version="1.0"?>
<methodCall>
<methodName>{$this->method}</methodName>
<params>
EOD;
$this->xml .= '<param><value>';
$v = new IXR_Value($arg);
$this->xml .= $v->getXml();
$this->xml .= "</value></param>\n";
}
$this->xml .= '</params></methodCall>';
}
function getLength() {
return strlen($this->xml);
}
function getXml() {
return $this->xml;
}
}
* 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
* HTTP client library which will respect proxy settings
*
* Because the XMLRPC client is not used in DokuWiki currently this is completely
* untested
*/
class IXR_Client extends DokuHTTPClient {
var $posturl = '';
// Storage place for an error message
/**
* @param string $server
* @param string|bool $path
* @param int $port
* @param int $timeout
function __construct($server, $path = false, $port = 80, $timeout = 15) {
} else {
$this->posturl = 'http://' . $server . ':' . $port . $path;
$this->timeout = $timeout;
/**
* parameters: method and arguments
* @return bool success or error
*/
function query() {
$args = func_get_args();
$method = array_shift($args);
$request = new IXR_Request($method, $args);
$xml = $request->getXml();
$this->headers['Content-Type'] = 'text/xml';
if(!$this->sendRequest($this->posturl, $xml, 'POST')) {
$this->xmlerror = new IXR_Error(-32300, 'transport error - ' . $this->error);
if($this->status < 200 || $this->status > 206) {
$this->xmlerror = new IXR_Error(-32300, 'transport error - HTTP status ' . $this->status);
$this->message = new IXR_Message($this->resp_body);
$this->xmlerror = new IXR_Error(-32700, 'parse error. not well formed');
$this->xmlerror = new IXR_Error($this->message->faultCode, $this->message->faultString);
function getResponse() {
// methodResponses can only have one param - return that
return $this->message->params[0];
}
return (is_object($this->xmlerror));
* IXR_Error
*
* @package IXR
* @since 1.5
class IXR_Error {
var $code;
var $message;
/**
* @param int $code
* @param string $message
*/
function __construct($code, $message) {
$this->message = htmlspecialchars($message);
function getXml() {
$xml = <<<EOD
<methodResponse>
<fault>
<value>
<struct>
<member>
<name>faultCode</name>
<value><int>{$this->code}</int></value>
</member>
<member>
<name>faultString</name>
<value><string>{$this->message}</string></value>
</member>
</struct>
</value>
</fault>
</methodResponse>
EOD;
return $xml;
}
}
* IXR_Date
*
* @package IXR
* @since 1.5
/** @var DateTime */
protected $date;
public function __construct($time) {
// $time can be a PHP timestamp or an ISO one
$this->parseTimestamp($time);
} else {
$this->parseIso($time);
}
}
protected function parseTimestamp($timestamp) {
$this->date = new DateTime('@' . $timestamp);
* Parses less or more complete iso dates and much more, if no timezone given assumes UTC
*
protected function parseIso($iso) {
$this->date = new DateTime($iso, new DateTimeZone("UTC"));
* Returns date in ISO 8601 format
*
public function getIso() {
return $this->date->format(DateTime::ISO8601);
* Returns date in valid xml
*
return '<dateTime.iso8601>' . $this->getIso() . '</dateTime.iso8601>';
return $this->date->getTimestamp();
* IXR_Base64
*
* @package IXR
* @since 1.5
return '<base64>' . base64_encode($this->data) . '</base64>';
* IXR_IntrospectionServer
*
* @package IXR
* @since 1.5
class IXR_IntrospectionServer extends IXR_Server {
var $signatures;
/** @var string[] */
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
$this->setCallbacks();
$this->setCapabilities();
$this->capabilities['introspection'] = array(
'specUrl' => 'http://xmlrpc.usefulinc.com/doc/reserved.html',
'specVersion' => 1
);
$this->addCallback(
'system.methodSignature',
'this:methodSignature',
array('array', 'string'),
'Returns an array describing the return type and required parameters of a method'
);
$this->addCallback(
'system.getCapabilities',
'this:getCapabilities',
array('struct'),
'Returns a struct describing the XML-RPC specifications supported by this server'
);
$this->addCallback(
'system.listMethods',
'this:listMethods',
array('array'),
'Returns an array of available methods on this server'
);
$this->addCallback(
'system.methodHelp',
'this:methodHelp',
array('string', 'string'),
'Returns a documentation string for the specified method'
);
}
* @param string $method
* @param string $callback
* @param string[] $args
function addCallback($method, $callback, $args, $help) {
$this->callbacks[$method] = $callback;
$this->signatures[$method] = $args;
$this->help[$method] = $help;
}
/**
* @param string $methodname
* @param array $args
* @return IXR_Error|mixed
*/
function call($methodname, $args) {
// Make sure it's in an array
// 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.');
}
$method = $this->callbacks[$methodname];
$signature = $this->signatures[$methodname];
$returnType = array_shift($signature);
// Check the number of arguments. Check only, if the minimum count of parameters is specified. More parameters are possible.
// This is a hack to allow optional parameters...
// 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;
for($i = 0, $j = count($args); $i < $j; $i++) {
$arg = array_shift($args);
$type = array_shift($signature);