| <?php |
| /** |
| * File containing the ezcWebdavMemoryBackend 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 |
| * @access private |
| */ |
| /** |
| * Backend that only resides in memory. |
| * |
| * Memory backend to serve some virtual content tree, offering options to cause |
| * failures in operations, mainly for testing the webdav server. |
| * |
| * The content of the backend is constructed from a multidimentional array |
| * structure representing the collections and files. The metadata may only be |
| * set by appropriate requests to the backend. No information is stored |
| * anywhere, so that every reinitialisations gives you a fresh backend. |
| * |
| * <code> |
| * $backend = new ezcWebdavMemoryBackend(); |
| * $backend->addContents( |
| * array( |
| * 'foo' => 'bar', // File with content "bar" |
| * 'bar' => array( // Collection "bar" |
| * 'blubb' => 'Some more content.' |
| * // File bar/blubb with some more content |
| * ), |
| * ) |
| * ); |
| * </code> |
| * |
| * This backend does not implement any special features to test the servers |
| * capabilities to work with those features. |
| * |
| * @version //autogentag// |
| * @package Webdav |
| * @access private |
| */ |
| class ezcWebdavMemoryBackend extends ezcWebdavSimpleBackend implements ezcWebdavLockBackend |
| { |
| /** |
| * Options of the memory backend |
| * |
| * @var ezcWebdavMemoryBackendOptions |
| */ |
| protected $options; |
| |
| /** |
| * Content structure of memory backend |
| * |
| * @var array |
| */ |
| protected $content = array( |
| '/' => array(), |
| ); |
| |
| /** |
| * Properties for collections and resources. |
| * |
| * They are stored in an array of the following form reusing the initial |
| * content example: |
| * |
| * array( |
| * '/foo' => array( |
| * 'property name' => 'property value', |
| * ), |
| * '/bar' => array(), |
| * '/bar/blubb' => array(), |
| * ... |
| * ) |
| * |
| * @var array |
| */ |
| protected $props = array(); |
| |
| /** |
| * Indicates wheather to fake live properties. |
| * |
| * @var bool |
| */ |
| protected $fakeLiveProperties; |
| |
| /** |
| * Construct backend from a given path. |
| * |
| * @param bool $fakeLiveProperties |
| * @return void |
| */ |
| public function __construct( $fakeLiveProperties = true ) |
| { |
| $this->options = new ezcWebdavMemoryBackendOptions(); |
| |
| $this->fakeLiveProperties = $fakeLiveProperties; |
| |
| // Initialize properties for root |
| if ( $fakeLiveProperties ) |
| { |
| $this->props['/'] = $this->initializeProperties( '/', true ); |
| } |
| } |
| |
| /** |
| * Acquire a backend lock. |
| * |
| * Locks the complete backend using the lock file specified in {@link |
| * ezcWebdavMemoryBackendOptions->$lockFile}. |
| * |
| * @param int $waitTime Microseconds. |
| * @param int $timeout Microseconds. |
| * @return void |
| */ |
| public function lock( $waitTime, $timeout ) |
| { |
| $lockFile = $this->options->lockFile; |
| |
| $lockStart = microtime( true ); |
| |
| // fopen in mode 'x' will only open the file, if it does not exist yet. |
| // Even this is is expected it will throw a warning, if the file |
| // exists, which we need to silence using the @ |
| while ( ( $fp = @fopen( $lockFile, 'x' ) ) === false ) |
| { |
| // This is untestable. |
| if ( microtime( true ) - $lockStart > $timeout ) |
| { |
| // Release timed out lock |
| unlink( $lockFile ); |
| $lockStart = microtime( true ); |
| } |
| else |
| { |
| usleep( $waitTime ); |
| } |
| } |
| |
| // Store random bit in file ... the microtime for example - might prove |
| // useful some time. |
| fwrite( $fp, microtime() ); |
| fclose( $fp ); |
| } |
| |
| /** |
| * Release the backend lock. |
| * |
| * Releases the lock acquired by {@link lock()}. |
| * |
| * @return void |
| */ |
| public function unlock() |
| { |
| // Silence since lock maybe released if request processing takes too |
| // long |
| @unlink( $this->options->lockFile ); |
| } |
| |
| /** |
| * Offer access to some of the server properties. |
| * |
| * @throws ezcBasePropertyNotFoundException |
| * If the property $name is not defined |
| * @param string $name |
| * @return mixed |
| * @ignore |
| */ |
| public function __get( $name ) |
| { |
| switch ( $name ) |
| { |
| case 'options': |
| return $this->$name; |
| |
| default: |
| throw new ezcBasePropertyNotFoundException( $name ); |
| } |
| } |
| |
| /** |
| * Sets the option $name to $value. |
| * |
| * @throws ezcBasePropertyNotFoundException |
| * if the property $name is not defined |
| * @throws ezcBaseValueException |
| * if $value is not correct for the property $name |
| * @param string $name |
| * @param mixed $value |
| * @return void |
| * @ignore |
| */ |
| public function __set( $name, $value ) |
| { |
| switch ( $name ) |
| { |
| case 'options': |
| if ( ! $value instanceof ezcWebdavMemoryBackendOptions ) |
| { |
| throw new ezcBaseValueException( $name, $value, 'ezcWebdavMemoryBackendOptions' ); |
| } |
| |
| $this->$name = $value; |
| break; |
| |
| default: |
| throw new ezcBasePropertyNotFoundException( $name ); |
| } |
| } |
| |
| /** |
| * Return an initial set of properties for resources and collections. |
| * |
| * The second parameter indicates wheather the given resource is a |
| * collection. The returned properties are used to initialize the property |
| * arrays for the given content. |
| * |
| * @param string $name |
| * @param bool $isCollection |
| * @return array |
| * |
| * @access protected |
| */ |
| public function initializeProperties( $name, $isCollection = false ) |
| { |
| if ( $this->fakeLiveProperties ) |
| { |
| $propertyStorage = new ezcWebdavBasicPropertyStorage(); |
| |
| // Add default creation date |
| $propertyStorage->attach( |
| new ezcWebdavCreationDateProperty( new ezcWebdavDateTime( '@1054034820' ) ) |
| ); |
| |
| // Define default display name |
| $propertyStorage->attach( |
| new ezcWebdavDisplayNameProperty( basename( urldecode( $name ) ) ) |
| ); |
| |
| // Define default language |
| $propertyStorage->attach( |
| new ezcWebdavGetContentLanguageProperty( array( 'en' ) ) |
| ); |
| |
| // Define default content type |
| $propertyStorage->attach( |
| new ezcWebdavGetContentTypeProperty( |
| $isCollection ? 'httpd/unix-directory' : 'application/octet-stream' |
| ) |
| ); |
| |
| // Define default ETag |
| $propertyStorage->attach( |
| new ezcWebdavGetEtagProperty( $this->getETag( $name ) ) |
| ); |
| |
| // Define default modification time |
| $propertyStorage->attach( |
| new ezcWebdavGetLastModifiedProperty( new ezcWebdavDateTime( '@1124118780' ) ) |
| ); |
| |
| // Define content length if node is a resource. |
| $propertyStorage->attach( |
| new ezcWebdavGetContentLengthProperty( |
| $isCollection ? |
| ezcWebdavGetContentLengthProperty::COLLECTION : |
| (string) strlen( $this->content[$name] ) |
| ) |
| ); |
| |
| $propertyStorage->attach( |
| new ezcWebdavResourceTypeProperty( |
| ( $isCollection === true ? |
| ezcWebdavResourceTypeProperty::TYPE_COLLECTION : |
| ezcWebdavResourceTypeProperty::TYPE_RESOURCE |
| ) |
| ) |
| ); |
| } |
| else |
| { |
| $propertyStorage = new ezcWebdavBasicPropertyStorage(); |
| } |
| |
| return $propertyStorage; |
| } |
| |
| /** |
| * Clones the given $fromStorage for $toPath. |
| * |
| * Initializes a new property storage for $toPath with new live properties |
| * and clones all non exsitent properties from $fromStorage to it. |
| * |
| * @param string $toPath |
| * @param bool $isCollection |
| * @param ezcWebdavBasicPropertyStorage $fromStorage |
| * @return void |
| */ |
| private function cloneProperties( $toPath, $isCollection, ezcWebdavBasicPropertyStorage $fromStorage ) |
| { |
| $toStorage = $this->initializeProperties( $toPath, $isCollection ); |
| |
| foreach ( $fromStorage as $prop ) |
| { |
| if ( !$toStorage->contains( $prop->name, $prop->namespace ) ) |
| { |
| $toStorage->attach( clone $prop ); |
| } |
| } |
| $this->props[$toPath] = $toStorage; |
| } |
| |
| /** |
| * Overwrites ETag generation from simple backend. |
| * |
| * Generates an ETag based on $path and the content of $path (if available |
| * and not a collection). |
| * |
| * @param string $path |
| * @return string |
| */ |
| protected function getETag( $path ) |
| { |
| return ( md5( $path ) ); |
| } |
| |
| /** |
| * Read valid data from given content array and initialize property |
| * storage. |
| * |
| * @param array $contents |
| * @param string $path |
| * @return void |
| */ |
| public function addContents( array $contents, $path = '/' ) |
| { |
| foreach ( $contents as $name => $content ) |
| { |
| if ( !is_string( $name ) ) |
| { |
| // Ignore elements which do not have a string key |
| continue; |
| } |
| |
| // Full path to resource |
| $resourcePath = $path . $name; |
| |
| if ( is_array( $content ) ) |
| { |
| // Content is a collection |
| $this->content[$resourcePath] = array(); |
| $this->props[$resourcePath] = $this->initializeProperties( |
| $resourcePath, |
| true |
| ); |
| |
| // Recurse |
| $this->addContents( $content, $resourcePath . '/' ); |
| } |
| elseif ( is_string( $content ) ) |
| { |
| // Content is a file |
| $this->content[$resourcePath] = $content; |
| $this->props[$resourcePath] = $this->initializeProperties( |
| $resourcePath |
| ); |
| } |
| else |
| { |
| // Ignore everything else... |
| continue; |
| } |
| |
| // Add contents to parent directory |
| $parent = ( $path === '/' ? '/' : substr( $path, 0, -1 ) ); |
| $this->content[$parent][] = $resourcePath; |
| } |
| } |
| |
| /** |
| * Create a new collection. |
| * |
| * Creates a new collection at the given path. |
| * |
| * @param string $path |
| * @return void |
| */ |
| protected function createCollection( $path ) |
| { |
| // Create collection |
| $this->content[$path] = array(); |
| |
| // Add collection to parent node |
| $this->content[dirname( $path )][] = $path; |
| |
| // Set initial metadata for collection |
| $this->props[$path] = $this->initializeProperties( $path, true ); |
| } |
| |
| /** |
| * Create a new resource. |
| * |
| * Creates a new resource at the given path, optionally with the given |
| * content. |
| * |
| * @param string $path |
| * @param string $content |
| * @return void |
| */ |
| protected function createResource( $path, $content = null ) |
| { |
| // Create resource |
| $this->content[$path] = $content; |
| |
| // Add resource to parent node |
| $this->content[dirname( $path )][] = $path; |
| |
| // Set initial metadata for collection |
| $this->props[$path] = $this->initializeProperties( $path, false ); |
| } |
| |
| /** |
| * Set contents of a resource. |
| * |
| * Change the contents of the given resource to the given content. |
| * |
| * @param string $path |
| * @param string $content |
| * @return void |
| */ |
| protected function setResourceContents( $path, $content ) |
| { |
| $this->content[$path] = $content; |
| } |
| |
| /** |
| * Get contents of a resource. |
| * |
| * @param string $path |
| * @return string |
| */ |
| protected function getResourceContents( $path ) |
| { |
| return $this->content[$path]; |
| } |
| |
| /** |
| * Manually set a property on a resource to request it later. |
| * |
| * @param string $resource |
| * @param ezcWebdavProperty $property |
| * @return bool |
| */ |
| public function setProperty( $resource, ezcWebdavProperty $property ) |
| { |
| // Live properties may not be updated by a client. |
| if ( $this->options->failingOperations & ezcWebdavMemoryBackendOptions::REQUEST_PROPPATCH ) |
| { |
| return false; |
| } |
| |
| // Bail out, if the resource is not known yet. |
| if ( !array_key_exists( $resource, $this->props ) ) |
| { |
| return false; |
| } |
| |
| $this->props[$resource]->attach( $property ); |
| return true; |
| } |
| |
| /** |
| * Manually remove a property from a resource. |
| * |
| * @param string $resource |
| * @param ezcWebdavProperty $property |
| * @return bool |
| */ |
| public function removeProperty( $resource, ezcWebdavProperty $property ) |
| { |
| // Live properties may not be removed. |
| if ( $property instanceof ezcWebdavLiveProperty ) |
| { |
| return false; |
| } |
| |
| $this->props[$resource]->detach( $property->name, $property->namespace ); |
| return true; |
| } |
| |
| /** |
| * Reset property storage for a resource. |
| * |
| * @param string $resource |
| * @param ezcWebdavPropertyStorage $properties |
| * @return bool |
| */ |
| public function resetProperties( $resource, ezcWebdavPropertyStorage $properties ) |
| { |
| $this->props[$resource] = $properties; |
| } |
| |
| /** |
| * Manually get a property on a resource. |
| * |
| * Get the property with the given name from the given resource. You may |
| * optionally define a namespace to receive the property from. |
| * |
| * @param string $resource |
| * @param string $propertyName |
| * @param string $namespace |
| * @return ezcWebdavProperty |
| */ |
| public function getProperty( $resource, $propertyName, $namespace = 'DAV:' ) |
| { |
| return $this->props[$resource]->get( $propertyName, $namespace ); |
| } |
| |
| /** |
| * Manually get a property on a resource. |
| * |
| * Get all properties for the given resource as a {@link |
| * ezcWebdavPropertyStorage} |
| * |
| * @param string $resource |
| * @return ezcWebdavBasicPropertyStorage |
| */ |
| public function getAllProperties( $resource ) |
| { |
| return $this->props[$resource]; |
| } |
| |
| /** |
| * Copy resources recursively from one path to another. |
| * |
| * Returns an array with {@link ezcWebdavErrorResponse}s for all subtree, |
| * where the copy operation failed. Errors subsequent nodes in a subtree |
| * should be ommitted. |
| * |
| * If an empty array is return, the operation has been completed |
| * successfully. |
| * |
| * @param string $fromPath |
| * @param string $toPath |
| * @param int $depth |
| * @return array(ezcWebdavErrorResponse) |
| */ |
| protected function performCopy( $fromPath, $toPath, $depth = ezcWebdavRequest::DEPTH_INFINITY ) |
| { |
| $causeErrors = (bool) ( $this->options->failingOperations & ( ezcWebdavMemoryBackendOptions::REQUEST_COPY | ezcWebdavMemoryBackendOptions::REQUEST_MOVE ) ); |
| $errors = array(); |
| |
| if ( !is_array( $this->content[$fromPath] ) || |
| ( is_array( $this->content[$fromPath] ) && ( $depth === ezcWebdavRequest::DEPTH_ZERO ) ) ) |
| { |
| // Copy a resource, or a collection, but the depth header told not |
| // to recurse into collections |
| if ( $causeErrors && preg_match( $this->options->failForRegexp, $fromPath ) > 0 ) |
| { |
| // Completely abort with error |
| return array( ezcWebdavErrorResponse( |
| ezcWebdavResponse::STATUS_423, |
| $fromPath |
| ) ); |
| } |
| if ( $causeErrors && preg_match( $this->options->failForRegexp, $toPath ) > 0 ) |
| { |
| // Completely abort with error |
| return array( ezcWebdavErrorResponse( |
| ezcWebdavResponse::STATUS_412, |
| $toPath |
| ) ); |
| } |
| |
| // Perform copy operation |
| if ( is_array( $this->content[$fromPath] ) ) |
| { |
| // Create a new empty collection |
| $this->content[$toPath] = array(); |
| } |
| else |
| { |
| // Copy file content |
| $this->content[$toPath] = $this->content[$fromPath]; |
| } |
| |
| // Copy properties |
| $this->cloneProperties( |
| $toPath, |
| is_array( $this->content[$toPath] ), |
| $this->props[$fromPath] |
| ); |
| |
| // Add to parent node |
| $this->content[dirname( $toPath )][] = $toPath; |
| } |
| else |
| { |
| // Copy a collection |
| $errnousSubtrees = array(); |
| |
| // Array of copied collections, where the child names are required |
| // to be modified depending on the success of the copy operation. |
| $copiedCollections = array(); |
| |
| // Check all nodes, if they math the fromPath |
| foreach ( $this->content as $resource => $content ) |
| { |
| if ( strpos( $resource, $fromPath ) !== 0 ) |
| { |
| // This resource is not affected by the copy operation |
| continue; |
| } |
| |
| // Check if this resource should be skipped, because |
| // already one of the parent nodes caused an error. |
| foreach ( $errnousSubtrees as $subtree ) |
| { |
| if ( strpos( $resource, $subtree ) ) |
| { |
| // Skip resource, then. |
| continue 2; |
| } |
| } |
| |
| // Check if this resource should cause an error |
| if ( $causeErrors && preg_match( $this->options->failForRegexp, $resource ) ) |
| { |
| // Cause an error and skip resource |
| $errors[] = new ezcWebdavErrorResponse( |
| ezcWebdavResponse::STATUS_423, |
| $resource |
| ); |
| continue; |
| } |
| |
| // To actually perform the copy operation, modify the |
| // destination resource name |
| $newResourceName = preg_replace( '(^' . preg_quote( $fromPath ) . ')', $toPath, $resource ); |
| |
| // Check if this resource should cause an error |
| if ( $causeErrors && preg_match( $this->options->failForRegexp, $newResourceName ) ) |
| { |
| // Cause an error and skip resource |
| $errors[] = new ezcWebdavErrorResponse( |
| ezcWebdavResponse::STATUS_412, |
| $newResourceName |
| ); |
| continue; |
| } |
| |
| // Add collection to collection child recalculation array |
| if ( is_array( $this->content[$resource] ) ) |
| { |
| $copiedCollections[] = $newResourceName; |
| } |
| |
| // Actually copy |
| $this->content[$newResourceName] = $this->content[$resource]; |
| |
| // Copy properties |
| $this->cloneProperties( |
| $newResourceName, |
| is_array( $this->content[$resource] ), |
| $this->props[$resource] |
| ); |
| |
| // Add to parent node |
| $this->content[dirname( $newResourceName )][] = $newResourceName; |
| } |
| |
| // Iterate over all copied collections and update the child |
| // references |
| foreach ( $copiedCollections as $collection ) |
| { |
| foreach ( $this->content[$collection] as $nr => $child ) |
| { |
| foreach ( $errnousSubtrees as $subtree ) |
| { |
| if ( strpos( $child, $subtree ) ) |
| { |
| // If child caused an error, it has not been |
| // copied, so we remove it. |
| unset( $this->content[$collection][$nr] ); |
| continue 2; |
| } |
| } |
| |
| // Also remove all references to old children, new children |
| // have already been added during the last step. |
| if ( preg_match( '(^' . preg_quote( $fromPath ) . ')', $child ) ) |
| { |
| unset( $this->content[$collection][$nr] ); |
| } |
| } |
| |
| $this->content[$collection] = array_values( $this->content[$collection] ); |
| } |
| } |
| |
| return $errors; |
| } |
| |
| /** |
| * Delete everything below this path. |
| * |
| * Returns an error response if the deletion failed, and null on success. |
| * |
| * @param string $path |
| * @return ezcWebdavErrorResponse |
| */ |
| protected function performDelete( $path ) |
| { |
| // Check if any errors would occur during deletion process |
| $error = array(); |
| foreach ( $this->content as $name => $content ) |
| { |
| if ( strpos( $name, $path ) === 0 && ( substr( $name, strlen( $path ), 1 ) === '/' || $name === $path ) ) |
| { |
| // Check if we want to cause some errors here. |
| if ( $this->options->failingOperations & ezcWebdavMemoryBackendOptions::REQUEST_DELETE && preg_match( $this->options->failForRegexp, $name ) > 0 ) |
| { |
| $error[] = new ezcWebdavErrorResponse( |
| ezcWebdavResponse::STATUS_423, |
| $name |
| ); |
| } |
| } |
| } |
| |
| // If errors occured, return them |
| if ( count( $error ) ) |
| { |
| return new ezcWebdavMultistatusResponse( |
| $error |
| ); |
| } |
| |
| // Remove all content nodes starting with requested path |
| foreach ( $this->content as $name => $content ) |
| { |
| if ( strpos( $name, $path ) === 0 && ( substr( $name, strlen( $path ), 1 ) === '/' || $name === $path ) ) |
| { |
| unset( $this->content[$name] ); |
| unset( $this->props[$name] ); |
| } |
| } |
| |
| // Remove parent node assignement to removed node |
| $id = array_search( $path, $this->content[$parent = dirname( $path )] ); |
| if ( $id !== false ) |
| { |
| unset( $this->content[$parent][$id] ); |
| $this->content[$parent] = array_values( $this->content[$parent] ); |
| } |
| |
| return null; |
| } |
| |
| /** |
| * Check if node exists. |
| * |
| * Check if a node exists with the given path. |
| * |
| * @param string $path |
| * @return bool |
| * |
| * @access protected |
| */ |
| public function nodeExists( $path ) |
| { |
| return isset( $this->content[$path] ); |
| } |
| |
| /** |
| * Check if node is a collection. |
| * |
| * Check if the node behind the given path is a collection. |
| * |
| * @param string $path |
| * @return bool |
| * |
| * @access protected |
| */ |
| public function isCollection( $path ) |
| { |
| return $this->nodeExists( $path ) && is_array( $this->content[$path] ); |
| } |
| |
| /** |
| * Get members of collection. |
| * |
| * Returns an array with the members of the collection given by the path of |
| * the collection. |
| * |
| * The returned array holds elements which are either ezcWebdavCollection, |
| * or ezcWebdavResource. |
| * |
| * @param string $path |
| * @return array |
| */ |
| protected function getCollectionMembers( $path ) |
| { |
| $contents = array(); |
| |
| foreach ( $this->content[$path] as $child ) |
| { |
| if ( is_array( $this->content[$child] ) ) |
| { |
| // Add collection without any children |
| $contents[] = new ezcWebdavCollection( |
| $child |
| ); |
| } |
| else |
| { |
| // Add files without content |
| $contents[] = new ezcWebdavResource( |
| $child |
| ); |
| } |
| } |
| |
| return $contents; |
| } |
| |
| /** |
| * Clones the memory backend deeply. |
| * |
| * @return void |
| */ |
| public function __clone() |
| { |
| foreach ( $this->props as $path => $propStorage ) |
| { |
| $this->props[$path] = clone $propStorage; |
| } |
| } |
| } |
| |
| ?> |