<?php /** * Zend Framework * * LICENSE * * This source file is subject to the new BSD license that is bundled * with this package in the file LICENSE.txt. * It is also available through the world-wide-web at this URL: * http://framework.zend.com/license/new-bsd * If you did not receive a copy of the license and are unable to * obtain it through the world-wide-web, please send an email * to license@zend.com so we can send you a copy immediately. * * @category Zend * @package Zend_XmlRpc * @subpackage Server * @copyright Copyright (c) 2005-2014 Zend Technologies USA Inc. (http://www.zend.com) * @license http://framework.zend.com/license/new-bsd New BSD License * @version $Id$ */ /** * Extends Zend_Server_Abstract */ require_once 'Zend/Server/Abstract.php'; /** * XMLRPC Request */ require_once 'Zend/XmlRpc/Request.php'; /** * XMLRPC Response */ require_once 'Zend/XmlRpc/Response.php'; /** * XMLRPC HTTP Response */ require_once 'Zend/XmlRpc/Response/Http.php'; /** * XMLRPC server fault class */ require_once 'Zend/XmlRpc/Server/Fault.php'; /** * XMLRPC server system methods class */ require_once 'Zend/XmlRpc/Server/System.php'; /** * Convert PHP to and from xmlrpc native types */ require_once 'Zend/XmlRpc/Value.php'; /** * Reflection API for function/method introspection */ require_once 'Zend/Server/Reflection.php'; /** * Zend_Server_Reflection_Function_Abstract */ require_once 'Zend/Server/Reflection/Function/Abstract.php'; /** * Specifically grab the Zend_Server_Reflection_Method for manually setting up * system.* methods and handling callbacks in {@link loadFunctions()}. */ require_once 'Zend/Server/Reflection/Method.php'; /** * An XML-RPC server implementation * * Example: * <code> * require_once 'Zend/XmlRpc/Server.php'; * require_once 'Zend/XmlRpc/Server/Cache.php'; * require_once 'Zend/XmlRpc/Server/Fault.php'; * require_once 'My/Exception.php'; * require_once 'My/Fault/Observer.php'; * * // Instantiate server * $server = new Zend_XmlRpc_Server(); * * // Allow some exceptions to report as fault responses: * Zend_XmlRpc_Server_Fault::attachFaultException('My_Exception'); * Zend_XmlRpc_Server_Fault::attachObserver('My_Fault_Observer'); * * // Get or build dispatch table: * if (!Zend_XmlRpc_Server_Cache::get($filename, $server)) { * require_once 'Some/Service/Class.php'; * require_once 'Another/Service/Class.php'; * * // 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 * Zend_XmlRpc_Server_Cache::save($filename, $server); * } * * $response = $server->handle(); * echo $response; * </code> * * @category Zend * @package Zend_XmlRpc * @subpackage Server * @copyright Copyright (c) 2005-2014 Zend Technologies USA Inc. (http://www.zend.com) * @license http://framework.zend.com/license/new-bsd New BSD License */ class Zend_XmlRpc_Server extends Zend_Server_Abstract { /** * Character encoding * @var string */ protected $_encoding = 'UTF-8'; /** * Request processed * @var null|Zend_XmlRpc_Request */ protected $_request = null; /** * Class to use for responses; defaults to {@link Zend_XmlRpc_Response_Http} * @var string */ protected $_responseClass = 'Zend_XmlRpc_Response_Http'; /** * Dispatch table of name => method pairs * @var Zend_Server_Definition */ protected $_table; /** * PHP types => XML-RPC types * @var array */ protected $_typeMap = array( 'i4' => 'i4', 'int' => 'int', 'integer' => 'int', 'Zend_Crypt_Math_BigInteger' => 'i8', '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', 'time' => 'dateTime.iso8601', 'Zend_Date' => '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; /** * Constructor * * Creates system.* methods. * * @return void */ public function __construct() { $this->_table = new Zend_Server_Definition(); $this->_registerSystemMethods(); } /** * Proxy calls to system object * * @param string $method * @param array $params * @return mixed * @throws Zend_XmlRpc_Server_Exception */ public function __call($method, $params) { $system = $this->getSystem(); if (!method_exists($system, $method)) { require_once 'Zend/XmlRpc/Server/Exception.php'; throw new Zend_XmlRpc_Server_Exception('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 $function Valid callback * @param string $namespace Optional namespace prefix * @return void * @throws Zend_XmlRpc_Server_Exception */ public function addFunction($function, $namespace = '') { if (!is_string($function) && !is_array($function)) { require_once 'Zend/XmlRpc/Server/Exception.php'; throw new Zend_XmlRpc_Server_Exception('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)) { require_once 'Zend/XmlRpc/Server/Exception.php'; throw new Zend_XmlRpc_Server_Exception('Unable to attach function; invalid', 611); } $reflection = Zend_Server_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 Zend_XmlRpc_Server_Exception on invalid input */ public function setClass($class, $namespace = '', $argv = null) { if (is_string($class) && !class_exists($class)) { require_once 'Zend/XmlRpc/Server/Exception.php'; throw new Zend_XmlRpc_Server_Exception('Invalid method class', 610); } $argv = null; if (2 < func_num_args()) { $argv = func_get_args(); $argv = array_slice($argv, 2); } $dispatchable = Zend_Server_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 Zend_XmlRpc_Server_Fault */ public function fault($fault = null, $code = 404) { if (!$fault instanceof Exception) { $fault = (string) $fault; if (empty($fault)) { $fault = 'Unknown Error'; } require_once 'Zend/XmlRpc/Server/Exception.php'; $fault = new Zend_XmlRpc_Server_Exception($fault, $code); } return Zend_XmlRpc_Server_Fault::getInstance($fault); } /** * Handle an xmlrpc call * * @param Zend_XmlRpc_Request $request Optional * @return Zend_XmlRpc_Response|Zend_XmlRpc_Fault */ public function handle($request = false) { // Get request if ((!$request || !$request instanceof Zend_XmlRpc_Request) && (null === ($request = $this->getRequest())) ) { require_once 'Zend/XmlRpc/Request/Http.php'; $request = new Zend_XmlRpc_Request_Http(); $request->setEncoding($this->getEncoding()); } $this->setRequest($request); if ($request->isFault()) { $response = $request->getFault(); } else { try { $response = $this->_handle($request); } catch (Exception $e) { $response = $this->fault($e); } } // Set output encoding $response->setEncoding($this->getEncoding()); 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|Zend_Server_Definition $definition * @return void * @throws Zend_XmlRpc_Server_Exception on invalid input */ public function loadFunctions($definition) { if (!is_array($definition) && (!$definition instanceof Zend_Server_Definition)) { if (is_object($definition)) { $type = get_class($definition); } else { $type = gettype($definition); } require_once 'Zend/XmlRpc/Server/Exception.php'; throw new Zend_XmlRpc_Server_Exception('Unable to load server definition; must be an array or Zend_Server_Definition, received ' . $type, 612); } $this->_table->clearMethods(); $this->_registerSystemMethods(); if ($definition instanceof Zend_Server_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 Zend_XmlRpc_Server */ public function setEncoding($encoding) { $this->_encoding = $encoding; Zend_XmlRpc_Value::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|Zend_XmlRpc_Request $request * @return Zend_XmlRpc_Server * @throws Zend_XmlRpc_Server_Exception on invalid request class or object */ public function setRequest($request) { if (is_string($request) && class_exists($request)) { $request = new $request(); if (!$request instanceof Zend_XmlRpc_Request) { require_once 'Zend/XmlRpc/Server/Exception.php'; throw new Zend_XmlRpc_Server_Exception('Invalid request class'); } $request->setEncoding($this->getEncoding()); } elseif (!$request instanceof Zend_XmlRpc_Request) { require_once 'Zend/XmlRpc/Server/Exception.php'; throw new Zend_XmlRpc_Server_Exception('Invalid request object'); } $this->_request = $request; return $this; } /** * Return currently registered request object * * @return null|Zend_XmlRpc_Request */ public function getRequest() { return $this->_request; } /** * Set the class to use for the response * * @param string $class * @return boolean True if class was set, false if not */ public function setResponseClass($class) { if (!class_exists($class) or ($c = new ReflectionClass($class) and !$c->isSubclassOf('Zend_XmlRpc_Response'))) { require_once 'Zend/XmlRpc/Server/Exception.php'; throw new Zend_XmlRpc_Server_Exception('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_Function, * _Method, and _Class items). * * @return array */ public function getFunctions() { return $this->_table->toArray(); } /** * Retrieve system object * * @return Zend_XmlRpc_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 Zend_XmlRpc_Request $request * @return Zend_XmlRpc_Response * @throws Zend_XmlRpcServer_Exception|Exception * Zend_XmlRpcServer_Exceptions are thrown for internal errors; otherwise, * any other exception may be thrown by the callback */ protected function _handle(Zend_XmlRpc_Request $request) { $method = $request->getMethod(); // Check for valid method if (!$this->_table->hasMethod($method)) { require_once 'Zend/XmlRpc/Server/Exception.php'; throw new Zend_XmlRpc_Server_Exception('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 = Zend_XmlRpc_Value::getXmlRpcValue($params[$i]); $sigCalled[] = $xmlRpcValue->getType(); } } $signatures = $info->getPrototypes(); foreach ($signatures as $signature) { $sigParams = $signature->getParameters(); if ($sigCalled === $sigParams) { $matched = true; break; } } if (!$matched) { require_once 'Zend/XmlRpc/Server/Exception.php'; throw new Zend_XmlRpc_Server_Exception('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 Zend_XmlRpc_Server_System($this); $this->_system = $system; $this->setClass($system, 'system'); } }