* This copyright notice MUST APPEAR in all copies of the script!
***************************************************************/
-class StatusReport implements \TYPO3\CMS\Reports\StatusProviderInterface {
- /**
- * Returns the status of an extension or (sub)system
- *
- * @return array An array of tx_reports_reports_status_Status objects
- */
- public function getStatus() {
- return array(
- 'mcryptAvailability' => $this->getMcryptAvailability()
- );
- }
+class StatusReport implements \TYPO3\CMS\Reports\StatusProviderInterface
+{
+ /**
+ * Returns the status of an extension or (sub)system
+ *
+ * @return array An array of tx_reports_reports_status_Status objects
+ */
+ public function getStatus()
+ {
+ return array(
+ 'mcryptAvailability' => $this->getMcryptAvailability()
+ );
+ }
- protected function getMcryptAvailability() {
- $mcryptAvailable = \TYPO3\FalWebdav\Utility\EncryptionUtility::isMcryptAvailable();
- $severity = $mcryptAvailable === TRUE ? \TYPO3\CMS\Reports\Status::OK : \TYPO3\CMS\Reports\Status::ERROR;
- $status = ($mcryptAvailable ? '' : 'Not ') . 'Available';
+ protected function getMcryptAvailability()
+ {
+ $mcryptAvailable = \TYPO3\FalWebdav\Utility\EncryptionUtility::isMcryptAvailable();
+ $severity = $mcryptAvailable === true ? \TYPO3\CMS\Reports\Status::OK : \TYPO3\CMS\Reports\Status::ERROR;
+ $status = ($mcryptAvailable ? '' : 'Not ') . 'Available';
- return \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Reports\\Status', 'PHP extension mcrypt', $status, '', $severity);
- }
+ return \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Reports\\Status',
+ 'PHP extension mcrypt', $status, '', $severity);
+ }
}
\ No newline at end of file
*
* @author Andreas Wolf <andreas.wolf@ikt-werk.de>
*/
-class TceMainHook {
- /**
- * @param array $incomingFieldArray
- * @param string $table
- * @param integer|string $id
- * @param \TYPO3\CMS\Core\DataHandling\DataHandler $tceMainObject
- * @return mixed
- */
- public function processDatamap_preProcessFieldArray(&$incomingFieldArray, $table, $id, \TYPO3\CMS\Core\DataHandling\DataHandler $tceMainObject) {
- if ($table !== 'sys_file_storage') {
- return;
- }
- if ($incomingFieldArray['driver'] !== 'WebDav') {
- return;
- }
+class TceMainHook
+{
+ /**
+ * @param array $incomingFieldArray
+ * @param string $table
+ * @param integer|string $id
+ * @param \TYPO3\CMS\Core\DataHandling\DataHandler $tceMainObject
+ * @return mixed
+ */
+ public function processDatamap_preProcessFieldArray(
+ &$incomingFieldArray,
+ $table,
+ $id,
+ \TYPO3\CMS\Core\DataHandling\DataHandler $tceMainObject
+ ) {
+ if ($table !== 'sys_file_storage') {
+ return;
+ }
+ if ($incomingFieldArray['driver'] !== 'WebDav') {
+ return;
+ }
- $url = &$incomingFieldArray['configuration']['data']['sDEF']['lDEF']['baseUrl']['vDEF'];
- $username = &$incomingFieldArray['configuration']['data']['sDEF']['lDEF']['username']['vDEF'];
- $password = &$incomingFieldArray['configuration']['data']['sDEF']['lDEF']['password']['vDEF'];
+ $url = &$incomingFieldArray['configuration']['data']['sDEF']['lDEF']['baseUrl']['vDEF'];
+ $username = &$incomingFieldArray['configuration']['data']['sDEF']['lDEF']['username']['vDEF'];
+ $password = &$incomingFieldArray['configuration']['data']['sDEF']['lDEF']['password']['vDEF'];
- list($cleanedUrl, $extractedUsername, $extractedPassword) = \TYPO3\FalWebdav\Utility\UrlTools::extractUsernameAndPasswordFromUrl($url);
- if ($cleanedUrl != $url) {
- $url = $cleanedUrl;
- }
- // if we found authentication information in the URL, use it instead of the information currently stored
- if ($extractedUsername !== '') {
- $username = $extractedUsername;
- $password = $extractedPassword;
- }
+ list($cleanedUrl, $extractedUsername, $extractedPassword) = \TYPO3\FalWebdav\Utility\UrlTools::extractUsernameAndPasswordFromUrl($url);
+ if ($cleanedUrl != $url) {
+ $url = $cleanedUrl;
+ }
+ // if we found authentication information in the URL, use it instead of the information currently stored
+ if ($extractedUsername !== '') {
+ $username = $extractedUsername;
+ $password = $extractedPassword;
+ }
- // skip encryption if we have no password set or the password is already encrypted
- if ($password === '' || substr($password, 0, 1) === '$') {
- return;
- }
+ // skip encryption if we have no password set or the password is already encrypted
+ if ($password === '' || substr($password, 0, 1) === '$') {
+ return;
+ }
- $password = \TYPO3\FalWebdav\Utility\EncryptionUtility::encryptPassword($password);
- }
+ $password = \TYPO3\FalWebdav\Utility\EncryptionUtility::encryptPassword($password);
+ }
}
use TYPO3\CMS\Core\Utility\GeneralUtility;
-class CachingWebDavFrontend extends WebDavFrontend {
-
- /**
- * @var FrontendInterface
- */
- protected $cache;
-
- protected $cacheHits = array(
- 'propFind' => 0,
- 'listFiles' => 0,
- 'listFolders' => 0,
- 'getFileInfo' => 0,
- );
-
- protected $cacheMisses = array(
- 'propFind' => 0,
- 'listFiles' => 0,
- 'listFolders' => 0,
- 'getFileInfo' => 0,
- );
-
-
- public function __construct(WebDavClient $client, $baseUrl, $storageUid, FrontendInterface $cache) {
- parent::__construct($client, $baseUrl, $storageUid);
-
- $this->cache = $cache;
- }
-
- /**
- * @return FrontendInterface
- */
- protected function getCache() {
- if (!$this->cache) {
- /** @var CacheManager $cacheManager */
- $cacheManager = GeneralUtility::makeInstance('TYPO3\\CMS\\Core\\Cache\\CacheManager');
- $this->cache = $cacheManager->getCache('tx_falwebdav_directorylisting');
- }
-
- return $this->cache;
- }
-
- public function propFind($path) {
- $cacheKey = $this->getCacheIdentifierForResponse($path);
-
- if (!$this->getCache()->has($cacheKey)) {
- ++$this->cacheMisses[__FUNCTION__];
- $this->logger->debug('propFind(): cache miss for ' . $path);
-
- $propFindResult = parent::propFind($path);
-
- $this->getCache()->set($cacheKey, $propFindResult);
-
- if (substr($path, -1) == '/' || strlen($path) == 0) {
- $this->extractFileInformationFromPropfindResult($propFindResult);
- }
- } else {
- $this->logger->debug('propFind(): cache hit for ' . $path);
- ++$this->cacheHits[__FUNCTION__];
- }
-
- return $this->getCache()->get($cacheKey);
- }
-
- public function listFiles($path) {
- $cacheKey = $this->getCacheIdentifierForFileList($path);
- if (!$this->getCache()->has($cacheKey)) {
- ++$this->cacheMisses[__FUNCTION__];
- $this->getCache()->set($cacheKey, parent::listFiles($path));
- } else {
- ++$this->cacheHits[__FUNCTION__];
- }
-
- return $this->getCache()->get($cacheKey);
- }
-
- public function listFolders($path) {
- $cacheKey = $this->getCacheIdentifierForFolderList($path);
- if (!$this->getCache()->has($cacheKey)) {
- ++$this->cacheMisses[__FUNCTION__];
- $this->getCache()->set($cacheKey, parent::listFolders($path));
- } else {
- ++$this->cacheHits[__FUNCTION__];
- }
-
- return $this->getCache()->get($cacheKey);
- }
-
- public function getFileInfo($path) {
- // the leading slash is already included in baseURL/basePath
- $path = ltrim($path, '/');
-
- $cacheKey = $this->getCacheIdentifierForFileInfo($path);
- if (!$this->getCache()->has($cacheKey)) {
- ++$this->cacheMisses[__FUNCTION__];
- $this->getCache()->set($cacheKey, parent::getFileInfo($path));
- } else {
- ++$this->cacheHits[__FUNCTION__];
- }
-
- return $this->getCache()->get($cacheKey);
- }
-
- public function logCacheStatistics() {
- if ($GLOBALS['TYPO3_AJAX'] === TRUE) {
- return;
- }
- sprintf('WebDAV frontend cache stats (hits/misses): '
- .'propFind %d/%d, listFiles %d/%d, listFolders %d/%d, getFileInfo %d/%d',
- $this->cacheHits['propFind'], $this->cacheMisses['propFind'],
- $this->cacheHits['listFiles'], $this->cacheMisses['listFiles'],
- $this->cacheHits['listFolders'], $this->cacheMisses['listFolders'],
- $this->cacheHits['getFileInfo'], $this->cacheMisses['getFileInfo']
- );
- }
-
- /**
- * Returns the cache identifier for the raw response for a given path
- *
- * @param string $path
- * @return string
- */
- protected function getCacheIdentifierForResponse($path) {
- return 'davResponse-' . $this->storageUid . '-' . sha1($path);
- }
-
- /**
- * Returns the cache identifier for the file list of a given path.
- *
- * @param string $path
- * @return string
- */
- protected function getCacheIdentifierForFileList($path) {
- return 'filelist-' . $this->storageUid . '-' . sha1($this->baseUrl . ':' . trim($path, '/') . '/');
- }
-
- /**
- * Returns the cache identifier for the folder list of a given path.
- *
- * @param string $path
- * @return string
- */
- protected function getCacheIdentifierForFolderList($path) {
- return 'folderlist-' . $this->storageUid . '-' . sha1($this->baseUrl . ':' . trim($path, '/') . '/');
- }
-
- /**
- * Returns the cache identifier for the file list of a given path.
- *
- * @param string $path
- * @return string
- */
- protected function getCacheIdentifierForFileInfo($path) {
- return 'fileinfo-' . $this->storageUid . '-' . sha1($this->baseUrl . ':' . $path);
- }
-
- function __destruct() {
- $this->logCacheStatistics();
- }
-
- /**
- * @param $propFindResult
- */
- protected function extractFileInformationFromPropfindResult($propFindResult) {
- $this->logger->debug('Extracting file information from request');
- foreach ($propFindResult as $filePath => $entry) {
- if (substr($filePath, -1) == '/') {
- continue;
- }
-
- $filePath = rawurldecode($filePath);
- $filePath = substr($filePath, strlen($this->basePath));
- $cacheKey = $this->getCacheIdentifierForFileInfo($filePath);
- if (!$this->getCache()->has($cacheKey)) {
- $this->getCache()->set($cacheKey, $this->extractFileInfo($filePath, $entry));
- }
- }
- }
+class CachingWebDavFrontend extends WebDavFrontend
+{
+
+ /**
+ * @var FrontendInterface
+ */
+ protected $cache;
+
+ protected $cacheHits = array(
+ 'propFind' => 0,
+ 'listFiles' => 0,
+ 'listFolders' => 0,
+ 'getFileInfo' => 0,
+ );
+
+ protected $cacheMisses = array(
+ 'propFind' => 0,
+ 'listFiles' => 0,
+ 'listFolders' => 0,
+ 'getFileInfo' => 0,
+ );
+
+
+ public function __construct(WebDavClient $client, $baseUrl, $storageUid, FrontendInterface $cache)
+ {
+ parent::__construct($client, $baseUrl, $storageUid);
+
+ $this->cache = $cache;
+ }
+
+ /**
+ * @return FrontendInterface
+ */
+ protected function getCache()
+ {
+ if (!$this->cache) {
+ /** @var CacheManager $cacheManager */
+ $cacheManager = GeneralUtility::makeInstance('TYPO3\\CMS\\Core\\Cache\\CacheManager');
+ $this->cache = $cacheManager->getCache('tx_falwebdav_directorylisting');
+ }
+
+ return $this->cache;
+ }
+
+ public function propFind($path)
+ {
+ $cacheKey = $this->getCacheIdentifierForResponse($path);
+
+ if (!$this->getCache()->has($cacheKey)) {
+ ++$this->cacheMisses[__FUNCTION__];
+ $this->logger->debug('propFind(): cache miss for ' . $path);
+
+ $propFindResult = parent::propFind($path);
+
+ $this->getCache()->set($cacheKey, $propFindResult);
+
+ if (substr($path, -1) == '/' || strlen($path) == 0) {
+ $this->extractFileInformationFromPropfindResult($propFindResult);
+ }
+ } else {
+ $this->logger->debug('propFind(): cache hit for ' . $path);
+ ++$this->cacheHits[__FUNCTION__];
+ }
+
+ return $this->getCache()->get($cacheKey);
+ }
+
+ public function listFiles($path)
+ {
+ $cacheKey = $this->getCacheIdentifierForFileList($path);
+ if (!$this->getCache()->has($cacheKey)) {
+ ++$this->cacheMisses[__FUNCTION__];
+ $this->getCache()->set($cacheKey, parent::listFiles($path));
+ } else {
+ ++$this->cacheHits[__FUNCTION__];
+ }
+
+ return $this->getCache()->get($cacheKey);
+ }
+
+ public function listFolders($path)
+ {
+ $cacheKey = $this->getCacheIdentifierForFolderList($path);
+ if (!$this->getCache()->has($cacheKey)) {
+ ++$this->cacheMisses[__FUNCTION__];
+ $this->getCache()->set($cacheKey, parent::listFolders($path));
+ } else {
+ ++$this->cacheHits[__FUNCTION__];
+ }
+
+ return $this->getCache()->get($cacheKey);
+ }
+
+ public function getFileInfo($path)
+ {
+ // the leading slash is already included in baseURL/basePath
+ $path = ltrim($path, '/');
+
+ $cacheKey = $this->getCacheIdentifierForFileInfo($path);
+ if (!$this->getCache()->has($cacheKey)) {
+ ++$this->cacheMisses[__FUNCTION__];
+ $this->getCache()->set($cacheKey, parent::getFileInfo($path));
+ } else {
+ ++$this->cacheHits[__FUNCTION__];
+ }
+
+ return $this->getCache()->get($cacheKey);
+ }
+
+ public function logCacheStatistics()
+ {
+ if ($GLOBALS['TYPO3_AJAX'] === true) {
+ return;
+ }
+ sprintf('WebDAV frontend cache stats (hits/misses): '
+ . 'propFind %d/%d, listFiles %d/%d, listFolders %d/%d, getFileInfo %d/%d',
+ $this->cacheHits['propFind'], $this->cacheMisses['propFind'],
+ $this->cacheHits['listFiles'], $this->cacheMisses['listFiles'],
+ $this->cacheHits['listFolders'], $this->cacheMisses['listFolders'],
+ $this->cacheHits['getFileInfo'], $this->cacheMisses['getFileInfo']
+ );
+ }
+
+ /**
+ * Returns the cache identifier for the raw response for a given path
+ *
+ * @param string $path
+ * @return string
+ */
+ protected function getCacheIdentifierForResponse($path)
+ {
+ return 'davResponse-' . $this->storageUid . '-' . sha1($path);
+ }
+
+ /**
+ * Returns the cache identifier for the file list of a given path.
+ *
+ * @param string $path
+ * @return string
+ */
+ protected function getCacheIdentifierForFileList($path)
+ {
+ return 'filelist-' . $this->storageUid . '-' . sha1($this->baseUrl . ':' . trim($path, '/') . '/');
+ }
+
+ /**
+ * Returns the cache identifier for the folder list of a given path.
+ *
+ * @param string $path
+ * @return string
+ */
+ protected function getCacheIdentifierForFolderList($path)
+ {
+ return 'folderlist-' . $this->storageUid . '-' . sha1($this->baseUrl . ':' . trim($path, '/') . '/');
+ }
+
+ /**
+ * Returns the cache identifier for the file list of a given path.
+ *
+ * @param string $path
+ * @return string
+ */
+ protected function getCacheIdentifierForFileInfo($path)
+ {
+ return 'fileinfo-' . $this->storageUid . '-' . sha1($this->baseUrl . ':' . $path);
+ }
+
+ function __destruct()
+ {
+ $this->logCacheStatistics();
+ }
+
+ /**
+ * @param $propFindResult
+ */
+ protected function extractFileInformationFromPropfindResult($propFindResult)
+ {
+ $this->logger->debug('Extracting file information from request');
+ foreach ($propFindResult as $filePath => $entry) {
+ if (substr($filePath, -1) == '/') {
+ continue;
+ }
+
+ $filePath = rawurldecode($filePath);
+ $filePath = substr($filePath, strlen($this->basePath));
+ $cacheKey = $this->getCacheIdentifierForFileInfo($filePath);
+ if (!$this->getCache()->has($cacheKey)) {
+ $this->getCache()->set($cacheKey, $this->extractFileInfo($filePath, $entry));
+ }
+ }
+ }
}
/**
* Helper class to circumvent limitations in SabreDAV's support for cURL's certificate verification options.
*/
-class WebDavClient extends Client {
-
- /**
- * Trigger to enable/disable peer certificate verification
- *
- * @var boolean
- */
- protected $verifyCertificates = TRUE;
-
- /**
- * The file to write the data to.
- *
- * @var resource
- */
- protected $outputFile = NULL;
-
-
- /**
- * @param boolean $peerVerification
- */
- public function setCertificateVerification($peerVerification) {
- $this->verifyCertificates = $peerVerification;
- }
-
- /**
- * Wrapper for all cUrl functions.
- *
- * @param resource $curlHandle
- *
- * @return array
- */
- protected function curlExec($curlHandle) {
- if ($this->verifyCertificates === FALSE) {
- curl_setopt($curlHandle, CURLOPT_SSL_VERIFYPEER, FALSE);
- }
- if ($this->outputFile !== NULL) {
- // make sure the file is never returned into the default output stream
- curl_setopt($curlHandle, CURLOPT_RETURNTRANSFER, FALSE);
- // don’t return the headers
- curl_setopt($curlHandle, CURLOPT_HEADER, FALSE);
- curl_setopt($curlHandle, CURLOPT_FILE, $this->outputFile);
- curl_setopt($curlHandle, CURLOPT_FOLLOWLOCATION, TRUE);
- $this->outputFile = NULL;
- } else {
- // set back to default as the cURL handle is not always reset; the file is not used anyways, but cURL
- // still complains about the handle being gone away if we closed the file that was used in an earlier
- // request
- curl_setopt($curlHandle, CURLOPT_FILE, fopen('php://stdout','w'));
-
- // we must use a different order here; setting RETURNTRANSFER before resetting the file handle will let
- // cURL still use the file.
- curl_setopt($curlHandle, CURLOPT_RETURNTRANSFER, TRUE);
- }
-
- return parent::curlExec($curlHandle);
- }
-
- function propFind($url, array $properties = NULL, $depth = 0) {
- if ($properties === NULL) {
- $properties = array(
- '{DAV:}resourcetype',
- '{DAV:}creationdate',
- '{DAV:}getcontentlength',
- '{DAV:}getlastmodified'
- );
- }
- return parent::propFind($url, $properties, $depth); // TODO: Change the autogenerated stub
- }
-
- /**
- * Reads the given URL’s contents to the given file handle. The handle must be writable, the disk must have enough
- * free space and
- *
- * @param string $url
- * @param resource $fileHandle
- * @param array $headers
- * @return HTTP\ResponseInterface
- */
- public function readUrlToHandle($url, $fileHandle, array $headers = []) {
- if (!is_resource($fileHandle)) {
- throw new \InvalidArgumentException('The file handle must be a valid resource');
- }
- $request = new HTTP\Request('GET', $url, $headers);
-
- // The actual option is set in curlExec(), as we don’t have access to the cURL handle here
- $this->outputFile = $fileHandle;
-
- return $this->send($request);
- }
+class WebDavClient extends Client
+{
+
+ /**
+ * Trigger to enable/disable peer certificate verification
+ *
+ * @var boolean
+ */
+ protected $verifyCertificates = true;
+
+ /**
+ * The file to write the data to.
+ *
+ * @var resource
+ */
+ protected $outputFile = null;
+
+
+ /**
+ * @param boolean $peerVerification
+ */
+ public function setCertificateVerification($peerVerification)
+ {
+ $this->verifyCertificates = $peerVerification;
+ }
+
+ /**
+ * Wrapper for all cUrl functions.
+ *
+ * @param resource $curlHandle
+ *
+ * @return array
+ */
+ protected function curlExec($curlHandle)
+ {
+ if ($this->verifyCertificates === false) {
+ curl_setopt($curlHandle, CURLOPT_SSL_VERIFYPEER, false);
+ }
+ if ($this->outputFile !== null) {
+ // make sure the file is never returned into the default output stream
+ curl_setopt($curlHandle, CURLOPT_RETURNTRANSFER, false);
+ // don’t return the headers
+ curl_setopt($curlHandle, CURLOPT_HEADER, false);
+ curl_setopt($curlHandle, CURLOPT_FILE, $this->outputFile);
+ curl_setopt($curlHandle, CURLOPT_FOLLOWLOCATION, true);
+ $this->outputFile = null;
+ } else {
+ // set back to default as the cURL handle is not always reset; the file is not used anyways, but cURL
+ // still complains about the handle being gone away if we closed the file that was used in an earlier
+ // request
+ curl_setopt($curlHandle, CURLOPT_FILE, fopen('php://stdout', 'w'));
+
+ // we must use a different order here; setting RETURNTRANSFER before resetting the file handle will let
+ // cURL still use the file.
+ curl_setopt($curlHandle, CURLOPT_RETURNTRANSFER, true);
+ }
+
+ return parent::curlExec($curlHandle);
+ }
+
+ function propFind($url, array $properties = null, $depth = 0)
+ {
+ if ($properties === null) {
+ $properties = array(
+ '{DAV:}resourcetype',
+ '{DAV:}creationdate',
+ '{DAV:}getcontentlength',
+ '{DAV:}getlastmodified'
+ );
+ }
+
+ return parent::propFind($url, $properties, $depth); // TODO: Change the autogenerated stub
+ }
+
+ /**
+ * Reads the given URL’s contents to the given file handle. The handle must be writable, the disk must have enough
+ * free space and
+ *
+ * @param string $url
+ * @param resource $fileHandle
+ * @param array $headers
+ * @return HTTP\ResponseInterface
+ */
+ public function readUrlToHandle($url, $fileHandle, array $headers = [])
+ {
+ if (!is_resource($fileHandle)) {
+ throw new \InvalidArgumentException('The file handle must be a valid resource');
+ }
+ $request = new HTTP\Request('GET', $url, $headers);
+
+ // The actual option is set in curlExec(), as we don’t have access to the cURL handle here
+ $this->outputFile = $fileHandle;
+
+ return $this->send($request);
+ }
}
use Sabre\DAV;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Core\Utility\HttpUtility;
-use TYPO3\FalWebdav\Utility\UrlTools;
/**
* All identifiers within this class are relative to the DAV storage’s base path and thus have no slash at the
* beginning (unlike FAL identifiers, which are always prepended with a slash).
*/
-class WebDavFrontend {
-
- /**
- * @var WebDavClient
- */
- protected $davClient;
-
- /**
- * The storage base URL, has to be already URL-encoded
- *
- * @var string
- */
- protected $baseUrl;
-
- /**
- * @var string
- */
- protected $basePath;
-
- /**
- * @var \TYPO3\CMS\Core\Log\Logger
- */
- protected $logger;
-
- /**
- * @var int
- */
- protected $storageUid;
-
-
- public function __construct(WebDavClient $client, $baseUrl, $storageUid) {
- $this->logger = GeneralUtility::makeInstance('TYPO3\CMS\Core\Log\LogManager')->getLogger(__CLASS__);
-
- $this->davClient = $client;
- $this->baseUrl = rtrim($baseUrl, '/') . '/';
- $urlParts = parse_url($this->baseUrl);
- $this->basePath = $urlParts['path'];
- $this->storageUid = $storageUid;
- }
-
- /**
- * @param string $url
- * @return string
- */
- protected function encodeUrl($url) {
- $urlParts = parse_url($url);
- $urlParts['path'] = $this->urlEncodePath($urlParts['path']);
-
- return HttpUtility::buildUrl($urlParts);
- }
-
- /**
- * Wrapper around the PROPFIND method of the WebDAV client to get proper local error handling.
- *
- * @param string $path
- * @return array
- * @throws DAV\Exception\NotFound if the given URL does not hold a resource
- *
- * TODO define proper error handling for other cases
- */
- public function propFind($path) {
- $url = $this->baseUrl . $path;
- $encodedUrl = $this->encodeUrl($url);
-
- try {
- // propfind() already decodes the XML, so we get back an array
- $propfindResultArray = $this->davClient->propfind($encodedUrl, NULL, 1);
-
- // the returned items are indexed by their key, so sort them here to return the correct items.
- // At least Apache does not sort them before returning
- uksort($propfindResultArray, 'strnatcasecmp');
-
- return $propfindResultArray;
- } catch (DAV\Exception\NotFound $exception) {
- $this->logger->warning('URL not found: ' . $url);
- // If a file is not found, we have to deal with that on a higher level, so throw the exception again
- throw $exception;
- } catch (DAV\Exception $exception) {
- // log all other exceptions
- $this->logger->error(sprintf(
- 'Error while executing DAV PROPFIND request. Original message: "%s" (Exception %s, id: %u)',
- $exception->getMessage(), get_class($exception), $exception->getCode()
- ));
- // TODO check how we can let this propagate to the driver
- return array();
- }
- }
-
- /**
- *
- * @param string $path
- * @return array A list of file names in the path
- */
- public function listFiles($path) {
- $files = $this->listItems($path, function ($currentItem) {
- if (substr($currentItem, -1) == '/') {
- return FALSE;
- }
- return TRUE;
- });
-
- return $files;
- }
-
- /**
- * @param string $path
- * @return array A list of folder names in the path
- */
- public function listFolders($path) {
- $folders = $this->listItems($path, function ($currentItem) {
- if (substr($currentItem, -1) != '/') {
- return FALSE;
- }
- return TRUE;
- });
-
- return $folders;
- }
-
- protected function listItems($path, $itemFilterCallback) {
- $path = trim($path, '/');
- if (strlen($path) > 0) {
- $path .= '/';
- }
- $urlParts = parse_url($this->baseUrl . $path);
- $unencodedBasePath = $urlParts['path'];
-
- $result = $this->propFind($path);
-
- // remove first entry, as it is the folder itself
- array_shift($result);
-
- $files = array();
- // $filePath contains the path part of the URL, no server name and protocol!
- foreach ($result as $filePath => $fileInfo) {
- $decodedFilePath = urldecode($filePath);
- $decodedFilePath = substr($decodedFilePath, strlen($unencodedBasePath));
- // ignore folder entries
- if (!$itemFilterCallback($decodedFilePath)) {
- continue;
- }
-
- // TODO if depth is > 1, we will also include deeper entries here, which we should not
-
- $files[] = basename($decodedFilePath);
- }
-
- return $files;
- }
-
- /**
- * Returns information about the given file.
- *
- * TODO define what to return
- *
- * @param string $path
- * @return array
- */
- public function getFileInfo($path) {
- // the leading slash is already included in baseURL/basePath
- $path = ltrim($path, '/');
-
- $result = $this->propFind($path);
-
- return $this->extractFileInfo($path, $result[$this->basePath . $this->urlEncodePath($path)]);
- }
-
- protected function extractFileInfo($path, $propFindArray) {
- $fileInfo = array(
- 'mtime' => (int)strtotime($propFindArray['{DAV:}getlastmodified']),
- 'ctime' => (int)strtotime($propFindArray['{DAV:}creationdate']),
- 'mimetype' => (string)$propFindArray['{DAV:}getcontenttype'],
- 'name' => basename($path),
- 'size' => (int)$propFindArray['{DAV:}getcontentlength'],
- 'identifier' => '/' . $path,
- 'storage' => $this->storageUid,
- 'identifier_hash' => sha1('/' . $path),
- 'folder_hash' => sha1('/' . $this->getFolderPathFromIdentifier($path)),
- );
-
- return $fileInfo;
- }
-
- /**
- * @param string $path The identifier, without a leading slash!
- * @return string The folder path, without a trailing slash. If the file is on root level, an empty string is returned
- */
- protected function getFolderPathFromIdentifier($path) {
- $dirPath = dirname($path);
-
- return $dirPath . ($dirPath !== '') ? '/' : '';
- }
-
- /**
- * @param $path
- * @return string
- */
- protected function urlEncodePath($path) {
- // using urlencode() does not work because it encodes a space as "+" and not as "%20".
- return implode('/', array_map('rawurlencode', explode('/', $path)));
- }
+class WebDavFrontend
+{
+
+ /**
+ * @var WebDavClient
+ */
+ protected $davClient;
+
+ /**
+ * The storage base URL, has to be already URL-encoded
+ *
+ * @var string
+ */
+ protected $baseUrl;
+
+ /**
+ * @var string
+ */
+ protected $basePath;
+
+ /**
+ * @var \TYPO3\CMS\Core\Log\Logger
+ */
+ protected $logger;
+
+ /**
+ * @var int
+ */
+ protected $storageUid;
+
+
+ public function __construct(WebDavClient $client, $baseUrl, $storageUid)
+ {
+ $this->logger = GeneralUtility::makeInstance('TYPO3\CMS\Core\Log\LogManager')->getLogger(__CLASS__);
+
+ $this->davClient = $client;
+ $this->baseUrl = rtrim($baseUrl, '/') . '/';
+ $urlParts = parse_url($this->baseUrl);
+ $this->basePath = $urlParts['path'];
+ $this->storageUid = $storageUid;
+ }
+
+ /**
+ * @param string $url
+ * @return string
+ */
+ protected function encodeUrl($url)
+ {
+ $urlParts = parse_url($url);
+ $urlParts['path'] = $this->urlEncodePath($urlParts['path']);
+
+ return HttpUtility::buildUrl($urlParts);
+ }
+
+ /**
+ * Wrapper around the PROPFIND method of the WebDAV client to get proper local error handling.
+ *
+ * @param string $path
+ * @return array
+ * @throws DAV\Exception\NotFound if the given URL does not hold a resource
+ *
+ * TODO define proper error handling for other cases
+ */
+ public function propFind($path)
+ {
+ $url = $this->baseUrl . $path;
+ $encodedUrl = $this->encodeUrl($url);
+
+ try {
+ // propfind() already decodes the XML, so we get back an array
+ $propfindResultArray = $this->davClient->propfind($encodedUrl, null, 1);
+
+ // the returned items are indexed by their key, so sort them here to return the correct items.
+ // At least Apache does not sort them before returning
+ uksort($propfindResultArray, 'strnatcasecmp');
+
+ return $propfindResultArray;
+ } catch (DAV\Exception\NotFound $exception) {
+ $this->logger->warning('URL not found: ' . $url);
+ // If a file is not found, we have to deal with that on a higher level, so throw the exception again
+ throw $exception;
+ } catch (DAV\Exception $exception) {
+ // log all other exceptions
+ $this->logger->error(sprintf(
+ 'Error while executing DAV PROPFIND request. Original message: "%s" (Exception %s, id: %u)',
+ $exception->getMessage(), get_class($exception), $exception->getCode()
+ ));
+
+ // TODO check how we can let this propagate to the driver
+ return array();
+ }
+ }
+
+ /**
+ *
+ * @param string $path
+ * @return array A list of file names in the path
+ */
+ public function listFiles($path)
+ {
+ $files = $this->listItems($path, function ($currentItem) {
+ if (substr($currentItem, -1) == '/') {
+ return false;
+ }
+
+ return true;
+ });
+
+ return $files;
+ }
+
+ /**
+ * @param string $path
+ * @return array A list of folder names in the path
+ */
+ public function listFolders($path)
+ {
+ $folders = $this->listItems($path, function ($currentItem) {
+ if (substr($currentItem, -1) != '/') {
+ return false;
+ }
+
+ return true;
+ });
+
+ return $folders;
+ }
+
+ protected function listItems($path, $itemFilterCallback)
+ {
+ $path = trim($path, '/');
+ if (strlen($path) > 0) {
+ $path .= '/';
+ }
+ $urlParts = parse_url($this->baseUrl . $path);
+ $unencodedBasePath = $urlParts['path'];
+
+ $result = $this->propFind($path);
+
+ // remove first entry, as it is the folder itself
+ array_shift($result);
+
+ $files = array();
+ // $filePath contains the path part of the URL, no server name and protocol!
+ foreach ($result as $filePath => $fileInfo) {
+ $decodedFilePath = urldecode($filePath);
+ $decodedFilePath = substr($decodedFilePath, strlen($unencodedBasePath));
+ // ignore folder entries
+ if (!$itemFilterCallback($decodedFilePath)) {
+ continue;
+ }
+
+ // TODO if depth is > 1, we will also include deeper entries here, which we should not
+
+ $files[] = basename($decodedFilePath);
+ }
+
+ return $files;
+ }
+
+ /**
+ * Returns information about the given file.
+ *
+ * TODO define what to return
+ *
+ * @param string $path
+ * @return array
+ */
+ public function getFileInfo($path)
+ {
+ // the leading slash is already included in baseURL/basePath
+ $path = ltrim($path, '/');
+
+ $result = $this->propFind($path);
+
+ return $this->extractFileInfo($path, $result[$this->basePath . $this->urlEncodePath($path)]);
+ }
+
+ protected function extractFileInfo($path, $propFindArray)
+ {
+ $fileInfo = array(
+ 'mtime' => (int)strtotime($propFindArray['{DAV:}getlastmodified']),
+ 'ctime' => (int)strtotime($propFindArray['{DAV:}creationdate']),
+ 'mimetype' => (string)$propFindArray['{DAV:}getcontenttype'],
+ 'name' => basename($path),
+ 'size' => (int)$propFindArray['{DAV:}getcontentlength'],
+ 'identifier' => '/' . $path,
+ 'storage' => $this->storageUid,
+ 'identifier_hash' => sha1('/' . $path),
+ 'folder_hash' => sha1('/' . $this->getFolderPathFromIdentifier($path)),
+ );
+
+ return $fileInfo;
+ }
+
+ /**
+ * @param string $path The identifier, without a leading slash!
+ * @return string The folder path, without a trailing slash. If the file is on root level, an empty string is
+ * returned
+ */
+ protected function getFolderPathFromIdentifier($path)
+ {
+ $dirPath = dirname($path);
+
+ return $dirPath . ($dirPath !== '') ? '/' : '';
+ }
+
+ /**
+ * @param $path
+ * @return string
+ */
+ protected function urlEncodePath($path)
+ {
+ // using urlencode() does not work because it encodes a space as "+" and not as "%20".
+ return implode('/', array_map('rawurlencode', explode('/', $path)));
+ }
}
use Sabre\DAV;
use TYPO3\CMS\Core\Cache\CacheManager;
use TYPO3\CMS\Core\Cache\Frontend\FrontendInterface;
-use TYPO3\CMS\Core\Resource\Driver\AbstractDriver;
use TYPO3\CMS\Core\Resource\Driver\AbstractHierarchicalFilesystemDriver;
use TYPO3\CMS\Core\Resource\Exception\FileOperationErrorException;
use TYPO3\CMS\Core\Resource\Exception\FolderDoesNotExistException;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Core\Utility\HttpUtility;
use TYPO3\FalWebdav\Dav\CachingWebDavFrontend;
-use TYPO3\FalWebdav\Dav\WebDavFrontend;
use TYPO3\FalWebdav\Dav\WebDavClient;
+use TYPO3\FalWebdav\Dav\WebDavFrontend;
use TYPO3\FalWebdav\Utility\EncryptionUtility;
/**
* The driver class for WebDAV storages.
*/
-class WebDavDriver extends AbstractHierarchicalFilesystemDriver {
-
- /**
- * The base URL of the WebDAV share. Always ends with a trailing slash.
- *
- * @var string
- */
- protected $baseUrl = '';
-
- /**
- * The base URL to fetch resources. Includes authentication information if authentication is enabled, so this must
- * never be published!
- *
- * @var string
- */
- protected $resourceBaseUrl = '';
-
- /**
- * The base path of the WebDAV store. This is the URL without protocol, host and port (i.e., only the path on the host).
- * Always ends with a trailing slash.
- *
- * @var string
- */
- protected $basePath = '';
-
- /**
- * @var WebDavClient
- */
- protected $davClient;
-
- /**
- * The username to use for connecting to the storage.
- *
- * @var string
- */
- protected $username = '';
-
- /**
- * The password to use for connecting to the storage.
- *
- * @var string
- */
- protected $password = '';
-
- /**
- * @var \TYPO3\CMS\Core\Cache\Frontend\AbstractFrontend
- */
- protected $directoryListingCache;
-
- /**
- * @var WebDavFrontend
- */
- protected $frontend;
-
- /**
- * @var \TYPO3\CMS\Core\Log\Logger
- */
- protected $logger;
-
- public function __construct(array $configuration = array()) {
- $this->logger = GeneralUtility::makeInstance('TYPO3\CMS\Core\Log\LogManager')->getLogger(__CLASS__);
-
- parent::__construct($configuration);
- }
-
- /**
- * Initializes this object. This is called by the storage after the driver has been attached.
- *
- * @return void
- */
- public function initialize() {
- $this->capabilities = ResourceStorage::CAPABILITY_BROWSABLE
- + ResourceStorage::CAPABILITY_PUBLIC
- + ResourceStorage::CAPABILITY_WRITABLE;
- }
-
- /**
- * Inject method for the DAV client. Mostly useful for unit tests.
- *
- * @param WebDavClient $client
- */
- public function injectDavClient(WebDavClient $client) {
- $this->davClient = $client;
- }
-
- /**
- * Only used in tests.
- *
- * @param FrontendInterface $cache
- */
- public function injectDirectoryListingCache(FrontendInterface $cache) {
- $this->directoryListingCache = $cache;
- }
-
- public function injectFrontend(WebDavFrontend $frontend) {
- $this->frontend = $frontend;
- }
-
- protected function getFrontend() {
- if (!$this->frontend) {
- $this->frontend = new CachingWebDavFrontend($this->davClient, $this->baseUrl, $this->storageUid, $this->getCache());
- }
- return $this->frontend;
- }
-
- /**
- * @return FrontendInterface
- */
- protected function getCache() {
- if (!$this->directoryListingCache) {
- /** @var CacheManager $cacheManager */
- $cacheManager = GeneralUtility::makeInstance('TYPO3\\CMS\\Core\\Cache\\CacheManager');
- $this->directoryListingCache = $cacheManager->getCache('tx_falwebdav_directorylisting');
- }
-
- return $this->directoryListingCache;
- }
-
- /**
- * Processes the configuration coming from the storage record and prepares the SabreDAV object.
- *
- * @return void
- * @throws \InvalidArgumentException
- */
- public function processConfiguration() {
- foreach ($this->configuration as $key => $value) {
- $this->configuration[$key] = trim($value);
- }
-
- $baseUrl = $this->configuration['baseUrl'];
-
- $urlInfo = parse_url($baseUrl);
- if ($urlInfo === FALSE) {
- throw new \InvalidArgumentException('Invalid base URL configured for WebDAV driver: ' . $this->configuration['baseUrl'], 1325771040);
- }
- $this->basePath = rtrim($urlInfo['path'], '/') . '/';
-
- $extConf = unserialize($GLOBALS['TYPO3_CONF_VARS']['EXT']['extConf']['fal_webdav']);
- $configuration['enableZeroByteFilesIndexing'] = (boolean)$extConf['enableZeroByteFilesIndexing'];
-
- // Use authentication only if enabled
- $settings = array();
- if ($this->configuration['useAuthentication']) {
- $this->username = $urlInfo['user'] ? $urlInfo['user'] : $this->configuration['username'];
- $this->password = $urlInfo['pass'] ? $urlInfo['pass'] : EncryptionUtility::decryptPassword($this->configuration['password']);
- $settings = array(
- 'userName' => $this->username,
- 'password' => $this->password
- );
- }
-
- // make sure the URL contains authentication data and build the resource URL for directly fetching data
- $urlInfo['user'] = $this->username;
- $urlInfo['pass'] = $this->password;
- $this->resourceBaseUrl = rtrim(HttpUtility::buildUrl($urlInfo), '/') . '/';
- // create cleaned URL without credentials
- unset($urlInfo['user']);
- unset($urlInfo['pass']);
- $this->baseUrl = rtrim(HttpUtility::buildUrl($urlInfo), '/') . '/';
- $settings['baseUri'] = $this->baseUrl;
-
- $this->davClient = new WebDavClient($settings);
- $this->davClient->setThrowExceptions(TRUE);
-
- $this->davClient->setCertificateVerification($this->configuration['disableCertificateVerification'] != 1);
- }
-
- /**
- * Checks if a configuration is valid for this driver.
- *
- * Throws an exception if a configuration will not work.
- *
- * @param array $configuration
- * @return void
- */
- public static function verifyConfiguration(array $configuration) {
- // TODO: Implement verifyConfiguration() method.
- }
-
- /**
- * Executes a MOVE request from $oldPath to $newPath.
- *
- * @param string $oldPath
- * @param string $newPath
- * @return array The result as returned by SabreDAV
- */
- public function executeMoveRequest($oldPath, $newPath) {
- $oldUrl = $this->baseUrl . ltrim($oldPath, '/');
- $newUrl = $this->baseUrl . ltrim($newPath, '/');
-
- // force overwriting the file (header Overwrite: T) because the Storage already handled possible conflicts
- // for us
- return $this->executeDavRequest('MOVE', $oldUrl, NULL, array('Destination' => $newUrl, 'Overwrite' => 'T'));
- }
-
- protected function encodeUrl($url) {
- $urlParts = parse_url($url);
- $urlParts['path'] = implode('/', array_map('rawurlencode', explode('/', $urlParts['path'])));
-
- return HttpUtility::buildUrl($urlParts);
- }
-
- /**
- * Executes a request on the DAV driver.
- *
- * @param string $method
- * @param string $url
- * @param string $body
- * @param array $headers
- * @return array
- * @throws \Exception If anything goes wrong
- */
- protected function executeDavRequest($method, $url, $body = NULL, array $headers = array()) {
- try {
- $url = $this->encodeUrl($url);
- return $this->davClient->request($method, $url, $body, $headers);
- } catch (\Sabre\DAV\Exception\NotFound $exception) {
- // If a file is not found, we have to deal with that on a higher level, so throw the exception again
- throw $exception;
- } catch (DAV\Exception $exception) {
- // log all other exceptions
- $this->logger->error(sprintf(
- 'Error while executing DAV request. Original message: "%s" (Exception %s, id: %u)',
- $exception->getMessage(), get_class($exception), $exception->getCode()
- ));
- // TODO check how we can let this propagate to the driver
- return array();
- }
- }
-
-
-
- /**
- * Checks if a given resource exists in this DAV share.
- *
- * @param string $resourcePath The path to the resource, i.e. a regular identifier as used everywhere else here.
- * @return bool
- * @throws \InvalidArgumentException
- */
- public function resourceExists($resourcePath) {
- if ($resourcePath == '') {
- throw new \InvalidArgumentException('Resource path cannot be empty');
- }
- $url = $this->baseUrl . ltrim($resourcePath, '/');
- try {
- $result = $this->executeDavRequest('HEAD', $url);
- } catch (\Sabre\Http\HttpException $exception) {
- return $exception->getHttpStatus() != 404;
- }
- // TODO check if other status codes may also indicate that the file is not present
- return $result['statusCode'] < 400;
- }
-
-
- /**
- * Returns the complete URL to a file. This is not necessarily the publicly available URL!
- *
- * @param string $file The file object or its identifier
- * @return string
- */
- protected function getResourceUrl($file) {
- return $this->resourceBaseUrl . ltrim($file, '/');
- }
-
- /**
- * Returns the public URL to a file. This does not contain a username or password, even if this is
- * necessary to display the file.
- *
- * TODO make it optional to include the username/password
- *
- * @param string $identifier
- * @return string
- */
- public function getPublicUrl($identifier) {
- // as the storage is marked as public, we can simply use the public URL here.
- return $this->baseUrl . ltrim($identifier, '/');
- }
-
- /**
- * Creates a (cryptographic) hash for a file.
- *
- * @param string $identifier The file identifier
- * @param string $hashAlgorithm The hash algorithm to use
- * @return string
- * TODO switch parameter order?
- */
- public function hash($identifier, $hashAlgorithm) {
- // TODO add unit test
- $fileCopy = $this->copyFileToTemporaryPath($identifier);
-
- switch ($hashAlgorithm) {
- case 'sha1':
- $hash = sha1_file($fileCopy);
- break;
-
- default:
- throw new \InvalidArgumentException('Unsupported hash algorithm ' . $hashAlgorithm);
- }
-
- unlink($fileCopy);
-
- return $hash;
- }
-
- /**
- * Creates a new file and returns its identifier.
- *
- * @param string $fileName
- * @param string $parentFolderIdentifier
- * @return \TYPO3\CMS\Core\Resource\FileInterface
- */
- public function createFile($fileName, $parentFolderIdentifier) {
- $fileIdentifier = $parentFolderIdentifier . $fileName;
- $fileUrl = $this->baseUrl . ltrim($fileIdentifier, '/');
-
- $this->executeDavRequest('PUT', $fileUrl, '');
-
- $this->removeCacheForPath($parentFolderIdentifier);
-
- return $fileIdentifier;
- }
-
- /**
- * Returns the contents of a file. Beware that this requires to load the complete file into memory and also may
- * require fetching the file from an external location. So this might be an expensive operation (both in terms of
- * processing resources and money) for large files.
- *
- * @param \TYPO3\CMS\Core\Resource\FileInterface $fileIdentifier
- * @return string The file contents
- */
- public function getFileContents($fileIdentifier) {
- $fileUrl = $this->baseUrl . ltrim($fileIdentifier, '/');
-
- $result = $this->executeDavRequest('GET', $fileUrl);
-
- return $result['body'];
- }
-
- /**
- * Sets the contents of a file to the specified value.
- *
- * @param string $fileIdentifier
- * @param string $contents
- * @return bool TRUE if setting the contents succeeded
- * @throws \RuntimeException if the operation failed
- */
- public function setFileContents($fileIdentifier, $contents) {
- // Apache returns a "204 no content" status after a successful put operation
-
- $fileUrl = $this->getResourceUrl($fileIdentifier);
- $result = $this->executeDavRequest('PUT', $fileUrl, $contents);
-
- $this->removeCacheForPath(dirname($fileIdentifier));
-
- // TODO check result
- }
-
- /**
- * Adds a file from the local server hard disk to a given path in TYPO3s virtual file system.
- *
- * This assumes that the local file exists, so no further check is done here!
- *
- * @param string $localFilePath (within PATH_site)
- * @param string $targetFolderIdentifier
- * @param string $newFileName optional, if not given original name is used
- * @param boolean $removeOriginal if set the original file will be removed
- * after successful operation
- * @return string the identifier of the new file
- */
- public function addFile($localFilePath, $targetFolderIdentifier, $newFileName = '', $removeOriginal = TRUE) {
- $fileIdentifier = $targetFolderIdentifier . $newFileName;
- $fileUrl = $this->baseUrl . ltrim($fileIdentifier);
-
- $fileHandle = fopen($localFilePath, 'r');
- if (!is_resource($fileHandle)) {
- throw new \RuntimeException('Could not open handle for ' . $localFilePath, 1325959310);
- }
- $result = $this->executeDavRequest('PUT', $fileUrl, $fileHandle);
-
- // TODO check result
-
- $this->removeCacheForPath($targetFolderIdentifier);
-
- return $fileIdentifier;
- }
-
- /**
- * Checks if a file exists.
- *
- * @param string $identifier
- * @return bool
- */
- public function fileExists($identifier) {
- return substr($identifier, -1) !== '/' && $this->resourceExists($identifier);
- }
-
- /**
- * Checks if a file inside a storage folder exists.
- *
- * @param string $fileName
- * @param string $folderIdentifier
- * @return boolean
- */
- public function fileExistsInFolder($fileName, $folderIdentifier) {
- // TODO add unit test
- $fileIdentifier = $folderIdentifier . $fileName;
-
- return $this->fileExists($fileIdentifier);
- }
-
- /**
- * Returns a (local copy of) a file for processing it. When changing the file, you have to take care of replacing the
- * current version yourself!
- *
- * @param string $fileIdentifier
- * @param bool $writable Set this to FALSE if you only need the file for read operations. This might speed up things, e.g. by using a cached local version. Never modify the file if you have set this flag!
- * @return string The path to the file on the local disk
- */
- public function getFileForLocalProcessing($fileIdentifier, $writable = TRUE) {
- return $this->copyFileToTemporaryPath($fileIdentifier);
- }
-
- /**
- * Renames a file
- *
- * @param \TYPO3\CMS\Core\Resource\FileInterface $file
- * @param string $newName
- * @return string The new identifier of the file
- */
- public function renameFile($fileIdentifier, $newName) {
- // TODO add unit test
- // Renaming works by invoking the MOVE method on the source URL and providing the new destination in the
- // "Destination:" HTTP header.
- $sourcePath = $fileIdentifier;
- $targetPath = dirname($fileIdentifier) . '/' . $newName;
-
- $this->executeMoveRequest($sourcePath, $targetPath);
-
- $this->removeCacheForPath(dirname($fileIdentifier));
-
- return $targetPath;
- }
-
- /**
- * Replaces the contents (and file-specific metadata) of a file object with a local file.
- *
- * @param string $fileIdentifier
- * @param string $localFilePath
- * @return bool
- * @throws \RuntimeException
- */
- public function replaceFile($fileIdentifier, $localFilePath) {
- $fileUrl = $this->getResourceUrl($fileIdentifier);
- $fileHandle = fopen($localFilePath, 'r');
- if (!is_resource($fileHandle)) {
- throw new \RuntimeException('Could not open handle for ' . $localFilePath, 1325959311);
- }
-
- $this->removeCacheForPath(dirname($fileIdentifier));
-
- $this->executeDavRequest('PUT', $fileUrl, $fileHandle);
- }
-
- /**
- * Returns information about a file for a given file identifier.
- *
- * @param string $identifier The (relative) path to the file.
- * @param array $propertiesToExtract The properties to get
- * @return array
- */
- public function getFileInfoByIdentifier($identifier, array $propertiesToExtract = array()) {
- assert($identifier[0] === '/', 'Identifier must start with a slash, got ' . $identifier);
-
- return $this->getFrontend()->getFileInfo($identifier);
- }
-
- /**
- * Returns the cache identifier for a given path.
- *
- * @param string $path
- * @return string
- */
- protected function getCacheIdentifierForPath($path) {
- return sha1($this->storageUid . ':' . trim($path, '/') . '/');
- }
-
- /**
- * Flushes the cache for a given path inside this storage.
- *
- * @param $path
- * @return void
- * @deprecated this should be moved to WebDavFrontend
- */
- protected function removeCacheForPath($path) {
- $this->getCache()->remove($this->getCacheIdentifierForPath($path));
- }
-
- /**
- * Copies a file to a temporary path and returns that path. You have to take care of removing the temporary file yourself!
- *
- * @param string $fileIdentifier
- * @return string The temporary path
- */
- public function copyFileToTemporaryPath($fileIdentifier) {
- $temporaryPath = GeneralUtility::tempnam('vfs-tempfile-');
- $fileUrl = $this->getResourceUrl($fileIdentifier);
-
- $fileHandle = fopen($temporaryPath, 'w');
- $fileUrl = $this->encodeUrl($fileUrl);
- $this->davClient->readUrlToHandle($fileUrl, $fileHandle);
-
- // the handle is not closed by readUrlToHandle()!
- fclose($fileHandle);
-
- return $temporaryPath;
- }
-
- /**
- * Moves a file *within* the current storage.
- * Note that this is only about an intra-storage move action, where a file is just
- * moved to another folder in the same storage.
- *
- * @param string $fileIdentifier
- * @param string $targetFolderIdentifier
- * @param string $newFileName
- *
- * @return string
- * @throws FileOperationErrorException
- */
- public function moveFileWithinStorage($fileIdentifier, $targetFolderIdentifier, $newFileName) {
- $newPath = $targetFolderIdentifier . $newFileName;
-
- try {
- $result = $this->executeMoveRequest($fileIdentifier, $newPath);
- } catch (DAV\Exception $e) {
- // TODO insert correct exception here
- throw new FileOperationErrorException('Moving file ' . $fileIdentifier
- . ' to ' . $newPath . ' failed.', 1325848030);
- }
- // TODO check if there are some return codes that signalize an error, but do not throw an exception
- // status codes: 204: file was overwritten; 201: file was created;
-
- return $newPath;
- }
-
- /**
- * Copies a file *within* the current storage.
- * Note that this is only about an intra-storage copy action, where a file is just
- * copied to another folder in the same storage.
- *
- * @param string $fileIdentifier
- * @param string $targetFolderIdentifier
- * @param string $fileName
- *
- * @return string the Identifier of the new file
- * @throws FileOperationErrorException
- */
- public function copyFileWithinStorage($fileIdentifier, $targetFolderIdentifier, $fileName) {
- $oldFileUrl = $this->getResourceUrl($fileIdentifier);
- $newFileUrl = $this->getResourceUrl($targetFolderIdentifier) . $fileName;
- $newFileIdentifier = $targetFolderIdentifier . $fileName;
-
- try {
- // force overwriting the file (header Overwrite: T) because the Storage already handled possible conflicts
- // for us
- $result = $this->executeDavRequest('COPY', $oldFileUrl, NULL, array('Destination' => $newFileUrl, 'Overwrite' => 'T'));
- } catch (DAV\Exception $e) {
- // TODO insert correct exception here
- throw new FileOperationErrorException('Copying file ' . $fileIdentifier . ' to '
- . $newFileIdentifier . ' failed.', 1325848030);
- }
- // TODO check if there are some return codes that signalize an error, but do not throw an exception
- // status codes: 204: file was overwritten; 201: file was created;
-
- return $newFileIdentifier;
- }
-
- /**
- * Folder equivalent to moveFileWithinStorage().
- *
- * @param string $sourceFolderIdentifier
- * @param string $targetFolderIdentifier
- * @param string $newFolderName
- *
- * @return array All files which are affected, map of old => new file identifiers
- * @throws FileOperationErrorException
- */
- public function moveFolderWithinStorage($sourceFolderIdentifier, $targetFolderIdentifier, $newFolderName) {
- $newFolderIdentifier = $targetFolderIdentifier . $newFolderName . '/';
-
- try {
- $result = $this->executeMoveRequest($sourceFolderIdentifier, $newFolderIdentifier);
- } catch (DAV\Exception $e) {
- // TODO insert correct exception here
- throw new FileOperationErrorException('Moving folder ' . $sourceFolderIdentifier
- . ' to ' . $newFolderIdentifier . ' failed: ' . $e->getMessage(), 1326135944);
- }
- // TODO check if there are some return codes that signalize an error, but do not throw an exception
- // status codes: 204: file was overwritten; 201: file was created;
-
- // TODO extract mapping of old to new identifiers from server response
- }
-
- /**
- * Folder equivalent to copyFileWithinStorage().
- *
- * @param string $sourceFolderIdentifier
- * @param string $targetFolderIdentifier
- * @param string $newFolderName
- *
- * @return boolean
- * @throws FileOperationErrorException
- */
- public function copyFolderWithinStorage($sourceFolderIdentifier, $targetFolderIdentifier, $newFolderName) {
- $oldFolderUrl = $this->getResourceUrl($sourceFolderIdentifier);
- $newFolderUrl = $this->getResourceUrl($targetFolderIdentifier) . $newFolderName . '/';
- $newFolderIdentifier = $targetFolderIdentifier . $newFolderName . '/';
-
- try {
- $result = $this->executeDavRequest('COPY', $oldFolderUrl, NULL, array('Destination' => $newFolderUrl, 'Overwrite' => 'T'));
- } catch (DAV\Exception $e) {
- // TODO insert correct exception here
- throw new FileOperationErrorException('Moving folder ' . $sourceFolderIdentifier
- . ' to ' . $newFolderIdentifier . ' failed.', 1326135944);
- }
- // TODO check if there are some return codes that signalize an error, but do not throw an exception
- // status codes: 204: file was overwritten; 201: file was created;
-
- return $newFolderIdentifier;
- }
-
- /**
- * Removes a file from this storage. This does not check if the file is still used or if it is a bad idea to delete
- * it for some other reason - this has to be taken care of in the upper layers (e.g. the Storage)!
- *
- * @param string $fileIdentifier
- * @return boolean TRUE if the operation succeeded
- */
- public function deleteFile($fileIdentifier) {
- // TODO add unit tests
- $fileUrl = $this->baseUrl . ltrim($fileIdentifier, '/');
-
- $result = $this->executeDavRequest('DELETE', $fileUrl);
-
- // 204 is derived from the answer Apache gives - there might be other status codes that indicate success
- return ($result['statusCode'] == 204);
- }
-
- /**
- * Returns the root level folder of the storage.
- *
- * @return \TYPO3\CMS\Core\Resource\Folder
- */
- public function getRootLevelFolder() {
- return '/';
- }
-
- /**
- * Returns the default folder new files should be put into.
- *
- * @return \TYPO3\CMS\Core\Resource\Folder
- */
- public function getDefaultFolder() {
- return '/';
- }
-
- /**
- * Creates a folder, within a parent folder.
- * If no parent folder is given, a root level folder will be created
- *
- * @param string $newFolderName
- * @param string $parentFolderIdentifier
- * @param boolean $recursive If set, parent folders will be created if they don’t exist
- * @return string The new folder’s identifier
- */
- public function createFolder($newFolderName, $parentFolderIdentifier = '', $recursive = FALSE) {
- // TODO test if recursive creation works
- // We add a slash to the path as some actions require a trailing slash on some servers.
- // Apache's mod_dav e.g. does not do it for this action, but it does not do harm either, so we add it anyways
- $folderPath = $parentFolderIdentifier . $newFolderName . '/';
- $folderUrl = $this->baseUrl . ltrim($folderPath, '/');
-
- $this->executeDavRequest('MKCOL', $folderUrl);
-
- $this->removeCacheForPath($parentFolderIdentifier);
-
- return $folderPath;
- }
-
- /**
- * Checks if a folder exists
- *
- * @param string $identifier
- * @return bool
- */
- public function folderExists($identifier) {
- // TODO add unit test
- // TODO check if this test suffices to find out if the resource really is a folder - it might not do with some implementations
- $identifier = '/' . trim($identifier, '/') . '/';
- return $this->resourceExists($identifier);
- }
-
- /**
- * Checks if a file inside a storage folder exists.
- *
- * @param string $folderName
- * @param string $folderIdentifier
- * @return bool
- */
- public function folderExistsInFolder($folderName, $folderIdentifier) {
- $folderIdentifier = $folderIdentifier . $folderName . '/';
- return $this->resourceExists($folderIdentifier);
- }
-
- /**
- * Checks if a given identifier is within a container, e.g. if a file or folder is within another folder.
- * This can be used to check for webmounts.
- *
- * @param string $containerIdentifier
- * @param string $content
- * @return bool
- */
- public function isWithin($containerIdentifier, $content) {
- $content = '/' . ltrim($content, '/');
-
- return GeneralUtility::isFirstPartOfStr($content, $containerIdentifier);
- }
-
- /**
- * Removes a folder from this storage.
- *
- * @param string $folderIdentifier
- * @param bool $deleteRecursively
- * @return boolean
- */
- public function deleteFolder($folderIdentifier, $deleteRecursively = FALSE) {
- $folderUrl = $this->getResourceUrl($folderIdentifier);
-
- $this->removeCacheForPath(dirname($folderIdentifier));
-
- // We don't need to specify a depth header when deleting (see sect. 9.6.1 of RFC #4718)
- $this->executeDavRequest('DELETE', $folderUrl, '', array());
- }
-
- /**
- * Renames a folder in this storage.
- *
- * @param string $folderIdentifier
- * @param string $newName The new folder name
- * @return string The new identifier of the folder if the operation succeeds
- * @throws \RuntimeException if renaming the folder failed
- * @throws FileOperationErrorException
- */
- public function renameFolder($folderIdentifier, $newName) {
- $targetPath = dirname($folderIdentifier) . '/' . $newName . '/';
-
- try {
- $result = $this->executeMoveRequest($folderIdentifier, $targetPath);
- } catch (DAV\Exception $e) {
- // TODO insert correct exception here
- throw new FileOperationErrorException('Renaming ' . $folderIdentifier . ' to '
- . $targetPath . ' failed.', 1325848030);
- }
-
- $this->removeCacheForPath(dirname($folderIdentifier));
-
- return $targetPath;
- }
-
- /**
- * Checks if a folder contains files and (if supported) other folders.
- *
- * @param string $folderIdentifier
- * @return bool TRUE if there are no files and folders within $folder
- */
- public function isFolderEmpty($folderIdentifier) {
- $folderContents = $this->frontend->propFind($folderIdentifier);
-
- return (count($folderContents) == 1);
- }
-
- /**
- * Merges the capabilites set by the administrator in the storage configuration with the actual capabilities of
- * this driver and returns the result.
- *
- * @param integer $capabilities
- *
- * @return integer
- */
- public function mergeConfigurationCapabilities($capabilities) {
- $this->capabilities &= $capabilities;
- return $this->capabilities;
- }
-
- /**
- * Returns the identifier of the folder the file resides in
- *
- * @param string $fileIdentifier
- *
- * @return string
- */
- public function getParentFolderIdentifierOfIdentifier($fileIdentifier) {
- return dirname($fileIdentifier);
- }
-
- /**
- * Returns the permissions of a file/folder as an array
- * (keys r, w) of boolean flags
- *
- * @param string $identifier
- * @return array
- */
- public function getPermissions($identifier) {
- // TODO check this again
- return array('r' => TRUE, 'w' => TRUE);
- }
-
- /**
- * Directly output the contents of the file to the output
- * buffer. Should not take care of header files or flushing
- * buffer before. Will be taken care of by the Storage.
- *
- * @param string $identifier
- * @return void
- */
- public function dumpFileContents($identifier) {
- // TODO: Implement dumpFileContents() method.
- }
-
- /**
- * Returns information about a file.
- *
- * @param string $folderIdentifier
- *
- * @return array
- * @throws FolderDoesNotExistException
- */
- public function getFolderInfoByIdentifier($folderIdentifier) {
- if (!$this->folderExists($folderIdentifier)) {
- throw new FolderDoesNotExistException(
- 'Folder ' . $folderIdentifier . ' does not exist.',
- 1314516810
- );
- }
- return array(
- 'identifier' => $folderIdentifier,
- 'name' => basename($folderIdentifier),
- 'storage' => $this->storageUid
- );
- }
-
- /**
- * Returns the identifier of a file inside the folder.
- *
- * @param string $fileName
- * @param string $folderIdentifier
- * @return string file identifier
- */
- public function getFileInFolder($fileName, $folderIdentifier) {
- return $this->canonicalizeAndCheckFileIdentifier($folderIdentifier . '/' . $fileName);
- }
-
- /**
- * Returns a list of files inside the specified path
- *
- * @param string $folderIdentifier
- * @param integer $start
- * @param integer $numberOfItems
- * @param boolean $recursive
- * @param array $filenameFilterCallbacks callbacks for filtering the items
- * @param string $sort Property name used to sort the items.
- * Among them may be: '' (empty, no sorting), name,
- * fileext, size, tstamp and rw.
- * If a driver does not support the given property, it
- * should fall back to "name".
- * @param bool $sortRev TRUE to indicate reverse sorting (last to first)
- *
- * @return array of FileIdentifiers
- */
- public function getFilesInFolder($folderIdentifier, $start = 0, $numberOfItems = 0, $recursive = FALSE,
- array $filenameFilterCallbacks = array(), $sort = '', $sortRev = FALSE) {
- $files = $this->getFrontend()->listFiles($folderIdentifier);
-
- // TODO implement sorting
-
- $items = array();
- foreach ($files as $filename) {
- $items[$filename] = $folderIdentifier . $filename;
- }
-
- return $items;
- }
-
- /**
- * Returns the number of files inside the specified path
- *
- * @param string $folderIdentifier
- * @param boolean $recursive
- * @param array $filenameFilterCallbacks callbacks for filtering the items
- * @return integer Number of files in folder
- */
- public function countFilesInFolder($folderIdentifier, $recursive = FALSE,
- array $filenameFilterCallbacks = array()) {
- return count($this->getFrontend()->listFiles($folderIdentifier));
- }
-
- /**
- * Returns the identifier of a folder inside the folder.
- *
- * @param string $folderName The name of the target folder
- * @param string $folderIdentifier
- * @return string folder identifier
- */
- public function getFolderInFolder($folderName, $folderIdentifier) {
- return $this->canonicalizeAndCheckFolderIdentifier($folderIdentifier . '/' . $folderName);
- }
-
- /**
- * Returns a list of folders inside the specified path
- *
- * @param string $folderIdentifier
- * @param integer $start
- * @param integer $numberOfItems
- * @param boolean $recursive
- * @param array $folderNameFilterCallbacks callbacks for filtering the items
- * @param string $sort Property name used to sort the items.
- * Among them may be: '' (empty, no sorting), name,
- * fileext, size, tstamp and rw.
- * If a driver does not support the given property, it
- * should fall back to "name".
- * @param bool $sortRev TRUE to indicate reverse sorting (last to first)
- *
- * @return array of folder identifiers
- */
- public function getFoldersInFolder($folderIdentifier, $start = 0, $numberOfItems = 0, $recursive = FALSE,
- array $folderNameFilterCallbacks = array(), $sort = '', $sortRev = FALSE) {
- $folders = $this->getFrontend()->listFolders($folderIdentifier);
-
- // TODO implement sorting
-
- $items = array();
- foreach ($folders as $name) {
- $items[$name] = $folderIdentifier . $name . '/';
- }
-
- return $items;
- }
-
- /**
- * Returns the number of folders inside the specified path
- *
- * @param string $folderIdentifier
- * @param boolean $recursive
- * @param array $folderNameFilterCallbacks callbacks for filtering the items
- * @return integer Number of folders in folder
- */
- public function countFoldersInFolder($folderIdentifier, $recursive = FALSE,
- array $folderNameFilterCallbacks = array()) {
- return count($this->getFrontend()->listFolders($folderIdentifier));
- }
+class WebDavDriver extends AbstractHierarchicalFilesystemDriver
+{
+
+ /**
+ * The base URL of the WebDAV share. Always ends with a trailing slash.
+ *
+ * @var string
+ */
+ protected $baseUrl = '';
+
+ /**
+ * The base URL to fetch resources. Includes authentication information if authentication is enabled, so this must
+ * never be published!
+ *
+ * @var string
+ */
+ protected $resourceBaseUrl = '';
+
+ /**
+ * The base path of the WebDAV store. This is the URL without protocol, host and port (i.e., only the path on the
+ * host). Always ends with a trailing slash.
+ *
+ * @var string
+ */
+ protected $basePath = '';
+
+ /**
+ * @var WebDavClient
+ */
+ protected $davClient;
+
+ /**
+ * The username to use for connecting to the storage.
+ *
+ * @var string
+ */
+ protected $username = '';
+
+ /**
+ * The password to use for connecting to the storage.
+ *
+ * @var string
+ */
+ protected $password = '';
+
+ /**
+ * @var \TYPO3\CMS\Core\Cache\Frontend\AbstractFrontend
+ */
+ protected $directoryListingCache;
+
+ /**
+ * @var WebDavFrontend
+ */
+ protected $frontend;
+
+ /**
+ * @var \TYPO3\CMS\Core\Log\Logger
+ */
+ protected $logger;
+
+ public function __construct(array $configuration = array())
+ {
+ $this->logger = GeneralUtility::makeInstance('TYPO3\CMS\Core\Log\LogManager')->getLogger(__CLASS__);
+
+ parent::__construct($configuration);
+ }
+
+ /**
+ * Initializes this object. This is called by the storage after the driver has been attached.
+ *
+ * @return void
+ */
+ public function initialize()
+ {
+ $this->capabilities = ResourceStorage::CAPABILITY_BROWSABLE
+ + ResourceStorage::CAPABILITY_PUBLIC
+ + ResourceStorage::CAPABILITY_WRITABLE;
+ }
+
+ /**
+ * Inject method for the DAV client. Mostly useful for unit tests.
+ *
+ * @param WebDavClient $client
+ */
+ public function injectDavClient(WebDavClient $client)
+ {
+ $this->davClient = $client;
+ }
+
+ /**
+ * Only used in tests.
+ *
+ * @param FrontendInterface $cache
+ */
+ public function injectDirectoryListingCache(FrontendInterface $cache)
+ {
+ $this->directoryListingCache = $cache;
+ }
+
+ public function injectFrontend(WebDavFrontend $frontend)
+ {
+ $this->frontend = $frontend;
+ }
+
+ protected function getFrontend()
+ {
+ if (!$this->frontend) {
+ $this->frontend = new CachingWebDavFrontend($this->davClient, $this->baseUrl, $this->storageUid,
+ $this->getCache());
+ }
+
+ return $this->frontend;
+ }
+
+ /**
+ * @return FrontendInterface
+ */
+ protected function getCache()
+ {
+ if (!$this->directoryListingCache) {
+ /** @var CacheManager $cacheManager */
+ $cacheManager = GeneralUtility::makeInstance('TYPO3\\CMS\\Core\\Cache\\CacheManager');
+ $this->directoryListingCache = $cacheManager->getCache('tx_falwebdav_directorylisting');
+ }
+
+ return $this->directoryListingCache;
+ }
+
+ /**
+ * Processes the configuration coming from the storage record and prepares the SabreDAV object.
+ *
+ * @return void
+ * @throws \InvalidArgumentException
+ */
+ public function processConfiguration()
+ {
+ foreach ($this->configuration as $key => $value) {
+ $this->configuration[$key] = trim($value);
+ }
+
+ $baseUrl = $this->configuration['baseUrl'];
+
+ $urlInfo = parse_url($baseUrl);
+ if ($urlInfo === false) {
+ throw new \InvalidArgumentException('Invalid base URL configured for WebDAV driver: ' . $this->configuration['baseUrl'],
+ 1325771040);
+ }
+ $this->basePath = rtrim($urlInfo['path'], '/') . '/';
+
+ $extConf = unserialize($GLOBALS['TYPO3_CONF_VARS']['EXT']['extConf']['fal_webdav']);
+ $configuration['enableZeroByteFilesIndexing'] = (boolean)$extConf['enableZeroByteFilesIndexing'];
+
+ // Use authentication only if enabled
+ $settings = array();
+ if ($this->configuration['useAuthentication']) {
+ $this->username = $urlInfo['user'] ? $urlInfo['user'] : $this->configuration['username'];
+ $this->password = $urlInfo['pass'] ? $urlInfo['pass'] : EncryptionUtility::decryptPassword($this->configuration['password']);
+ $settings = array(
+ 'userName' => $this->username,
+ 'password' => $this->password
+ );
+ }
+
+ // make sure the URL contains authentication data and build the resource URL for directly fetching data
+ $urlInfo['user'] = $this->username;
+ $urlInfo['pass'] = $this->password;
+ $this->resourceBaseUrl = rtrim(HttpUtility::buildUrl($urlInfo), '/') . '/';
+ // create cleaned URL without credentials
+ unset($urlInfo['user']);
+ unset($urlInfo['pass']);
+ $this->baseUrl = rtrim(HttpUtility::buildUrl($urlInfo), '/') . '/';
+ $settings['baseUri'] = $this->baseUrl;
+
+ $this->davClient = new WebDavClient($settings);
+ $this->davClient->setThrowExceptions(true);
+
+ $this->davClient->setCertificateVerification($this->configuration['disableCertificateVerification'] != 1);
+ }
+
+ /**
+ * Checks if a configuration is valid for this driver.
+ *
+ * Throws an exception if a configuration will not work.
+ *
+ * @param array $configuration
+ * @return void
+ */
+ public static function verifyConfiguration(array $configuration)
+ {
+ // TODO: Implement verifyConfiguration() method.
+ }
+
+ /**
+ * Executes a MOVE request from $oldPath to $newPath.
+ *
+ * @param string $oldPath
+ * @param string $newPath
+ * @return array The result as returned by SabreDAV
+ */
+ public function executeMoveRequest($oldPath, $newPath)
+ {
+ $oldUrl = $this->baseUrl . ltrim($oldPath, '/');
+ $newUrl = $this->baseUrl . ltrim($newPath, '/');
+
+ // force overwriting the file (header Overwrite: T) because the Storage already handled possible conflicts
+ // for us
+ return $this->executeDavRequest('MOVE', $oldUrl, null, array('Destination' => $newUrl, 'Overwrite' => 'T'));
+ }
+
+ protected function encodeUrl($url)
+ {
+ $urlParts = parse_url($url);
+ $urlParts['path'] = implode('/', array_map('rawurlencode', explode('/', $urlParts['path'])));
+
+ return HttpUtility::buildUrl($urlParts);
+ }
+
+ /**
+ * Executes a request on the DAV driver.
+ *
+ * @param string $method
+ * @param string $url
+ * @param string $body
+ * @param array $headers
+ * @return array
+ * @throws \Exception If anything goes wrong
+ */
+ protected function executeDavRequest($method, $url, $body = null, array $headers = array())
+ {
+ try {
+ $url = $this->encodeUrl($url);
+
+ return $this->davClient->request($method, $url, $body, $headers);
+ } catch (\Sabre\DAV\Exception\NotFound $exception) {
+ // If a file is not found, we have to deal with that on a higher level, so throw the exception again
+ throw $exception;
+ } catch (DAV\Exception $exception) {
+ // log all other exceptions
+ $this->logger->error(sprintf(
+ 'Error while executing DAV request. Original message: "%s" (Exception %s, id: %u)',
+ $exception->getMessage(), get_class($exception), $exception->getCode()
+ ));
+
+ // TODO check how we can let this propagate to the driver
+ return array();
+ }
+ }
+
+
+ /**
+ * Checks if a given resource exists in this DAV share.
+ *
+ * @param string $resourcePath The path to the resource, i.e. a regular identifier as used everywhere else here.
+ * @return bool
+ * @throws \InvalidArgumentException
+ */
+ public function resourceExists($resourcePath)
+ {
+ if ($resourcePath == '') {
+ throw new \InvalidArgumentException('Resource path cannot be empty');
+ }
+ $url = $this->baseUrl . ltrim($resourcePath, '/');
+ try {
+ $result = $this->executeDavRequest('HEAD', $url);
+ } catch (\Sabre\Http\HttpException $exception) {
+ return $exception->getHttpStatus() != 404;
+ }
+
+ // TODO check if other status codes may also indicate that the file is not present
+ return $result['statusCode'] < 400;
+ }
+
+
+ /**
+ * Returns the complete URL to a file. This is not necessarily the publicly available URL!
+ *
+ * @param string $file The file object or its identifier
+ * @return string
+ */
+ protected function getResourceUrl($file)
+ {
+ return $this->resourceBaseUrl . ltrim($file, '/');
+ }
+
+ /**
+ * Returns the public URL to a file. This does not contain a username or password, even if this is
+ * necessary to display the file.
+ *
+ * TODO make it optional to include the username/password
+ *
+ * @param string $identifier
+ * @return string
+ */
+ public function getPublicUrl($identifier)
+ {
+ // as the storage is marked as public, we can simply use the public URL here.
+ return $this->baseUrl . ltrim($identifier, '/');
+ }
+
+ /**
+ * Creates a (cryptographic) hash for a file.
+ *
+ * @param string $identifier The file identifier
+ * @param string $hashAlgorithm The hash algorithm to use
+ * @return string
+ * TODO switch parameter order?
+ */
+ public function hash($identifier, $hashAlgorithm)
+ {
+ // TODO add unit test
+ $fileCopy = $this->copyFileToTemporaryPath($identifier);
+
+ switch ($hashAlgorithm) {
+ case 'sha1':
+ $hash = sha1_file($fileCopy);
+ break;
+
+ default:
+ throw new \InvalidArgumentException('Unsupported hash algorithm ' . $hashAlgorithm);
+ }
+
+ unlink($fileCopy);
+
+ return $hash;
+ }
+
+ /**
+ * Creates a new file and returns its identifier.
+ *
+ * @param string $fileName
+ * @param string $parentFolderIdentifier
+ * @return \TYPO3\CMS\Core\Resource\FileInterface
+ */
+ public function createFile($fileName, $parentFolderIdentifier)
+ {
+ $fileIdentifier = $parentFolderIdentifier . $fileName;
+ $fileUrl = $this->baseUrl . ltrim($fileIdentifier, '/');
+
+ $this->executeDavRequest('PUT', $fileUrl, '');
+
+ $this->removeCacheForPath($parentFolderIdentifier);
+
+ return $fileIdentifier;
+ }
+
+ /**
+ * Returns the contents of a file. Beware that this requires to load the complete file into memory and also may
+ * require fetching the file from an external location. So this might be an expensive operation (both in terms of
+ * processing resources and money) for large files.
+ *
+ * @param \TYPO3\CMS\Core\Resource\FileInterface $fileIdentifier
+ * @return string The file contents
+ */
+ public function getFileContents($fileIdentifier)
+ {
+ $fileUrl = $this->baseUrl . ltrim($fileIdentifier, '/');
+
+ $result = $this->executeDavRequest('GET', $fileUrl);
+
+ return $result['body'];
+ }
+
+ /**
+ * Sets the contents of a file to the specified value.
+ *
+ * @param string $fileIdentifier
+ * @param string $contents
+ * @return bool TRUE if setting the contents succeeded
+ * @throws \RuntimeException if the operation failed
+ */
+ public function setFileContents($fileIdentifier, $contents)
+ {
+ // Apache returns a "204 no content" status after a successful put operation
+
+ $fileUrl = $this->getResourceUrl($fileIdentifier);
+ $result = $this->executeDavRequest('PUT', $fileUrl, $contents);
+
+ $this->removeCacheForPath(dirname($fileIdentifier));
+
+ // TODO check result
+ }
+
+ /**
+ * Adds a file from the local server hard disk to a given path in TYPO3s virtual file system.
+ *
+ * This assumes that the local file exists, so no further check is done here!
+ *
+ * @param string $localFilePath (within PATH_site)
+ * @param string $targetFolderIdentifier
+ * @param string $newFileName optional, if not given original name is used
+ * @param boolean $removeOriginal if set the original file will be removed
+ * after successful operation
+ * @return string the identifier of the new file
+ */
+ public function addFile($localFilePath, $targetFolderIdentifier, $newFileName = '', $removeOriginal = true)
+ {
+ $fileIdentifier = $targetFolderIdentifier . $newFileName;
+ $fileUrl = $this->baseUrl . ltrim($fileIdentifier);
+
+ $fileHandle = fopen($localFilePath, 'r');
+ if (!is_resource($fileHandle)) {
+ throw new \RuntimeException('Could not open handle for ' . $localFilePath, 1325959310);
+ }
+ $result = $this->executeDavRequest('PUT', $fileUrl, $fileHandle);
+
+ // TODO check result
+
+ $this->removeCacheForPath($targetFolderIdentifier);
+
+ return $fileIdentifier;
+ }
+
+ /**
+ * Checks if a file exists.
+ *
+ * @param string $identifier
+ * @return bool
+ */
+ public function fileExists($identifier)
+ {
+ return substr($identifier, -1) !== '/' && $this->resourceExists($identifier);
+ }
+
+ /**
+ * Checks if a file inside a storage folder exists.
+ *
+ * @param string $fileName
+ * @param string $folderIdentifier
+ * @return boolean
+ */
+ public function fileExistsInFolder($fileName, $folderIdentifier)
+ {
+ // TODO add unit test
+ $fileIdentifier = $folderIdentifier . $fileName;
+
+ return $this->fileExists($fileIdentifier);
+ }
+
+ /**
+ * Returns a (local copy of) a file for processing it. When changing the file, you have to take care of replacing
+ * the current version yourself!
+ *
+ * @param string $fileIdentifier
+ * @param bool $writable Set this to FALSE if you only need the file for read operations. This might speed up
+ * things, e.g. by using a cached local version. Never modify the file if you have set this flag!
+ * @return string The path to the file on the local disk
+ */
+ public function getFileForLocalProcessing($fileIdentifier, $writable = true)
+ {
+ return $this->copyFileToTemporaryPath($fileIdentifier);
+ }
+
+ /**
+ * Renames a file
+ *
+ * @param \TYPO3\CMS\Core\Resource\FileInterface $file
+ * @param string $newName
+ * @return string The new identifier of the file
+ */
+ public function renameFile($fileIdentifier, $newName)
+ {
+ // TODO add unit test
+ // Renaming works by invoking the MOVE method on the source URL and providing the new destination in the
+ // "Destination:" HTTP header.
+ $sourcePath = $fileIdentifier;
+ $targetPath = dirname($fileIdentifier) . '/' . $newName;
+
+ $this->executeMoveRequest($sourcePath, $targetPath);
+
+ $this->removeCacheForPath(dirname($fileIdentifier));
+
+ return $targetPath;
+ }
+
+ /**
+ * Replaces the contents (and file-specific metadata) of a file object with a local file.
+ *
+ * @param string $fileIdentifier
+ * @param string $localFilePath
+ * @return bool
+ * @throws \RuntimeException
+ */
+ public function replaceFile($fileIdentifier, $localFilePath)
+ {
+ $fileUrl = $this->getResourceUrl($fileIdentifier);
+ $fileHandle = fopen($localFilePath, 'r');
+ if (!is_resource($fileHandle)) {
+ throw new \RuntimeException('Could not open handle for ' . $localFilePath, 1325959311);
+ }
+
+ $this->removeCacheForPath(dirname($fileIdentifier));
+
+ $this->executeDavRequest('PUT', $fileUrl, $fileHandle);
+ }
+
+ /**
+ * Returns information about a file for a given file identifier.
+ *
+ * @param string $identifier The (relative) path to the file.
+ * @param array $propertiesToExtract The properties to get
+ * @return array
+ */
+ public function getFileInfoByIdentifier($identifier, array $propertiesToExtract = array())
+ {
+ assert($identifier[0] === '/', 'Identifier must start with a slash, got ' . $identifier);
+
+ return $this->getFrontend()->getFileInfo($identifier);
+ }
+
+ /**
+ * Returns the cache identifier for a given path.
+ *
+ * @param string $path
+ * @return string
+ */
+ protected function getCacheIdentifierForPath($path)
+ {
+ return sha1($this->storageUid . ':' . trim($path, '/') . '/');
+ }
+
+ /**
+ * Flushes the cache for a given path inside this storage.
+ *
+ * @param $path
+ * @return void
+ * @deprecated this should be moved to WebDavFrontend
+ */
+ protected function removeCacheForPath($path)
+ {
+ $this->getCache()->remove($this->getCacheIdentifierForPath($path));
+ }
+
+ /**
+ * Copies a file to a temporary path and returns that path. You have to take care of removing the temporary file
+ * yourself!
+ *
+ * @param string $fileIdentifier
+ * @return string The temporary path
+ */
+ public function copyFileToTemporaryPath($fileIdentifier)
+ {
+ $temporaryPath = GeneralUtility::tempnam('vfs-tempfile-');
+ $fileUrl = $this->getResourceUrl($fileIdentifier);
+
+ $fileHandle = fopen($temporaryPath, 'w');
+ $fileUrl = $this->encodeUrl($fileUrl);
+ $this->davClient->readUrlToHandle($fileUrl, $fileHandle);
+
+ // the handle is not closed by readUrlToHandle()!
+ fclose($fileHandle);
+
+ return $temporaryPath;
+ }
+
+ /**
+ * Moves a file *within* the current storage.
+ * Note that this is only about an intra-storage move action, where a file is just
+ * moved to another folder in the same storage.
+ *
+ * @param string $fileIdentifier
+ * @param string $targetFolderIdentifier
+ * @param string $newFileName
+ *
+ * @return string
+ * @throws FileOperationErrorException
+ */
+ public function moveFileWithinStorage($fileIdentifier, $targetFolderIdentifier, $newFileName)
+ {
+ $newPath = $targetFolderIdentifier . $newFileName;
+
+ try {
+ $result = $this->executeMoveRequest($fileIdentifier, $newPath);
+ } catch (DAV\Exception $e) {
+ // TODO insert correct exception here
+ throw new FileOperationErrorException('Moving file ' . $fileIdentifier
+ . ' to ' . $newPath . ' failed.', 1325848030);
+ }
+ // TODO check if there are some return codes that signalize an error, but do not throw an exception
+ // status codes: 204: file was overwritten; 201: file was created;
+
+ return $newPath;
+ }
+
+ /**
+ * Copies a file *within* the current storage.
+ * Note that this is only about an intra-storage copy action, where a file is just
+ * copied to another folder in the same storage.
+ *
+ * @param string $fileIdentifier
+ * @param string $targetFolderIdentifier
+ * @param string $fileName
+ *
+ * @return string the Identifier of the new file
+ * @throws FileOperationErrorException
+ */
+ public function copyFileWithinStorage($fileIdentifier, $targetFolderIdentifier, $fileName)
+ {
+ $oldFileUrl = $this->getResourceUrl($fileIdentifier);
+ $newFileUrl = $this->getResourceUrl($targetFolderIdentifier) . $fileName;
+ $newFileIdentifier = $targetFolderIdentifier . $fileName;
+
+ try {
+ // force overwriting the file (header Overwrite: T) because the Storage already handled possible conflicts
+ // for us
+ $result = $this->executeDavRequest('COPY', $oldFileUrl, null,
+ array('Destination' => $newFileUrl, 'Overwrite' => 'T'));
+ } catch (DAV\Exception $e) {
+ // TODO insert correct exception here
+ throw new FileOperationErrorException('Copying file ' . $fileIdentifier . ' to '
+ . $newFileIdentifier . ' failed.', 1325848030);
+ }
+ // TODO check if there are some return codes that signalize an error, but do not throw an exception
+ // status codes: 204: file was overwritten; 201: file was created;
+
+ return $newFileIdentifier;
+ }
+
+ /**
+ * Folder equivalent to moveFileWithinStorage().
+ *
+ * @param string $sourceFolderIdentifier
+ * @param string $targetFolderIdentifier
+ * @param string $newFolderName
+ *
+ * @return array All files which are affected, map of old => new file identifiers
+ * @throws FileOperationErrorException
+ */
+ public function moveFolderWithinStorage($sourceFolderIdentifier, $targetFolderIdentifier, $newFolderName)
+ {
+ $newFolderIdentifier = $targetFolderIdentifier . $newFolderName . '/';
+
+ try {
+ $result = $this->executeMoveRequest($sourceFolderIdentifier, $newFolderIdentifier);
+ } catch (DAV\Exception $e) {
+ // TODO insert correct exception here
+ throw new FileOperationErrorException('Moving folder ' . $sourceFolderIdentifier
+ . ' to ' . $newFolderIdentifier . ' failed: ' . $e->getMessage(), 1326135944);
+ }
+ // TODO check if there are some return codes that signalize an error, but do not throw an exception
+ // status codes: 204: file was overwritten; 201: file was created;
+
+ // TODO extract mapping of old to new identifiers from server response
+ }
+
+ /**
+ * Folder equivalent to copyFileWithinStorage().
+ *
+ * @param string $sourceFolderIdentifier
+ * @param string $targetFolderIdentifier
+ * @param string $newFolderName
+ *
+ * @return boolean
+ * @throws FileOperationErrorException
+ */
+ public function copyFolderWithinStorage($sourceFolderIdentifier, $targetFolderIdentifier, $newFolderName)
+ {
+ $oldFolderUrl = $this->getResourceUrl($sourceFolderIdentifier);
+ $newFolderUrl = $this->getResourceUrl($targetFolderIdentifier) . $newFolderName . '/';
+ $newFolderIdentifier = $targetFolderIdentifier . $newFolderName . '/';
+
+ try {
+ $result = $this->executeDavRequest('COPY', $oldFolderUrl, null,
+ array('Destination' => $newFolderUrl, 'Overwrite' => 'T'));
+ } catch (DAV\Exception $e) {
+ // TODO insert correct exception here
+ throw new FileOperationErrorException('Moving folder ' . $sourceFolderIdentifier
+ . ' to ' . $newFolderIdentifier . ' failed.', 1326135944);
+ }
+ // TODO check if there are some return codes that signalize an error, but do not throw an exception
+ // status codes: 204: file was overwritten; 201: file was created;
+
+ return $newFolderIdentifier;
+ }
+
+ /**
+ * Removes a file from this storage. This does not check if the file is still used or if it is a bad idea to delete
+ * it for some other reason - this has to be taken care of in the upper layers (e.g. the Storage)!
+ *
+ * @param string $fileIdentifier
+ * @return boolean TRUE if the operation succeeded
+ */
+ public function deleteFile($fileIdentifier)
+ {
+ // TODO add unit tests
+ $fileUrl = $this->baseUrl . ltrim($fileIdentifier, '/');
+
+ $result = $this->executeDavRequest('DELETE', $fileUrl);
+
+ // 204 is derived from the answer Apache gives - there might be other status codes that indicate success
+ return ($result['statusCode'] == 204);
+ }
+
+ /**
+ * Returns the root level folder of the storage.
+ *
+ * @return \TYPO3\CMS\Core\Resource\Folder
+ */
+ public function getRootLevelFolder()
+ {
+ return '/';
+ }
+
+ /**
+ * Returns the default folder new files should be put into.
+ *
+ * @return \TYPO3\CMS\Core\Resource\Folder
+ */
+ public function getDefaultFolder()
+ {
+ return '/';
+ }
+
+ /**
+ * Creates a folder, within a parent folder.
+ * If no parent folder is given, a root level folder will be created
+ *
+ * @param string $newFolderName
+ * @param string $parentFolderIdentifier
+ * @param boolean $recursive If set, parent folders will be created if they don’t exist
+ * @return string The new folder’s identifier
+ */
+ public function createFolder($newFolderName, $parentFolderIdentifier = '', $recursive = false)
+ {
+ // TODO test if recursive creation works
+ // We add a slash to the path as some actions require a trailing slash on some servers.
+ // Apache's mod_dav e.g. does not do it for this action, but it does not do harm either, so we add it anyways
+ $folderPath = $parentFolderIdentifier . $newFolderName . '/';
+ $folderUrl = $this->baseUrl . ltrim($folderPath, '/');
+
+ $this->executeDavRequest('MKCOL', $folderUrl);
+
+ $this->removeCacheForPath($parentFolderIdentifier);
+
+ return $folderPath;
+ }
+
+ /**
+ * Checks if a folder exists
+ *
+ * @param string $identifier
+ * @return bool
+ */
+ public function folderExists($identifier)
+ {
+ // TODO add unit test
+ // TODO check if this test suffices to find out if the resource really is a folder - it might not do with some implementations
+ $identifier = '/' . trim($identifier, '/') . '/';
+
+ return $this->resourceExists($identifier);
+ }
+
+ /**
+ * Checks if a file inside a storage folder exists.
+ *
+ * @param string $folderName
+ * @param string $folderIdentifier
+ * @return bool
+ */
+ public function folderExistsInFolder($folderName, $folderIdentifier)
+ {
+ $folderIdentifier = $folderIdentifier . $folderName . '/';
+
+ return $this->resourceExists($folderIdentifier);
+ }
+
+ /**
+ * Checks if a given identifier is within a container, e.g. if a file or folder is within another folder.
+ * This can be used to check for webmounts.
+ *
+ * @param string $containerIdentifier
+ * @param string $content
+ * @return bool
+ */
+ public function isWithin($containerIdentifier, $content)
+ {
+ $content = '/' . ltrim($content, '/');
+
+ return GeneralUtility::isFirstPartOfStr($content, $containerIdentifier);
+ }
+
+ /**
+ * Removes a folder from this storage.
+ *
+ * @param string $folderIdentifier
+ * @param bool $deleteRecursively
+ * @return boolean
+ */
+ public function deleteFolder($folderIdentifier, $deleteRecursively = false)
+ {
+ $folderUrl = $this->getResourceUrl($folderIdentifier);
+
+ $this->removeCacheForPath(dirname($folderIdentifier));
+
+ // We don't need to specify a depth header when deleting (see sect. 9.6.1 of RFC #4718)
+ $this->executeDavRequest('DELETE', $folderUrl, '', array());
+ }
+
+ /**
+ * Renames a folder in this storage.
+ *
+ * @param string $folderIdentifier
+ * @param string $newName The new folder name
+ * @return string The new identifier of the folder if the operation succeeds
+ * @throws \RuntimeException if renaming the folder failed
+ * @throws FileOperationErrorException
+ */
+ public function renameFolder($folderIdentifier, $newName)
+ {
+ $targetPath = dirname($folderIdentifier) . '/' . $newName . '/';
+
+ try {
+ $result = $this->executeMoveRequest($folderIdentifier, $targetPath);
+ } catch (DAV\Exception $e) {
+ // TODO insert correct exception here
+ throw new FileOperationErrorException('Renaming ' . $folderIdentifier . ' to '
+ . $targetPath . ' failed.', 1325848030);
+ }
+
+ $this->removeCacheForPath(dirname($folderIdentifier));
+
+ return $targetPath;
+ }
+
+ /**
+ * Checks if a folder contains files and (if supported) other folders.
+ *
+ * @param string $folderIdentifier
+ * @return bool TRUE if there are no files and folders within $folder
+ */
+ public function isFolderEmpty($folderIdentifier)
+ {
+ $folderContents = $this->frontend->propFind($folderIdentifier);
+
+ return (count($folderContents) == 1);
+ }
+
+ /**
+ * Merges the capabilites set by the administrator in the storage configuration with the actual capabilities of
+ * this driver and returns the result.
+ *
+ * @param integer $capabilities
+ *
+ * @return integer
+ */
+ public function mergeConfigurationCapabilities($capabilities)
+ {
+ $this->capabilities &= $capabilities;
+
+ return $this->capabilities;
+ }
+
+ /**
+ * Returns the identifier of the folder the file resides in
+ *
+ * @param string $fileIdentifier
+ *
+ * @return string
+ */
+ public function getParentFolderIdentifierOfIdentifier($fileIdentifier)
+ {
+ return dirname($fileIdentifier);
+ }
+
+ /**
+ * Returns the permissions of a file/folder as an array
+ * (keys r, w) of boolean flags
+ *
+ * @param string $identifier
+ * @return array
+ */
+ public function getPermissions($identifier)
+ {
+ // TODO check this again
+ return array('r' => true, 'w' => true);
+ }
+
+ /**
+ * Directly output the contents of the file to the output
+ * buffer. Should not take care of header files or flushing
+ * buffer before. Will be taken care of by the Storage.
+ *
+ * @param string $identifier
+ * @return void
+ */
+ public function dumpFileContents($identifier)
+ {
+ // TODO: Implement dumpFileContents() method.
+ }
+
+ /**
+ * Returns information about a file.
+ *
+ * @param string $folderIdentifier
+ *
+ * @return array
+ * @throws FolderDoesNotExistException
+ */
+ public function getFolderInfoByIdentifier($folderIdentifier)
+ {
+ if (!$this->folderExists($folderIdentifier)) {
+ throw new FolderDoesNotExistException(
+ 'Folder ' . $folderIdentifier . ' does not exist.',
+ 1314516810
+ );
+ }
+
+ return array(
+ 'identifier' => $folderIdentifier,
+ 'name' => basename($folderIdentifier),
+ 'storage' => $this->storageUid
+ );
+ }
+
+ /**
+ * Returns the identifier of a file inside the folder.
+ *
+ * @param string $fileName
+ * @param string $folderIdentifier
+ * @return string file identifier
+ */
+ public function getFileInFolder($fileName, $folderIdentifier)
+ {
+ return $this->canonicalizeAndCheckFileIdentifier($folderIdentifier . '/' . $fileName);
+ }
+
+ /**
+ * Returns a list of files inside the specified path
+ *
+ * @param string $folderIdentifier
+ * @param integer $start
+ * @param integer $numberOfItems
+ * @param boolean $recursive
+ * @param array $filenameFilterCallbacks callbacks for filtering the items
+ * @param string $sort Property name used to sort the items.
+ * Among them may be: '' (empty, no sorting), name,
+ * fileext, size, tstamp and rw.
+ * If a driver does not support the given property, it
+ * should fall back to "name".
+ * @param bool $sortRev TRUE to indicate reverse sorting (last to first)
+ *
+ * @return array of FileIdentifiers
+ */
+ public function getFilesInFolder(
+ $folderIdentifier,
+ $start = 0,
+ $numberOfItems = 0,
+ $recursive = false,
+ array $filenameFilterCallbacks = array(),
+ $sort = '',
+ $sortRev = false
+ ) {
+ $files = $this->getFrontend()->listFiles($folderIdentifier);
+
+ // TODO implement sorting
+
+ $items = array();
+ foreach ($files as $filename) {
+ $items[$filename] = $folderIdentifier . $filename;
+ }
+
+ return $items;
+ }
+
+ /**
+ * Returns the number of files inside the specified path
+ *
+ * @param string $folderIdentifier
+ * @param boolean $recursive
+ * @param array $filenameFilterCallbacks callbacks for filtering the items
+ * @return integer Number of files in folder
+ */
+ public function countFilesInFolder(
+ $folderIdentifier,
+ $recursive = false,
+ array $filenameFilterCallbacks = array()
+ ) {
+ return count($this->getFrontend()->listFiles($folderIdentifier));
+ }
+
+ /**
+ * Returns the identifier of a folder inside the folder.
+ *
+ * @param string $folderName The name of the target folder
+ * @param string $folderIdentifier
+ * @return string folder identifier
+ */
+ public function getFolderInFolder($folderName, $folderIdentifier)
+ {
+ return $this->canonicalizeAndCheckFolderIdentifier($folderIdentifier . '/' . $folderName);
+ }
+
+ /**
+ * Returns a list of folders inside the specified path
+ *
+ * @param string $folderIdentifier
+ * @param integer $start
+ * @param integer $numberOfItems
+ * @param boolean $recursive
+ * @param array $folderNameFilterCallbacks callbacks for filtering the items
+ * @param string $sort Property name used to sort the items.
+ * Among them may be: '' (empty, no sorting), name,
+ * fileext, size, tstamp and rw.
+ * If a driver does not support the given property, it
+ * should fall back to "name".
+ * @param bool $sortRev TRUE to indicate reverse sorting (last to first)
+ *
+ * @return array of folder identifiers
+ */
+ public function getFoldersInFolder(
+ $folderIdentifier,
+ $start = 0,
+ $numberOfItems = 0,
+ $recursive = false,
+ array $folderNameFilterCallbacks = array(),
+ $sort = '',
+ $sortRev = false
+ ) {
+ $folders = $this->getFrontend()->listFolders($folderIdentifier);
+
+ // TODO implement sorting
+
+ $items = array();
+ foreach ($folders as $name) {
+ $items[$name] = $folderIdentifier . $name . '/';
+ }
+
+ return $items;
+ }
+
+ /**
+ * Returns the number of folders inside the specified path
+ *
+ * @param string $folderIdentifier
+ * @param boolean $recursive
+ * @param array $folderNameFilterCallbacks callbacks for filtering the items
+ * @return integer Number of folders in folder
+ */
+ public function countFoldersInFolder(
+ $folderIdentifier,
+ $recursive = false,
+ array $folderNameFilterCallbacks = array()
+ ) {
+ return count($this->getFrontend()->listFolders($folderIdentifier));
+ }
}
<?php
namespace TYPO3\FalWebdav\Utility;
-use \TYPO3\CMS\Core\Utility;
+use TYPO3\CMS\Core\Utility;
+
/**
* Utility methods for encrypting/decrypting data. Currently only supports Blowfish encryption in CBC mode,
*
* @author Andreas Wolf <andreas.wolf@ikt-werk.de>
*/
-class EncryptionUtility {
-
- protected static $encryptionMethod = MCRYPT_BLOWFISH;
-
- protected static $encryptionMode = MCRYPT_MODE_CBC;
-
- /**
- * @var \TYPO3\CMS\Core\Log\Logger
- */
- protected static $logger;
-
- public static function getEncryptionMethod() {
- return self::$encryptionMethod;
- }
-
- public static function getEncryptionMode() {
- return self::$encryptionMode;
- }
-
- /**
- * Returns TRUE if the mcrypt extension is available.
- *
- * @return bool
- */
- public static function isMcryptAvailable() {
- return extension_loaded('mcrypt');
- }
-
- /**
- * @return \TYPO3\CMS\Core\Log\Logger
- */
- protected static function getLogger() {
- if (self::$logger === NULL) {
- /** @var $logManager \TYPO3\CMS\Core\Log\LogManager */
- $logManager = Utility\GeneralUtility::makeInstance('TYPO3\CMS\Core\Log\LogManager');
-
- self::$logger = $logManager->getLogger(__CLASS__);
- }
-
- return self::$logger;
- }
-
- /**
- * Returns an initialization vector suitable for the chosen encryption method.
- *
- * @param string $encryptionMethod
- * @param string $encryptionMode
- * @return string
- */
- protected static function getInitializationVector($encryptionMethod = NULL, $encryptionMode = NULL) {
- if (!$encryptionMethod) {
- $encryptionMethod = self::$encryptionMethod;
- }
- if (!$encryptionMode) {
- $encryptionMode = self::$encryptionMode;
- }
-
- $iv_size = mcrypt_get_iv_size($encryptionMethod, $encryptionMode);
- $iv = mcrypt_create_iv($iv_size, MCRYPT_RAND);
- return $iv;
- }
-
- /**
- * Creates and returns a resource pointer for the encryption method. This is required for e.g. retrieving an
- * encryption key.
- *
- * @param string $encryptionMethod
- * @param string $encryptionMode
- * @return resource
- */
- protected static function createEncryptionContext($encryptionMethod = NULL, $encryptionMode = NULL) {
- if (!$encryptionMethod) {
- $encryptionMethod = self::$encryptionMethod;
- }
- if (!$encryptionMode) {
- $encryptionMode = self::$encryptionMode;
- }
-
- $td = mcrypt_module_open($encryptionMethod, '', $encryptionMode, '');
- return $td;
- }
-
- /**
- * @param $td
- * @return string
- */
- protected static function getEncryptionKey($td) {
- $ks = mcrypt_enc_get_key_size($td);
- $key = substr(md5($GLOBALS['TYPO3_CONF_VARS']['SYS']['encryptionKey']), 0, $ks);
- return $key;
- }
-
- /**
- * Encrypts a password. The algorithm used and - if necessary - the initialization vector are stored within the
- * password string. The encrypted password itself is stored base64 encoded to make it possible to store this password
- * in an XML structure.
- *
- * @param $value
- * @return string
- * @see decryptPassword()
- */
- public static function encryptPassword($value) {
- if ($value == '') {
- return '';
- }
- if (!self::isMcryptAvailable()) {
- self::getLogger()->error('Encryption utility is not available. See reports module for more information.');
- return '';
- }
-
- $td = self::createEncryptionContext();
- $iv = self::getInitializationVector();
- $key = self::getEncryptionKey($td);
- mcrypt_generic_init($td, $key, $iv);
-
- $encryptedPassword = mcrypt_generic($td, $value);
- mcrypt_generic_deinit($td);
-
- $encryptedText = sprintf('$%s$%s$%s$%s',
- self::$encryptionMethod,
- self::$encryptionMode,
- base64_encode($iv),
- base64_encode($encryptedPassword)
- );
-
- // close the module opened for encrypting
- mcrypt_module_close($td);
-
- return $encryptedText;
- }
-
- /**
- * Decrypts a password. The necessary initialization vector is extracted from the password.
- *
- * @param string $encryptedPassword
- * @return string
- * @see encryptPassword()
- */
- public static function decryptPassword($encryptedPassword) {
- if ($encryptedPassword == '') {
- return '';
- }
- if (!self::isMcryptAvailable()) {
- self::getLogger()->error('Encryption utility is not available. See reports module for more information.');
- return '';
- }
-
- if (substr_count($encryptedPassword, '$') > 0) {
- $passwordParts = \TYPO3\CMS\Core\Utility\GeneralUtility::trimExplode('$', $encryptedPassword, TRUE);
- // Base64 decoding the password is done below
- $encryptedPassword = $passwordParts[3];
-
- $encryptionMethod = $passwordParts[0];
- $mode = $passwordParts[1];
- $td = self::createEncryptionContext($encryptionMethod, $mode);
-
- $iv = base64_decode($passwordParts[2]);
- } else {
- $td = self::createEncryptionContext(self::$encryptionMethod, self::$encryptionMode);
- $iv = self::getInitializationVector();
- }
- $key = self::getEncryptionKey($td);
-
- mcrypt_generic_init($td, $key, $iv);
- $decryptedPassword = trim(mdecrypt_generic($td, base64_decode($encryptedPassword)));
- mcrypt_generic_deinit($td);
-
- // close the module opened for decrypting
- mcrypt_module_close($td);
-
- return $decryptedPassword;
- }
+class EncryptionUtility
+{
+
+ protected static $encryptionMethod = MCRYPT_BLOWFISH;
+
+ protected static $encryptionMode = MCRYPT_MODE_CBC;
+
+ /**
+ * @var \TYPO3\CMS\Core\Log\Logger
+ */
+ protected static $logger;
+
+ public static function getEncryptionMethod()
+ {
+ return self::$encryptionMethod;
+ }
+
+ public static function getEncryptionMode()
+ {
+ return self::$encryptionMode;
+ }
+
+ /**
+ * Returns TRUE if the mcrypt extension is available.
+ *
+ * @return bool
+ */
+ public static function isMcryptAvailable()
+ {
+ return extension_loaded('mcrypt');
+ }
+
+ /**
+ * @return \TYPO3\CMS\Core\Log\Logger
+ */
+ protected static function getLogger()
+ {
+ if (self::$logger === null) {
+ /** @var $logManager \TYPO3\CMS\Core\Log\LogManager */
+ $logManager = Utility\GeneralUtility::makeInstance('TYPO3\CMS\Core\Log\LogManager');
+
+ self::$logger = $logManager->getLogger(__CLASS__);
+ }
+
+ return self::$logger;
+ }
+
+ /**
+ * Returns an initialization vector suitable for the chosen encryption method.
+ *
+ * @param string $encryptionMethod
+ * @param string $encryptionMode
+ * @return string
+ */
+ protected static function getInitializationVector($encryptionMethod = null, $encryptionMode = null)
+ {
+ if (!$encryptionMethod) {
+ $encryptionMethod = self::$encryptionMethod;
+ }
+ if (!$encryptionMode) {
+ $encryptionMode = self::$encryptionMode;
+ }
+
+ $iv_size = mcrypt_get_iv_size($encryptionMethod, $encryptionMode);
+ $iv = mcrypt_create_iv($iv_size, MCRYPT_RAND);
+
+ return $iv;
+ }
+
+ /**
+ * Creates and returns a resource pointer for the encryption method. This is required for e.g. retrieving an
+ * encryption key.
+ *
+ * @param string $encryptionMethod
+ * @param string $encryptionMode
+ * @return resource
+ */
+ protected static function createEncryptionContext($encryptionMethod = null, $encryptionMode = null)
+ {
+ if (!$encryptionMethod) {
+ $encryptionMethod = self::$encryptionMethod;
+ }
+ if (!$encryptionMode) {
+ $encryptionMode = self::$encryptionMode;
+ }
+
+ $td = mcrypt_module_open($encryptionMethod, '', $encryptionMode, '');
+
+ return $td;
+ }
+
+ /**
+ * @param $td
+ * @return string
+ */
+ protected static function getEncryptionKey($td)
+ {
+ $ks = mcrypt_enc_get_key_size($td);
+ $key = substr(md5($GLOBALS['TYPO3_CONF_VARS']['SYS']['encryptionKey']), 0, $ks);
+
+ return $key;
+ }
+
+ /**
+ * Encrypts a password. The algorithm used and - if necessary - the initialization vector are stored within the
+ * password string. The encrypted password itself is stored base64 encoded to make it possible to store this
+ * password in an XML structure.
+ *
+ * @param $value
+ * @return string
+ * @see decryptPassword()
+ */
+ public static function encryptPassword($value)
+ {
+ if ($value == '') {
+ return '';
+ }
+ if (!self::isMcryptAvailable()) {
+ self::getLogger()->error('Encryption utility is not available. See reports module for more information.');
+
+ return '';
+ }
+
+ $td = self::createEncryptionContext();
+ $iv = self::getInitializationVector();
+ $key = self::getEncryptionKey($td);
+ mcrypt_generic_init($td, $key, $iv);
+
+ $encryptedPassword = mcrypt_generic($td, $value);
+ mcrypt_generic_deinit($td);
+
+ $encryptedText = sprintf('$%s$%s$%s$%s',
+ self::$encryptionMethod,
+ self::$encryptionMode,
+ base64_encode($iv),
+ base64_encode($encryptedPassword)
+ );
+
+ // close the module opened for encrypting
+ mcrypt_module_close($td);
+
+ return $encryptedText;
+ }
+
+ /**
+ * Decrypts a password. The necessary initialization vector is extracted from the password.
+ *
+ * @param string $encryptedPassword
+ * @return string
+ * @see encryptPassword()
+ */
+ public static function decryptPassword($encryptedPassword)
+ {
+ if ($encryptedPassword == '') {
+ return '';
+ }
+ if (!self::isMcryptAvailable()) {
+ self::getLogger()->error('Encryption utility is not available. See reports module for more information.');
+
+ return '';
+ }
+
+ if (substr_count($encryptedPassword, '$') > 0) {
+ $passwordParts = \TYPO3\CMS\Core\Utility\GeneralUtility::trimExplode('$', $encryptedPassword, true);
+ // Base64 decoding the password is done below
+ $encryptedPassword = $passwordParts[3];
+
+ $encryptionMethod = $passwordParts[0];
+ $mode = $passwordParts[1];
+ $td = self::createEncryptionContext($encryptionMethod, $mode);
+
+ $iv = base64_decode($passwordParts[2]);
+ } else {
+ $td = self::createEncryptionContext(self::$encryptionMethod, self::$encryptionMode);
+ $iv = self::getInitializationVector();
+ }
+ $key = self::getEncryptionKey($td);
+
+ mcrypt_generic_init($td, $key, $iv);
+ $decryptedPassword = trim(mdecrypt_generic($td, base64_decode($encryptedPassword)));
+ mcrypt_generic_deinit($td);
+
+ // close the module opened for decrypting
+ mcrypt_module_close($td);
+
+ return $decryptedPassword;
+ }
}
\ No newline at end of file
*
* @author Andreas Wolf <andreas.wolf@ikt-werk.de>
*/
-class UrlTools {
- /**
- * Helper method to extract the username and password from a url. Returns username, password and the url without
- * authentication info.
- *
- * @param string
- * @return array An array with the cleaned url, the username and the password as entries
- */
- public static function extractUsernameAndPasswordFromUrl($url) {
- $urlInfo = parse_url($url);
+class UrlTools
+{
+ /**
+ * Helper method to extract the username and password from a url. Returns username, password and the url without
+ * authentication info.
+ *
+ * @param string
+ * @return array An array with the cleaned url, the username and the password as entries
+ */
+ public static function extractUsernameAndPasswordFromUrl($url)
+ {
+ $urlInfo = parse_url($url);
- $user = $pass = '';
- if (isset($urlInfo['user'])) {
- $user = $urlInfo['user'];
- unset($urlInfo['user']);
- }
- if (isset($urlInfo['pass'])) {
- $pass = $urlInfo['pass'];
- unset($urlInfo['pass']);
- }
- return array(\TYPO3\CMS\Core\Utility\HttpUtility::buildUrl($urlInfo), $user, $pass);
- }
+ $user = $pass = '';
+ if (isset($urlInfo['user'])) {
+ $user = $urlInfo['user'];
+ unset($urlInfo['user']);
+ }
+ if (isset($urlInfo['pass'])) {
+ $pass = $urlInfo['pass'];
+ unset($urlInfo['pass']);
+ }
+
+ return array(\TYPO3\CMS\Core\Utility\HttpUtility::buildUrl($urlInfo), $user, $pass);
+ }
}
<?php
namespace TYPO3\FalWebdav\Tests\Backend;
-/* *
- * This script belongs to the TYPO3 project. *
- * *
- * It is free software; you can redistribute it and/or modify it under *
- * the terms of the GNU General Public License as published by the Free *
- * Software Foundation, either version 2 of the License, or (at your *
- * option) any later version. *
- * *
- * This script is distributed in the hope that it will be useful, but *
- * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHAN- *
- * TABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General *
- * Public License for more details. *
- * *
- * You should have received a copy of the GNU General Public License *
- * along with the script. *
- * If not, see http://www.gnu.org/licenses/gpl.html *
- * *
- * The TYPO3 project - inspiring people to share! *
- * */
+ /* *
+ * This script belongs to the TYPO3 project. *
+ * *
+ * It is free software; you can redistribute it and/or modify it under *
+ * the terms of the GNU General Public License as published by the Free *
+ * Software Foundation, either version 2 of the License, or (at your *
+ * option) any later version. *
+ * *
+ * This script is distributed in the hope that it will be useful, but *
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHAN- *
+ * TABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General *
+ * Public License for more details. *
+ * *
+ * You should have received a copy of the GNU General Public License *
+ * along with the script. *
+ * If not, see http://www.gnu.org/licenses/gpl.html *
+ * *
+ * The TYPO3 project - inspiring people to share! *
+ * */
/**
* Testcase for the WebDAV driver configuration TCEmain hook.
* @package TYPO3
* @subpackage fal_webdav
*/
-class TceMainHookTest extends \Tx_Phpunit_TestCase {
+class TceMainHookTest extends \Tx_Phpunit_TestCase
+{
- /**
- * @var \TYPO3\FalWebdav\Backend\TceMainHook
- */
- protected $fixture;
+ /**
+ * @var \TYPO3\FalWebdav\Backend\TceMainHook
+ */
+ protected $fixture;
- public function setUp() {
- $this->fixture = new \TYPO3\FalWebdav\Backend\TceMainHook();
- }
+ public function setUp()
+ {
+ $this->fixture = new \TYPO3\FalWebdav\Backend\TceMainHook();
+ }
- protected function prepareFieldArrayFixture(array $fieldValues) {
- $fieldArray = array(
- 'configuration' => array(
- 'data' => array(
- 'sDEF' => array(
- 'lDEF' => array(
- // preset the driver as the TCEmain hook checks this
- 'driver' => array(
- 'vDEF' => 'WebDav'
- )
- )
- )
- )
- )
- );
+ protected function prepareFieldArrayFixture(array $fieldValues)
+ {
+ $fieldArray = array(
+ 'configuration' => array(
+ 'data' => array(
+ 'sDEF' => array(
+ 'lDEF' => array(
+ // preset the driver as the TCEmain hook checks this
+ 'driver' => array(
+ 'vDEF' => 'WebDav'
+ )
+ )
+ )
+ )
+ )
+ );
- foreach ($fieldValues as $field => $value) {
- $fieldArray['configuration']['data']['sDEF']['lDEF'][$field] = array(
- 'vDEF' => $value
- );
- }
- return $fieldArray;
- }
+ foreach ($fieldValues as $field => $value) {
+ $fieldArray['configuration']['data']['sDEF']['lDEF'][$field] = array(
+ 'vDEF' => $value
+ );
+ }
- /**
- * @test
- */
- public function usernameAndPasswordFromUrlOverrideSetValuesInConfigurationFields() {
- $fieldArray = $this->prepareFieldArrayFixture(array(
- 'baseUrl' => 'http://newUser:newPass@localhost/some/storage/',
- 'username' => 'oldUser',
- 'password' => 'oldPassword'
- ));
+ return $fieldArray;
+ }
- $this->fixture->processDatamap_preProcessFieldArray($fieldArray, 'sys_file_storage', -1, new \StdClass());
+ /**
+ * @test
+ */
+ public function usernameAndPasswordFromUrlOverrideSetValuesInConfigurationFields()
+ {
+ $fieldArray = $this->prepareFieldArrayFixture(array(
+ 'baseUrl' => 'http://newUser:newPass@localhost/some/storage/',
+ 'username' => 'oldUser',
+ 'password' => 'oldPassword'
+ ));
- $this->assertEquals('newUser', $fieldArray['configuration']['data']['sDEF']['lDEF']['username']['vDEF']);
- $this->assertEquals('newPass', \TYPO3\FalWebdav\Utility\Encryption::decryptPassword($fieldArray['configuration']['data']['sDEF']['lDEF']['password']['vDEF']));
- $this->assertEquals('http://localhost/some/storage/', $fieldArray['configuration']['data']['sDEF']['lDEF']['baseUrl']['vDEF']);
- }
+ $this->fixture->processDatamap_preProcessFieldArray($fieldArray, 'sys_file_storage', -1, new \StdClass());
- /**
- * @test
- */
- public function usernameAndPasswordInConfigurationFieldsAreLeftUnchangedIfNoAuthenticationInfoIsGivenInUrl() {
- $fieldArray = $this->prepareFieldArrayFixture(array(
- 'baseUrl' => 'http://localhost/some/storage/',
- 'username' => 'oldUser',
- 'password' => 'oldPassword'
- ));
+ $this->assertEquals('newUser', $fieldArray['configuration']['data']['sDEF']['lDEF']['username']['vDEF']);
+ $this->assertEquals('newPass',
+ \TYPO3\FalWebdav\Utility\Encryption::decryptPassword($fieldArray['configuration']['data']['sDEF']['lDEF']['password']['vDEF']));
+ $this->assertEquals('http://localhost/some/storage/',
+ $fieldArray['configuration']['data']['sDEF']['lDEF']['baseUrl']['vDEF']);
+ }
- $this->fixture->processDatamap_preProcessFieldArray($fieldArray, 'sys_file_storage', -1, new \StdClass());
+ /**
+ * @test
+ */
+ public function usernameAndPasswordInConfigurationFieldsAreLeftUnchangedIfNoAuthenticationInfoIsGivenInUrl()
+ {
+ $fieldArray = $this->prepareFieldArrayFixture(array(
+ 'baseUrl' => 'http://localhost/some/storage/',
+ 'username' => 'oldUser',
+ 'password' => 'oldPassword'
+ ));
- $this->assertEquals('oldUser', $fieldArray['configuration']['data']['sDEF']['lDEF']['username']['vDEF']);
- $this->assertEquals('oldPassword', \TYPO3\FalWebdav\Utility\Encryption::decryptPassword($fieldArray['configuration']['data']['sDEF']['lDEF']['password']['vDEF']));
- }
+ $this->fixture->processDatamap_preProcessFieldArray($fieldArray, 'sys_file_storage', -1, new \StdClass());
+
+ $this->assertEquals('oldUser', $fieldArray['configuration']['data']['sDEF']['lDEF']['username']['vDEF']);
+ $this->assertEquals('oldPassword',
+ \TYPO3\FalWebdav\Utility\Encryption::decryptPassword($fieldArray['configuration']['data']['sDEF']['lDEF']['password']['vDEF']));
+ }
}
* @package TYPO3
* @subpackage fal_webdav
*/
-class WebDavDriverTest extends \TYPO3\CMS\Core\Tests\Unit\Resource\BaseTestCase {
-
- /**
- * @var \TYPO3\FalWebdav\Driver\WebDavDriver
- */
- protected $fixture;
-
- /**
- * @var string
- */
- protected $baseUrl;
-
- public function setUp() {
- $this->baseUrl = 'http://example.org/webdav-root/';
- }
-
- protected function mockDavClient() {
- return $this->getMock('TYPO3\FalWebdav\Dav\WebDavClient', array(), array(), '', FALSE);
- }
-
- protected function prepareFixture(WebDavClient $client = NULL, $storageUid = NULL) {
- if ($client === NULL) {
- $client = $this->mockDavClient();
- }
- if ($storageUid === NULL) {
- $storageUid = 1;
- }
-
- $this->fixture = new \TYPO3\FalWebdav\Driver\WebDavDriver(array('baseUrl' => $this->baseUrl));
- $this->fixture->setStorageUid($storageUid);
- $this->fixture->injectDirectoryListingCache($this->getMock('TYPO3\CMS\Core\Cache\Frontend\FrontendInterface'));
- $this->fixture->processConfiguration();
- $this->fixture->injectDavClient($client);
- $this->fixture->initialize();
- }
-
- /**
- * @test
- */
- public function createFileIssuesCorrectCommandOnServer() {
- /** @var $clientMock WebDavClient */
- $clientMock = $this->mockDavClient();
- $clientMock->expects($this->once())->method('request')->with($this->equalTo('PUT'), $this->stringEndsWith('/someFile'));
- $this->prepareFixture($clientMock);
- $mockedFolder = '/';
-
- $this->fixture->createFile('someFile', $mockedFolder);
- }
-
- /**
- * @test
- * @return \TYPO3\CMS\Core\Resource\Folder
- */
- public function createFolderIssuesCorrectCreateCommandOnServer() {
- /** @var $clientMock WebDavClient */
- $clientMock = $this->mockDavClient();
- $clientMock->expects($this->at(0))->method('request')->with($this->equalTo('MKCOL'), $this->stringEndsWith('/mainFolder/subFolder/'));
- $this->prepareFixture($clientMock);
- $mockedFolder = '/mainFolder/';
-
- return $this->fixture->createFolder('subFolder', $mockedFolder);
- }
-
- /**
- * @test
- * @depends createFolderIssuesCorrectCreateCommandOnServer
- * @param string $folderIdentifier
- */
- public function createFolderReturnsObjectWithCorrectIdentifier($folderIdentifier) {
- $this->assertEquals('/mainFolder/subFolder/', $folderIdentifier);
- }
-
- /**
- * @test
- */
- public function copyFileToTemporaryPathCreatesLocalCopyOfFile() {
- $fileContents = uniqid();
-
- /** @var $clientMock WebDavClient */
- $clientMock = $this->mockDavClient();
- $clientMock->expects($this->once())->method('request')->with($this->equalTo('GET'), $this->stringEndsWith('/mainFolder/file.txt'))
- ->will($this->returnValue(array('body' => $fileContents)));
- $this->prepareFixture($clientMock);
- $mockedFile = '/mainFolder/file.txt';
-
- $temporaryPath = $this->fixture->copyFileToTemporaryPath($mockedFile);
- $this->assertEquals($fileContents, file_get_contents($temporaryPath));
-
- unlink($temporaryPath);
- }
-
- /**
- * @test
- */
- public function moveFileWithinStorageIssuesCorrectCommand() {
- $mockedFile = '/someFile';
- $mockedFolder = '/targetFolder/';
-
- /** @var $clientMock WebDavClient */
- $clientMock = $this->mockDavClient();
- // TODO make the parameter matching here more special as soon as PHPUnit supports doing so
- $clientMock->expects($this->once())->method('request')->with($this->equalTo('MOVE'), $this->stringEndsWith('/someFile'),
- NULL, $this->logicalAnd($this->contains($this->baseUrl . 'targetFolder/movedFile'), $this->contains('T')));
- $this->prepareFixture($clientMock);
-
- $newFileIdentifier = $this->fixture->moveFileWithinStorage($mockedFile, $mockedFolder, 'movedFile');
-
- $this->assertEquals('/targetFolder/movedFile', $newFileIdentifier);
- }
-
- /**
- * @return array
- */
- public function isWithin_dataProvider() {
- return array(
- 'file in folder' => array(
- '/someFolder',
- '/someFolder/file',
- TRUE
- ),
- 'file within subfolder' => array(
- '/someFolder',
- '/someFolder/someOtherFolder/file',
- TRUE
- ),
- 'file in root folder' => array(
- '/',
- '/file',
- TRUE
- )
- );
- }
-
- /**
- * @test
- * @dataProvider isWithin_dataProvider
- */
- public function isWithinCorrectlyDetectsPaths($containerPath, $contentPath, $expectedResult) {
- $mockedFolder = $containerPath;
- $this->prepareFixture();
-
- $actualResult = $this->fixture->isWithin($mockedFolder, $contentPath);
-
- $this->assertEquals($expectedResult, $actualResult);
- }
-
- /**
- * @test
- */
- public function setFileContentsIssuesCorrectCommandOnServer() {
- $fileContents = uniqid();
- /** @var $clientMock WebDavClient */
- $clientMock = $this->mockDavClient();
- $clientMock->expects($this->once())->method('request')->with($this->equalTo('PUT'), $this->stringEndsWith('/someFile'), $fileContents);
- $this->prepareFixture($clientMock);
- $mockedFile = '/someFile';
-
- $this->fixture->setFileContents($mockedFile, $fileContents);
- }
-
- /**
- * @test
- */
- public function getPublicUrlReturnsCorrectUrlIfStorageIsPublic() {
- $mockedStorage = $this->getMock(ResourceStorage::class, array(), array(), '', FALSE);
- $mockedStorage->expects($this->any())->method('isPublic')->will($this->returnValue(TRUE));
- $this->prepareFixture();
-
- $this->fixture->getPublicUrl('/someFolder/someFile.jpg');
- }
-
- /**
- * @test
- */
- public function deleteFolderIssuesCorrectCommandOnServer() {
- /** @var $clientMock WebDavClient */
- $clientMock = $this->mockDavClient();
- $clientMock->expects($this->once())->method('request')->with($this->equalTo('DELETE'), $this->stringEndsWith('/someFolder/'));
- $this->prepareFixture($clientMock);
-
- $this->fixture->deleteFolder('/someFolder/');
- }
-
- /**
- * @test
- */
- public function timeoutOnRequestThrowsException() {
- $this->markTestSkipped('This test needs to be adjusted (timeout configuration, exception class)');
- $this->setExpectedException('Sabre_DAV_Exception_Timeout');
-
- // 192.0.2.0/24 is a network that should be used in documentation and for tests, but not in the wild;
- // see http://tools.ietf.org/html/rfc5737
- $client = new WebDavClient(array('baseUri' => 'http://192.0.2.1/', 'timeout' => 5));
-
- print_r($client->request('GET', '/something'));
- }
+class WebDavDriverTest extends \TYPO3\CMS\Core\Tests\Unit\Resource\BaseTestCase
+{
+
+ /**
+ * @var \TYPO3\FalWebdav\Driver\WebDavDriver
+ */
+ protected $fixture;
+
+ /**
+ * @var string
+ */
+ protected $baseUrl;
+
+ public function setUp()
+ {
+ $this->baseUrl = 'http://example.org/webdav-root/';
+ }
+
+ protected function mockDavClient()
+ {
+ return $this->getMock('TYPO3\FalWebdav\Dav\WebDavClient', array(), array(), '', false);
+ }
+
+ protected function prepareFixture(WebDavClient $client = null, $storageUid = null)
+ {
+ if ($client === null) {
+ $client = $this->mockDavClient();
+ }
+ if ($storageUid === null) {
+ $storageUid = 1;
+ }
+
+ $this->fixture = new \TYPO3\FalWebdav\Driver\WebDavDriver(array('baseUrl' => $this->baseUrl));
+ $this->fixture->setStorageUid($storageUid);
+ $this->fixture->injectDirectoryListingCache($this->getMock('TYPO3\CMS\Core\Cache\Frontend\FrontendInterface'));
+ $this->fixture->processConfiguration();
+ $this->fixture->injectDavClient($client);
+ $this->fixture->initialize();
+ }
+
+ /**
+ * @test
+ */
+ public function createFileIssuesCorrectCommandOnServer()
+ {
+ /** @var $clientMock WebDavClient */
+ $clientMock = $this->mockDavClient();
+ $clientMock->expects($this->once())->method('request')->with($this->equalTo('PUT'),
+ $this->stringEndsWith('/someFile'));
+ $this->prepareFixture($clientMock);
+ $mockedFolder = '/';
+
+ $this->fixture->createFile('someFile', $mockedFolder);
+ }
+
+ /**
+ * @test
+ * @return \TYPO3\CMS\Core\Resource\Folder
+ */
+ public function createFolderIssuesCorrectCreateCommandOnServer()
+ {
+ /** @var $clientMock WebDavClient */
+ $clientMock = $this->mockDavClient();
+ $clientMock->expects($this->at(0))->method('request')->with($this->equalTo('MKCOL'),
+ $this->stringEndsWith('/mainFolder/subFolder/'));
+ $this->prepareFixture($clientMock);
+ $mockedFolder = '/mainFolder/';
+
+ return $this->fixture->createFolder('subFolder', $mockedFolder);
+ }
+
+ /**
+ * @test
+ * @depends createFolderIssuesCorrectCreateCommandOnServer
+ * @param string $folderIdentifier
+ */
+ public function createFolderReturnsObjectWithCorrectIdentifier($folderIdentifier)
+ {
+ $this->assertEquals('/mainFolder/subFolder/', $folderIdentifier);
+ }
+
+ /**
+ * @test
+ */
+ public function copyFileToTemporaryPathCreatesLocalCopyOfFile()
+ {
+ $fileContents = uniqid();
+
+ /** @var $clientMock WebDavClient */
+ $clientMock = $this->mockDavClient();
+ $clientMock->expects($this->once())->method('request')->with($this->equalTo('GET'),
+ $this->stringEndsWith('/mainFolder/file.txt'))
+ ->will($this->returnValue(array('body' => $fileContents)));
+ $this->prepareFixture($clientMock);
+ $mockedFile = '/mainFolder/file.txt';
+
+ $temporaryPath = $this->fixture->copyFileToTemporaryPath($mockedFile);
+ $this->assertEquals($fileContents, file_get_contents($temporaryPath));
+
+ unlink($temporaryPath);
+ }
+
+ /**
+ * @test
+ */
+ public function moveFileWithinStorageIssuesCorrectCommand()
+ {
+ $mockedFile = '/someFile';
+ $mockedFolder = '/targetFolder/';
+
+ /** @var $clientMock WebDavClient */
+ $clientMock = $this->mockDavClient();
+ // TODO make the parameter matching here more special as soon as PHPUnit supports doing so
+ $clientMock->expects($this->once())->method('request')->with($this->equalTo('MOVE'),
+ $this->stringEndsWith('/someFile'),
+ null, $this->logicalAnd($this->contains($this->baseUrl . 'targetFolder/movedFile'), $this->contains('T')));
+ $this->prepareFixture($clientMock);
+
+ $newFileIdentifier = $this->fixture->moveFileWithinStorage($mockedFile, $mockedFolder, 'movedFile');
+
+ $this->assertEquals('/targetFolder/movedFile', $newFileIdentifier);
+ }
+
+ /**
+ * @return array
+ */
+ public function isWithin_dataProvider()
+ {
+ return array(
+ 'file in folder' => array(
+ '/someFolder',
+ '/someFolder/file',
+ true
+ ),
+ 'file within subfolder' => array(
+ '/someFolder',
+ '/someFolder/someOtherFolder/file',
+ true
+ ),
+ 'file in root folder' => array(
+ '/',
+ '/file',
+ true
+ )
+ );
+ }
+
+ /**
+ * @test
+ * @dataProvider isWithin_dataProvider
+ */
+ public function isWithinCorrectlyDetectsPaths($containerPath, $contentPath, $expectedResult)
+ {
+ $mockedFolder = $containerPath;
+ $this->prepareFixture();
+
+ $actualResult = $this->fixture->isWithin($mockedFolder, $contentPath);
+
+ $this->assertEquals($expectedResult, $actualResult);
+ }
+
+ /**
+ * @test
+ */
+ public function setFileContentsIssuesCorrectCommandOnServer()
+ {
+ $fileContents = uniqid();
+ /** @var $clientMock WebDavClient */
+ $clientMock = $this->mockDavClient();
+ $clientMock->expects($this->once())->method('request')->with($this->equalTo('PUT'),
+ $this->stringEndsWith('/someFile'), $fileContents);
+ $this->prepareFixture($clientMock);
+ $mockedFile = '/someFile';
+
+ $this->fixture->setFileContents($mockedFile, $fileContents);
+ }
+
+ /**
+ * @test
+ */
+ public function getPublicUrlReturnsCorrectUrlIfStorageIsPublic()
+ {
+ $mockedStorage = $this->getMock(ResourceStorage::class, array(), array(), '', false);
+ $mockedStorage->expects($this->any())->method('isPublic')->will($this->returnValue(true));
+ $this->prepareFixture();
+
+ $this->fixture->getPublicUrl('/someFolder/someFile.jpg');
+ }
+
+ /**
+ * @test
+ */
+ public function deleteFolderIssuesCorrectCommandOnServer()
+ {
+ /** @var $clientMock WebDavClient */
+ $clientMock = $this->mockDavClient();
+ $clientMock->expects($this->once())->method('request')->with($this->equalTo('DELETE'),
+ $this->stringEndsWith('/someFolder/'));
+ $this->prepareFixture($clientMock);
+
+ $this->fixture->deleteFolder('/someFolder/');
+ }
+
+ /**
+ * @test
+ */
+ public function timeoutOnRequestThrowsException()
+ {
+ $this->markTestSkipped('This test needs to be adjusted (timeout configuration, exception class)');
+ $this->setExpectedException('Sabre_DAV_Exception_Timeout');
+
+ // 192.0.2.0/24 is a network that should be used in documentation and for tests, but not in the wild;
+ // see http://tools.ietf.org/html/rfc5737
+ $client = new WebDavClient(array('baseUri' => 'http://192.0.2.1/', 'timeout' => 5));
+
+ print_r($client->request('GET', '/something'));
+ }
}
<?php
namespace TYPO3\FalWebdav\Tests\Utility;
-/* *
- * This script belongs to the TYPO3 project. *
- * *
- * It is free software; you can redistribute it and/or modify it under *
- * the terms of the GNU General Public License as published by the Free *
- * Software Foundation, either version 2 of the License, or (at your *
- * option) any later version. *
- * *
- * This script is distributed in the hope that it will be useful, but *
- * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHAN- *
- * TABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General *
- * Public License for more details. *
- * *
- * You should have received a copy of the GNU General Public License *
- * along with the script. *
- * If not, see http://www.gnu.org/licenses/gpl.html *
- * *
- * The TYPO3 project - inspiring people to share! *
- * */
+ /* *
+ * This script belongs to the TYPO3 project. *
+ * *
+ * It is free software; you can redistribute it and/or modify it under *
+ * the terms of the GNU General Public License as published by the Free *
+ * Software Foundation, either version 2 of the License, or (at your *
+ * option) any later version. *
+ * *
+ * This script is distributed in the hope that it will be useful, but *
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHAN- *
+ * TABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General *
+ * Public License for more details. *
+ * *
+ * You should have received a copy of the GNU General Public License *
+ * along with the script. *
+ * If not, see http://www.gnu.org/licenses/gpl.html *
+ * *
+ * The TYPO3 project - inspiring people to share! *
+ * */
/**
* Testcase for the WebDAV driver encryption tools
* @package TYPO3
* @subpackage fal_webdav
*/
-class EncryptionTest extends \Tx_Phpunit_TestCase {
- /**
- * @test
- */
- public function passwordsCanBeEncryptedAndDecrypted() {
- $password = uniqid();
+class EncryptionTest extends \Tx_Phpunit_TestCase
+{
+ /**
+ * @test
+ */
+ public function passwordsCanBeEncryptedAndDecrypted()
+ {
+ $password = uniqid();
- $encryptedPassword = \TYPO3\FalWebdav\Utility\Encryption::encryptPassword($password);
- $decryptedPassword = \TYPO3\FalWebdav\Utility\Encryption::decryptPassword($encryptedPassword);
+ $encryptedPassword = \TYPO3\FalWebdav\Utility\Encryption::encryptPassword($password);
+ $decryptedPassword = \TYPO3\FalWebdav\Utility\Encryption::decryptPassword($encryptedPassword);
- $this->assertEquals($password, $decryptedPassword);
- }
+ $this->assertEquals($password, $decryptedPassword);
+ }
- /**
- * @test
- */
- public function encryptedPasswordContainsAlgorithm() {
- $password = uniqid();
+ /**
+ * @test
+ */
+ public function encryptedPasswordContainsAlgorithm()
+ {
+ $password = uniqid();
- $encryptedPassword = \TYPO3\FalWebdav\Utility\Encryption::encryptPassword($password);
+ $encryptedPassword = \TYPO3\FalWebdav\Utility\Encryption::encryptPassword($password);
- $this->assertStringStartsWith(
- sprintf('$%s$%s$',
- \TYPO3\FalWebdav\Utility\Encryption::getEncryptionMethod(),
- \TYPO3\FalWebdav\Utility\Encryption::getEncryptionMode()
- ),
- $encryptedPassword
- );
- }
+ $this->assertStringStartsWith(
+ sprintf('$%s$%s$',
+ \TYPO3\FalWebdav\Utility\Encryption::getEncryptionMethod(),
+ \TYPO3\FalWebdav\Utility\Encryption::getEncryptionMode()
+ ),
+ $encryptedPassword
+ );
+ }
- /**
- * @test
- */
- public function encryptingEmptyStringReturnsEmptyString() {
- $encryptedPassword = \TYPO3\FalWebdav\Utility\Encryption::encryptPassword('');
+ /**
+ * @test
+ */
+ public function encryptingEmptyStringReturnsEmptyString()
+ {
+ $encryptedPassword = \TYPO3\FalWebdav\Utility\Encryption::encryptPassword('');
- $this->assertEmpty($encryptedPassword);
- }
+ $this->assertEmpty($encryptedPassword);
+ }
- /**
- * @test
- */
- public function decryptingEmptyStringReturnsEmptyString() {
- $this->assertEquals('', \TYPO3\FalWebdav\Utility\Encryption::decryptPassword(''));
- }
+ /**
+ * @test
+ */
+ public function decryptingEmptyStringReturnsEmptyString()
+ {
+ $this->assertEquals('', \TYPO3\FalWebdav\Utility\Encryption::decryptPassword(''));
+ }
}
\ No newline at end of file
*
* @author Andreas Wolf <andreas.wolf@ikt-werk.de>
*/
-class UrlToolsTest extends \Tx_Phpunit_TestCase {
+class UrlToolsTest extends \Tx_Phpunit_TestCase
+{
- public function urlDataProvider() {
- return array(
- 'regular URL with username and password' => array(
- 'http://someuser:somepass@localhost/test.php',
- array('http://localhost/test.php', 'someuser', 'somepass')
- ),
- 'URL with just user' => array(
- 'http://someuser@localhost/test.php',
- array('http://localhost/test.php', 'someuser', '')
- ),
- 'HTTPS URL with username and password' => array(
- 'https://someuser:somepass@localhost/test.php',
- array('https://localhost/test.php', 'someuser', 'somepass')
- ),
- 'URL without authentication' => array(
- 'http://localhost/test.php',
- array('http://localhost/test.php', '', '')
- )
- );
- }
+ public function urlDataProvider()
+ {
+ return array(
+ 'regular URL with username and password' => array(
+ 'http://someuser:somepass@localhost/test.php',
+ array('http://localhost/test.php', 'someuser', 'somepass')
+ ),
+ 'URL with just user' => array(
+ 'http://someuser@localhost/test.php',
+ array('http://localhost/test.php', 'someuser', '')
+ ),
+ 'HTTPS URL with username and password' => array(
+ 'https://someuser:somepass@localhost/test.php',
+ array('https://localhost/test.php', 'someuser', 'somepass')
+ ),
+ 'URL without authentication' => array(
+ 'http://localhost/test.php',
+ array('http://localhost/test.php', '', '')
+ )
+ );
+ }
- /**
- * @test
- * @dataProvider urlDataProvider
- */
- public function usernameAndPasswordAreProperlyExtractedFromUrl($url, $expectedOutput) {
- $output = \TYPO3\FalWebdav\Utility\UrlTools::extractUsernameAndPasswordFromUrl($url);
+ /**
+ * @test
+ * @dataProvider urlDataProvider
+ */
+ public function usernameAndPasswordAreProperlyExtractedFromUrl($url, $expectedOutput)
+ {
+ $output = \TYPO3\FalWebdav\Utility\UrlTools::extractUsernameAndPasswordFromUrl($url);
- $this->assertEquals($expectedOutput, $output);
- }
+ $this->assertEquals($expectedOutput, $output);
+ }
}
########################################################################
$EM_CONF[$_EXTKEY] = array(
- 'title' => 'WebDAV driver for FAL',
- 'description' => 'Provides a WebDAV driver for the TYPO3 File Abstraction Layer.',
- 'category' => 'be',
- 'author' => 'Andreas Wolf',
- 'author_email' => 'andreas.wolf@typo3.org',
- 'shy' => '',
- 'dependencies' => '',
- 'conflicts' => '',
- 'priority' => '',
- 'module' => '',
- 'state' => 'beta',
- 'internal' => '',
- 'uploadfolder' => 0,
- 'createDirs' => '',
- 'modify_tables' => '',
- 'clearCacheOnLoad' => 0,
- 'lockType' => '',
- 'author_company' => '',
- 'version' => '1.0.0-dev',
- 'constraints' => array(
- 'depends' => array(
- 'typo3' => '6.0.0-'
- ),
- 'conflicts' => array(
- ),
- 'suggests' => array(
- ),
- ),
- '_md5_values_when_last_written' => 'a:5:{s:9:"ChangeLog";s:4:"6f31";s:10:"README.txt";s:4:"ee2d";s:12:"ext_icon.gif";s:4:"1bdc";s:19:"doc/wizard_form.dat";s:4:"c0c7";s:20:"doc/wizard_form.html";s:4:"4670";}',
+ 'title' => 'WebDAV driver for FAL',
+ 'description' => 'Provides a WebDAV driver for the TYPO3 File Abstraction Layer.',
+ 'category' => 'be',
+ 'author' => 'Andreas Wolf',
+ 'author_email' => 'andreas.wolf@typo3.org',
+ 'shy' => '',
+ 'dependencies' => '',
+ 'conflicts' => '',
+ 'priority' => '',
+ 'module' => '',
+ 'state' => 'beta',
+ 'internal' => '',
+ 'uploadfolder' => 0,
+ 'createDirs' => '',
+ 'modify_tables' => '',
+ 'clearCacheOnLoad' => 0,
+ 'lockType' => '',
+ 'author_company' => '',
+ 'version' => '1.0.0-dev',
+ 'constraints' => array(
+ 'depends' => array(
+ 'typo3' => '6.0.0-'
+ ),
+ 'conflicts' => array(),
+ 'suggests' => array(),
+ ),
+ '_md5_values_when_last_written' => 'a:5:{s:9:"ChangeLog";s:4:"6f31";s:10:"README.txt";s:4:"ee2d";s:12:"ext_icon.gif";s:4:"1bdc";s:19:"doc/wizard_form.dat";s:4:"c0c7";s:20:"doc/wizard_form.html";s:4:"4670";}',
);
?>
\ No newline at end of file
<?php
if (!defined('TYPO3_MODE')) {
- die('Access denied.');
+ die('Access denied.');
}
include_once \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::extPath('fal_webdav')
- . 'Resources/Composer/vendor/autoload.php';
+ . 'Resources/Composer/vendor/autoload.php';
/** @var \TYPO3\CMS\Core\Resource\Driver\DriverRegistry $registry */
$registry = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\CMS\Core\Resource\Driver\DriverRegistry');
-$registry->registerDriverClass('TYPO3\FalWebdav\Driver\WebDavDriver', 'WebDav', 'WebDAV', 'FILE:EXT:fal_webdav/Configuration/FlexForm/WebDavDriverFlexForm.xml');
+$registry->registerDriverClass('TYPO3\FalWebdav\Driver\WebDavDriver', 'WebDav', 'WebDAV',
+ 'FILE:EXT:fal_webdav/Configuration/FlexForm/WebDavDriverFlexForm.xml');
$TYPO3_CONF_VARS['SC_OPTIONS']['t3lib/class.t3lib_tcemain.php']['processDatamapClass']['fal_webdav'] = 'TYPO3\\FalWebdav\\Backend\\TceMainHook';
- // Cache configuration, see http://wiki.typo3.org/Caching_Framework
+// Cache configuration, see http://wiki.typo3.org/Caching_Framework
if (!is_array($TYPO3_CONF_VARS['SYS']['caching']['cacheConfigurations']['tx_falwebdav_directorylisting'])) {
- $TYPO3_CONF_VARS['SYS']['caching']['cacheConfigurations']['tx_falwebdav_directorylisting'] = array(
- 'frontend' => 'TYPO3\CMS\Core\Cache\Frontend\VariableFrontend',
- 'backend' => 'TYPO3\CMS\Core\Cache\Backend\TransientMemoryBackend',
- 'options' => array(),
- 'groups' => array()
+ $TYPO3_CONF_VARS['SYS']['caching']['cacheConfigurations']['tx_falwebdav_directorylisting'] = array(
+ 'frontend' => 'TYPO3\CMS\Core\Cache\Frontend\VariableFrontend',
+ 'backend' => 'TYPO3\CMS\Core\Cache\Backend\TransientMemoryBackend',
+ 'options' => array(),
+ 'groups' => array()
);
}