| Current Path : /home/magalijoj/www/blog/inc/clearbricks/net.xmlrpc/ |
| Current File : /home/magalijoj/www/blog/inc/clearbricks/net.xmlrpc/class.net.xmlrpc.php |
<?php
# ***** BEGIN LICENSE BLOCK *****
# This file is part of Clearbricks.
# Copyright (c) 2007 Olivier Meunier and contributors.
# All rights reserved.
#
# Clearbricks is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# Clearbricks is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Clearbricks; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
# ***** END LICENSE BLOCK *****
class xmlrpcException extends Exception
{
public function __construct($message,$code=0)
{
parent::__construct($message,$code);
}
}
class xmlrpcValue
{
protected $data;
protected $type;
public function __construct($data, $type = false)
{
$this->data = $data;
if (!$type) {
$type = $this->calculateType();
}
$this->type = $type;
if ($type == 'struct') {
# Turn all the values in the array in to new xmlrpcValue objects
foreach ($this->data as $key => $value) {
$this->data[$key] = new xmlrpcValue($value);
}
}
if ($type == 'array') {
for ($i = 0, $j = count($this->data); $i < $j; $i++) {
$this->data[$i] = new xmlrpcValue($this->data[$i]);
}
}
}
public function getXml()
{
# Return XML for this value
switch ($this->type)
{
case 'boolean':
return '<boolean>'.(($this->data) ? '1' : '0').'</boolean>';
break;
case 'int':
return '<int>'.$this->data.'</int>';
break;
case 'double':
return '<double>'.$this->data.'</double>';
break;
case 'string':
return '<string>'.htmlspecialchars($this->data).'</string>';
break;
case 'array':
$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;
}
protected function calculateType()
{
if ($this->data === true || $this->data === false) {
return 'boolean';
}
if (is_integer($this->data)) {
return 'int';
}
if (is_double($this->data)) {
return 'double';
}
# Deal with xmlrpc object types base64 and date
if (is_object($this->data) && $this->data instanceof xmlrpcDate) {
return 'date';
}
if (is_object($this->data) && $this->data instanceof xmlrpcBase64) {
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 ?
if ($this->isStruct($this->data)) {
return 'struct';
} else {
return 'array';
}
}
protected 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) {
return true;
}
$expected++;
}
return false;
}
}
class xmlrpcMessage
{
protected $brutxml;
protected $message;
public $messageType; # methodCall / methodResponse / fault
public $faultCode;
public $faultString;
public $methodName;
public $params;
# Current variable stacks
protected $_arraystructs = array(); # The stack used to keep track of the current array/struct
protected $_arraystructstypes = array(); # Stack keeping track of if things are structs or array
protected $_currentStructName = array(); # A stack as well
protected $_param;
protected $_value;
protected $_currentTag;
protected $_currentTagContents;
# The XML parser
protected $_parser;
public function __construct($message)
{
$this->brutxml = $this->message = $message;
}
public function parse()
{
// first remove the XML declaration
$this->message = preg_replace('/<\?xml(.*)?\?'.'>/', '', $this->message);
if (trim($this->message) == '') {
throw new Exception('XML Parser Error. Empty message');
}
$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');
if (!xml_parse($this->_parser, $this->message))
{
$c = xml_get_error_code($this->_parser);
$e = xml_error_string($c);
$e .= ' on line '.xml_get_current_line_number($this->_parser);
throw new Exception('XML Parser Error. '.$e,$c);
}
xml_parser_free($this->_parser);
# Grab the error messages, if any
if ($this->messageType == 'fault')
{
$this->faultCode = $this->params[0]['faultCode'];
$this->faultString = $this->params[0]['faultString'];
}
return true;
}
protected function tag_open($parser,$tag,$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 puposes more interesting than array
$this->_arraystructstypes[] = 'array';
$this->_arraystructs[] = array();
break;
case 'struct':
$this->_arraystructstypes[] = 'struct';
$this->_arraystructs[] = array();
break;
}
}
protected function cdata($parser,$cdata)
{
$this->_currentTagContents .= $cdata;
}
protected function tag_close($parser,$tag)
{
$valueFlag = false;
switch($tag)
{
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)trim($this->_currentTagContents);
$this->_currentTagContents = '';
$valueFlag = true;
break;
case 'dateTime.iso8601':
$value = new xmlrpcDate(trim($this->_currentTagContents));
# $value = $iso->getTimestamp();
$this->_currentTagContents = '';
$valueFlag = true;
break;
case 'value':
# "If no type is indicated, the type is string."
if (trim($this->_currentTagContents) != '')
{
$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
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);
$this->_currentTagContents = '';
break;
case 'methodName':
$this->methodName = trim($this->_currentTagContents);
$this->_currentTagContents = '';
break;
}
if ($valueFlag)
{
if (count($this->_arraystructs) > 0)
{
# Add value to struct or array
if ($this->_arraystructstypes[count($this->_arraystructstypes)-1] == 'struct') {
# Add to struct
$this->_arraystructs[count($this->_arraystructs)-1][$this->_currentStructName[count($this->_currentStructName)-1]] = $value;
} else {
# Add to array
$this->_arraystructs[count($this->_arraystructs)-1][] = $value;
}
}
else
{
# Just add as a paramater
$this->params[] = $value;
}
}
}
}
class xmlrpcRequest
{
public $method;
public $args;
public $xml;
function __construct($method, $args)
{
$this->method = $method;
$this->args = $args;
$this->xml =
'<?xml version="1.0"?>'."\n".
"<methodCall>\n".
' <methodName>'.$this->method."</methodName>\n".
" <params>\n";
foreach ($this->args as $arg)
{
$this->xml .= ' <param><value>';
$v = new xmlrpcValue($arg);
$this->xml .= $v->getXml();
$this->xml .= "</value></param>\n";
}
$this->xml .= ' </params></methodCall>';
}
public function getLength()
{
return strlen($this->xml);
}
public function getXml()
{
return $this->xml;
}
}
class xmlrpcDate
{
protected $year;
protected $month;
protected $day;
protected $hour;
protected $minute;
protected $second;
public function __construct($time)
{
# $time can be a PHP timestamp or an ISO one
if (is_numeric($time)) {
$this->parseTimestamp($time);
} else {
$this->parseTimestamp(strtotime($time));
}
}
protected function parseTimestamp($timestamp)
{
$this->year = date('Y', $timestamp);
$this->month = date('m', $timestamp);
$this->day = date('d', $timestamp);
$this->hour = date('H', $timestamp);
$this->minute = date('i', $timestamp);
$this->second = date('s', $timestamp);
$this->ts = $timestamp;
}
public function getIso()
{
return $this->year.$this->month.$this->day.'T'.$this->hour.':'.$this->minute.':'.$this->second;
}
public function getXml()
{
return '<dateTime.iso8601>'.$this->getIso().'</dateTime.iso8601>';
}
public function getTimestamp()
{
return mktime($this->hour, $this->minute, $this->second, $this->month, $this->day, $this->year);
}
}
class xmlrpcBase64
{
protected $data;
public function __construct($data)
{
$this->data = $data;
}
public function getXml()
{
return '<base64>'.base64_encode($this->data).'</base64>';
}
}
class xmlrpcClient extends netHttp
{
protected $request;
protected $message;
public function __construct($url)
{
if (!$this->readUrl($url,$ssl,$host,$port,$path,$user,$pass)) {
return false;
}
parent::__construct($host,$port);
$this->useSSL($ssl);
$this->setAuthorization($user,$pass);
$this->path = $path;
$this->user_agent = 'Clearbricks XML/RPC Client';
}
public function query()
{
$args = func_get_args();
$method = array_shift($args);
$this->request = new xmlrpcRequest($method, $args);
$this->doRequest();
if ($this->status != 200) {
throw new Exception('HTTP Error. '.$this->status.' '.$this->status_string);
}
# Now parse what we've got back
$this->message = new xmlrpcMessage($this->content);
$this->message->parse();
# Is the message a fault?
if ($this->message->messageType == 'fault')
{
throw new xmlrpcException($this->message->faultString,$this->message->faultCode);
}
return $this->message->params[0];
}
# Overloading netHttp::buildRequest method, we don't need all the stuff of
# HTTP client.
protected function buildRequest()
{
if ($this->proxy_host) {
$path = $this->getRequestURL();
} else {
$path = $this->path;
}
return array(
'POST '.$path.' HTTP/1.0',
'Host: '.$this->host,
'Content-Type: text/xml',
'User-Agent: '.$this->user_agent,
'Content-Length: '.$this->request->getLength(),
'',
$this->request->getXML()
);
}
}
class xmlrpcClientMulticall extends xmlrpcClient
{
protected $calls = array();
function __construct($url)
{
parent::__construct($url);
}
function addCall()
{
$args = func_get_args();
$methodName = array_shift($args);
$struct = array(
'methodName' => $methodName,
'params' => $args
);
$this->calls[] = $struct;
}
function query()
{
# Prepare multicall, then call the parent::query() method
return parent::query('system.multicall',$this->calls);
}
}
class xmlrpcServer
{
protected $callbacks = array();
protected $data;
protected $encoding;
protected $message;
protected $capabilities;
public $strict_check = false;
public function __construct($callbacks=false,$data=false,$encoding='UTF-8')
{
$this->encoding = $encoding;
$this->setCapabilities();
if ($callbacks) {
$this->callbacks = $callbacks;
}
$this->setCallbacks();
$this->serve($data);
}
public function serve($data=false)
{
if (!$data)
{
try
{
# Check HTTP Method
if ($_SERVER['REQUEST_METHOD'] != 'POST') {
throw new Exception('XML-RPC server accepts POST requests only.',405);
}
# Check HTTP_HOST
if (!isset($_SERVER['HTTP_HOST'])) {
throw new Exception('No Host Specified',400);
}
global $HTTP_RAW_POST_DATA;
if (!$HTTP_RAW_POST_DATA) {
throw new Exception('No Message',400);
}
if ($this->strict_check)
{
# Check USER_AGENT
if (!isset($_SERVER['HTTP_USER_AGENT'])) {
throw new Exception('No User Agent Specified',400);
}
# Check CONTENT_TYPE
if (!isset($_SERVER['CONTENT_TYPE']) || strpos($_SERVER['CONTENT_TYPE'],'text/xml') !== 0) {
throw new Exception('Invalid Content-Type',400);
}
# Check CONTENT_LENGTH
if (!isset($_SERVER['CONTENT_LENGTH']) || $_SERVER['CONTENT_LENGTH'] != strlen($HTTP_RAW_POST_DATA)) {
throw new Exception('Invalid Content-Lenth',400);
}
}
$data = $HTTP_RAW_POST_DATA;
}
catch (Exception $e)
{
if ($e->getCode() == 400) {
$this->head(400,'Bad Request');
} elseif ($e->getCode() == 405) {
$this->head(405,'Method Not Allowed');
header('Allow: POST');
}
header('Content-Type: text/plain');
echo $e->getMessage();
exit;
}
}
$this->message = new xmlrpcMessage($data);
try
{
$this->message->parse();
if ($this->message->messageType != 'methodCall') {
throw new xmlrpcException('Server error. Invalid xml-rpc. not conforming to spec. Request must be a methodCall',-32600);
}
$result = $this->call($this->message->methodName,$this->message->params);
}
catch (Exception $e)
{
$this->error($e);
}
# Encode the result
$r = new xmlrpcValue($result);
$resultxml = $r->getXml();
# Create the XML
$xml =
"<methodResponse>\n".
"<params>\n".
"<param>\n".
" <value>\n".
' '.$resultxml."\n".
" </value>\n".
"</param>\n".
"</params>\n".
"</methodResponse>";
# Send it
$this->output($xml);
}
protected function head($code,$msg)
{
$status_mode = preg_match('/cgi/',php_sapi_name());
if ($status_mode) {
header('Status: '.$code.' '.$msg);
} else {
if (version_compare(phpversion(),'4.3.0','>=')) {
header($msg,true,$code);
} else {
header('HTTP/1.x '.$code.' '.$msg);
}
}
}
protected function call($methodname,$args)
{
if (!$this->hasMethod($methodname)) {
throw new xmlrpcException('server error. requested method "'.$methodname.'" does not exist.',-32601);
}
$method = $this->callbacks[$methodname];
# Perform the callback and send the response
if (!is_callable($method)) {
throw new xmlrpcException('server error. internal requested function for "'.$methodname.'" does not exist.',-32601);
}
return call_user_func_array($method,$args);
}
protected function error($e)
{
$msg = $e->getMessage();
$this->output(
"<methodResponse>\n".
" <fault>\n".
" <value>\n".
" <struct>\n".
" <member>\n".
" <name>faultCode</name>\n".
' <value><int>'.$e->getCode()."</int></value>\n".
" </member>\n".
" <member>\n".
" <name>faultString</name>\n".
' <value><string>'.$msg."</string></value>\n".
" </member>\n".
" </struct>\n".
" </value>\n".
" </fault>\n".
"</methodResponse>\n"
);
}
protected function output($xml)
{
$xml = '<?xml version="1.0" encoding="'.$this->encoding.'"?>'."\n".$xml;
$length = strlen($xml);
header('Connection: close');
header('Content-Length: '.$length);
header('Content-Type: text/xml');
header('Date: '.date('r'));
echo $xml;
exit;
}
protected function hasMethod($method)
{
return in_array($method, array_keys($this->callbacks));
}
protected 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
),
);
}
protected function getCapabilities()
{
return $this->capabilities;
}
protected function setCallbacks()
{
$this->callbacks['system.getCapabilities'] = array($this,'getCapabilities');
$this->callbacks['system.listMethods'] = array($this,'listMethods');
$this->callbacks['system.multicall'] = array($this,'multiCall');
}
protected 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));
}
protected function multiCall($methodcalls)
{
# See http://www.xmlrpc.com/discuss/msgReader$1208
$return = array();
foreach ($methodcalls as $call)
{
$method = $call['methodName'];
$params = $call['params'];
try
{
if ($method == 'system.multicall') {
throw new xmlrpcException('Recursive calls to system.multicall are forbidden',-32600);
}
$result = $this->call($method, $params);
$return[] = array($result);
}
catch (Exception $e)
{
$return[] = array(
'faultCode' => $e->getCode(),
'faultString' => $e->getMessage()
);
}
}
return $return;
}
}
class xmlrpcIntrospectionServer extends xmlrpcServer
{
protected $signatures;
protected $help;
public function __construct($encoding='UTF-8')
{
$this->encoding = $encoding;
$this->setCallbacks();
$this->setCapabilities();
$this->capabilities['introspection'] = array (
'specUrl' => 'http://xmlrpc.usefulinc.com/doc/reserved.html',
'specVersion' => 1
);
$this->addCallback(
'system.methodSignature',
array($this,'methodSignature'),
array('array','string'),
'Returns an array describing the return type and required parameters of a method'
);
$this->addCallback(
'system.getCapabilities',
array($this,'getCapabilities'),
array('struct'),
'Returns a struct describing the XML-RPC specifications supported by this server'
);
$this->addCallback(
'system.listMethods',
array($this,'listMethods'),
array('array'),
'Returns an array of available methods on this server'
);
$this->addCallback(
'system.methodHelp',
array($this,'methodHelp'),
array('string','string'),
'Returns a documentation string for the specified method'
);
$this->addCallback(
'system.multicall',
array($this,'multiCall'),
array('struct','array'),
'Returns result of multiple methods calls'
);
}
protected function addCallback($method, $callback, $args, $help)
{
$this->callbacks[$method] = $callback;
$this->signatures[$method] = $args;
$this->help[$method] = $help;
}
protected function call($methodname,$args)
{
# Make sure it's in an array
if ($args && !is_array($args)) {
$args = array($args);
}
# Over-rides default call method, adds signature check
if (!$this->hasMethod($methodname)) {
throw new xmlrpcException('Server error. Requested method "'.$methodname.'" not specified.',-32601);
}
$method = $this->callbacks[$methodname];
$signature = $this->signatures[$methodname];
if (!is_array($signature)) {
throw new xmlrpcException('Server error. Wrong method signature',-36600);
}
$return_type = array_shift($signature);
# Check the number of arguments
if (count($args) != count($signature)) {
throw new xmlrpcException('Server error. Wrong number of method parameters',-32602);
}
# Check the argument types
if (!$this->checkArgs($args,$signature)) {
throw new xmlrpcException('Server error. Invalid method parameters',-32602);
}
# It passed the test - run the "real" method call
return parent::call($methodname, $args);
}
protected function checkArgs($args,$signature)
{
for ($i = 0, $j = count($args); $i < $j; $i++)
{
$arg = array_shift($args);
$type = array_shift($signature);
switch ($type)
{
case 'int':
case 'i4':
if (is_array($arg) || !is_int($arg)) {
return false;
}
break;
case 'base64':
case 'string':
if (!is_string($arg)) {
return false;
}
break;
case 'boolean':
if ($arg !== false && $arg !== true) {
return false;
}
break;
case 'float':
case 'double':
if (!is_float($arg)) {
return false;
}
break;
case 'date':
case 'dateTime.iso8601':
if (!($arg instanceof xmlrpcDate)) {
return false;
}
break;
}
}
return true;
}
protected function methodSignature($method)
{
if (!$this->hasMethod($method)) {
throw new xmlrpcException('Server error. Requested method "'.$method.'" not specified.',-32601);
}
# We should be returning an array of types
$types = $this->signatures[$method];
$return = array();
foreach ($types as $type)
{
switch ($type)
{
case 'string':
$return[] = 'string';
break;
case 'int':
case 'i4':
$return[] = 42;
break;
case 'double':
$return[] = 3.1415;
break;
case 'dateTime.iso8601':
$return[] = new xmlrpcDate(time());
break;
case 'boolean':
$return[] = true;
break;
case 'base64':
$return[] = new xmlrpcBase64('base64');
break;
case 'array':
$return[] = array('array');
break;
case 'struct':
$return[] = array('struct' => 'struct');
break;
}
}
return $return;
}
protected function methodHelp($method)
{
return $this->help[$method];
}
}
?>