| <?php |
| /** |
| * File containing the ezcWebdavServer class. |
| * |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| * |
| * @package Webdav |
| * @version //autogentag// |
| * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License, Version 2.0 |
| */ |
| /** |
| * Base class for creating a webdav server, capable of serving webdav requests. |
| * |
| * <code> |
| * $server = ezcWebdavServer::getInstance(); |
| * |
| * // Optionally register aditional transport handlers |
| * |
| * // This step is only required, if you want to add custom or third party extensions |
| * // implementations for special clients. |
| * // Create a new configuration set for the client |
| * $newClientConf = new ezcWebdavServerConfiguration( |
| * // Regular expression to match client name |
| * '(My.*Webdav\s+Cliengt)i', |
| * // Class name of transport handler, extending {@link ezcWebdavTransport} |
| * 'myCustomTransportTransport' |
| * // There are more settings you can provide, see {@link |
| * // ezcWebdavServerConfiguration}. |
| * ); |
| * // Append the configuration at front, because the last configuration is a |
| * // catch all for misc clients. |
| * $server->configurations->insertBefore( $newClientConf, 0 ); |
| * |
| * // If you want to use a different path factory globally, you need to replace |
| * // it in every configuration. |
| * $myPathFactory = new ezcWebdavBasicPathFactory( 'http://webdav.server/base/path' ); |
| * foreach ( $server->configuration as $config ) |
| * { |
| * $config->pathFactory = $myPathFactory; |
| * } |
| * |
| * // Serve data using file backend with data in the local directory "/path" |
| * // Make sure this directory is read and writable for your server and that |
| * // the umask is set accordingly in the server settings, if you want to |
| * // access the files as a different user, too. |
| * $backend = new ezcWebdavBackendFile( '/path' ); |
| * |
| * // Make the server serve WebDAV requests |
| * $server->handle( $backend ); |
| * </code> |
| * |
| * @property ezcWebdavServerConfigurationManager $configurations |
| * Webdav server configuration manager, which holds and dispatches |
| * configurations that fit for a certain client. |
| * @property ezcWebdavAuth $auth |
| * The central authentication mechanism for the WebDAV server. This |
| * instance will be used to perform authentication and authorization |
| * on every incoming request. A valid property value is an object |
| * that at least implements {@link ezcWebdavBasicAuthenticator} or |
| * {@link ezcWebdavDigestAuthenticator} or both. In addition {@link |
| * ezcWebdavAuthorizer} may be implemented. The default is null, |
| * indicating that no authentication/authorization is provided. |
| * |
| * @property-read ezcWebdavBackend $backend |
| * The backend given to {@link ezcWebdavServer->handle()}. Null |
| * before handle() was called. |
| * @property-read ezcWebdavPluginRegistry $pluginRegistry |
| * The internal plugin registry. Can be accessed to register and |
| * remove plugins. |
| * @property-read ezcWebdavPathFactory $pathFactory |
| * The path factory object used to translate between URIs and |
| * local paths. Configured by the {@link |
| * ezcWebdavServerConfigurationManager} when the {@link |
| * ezcWebdavServer::handle()} method is run}. |
| * @property-read ezcWebdavXmlTool $xmlTool |
| * The XML tool object used for XML related operations in the |
| * server and transport level. Configured by the {@link |
| * ezcWebdavServerConfigurationManager} when the {@link |
| * ezcWebdavServer::handle()} method is run}. |
| * @property-read ezcWebdavPropertyHandler $propertyHandler |
| * The property handler object used to parse and serialize |
| * WebDAV properties on the transport level. Configured by the |
| * {@link ezcWebdavServerConfigurationManager} when the {@link |
| * ezcWebdavServer::handle()} method is run}. |
| * @property-read ezcWebdavTransport $transport |
| * The transport layer object used to parse and serialize WebDAV |
| * requests and responses. Configured by the {@link |
| * ezcWebdavServerConfigurationManager} when the {@link |
| * ezcWebdavServer::handle()} method is run}. |
| * @property ezcWebdavServerConfigurationManager $configurations |
| * Configuration manager, handling different client configurations |
| * for this server. |
| * |
| * @version //autogentag// |
| * @package Webdav |
| * @mainclass |
| */ |
| class ezcWebdavServer |
| { |
| /** |
| * Singleton instance. |
| * |
| * @var ezcWebdavServer |
| */ |
| protected static $instance; |
| |
| /** |
| * Properties. |
| * |
| * @var array(string=>mixed) |
| */ |
| protected $properties = array(); |
| |
| /** |
| * Creates a new instance. |
| * |
| * The constructor is protected due to singleton reasons. Use {@link |
| * getInstance()} and then use the properties of the server to adjust its |
| * configuration. |
| * |
| * @return void |
| */ |
| protected function __construct() |
| { |
| $this->reset(); |
| } |
| |
| /** |
| * Returns singleton instance. |
| * |
| * The instantiation of 2 WebDAV servers at the same time does not make |
| * sense and could possibly cause strange effects, like double sending of a |
| * response. Therefore the server implements a singleton and its only |
| * instance must be retrieved using this method. Configuration changes can |
| * then be performed through the properties of this instance. |
| * |
| * @return ezcWebdavServer |
| */ |
| public static function getInstance() |
| { |
| if ( self::$instance === null ) |
| { |
| self::$instance = new ezcWebdavServer(); |
| } |
| return self::$instance; |
| } |
| |
| /** |
| * Handles the current request. |
| * |
| * This method is the absolute heart of the Webdav component. It is called |
| * to make the server instance handle the current request. This means, a |
| * {@link ezcWebdavTransport} is selected and instantiated through the |
| * {@link ezcWebdavServerConfigurationManager} in {@link $configurations}. |
| * This transport (and all other objects, created from the configuration) |
| * is used to parse the incoming request into an instance of {@link |
| * ezcWebdavRequest}, which is then handed to the submitted $backend for |
| * handling. The resulting {@link ezcWebdavResponse} is serialized by the |
| * {@link ezcWebdavTransport} and send back to the client. |
| * |
| * The method receives at least an instance of {@link ezcWebdavBackend}, |
| * which is used to server the request. Optionally, the request URI can be |
| * submitted in $uri. If this is not the case, the request URI is |
| * determined by the server variables |
| * <ul> |
| * <li>$_SERVER['SERVER_NAME']</li> |
| * <li>$_SERVER['REQUEST_URI']</li> |
| * </ul> |
| * |
| * @param ezcWebdavBackend $backend |
| * @param string $uri |
| * |
| * @return void |
| */ |
| public final function handle( ezcWebdavBackend $backend, $uri = null ) |
| { |
| $uri = ( $uri === null |
| ? 'http://' . $_SERVER['SERVER_NAME'] . $_SERVER['REQUEST_URI'] |
| : $uri ); |
| |
| // Perform final setup |
| $this->properties['backend'] = $backend; |
| if ( !isset( $_SERVER['HTTP_USER_AGENT'] ) ) |
| { |
| throw new ezcWebdavMissingHeaderException( 'User-Agent' ); |
| } |
| // Configure the server according to the requesting client |
| $this->configurations->configure( $this, $_SERVER['HTTP_USER_AGENT'] ); |
| |
| // Initialize all plugins |
| $this->pluginRegistry->initPlugins(); |
| |
| // Parse request into request object |
| $request = $this->transport->parseRequest( $uri ); |
| |
| // Perform authentication / authorization on the given request, |
| // if it is known by the server. |
| if ( $request instanceof ezcWebdavRequest && is_object( $this->properties['auth'] ) ) |
| { |
| $res = $this->authenticate( $request ); |
| if ( $res !== null ) |
| { |
| $request = $res; |
| } |
| } |
| |
| if ( $request instanceof ezcWebdavRequest ) |
| { |
| // Plugin hook receivedRequest |
| $pluginRes = ezcWebdavServer::getInstance()->pluginRegistry->announceHook( |
| __CLASS__, |
| 'receivedRequest', |
| new ezcWebdavPluginParameters( |
| array( |
| 'request' => $request, |
| ) |
| ) |
| ); |
| if ( is_object( $pluginRes ) && $pluginRes instanceof ezcWebdavResponse ) |
| { |
| // Plugin already took care about processing the request |
| $response = $pluginRes; |
| } |
| else |
| { |
| // Let backend process the request |
| $response = $this->backend->performRequest( $request ); |
| } |
| } |
| else |
| { |
| // The transport layer or auth mechanism already issued an error. |
| $response = $request; |
| } |
| |
| // Plugin hook generatedResponse |
| ezcWebdavServer::getInstance()->pluginRegistry->announceHook( |
| __CLASS__, |
| 'generatedResponse', |
| new ezcWebdavPluginParameters( |
| array( |
| 'response' => $response, |
| ) |
| ) |
| ); |
| |
| $this->transport->handleResponse( $response ); |
| } |
| |
| /** |
| * Initializes the server with the given objects. |
| * |
| * This method is marked proteced, because it is intended to be used by by |
| * {@link ezcWebdavServerConfiguration} instances and instances of derived |
| * classes, but not directly. |
| * |
| * @param ezcWebdavPathFactory $pathFactory |
| * @param ezcWebdavXmlTool $xmlTool |
| * @param ezcWebdavPropertyHandler $propertyHandler |
| * @param ezcWebdavHeaderHandler $headerHandler |
| * @param ezcWebdavTransport $transport |
| * @access protected |
| * @return void |
| */ |
| public function init( |
| ezcWebdavPathFactory $pathFactory, |
| ezcWebdavXmlTool $xmlTool, |
| ezcWebdavPropertyHandler $propertyHandler, |
| ezcWebdavHeaderHandler $headerHandler, |
| ezcWebdavTransport $transport |
| ) |
| { |
| $this->properties['pathFactory'] = $pathFactory; |
| $this->properties['xmlTool'] = $xmlTool; |
| $this->properties['propertyHandler'] = $propertyHandler; |
| $this->properties['headerHandler'] = $headerHandler; |
| $this->properties['transport'] = $transport; |
| } |
| |
| /** |
| * Reset the server to its initial state. |
| * |
| * Resets the internal server state as if a new instance has just been |
| * constructed. |
| * |
| * @return void |
| */ |
| public function reset() |
| { |
| unset( $this->properties['configurations'] ); |
| unset( $this->properties['pluginRegistry'] ); |
| $this->properties['configurations'] = new ezcWebdavServerConfigurationManager(); |
| $this->properties['pluginRegistry'] = new ezcWebdavPluginRegistry(); |
| $this->properties['auth'] = null; |
| $this->properties['options'] = new ezcWebdavServerOptions(); |
| |
| $this->properties['transport'] = null; |
| $this->properties['backend'] = null; |
| $this->properties['pathFactory'] = null; |
| $this->properties['xmlTool'] = null; |
| $this->properties['propertyHandler'] = null; |
| $this->properties['headerHandler'] = null; |
| } |
| |
| /** |
| * Performs authentication and authorization. |
| * |
| * @param ezcWebdavRequest $req |
| * @return ezcWebdavErrorResponse|null |
| */ |
| private function authenticate( ezcWebdavRequest $req ) |
| { |
| if ( $this->properties['auth'] === null ) |
| { |
| // No authentication |
| return null; |
| } |
| |
| $creds = $req->getHeader( 'Authorization' ); |
| |
| $res = null; |
| // Authenticate user |
| |
| switch ( get_class( $creds ) ) |
| { |
| case 'ezcWebdavAnonymousAuth': |
| if ( $this->properties['auth'] instanceof ezcWebdavAnonymousAuthenticator ) |
| { |
| $res = $this->properties['auth']->authenticateAnonymous( $creds ); |
| } |
| break; |
| case 'ezcWebdavBasicAuth': |
| if ( $this->properties['auth'] instanceof ezcWebdavBasicAuthenticator ) |
| { |
| $res = $this->properties['auth']->authenticateBasic( $creds ); |
| } |
| break; |
| case 'ezcWebdavDigestAuth': |
| if ( $this->properties['auth'] instanceof ezcWebdavDigestAuthenticator ) |
| { |
| $res = $this->properties['auth']->authenticateDigest( $creds ); |
| } |
| break; |
| } |
| |
| // $res is now null or bool, if not evaluates to true, authentication failed |
| if ( !$res ) |
| { |
| return $this->createUnauthenticatedResponse( |
| $req->requestUri, 'Authentication failed.' |
| ); |
| } |
| |
| return null; |
| } |
| |
| /** |
| * Performs authorization. |
| * |
| * This method does several things: |
| * |
| * - Check if authorization is enabled by ezcWebdavServer->$auth |
| * - If it is, extract username from Authenticate header or choose '' |
| * - Check authorization |
| * |
| * It returns true, if authorization is not enabled or succeeded. False is |
| * returned otherwise. |
| * |
| * @param string $path |
| * @param ezcWebdavAuth $credentials |
| * @param int $access |
| * @return bool |
| * |
| * @todo Mark protected as soon as API is final. |
| * @access private |
| */ |
| public function isAuthorized( $path, ezcWebdavAuth $credentials, $access = ezcWebdavAuthorizer::ACCESS_READ ) |
| { |
| $auth = $this->auth; |
| |
| if ( $auth === null || !( $auth instanceof ezcWebdavAuthorizer ) ) |
| { |
| // No auth mechanism |
| return true; |
| } |
| |
| return $auth->authorize( $credentials->username, $path, $access ); |
| } |
| |
| /** |
| * Creates an ezcWebdavErrorResponse to indicate the need for authentication. |
| * |
| * Creates a {@link ezcWebdavErrorResponse} object with status code {@link |
| * ezcWebdavResponse::STATUS_401} and a corresponding WWW-Authenticate |
| * header using the $realm define in {@link ezcWebdavServerOptions}. The |
| * $uri and $desc parameters are used to create the error response. |
| * |
| * @param string $uri |
| * @param string $desc |
| * |
| * @return ezcWebdavErrorResponse |
| * |
| * @access private |
| */ |
| public function createUnauthenticatedResponse( $uri, $desc ) |
| { |
| $res = new ezcWebdavErrorResponse( ezcWebdavResponse::STATUS_401, $uri, $desc ); |
| $wwwAuthHeader = array( |
| 'basic' => 'Basic realm="' . $this->options->realm . '"', |
| ); |
| if ( $this->properties['auth'] instanceof ezcWebdavDigestAuthenticator ) |
| { |
| $wwwAuthHeader['digest'] = 'Digest realm="' .$this->options->realm . '"' |
| . ', nonce="' . $this->getNonce() . '"' |
| . ', algorithm="MD5"'; |
| // @todo Do we want an opaque value here, too? |
| } |
| $res->setHeader( 'WWW-Authenticate', $wwwAuthHeader ); |
| |
| return $res; |
| } |
| |
| /** |
| * Creates an ezcWebdavErrorResponse to indicate unauthorized access. |
| * |
| * Creates a {@link ezcWebdavErrorResponse} object with status code {@link |
| * ezcWebdavResponse::STATUS_403}. The $uri and $desc parameters are used |
| * to create the error response. |
| * |
| * @param string $uri |
| * @param string $desc |
| * |
| * @return ezcWebdavErrorResponse |
| * |
| * @access private |
| */ |
| public function createUnauthorizedResponse( $uri, $desc ) |
| { |
| return new ezcWebdavErrorResponse( ezcWebdavResponse::STATUS_403, $uri, $desc ); |
| } |
| |
| /** |
| * Creates a unique, hard to guess nounce value. |
| * |
| * @return string |
| */ |
| private function getNonce() |
| { |
| // This should be random enough that it cannot be guessed easily |
| return md5( |
| $this->options->realm |
| . ':' . microtime() |
| . ':' . $_SERVER['SERVER_NAME'] |
| . ':' . uniqid( mt_rand(), true ) |
| ); |
| } |
| |
| /** |
| * Sets a property. |
| * |
| * This method is called when an property is to be set. |
| * |
| * @param string $propertyName The name of the property to set. |
| * @param mixed $propertyValue The property value. |
| * @return void |
| * @ignore |
| * |
| * @throws ezcBasePropertyNotFoundException |
| * if the given property does not exist. |
| * @throws ezcBaseValueException |
| * if the value to be assigned to a property is invalid. |
| * @throws ezcBasePropertyPermissionException |
| * if the property to be set is a read-only property. |
| */ |
| public function __set( $propertyName, $propertyValue ) |
| { |
| switch ( $propertyName ) |
| { |
| case 'configurations': |
| if ( !( $propertyValue instanceof ezcWebdavServerConfigurationManager ) ) |
| { |
| throw new ezcBaseValueException( $propertyName, $propertyValue, 'ezcWebdavServerConfigurationManager' ); |
| } |
| break; |
| case 'auth': |
| if ( $propertyValue !== null |
| && ( !is_object( $propertyValue ) || !( $propertyValue instanceof ezcWebdavBasicAuthenticator ) ) |
| ) |
| { |
| throw new ezcBaseValueException( $propertyName, $propertyValue, 'ezcWebdavBasicAuthenticator and/or ezcWebdavDigestAuthenticator' ); |
| } |
| break; |
| case 'options': |
| if ( !( $propertyValue instanceof ezcWebdavServerOptions ) ) |
| { |
| throw new ezcBaseValueException( $propertyName, $propertyValue, 'ezcWebdavServerOptions' ); |
| } |
| break; |
| case 'backend': |
| case 'pluginRegistry': |
| case 'pathFactory': |
| case 'xmlTool': |
| case 'propertyHandler': |
| case 'headerHandler': |
| case 'transport': |
| throw new ezcBasePropertyPermissionException( $propertyName, ezcBasePropertyPermissionException::READ ); |
| |
| default: |
| throw new ezcBasePropertyNotFoundException( $propertyName ); |
| } |
| $this->properties[$propertyName] = $propertyValue; |
| } |
| |
| /** |
| * Property get access. |
| * |
| * Simply returns a given property. |
| * |
| * @param string $propertyName The name of the property to get. |
| * @return mixed The property value. |
| * |
| * @ignore |
| * |
| * @throws ezcBasePropertyNotFoundException |
| * if the given property does not exist. |
| * @throws ezcBasePropertyPermissionException |
| * if the property to be set is a write-only property. |
| */ |
| public function __get( $propertyName ) |
| { |
| if ( $this->__isset( $propertyName ) ) |
| { |
| return $this->properties[$propertyName]; |
| } |
| throw new ezcBasePropertyNotFoundException( $propertyName ); |
| } |
| |
| /** |
| * Returns if a property exists. |
| * |
| * Returns true if the property exists in the {@link $properties} array |
| * (even if it is null) and false otherwise. |
| * |
| * @param string $propertyName Option name to check for. |
| * @return void |
| * @ignore |
| */ |
| public function __isset( $propertyName ) |
| { |
| return array_key_exists( $propertyName, $this->properties ); |
| } |
| } |
| |
| ?> |