torrentpier-lts/library/Zend/XmlRpc/Server.php

610 lines
17 KiB
PHP
Raw Normal View History

<?php
/**
* Zend Framework (http://framework.zend.com/)
*
* @link http://github.com/zendframework/zf2 for the canonical source repository
* @copyright Copyright (c) 2005-2014 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
*/
namespace Zend\XmlRpc;
use ReflectionClass;
use Zend\Server\AbstractServer;
use Zend\Server\Definition;
use Zend\Server\Reflection;
/**
* An XML-RPC server implementation
*
* Example:
* <code>
* use Zend\XmlRpc;
*
* // Instantiate server
* $server = new XmlRpc\Server();
*
* // Allow some exceptions to report as fault responses:
* XmlRpc\Server\Fault::attachFaultException('My\\Exception');
* XmlRpc\Server\Fault::attachObserver('My\\Fault\\Observer');
*
* // Get or build dispatch table:
* if (!XmlRpc\Server\Cache::get($filename, $server)) {
*
* // Attach Some_Service_Class in 'some' namespace
* $server->setClass('Some\\Service\\Class', 'some');
*
* // Attach Another_Service_Class in 'another' namespace
* $server->setClass('Another\\Service\\Class', 'another');
*
* // Create dispatch table cache file
* XmlRpc\Server\Cache::save($filename, $server);
* }
*
* $response = $server->handle();
* echo $response;
* </code>
*/
class Server extends AbstractServer
{
/**
* Character encoding
* @var string
*/
protected $encoding = 'UTF-8';
/**
* Request processed
* @var null|Request
*/
protected $request = null;
/**
* Class to use for responses; defaults to {@link Response\Http}
* @var string
*/
protected $responseClass = 'Zend\XmlRpc\Response\Http';
/**
* Dispatch table of name => method pairs
* @var Definition
*/
protected $table;
/**
* PHP types => XML-RPC types
* @var array
*/
protected $typeMap = array(
'i4' => 'i4',
'int' => 'int',
'integer' => 'int',
'i8' => 'i8',
'ex:i8' => 'i8',
'double' => 'double',
'float' => 'double',
'real' => 'double',
'boolean' => 'boolean',
'bool' => 'boolean',
'true' => 'boolean',
'false' => 'boolean',
'string' => 'string',
'str' => 'string',
'base64' => 'base64',
'dateTime.iso8601' => 'dateTime.iso8601',
'date' => 'dateTime.iso8601',
'time' => 'dateTime.iso8601',
'DateTime' => 'dateTime.iso8601',
'array' => 'array',
'struct' => 'struct',
'null' => 'nil',
'nil' => 'nil',
'ex:nil' => 'nil',
'void' => 'void',
'mixed' => 'struct',
);
/**
* Send arguments to all methods or just constructor?
*
* @var bool
*/
protected $sendArgumentsToAllMethods = true;
/**
* Flag: whether or not {@link handle()} should return a response instead
* of automatically emitting it.
* @var bool
*/
protected $returnResponse = false;
/**
* Last response results.
* @var Response
*/
protected $response;
/**
* Constructor
*
* Creates system.* methods.
*
*/
public function __construct()
{
$this->table = new Definition();
$this->registerSystemMethods();
}
/**
* Proxy calls to system object
*
* @param string $method
* @param array $params
* @return mixed
* @throws Server\Exception\BadMethodCallException
*/
public function __call($method, $params)
{
$system = $this->getSystem();
if (!method_exists($system, $method)) {
throw new Server\Exception\BadMethodCallException('Unknown instance method called on server: ' . $method);
}
return call_user_func_array(array($system, $method), $params);
}
/**
* Attach a callback as an XMLRPC method
*
* Attaches a callback as an XMLRPC method, prefixing the XMLRPC method name
* with $namespace, if provided. Reflection is done on the callback's
* docblock to create the methodHelp for the XMLRPC method.
*
* Additional arguments to pass to the function at dispatch may be passed;
* any arguments following the namespace will be aggregated and passed at
* dispatch time.
*
* @param string|array|callable $function Valid callback
* @param string $namespace Optional namespace prefix
* @throws Server\Exception\InvalidArgumentException
* @return void
*/
public function addFunction($function, $namespace = '')
{
if (!is_string($function) && !is_array($function)) {
throw new Server\Exception\InvalidArgumentException('Unable to attach function; invalid', 611);
}
$argv = null;
if (2 < func_num_args()) {
$argv = func_get_args();
$argv = array_slice($argv, 2);
}
$function = (array) $function;
foreach ($function as $func) {
if (!is_string($func) || !function_exists($func)) {
throw new Server\Exception\InvalidArgumentException('Unable to attach function; invalid', 611);
}
$reflection = Reflection::reflectFunction($func, $argv, $namespace);
$this->_buildSignature($reflection);
}
}
/**
* Attach class methods as XMLRPC method handlers
*
* $class may be either a class name or an object. Reflection is done on the
* class or object to determine the available public methods, and each is
* attached to the server as an available method; if a $namespace has been
* provided, that namespace is used to prefix the XMLRPC method names.
*
* Any additional arguments beyond $namespace will be passed to a method at
* invocation.
*
* @param string|object $class
* @param string $namespace Optional
* @param mixed $argv Optional arguments to pass to methods
* @return void
* @throws Server\Exception\InvalidArgumentException on invalid input
*/
public function setClass($class, $namespace = '', $argv = null)
{
if (is_string($class) && !class_exists($class)) {
throw new Server\Exception\InvalidArgumentException('Invalid method class', 610);
}
if (2 < func_num_args()) {
$argv = func_get_args();
$argv = array_slice($argv, 2);
}
$dispatchable = Reflection::reflectClass($class, $argv, $namespace);
foreach ($dispatchable->getMethods() as $reflection) {
$this->_buildSignature($reflection, $class);
}
}
/**
* Raise an xmlrpc server fault
*
* @param string|\Exception $fault
* @param int $code
* @return Server\Fault
*/
public function fault($fault = null, $code = 404)
{
if (!$fault instanceof \Exception) {
$fault = (string) $fault;
if (empty($fault)) {
$fault = 'Unknown Error';
}
$fault = new Server\Exception\RuntimeException($fault, $code);
}
return Server\Fault::getInstance($fault);
}
/**
* Set return response flag
*
* If true, {@link handle()} will return the response instead of
* automatically sending it back to the requesting client.
*
* The response is always available via {@link getResponse()}.
*
* @param bool $flag
* @return Server
*/
public function setReturnResponse($flag = true)
{
$this->returnResponse = ($flag) ? true : false;
return $this;
}
/**
* Retrieve return response flag
*
* @return bool
*/
public function getReturnResponse()
{
return $this->returnResponse;
}
/**
* Handle an xmlrpc call
*
* @param Request $request Optional
* @return Response|Fault
*/
public function handle($request = false)
{
// Get request
if ((!$request || !$request instanceof Request)
&& (null === ($request = $this->getRequest()))
) {
$request = new Request\Http();
$request->setEncoding($this->getEncoding());
}
$this->setRequest($request);
if ($request->isFault()) {
$response = $request->getFault();
} else {
try {
$response = $this->handleRequest($request);
} catch (\Exception $e) {
$response = $this->fault($e);
}
}
// Set output encoding
$response->setEncoding($this->getEncoding());
$this->response = $response;
if (!$this->returnResponse) {
echo $response;
return;
}
return $response;
}
/**
* Load methods as returned from {@link getFunctions}
*
* Typically, you will not use this method; it will be called using the
* results pulled from {@link Zend\XmlRpc\Server\Cache::get()}.
*
* @param array|Definition $definition
* @return void
* @throws Server\Exception\InvalidArgumentException on invalid input
*/
public function loadFunctions($definition)
{
if (!is_array($definition) && (!$definition instanceof Definition)) {
if (is_object($definition)) {
$type = get_class($definition);
} else {
$type = gettype($definition);
}
throw new Server\Exception\InvalidArgumentException('Unable to load server definition; must be an array or Zend\Server\Definition, received ' . $type, 612);
}
$this->table->clearMethods();
$this->registerSystemMethods();
if ($definition instanceof Definition) {
$definition = $definition->getMethods();
}
foreach ($definition as $key => $method) {
if ('system.' == substr($key, 0, 7)) {
continue;
}
$this->table->addMethod($method, $key);
}
}
/**
* Set encoding
*
* @param string $encoding
* @return Server
*/
public function setEncoding($encoding)
{
$this->encoding = $encoding;
AbstractValue::setEncoding($encoding);
return $this;
}
/**
* Retrieve current encoding
*
* @return string
*/
public function getEncoding()
{
return $this->encoding;
}
/**
* Do nothing; persistence is handled via {@link Zend\XmlRpc\Server\Cache}
*
* @param mixed $mode
* @return void
*/
public function setPersistence($mode)
{
}
/**
* Set the request object
*
* @param string|Request $request
* @return Server
* @throws Server\Exception\InvalidArgumentException on invalid request class or object
*/
public function setRequest($request)
{
if (is_string($request) && class_exists($request)) {
$request = new $request();
if (!$request instanceof Request) {
throw new Server\Exception\InvalidArgumentException('Invalid request class');
}
$request->setEncoding($this->getEncoding());
} elseif (!$request instanceof Request) {
throw new Server\Exception\InvalidArgumentException('Invalid request object');
}
$this->request = $request;
return $this;
}
/**
* Return currently registered request object
*
* @return null|Request
*/
public function getRequest()
{
return $this->request;
}
/**
* Last response.
*
* @return Response
*/
public function getResponse()
{
return $this->response;
}
/**
* Set the class to use for the response
*
* @param string $class
* @throws Server\Exception\InvalidArgumentException if invalid response class
* @return bool True if class was set, false if not
*/
public function setResponseClass($class)
{
if (!class_exists($class) || !static::isSubclassOf($class, 'Zend\XmlRpc\Response')) {
throw new Server\Exception\InvalidArgumentException('Invalid response class');
}
$this->responseClass = $class;
return true;
}
/**
* Retrieve current response class
*
* @return string
*/
public function getResponseClass()
{
return $this->responseClass;
}
/**
* Retrieve dispatch table
*
* @return array
*/
public function getDispatchTable()
{
return $this->table;
}
/**
* Returns a list of registered methods
*
* Returns an array of dispatchables (Zend\Server\Reflection\ReflectionFunction,
* ReflectionMethod, and ReflectionClass items).
*
* @return array
*/
public function getFunctions()
{
return $this->table->toArray();
}
/**
* Retrieve system object
*
* @return Server\System
*/
public function getSystem()
{
return $this->system;
}
/**
* Send arguments to all methods?
*
* If setClass() is used to add classes to the server, this flag defined
* how to handle arguments. If set to true, all methods including constructor
* will receive the arguments. If set to false, only constructor will receive the
* arguments
*/
public function sendArgumentsToAllMethods($flag = null)
{
if ($flag === null) {
return $this->sendArgumentsToAllMethods;
}
$this->sendArgumentsToAllMethods = (bool) $flag;
return $this;
}
/**
* Map PHP type to XML-RPC type
*
* @param string $type
* @return string
*/
protected function _fixType($type)
{
if (isset($this->typeMap[$type])) {
return $this->typeMap[$type];
}
return 'void';
}
/**
* Handle an xmlrpc call (actual work)
*
* @param Request $request
* @return Response
* @throws Server\Exception\RuntimeException
* Zend\XmlRpc\Server\Exceptions are thrown for internal errors; otherwise,
* any other exception may be thrown by the callback
*/
protected function handleRequest(Request $request)
{
$method = $request->getMethod();
// Check for valid method
if (!$this->table->hasMethod($method)) {
throw new Server\Exception\RuntimeException('Method "' . $method . '" does not exist', 620);
}
$info = $this->table->getMethod($method);
$params = $request->getParams();
$argv = $info->getInvokeArguments();
if (0 < count($argv) and $this->sendArgumentsToAllMethods()) {
$params = array_merge($params, $argv);
}
// Check calling parameters against signatures
$matched = false;
$sigCalled = $request->getTypes();
$sigLength = count($sigCalled);
$paramsLen = count($params);
if ($sigLength < $paramsLen) {
for ($i = $sigLength; $i < $paramsLen; ++$i) {
$xmlRpcValue = AbstractValue::getXmlRpcValue($params[$i]);
$sigCalled[] = $xmlRpcValue->getType();
}
}
$signatures = $info->getPrototypes();
foreach ($signatures as $signature) {
$sigParams = $signature->getParameters();
if ($sigCalled === $sigParams) {
$matched = true;
break;
}
}
if (!$matched) {
throw new Server\Exception\RuntimeException('Calling parameters do not match signature', 623);
}
$return = $this->_dispatch($info, $params);
$responseClass = $this->getResponseClass();
return new $responseClass($return);
}
/**
* Register system methods with the server
*
* @return void
*/
protected function registerSystemMethods()
{
$system = new Server\System($this);
$this->system = $system;
$this->setClass($system, 'system');
}
/**
* Checks if the object has this class as one of its parents
*
* @see https://bugs.php.net/bug.php?id=53727
* @see https://github.com/zendframework/zf2/pull/1807
*
* @param string $className
* @param string $type
* @return bool
*/
protected static function isSubclassOf($className, $type)
{
if (is_subclass_of($className, $type)) {
return true;
}
if (PHP_VERSION_ID >= 50307) {
return false;
}
if (!interface_exists($type)) {
return false;
}
$r = new ReflectionClass($className);
return $r->implementsInterface($type);
}
}