PageRenderTime 56ms CodeModel.GetById 22ms RepoModel.GetById 1ms app.codeStats 0ms

/Webdav/src/backends/file.php

https://github.com/Yannix/zetacomponents
PHP | 1306 lines | 544 code | 129 blank | 633 comment | 44 complexity | 8bf534bba0ea7d2f0a0ad0dcdcbb469d MD5 | raw file
  1. <?php
  2. /**
  3. * File containing the ezcWebdavFileBackend class.
  4. *
  5. * Licensed to the Apache Software Foundation (ASF) under one
  6. * or more contributor license agreements. See the NOTICE file
  7. * distributed with this work for additional information
  8. * regarding copyright ownership. The ASF licenses this file
  9. * to you under the Apache License, Version 2.0 (the
  10. * "License"); you may not use this file except in compliance
  11. * with the License. You may obtain a copy of the License at
  12. *
  13. * http://www.apache.org/licenses/LICENSE-2.0
  14. *
  15. * Unless required by applicable law or agreed to in writing,
  16. * software distributed under the License is distributed on an
  17. * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
  18. * KIND, either express or implied. See the License for the
  19. * specific language governing permissions and limitations
  20. * under the License.
  21. *
  22. * @package Webdav
  23. * @version //autogentag//
  24. * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License, Version 2.0
  25. */
  26. /**
  27. * File system based backend.
  28. *
  29. * This backend serves WebDAV resources from a directory structure in the file
  30. * system. It simply handles directories as collection resources and files as
  31. * non-collection resources. The path to server resources from is defined
  32. * during construction.
  33. *
  34. * <code>
  35. * $backend = new ezcWebdavFileBackend(
  36. * 'directory/'
  37. * );
  38. * </code>
  39. *
  40. * Live properties are partly determined from the file systems itself (like
  41. * {@link ezcWebdavGetContentLengthProperty}), others need to be stored like
  42. * dead properties. This backend uses a special path for each resource to store
  43. * this information in its XML representation.
  44. *
  45. * @version //autogentag//
  46. * @package Webdav
  47. * @mainclass
  48. */
  49. class ezcWebdavFileBackend extends ezcWebdavSimpleBackend implements ezcWebdavLockBackend
  50. {
  51. /**
  52. * Options.
  53. *
  54. * @var ezcWebdavFileBackendOptions
  55. */
  56. protected $options;
  57. /**
  58. * Root directory to serve content from. All paths are seen relatively to this one.
  59. *
  60. * @var string
  61. */
  62. protected $root;
  63. /**
  64. * Keeps track of the lock level.
  65. *
  66. * Each time the lock() method is called, this counter is raised by 1. if
  67. * it was 0 before, the actual locking mechanism gets into action,
  68. * otherwise just the counter is raised. The lock is physically only freed,
  69. * if this counter is 0.
  70. *
  71. * This mechanism allows nested locking, as it is necessary, if the lock
  72. * plugin locks this backend external, but interal locking needs still to
  73. * be supported.
  74. *
  75. * @var int
  76. */
  77. protected $lockLevel = 0;
  78. /**
  79. * Names of live properties from the DAV: namespace which will be handled
  80. * live, and should not be stored like dead properties.
  81. *
  82. * @var array(int=>string)
  83. */
  84. protected $handledLiveProperties = array(
  85. 'getcontentlength',
  86. 'getlastmodified',
  87. 'creationdate',
  88. 'displayname',
  89. 'getetag',
  90. 'getcontenttype',
  91. 'resourcetype',
  92. 'supportedlock',
  93. 'lockdiscovery',
  94. );
  95. /**
  96. * Creates a new backend instance.
  97. *
  98. * Creates a new backend to server WebDAV content from the file system path
  99. * identified by $root. If the given path does not exist or is not a
  100. * directory, an exception will be thrown.
  101. *
  102. * @param string $root
  103. * @return void
  104. *
  105. * @throws ezcBaseFileNotFoundException
  106. * if the given $root does not exist or is not a directory.
  107. * @throws ezcBaseFilePermissionException
  108. * if the given $root is not readable.
  109. */
  110. public function __construct( $root )
  111. {
  112. if ( !is_dir( $root ) )
  113. {
  114. throw new ezcBaseFileNotFoundException( $root );
  115. }
  116. if ( !is_readable( $root ) )
  117. {
  118. throw new ezcBaseFilePermissionException( $root, ezcBaseFileException::READ );
  119. }
  120. $this->root = realpath( $root );
  121. $this->options = new ezcWebdavFileBackendOptions();
  122. }
  123. /**
  124. * Locks the backend.
  125. *
  126. * Tries to lock the backend. If the lock is already owned by this process,
  127. * locking is successful. If $timeout is reached before a lock could be
  128. * acquired, an {@link ezcWebdavLockTimeoutException} is thrown. Waits
  129. * $waitTime microseconds between attempts to lock the backend.
  130. *
  131. * @param int $waitTime
  132. * @param int $timeout
  133. * @return void
  134. */
  135. public function lock( $waitTime, $timeout )
  136. {
  137. // Check and raise lockLevel counter
  138. if ( $this->lockLevel > 0 )
  139. {
  140. // Lock already acquired
  141. ++$this->lockLevel;
  142. return;
  143. }
  144. $lockStart = microtime( true );
  145. $lockFileName = $this->root . '/' . $this->options->lockFileName;
  146. if ( is_file( $lockFileName ) && !is_writable( $lockFileName )
  147. || !is_file( $lockFileName ) && !is_writable(dirname( $lockFileName ) ) )
  148. {
  149. throw new ezcBaseFilePermissionException(
  150. $lockFileName,
  151. ezcBaseFileException::WRITE,
  152. 'Cannot be used as lock file.'
  153. );
  154. }
  155. // fopen in mode 'x' will only open the file, if it does not exist yet.
  156. // Even this is is expected it will throw a warning, if the file
  157. // exists, which we need to silence using the @
  158. while ( ( $fp = @fopen( $lockFileName, 'x' ) ) === false )
  159. {
  160. // This is untestable.
  161. if ( microtime( true ) - $lockStart > $timeout )
  162. {
  163. // Release timed out lock
  164. unlink( $lockFileName );
  165. $lockStart = microtime( true );
  166. }
  167. else
  168. {
  169. usleep( $waitTime );
  170. }
  171. }
  172. // Store random bit in file ... the microtime for example - might prove
  173. // useful some time.
  174. fwrite( $fp, microtime() );
  175. fclose( $fp );
  176. // Add first lock
  177. ++$this->lockLevel;
  178. }
  179. /**
  180. * Removes the lock.
  181. *
  182. * @return void
  183. */
  184. public function unlock()
  185. {
  186. if ( --$this->lockLevel === 0 )
  187. {
  188. // Remove the lock file
  189. $lockFileName = $this->root . '/' . $this->options->lockFileName;
  190. unlink( $lockFileName );
  191. }
  192. }
  193. /**
  194. * Property get access.
  195. * Simply returns a given property.
  196. *
  197. * @throws ezcBasePropertyNotFoundException
  198. * If a the value for the property propertys is not an instance of
  199. * @param string $name The name of the property to get.
  200. * @return mixed The property value.
  201. *
  202. * @ignore
  203. *
  204. * @throws ezcBasePropertyNotFoundException
  205. * if the given property does not exist.
  206. * @throws ezcBasePropertyPermissionException
  207. * if the property to be set is a write-only property.
  208. */
  209. public function __get( $name )
  210. {
  211. switch ( $name )
  212. {
  213. case 'options':
  214. return $this->$name;
  215. default:
  216. throw new ezcBasePropertyNotFoundException( $name );
  217. }
  218. }
  219. /**
  220. * Sets a property.
  221. * This method is called when an property is to be set.
  222. *
  223. * @param string $name The name of the property to set.
  224. * @param mixed $value The property value.
  225. * @return void
  226. * @ignore
  227. *
  228. * @throws ezcBasePropertyNotFoundException
  229. * if the given property does not exist.
  230. * @throws ezcBaseValueException
  231. * if the value to be assigned to a property is invalid.
  232. * @throws ezcBasePropertyPermissionException
  233. * if the property to be set is a read-only property.
  234. */
  235. public function __set( $name, $value )
  236. {
  237. switch ( $name )
  238. {
  239. case 'options':
  240. if ( ! $value instanceof ezcWebdavFileBackendOptions )
  241. {
  242. throw new ezcBaseValueException( $name, $value, 'ezcWebdavFileBackendOptions' );
  243. }
  244. $this->$name = $value;
  245. break;
  246. default:
  247. throw new ezcBasePropertyNotFoundException( $name );
  248. }
  249. }
  250. /**
  251. * Wait and get lock for complete directory tree.
  252. *
  253. * Acquire lock for the complete tree for read or write operations. This
  254. * does not implement any priorities for operations, or check if several
  255. * read operation may run in parallel. The plain locking should / could be
  256. * extended by something more sophisticated.
  257. *
  258. * If the tree already has been locked, the method waits until the lock can
  259. * be acquired.
  260. *
  261. * The optional second parameter $readOnly indicates wheather a read only
  262. * lock should be acquired. This may be used by extended implementations,
  263. * but it is not used in this implementation.
  264. *
  265. * @param bool $readOnly
  266. * @return void
  267. *
  268. * @todo The locking mechanism affects the ETag of the base collection. The
  269. * ETag is different on each request, which might result in problems
  270. * for clients that make extensive use of If-* headers. No client is
  271. * known so far, if problems occur here we need to find a solution
  272. * for this.
  273. */
  274. protected function acquireLock( $readOnly = false )
  275. {
  276. if ( $this->options->noLock )
  277. {
  278. return true;
  279. }
  280. try
  281. {
  282. $this->lock( $this->options->waitForLock, $this->options->lockTimeout );
  283. }
  284. catch ( ezcWebdavLockTimeoutException $e )
  285. {
  286. return false;
  287. }
  288. return true;
  289. }
  290. /**
  291. * Free lock.
  292. *
  293. * Frees the lock after the operation has been finished.
  294. *
  295. * @return void
  296. */
  297. protected function freeLock()
  298. {
  299. if ( $this->options->noLock )
  300. {
  301. return true;
  302. }
  303. $this->unlock();
  304. }
  305. /**
  306. * Returns the mime type of a resource.
  307. *
  308. * Return the mime type of the resource identified by $path. If a mime type
  309. * extension is available it will be used to read the real mime type,
  310. * otherwise the original mime type passed by the client when uploading the
  311. * file will be returned. If no mimetype has ever been associated with the
  312. * file, the method will just return 'application/octet-stream'.
  313. *
  314. * @param string $path
  315. * @return string
  316. */
  317. protected function getMimeType( $path )
  318. {
  319. // Check if extension pecl/fileinfo is usable.
  320. if ( $this->options->useMimeExts && ezcBaseFeatures::hasExtensionSupport( 'fileinfo' ) )
  321. {
  322. $fInfo = new fInfo( FILEINFO_MIME );
  323. $mimeType = $fInfo->file( $this->root . $path );
  324. // The documentation tells to do this, but it does not work with a
  325. // current version of pecl/fileinfo
  326. // $fInfo->close();
  327. return $mimeType;
  328. }
  329. // Check if extension ext/mime-magic is usable.
  330. if ( $this->options->useMimeExts &&
  331. ezcBaseFeatures::hasExtensionSupport( 'mime_magic' ) &&
  332. ( $mimeType = mime_content_type( $this->root . $path ) ) !== false )
  333. {
  334. return $mimeType;
  335. }
  336. // Check if some browser submitted mime type is available.
  337. $storage = $this->getPropertyStorage( $path );
  338. $properties = $storage->getAllProperties();
  339. if ( isset( $properties['DAV:']['getcontenttype'] ) )
  340. {
  341. return $properties['DAV:']['getcontenttype']->mime;
  342. }
  343. // Default to 'application/octet-stream' if nothing else is available.
  344. return 'application/octet-stream';
  345. }
  346. /**
  347. * Creates a new collection.
  348. *
  349. * Creates a new collection at the given $path.
  350. *
  351. * @param string $path
  352. * @return void
  353. */
  354. protected function createCollection( $path )
  355. {
  356. mkdir( $this->root . $path );
  357. chmod( $this->root . $path, $this->options->directoryMode );
  358. // This automatically creates the property storage
  359. $storage = $this->getPropertyStoragePath( $path . '/foo' );
  360. }
  361. /**
  362. * Creates a new resource.
  363. *
  364. * Creates a new resource at the given $path, optionally with the given
  365. * content. If $content is empty, an empty resource will be created.
  366. *
  367. * @param string $path
  368. * @param string $content
  369. * @return void
  370. */
  371. protected function createResource( $path, $content = null )
  372. {
  373. file_put_contents( $this->root . $path, $content );
  374. chmod( $this->root . $path, $this->options->fileMode );
  375. // This automatically creates the property storage if missing
  376. $storage = $this->getPropertyStoragePath( $path );
  377. }
  378. /**
  379. * Sets the contents of a resource.
  380. *
  381. * This method replaces the content of the resource identified by $path
  382. * with the submitted $content.
  383. *
  384. * @param string $path
  385. * @param string $content
  386. * @return void
  387. */
  388. protected function setResourceContents( $path, $content )
  389. {
  390. file_put_contents( $this->root . $path, $content );
  391. chmod( $this->root . $path, $this->options->fileMode );
  392. }
  393. /**
  394. * Returns the contents of a resource.
  395. *
  396. * This method returns the content of the resource identified by $path as a
  397. * string.
  398. *
  399. * @param string $path
  400. * @return string
  401. */
  402. protected function getResourceContents( $path )
  403. {
  404. return file_get_contents( $this->root . $path );
  405. }
  406. /**
  407. * Returns the storage path for a property.
  408. *
  409. * Returns the file systems path where properties are stored for the
  410. * resource identified by $path. This depends on the name of the resource.
  411. *
  412. * @param string $path
  413. * @return string
  414. */
  415. protected function getPropertyStoragePath( $path )
  416. {
  417. // Get storage path for properties depending on the type of the
  418. // resource.
  419. $storagePath = realpath( $this->root . dirname( $path ) )
  420. . '/' . $this->options->propertyStoragePath . '/'
  421. . basename( $path ) . '.xml';
  422. // Create property storage if it does not exist yet
  423. if ( !is_dir( dirname( $storagePath ) ) )
  424. {
  425. mkdir( dirname( $storagePath ), $this->options->directoryMode );
  426. }
  427. // Append name of namespace to property storage path
  428. return $storagePath;
  429. }
  430. /**
  431. * Returns the property storage for a resource.
  432. *
  433. * Returns the {@link ezcWebdavPropertyStorage} instance containing the
  434. * properties for the resource identified by $path.
  435. *
  436. * @param string $path
  437. * @return ezcWebdavBasicPropertyStorage
  438. */
  439. protected function getPropertyStorage( $path )
  440. {
  441. $storagePath = $this->getPropertyStoragePath( $path );
  442. // If no properties has been stored yet, just return an empty property
  443. // storage.
  444. if ( !is_file( $storagePath ) )
  445. {
  446. return new ezcWebdavBasicPropertyStorage();
  447. }
  448. // Create handler structure to read properties
  449. $handler = new ezcWebdavPropertyHandler(
  450. $xml = new ezcWebdavXmlTool()
  451. );
  452. $storage = new ezcWebdavBasicPropertyStorage();
  453. // Read document
  454. try
  455. {
  456. $doc = $xml->createDom( file_get_contents( $storagePath ) );
  457. }
  458. catch ( ezcWebdavInvalidXmlException $e )
  459. {
  460. throw new ezcWebdavFileBackendBrokenStorageException(
  461. "Could not open XML as DOMDocument: '{$storage}'."
  462. );
  463. }
  464. // Get property node from document
  465. $properties = $doc->getElementsByTagname( 'properties' )->item( 0 )->childNodes;
  466. // Extract and return properties
  467. $handler->extractProperties(
  468. $properties,
  469. $storage
  470. );
  471. return $storage;
  472. }
  473. /**
  474. * Stores properties for a resource.
  475. *
  476. * Creates a new property storage file and stores the properties given for
  477. * the resource identified by $path. This depends on the affected resource
  478. * and the actual properties in the property storage.
  479. *
  480. * @param string $path
  481. * @param ezcWebdavBasicPropertyStorage $storage
  482. * @return void
  483. */
  484. protected function storeProperties( $path, ezcWebdavBasicPropertyStorage $storage )
  485. {
  486. $storagePath = $this->getPropertyStoragePath( $path );
  487. // Create handler structure to read properties
  488. $handler = new ezcWebdavPropertyHandler(
  489. $xml = new ezcWebdavXmlTool()
  490. );
  491. // Create new dom document with property storage for one namespace
  492. $doc = new DOMDocument( '1.0' );
  493. $properties = $doc->createElement( 'properties' );
  494. $doc->appendChild( $properties );
  495. // Store and store properties
  496. $handler->serializeProperties(
  497. $storage,
  498. $properties
  499. );
  500. return $doc->save( $storagePath );
  501. }
  502. /**
  503. * Manually sets a property on a resource.
  504. *
  505. * Sets the given $propertyBackup for the resource identified by $path.
  506. *
  507. * @param string $path
  508. * @param ezcWebdavProperty $property
  509. * @return bool
  510. */
  511. public function setProperty( $path, ezcWebdavProperty $property )
  512. {
  513. // Check if property is a self handled live property and return an
  514. // error in this case.
  515. if ( ( $property->namespace === 'DAV:' ) &&
  516. in_array( $property->name, $this->handledLiveProperties, true ) &&
  517. ( $property->name !== 'getcontenttype' ) &&
  518. ( $property->name !== 'lockdiscovery' ) )
  519. {
  520. return false;
  521. }
  522. // Get namespace property storage
  523. $storage = $this->getPropertyStorage( $path );
  524. // Attach property to store
  525. $storage->attach( $property );
  526. // Store document back
  527. $this->storeProperties( $path, $storage );
  528. return true;
  529. }
  530. /**
  531. * Manually removes a property from a resource.
  532. *
  533. * Removes the given $property form the resource identified by $path.
  534. *
  535. * @param string $path
  536. * @param ezcWebdavProperty $property
  537. * @return bool
  538. */
  539. public function removeProperty( $path, ezcWebdavProperty $property )
  540. {
  541. // Live properties may not be removed.
  542. if ( $property instanceof ezcWebdavLiveProperty )
  543. {
  544. return false;
  545. }
  546. // Get namespace property storage
  547. $storage = $this->getPropertyStorage( $path );
  548. // Attach property to store
  549. $storage->detach( $property->name, $property->namespace );
  550. // Store document back
  551. $this->storeProperties( $path, $storage );
  552. return true;
  553. }
  554. /**
  555. * Resets the property storage for a resource.
  556. *
  557. * Discardes the current {@link ezcWebdavPropertyStorage} of the resource
  558. * identified by $path and replaces it with the given $properties.
  559. *
  560. * @param string $path
  561. * @param ezcWebdavPropertyStorage $storage
  562. * @return bool
  563. */
  564. public function resetProperties( $path, ezcWebdavPropertyStorage $storage )
  565. {
  566. $this->storeProperties( $path, $storage );
  567. }
  568. /**
  569. * Returns a property of a resource.
  570. *
  571. * Returns the property with the given $propertyName, from the resource
  572. * identified by $path. You may optionally define a $namespace to receive
  573. * the property from.
  574. *
  575. * @param string $path
  576. * @param string $propertyName
  577. * @param string $namespace
  578. * @return ezcWebdavProperty
  579. */
  580. public function getProperty( $path, $propertyName, $namespace = 'DAV:' )
  581. {
  582. $storage = $this->getPropertyStorage( $path );
  583. // Handle dead propreties
  584. if ( $namespace !== 'DAV:' )
  585. {
  586. $properties = $storage->getAllProperties();
  587. return $properties[$namespace][$propertyName];
  588. }
  589. // Handle live properties
  590. switch ( $propertyName )
  591. {
  592. case 'getcontentlength':
  593. $property = new ezcWebdavGetContentLengthProperty();
  594. $property->length = $this->getContentLength( $path );
  595. return $property;
  596. case 'getlastmodified':
  597. $property = new ezcWebdavGetLastModifiedProperty();
  598. $property->date = new ezcWebdavDateTime( '@' . filemtime( $this->root . $path ) );
  599. return $property;
  600. case 'creationdate':
  601. $property = new ezcWebdavCreationDateProperty();
  602. $property->date = new ezcWebdavDateTime( '@' . filectime( $this->root . $path ) );
  603. return $property;
  604. case 'displayname':
  605. $property = new ezcWebdavDisplayNameProperty();
  606. $property->displayName = urldecode( basename( $path ) );
  607. return $property;
  608. case 'getcontenttype':
  609. $property = new ezcWebdavGetContentTypeProperty(
  610. $this->getMimeType( $path )
  611. );
  612. return $property;
  613. case 'getetag':
  614. $property = new ezcWebdavGetEtagProperty();
  615. $property->etag = $this->getETag( $path );
  616. return $property;
  617. case 'resourcetype':
  618. $property = new ezcWebdavResourceTypeProperty();
  619. $property->type = $this->isCollection( $path ) ?
  620. ezcWebdavResourceTypeProperty::TYPE_COLLECTION :
  621. ezcWebdavResourceTypeProperty::TYPE_RESOURCE;
  622. return $property;
  623. case 'supportedlock':
  624. $property = new ezcWebdavSupportedLockProperty();
  625. return $property;
  626. case 'lockdiscovery':
  627. $property = new ezcWebdavLockDiscoveryProperty();
  628. return $property;
  629. default:
  630. // Handle all other live properties like dead properties
  631. $properties = $storage->getAllProperties();
  632. return $properties[$namespace][$propertyName];
  633. }
  634. }
  635. /**
  636. * Returns the content length.
  637. *
  638. * Returns the content length (filesize) of the resource identified by
  639. * $path.
  640. *
  641. * @param string $path
  642. * @return string The content length.
  643. */
  644. private function getContentLength( $path )
  645. {
  646. $length = ezcWebdavGetContentLengthProperty::COLLECTION;
  647. if ( !$this->isCollection( $path ) )
  648. {
  649. $length = (string) filesize( $this->root . $path );
  650. }
  651. return $length;
  652. }
  653. /**
  654. * Returns the etag representing the current state of $path.
  655. *
  656. * Calculates and returns the ETag for the resource represented by $path.
  657. * The ETag is calculated from the $path itself and the following
  658. * properties, which are concatenated and md5 hashed:
  659. *
  660. * <ul>
  661. * <li>getcontentlength</li>
  662. * <li>getlastmodified</li>
  663. * </ul>
  664. *
  665. * This method can be overwritten in custom backend implementations to
  666. * access the information needed directly without using the way around
  667. * properties.
  668. *
  669. * Custom backend implementations are encouraged to use the same mechanism
  670. * (or this method itself) to determine and generate ETags.
  671. *
  672. * @param mixed $path
  673. * @return void
  674. */
  675. protected function getETag( $path )
  676. {
  677. clearstatcache();
  678. return md5(
  679. $path
  680. . $this->getContentLength( $path )
  681. . date( 'c', filemtime( $this->root . $path ) )
  682. );
  683. }
  684. /**
  685. * Returns all properties for a resource.
  686. *
  687. * Returns all properties for the resource identified by $path as a {@link
  688. * ezcWebdavBasicPropertyStorage}.
  689. *
  690. * @param string $path
  691. * @return ezcWebdavPropertyStorage
  692. */
  693. public function getAllProperties( $path )
  694. {
  695. $storage = $this->getPropertyStorage( $path );
  696. // Add all live properties to stored properties
  697. foreach ( $this->handledLiveProperties as $property )
  698. {
  699. $storage->attach(
  700. $this->getProperty( $path, $property )
  701. );
  702. }
  703. return $storage;
  704. }
  705. /**
  706. * Recursively copy a file or directory.
  707. *
  708. * Recursively copy a file or directory in $source to the given
  709. * $destination. If a $depth is given, the operation will stop as soon as
  710. * the given recursion depth is reached. A depth of -1 means no limit,
  711. * while a depth of 0 means, that only the current file or directory will
  712. * be copied, without any recursion.
  713. *
  714. * Returns an empty array if no errors occured, and an array with the files
  715. * which caused errors otherwise.
  716. *
  717. * @param string $source
  718. * @param string $destination
  719. * @param int $depth
  720. * @return array
  721. */
  722. public function copyRecursive( $source, $destination, $depth = ezcWebdavRequest::DEPTH_INFINITY )
  723. {
  724. // Skip non readable files in source directory, or non writeable
  725. // destination directories.
  726. if ( !is_readable( $source ) || !is_writeable( dirname( $destination ) ) )
  727. {
  728. return array( $source );
  729. }
  730. // Copy
  731. if ( is_dir( $source ) )
  732. {
  733. mkdir( $destination );
  734. // To ignore umask, umask() should not be changed on multithreaded
  735. // servers...
  736. chmod( $destination, $this->options->directoryMode );
  737. }
  738. elseif ( is_file( $source ) )
  739. {
  740. copy( $source, $destination );
  741. chmod( $destination, $this->options->fileMode );
  742. }
  743. if ( ( $depth === ezcWebdavRequest::DEPTH_ZERO ) ||
  744. ( !is_dir( $source ) ) )
  745. {
  746. // Do not recurse (any more)
  747. return array();
  748. }
  749. // Recurse
  750. $dh = opendir( $source );
  751. $errors = array();
  752. while ( $file = readdir( $dh ) )
  753. {
  754. if ( ( $file === '.' ) ||
  755. ( $file === '..' ) )
  756. {
  757. continue;
  758. }
  759. $errors = array_merge(
  760. $errors,
  761. $this->copyRecursive(
  762. $source . '/' . $file,
  763. $destination . '/' . $file,
  764. $depth - 1
  765. )
  766. );
  767. }
  768. closedir( $dh );
  769. return $errors;
  770. }
  771. /**
  772. * Copies resources recursively from one path to another.
  773. *
  774. * Copies the resourced identified by $fromPath recursively to $toPath with
  775. * the given $depth, where $depth is one of {@link
  776. * ezcWebdavRequest::DEPTH_ZERO}, {@link ezcWebdavRequest::DEPTH_ONE},
  777. * {@link ezcWebdavRequest::DEPTH_INFINITY}.
  778. *
  779. * Returns an array with {@link ezcWebdavErrorResponse}s for all subtrees,
  780. * where the copy operation failed. Errors for subsequent resources in a
  781. * subtree should be ommitted.
  782. *
  783. * If an empty array is return, the operation has been completed
  784. * successfully.
  785. *
  786. * @param string $fromPath
  787. * @param string $toPath
  788. * @param int $depth
  789. * @return array(ezcWebdavErrorResponse)
  790. */
  791. protected function performCopy( $fromPath, $toPath, $depth = ezcWebdavRequest::DEPTH_INFINITY )
  792. {
  793. $errors = $this->copyRecursive( $this->root . $fromPath, $this->root . $toPath, $depth );
  794. // Transform errors
  795. foreach ( $errors as $nr => $error )
  796. {
  797. $errors[$nr] = new ezcWebdavErrorResponse(
  798. ezcWebdavResponse::STATUS_423,
  799. str_replace( $this->root, '', $error )
  800. );
  801. }
  802. // Copy dead properties
  803. $storage = $this->getPropertyStorage( $fromPath );
  804. $this->storeProperties( $toPath, $storage );
  805. // Updateable live properties are updated automagically, because they
  806. // are regenerated on request on base of the file they affect. So there
  807. // is no reason to keep them "alive".
  808. return $errors;
  809. }
  810. /**
  811. * Returns if everything below a path can be deleted recursively.
  812. *
  813. * Checks files and directories recursively and returns if everything can
  814. * be deleted. Returns an empty array if no errors occured, and an array
  815. * with the files which caused errors otherwise.
  816. *
  817. * @param string $source
  818. * @return array
  819. */
  820. public function checkDeleteRecursive( $source )
  821. {
  822. // Skip non readable files in source directory, or non writeable
  823. // destination directories.
  824. if ( !is_writeable( dirname( $source ) ) )
  825. {
  826. return array(
  827. new ezcWebdavErrorResponse(
  828. ezcWebdavResponse::STATUS_403,
  829. substr( $source, strlen( $this->root ) )
  830. ),
  831. );
  832. }
  833. if ( is_file( $source ) )
  834. {
  835. // For plain files the above checks should be sufficant
  836. return array();
  837. }
  838. // Recurse
  839. $dh = opendir( $source );
  840. $errors = array();
  841. while ( $file = readdir( $dh ) )
  842. {
  843. if ( ( $file === '.' ) ||
  844. ( $file === '..' ) )
  845. {
  846. continue;
  847. }
  848. $errors = array_merge(
  849. $errors,
  850. $this->checkDeleteRecursive( $source . '/' . $file )
  851. );
  852. }
  853. closedir( $dh );
  854. // Return errors
  855. return $errors;
  856. }
  857. /**
  858. * Deletes everything below a path.
  859. *
  860. * Deletes the resource identified by $path recursively. Returns an
  861. * instance of {@link ezcWebdavErrorResponse} if the deletion failed, and
  862. * null on success.
  863. *
  864. * @param string $path
  865. * @return ezcWebdavErrorResponse
  866. */
  867. protected function performDelete( $path )
  868. {
  869. $errors = $this->checkDeleteRecursive( $this->root . $path );
  870. // If an error will occur return the proper status. We return
  871. // multistatus in any case.
  872. if ( count( $errors ) )
  873. {
  874. return new ezcWebdavMultistatusResponse(
  875. $errors
  876. );
  877. }
  878. // Just delete otherwise
  879. if ( is_file( $this->root . $path ) )
  880. {
  881. unlink( $this->root . $path );
  882. }
  883. else
  884. {
  885. ezcBaseFile::removeRecursive( $this->root . $path );
  886. }
  887. // Finally empty property storage for removed node
  888. $storagePath = $this->getPropertyStoragePath( $path );
  889. if ( is_file( $storagePath ) )
  890. {
  891. unlink( $storagePath );
  892. }
  893. return null;
  894. }
  895. /**
  896. * Returns if a resource exists.
  897. *
  898. * Returns if a the resource identified by $path exists.
  899. *
  900. * @param string $path
  901. * @return bool
  902. */
  903. protected function nodeExists( $path )
  904. {
  905. return ( is_file( $this->root . $path ) || is_dir( $this->root . $path ) );
  906. }
  907. /**
  908. * Returns if resource is a collection.
  909. *
  910. * Returns if the resource identified by $path is a collection resource
  911. * (true) or a non-collection one (false).
  912. *
  913. * @param string $path
  914. * @return bool
  915. */
  916. protected function isCollection( $path )
  917. {
  918. return is_dir( $this->root . $path );
  919. }
  920. /**
  921. * Returns members of collection.
  922. *
  923. * Returns an array with the members of the collection identified by $path.
  924. * The returned array can contain {@link ezcWebdavCollection}, and {@link
  925. * ezcWebdavResource} instances and might also be empty, if the collection
  926. * has no members.
  927. *
  928. * @param string $path
  929. * @return array(ezcWebdavResource|ezcWebdavCollection)
  930. */
  931. protected function getCollectionMembers( $path )
  932. {
  933. $contents = array();
  934. $errors = array();
  935. $files = glob( $this->root . $path . '/*' );
  936. if ( $this->options->hideDotFiles === false )
  937. {
  938. $files = array_merge(
  939. $files,
  940. glob( $this->root . $path . '/.*' )
  941. );
  942. }
  943. foreach ( $files as $file )
  944. {
  945. // Skip files used for somethig else...
  946. if ( ( strpos( $file, '/' . $this->options->lockFileName ) !== false ) ||
  947. ( strpos( $file, '/' . $this->options->propertyStoragePath ) !== false ) )
  948. {
  949. continue;
  950. }
  951. $file = $path . '/' . basename( $file );
  952. if ( is_dir( $this->root . $file ) )
  953. {
  954. // Add collection without any children
  955. $contents[] = new ezcWebdavCollection( $file );
  956. }
  957. else
  958. {
  959. // Add files without content
  960. $contents[] = new ezcWebdavResource( $file );
  961. }
  962. }
  963. return $contents;
  964. }
  965. /**
  966. * Serves GET requests.
  967. *
  968. * The method receives a {@link ezcWebdavGetRequest} object containing all
  969. * relevant information obout the clients request and will return an {@link
  970. * ezcWebdavErrorResponse} instance on error or an instance of {@link
  971. * ezcWebdavGetResourceResponse} or {@link ezcWebdavGetCollectionResponse}
  972. * on success, depending on the type of resource that is referenced by the
  973. * request.
  974. *
  975. * This method acquires the internal lock of the backend, dispatches to
  976. * {@link ezcWebdavSimpleBackend} to perform the operation and releases the
  977. * lock afterwards.
  978. *
  979. * @param ezcWebdavGetRequest $request
  980. * @return ezcWebdavResponse
  981. */
  982. public function get( ezcWebdavGetRequest $request )
  983. {
  984. $this->acquireLock( true );
  985. $return = parent::get( $request );
  986. $this->freeLock();
  987. return $return;
  988. }
  989. /**
  990. * Serves HEAD requests.
  991. *
  992. * The method receives a {@link ezcWebdavHeadRequest} object containing all
  993. * relevant information obout the clients request and will return an {@link
  994. * ezcWebdavErrorResponse} instance on error or an instance of {@link
  995. * ezcWebdavHeadResponse} on success.
  996. *
  997. * This method acquires the internal lock of the backend, dispatches to
  998. * {@link ezcWebdavSimpleBackend} to perform the operation and releases the
  999. * lock afterwards.
  1000. *
  1001. * @param ezcWebdavHeadRequest $request
  1002. * @return ezcWebdavResponse
  1003. */
  1004. public function head( ezcWebdavHeadRequest $request )
  1005. {
  1006. $this->acquireLock( true );
  1007. $return = parent::head( $request );
  1008. $this->freeLock();
  1009. return $return;
  1010. }
  1011. /**
  1012. * Serves PROPFIND requests.
  1013. *
  1014. * The method receives a {@link ezcWebdavPropFindRequest} object containing
  1015. * all relevant information obout the clients request and will either
  1016. * return an instance of {@link ezcWebdavErrorResponse} to indicate an error
  1017. * or a {@link ezcWebdavPropFindResponse} on success. If the referenced
  1018. * resource is a collection or if some properties produced errors, an
  1019. * instance of {@link ezcWebdavMultistatusResponse} may be returned.
  1020. *
  1021. * The {@link ezcWebdavPropFindRequest} object contains a definition to
  1022. * find one or more properties of a given collection or non-collection
  1023. * resource.
  1024. *
  1025. * This method acquires the internal lock of the backend, dispatches to
  1026. * {@link ezcWebdavSimpleBackend} to perform the operation and releases the
  1027. * lock afterwards.
  1028. *
  1029. * @param ezcWebdavPropFindRequest $request
  1030. * @return ezcWebdavResponse
  1031. */
  1032. public function propFind( ezcWebdavPropFindRequest $request )
  1033. {
  1034. $this->acquireLock( true );
  1035. $return = parent::propFind( $request );
  1036. $this->freeLock();
  1037. return $return;
  1038. }
  1039. /**
  1040. * Serves PROPPATCH requests.
  1041. *
  1042. * The method receives a {@link ezcWebdavPropPatchRequest} object
  1043. * containing all relevant information obout the clients request and will
  1044. * return an instance of {@link ezcWebdavErrorResponse} on error or a
  1045. * {@link ezcWebdavPropPatchResponse} response on success. If the
  1046. * referenced resource is a collection or if only some properties produced
  1047. * errors, an instance of {@link ezcWebdavMultistatusResponse} may be
  1048. * returned.
  1049. *
  1050. * This method acquires the internal lock of the backend, dispatches to
  1051. * {@link ezcWebdavSimpleBackend} to perform the operation and releases the
  1052. * lock afterwards.
  1053. *
  1054. * @param ezcWebdavPropPatchRequest $request
  1055. * @return ezcWebdavResponse
  1056. */
  1057. public function propPatch( ezcWebdavPropPatchRequest $request )
  1058. {
  1059. $this->acquireLock();
  1060. $return = parent::propPatch( $request );
  1061. $this->freeLock();
  1062. return $return;
  1063. }
  1064. /**
  1065. * Serves PUT requests.
  1066. *
  1067. * The method receives a {@link ezcWebdavPutRequest} objects containing all
  1068. * relevant information obout the clients request and will return an
  1069. * instance of {@link ezcWebdavErrorResponse} on error or {@link
  1070. * ezcWebdavPutResponse} on success.
  1071. *
  1072. * This method acquires the internal lock of the backend, dispatches to
  1073. * {@link ezcWebdavSimpleBackend} to perform the operation and releases the
  1074. * lock afterwards.
  1075. *
  1076. * @param ezcWebdavPutRequest $request
  1077. * @return ezcWebdavResponse
  1078. */
  1079. public function put( ezcWebdavPutRequest $request )
  1080. {
  1081. $this->acquireLock();
  1082. $return = parent::put( $request );
  1083. $this->freeLock();
  1084. return $return;
  1085. }
  1086. /**
  1087. * Serves DELETE requests.
  1088. *
  1089. * The method receives a {@link ezcWebdavDeleteRequest} objects containing
  1090. * all relevant information obout the clients request and will return an
  1091. * instance of {@link ezcWebdavErrorResponse} on error or {@link
  1092. * ezcWebdavDeleteResponse} on success.
  1093. *
  1094. * This method acquires the internal lock of the backend, dispatches to
  1095. * {@link ezcWebdavSimpleBackend} to perform the operation and releases the
  1096. * lock afterwards.
  1097. *
  1098. * @param ezcWebdavDeleteRequest $request
  1099. * @return ezcWebdavResponse
  1100. */
  1101. public function delete( ezcWebdavDeleteRequest $request )
  1102. {
  1103. $this->acquireLock();
  1104. $return = parent::delete( $request );
  1105. $this->freeLock();
  1106. return $return;
  1107. }
  1108. /**
  1109. * Serves COPY requests.
  1110. *
  1111. * The method receives a {@link ezcWebdavCopyRequest} objects containing
  1112. * all relevant information obout the clients request and will return an
  1113. * instance of {@link ezcWebdavErrorResponse} on error or {@link
  1114. * ezcWebdavCopyResponse} on success. If only some operations failed, this
  1115. * method may return an instance of {@link ezcWebdavMultistatusResponse}.
  1116. *
  1117. * This method acquires the internal lock of the backend, dispatches to
  1118. * {@link ezcWebdavSimpleBackend} to perform the operation and releases the
  1119. * lock afterwards.
  1120. *
  1121. * @param ezcWebdavCopyRequest $request
  1122. * @return ezcWebdavResponse
  1123. */
  1124. public function copy( ezcWebdavCopyRequest $request )
  1125. {
  1126. $this->acquireLock();
  1127. $return = parent::copy( $request );
  1128. $this->freeLock();
  1129. return $return;
  1130. }
  1131. /**
  1132. * Serves MOVE requests.
  1133. *
  1134. * The method receives a {@link ezcWebdavMoveRequest} objects containing
  1135. * all relevant information obout the clients request and will return an
  1136. * instance of {@link ezcWebdavErrorResponse} on error or {@link
  1137. * ezcWebdavMoveResponse} on success. If only some operations failed, this
  1138. * method may return an instance of {@link ezcWebdavMultistatusResponse}.
  1139. *
  1140. * This method acquires the internal lock of the backend, dispatches to
  1141. * {@link ezcWebdavSimpleBackend} to perform the operation and releases the
  1142. * lock afterwards.
  1143. *
  1144. * @param ezcWebdavMoveRequest $request
  1145. * @return ezcWebdavResponse
  1146. */
  1147. public function move( ezcWebdavMoveRequest $request )
  1148. {
  1149. $this->acquireLock();
  1150. $return = parent::move( $request );
  1151. $this->freeLock();
  1152. return $return;
  1153. }
  1154. /**
  1155. * Serves MKCOL (make collection) requests.
  1156. *
  1157. * The method receives a {@link ezcWebdavMakeCollectionRequest} objects
  1158. * containing all relevant information obout the clients request and will
  1159. * return an instance of {@link ezcWebdavErrorResponse} on error or {@link
  1160. * ezcWebdavMakeCollectionResponse} on success.
  1161. *
  1162. * This method acquires the internal lock of the backend, dispatches to
  1163. * {@link ezcWebdavSimpleBackend} to perform the operation and releases the
  1164. * lock afterwards.
  1165. *
  1166. * @param ezcWebdavMakeCollectionRequest $request
  1167. * @return ezcWebdavResponse
  1168. */
  1169. public function makeCollection( ezcWebdavMakeCollectionRequest $request )
  1170. {
  1171. $this->acquireLock();
  1172. $return = parent::makeCollection( $request );
  1173. $this->freeLock();
  1174. return $return;
  1175. }
  1176. }
  1177. ?>