mirror of
https://github.com/torrentpier/torrentpier-lts.git
synced 2025-03-01 15:21:02 +03:00
610 lines
17 KiB
PHP
610 lines
17 KiB
PHP
|
<?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);
|
||
|
}
|
||
|
}
|