method pairs * @var array */ protected $_table = array(); /** * * @var bool session flag; whether or not to add a session to each response. */ protected $_session = false; /** * Namespace allows all AMF calls to not clobber other PHP session variables * @var Zend_Session_NameSpace default session namespace zend_amf */ protected $_sesionNamespace = 'zend_amf'; /** * Set the default session.name if php_ * @var string */ protected $_sessionName = 'PHPSESSID'; /** * Authentication handler object * * @var Zend_Amf_Auth_Abstract */ protected $_auth; /** * ACL handler object * * @var Zend_Acl */ protected $_acl; /** * The server constructor */ public function __construct() { Zend_Amf_Parse_TypeLoader::setResourceLoader(new Zend_Loader_PluginLoader(array("Zend_Amf_Parse_Resource" => "Zend/Amf/Parse/Resource"))); } /** * Set authentication adapter * * If the authentication adapter implements a "getAcl()" method, populate * the ACL of this instance with it (if none exists already). * * @param Zend_Amf_Auth_Abstract $auth * @return Zend_Amf_Server */ public function setAuth(Zend_Amf_Auth_Abstract $auth) { $this->_auth = $auth; if ((null === $this->getAcl()) && method_exists($auth, 'getAcl')) { $this->setAcl($auth->getAcl()); } return $this; } /** * Get authentication adapter * * @return Zend_Amf_Auth_Abstract */ public function getAuth() { return $this->_auth; } /** * Set ACL adapter * * @param Zend_Acl $acl * @return Zend_Amf_Server */ public function setAcl(Zend_Acl $acl) { $this->_acl = $acl; return $this; } /** * Get ACL adapter * * @return Zend_Acl */ public function getAcl() { return $this->_acl; } /** * Set production flag * * @param bool $flag * @return Zend_Amf_Server */ public function setProduction($flag) { $this->_production = (bool) $flag; return $this; } /** * Whether or not the server is in production * * @return bool */ public function isProduction() { return $this->_production; } /** * @param namespace of all incoming sessions defaults to Zend_Amf * @return Zend_Amf_Server */ public function setSession($namespace = 'Zend_Amf') { require_once 'Zend/Session.php'; $this->_session = true; $this->_sesionNamespace = new Zend_Session_Namespace($namespace); return $this; } /** * Whether of not the server is using sessions * @return bool */ public function isSession() { return $this->_session; } /** * Check if the ACL allows accessing the function or method * * @param string|object $object Object or class being accessed * @param string $function Function or method being accessed * @return unknown_type */ protected function _checkAcl($object, $function) { if(!$this->_acl) { return true; } if($object) { $class = is_object($object)?get_class($object):$object; if(!$this->_acl->has($class)) { require_once 'Zend/Acl/Resource.php'; $this->_acl->add(new Zend_Acl_Resource($class)); } $call = array($object, "initAcl"); if(is_callable($call) && !call_user_func($call, $this->_acl)) { // if initAcl returns false, no ACL check return true; } } else { $class = null; } $auth = Zend_Auth::getInstance(); if($auth->hasIdentity()) { $role = $auth->getIdentity()->role; } else { if($this->_acl->hasRole(Zend_Amf_Constants::GUEST_ROLE)) { $role = Zend_Amf_Constants::GUEST_ROLE; } else { require_once 'Zend/Amf/Server/Exception.php'; throw new Zend_Amf_Server_Exception("Unauthenticated access not allowed"); } } if($this->_acl->isAllowed($role, $class, $function)) { return true; } else { require_once 'Zend/Amf/Server/Exception.php'; throw new Zend_Amf_Server_Exception("Access not allowed"); } } /** * Get PluginLoader for the Server * * @return Zend_Loader_PluginLoader */ protected function getLoader() { if(empty($this->_loader)) { require_once 'Zend/Loader/PluginLoader.php'; $this->_loader = new Zend_Loader_PluginLoader(); } return $this->_loader; } /** * Loads a remote class or method and executes the function and returns * the result * * @param string $method Is the method to execute * @param mixed $param values for the method * @return mixed $response the result of executing the method * @throws Zend_Amf_Server_Exception */ protected function _dispatch($method, $params = null, $source = null) { if($source) { if(($mapped = Zend_Amf_Parse_TypeLoader::getMappedClassName($source)) !== false) { $source = $mapped; } } $qualifiedName = empty($source) ? $method : $source . '.' . $method; if (!isset($this->_table[$qualifiedName])) { // if source is null a method that was not defined was called. if ($source) { $className = str_replace('.', '_', $source); if(class_exists($className, false) && !isset($this->_classAllowed[$className])) { require_once 'Zend/Amf/Server/Exception.php'; throw new Zend_Amf_Server_Exception('Can not call "' . $className . '" - use setClass()'); } try { $this->getLoader()->load($className); } catch (Exception $e) { require_once 'Zend/Amf/Server/Exception.php'; throw new Zend_Amf_Server_Exception('Class "' . $className . '" does not exist: '.$e->getMessage(), 0, $e); } // Add the new loaded class to the server. require_once 'Zend/Amf/Server/Exception.php'; $this->setClass($className, $source); } if (!isset($this->_table[$qualifiedName])) { // Source is null or doesn't contain specified method require_once 'Zend/Amf/Server/Exception.php'; throw new Zend_Amf_Server_Exception('Method "' . $method . '" does not exist'); } } $info = $this->_table[$qualifiedName]; $argv = $info->getInvokeArguments(); if (0 < count($argv)) { $params = array_merge($params, $argv); } $params = $this->_castParameters($info, $params); if ($info instanceof Zend_Server_Reflection_Function) { $func = $info->getName(); $this->_checkAcl(null, $func); $return = call_user_func_array($func, $params); } elseif ($info instanceof Zend_Server_Reflection_Method) { // Get class $class = $info->getDeclaringClass()->getName(); if ('static' == $info->isStatic()) { // for some reason, invokeArgs() does not work the same as // invoke(), and expects the first argument to be an object. // So, using a callback if the method is static. $this->_checkAcl($class, $info->getName()); $return = call_user_func_array(array($class, $info->getName()), $params); } else { // Object methods try { $object = $info->getDeclaringClass()->newInstance(); } catch (Exception $e) { require_once 'Zend/Amf/Server/Exception.php'; throw new Zend_Amf_Server_Exception('Error instantiating class ' . $class . ' to invoke method ' . $info->getName() . ': '.$e->getMessage(), 621, $e); } $this->_checkAcl($object, $info->getName()); $return = $info->invokeArgs($object, $params); } } else { require_once 'Zend/Amf/Server/Exception.php'; throw new Zend_Amf_Server_Exception('Method missing implementation ' . get_class($info)); } return $return; } /** * Handles each of the 11 different command message types. * * A command message is a flex.messaging.messages.CommandMessage * * @see Zend_Amf_Value_Messaging_CommandMessage * @param Zend_Amf_Value_Messaging_CommandMessage $message * @return Zend_Amf_Value_Messaging_AcknowledgeMessage */ protected function _loadCommandMessage(Zend_Amf_Value_Messaging_CommandMessage $message) { require_once 'Zend/Amf/Value/Messaging/AcknowledgeMessage.php'; switch($message->operation) { case Zend_Amf_Value_Messaging_CommandMessage::DISCONNECT_OPERATION : case Zend_Amf_Value_Messaging_CommandMessage::CLIENT_PING_OPERATION : $return = new Zend_Amf_Value_Messaging_AcknowledgeMessage($message); break; case Zend_Amf_Value_Messaging_CommandMessage::LOGIN_OPERATION : $data = explode(':', base64_decode($message->body)); $userid = $data[0]; $password = isset($data[1])?$data[1]:""; if(empty($userid)) { require_once 'Zend/Amf/Server/Exception.php'; throw new Zend_Amf_Server_Exception('Login failed: username not supplied'); } if(!$this->_handleAuth($userid, $password)) { require_once 'Zend/Amf/Server/Exception.php'; throw new Zend_Amf_Server_Exception('Authentication failed'); } $return = new Zend_Amf_Value_Messaging_AcknowledgeMessage($message); break; case Zend_Amf_Value_Messaging_CommandMessage::LOGOUT_OPERATION : if($this->_auth) { Zend_Auth::getInstance()->clearIdentity(); } $return = new Zend_Amf_Value_Messaging_AcknowledgeMessage($message); break; default : require_once 'Zend/Amf/Server/Exception.php'; throw new Zend_Amf_Server_Exception('CommandMessage::' . $message->operation . ' not implemented'); break; } return $return; } /** * Create appropriate error message * * @param int $objectEncoding Current AMF encoding * @param string $message Message that was being processed when error happened * @param string $description Error description * @param mixed $detail Detailed data about the error * @param int $code Error code * @param int $line Error line * @return Zend_Amf_Value_Messaging_ErrorMessage|array */ protected function _errorMessage($objectEncoding, $message, $description, $detail, $code, $line) { $return = null; switch ($objectEncoding) { case Zend_Amf_Constants::AMF0_OBJECT_ENCODING : return array ( 'description' => ($this->isProduction ()) ? '' : $description, 'detail' => ($this->isProduction ()) ? '' : $detail, 'line' => ($this->isProduction ()) ? 0 : $line, 'code' => $code ); case Zend_Amf_Constants::AMF3_OBJECT_ENCODING : require_once 'Zend/Amf/Value/Messaging/ErrorMessage.php'; $return = new Zend_Amf_Value_Messaging_ErrorMessage ( $message ); $return->faultString = $this->isProduction () ? '' : $description; $return->faultCode = $code; $return->faultDetail = $this->isProduction () ? '' : $detail; break; } return $return; } /** * Handle AMF authentication * * @param string $userid * @param string $password * @return boolean */ protected function _handleAuth( $userid, $password) { if (!$this->_auth) { return true; } $this->_auth->setCredentials($userid, $password); $auth = Zend_Auth::getInstance(); $result = $auth->authenticate($this->_auth); if ($result->isValid()) { if (!$this->isSession()) { $this->setSession(); } return true; } else { // authentication failed, good bye require_once 'Zend/Amf/Server/Exception.php'; throw new Zend_Amf_Server_Exception( "Authentication failed: " . join("\n", $result->getMessages()), $result->getCode()); } } /** * Takes the deserialized AMF request and performs any operations. * * @todo should implement and SPL observer pattern for custom AMF headers * @todo DescribeService support * @param Zend_Amf_Request $request * @return Zend_Amf_Response * @throws Zend_Amf_server_Exception|Exception */ protected function _handle(Zend_Amf_Request $request) { // Get the object encoding of the request. $objectEncoding = $request->getObjectEncoding(); // create a response object to place the output from the services. $response = $this->getResponse(); // set response encoding $response->setObjectEncoding($objectEncoding); // Authenticate, if we have credential headers $error = false; $headers = $request->getAmfHeaders(); if (isset($headers[Zend_Amf_Constants::CREDENTIALS_HEADER]) && isset($headers[Zend_Amf_Constants::CREDENTIALS_HEADER]->userid) && isset($headers[Zend_Amf_Constants::CREDENTIALS_HEADER]->password) ) { try { if ($this->_handleAuth( $headers[Zend_Amf_Constants::CREDENTIALS_HEADER]->userid, $headers[Zend_Amf_Constants::CREDENTIALS_HEADER]->password )) { // use RequestPersistentHeader to clear credentials $response->addAmfHeader( new Zend_Amf_Value_MessageHeader( Zend_Amf_Constants::PERSISTENT_HEADER, false, new Zend_Amf_Value_MessageHeader( Zend_Amf_Constants::CREDENTIALS_HEADER, false, null ) ) ); } } catch (Exception $e) { // Error during authentication; report it $error = $this->_errorMessage( $objectEncoding, '', $e->getMessage(), $e->getTraceAsString(), $e->getCode(), $e->getLine() ); $responseType = Zend_AMF_Constants::STATUS_METHOD; } } // Iterate through each of the service calls in the AMF request foreach($request->getAmfBodies() as $body) { if ($error) { // Error during authentication; just report it and be done $responseURI = $body->getResponseURI() . $responseType; $newBody = new Zend_Amf_Value_MessageBody($responseURI, null, $error); $response->addAmfBody($newBody); continue; } try { switch ($objectEncoding) { case Zend_Amf_Constants::AMF0_OBJECT_ENCODING: // AMF0 Object Encoding $targetURI = $body->getTargetURI(); $message = ''; // Split the target string into its values. $source = substr($targetURI, 0, strrpos($targetURI, '.')); if ($source) { // Break off method name from namespace into source $method = substr(strrchr($targetURI, '.'), 1); $return = $this->_dispatch($method, $body->getData(), $source); } else { // Just have a method name. $return = $this->_dispatch($targetURI, $body->getData()); } break; case Zend_Amf_Constants::AMF3_OBJECT_ENCODING: default: // AMF3 read message type $message = $body->getData(); if ($message instanceof Zend_Amf_Value_Messaging_CommandMessage) { // async call with command message $return = $this->_loadCommandMessage($message); } elseif ($message instanceof Zend_Amf_Value_Messaging_RemotingMessage) { require_once 'Zend/Amf/Value/Messaging/AcknowledgeMessage.php'; $return = new Zend_Amf_Value_Messaging_AcknowledgeMessage($message); $return->body = $this->_dispatch($message->operation, $message->body, $message->source); } else { // Amf3 message sent with netConnection $targetURI = $body->getTargetURI(); // Split the target string into its values. $source = substr($targetURI, 0, strrpos($targetURI, '.')); if ($source) { // Break off method name from namespace into source $method = substr(strrchr($targetURI, '.'), 1); $return = $this->_dispatch($method, $body->getData(), $source); } else { // Just have a method name. $return = $this->_dispatch($targetURI, $body->getData()); } } break; } $responseType = Zend_AMF_Constants::RESULT_METHOD; } catch (Exception $e) { $return = $this->_errorMessage($objectEncoding, $message, $e->getMessage(), $e->getTraceAsString(),$e->getCode(), $e->getLine()); $responseType = Zend_AMF_Constants::STATUS_METHOD; } $responseURI = $body->getResponseURI() . $responseType; $newBody = new Zend_Amf_Value_MessageBody($responseURI, null, $return); $response->addAmfBody($newBody); } // Add a session header to the body if session is requested. if($this->isSession()) { $currentID = session_id(); $joint = "?"; if(isset($_SERVER['QUERY_STRING'])) { if(!strpos($_SERVER['QUERY_STRING'], $currentID) !== FALSE) { if(strrpos($_SERVER['QUERY_STRING'], "?") !== FALSE) { $joint = "&"; } } } // create a new AMF message header with the session id as a variable. $sessionValue = $joint . $this->_sessionName . "=" . $currentID; $sessionHeader = new Zend_Amf_Value_MessageHeader(Zend_Amf_Constants::URL_APPEND_HEADER, false, $sessionValue); $response->addAmfHeader($sessionHeader); } // serialize the response and return serialized body. $response->finalize(); } /** * Handle an AMF call from the gateway. * * @param null|Zend_Amf_Request $request Optional * @return Zend_Amf_Response */ public function handle($request = null) { // Check if request was passed otherwise get it from the server if ($request === null || !$request instanceof Zend_Amf_Request) { $request = $this->getRequest(); } else { $this->setRequest($request); } if ($this->isSession()) { // Check if a session is being sent from the amf call if (isset($_COOKIE[$this->_sessionName])) { session_id($_COOKIE[$this->_sessionName]); } } // Check for errors that may have happend in deserialization of Request. try { // Take converted PHP objects and handle service call. // Serialize to Zend_Amf_response for output stream $this->_handle($request); $response = $this->getResponse(); } catch (Exception $e) { // Handle any errors in the serialization and service calls. require_once 'Zend/Amf/Server/Exception.php'; throw new Zend_Amf_Server_Exception('Handle error: ' . $e->getMessage() . ' ' . $e->getLine(), 0, $e); } // Return the Amf serialized output string return $response; } /** * Set request object * * @param string|Zend_Amf_Request $request * @return Zend_Amf_Server */ public function setRequest($request) { if (is_string($request) && class_exists($request)) { $request = new $request(); if (!$request instanceof Zend_Amf_Request) { require_once 'Zend/Amf/Server/Exception.php'; throw new Zend_Amf_Server_Exception('Invalid request class'); } } elseif (!$request instanceof Zend_Amf_Request) { require_once 'Zend/Amf/Server/Exception.php'; throw new Zend_Amf_Server_Exception('Invalid request object'); } $this->_request = $request; return $this; } /** * Return currently registered request object * * @return null|Zend_Amf_Request */ public function getRequest() { if (null === $this->_request) { require_once 'Zend/Amf/Request/Http.php'; $this->setRequest(new Zend_Amf_Request_Http()); } return $this->_request; } /** * Public access method to private Zend_Amf_Server_Response reference * * @param string|Zend_Amf_Server_Response $response * @return Zend_Amf_Server */ public function setResponse($response) { if (is_string($response) && class_exists($response)) { $response = new $response(); if (!$response instanceof Zend_Amf_Response) { require_once 'Zend/Amf/Server/Exception.php'; throw new Zend_Amf_Server_Exception('Invalid response class'); } } elseif (!$response instanceof Zend_Amf_Response) { require_once 'Zend/Amf/Server/Exception.php'; throw new Zend_Amf_Server_Exception('Invalid response object'); } $this->_response = $response; return $this; } /** * get a reference to the Zend_Amf_response instance * * @return Zend_Amf_Server_Response */ public function getResponse() { if (null === ($response = $this->_response)) { require_once 'Zend/Amf/Response/Http.php'; $this->setResponse(new Zend_Amf_Response_Http()); } return $this->_response; } /** * Attach a class or object to the server * * Class may be either a class name or an instantiated object. Reflection * is done on the class or object to determine the available public * methods, and each is attached to the server as and available method. If * a $namespace has been provided, that namespace is used to prefix * AMF service call. * * @param string|object $class * @param string $namespace Optional * @param mixed $arg Optional arguments to pass to a method * @return Zend_Amf_Server * @throws Zend_Amf_Server_Exception on invalid input */ public function setClass($class, $namespace = '', $argv = null) { if (is_string($class) && !class_exists($class)){ require_once 'Zend/Amf/Server/Exception.php'; throw new Zend_Amf_Server_Exception('Invalid method or class'); } elseif (!is_string($class) && !is_object($class)) { require_once 'Zend/Amf/Server/Exception.php'; throw new Zend_Amf_Server_Exception('Invalid method or class; must be a classname or object'); } $argv = null; if (2 < func_num_args()) { $argv = array_slice(func_get_args(), 2); } // Use the class name as the name space by default. if ($namespace == '') { $namespace = is_object($class) ? get_class($class) : $class; } $this->_classAllowed[is_object($class) ? get_class($class) : $class] = true; $this->_methods[] = Zend_Server_Reflection::reflectClass($class, $argv, $namespace); $this->_buildDispatchTable(); return $this; } /** * Attach a function to the server * * 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 Zend_Amf_Server * @throws Zend_Amf_Server_Exception */ public function addFunction($function, $namespace = '') { if (!is_string($function) && !is_array($function)) { require_once 'Zend/Amf/Server/Exception.php'; throw new Zend_Amf_Server_Exception('Unable to attach function'); } $argv = null; if (2 < func_num_args()) { $argv = array_slice(func_get_args(), 2); } $function = (array) $function; foreach ($function as $func) { if (!is_string($func) || !function_exists($func)) { require_once 'Zend/Amf/Server/Exception.php'; throw new Zend_Amf_Server_Exception('Unable to attach function'); } $this->_methods[] = Zend_Server_Reflection::reflectFunction($func, $argv, $namespace); } $this->_buildDispatchTable(); return $this; } /** * Creates an array of directories in which services can reside. * TODO: add support for prefixes? * * @param string $dir */ public function addDirectory($dir) { $this->getLoader()->addPrefixPath("", $dir); } /** * Returns an array of directories that can hold services. * * @return array */ public function getDirectory() { return $this->getLoader()->getPaths(""); } /** * (Re)Build the dispatch table * * The dispatch table consists of a an array of method name => * Zend_Server_Reflection_Function_Abstract pairs * * @return void */ protected function _buildDispatchTable() { $table = array(); foreach ($this->_methods as $key => $dispatchable) { if ($dispatchable instanceof Zend_Server_Reflection_Function_Abstract) { $ns = $dispatchable->getNamespace(); $name = $dispatchable->getName(); $name = empty($ns) ? $name : $ns . '.' . $name; if (isset($table[$name])) { require_once 'Zend/Amf/Server/Exception.php'; throw new Zend_Amf_Server_Exception('Duplicate method registered: ' . $name); } $table[$name] = $dispatchable; continue; } if ($dispatchable instanceof Zend_Server_Reflection_Class) { foreach ($dispatchable->getMethods() as $method) { $ns = $method->getNamespace(); $name = $method->getName(); $name = empty($ns) ? $name : $ns . '.' . $name; if (isset($table[$name])) { require_once 'Zend/Amf/Server/Exception.php'; throw new Zend_Amf_Server_Exception('Duplicate method registered: ' . $name); } $table[$name] = $method; continue; } } } $this->_table = $table; } /** * Raise a server fault * * Unimplemented * * @param string|Exception $fault * @return void */ public function fault($fault = null, $code = 404) { } /** * 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; } /** * Set server persistence * * Unimplemented * * @param mixed $mode * @return void */ public function setPersistence($mode) { } /** * Load server definition * * Unimplemented * * @param array $definition * @return void */ public function loadFunctions($definition) { } /** * Map ActionScript classes to PHP classes * * @param string $asClass * @param string $phpClass * @return Zend_Amf_Server */ public function setClassMap($asClass, $phpClass) { require_once 'Zend/Amf/Parse/TypeLoader.php'; Zend_Amf_Parse_TypeLoader::setMapping($asClass, $phpClass); return $this; } /** * List all available methods * * Returns an array of method names. * * @return array */ public function listMethods() { return array_keys($this->_table); } /** * Cast parameters * * Takes the provided parameters from the request, and attempts to cast them * to objects, if the prototype defines any as explicit object types * * @param Reflection $reflectionMethod * @param array $params * @return array */ protected function _castParameters($reflectionMethod, array $params) { $prototypes = $reflectionMethod->getPrototypes(); $nonObjectTypes = array( 'null', 'mixed', 'void', 'unknown', 'bool', 'boolean', 'number', 'int', 'integer', 'double', 'float', 'string', 'array', 'object', 'stdclass', ); $types = array(); foreach ($prototypes as $prototype) { foreach ($prototype->getParameters() as $parameter) { $type = $parameter->getType(); if (in_array(strtolower($type), $nonObjectTypes)) { continue; } $position = $parameter->getPosition(); $types[$position] = $type; } } if (empty($types)) { return $params; } foreach ($params as $position => $value) { if (!isset($types[$position])) { // No specific type to cast to? done continue; } $type = $types[$position]; if (!class_exists($type)) { // Not a class, apparently. done continue; } if ($value instanceof $type) { // Already of the right type? done continue; } if (!is_array($value) && !is_object($value)) { // Can't cast scalars to objects easily; done continue; } // Create instance, and loop through value to set $object = new $type; foreach ($value as $property => $defined) { $object->{$property} = $defined; } $params[$position] = $object; } return $params; } }