From dedf392751e1b27163f9dd49b2a54f410727c823 Mon Sep 17 00:00:00 2001 From: Roeland Jago Douma Date: Sun, 24 Apr 2016 19:45:43 +0200 Subject: Move \OC\Files to PSR-4 --- lib/private/Files/Cache/Cache.php | 837 ++++++++ lib/private/Files/Cache/CacheEntry.php | 114 ++ lib/private/Files/Cache/FailedCache.php | 142 ++ lib/private/Files/Cache/HomeCache.php | 86 + lib/private/Files/Cache/HomePropagator.php | 50 + lib/private/Files/Cache/MoveFromCacheTrait.php | 87 + lib/private/Files/Cache/Propagator.php | 74 + lib/private/Files/Cache/Scanner.php | 503 +++++ lib/private/Files/Cache/Storage.php | 189 ++ lib/private/Files/Cache/Updater.php | 228 +++ lib/private/Files/Cache/Watcher.php | 140 ++ lib/private/Files/Cache/Wrapper/CacheJail.php | 300 +++ .../Files/Cache/Wrapper/CachePermissionsMask.php | 46 + lib/private/Files/Cache/Wrapper/CacheWrapper.php | 309 +++ lib/private/Files/Config/CachedMountInfo.php | 107 + lib/private/Files/Config/LazyStorageMountInfo.php | 74 + .../Files/Config/MountProviderCollection.php | 108 + lib/private/Files/Config/UserMountCache.php | 290 +++ .../Files/Config/UserMountCacheListener.php | 48 + lib/private/Files/FileInfo.php | 346 ++++ lib/private/Files/Filesystem.php | 928 +++++++++ lib/private/Files/Mount/Manager.php | 165 ++ lib/private/Files/Mount/MountPoint.php | 251 +++ lib/private/Files/Mount/MoveableMount.php | 44 + lib/private/Files/Node/File.php | 176 ++ lib/private/Files/Node/Folder.php | 360 ++++ lib/private/Files/Node/HookConnector.php | 164 ++ lib/private/Files/Node/LazyRoot.php | 474 +++++ lib/private/Files/Node/Node.php | 383 ++++ lib/private/Files/Node/NonExistingFile.php | 143 ++ lib/private/Files/Node/NonExistingFolder.php | 172 ++ lib/private/Files/Node/Root.php | 357 ++++ .../Files/ObjectStore/HomeObjectStoreStorage.php | 68 + lib/private/Files/ObjectStore/NoopScanner.php | 79 + .../Files/ObjectStore/ObjectStoreStorage.php | 407 ++++ lib/private/Files/ObjectStore/Swift.php | 154 ++ lib/private/Files/Storage/Common.php | 697 +++++++ lib/private/Files/Storage/CommonTest.php | 84 + lib/private/Files/Storage/DAV.php | 817 ++++++++ lib/private/Files/Storage/FailedStorage.php | 215 ++ lib/private/Files/Storage/Flysystem.php | 256 +++ lib/private/Files/Storage/Home.php | 114 ++ lib/private/Files/Storage/Local.php | 404 ++++ lib/private/Files/Storage/LocalTempFileTrait.php | 80 + .../Files/Storage/PolyFill/CopyDirectory.php | 103 + lib/private/Files/Storage/Storage.php | 119 ++ lib/private/Files/Storage/StorageFactory.php | 107 + lib/private/Files/Storage/Temporary.php | 47 + lib/private/Files/Storage/Wrapper/Availability.php | 461 +++++ lib/private/Files/Storage/Wrapper/Encryption.php | 993 ++++++++++ lib/private/Files/Storage/Wrapper/Jail.php | 489 +++++ .../Files/Storage/Wrapper/PermissionsMask.php | 131 ++ lib/private/Files/Storage/Wrapper/Quota.php | 200 ++ lib/private/Files/Storage/Wrapper/Wrapper.php | 608 ++++++ lib/private/Files/Stream/Close.php | 118 ++ lib/private/Files/Stream/Dir.php | 66 + lib/private/Files/Stream/Encryption.php | 500 +++++ lib/private/Files/Stream/OC.php | 153 ++ lib/private/Files/Stream/Quota.php | 156 ++ lib/private/Files/Stream/StaticStream.php | 170 ++ lib/private/Files/Type/Detection.php | 318 +++ lib/private/Files/Type/Loader.php | 173 ++ lib/private/Files/Type/TemplateManager.php | 61 + lib/private/Files/Utils/Scanner.php | 172 ++ lib/private/Files/View.php | 2058 ++++++++++++++++++++ lib/private/files/cache/cache.php | 837 -------- lib/private/files/cache/cacheentry.php | 114 -- lib/private/files/cache/failedcache.php | 142 -- lib/private/files/cache/homecache.php | 86 - lib/private/files/cache/homepropagator.php | 50 - lib/private/files/cache/movefromcachetrait.php | 87 - lib/private/files/cache/propagator.php | 74 - lib/private/files/cache/scanner.php | 503 ----- lib/private/files/cache/storage.php | 189 -- lib/private/files/cache/updater.php | 228 --- lib/private/files/cache/watcher.php | 140 -- lib/private/files/cache/wrapper/cachejail.php | 300 --- .../files/cache/wrapper/cachepermissionsmask.php | 46 - lib/private/files/cache/wrapper/cachewrapper.php | 309 --- lib/private/files/config/cachedmountinfo.php | 107 - lib/private/files/config/lazystoragemountinfo.php | 74 - .../files/config/mountprovidercollection.php | 108 - lib/private/files/config/usermountcache.php | 290 --- .../files/config/usermountcachelistener.php | 48 - lib/private/files/fileinfo.php | 346 ---- lib/private/files/filesystem.php | 928 --------- lib/private/files/mount/manager.php | 165 -- lib/private/files/mount/mountpoint.php | 251 --- lib/private/files/mount/moveablemount.php | 44 - lib/private/files/node/file.php | 176 -- lib/private/files/node/folder.php | 360 ---- lib/private/files/node/hookconnector.php | 164 -- lib/private/files/node/lazyroot.php | 474 ----- lib/private/files/node/node.php | 383 ---- lib/private/files/node/nonexistingfile.php | 143 -- lib/private/files/node/nonexistingfolder.php | 172 -- lib/private/files/node/root.php | 357 ---- .../files/objectstore/homeobjectstorestorage.php | 68 - lib/private/files/objectstore/noopscanner.php | 79 - .../files/objectstore/objectstorestorage.php | 407 ---- lib/private/files/objectstore/swift.php | 154 -- lib/private/files/storage/common.php | 697 ------- lib/private/files/storage/commontest.php | 84 - lib/private/files/storage/dav.php | 817 -------- lib/private/files/storage/failedstorage.php | 215 -- lib/private/files/storage/flysystem.php | 256 --- lib/private/files/storage/home.php | 114 -- lib/private/files/storage/local.php | 404 ---- lib/private/files/storage/localtempfiletrait.php | 80 - .../files/storage/polyfill/copydirectory.php | 103 - lib/private/files/storage/storage.php | 119 -- lib/private/files/storage/storagefactory.php | 107 - lib/private/files/storage/temporary.php | 47 - lib/private/files/storage/wrapper/availability.php | 461 ----- lib/private/files/storage/wrapper/encryption.php | 993 ---------- lib/private/files/storage/wrapper/jail.php | 489 ----- .../files/storage/wrapper/permissionsmask.php | 131 -- lib/private/files/storage/wrapper/quota.php | 200 -- lib/private/files/storage/wrapper/wrapper.php | 608 ------ lib/private/files/stream/close.php | 118 -- lib/private/files/stream/dir.php | 66 - lib/private/files/stream/encryption.php | 500 ----- lib/private/files/stream/oc.php | 153 -- lib/private/files/stream/quota.php | 156 -- lib/private/files/stream/staticstream.php | 170 -- lib/private/files/type/detection.php | 318 --- lib/private/files/type/loader.php | 173 -- lib/private/files/type/templatemanager.php | 61 - lib/private/files/utils/scanner.php | 172 -- lib/private/files/view.php | 2058 -------------------- 130 files changed, 18273 insertions(+), 18273 deletions(-) create mode 100644 lib/private/Files/Cache/Cache.php create mode 100644 lib/private/Files/Cache/CacheEntry.php create mode 100644 lib/private/Files/Cache/FailedCache.php create mode 100644 lib/private/Files/Cache/HomeCache.php create mode 100644 lib/private/Files/Cache/HomePropagator.php create mode 100644 lib/private/Files/Cache/MoveFromCacheTrait.php create mode 100644 lib/private/Files/Cache/Propagator.php create mode 100644 lib/private/Files/Cache/Scanner.php create mode 100644 lib/private/Files/Cache/Storage.php create mode 100644 lib/private/Files/Cache/Updater.php create mode 100644 lib/private/Files/Cache/Watcher.php create mode 100644 lib/private/Files/Cache/Wrapper/CacheJail.php create mode 100644 lib/private/Files/Cache/Wrapper/CachePermissionsMask.php create mode 100644 lib/private/Files/Cache/Wrapper/CacheWrapper.php create mode 100644 lib/private/Files/Config/CachedMountInfo.php create mode 100644 lib/private/Files/Config/LazyStorageMountInfo.php create mode 100644 lib/private/Files/Config/MountProviderCollection.php create mode 100644 lib/private/Files/Config/UserMountCache.php create mode 100644 lib/private/Files/Config/UserMountCacheListener.php create mode 100644 lib/private/Files/FileInfo.php create mode 100644 lib/private/Files/Filesystem.php create mode 100644 lib/private/Files/Mount/Manager.php create mode 100644 lib/private/Files/Mount/MountPoint.php create mode 100644 lib/private/Files/Mount/MoveableMount.php create mode 100644 lib/private/Files/Node/File.php create mode 100644 lib/private/Files/Node/Folder.php create mode 100644 lib/private/Files/Node/HookConnector.php create mode 100644 lib/private/Files/Node/LazyRoot.php create mode 100644 lib/private/Files/Node/Node.php create mode 100644 lib/private/Files/Node/NonExistingFile.php create mode 100644 lib/private/Files/Node/NonExistingFolder.php create mode 100644 lib/private/Files/Node/Root.php create mode 100644 lib/private/Files/ObjectStore/HomeObjectStoreStorage.php create mode 100644 lib/private/Files/ObjectStore/NoopScanner.php create mode 100644 lib/private/Files/ObjectStore/ObjectStoreStorage.php create mode 100644 lib/private/Files/ObjectStore/Swift.php create mode 100644 lib/private/Files/Storage/Common.php create mode 100644 lib/private/Files/Storage/CommonTest.php create mode 100644 lib/private/Files/Storage/DAV.php create mode 100644 lib/private/Files/Storage/FailedStorage.php create mode 100644 lib/private/Files/Storage/Flysystem.php create mode 100644 lib/private/Files/Storage/Home.php create mode 100644 lib/private/Files/Storage/Local.php create mode 100644 lib/private/Files/Storage/LocalTempFileTrait.php create mode 100644 lib/private/Files/Storage/PolyFill/CopyDirectory.php create mode 100644 lib/private/Files/Storage/Storage.php create mode 100644 lib/private/Files/Storage/StorageFactory.php create mode 100644 lib/private/Files/Storage/Temporary.php create mode 100644 lib/private/Files/Storage/Wrapper/Availability.php create mode 100644 lib/private/Files/Storage/Wrapper/Encryption.php create mode 100644 lib/private/Files/Storage/Wrapper/Jail.php create mode 100644 lib/private/Files/Storage/Wrapper/PermissionsMask.php create mode 100644 lib/private/Files/Storage/Wrapper/Quota.php create mode 100644 lib/private/Files/Storage/Wrapper/Wrapper.php create mode 100644 lib/private/Files/Stream/Close.php create mode 100644 lib/private/Files/Stream/Dir.php create mode 100644 lib/private/Files/Stream/Encryption.php create mode 100644 lib/private/Files/Stream/OC.php create mode 100644 lib/private/Files/Stream/Quota.php create mode 100644 lib/private/Files/Stream/StaticStream.php create mode 100644 lib/private/Files/Type/Detection.php create mode 100644 lib/private/Files/Type/Loader.php create mode 100644 lib/private/Files/Type/TemplateManager.php create mode 100644 lib/private/Files/Utils/Scanner.php create mode 100644 lib/private/Files/View.php delete mode 100644 lib/private/files/cache/cache.php delete mode 100644 lib/private/files/cache/cacheentry.php delete mode 100644 lib/private/files/cache/failedcache.php delete mode 100644 lib/private/files/cache/homecache.php delete mode 100644 lib/private/files/cache/homepropagator.php delete mode 100644 lib/private/files/cache/movefromcachetrait.php delete mode 100644 lib/private/files/cache/propagator.php delete mode 100644 lib/private/files/cache/scanner.php delete mode 100644 lib/private/files/cache/storage.php delete mode 100644 lib/private/files/cache/updater.php delete mode 100644 lib/private/files/cache/watcher.php delete mode 100644 lib/private/files/cache/wrapper/cachejail.php delete mode 100644 lib/private/files/cache/wrapper/cachepermissionsmask.php delete mode 100644 lib/private/files/cache/wrapper/cachewrapper.php delete mode 100644 lib/private/files/config/cachedmountinfo.php delete mode 100644 lib/private/files/config/lazystoragemountinfo.php delete mode 100644 lib/private/files/config/mountprovidercollection.php delete mode 100644 lib/private/files/config/usermountcache.php delete mode 100644 lib/private/files/config/usermountcachelistener.php delete mode 100644 lib/private/files/fileinfo.php delete mode 100644 lib/private/files/filesystem.php delete mode 100644 lib/private/files/mount/manager.php delete mode 100644 lib/private/files/mount/mountpoint.php delete mode 100644 lib/private/files/mount/moveablemount.php delete mode 100644 lib/private/files/node/file.php delete mode 100644 lib/private/files/node/folder.php delete mode 100644 lib/private/files/node/hookconnector.php delete mode 100644 lib/private/files/node/lazyroot.php delete mode 100644 lib/private/files/node/node.php delete mode 100644 lib/private/files/node/nonexistingfile.php delete mode 100644 lib/private/files/node/nonexistingfolder.php delete mode 100644 lib/private/files/node/root.php delete mode 100644 lib/private/files/objectstore/homeobjectstorestorage.php delete mode 100644 lib/private/files/objectstore/noopscanner.php delete mode 100644 lib/private/files/objectstore/objectstorestorage.php delete mode 100644 lib/private/files/objectstore/swift.php delete mode 100644 lib/private/files/storage/common.php delete mode 100644 lib/private/files/storage/commontest.php delete mode 100644 lib/private/files/storage/dav.php delete mode 100644 lib/private/files/storage/failedstorage.php delete mode 100644 lib/private/files/storage/flysystem.php delete mode 100644 lib/private/files/storage/home.php delete mode 100644 lib/private/files/storage/local.php delete mode 100644 lib/private/files/storage/localtempfiletrait.php delete mode 100644 lib/private/files/storage/polyfill/copydirectory.php delete mode 100644 lib/private/files/storage/storage.php delete mode 100644 lib/private/files/storage/storagefactory.php delete mode 100644 lib/private/files/storage/temporary.php delete mode 100644 lib/private/files/storage/wrapper/availability.php delete mode 100644 lib/private/files/storage/wrapper/encryption.php delete mode 100644 lib/private/files/storage/wrapper/jail.php delete mode 100644 lib/private/files/storage/wrapper/permissionsmask.php delete mode 100644 lib/private/files/storage/wrapper/quota.php delete mode 100644 lib/private/files/storage/wrapper/wrapper.php delete mode 100644 lib/private/files/stream/close.php delete mode 100644 lib/private/files/stream/dir.php delete mode 100644 lib/private/files/stream/encryption.php delete mode 100644 lib/private/files/stream/oc.php delete mode 100644 lib/private/files/stream/quota.php delete mode 100644 lib/private/files/stream/staticstream.php delete mode 100644 lib/private/files/type/detection.php delete mode 100644 lib/private/files/type/loader.php delete mode 100644 lib/private/files/type/templatemanager.php delete mode 100644 lib/private/files/utils/scanner.php delete mode 100644 lib/private/files/view.php (limited to 'lib/private') diff --git a/lib/private/Files/Cache/Cache.php b/lib/private/Files/Cache/Cache.php new file mode 100644 index 00000000000..53467c278d2 --- /dev/null +++ b/lib/private/Files/Cache/Cache.php @@ -0,0 +1,837 @@ + + * @author Björn Schießle + * @author Florin Peter + * @author Jens-Christian Fischer + * @author Joas Schilling + * @author Jörn Friedrich Dreyer + * @author Lukas Reschke + * @author Michael Gapczynski + * @author Morris Jobke + * @author Robin Appelman + * @author Robin McCorkell + * @author Roeland Jago Douma + * @author TheSFReader + * @author Thomas Müller + * @author Vincent Petry + * + * @copyright Copyright (c) 2016, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Files\Cache; + +use OCP\Files\Cache\ICache; +use OCP\Files\Cache\ICacheEntry; +use \OCP\Files\IMimeTypeLoader; +use OCP\IDBConnection; + +/** + * Metadata cache for a storage + * + * The cache stores the metadata for all files and folders in a storage and is kept up to date trough the following mechanisms: + * + * - Scanner: scans the storage and updates the cache where needed + * - Watcher: checks for changes made to the filesystem outside of the ownCloud instance and rescans files and folder when a change is detected + * - Updater: listens to changes made to the filesystem inside of the ownCloud instance and updates the cache where needed + * - ChangePropagator: updates the mtime and etags of parent folders whenever a change to the cache is made to the cache by the updater + */ +class Cache implements ICache { + use MoveFromCacheTrait { + MoveFromCacheTrait::moveFromCache as moveFromCacheFallback; + } + + /** + * @var array partial data for the cache + */ + protected $partial = array(); + + /** + * @var string + */ + protected $storageId; + + /** + * @var Storage $storageCache + */ + protected $storageCache; + + /** @var IMimeTypeLoader */ + protected $mimetypeLoader; + + /** + * @var IDBConnection + */ + protected $connection; + + /** + * @param \OC\Files\Storage\Storage|string $storage + */ + public function __construct($storage) { + if ($storage instanceof \OC\Files\Storage\Storage) { + $this->storageId = $storage->getId(); + } else { + $this->storageId = $storage; + } + if (strlen($this->storageId) > 64) { + $this->storageId = md5($this->storageId); + } + + $this->storageCache = new Storage($storage); + $this->mimetypeLoader = \OC::$server->getMimeTypeLoader(); + $this->connection = \OC::$server->getDatabaseConnection(); + } + + /** + * Get the numeric storage id for this cache's storage + * + * @return int + */ + public function getNumericStorageId() { + return $this->storageCache->getNumericId(); + } + + /** + * get the stored metadata of a file or folder + * + * @param string | int $file either the path of a file or folder or the file id for a file or folder + * @return ICacheEntry|false the cache entry as array of false if the file is not found in the cache + */ + public function get($file) { + if (is_string($file) or $file == '') { + // normalize file + $file = $this->normalize($file); + + $where = 'WHERE `storage` = ? AND `path_hash` = ?'; + $params = array($this->getNumericStorageId(), md5($file)); + } else { //file id + $where = 'WHERE `fileid` = ?'; + $params = array($file); + } + $sql = 'SELECT `fileid`, `storage`, `path`, `parent`, `name`, `mimetype`, `mimepart`, `size`, `mtime`, + `storage_mtime`, `encrypted`, `etag`, `permissions`, `checksum` + FROM `*PREFIX*filecache` ' . $where; + $result = $this->connection->executeQuery($sql, $params); + $data = $result->fetch(); + + //FIXME hide this HACK in the next database layer, or just use doctrine and get rid of MDB2 and PDO + //PDO returns false, MDB2 returns null, oracle always uses MDB2, so convert null to false + if ($data === null) { + $data = false; + } + + //merge partial data + if (!$data and is_string($file)) { + if (isset($this->partial[$file])) { + $data = $this->partial[$file]; + } + return $data; + } else { + //fix types + $data['fileid'] = (int)$data['fileid']; + $data['parent'] = (int)$data['parent']; + $data['size'] = 0 + $data['size']; + $data['mtime'] = (int)$data['mtime']; + $data['storage_mtime'] = (int)$data['storage_mtime']; + $data['encryptedVersion'] = (int)$data['encrypted']; + $data['encrypted'] = (bool)$data['encrypted']; + $data['storage'] = $this->storageId; + $data['mimetype'] = $this->mimetypeLoader->getMimetypeById($data['mimetype']); + $data['mimepart'] = $this->mimetypeLoader->getMimetypeById($data['mimepart']); + if ($data['storage_mtime'] == 0) { + $data['storage_mtime'] = $data['mtime']; + } + $data['permissions'] = (int)$data['permissions']; + return new CacheEntry($data); + } + } + + /** + * get the metadata of all files stored in $folder + * + * @param string $folder + * @return ICacheEntry[] + */ + public function getFolderContents($folder) { + $fileId = $this->getId($folder); + return $this->getFolderContentsById($fileId); + } + + /** + * get the metadata of all files stored in $folder + * + * @param int $fileId the file id of the folder + * @return ICacheEntry[] + */ + public function getFolderContentsById($fileId) { + if ($fileId > -1) { + $sql = 'SELECT `fileid`, `storage`, `path`, `parent`, `name`, `mimetype`, `mimepart`, `size`, `mtime`, + `storage_mtime`, `encrypted`, `etag`, `permissions`, `checksum` + FROM `*PREFIX*filecache` WHERE `parent` = ? ORDER BY `name` ASC'; + $result = $this->connection->executeQuery($sql, [$fileId]); + $files = $result->fetchAll(); + foreach ($files as &$file) { + $file['mimetype'] = $this->mimetypeLoader->getMimetypeById($file['mimetype']); + $file['mimepart'] = $this->mimetypeLoader->getMimetypeById($file['mimepart']); + if ($file['storage_mtime'] == 0) { + $file['storage_mtime'] = $file['mtime']; + } + $file['permissions'] = (int)$file['permissions']; + $file['mtime'] = (int)$file['mtime']; + $file['storage_mtime'] = (int)$file['storage_mtime']; + $file['size'] = 0 + $file['size']; + } + return array_map(function (array $data) { + return new CacheEntry($data); + }, $files); + } else { + return array(); + } + } + + /** + * insert or update meta data for a file or folder + * + * @param string $file + * @param array $data + * + * @return int file id + * @throws \RuntimeException + */ + public function put($file, array $data) { + if (($id = $this->getId($file)) > -1) { + $this->update($id, $data); + return $id; + } else { + return $this->insert($file, $data); + } + } + + /** + * insert meta data for a new file or folder + * + * @param string $file + * @param array $data + * + * @return int file id + * @throws \RuntimeException + */ + public function insert($file, array $data) { + // normalize file + $file = $this->normalize($file); + + if (isset($this->partial[$file])) { //add any saved partial data + $data = array_merge($this->partial[$file], $data); + unset($this->partial[$file]); + } + + $requiredFields = array('size', 'mtime', 'mimetype'); + foreach ($requiredFields as $field) { + if (!isset($data[$field])) { //data not complete save as partial and return + $this->partial[$file] = $data; + return -1; + } + } + + $data['path'] = $file; + $data['parent'] = $this->getParentId($file); + $data['name'] = \OC_Util::basename($file); + + list($queryParts, $params) = $this->buildParts($data); + $queryParts[] = '`storage`'; + $params[] = $this->getNumericStorageId(); + + $queryParts = array_map(function ($item) { + return trim($item, "`"); + }, $queryParts); + $values = array_combine($queryParts, $params); + if (\OC::$server->getDatabaseConnection()->insertIfNotExist('*PREFIX*filecache', $values, [ + 'storage', + 'path_hash', + ]) + ) { + return (int)$this->connection->lastInsertId('*PREFIX*filecache'); + } + + // The file was created in the mean time + if (($id = $this->getId($file)) > -1) { + $this->update($id, $data); + return $id; + } else { + throw new \RuntimeException('File entry could not be inserted with insertIfNotExist() but could also not be selected with getId() in order to perform an update. Please try again.'); + } + } + + /** + * update the metadata of an existing file or folder in the cache + * + * @param int $id the fileid of the existing file or folder + * @param array $data [$key => $value] the metadata to update, only the fields provided in the array will be updated, non-provided values will remain unchanged + */ + public function update($id, array $data) { + + if (isset($data['path'])) { + // normalize path + $data['path'] = $this->normalize($data['path']); + } + + if (isset($data['name'])) { + // normalize path + $data['name'] = $this->normalize($data['name']); + } + + list($queryParts, $params) = $this->buildParts($data); + // duplicate $params because we need the parts twice in the SQL statement + // once for the SET part, once in the WHERE clause + $params = array_merge($params, $params); + $params[] = $id; + + // don't update if the data we try to set is the same as the one in the record + // some databases (Postgres) don't like superfluous updates + $sql = 'UPDATE `*PREFIX*filecache` SET ' . implode(' = ?, ', $queryParts) . '=? ' . + 'WHERE (' . + implode(' <> ? OR ', $queryParts) . ' <> ? OR ' . + implode(' IS NULL OR ', $queryParts) . ' IS NULL' . + ') AND `fileid` = ? '; + $this->connection->executeQuery($sql, $params); + + } + + /** + * extract query parts and params array from data array + * + * @param array $data + * @return array [$queryParts, $params] + * $queryParts: string[], the (escaped) column names to be set in the query + * $params: mixed[], the new values for the columns, to be passed as params to the query + */ + protected function buildParts(array $data) { + $fields = array( + 'path', 'parent', 'name', 'mimetype', 'size', 'mtime', 'storage_mtime', 'encrypted', + 'etag', 'permissions', 'checksum'); + + $doNotCopyStorageMTime = false; + if (array_key_exists('mtime', $data) && $data['mtime'] === null) { + // this horrific magic tells it to not copy storage_mtime to mtime + unset($data['mtime']); + $doNotCopyStorageMTime = true; + } + + $params = array(); + $queryParts = array(); + foreach ($data as $name => $value) { + if (array_search($name, $fields) !== false) { + if ($name === 'path') { + $params[] = md5($value); + $queryParts[] = '`path_hash`'; + } elseif ($name === 'mimetype') { + $params[] = $this->mimetypeLoader->getId(substr($value, 0, strpos($value, '/'))); + $queryParts[] = '`mimepart`'; + $value = $this->mimetypeLoader->getId($value); + } elseif ($name === 'storage_mtime') { + if (!$doNotCopyStorageMTime && !isset($data['mtime'])) { + $params[] = $value; + $queryParts[] = '`mtime`'; + } + } elseif ($name === 'encrypted') { + if(isset($data['encryptedVersion'])) { + $value = $data['encryptedVersion']; + } else { + // Boolean to integer conversion + $value = $value ? 1 : 0; + } + } + $params[] = $value; + $queryParts[] = '`' . $name . '`'; + } + } + return array($queryParts, $params); + } + + /** + * get the file id for a file + * + * A file id is a numeric id for a file or folder that's unique within an owncloud instance which stays the same for the lifetime of a file + * + * File ids are easiest way for apps to store references to a file since unlike paths they are not affected by renames or sharing + * + * @param string $file + * @return int + */ + public function getId($file) { + // normalize file + $file = $this->normalize($file); + + $pathHash = md5($file); + + $sql = 'SELECT `fileid` FROM `*PREFIX*filecache` WHERE `storage` = ? AND `path_hash` = ?'; + $result = $this->connection->executeQuery($sql, array($this->getNumericStorageId(), $pathHash)); + if ($row = $result->fetch()) { + return $row['fileid']; + } else { + return -1; + } + } + + /** + * get the id of the parent folder of a file + * + * @param string $file + * @return int + */ + public function getParentId($file) { + if ($file === '') { + return -1; + } else { + $parent = $this->getParentPath($file); + return (int)$this->getId($parent); + } + } + + private function getParentPath($path) { + $parent = dirname($path); + if ($parent === '.') { + $parent = ''; + } + return $parent; + } + + /** + * check if a file is available in the cache + * + * @param string $file + * @return bool + */ + public function inCache($file) { + return $this->getId($file) != -1; + } + + /** + * remove a file or folder from the cache + * + * when removing a folder from the cache all files and folders inside the folder will be removed as well + * + * @param string $file + */ + public function remove($file) { + $entry = $this->get($file); + $sql = 'DELETE FROM `*PREFIX*filecache` WHERE `fileid` = ?'; + $this->connection->executeQuery($sql, array($entry['fileid'])); + if ($entry['mimetype'] === 'httpd/unix-directory') { + $this->removeChildren($entry); + } + } + + /** + * Get all sub folders of a folder + * + * @param array $entry the cache entry of the folder to get the subfolders for + * @return array[] the cache entries for the subfolders + */ + private function getSubFolders($entry) { + $children = $this->getFolderContentsById($entry['fileid']); + return array_filter($children, function ($child) { + return $child['mimetype'] === 'httpd/unix-directory'; + }); + } + + /** + * Recursively remove all children of a folder + * + * @param array $entry the cache entry of the folder to remove the children of + * @throws \OC\DatabaseException + */ + private function removeChildren($entry) { + $subFolders = $this->getSubFolders($entry); + foreach ($subFolders as $folder) { + $this->removeChildren($folder); + } + $sql = 'DELETE FROM `*PREFIX*filecache` WHERE `parent` = ?'; + $this->connection->executeQuery($sql, array($entry['fileid'])); + } + + /** + * Move a file or folder in the cache + * + * @param string $source + * @param string $target + */ + public function move($source, $target) { + $this->moveFromCache($this, $source, $target); + } + + /** + * Get the storage id and path needed for a move + * + * @param string $path + * @return array [$storageId, $internalPath] + */ + protected function getMoveInfo($path) { + return [$this->getNumericStorageId(), $path]; + } + + /** + * Move a file or folder in the cache + * + * @param \OCP\Files\Cache\ICache $sourceCache + * @param string $sourcePath + * @param string $targetPath + * @throws \OC\DatabaseException + */ + public function moveFromCache(ICache $sourceCache, $sourcePath, $targetPath) { + if ($sourceCache instanceof Cache) { + // normalize source and target + $sourcePath = $this->normalize($sourcePath); + $targetPath = $this->normalize($targetPath); + + $sourceData = $sourceCache->get($sourcePath); + $sourceId = $sourceData['fileid']; + $newParentId = $this->getParentId($targetPath); + + list($sourceStorageId, $sourcePath) = $sourceCache->getMoveInfo($sourcePath); + list($targetStorageId, $targetPath) = $this->getMoveInfo($targetPath); + + // sql for final update + $moveSql = 'UPDATE `*PREFIX*filecache` SET `storage` = ?, `path` = ?, `path_hash` = ?, `name` = ?, `parent` =? WHERE `fileid` = ?'; + + if ($sourceData['mimetype'] === 'httpd/unix-directory') { + //find all child entries + $sql = 'SELECT `path`, `fileid` FROM `*PREFIX*filecache` WHERE `storage` = ? AND `path` LIKE ?'; + $result = $this->connection->executeQuery($sql, [$sourceStorageId, $this->connection->escapeLikeParameter($sourcePath) . '/%']); + $childEntries = $result->fetchAll(); + $sourceLength = strlen($sourcePath); + $this->connection->beginTransaction(); + $query = $this->connection->prepare('UPDATE `*PREFIX*filecache` SET `storage` = ?, `path` = ?, `path_hash` = ? WHERE `fileid` = ?'); + + foreach ($childEntries as $child) { + $newTargetPath = $targetPath . substr($child['path'], $sourceLength); + $query->execute([$targetStorageId, $newTargetPath, md5($newTargetPath), $child['fileid']]); + } + $this->connection->executeQuery($moveSql, [$targetStorageId, $targetPath, md5($targetPath), basename($targetPath), $newParentId, $sourceId]); + $this->connection->commit(); + } else { + $this->connection->executeQuery($moveSql, [$targetStorageId, $targetPath, md5($targetPath), basename($targetPath), $newParentId, $sourceId]); + } + } else { + $this->moveFromCacheFallback($sourceCache, $sourcePath, $targetPath); + } + } + + /** + * remove all entries for files that are stored on the storage from the cache + */ + public function clear() { + $sql = 'DELETE FROM `*PREFIX*filecache` WHERE `storage` = ?'; + $this->connection->executeQuery($sql, array($this->getNumericStorageId())); + + $sql = 'DELETE FROM `*PREFIX*storages` WHERE `id` = ?'; + $this->connection->executeQuery($sql, array($this->storageId)); + } + + /** + * Get the scan status of a file + * + * - Cache::NOT_FOUND: File is not in the cache + * - Cache::PARTIAL: File is not stored in the cache but some incomplete data is known + * - Cache::SHALLOW: The folder and it's direct children are in the cache but not all sub folders are fully scanned + * - Cache::COMPLETE: The file or folder, with all it's children) are fully scanned + * + * @param string $file + * + * @return int Cache::NOT_FOUND, Cache::PARTIAL, Cache::SHALLOW or Cache::COMPLETE + */ + public function getStatus($file) { + // normalize file + $file = $this->normalize($file); + + $pathHash = md5($file); + $sql = 'SELECT `size` FROM `*PREFIX*filecache` WHERE `storage` = ? AND `path_hash` = ?'; + $result = $this->connection->executeQuery($sql, array($this->getNumericStorageId(), $pathHash)); + if ($row = $result->fetch()) { + if ((int)$row['size'] === -1) { + return self::SHALLOW; + } else { + return self::COMPLETE; + } + } else { + if (isset($this->partial[$file])) { + return self::PARTIAL; + } else { + return self::NOT_FOUND; + } + } + } + + /** + * search for files matching $pattern + * + * @param string $pattern the search pattern using SQL search syntax (e.g. '%searchstring%') + * @return ICacheEntry[] an array of cache entries where the name matches the search pattern + */ + public function search($pattern) { + // normalize pattern + $pattern = $this->normalize($pattern); + + + $sql = ' + SELECT `fileid`, `storage`, `path`, `parent`, `name`, + `mimetype`, `mimepart`, `size`, `mtime`, `encrypted`, + `etag`, `permissions`, `checksum` + FROM `*PREFIX*filecache` + WHERE `storage` = ? AND `name` ILIKE ?'; + $result = $this->connection->executeQuery($sql, + [$this->getNumericStorageId(), $pattern] + ); + + $files = []; + while ($row = $result->fetch()) { + $row['mimetype'] = $this->mimetypeLoader->getMimetypeById($row['mimetype']); + $row['mimepart'] = $this->mimetypeLoader->getMimetypeById($row['mimepart']); + $files[] = $row; + } + return array_map(function(array $data) { + return new CacheEntry($data); + }, $files); + } + + /** + * search for files by mimetype + * + * @param string $mimetype either a full mimetype to search ('text/plain') or only the first part of a mimetype ('image') + * where it will search for all mimetypes in the group ('image/*') + * @return ICacheEntry[] an array of cache entries where the mimetype matches the search + */ + public function searchByMime($mimetype) { + if (strpos($mimetype, '/')) { + $where = '`mimetype` = ?'; + } else { + $where = '`mimepart` = ?'; + } + $sql = 'SELECT `fileid`, `storage`, `path`, `parent`, `name`, `mimetype`, `mimepart`, `size`, `mtime`, `encrypted`, `etag`, `permissions`, `checksum` + FROM `*PREFIX*filecache` WHERE ' . $where . ' AND `storage` = ?'; + $mimetype = $this->mimetypeLoader->getId($mimetype); + $result = $this->connection->executeQuery($sql, array($mimetype, $this->getNumericStorageId())); + $files = array(); + while ($row = $result->fetch()) { + $row['mimetype'] = $this->mimetypeLoader->getMimetypeById($row['mimetype']); + $row['mimepart'] = $this->mimetypeLoader->getMimetypeById($row['mimepart']); + $files[] = $row; + } + return array_map(function (array $data) { + return new CacheEntry($data); + }, $files); + } + + /** + * Search for files by tag of a given users. + * + * Note that every user can tag files differently. + * + * @param string|int $tag name or tag id + * @param string $userId owner of the tags + * @return ICacheEntry[] file data + */ + public function searchByTag($tag, $userId) { + $sql = 'SELECT `fileid`, `storage`, `path`, `parent`, `name`, ' . + '`mimetype`, `mimepart`, `size`, `mtime`, ' . + '`encrypted`, `etag`, `permissions`, `checksum` ' . + 'FROM `*PREFIX*filecache` `file`, ' . + '`*PREFIX*vcategory_to_object` `tagmap`, ' . + '`*PREFIX*vcategory` `tag` ' . + // JOIN filecache to vcategory_to_object + 'WHERE `file`.`fileid` = `tagmap`.`objid` ' . + // JOIN vcategory_to_object to vcategory + 'AND `tagmap`.`type` = `tag`.`type` ' . + 'AND `tagmap`.`categoryid` = `tag`.`id` ' . + // conditions + 'AND `file`.`storage` = ? ' . + 'AND `tag`.`type` = \'files\' ' . + 'AND `tag`.`uid` = ? '; + if (is_int($tag)) { + $sql .= 'AND `tag`.`id` = ? '; + } else { + $sql .= 'AND `tag`.`category` = ? '; + } + $result = $this->connection->executeQuery( + $sql, + [ + $this->getNumericStorageId(), + $userId, + $tag + ] + ); + $files = array(); + while ($row = $result->fetch()) { + $files[] = $row; + } + return array_map(function (array $data) { + return new CacheEntry($data); + }, $files); + } + + /** + * Re-calculate the folder size and the size of all parent folders + * + * @param string|boolean $path + * @param array $data (optional) meta data of the folder + */ + public function correctFolderSize($path, $data = null) { + $this->calculateFolderSize($path, $data); + if ($path !== '') { + $parent = dirname($path); + if ($parent === '.' or $parent === '/') { + $parent = ''; + } + $this->correctFolderSize($parent); + } + } + + /** + * calculate the size of a folder and set it in the cache + * + * @param string $path + * @param array $entry (optional) meta data of the folder + * @return int + */ + public function calculateFolderSize($path, $entry = null) { + $totalSize = 0; + if (is_null($entry) or !isset($entry['fileid'])) { + $entry = $this->get($path); + } + if (isset($entry['mimetype']) && $entry['mimetype'] === 'httpd/unix-directory') { + $id = $entry['fileid']; + $sql = 'SELECT SUM(`size`) AS f1, MIN(`size`) AS f2 ' . + 'FROM `*PREFIX*filecache` ' . + 'WHERE `parent` = ? AND `storage` = ?'; + $result = $this->connection->executeQuery($sql, array($id, $this->getNumericStorageId())); + if ($row = $result->fetch()) { + $result->closeCursor(); + list($sum, $min) = array_values($row); + $sum = 0 + $sum; + $min = 0 + $min; + if ($min === -1) { + $totalSize = $min; + } else { + $totalSize = $sum; + } + $update = array(); + if ($entry['size'] !== $totalSize) { + $update['size'] = $totalSize; + } + if (count($update) > 0) { + $this->update($id, $update); + } + } else { + $result->closeCursor(); + } + } + return $totalSize; + } + + /** + * get all file ids on the files on the storage + * + * @return int[] + */ + public function getAll() { + $sql = 'SELECT `fileid` FROM `*PREFIX*filecache` WHERE `storage` = ?'; + $result = $this->connection->executeQuery($sql, array($this->getNumericStorageId())); + $ids = array(); + while ($row = $result->fetch()) { + $ids[] = $row['fileid']; + } + return $ids; + } + + /** + * find a folder in the cache which has not been fully scanned + * + * If multiple incomplete folders are in the cache, the one with the highest id will be returned, + * use the one with the highest id gives the best result with the background scanner, since that is most + * likely the folder where we stopped scanning previously + * + * @return string|bool the path of the folder or false when no folder matched + */ + public function getIncomplete() { + $query = $this->connection->prepare('SELECT `path` FROM `*PREFIX*filecache`' + . ' WHERE `storage` = ? AND `size` = -1 ORDER BY `fileid` DESC', 1); + $query->execute([$this->getNumericStorageId()]); + if ($row = $query->fetch()) { + return $row['path']; + } else { + return false; + } + } + + /** + * get the path of a file on this storage by it's file id + * + * @param int $id the file id of the file or folder to search + * @return string|null the path of the file (relative to the storage) or null if a file with the given id does not exists within this cache + */ + public function getPathById($id) { + $sql = 'SELECT `path` FROM `*PREFIX*filecache` WHERE `fileid` = ? AND `storage` = ?'; + $result = $this->connection->executeQuery($sql, array($id, $this->getNumericStorageId())); + if ($row = $result->fetch()) { + // Oracle stores empty strings as null... + if ($row['path'] === null) { + return ''; + } + return $row['path']; + } else { + return null; + } + } + + /** + * get the storage id of the storage for a file and the internal path of the file + * unlike getPathById this does not limit the search to files on this storage and + * instead does a global search in the cache table + * + * @param int $id + * @deprecated use getPathById() instead + * @return array first element holding the storage id, second the path + */ + static public function getById($id) { + $connection = \OC::$server->getDatabaseConnection(); + $sql = 'SELECT `storage`, `path` FROM `*PREFIX*filecache` WHERE `fileid` = ?'; + $result = $connection->executeQuery($sql, array($id)); + if ($row = $result->fetch()) { + $numericId = $row['storage']; + $path = $row['path']; + } else { + return null; + } + + if ($id = Storage::getStorageId($numericId)) { + return array($id, $path); + } else { + return null; + } + } + + /** + * normalize the given path + * + * @param string $path + * @return string + */ + public function normalize($path) { + + return trim(\OC_Util::normalizeUnicode($path), '/'); + } +} diff --git a/lib/private/Files/Cache/CacheEntry.php b/lib/private/Files/Cache/CacheEntry.php new file mode 100644 index 00000000000..6d3c5d5b089 --- /dev/null +++ b/lib/private/Files/Cache/CacheEntry.php @@ -0,0 +1,114 @@ + + * + * @copyright Copyright (c) 2016, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Files\Cache; + +use OCP\Files\Cache\ICacheEntry; + +/** + * meta data for a file or folder + */ +class CacheEntry implements ICacheEntry, \ArrayAccess { + /** + * @var array + */ + private $data; + + public function __construct(array $data) { + $this->data = $data; + } + + public function offsetSet($offset, $value) { + $this->data[$offset] = $value; + } + + public function offsetExists($offset) { + return isset($this->data[$offset]); + } + + public function offsetUnset($offset) { + unset($this->data[$offset]); + } + + public function offsetGet($offset) { + if (isset($this->data[$offset])) { + return $this->data[$offset]; + } else { + return null; + } + } + + public function getId() { + return (int)$this->data['fileid']; + } + + public function getStorageId() { + return $this->data['storage']; + } + + + public function getPath() { + return $this->data['path']; + } + + + public function getName() { + return $this->data['name']; + } + + + public function getMimeType() { + return $this->data['mimetype']; + } + + + public function getMimePart() { + return $this->data['mimepart']; + } + + public function getSize() { + return $this->data['size']; + } + + public function getMTime() { + return $this->data['mtime']; + } + + public function getStorageMTime() { + return $this->data['storage_mtime']; + } + + public function getEtag() { + return $this->data['etag']; + } + + public function getPermissions() { + return $this->data['permissions']; + } + + public function isEncrypted() { + return isset($this->data['encrypted']) && $this->data['encrypted']; + } + + public function getData() { + return $this->data; + } +} diff --git a/lib/private/Files/Cache/FailedCache.php b/lib/private/Files/Cache/FailedCache.php new file mode 100644 index 00000000000..0386ba3ca32 --- /dev/null +++ b/lib/private/Files/Cache/FailedCache.php @@ -0,0 +1,142 @@ + + * + * @copyright Copyright (c) 2016, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Files\Cache; + +use OCP\Constants; +use OCP\Files\Cache\ICache; + +/** + * Storage placeholder to represent a missing precondition, storage unavailable + */ +class FailedCache implements ICache { + /** @var bool whether to show the failed storage in the ui */ + private $visible; + + /** + * FailedCache constructor. + * + * @param bool $visible + */ + public function __construct($visible = true) { + $this->visible = $visible; + } + + + public function getNumericStorageId() { + return -1; + } + + public function get($file) { + if ($file === '') { + return new CacheEntry([ + 'fileid' => -1, + 'size' => 0, + 'mimetype' => 'httpd/unix-directory', + 'mimepart' => 'httpd', + 'permissions' => $this->visible ? Constants::PERMISSION_READ : 0, + 'mtime' => time() + ]); + } else { + return false; + } + } + + public function getFolderContents($folder) { + return []; + } + + public function getFolderContentsById($fileId) { + return []; + } + + public function put($file, array $data) { + return; + } + + public function insert($file, array $data) { + return; + } + + public function update($id, array $data) { + return; + } + + public function getId($file) { + return -1; + } + + public function getParentId($file) { + return -1; + } + + public function inCache($file) { + return false; + } + + public function remove($file) { + return; + } + + public function move($source, $target) { + return; + } + + public function moveFromCache(ICache $sourceCache, $sourcePath, $targetPath) { + return; + } + + public function clear() { + return; + } + + public function getStatus($file) { + return ICache::NOT_FOUND; + } + + public function search($pattern) { + return []; + } + + public function searchByMime($mimetype) { + return []; + } + + public function searchByTag($tag, $userId) { + return []; + } + + public function getAll() { + return []; + } + + public function getIncomplete() { + return []; + } + + public function getPathById($id) { + return null; + } + + public function normalize($path) { + return $path; + } +} diff --git a/lib/private/Files/Cache/HomeCache.php b/lib/private/Files/Cache/HomeCache.php new file mode 100644 index 00000000000..ae92504ddd6 --- /dev/null +++ b/lib/private/Files/Cache/HomeCache.php @@ -0,0 +1,86 @@ + + * @author Björn Schießle + * @author Jörn Friedrich Dreyer + * @author Morris Jobke + * @author Robin Appelman + * @author Vincent Petry + * + * @copyright Copyright (c) 2016, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Files\Cache; + +use OCP\Files\Cache\ICacheEntry; + +class HomeCache extends Cache { + /** + * get the size of a folder and set it in the cache + * + * @param string $path + * @param array $entry (optional) meta data of the folder + * @return int + */ + public function calculateFolderSize($path, $entry = null) { + if ($path !== '/' and $path !== '' and $path !== 'files' and $path !== 'files_trashbin' and $path !== 'files_versions') { + return parent::calculateFolderSize($path, $entry); + } elseif ($path === '' or $path === '/') { + // since the size of / isn't used (the size of /files is used instead) there is no use in calculating it + return 0; + } + + $totalSize = 0; + if (is_null($entry)) { + $entry = $this->get($path); + } + if ($entry && $entry['mimetype'] === 'httpd/unix-directory') { + $id = $entry['fileid']; + $sql = 'SELECT SUM(`size`) AS f1 ' . + 'FROM `*PREFIX*filecache` ' . + 'WHERE `parent` = ? AND `storage` = ? AND `size` >= 0'; + $result = \OC_DB::executeAudited($sql, array($id, $this->getNumericStorageId())); + if ($row = $result->fetchRow()) { + $result->closeCursor(); + list($sum) = array_values($row); + $totalSize = 0 + $sum; + $entry['size'] += 0; + if ($entry['size'] !== $totalSize) { + $this->update($id, array('size' => $totalSize)); + } + } + } + return $totalSize; + } + + /** + * @param string $path + * @return ICacheEntry + */ + public function get($path) { + $data = parent::get($path); + if ($path === '' or $path === '/') { + // only the size of the "files" dir counts + $filesData = parent::get('files'); + + if (isset($filesData['size'])) { + $data['size'] = $filesData['size']; + } + } + return $data; + } +} diff --git a/lib/private/Files/Cache/HomePropagator.php b/lib/private/Files/Cache/HomePropagator.php new file mode 100644 index 00000000000..8edca9c0c87 --- /dev/null +++ b/lib/private/Files/Cache/HomePropagator.php @@ -0,0 +1,50 @@ + + * + * @copyright Copyright (c) 2016, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Files\Cache; + +class HomePropagator extends Propagator { + private $ignoredBaseFolders; + + /** + * @param \OC\Files\Storage\Storage $storage + */ + public function __construct(\OC\Files\Storage\Storage $storage) { + parent::__construct($storage); + $this->ignoredBaseFolders = ['files_encryption']; + } + + + /** + * @param string $internalPath + * @param int $time + * @param int $sizeDifference number of bytes the file has grown + * @return array[] all propagated entries + */ + public function propagateChange($internalPath, $time, $sizeDifference = 0) { + list($baseFolder) = explode('/', $internalPath, 2); + if (in_array($baseFolder, $this->ignoredBaseFolders)) { + return []; + } else { + return parent::propagateChange($internalPath, $time, $sizeDifference); + } + } +} diff --git a/lib/private/Files/Cache/MoveFromCacheTrait.php b/lib/private/Files/Cache/MoveFromCacheTrait.php new file mode 100644 index 00000000000..7d8ed7b5d21 --- /dev/null +++ b/lib/private/Files/Cache/MoveFromCacheTrait.php @@ -0,0 +1,87 @@ + + * + * @copyright Copyright (c) 2016, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Files\Cache; + +use OCP\Files\Cache\ICache; +use OCP\Files\Cache\ICacheEntry; + +/** + * Fallback implementation for moveFromCache + */ +trait MoveFromCacheTrait { + /** + * store meta data for a file or folder + * + * @param string $file + * @param array $data + * + * @return int file id + * @throws \RuntimeException + */ + abstract public function put($file, array $data); + + /** + * Move a file or folder in the cache + * + * @param \OCP\Files\Cache\ICache $sourceCache + * @param string $sourcePath + * @param string $targetPath + */ + public function moveFromCache(ICache $sourceCache, $sourcePath, $targetPath) { + $sourceEntry = $sourceCache->get($sourcePath); + + $this->copyFromCache($sourceCache, $sourceEntry, $targetPath); + + $sourceCache->remove($sourcePath); + } + + /** + * Copy a file or folder in the cache + * + * @param \OCP\Files\Cache\ICache $sourceCache + * @param ICacheEntry $sourceEntry + * @param string $targetPath + */ + public function copyFromCache(ICache $sourceCache, ICacheEntry $sourceEntry, $targetPath) { + $this->put($targetPath, $this->cacheEntryToArray($sourceEntry)); + if ($sourceEntry->getMimeType() === ICacheEntry::DIRECTORY_MIMETYPE) { + $folderContent = $sourceCache->getFolderContentsById($sourceEntry->getId()); + foreach ($folderContent as $subEntry) { + $subTargetPath = $targetPath . '/' . $subEntry->getName(); + $this->copyFromCache($sourceCache, $subEntry, $subTargetPath); + } + } + } + + private function cacheEntryToArray(ICacheEntry $entry) { + return [ + 'size' => $entry->getSize(), + 'mtime' => $entry->getMTime(), + 'storage_mtime' => $entry->getStorageMTime(), + 'mimetype' => $entry->getMimeType(), + 'mimepart' => $entry->getMimePart(), + 'etag' => $entry->getEtag(), + 'permissions' => $entry->getPermissions(), + 'encrypted' => $entry->isEncrypted() + ]; + } +} diff --git a/lib/private/Files/Cache/Propagator.php b/lib/private/Files/Cache/Propagator.php new file mode 100644 index 00000000000..50264e54d44 --- /dev/null +++ b/lib/private/Files/Cache/Propagator.php @@ -0,0 +1,74 @@ + + * + * @copyright Copyright (c) 2016, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Files\Cache; + +use OCP\Files\Cache\IPropagator; + +/** + * Propagate etags and mtimes within the storage + */ +class Propagator implements IPropagator { + /** + * @var \OC\Files\Storage\Storage + */ + protected $storage; + + /** + * @param \OC\Files\Storage\Storage $storage + */ + public function __construct(\OC\Files\Storage\Storage $storage) { + $this->storage = $storage; + } + + + /** + * @param string $internalPath + * @param int $time + * @param int $sizeDifference number of bytes the file has grown + * @return array[] all propagated entries + */ + public function propagateChange($internalPath, $time, $sizeDifference = 0) { + $cache = $this->storage->getCache($internalPath); + + $parentId = $cache->getParentId($internalPath); + $propagatedEntries = []; + while ($parentId !== -1) { + $entry = $cache->get($parentId); + $propagatedEntries[] = $entry; + if (!$entry) { + return $propagatedEntries; + } + $mtime = max($time, $entry['mtime']); + + if ($entry['size'] === -1) { + $newSize = -1; + } else { + $newSize = $entry['size'] + $sizeDifference; + } + $cache->update($parentId, ['mtime' => $mtime, 'etag' => $this->storage->getETag($entry['path']), 'size' => $newSize]); + + $parentId = $entry['parent']; + } + + return $propagatedEntries; + } +} diff --git a/lib/private/Files/Cache/Scanner.php b/lib/private/Files/Cache/Scanner.php new file mode 100644 index 00000000000..8730707f1c2 --- /dev/null +++ b/lib/private/Files/Cache/Scanner.php @@ -0,0 +1,503 @@ + + * @author Björn Schießle + * @author Joas Schilling + * @author Jörn Friedrich Dreyer + * @author Lukas Reschke + * @author Martin Mattel + * @author Michael Gapczynski + * @author Morris Jobke + * @author Owen Winkler + * @author Robin Appelman + * @author Robin McCorkell + * @author Thomas Müller + * @author Vincent Petry + * + * @copyright Copyright (c) 2016, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Files\Cache; + +use OC\Files\Filesystem; +use OC\Hooks\BasicEmitter; +use OCP\Config; +use OCP\Files\Cache\IScanner; +use OCP\Files\Storage\ILockingStorage; +use OCP\Lock\ILockingProvider; + +/** + * Class Scanner + * + * Hooks available in scope \OC\Files\Cache\Scanner: + * - scanFile(string $path, string $storageId) + * - scanFolder(string $path, string $storageId) + * - postScanFile(string $path, string $storageId) + * - postScanFolder(string $path, string $storageId) + * + * @package OC\Files\Cache + */ +class Scanner extends BasicEmitter implements IScanner { + /** + * @var \OC\Files\Storage\Storage $storage + */ + protected $storage; + + /** + * @var string $storageId + */ + protected $storageId; + + /** + * @var \OC\Files\Cache\Cache $cache + */ + protected $cache; + + /** + * @var boolean $cacheActive If true, perform cache operations, if false, do not affect cache + */ + protected $cacheActive; + + /** + * @var bool $useTransactions whether to use transactions + */ + protected $useTransactions = true; + + /** + * @var \OCP\Lock\ILockingProvider + */ + protected $lockingProvider; + + public function __construct(\OC\Files\Storage\Storage $storage) { + $this->storage = $storage; + $this->storageId = $this->storage->getId(); + $this->cache = $storage->getCache(); + $this->cacheActive = !Config::getSystemValue('filesystem_cache_readonly', false); + $this->lockingProvider = \OC::$server->getLockingProvider(); + } + + /** + * Whether to wrap the scanning of a folder in a database transaction + * On default transactions are used + * + * @param bool $useTransactions + */ + public function setUseTransactions($useTransactions) { + $this->useTransactions = $useTransactions; + } + + /** + * get all the metadata of a file or folder + * * + * + * @param string $path + * @return array an array of metadata of the file + */ + protected function getData($path) { + $data = $this->storage->getMetaData($path); + if (is_null($data)) { + \OCP\Util::writeLog('OC\Files\Cache\Scanner', "!!! Path '$path' is not accessible or present !!!", \OCP\Util::DEBUG); + } + return $data; + } + + /** + * scan a single file and store it in the cache + * + * @param string $file + * @param int $reuseExisting + * @param int $parentId + * @param array | null $cacheData existing data in the cache for the file to be scanned + * @param bool $lock set to false to disable getting an additional read lock during scanning + * @return array an array of metadata of the scanned file + * @throws \OC\ServerNotAvailableException + * @throws \OCP\Lock\LockedException + */ + public function scanFile($file, $reuseExisting = 0, $parentId = -1, $cacheData = null, $lock = true) { + + // only proceed if $file is not a partial file nor a blacklisted file + if (!self::isPartialFile($file) and !Filesystem::isFileBlacklisted($file)) { + + //acquire a lock + if ($lock) { + if ($this->storage->instanceOfStorage('\OCP\Files\Storage\ILockingStorage')) { + $this->storage->acquireLock($file, ILockingProvider::LOCK_SHARED, $this->lockingProvider); + } + } + + $data = $this->getData($file); + + if ($data) { + + // pre-emit only if it was a file. By that we avoid counting/treating folders as files + if ($data['mimetype'] !== 'httpd/unix-directory') { + $this->emit('\OC\Files\Cache\Scanner', 'scanFile', array($file, $this->storageId)); + \OC_Hook::emit('\OC\Files\Cache\Scanner', 'scan_file', array('path' => $file, 'storage' => $this->storageId)); + } + + $parent = dirname($file); + if ($parent === '.' or $parent === '/') { + $parent = ''; + } + if ($parentId === -1) { + $parentId = $this->cache->getId($parent); + } + + // scan the parent if it's not in the cache (id -1) and the current file is not the root folder + if ($file and $parentId === -1) { + $parentData = $this->scanFile($parent); + $parentId = $parentData['fileid']; + } + if ($parent) { + $data['parent'] = $parentId; + } + if (is_null($cacheData)) { + /** @var CacheEntry $cacheData */ + $cacheData = $this->cache->get($file); + } + if ($cacheData and $reuseExisting and isset($cacheData['fileid'])) { + // prevent empty etag + if (empty($cacheData['etag'])) { + $etag = $data['etag']; + } else { + $etag = $cacheData['etag']; + } + $fileId = $cacheData['fileid']; + $data['fileid'] = $fileId; + // only reuse data if the file hasn't explicitly changed + if (isset($data['storage_mtime']) && isset($cacheData['storage_mtime']) && $data['storage_mtime'] === $cacheData['storage_mtime']) { + $data['mtime'] = $cacheData['mtime']; + if (($reuseExisting & self::REUSE_SIZE) && ($data['size'] === -1)) { + $data['size'] = $cacheData['size']; + } + if ($reuseExisting & self::REUSE_ETAG) { + $data['etag'] = $etag; + } + } + // Only update metadata that has changed + $newData = array_diff_assoc($data, $cacheData->getData()); + } else { + $newData = $data; + $fileId = -1; + } + if (!empty($newData)) { + // Reset the checksum if the data has changed + $newData['checksum'] = ''; + $data['fileid'] = $this->addToCache($file, $newData, $fileId); + } + if (isset($cacheData['size'])) { + $data['oldSize'] = $cacheData['size']; + } else { + $data['oldSize'] = 0; + } + + if (isset($cacheData['encrypted'])) { + $data['encrypted'] = $cacheData['encrypted']; + } + + // post-emit only if it was a file. By that we avoid counting/treating folders as files + if ($data['mimetype'] !== 'httpd/unix-directory') { + $this->emit('\OC\Files\Cache\Scanner', 'postScanFile', array($file, $this->storageId)); + \OC_Hook::emit('\OC\Files\Cache\Scanner', 'post_scan_file', array('path' => $file, 'storage' => $this->storageId)); + } + + } else { + $this->removeFromCache($file); + } + + //release the acquired lock + if ($lock) { + if ($this->storage->instanceOfStorage('\OCP\Files\Storage\ILockingStorage')) { + $this->storage->releaseLock($file, ILockingProvider::LOCK_SHARED, $this->lockingProvider); + } + } + + if ($data && !isset($data['encrypted'])) { + $data['encrypted'] = false; + } + return $data; + } + + return null; + } + + protected function removeFromCache($path) { + \OC_Hook::emit('Scanner', 'removeFromCache', array('file' => $path)); + $this->emit('\OC\Files\Cache\Scanner', 'removeFromCache', array($path)); + if ($this->cacheActive) { + $this->cache->remove($path); + } + } + + /** + * @param string $path + * @param array $data + * @param int $fileId + * @return int the id of the added file + */ + protected function addToCache($path, $data, $fileId = -1) { + \OC_Hook::emit('Scanner', 'addToCache', array('file' => $path, 'data' => $data)); + $this->emit('\OC\Files\Cache\Scanner', 'addToCache', array($path, $this->storageId, $data)); + if ($this->cacheActive) { + if ($fileId !== -1) { + $this->cache->update($fileId, $data); + return $fileId; + } else { + return $this->cache->put($path, $data); + } + } else { + return -1; + } + } + + /** + * @param string $path + * @param array $data + * @param int $fileId + */ + protected function updateCache($path, $data, $fileId = -1) { + \OC_Hook::emit('Scanner', 'addToCache', array('file' => $path, 'data' => $data)); + $this->emit('\OC\Files\Cache\Scanner', 'updateCache', array($path, $this->storageId, $data)); + if ($this->cacheActive) { + if ($fileId !== -1) { + $this->cache->update($fileId, $data); + } else { + $this->cache->put($path, $data); + } + } + } + + /** + * scan a folder and all it's children + * + * @param string $path + * @param bool $recursive + * @param int $reuse + * @param bool $lock set to false to disable getting an additional read lock during scanning + * @return array an array of the meta data of the scanned file or folder + */ + public function scan($path, $recursive = self::SCAN_RECURSIVE, $reuse = -1, $lock = true) { + if ($reuse === -1) { + $reuse = ($recursive === self::SCAN_SHALLOW) ? self::REUSE_ETAG | self::REUSE_SIZE : self::REUSE_ETAG; + } + if ($lock) { + if ($this->storage->instanceOfStorage('\OCP\Files\Storage\ILockingStorage')) { + $this->storage->acquireLock($path, ILockingProvider::LOCK_SHARED, $this->lockingProvider); + } + } + $data = $this->scanFile($path, $reuse, -1, null, $lock); + if ($data and $data['mimetype'] === 'httpd/unix-directory') { + $size = $this->scanChildren($path, $recursive, $reuse, $data, $lock); + $data['size'] = $size; + } + if ($lock) { + if ($this->storage->instanceOfStorage('\OCP\Files\Storage\ILockingStorage')) { + $this->storage->releaseLock($path, ILockingProvider::LOCK_SHARED, $this->lockingProvider); + } + } + return $data; + } + + /** + * Get the children currently in the cache + * + * @param int $folderId + * @return array[] + */ + protected function getExistingChildren($folderId) { + $existingChildren = array(); + $children = $this->cache->getFolderContentsById($folderId); + foreach ($children as $child) { + $existingChildren[$child['name']] = $child; + } + return $existingChildren; + } + + /** + * Get the children from the storage + * + * @param string $folder + * @return string[] + */ + protected function getNewChildren($folder) { + $children = array(); + if ($dh = $this->storage->opendir($folder)) { + if (is_resource($dh)) { + while (($file = readdir($dh)) !== false) { + if (!Filesystem::isIgnoredDir($file)) { + $children[] = $file; + } + } + } + } + return $children; + } + + /** + * scan all the files and folders in a folder + * + * @param string $path + * @param bool $recursive + * @param int $reuse + * @param array $folderData existing cache data for the folder to be scanned + * @param bool $lock set to false to disable getting an additional read lock during scanning + * @return int the size of the scanned folder or -1 if the size is unknown at this stage + */ + protected function scanChildren($path, $recursive = self::SCAN_RECURSIVE, $reuse = -1, $folderData = null, $lock = true) { + if ($reuse === -1) { + $reuse = ($recursive === self::SCAN_SHALLOW) ? self::REUSE_ETAG | self::REUSE_SIZE : self::REUSE_ETAG; + } + $this->emit('\OC\Files\Cache\Scanner', 'scanFolder', array($path, $this->storageId)); + $size = 0; + $childQueue = array(); + if (is_array($folderData) and isset($folderData['fileid'])) { + $folderId = $folderData['fileid']; + } else { + $folderId = $this->cache->getId($path); + } + $existingChildren = $this->getExistingChildren($folderId); + $newChildren = $this->getNewChildren($path); + + if ($this->useTransactions) { + \OC::$server->getDatabaseConnection()->beginTransaction(); + } + $exceptionOccurred = false; + foreach ($newChildren as $file) { + $child = ($path) ? $path . '/' . $file : $file; + try { + $existingData = isset($existingChildren[$file]) ? $existingChildren[$file] : null; + $data = $this->scanFile($child, $reuse, $folderId, $existingData, $lock); + if ($data) { + if ($data['mimetype'] === 'httpd/unix-directory' and $recursive === self::SCAN_RECURSIVE) { + $childQueue[$child] = $data; + } else if ($data['size'] === -1) { + $size = -1; + } else if ($size !== -1) { + $size += $data['size']; + } + } + } catch (\Doctrine\DBAL\DBALException $ex) { + // might happen if inserting duplicate while a scanning + // process is running in parallel + // log and ignore + \OCP\Util::writeLog('core', 'Exception while scanning file "' . $child . '": ' . $ex->getMessage(), \OCP\Util::DEBUG); + $exceptionOccurred = true; + } catch (\OCP\Lock\LockedException $e) { + if ($this->useTransactions) { + \OC::$server->getDatabaseConnection()->rollback(); + } + throw $e; + } + } + $removedChildren = \array_diff(array_keys($existingChildren), $newChildren); + foreach ($removedChildren as $childName) { + $child = ($path) ? $path . '/' . $childName : $childName; + $this->removeFromCache($child); + } + if ($this->useTransactions) { + \OC::$server->getDatabaseConnection()->commit(); + } + if ($exceptionOccurred) { + // It might happen that the parallel scan process has already + // inserted mimetypes but those weren't available yet inside the transaction + // To make sure to have the updated mime types in such cases, + // we reload them here + \OC::$server->getMimeTypeLoader()->reset(); + } + + foreach ($childQueue as $child => $childData) { + $childSize = $this->scanChildren($child, self::SCAN_RECURSIVE, $reuse, $childData, $lock); + if ($childSize === -1) { + $size = -1; + } else if ($size !== -1) { + $size += $childSize; + } + } + if (!is_array($folderData) or !isset($folderData['size']) or $folderData['size'] !== $size) { + $this->updateCache($path, array('size' => $size), $folderId); + } + $this->emit('\OC\Files\Cache\Scanner', 'postScanFolder', array($path, $this->storageId)); + return $size; + } + + /** + * check if the file should be ignored when scanning + * NOTE: files with a '.part' extension are ignored as well! + * prevents unfinished put requests to be scanned + * + * @param string $file + * @return boolean + */ + public static function isPartialFile($file) { + if (pathinfo($file, PATHINFO_EXTENSION) === 'part') { + return true; + } + if (strpos($file, '.part/') !== false) { + return true; + } + + return false; + } + + /** + * walk over any folders that are not fully scanned yet and scan them + */ + public function backgroundScan() { + if (!$this->cache->inCache('')) { + $this->runBackgroundScanJob(function () { + $this->scan('', self::SCAN_RECURSIVE, self::REUSE_ETAG); + }, ''); + } else { + $lastPath = null; + while (($path = $this->cache->getIncomplete()) !== false && $path !== $lastPath) { + $this->runBackgroundScanJob(function() use ($path) { + $this->scan($path, self::SCAN_RECURSIVE, self::REUSE_ETAG); + }, $path); + // FIXME: this won't proceed with the next item, needs revamping of getIncomplete() + // to make this possible + $lastPath = $path; + } + } + } + + private function runBackgroundScanJob(callable $callback, $path) { + try { + $callback(); + \OC_Hook::emit('Scanner', 'correctFolderSize', array('path' => $path)); + if ($this->cacheActive) { + $this->cache->correctFolderSize($path); + } + } catch (\OCP\Files\StorageInvalidException $e) { + // skip unavailable storages + } catch (\OCP\Files\StorageNotAvailableException $e) { + // skip unavailable storages + } catch (\OCP\Files\ForbiddenException $e) { + // skip forbidden storages + } catch (\OCP\Lock\LockedException $e) { + // skip unavailable storages + } + } + + /** + * Set whether the cache is affected by scan operations + * + * @param boolean $active The active state of the cache + */ + public function setCacheActive($active) { + $this->cacheActive = $active; + } +} diff --git a/lib/private/Files/Cache/Storage.php b/lib/private/Files/Cache/Storage.php new file mode 100644 index 00000000000..90c451ecc21 --- /dev/null +++ b/lib/private/Files/Cache/Storage.php @@ -0,0 +1,189 @@ + + * @author Jörn Friedrich Dreyer + * @author Morris Jobke + * @author Robin Appelman + * @author Robin McCorkell + * @author Thomas Müller + * @author Vincent Petry + * + * @copyright Copyright (c) 2016, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Files\Cache; + +/** + * Handle the mapping between the string and numeric storage ids + * + * Each storage has 2 different ids + * a string id which is generated by the storage backend and reflects the configuration of the storage (e.g. 'smb://user@host/share') + * and a numeric storage id which is referenced in the file cache + * + * A mapping between the two storage ids is stored in the database and accessible trough this class + * + * @package OC\Files\Cache + */ +class Storage { + private $storageId; + private $numericId; + + /** + * @param \OC\Files\Storage\Storage|string $storage + * @param bool $isAvailable + * @throws \RuntimeException + */ + public function __construct($storage, $isAvailable = true) { + if ($storage instanceof \OC\Files\Storage\Storage) { + $this->storageId = $storage->getId(); + } else { + $this->storageId = $storage; + } + $this->storageId = self::adjustStorageId($this->storageId); + + if ($row = self::getStorageById($this->storageId)) { + $this->numericId = $row['numeric_id']; + } else { + $connection = \OC::$server->getDatabaseConnection(); + $available = $isAvailable ? 1 : 0; + if ($connection->insertIfNotExist('*PREFIX*storages', ['id' => $this->storageId, 'available' => $available])) { + $this->numericId = $connection->lastInsertId('*PREFIX*storages'); + } else { + if ($row = self::getStorageById($this->storageId)) { + $this->numericId = $row['numeric_id']; + } else { + throw new \RuntimeException('Storage could neither be inserted nor be selected from the database'); + } + } + } + } + + /** + * @param string $storageId + * @return array|null + */ + public static function getStorageById($storageId) { + $sql = 'SELECT * FROM `*PREFIX*storages` WHERE `id` = ?'; + $result = \OC_DB::executeAudited($sql, array($storageId)); + return $result->fetchRow(); + } + + /** + * Adjusts the storage id to use md5 if too long + * @param string $storageId storage id + * @return string unchanged $storageId if its length is less than 64 characters, + * else returns the md5 of $storageId + */ + public static function adjustStorageId($storageId) { + if (strlen($storageId) > 64) { + return md5($storageId); + } + return $storageId; + } + + /** + * Get the numeric id for the storage + * + * @return int + */ + public function getNumericId() { + return $this->numericId; + } + + /** + * Get the string id for the storage + * + * @param int $numericId + * @return string|null either the storage id string or null if the numeric id is not known + */ + public static function getStorageId($numericId) { + + $sql = 'SELECT `id` FROM `*PREFIX*storages` WHERE `numeric_id` = ?'; + $result = \OC_DB::executeAudited($sql, array($numericId)); + if ($row = $result->fetchRow()) { + return $row['id']; + } else { + return null; + } + } + + /** + * Get the numeric of the storage with the provided string id + * + * @param $storageId + * @return int|null either the numeric storage id or null if the storage id is not knwon + */ + public static function getNumericStorageId($storageId) { + $storageId = self::adjustStorageId($storageId); + + if ($row = self::getStorageById($storageId)) { + return $row['numeric_id']; + } else { + return null; + } + } + + /** + * @return array|null [ available, last_checked ] + */ + public function getAvailability() { + if ($row = self::getStorageById($this->storageId)) { + return [ + 'available' => ((int)$row['available'] === 1), + 'last_checked' => $row['last_checked'] + ]; + } else { + return null; + } + } + + /** + * @param bool $isAvailable + */ + public function setAvailability($isAvailable) { + $sql = 'UPDATE `*PREFIX*storages` SET `available` = ?, `last_checked` = ? WHERE `id` = ?'; + $available = $isAvailable ? 1 : 0; + \OC_DB::executeAudited($sql, array($available, time(), $this->storageId)); + } + + /** + * Check if a string storage id is known + * + * @param string $storageId + * @return bool + */ + public static function exists($storageId) { + return !is_null(self::getNumericStorageId($storageId)); + } + + /** + * remove the entry for the storage + * + * @param string $storageId + */ + public static function remove($storageId) { + $storageId = self::adjustStorageId($storageId); + $numericId = self::getNumericStorageId($storageId); + $sql = 'DELETE FROM `*PREFIX*storages` WHERE `id` = ?'; + \OC_DB::executeAudited($sql, array($storageId)); + + if (!is_null($numericId)) { + $sql = 'DELETE FROM `*PREFIX*filecache` WHERE `storage` = ?'; + \OC_DB::executeAudited($sql, array($numericId)); + } + } +} diff --git a/lib/private/Files/Cache/Updater.php b/lib/private/Files/Cache/Updater.php new file mode 100644 index 00000000000..3f80f2b6167 --- /dev/null +++ b/lib/private/Files/Cache/Updater.php @@ -0,0 +1,228 @@ + + * @author Michael Gapczynski + * @author Morris Jobke + * @author Robin Appelman + * @author Vincent Petry + * + * @copyright Copyright (c) 2016, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Files\Cache; +use OCP\Files\Cache\IUpdater; +use OCP\Files\Storage\IStorage; + +/** + * Update the cache and propagate changes + * + */ +class Updater implements IUpdater { + /** + * @var bool + */ + protected $enabled = true; + + /** + * @var \OC\Files\Storage\Storage + */ + protected $storage; + + /** + * @var \OC\Files\Cache\Propagator + */ + protected $propagator; + + /** + * @var Scanner + */ + protected $scanner; + + /** + * @var Cache + */ + protected $cache; + + /** + * @param \OC\Files\Storage\Storage $storage + */ + public function __construct(\OC\Files\Storage\Storage $storage) { + $this->storage = $storage; + $this->propagator = $storage->getPropagator(); + $this->scanner = $storage->getScanner(); + $this->cache = $storage->getCache(); + } + + /** + * Disable updating the cache trough this updater + */ + public function disable() { + $this->enabled = false; + } + + /** + * Re-enable the updating of the cache trough this updater + */ + public function enable() { + $this->enabled = true; + } + + /** + * Get the propagator for etags and mtime for the view the updater works on + * + * @return Propagator + */ + public function getPropagator() { + return $this->propagator; + } + + /** + * Propagate etag and mtime changes for the parent folders of $path up to the root of the filesystem + * + * @param string $path the path of the file to propagate the changes for + * @param int|null $time the timestamp to set as mtime for the parent folders, if left out the current time is used + */ + public function propagate($path, $time = null) { + if (Scanner::isPartialFile($path)) { + return; + } + $this->propagator->propagateChange($path, $time); + } + + /** + * Update the cache for $path and update the size, etag and mtime of the parent folders + * + * @param string $path + * @param int $time + */ + public function update($path, $time = null) { + if (!$this->enabled or Scanner::isPartialFile($path)) { + return; + } + if (is_null($time)) { + $time = time(); + } + + $data = $this->scanner->scan($path, Scanner::SCAN_SHALLOW, -1, false); + if ( + isset($data['oldSize']) && isset($data['size']) && + !$data['encrypted'] // encryption is a pita and touches the cache itself + ) { + $sizeDifference = $data['size'] - $data['oldSize']; + } else { + // scanner didn't provide size info, fallback to full size calculation + $sizeDifference = 0; + $this->cache->correctFolderSize($path, $data); + } + $this->correctParentStorageMtime($path); + $this->propagator->propagateChange($path, $time, $sizeDifference); + } + + /** + * Remove $path from the cache and update the size, etag and mtime of the parent folders + * + * @param string $path + */ + public function remove($path) { + if (!$this->enabled or Scanner::isPartialFile($path)) { + return; + } + + $parent = dirname($path); + if ($parent === '.') { + $parent = ''; + } + + $this->cache->remove($path); + $this->cache->correctFolderSize($parent); + $this->correctParentStorageMtime($path); + $this->propagator->propagateChange($path, time()); + } + + /** + * Rename a file or folder in the cache and update the size, etag and mtime of the parent folders + * + * @param IStorage $sourceStorage + * @param string $source + * @param string $target + */ + public function renameFromStorage(IStorage $sourceStorage, $source, $target) { + if (!$this->enabled or Scanner::isPartialFile($source) or Scanner::isPartialFile($target)) { + return; + } + + $time = time(); + + $sourceCache = $sourceStorage->getCache(); + $sourceUpdater = $sourceStorage->getUpdater(); + $sourcePropagator = $sourceStorage->getPropagator(); + + if ($sourceCache->inCache($source)) { + if ($this->cache->inCache($target)) { + $this->cache->remove($target); + } + + if ($sourceStorage === $this->storage) { + $this->cache->move($source, $target); + } else { + $this->cache->moveFromCache($sourceCache, $source, $target); + } + } + + if (pathinfo($source, PATHINFO_EXTENSION) !== pathinfo($target, PATHINFO_EXTENSION)) { + // handle mime type change + $mimeType = $this->storage->getMimeType($target); + $fileId = $this->cache->getId($target); + $this->cache->update($fileId, ['mimetype' => $mimeType]); + } + + $sourceCache->correctFolderSize($source); + $this->cache->correctFolderSize($target); + if ($sourceUpdater instanceof Updater) { + $sourceUpdater->correctParentStorageMtime($source); + } + $this->correctParentStorageMtime($target); + $this->updateStorageMTimeOnly($target); + $sourcePropagator->propagateChange($source, $time); + $this->propagator->propagateChange($target, $time); + } + + private function updateStorageMTimeOnly($internalPath) { + $fileId = $this->cache->getId($internalPath); + if ($fileId !== -1) { + $this->cache->update( + $fileId, [ + 'mtime' => null, // this magic tells it to not overwrite mtime + 'storage_mtime' => $this->storage->filemtime($internalPath) + ] + ); + } + } + + /** + * update the storage_mtime of the direct parent in the cache to the mtime from the storage + * + * @param string $internalPath + */ + private function correctParentStorageMtime($internalPath) { + $parentId = $this->cache->getParentId($internalPath); + $parent = dirname($internalPath); + if ($parentId != -1) { + $this->cache->update($parentId, array('storage_mtime' => $this->storage->filemtime($parent))); + } + } +} diff --git a/lib/private/Files/Cache/Watcher.php b/lib/private/Files/Cache/Watcher.php new file mode 100644 index 00000000000..a00e875a2d4 --- /dev/null +++ b/lib/private/Files/Cache/Watcher.php @@ -0,0 +1,140 @@ + + * @author Robin Appelman + * @author Vincent Petry + * + * @copyright Copyright (c) 2016, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Files\Cache; +use OCP\Files\Cache\ICacheEntry; +use OCP\Files\Cache\IWatcher; + +/** + * check the storage backends for updates and change the cache accordingly + */ +class Watcher implements IWatcher { + + protected $watchPolicy = self::CHECK_ONCE; + + protected $checkedPaths = array(); + + /** + * @var \OC\Files\Storage\Storage $storage + */ + protected $storage; + + /** + * @var Cache $cache + */ + protected $cache; + + /** + * @var Scanner $scanner ; + */ + protected $scanner; + + /** + * @param \OC\Files\Storage\Storage $storage + */ + public function __construct(\OC\Files\Storage\Storage $storage) { + $this->storage = $storage; + $this->cache = $storage->getCache(); + $this->scanner = $storage->getScanner(); + } + + /** + * @param int $policy either \OC\Files\Cache\Watcher::CHECK_NEVER, \OC\Files\Cache\Watcher::CHECK_ONCE, \OC\Files\Cache\Watcher::CHECK_ALWAYS + */ + public function setPolicy($policy) { + $this->watchPolicy = $policy; + } + + /** + * @return int either \OC\Files\Cache\Watcher::CHECK_NEVER, \OC\Files\Cache\Watcher::CHECK_ONCE, \OC\Files\Cache\Watcher::CHECK_ALWAYS + */ + public function getPolicy() { + return $this->watchPolicy; + } + + /** + * check $path for updates and update if needed + * + * @param string $path + * @param ICacheEntry|null $cachedEntry + * @return boolean true if path was updated + */ + public function checkUpdate($path, $cachedEntry = null) { + if (is_null($cachedEntry)) { + $cachedEntry = $this->cache->get($path); + } + if ($this->needsUpdate($path, $cachedEntry)) { + $this->update($path, $cachedEntry); + return true; + } else { + return false; + } + } + + /** + * Update the cache for changes to $path + * + * @param string $path + * @param ICacheEntry $cachedData + */ + public function update($path, $cachedData) { + if ($this->storage->is_dir($path)) { + $this->scanner->scan($path, Scanner::SCAN_SHALLOW); + } else { + $this->scanner->scanFile($path); + } + if ($cachedData['mimetype'] === 'httpd/unix-directory') { + $this->cleanFolder($path); + } + $this->cache->correctFolderSize($path); + } + + /** + * Check if the cache for $path needs to be updated + * + * @param string $path + * @param ICacheEntry $cachedData + * @return bool + */ + public function needsUpdate($path, $cachedData) { + if ($this->watchPolicy === self::CHECK_ALWAYS or ($this->watchPolicy === self::CHECK_ONCE and array_search($path, $this->checkedPaths) === false)) { + $this->checkedPaths[] = $path; + return $this->storage->hasUpdated($path, $cachedData['storage_mtime']); + } + return false; + } + + /** + * remove deleted files in $path from the cache + * + * @param string $path + */ + public function cleanFolder($path) { + $cachedContent = $this->cache->getFolderContents($path); + foreach ($cachedContent as $entry) { + if (!$this->storage->file_exists($entry['path'])) { + $this->cache->remove($entry['path']); + } + } + } +} diff --git a/lib/private/Files/Cache/Wrapper/CacheJail.php b/lib/private/Files/Cache/Wrapper/CacheJail.php new file mode 100644 index 00000000000..88b0f23a1fc --- /dev/null +++ b/lib/private/Files/Cache/Wrapper/CacheJail.php @@ -0,0 +1,300 @@ + + * @author Robin Appelman + * @author Robin McCorkell + * @author Vincent Petry + * + * @copyright Copyright (c) 2016, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Files\Cache\Wrapper; + +/** + * Jail to a subdirectory of the wrapped cache + */ +class CacheJail extends CacheWrapper { + /** + * @var string + */ + protected $root; + + /** + * @param \OCP\Files\Cache\ICache $cache + * @param string $root + */ + public function __construct($cache, $root) { + parent::__construct($cache); + $this->root = $root; + } + + protected function getSourcePath($path) { + if ($path === '') { + return $this->root; + } else { + return $this->root . '/' . ltrim($path, '/'); + } + } + + /** + * @param string $path + * @return null|string the jailed path or null if the path is outside the jail + */ + protected function getJailedPath($path) { + $rootLength = strlen($this->root) + 1; + if ($path === $this->root) { + return ''; + } else if (substr($path, 0, $rootLength) === $this->root . '/') { + return substr($path, $rootLength); + } else { + return null; + } + } + + /** + * @param array $entry + * @return array + */ + protected function formatCacheEntry($entry) { + if (isset($entry['path'])) { + $entry['path'] = $this->getJailedPath($entry['path']); + } + return $entry; + } + + protected function filterCacheEntry($entry) { + $rootLength = strlen($this->root) + 1; + return ($entry['path'] === $this->root) or (substr($entry['path'], 0, $rootLength) === $this->root . '/'); + } + + /** + * get the stored metadata of a file or folder + * + * @param string /int $file + * @return array|false + */ + public function get($file) { + if (is_string($file) or $file == '') { + $file = $this->getSourcePath($file); + } + return parent::get($file); + } + + /** + * insert meta data for a new file or folder + * + * @param string $file + * @param array $data + * + * @return int file id + * @throws \RuntimeException + */ + public function insert($file, array $data) { + return $this->cache->insert($this->getSourcePath($file), $data); + } + + /** + * update the metadata in the cache + * + * @param int $id + * @param array $data + */ + public function update($id, array $data) { + $this->cache->update($id, $data); + } + + /** + * get the file id for a file + * + * @param string $file + * @return int + */ + public function getId($file) { + return $this->cache->getId($this->getSourcePath($file)); + } + + /** + * get the id of the parent folder of a file + * + * @param string $file + * @return int + */ + public function getParentId($file) { + if ($file === '') { + return -1; + } else { + return $this->cache->getParentId($this->getSourcePath($file)); + } + } + + /** + * check if a file is available in the cache + * + * @param string $file + * @return bool + */ + public function inCache($file) { + return $this->cache->inCache($this->getSourcePath($file)); + } + + /** + * remove a file or folder from the cache + * + * @param string $file + */ + public function remove($file) { + $this->cache->remove($this->getSourcePath($file)); + } + + /** + * Move a file or folder in the cache + * + * @param string $source + * @param string $target + */ + public function move($source, $target) { + $this->cache->move($this->getSourcePath($source), $this->getSourcePath($target)); + } + + /** + * remove all entries for files that are stored on the storage from the cache + */ + public function clear() { + $this->cache->remove($this->root); + } + + /** + * @param string $file + * + * @return int Cache::NOT_FOUND, Cache::PARTIAL, Cache::SHALLOW or Cache::COMPLETE + */ + public function getStatus($file) { + return $this->cache->getStatus($this->getSourcePath($file)); + } + + private function formatSearchResults($results) { + $results = array_filter($results, array($this, 'filterCacheEntry')); + $results = array_values($results); + return array_map(array($this, 'formatCacheEntry'), $results); + } + + /** + * search for files matching $pattern + * + * @param string $pattern + * @return array an array of file data + */ + public function search($pattern) { + $results = $this->cache->search($pattern); + return $this->formatSearchResults($results); + } + + /** + * search for files by mimetype + * + * @param string $mimetype + * @return array + */ + public function searchByMime($mimetype) { + $results = $this->cache->searchByMime($mimetype); + return $this->formatSearchResults($results); + } + + /** + * search for files by mimetype + * + * @param string|int $tag name or tag id + * @param string $userId owner of the tags + * @return array + */ + public function searchByTag($tag, $userId) { + $results = $this->cache->searchByTag($tag, $userId); + return $this->formatSearchResults($results); + } + + /** + * update the folder size and the size of all parent folders + * + * @param string|boolean $path + * @param array $data (optional) meta data of the folder + */ + public function correctFolderSize($path, $data = null) { + $this->cache->correctFolderSize($this->getSourcePath($path), $data); + } + + /** + * get the size of a folder and set it in the cache + * + * @param string $path + * @param array $entry (optional) meta data of the folder + * @return int + */ + public function calculateFolderSize($path, $entry = null) { + return $this->cache->calculateFolderSize($this->getSourcePath($path), $entry); + } + + /** + * get all file ids on the files on the storage + * + * @return int[] + */ + public function getAll() { + // not supported + return array(); + } + + /** + * find a folder in the cache which has not been fully scanned + * + * If multiply incomplete folders are in the cache, the one with the highest id will be returned, + * use the one with the highest id gives the best result with the background scanner, since that is most + * likely the folder where we stopped scanning previously + * + * @return string|bool the path of the folder or false when no folder matched + */ + public function getIncomplete() { + // not supported + return false; + } + + /** + * get the path of a file on this storage by it's id + * + * @param int $id + * @return string|null + */ + public function getPathById($id) { + $path = $this->cache->getPathById($id); + return $this->getJailedPath($path); + } + + /** + * Move a file or folder in the cache + * + * Note that this should make sure the entries are removed from the source cache + * + * @param \OCP\Files\Cache\ICache $sourceCache + * @param string $sourcePath + * @param string $targetPath + */ + public function moveFromCache(\OCP\Files\Cache\ICache $sourceCache, $sourcePath, $targetPath) { + if ($sourceCache === $this) { + return $this->move($sourcePath, $targetPath); + } + return $this->cache->moveFromCache($sourceCache, $sourcePath, $targetPath); + } +} diff --git a/lib/private/Files/Cache/Wrapper/CachePermissionsMask.php b/lib/private/Files/Cache/Wrapper/CachePermissionsMask.php new file mode 100644 index 00000000000..b3a7bcb3a73 --- /dev/null +++ b/lib/private/Files/Cache/Wrapper/CachePermissionsMask.php @@ -0,0 +1,46 @@ + + * @author Robin Appelman + * + * @copyright Copyright (c) 2016, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Files\Cache\Wrapper; + +class CachePermissionsMask extends CacheWrapper { + /** + * @var int + */ + protected $mask; + + /** + * @param \OCP\Files\Cache\ICache $cache + * @param int $mask + */ + public function __construct($cache, $mask) { + parent::__construct($cache); + $this->mask = $mask; + } + + protected function formatCacheEntry($entry) { + if (isset($entry['permissions'])) { + $entry['permissions'] &= $this->mask; + } + return $entry; + } +} diff --git a/lib/private/Files/Cache/Wrapper/CacheWrapper.php b/lib/private/Files/Cache/Wrapper/CacheWrapper.php new file mode 100644 index 00000000000..8c77e3c340e --- /dev/null +++ b/lib/private/Files/Cache/Wrapper/CacheWrapper.php @@ -0,0 +1,309 @@ + + * @author Robin Appelman + * @author Robin McCorkell + * @author Vincent Petry + * + * @copyright Copyright (c) 2016, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Files\Cache\Wrapper; + +use OC\Files\Cache\Cache; +use OCP\Files\Cache\ICacheEntry; +use OCP\Files\Cache\ICache; + +class CacheWrapper extends Cache { + /** + * @var \OCP\Files\Cache\ICache + */ + protected $cache; + + /** + * @param \OCP\Files\Cache\ICache $cache + */ + public function __construct($cache) { + $this->cache = $cache; + } + + /** + * Make it easy for wrappers to modify every returned cache entry + * + * @param ICacheEntry $entry + * @return ICacheEntry + */ + protected function formatCacheEntry($entry) { + return $entry; + } + + /** + * get the stored metadata of a file or folder + * + * @param string|int $file + * @return ICacheEntry|false + */ + public function get($file) { + $result = $this->cache->get($file); + if ($result) { + $result = $this->formatCacheEntry($result); + } + return $result; + } + + /** + * get the metadata of all files stored in $folder + * + * @param string $folder + * @return ICacheEntry[] + */ + public function getFolderContents($folder) { + // can't do a simple $this->cache->.... call here since getFolderContentsById needs to be called on this + // and not the wrapped cache + $fileId = $this->getId($folder); + return $this->getFolderContentsById($fileId); + } + + /** + * get the metadata of all files stored in $folder + * + * @param int $fileId the file id of the folder + * @return array + */ + public function getFolderContentsById($fileId) { + $results = $this->cache->getFolderContentsById($fileId); + return array_map(array($this, 'formatCacheEntry'), $results); + } + + /** + * insert or update meta data for a file or folder + * + * @param string $file + * @param array $data + * + * @return int file id + * @throws \RuntimeException + */ + public function put($file, array $data) { + if (($id = $this->getId($file)) > -1) { + $this->update($id, $data); + return $id; + } else { + return $this->insert($file, $data); + } + } + + /** + * insert meta data for a new file or folder + * + * @param string $file + * @param array $data + * + * @return int file id + * @throws \RuntimeException + */ + public function insert($file, array $data) { + return $this->cache->insert($file, $data); + } + + /** + * update the metadata in the cache + * + * @param int $id + * @param array $data + */ + public function update($id, array $data) { + $this->cache->update($id, $data); + } + + /** + * get the file id for a file + * + * @param string $file + * @return int + */ + public function getId($file) { + return $this->cache->getId($file); + } + + /** + * get the id of the parent folder of a file + * + * @param string $file + * @return int + */ + public function getParentId($file) { + return $this->cache->getParentId($file); + } + + /** + * check if a file is available in the cache + * + * @param string $file + * @return bool + */ + public function inCache($file) { + return $this->cache->inCache($file); + } + + /** + * remove a file or folder from the cache + * + * @param string $file + */ + public function remove($file) { + $this->cache->remove($file); + } + + /** + * Move a file or folder in the cache + * + * @param string $source + * @param string $target + */ + public function move($source, $target) { + $this->cache->move($source, $target); + } + + public function moveFromCache(ICache $sourceCache, $sourcePath, $targetPath) { + $this->cache->moveFromCache($sourceCache, $sourcePath, $targetPath); + } + + /** + * remove all entries for files that are stored on the storage from the cache + */ + public function clear() { + $this->cache->clear(); + } + + /** + * @param string $file + * + * @return int Cache::NOT_FOUND, Cache::PARTIAL, Cache::SHALLOW or Cache::COMPLETE + */ + public function getStatus($file) { + return $this->cache->getStatus($file); + } + + /** + * search for files matching $pattern + * + * @param string $pattern + * @return ICacheEntry[] an array of file data + */ + public function search($pattern) { + $results = $this->cache->search($pattern); + return array_map(array($this, 'formatCacheEntry'), $results); + } + + /** + * search for files by mimetype + * + * @param string $mimetype + * @return ICacheEntry[] + */ + public function searchByMime($mimetype) { + $results = $this->cache->searchByMime($mimetype); + return array_map(array($this, 'formatCacheEntry'), $results); + } + + /** + * search for files by tag + * + * @param string|int $tag name or tag id + * @param string $userId owner of the tags + * @return ICacheEntry[] file data + */ + public function searchByTag($tag, $userId) { + $results = $this->cache->searchByTag($tag, $userId); + return array_map(array($this, 'formatCacheEntry'), $results); + } + + /** + * update the folder size and the size of all parent folders + * + * @param string|boolean $path + * @param array $data (optional) meta data of the folder + */ + public function correctFolderSize($path, $data = null) { + $this->cache->correctFolderSize($path, $data); + } + + /** + * get the size of a folder and set it in the cache + * + * @param string $path + * @param array $entry (optional) meta data of the folder + * @return int + */ + public function calculateFolderSize($path, $entry = null) { + return $this->cache->calculateFolderSize($path, $entry); + } + + /** + * get all file ids on the files on the storage + * + * @return int[] + */ + public function getAll() { + return $this->cache->getAll(); + } + + /** + * find a folder in the cache which has not been fully scanned + * + * If multiple incomplete folders are in the cache, the one with the highest id will be returned, + * use the one with the highest id gives the best result with the background scanner, since that is most + * likely the folder where we stopped scanning previously + * + * @return string|bool the path of the folder or false when no folder matched + */ + public function getIncomplete() { + return $this->cache->getIncomplete(); + } + + /** + * get the path of a file on this storage by it's id + * + * @param int $id + * @return string|null + */ + public function getPathById($id) { + return $this->cache->getPathById($id); + } + + /** + * Returns the numeric storage id + * + * @return int + */ + public function getNumericStorageId() { + return $this->cache->getNumericStorageId(); + } + + /** + * get the storage id of the storage for a file and the internal path of the file + * unlike getPathById this does not limit the search to files on this storage and + * instead does a global search in the cache table + * + * @param int $id + * @return array first element holding the storage id, second the path + */ + static public function getById($id) { + return parent::getById($id); + } +} diff --git a/lib/private/Files/Config/CachedMountInfo.php b/lib/private/Files/Config/CachedMountInfo.php new file mode 100644 index 00000000000..ce75cb66896 --- /dev/null +++ b/lib/private/Files/Config/CachedMountInfo.php @@ -0,0 +1,107 @@ + + * + * @copyright Copyright (c) 2016, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Files\Config; + +use OC\Files\Filesystem; +use OCP\Files\Config\ICachedMountInfo; +use OCP\Files\Node; +use OCP\IUser; + +class CachedMountInfo implements ICachedMountInfo { + /** + * @var IUser + */ + protected $user; + + /** + * @var int + */ + protected $storageId; + + /** + * @var int + */ + protected $rootId; + + /** + * @var string + */ + protected $mountPoint; + + /** + * CachedMountInfo constructor. + * + * @param IUser $user + * @param int $storageId + * @param int $rootId + * @param string $mountPoint + */ + public function __construct(IUser $user, $storageId, $rootId, $mountPoint) { + $this->user = $user; + $this->storageId = $storageId; + $this->rootId = $rootId; + $this->mountPoint = $mountPoint; + } + + /** + * @return IUser + */ + public function getUser() { + return $this->user; + } + + /** + * @return int the numeric storage id of the mount + */ + public function getStorageId() { + return $this->storageId; + } + + /** + * @return int the fileid of the root of the mount + */ + public function getRootId() { + return $this->rootId; + } + + /** + * @return Node the root node of the mount + */ + public function getMountPointNode() { + // TODO injection etc + Filesystem::initMountPoints($this->getUser()->getUID()); + $userNode = \OC::$server->getUserFolder($this->getUser()->getUID()); + $nodes = $userNode->getById($this->getRootId()); + if (count($nodes) > 0) { + return $nodes[0]; + } else { + return null; + } + } + + /** + * @return string the mount point of the mount for the user + */ + public function getMountPoint() { + return $this->mountPoint; + } +} diff --git a/lib/private/Files/Config/LazyStorageMountInfo.php b/lib/private/Files/Config/LazyStorageMountInfo.php new file mode 100644 index 00000000000..654c5b2b23e --- /dev/null +++ b/lib/private/Files/Config/LazyStorageMountInfo.php @@ -0,0 +1,74 @@ + + * + * @copyright Copyright (c) 2016, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Files\Config; + +use OC\Files\Filesystem; +use OCP\Files\Config\ICachedMountInfo; +use OCP\Files\Mount\IMountPoint; +use OCP\Files\Node; +use OCP\IUser; + +class LazyStorageMountInfo extends CachedMountInfo { + /** @var IMountPoint */ + private $mount; + + /** + * CachedMountInfo constructor. + * + * @param IUser $user + * @param IMountPoint $mount + */ + public function __construct(IUser $user, IMountPoint $mount) { + $this->user = $user; + $this->mount = $mount; + } + + /** + * @return int the numeric storage id of the mount + */ + public function getStorageId() { + if (!$this->storageId) { + $this->storageId = $this->mount->getStorage()->getStorageCache()->getNumericId(); + } + return parent::getStorageId(); + } + + /** + * @return int the fileid of the root of the mount + */ + public function getRootId() { + if (!$this->rootId) { + $this->rootId = $this->mount->getStorageRootId(); + } + return parent::getRootId(); + } + + /** + * @return string the mount point of the mount for the user + */ + public function getMountPoint() { + if (!$this->mountPoint) { + $this->mountPoint = $this->mount->getMountPoint(); + } + return parent::getMountPoint(); + } +} diff --git a/lib/private/Files/Config/MountProviderCollection.php b/lib/private/Files/Config/MountProviderCollection.php new file mode 100644 index 00000000000..499fa576fbc --- /dev/null +++ b/lib/private/Files/Config/MountProviderCollection.php @@ -0,0 +1,108 @@ + + * @author Robin Appelman + * + * @copyright Copyright (c) 2016, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Files\Config; + +use OC\Hooks\Emitter; +use OC\Hooks\EmitterTrait; +use OCP\Files\Config\IMountProviderCollection; +use OCP\Files\Config\IMountProvider; +use OCP\Files\Config\IUserMountCache; +use OCP\Files\Mount\IMountPoint; +use OCP\Files\Storage\IStorageFactory; +use OCP\IUser; + +class MountProviderCollection implements IMountProviderCollection, Emitter { + use EmitterTrait; + + /** + * @var \OCP\Files\Config\IMountProvider[] + */ + private $providers = array(); + + /** + * @var \OCP\Files\Storage\IStorageFactory + */ + private $loader; + + /** + * @var \OCP\Files\Config\IUserMountCache + */ + private $mountCache; + + /** + * @param \OCP\Files\Storage\IStorageFactory $loader + * @param IUserMountCache $mountCache + */ + public function __construct(IStorageFactory $loader, IUserMountCache $mountCache) { + $this->loader = $loader; + $this->mountCache = $mountCache; + } + + /** + * Get all configured mount points for the user + * + * @param \OCP\IUser $user + * @return \OCP\Files\Mount\IMountPoint[] + */ + public function getMountsForUser(IUser $user) { + $loader = $this->loader; + $mounts = array_map(function (IMountProvider $provider) use ($user, $loader) { + return $provider->getMountsForUser($user, $loader); + }, $this->providers); + $mounts = array_filter($mounts, function ($result) { + return is_array($result); + }); + return array_reduce($mounts, function (array $mounts, array $providerMounts) { + return array_merge($mounts, $providerMounts); + }, array()); + } + + /** + * Add a provider for mount points + * + * @param \OCP\Files\Config\IMountProvider $provider + */ + public function registerProvider(IMountProvider $provider) { + $this->providers[] = $provider; + $this->emit('\OC\Files\Config', 'registerMountProvider', [$provider]); + } + + /** + * Cache mounts for user + * + * @param IUser $user + * @param IMountPoint[] $mountPoints + */ + public function registerMounts(IUser $user, array $mountPoints) { + $this->mountCache->registerMounts($user, $mountPoints); + } + + /** + * Get the mount cache which can be used to search for mounts without setting up the filesystem + * + * @return IUserMountCache + */ + public function getMountCache() { + return $this->mountCache; + } +} diff --git a/lib/private/Files/Config/UserMountCache.php b/lib/private/Files/Config/UserMountCache.php new file mode 100644 index 00000000000..05ca146f4be --- /dev/null +++ b/lib/private/Files/Config/UserMountCache.php @@ -0,0 +1,290 @@ + + * @author Robin Appelman + * + * @copyright Copyright (c) 2016, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Files\Config; + +use Doctrine\DBAL\Exception\UniqueConstraintViolationException; +use OC\Files\Filesystem; +use OCA\Files_Sharing\SharedMount; +use OCP\DB\QueryBuilder\IQueryBuilder; +use OCP\Files\Config\ICachedMountInfo; +use OCP\Files\Config\IUserMountCache; +use OCP\Files\Mount\IMountPoint; +use OCP\Files\NotFoundException; +use OCP\ICache; +use OCP\IDBConnection; +use OCP\ILogger; +use OCP\IUser; +use OCP\IUserManager; + +/** + * Cache mounts points per user in the cache so we can easilly look them up + */ +class UserMountCache implements IUserMountCache { + /** + * @var IDBConnection + */ + private $connection; + + /** + * @var IUserManager + */ + private $userManager; + + /** @var ICachedMountInfo[][] [$userId => [$cachedMountInfo, ....], ...] */ + private $mountsForUsers = []; + + /** + * @var ILogger + */ + private $logger; + + private $cacheInfoCache = []; + + /** + * UserMountCache constructor. + * + * @param IDBConnection $connection + * @param IUserManager $userManager + * @param ILogger $logger + */ + public function __construct(IDBConnection $connection, IUserManager $userManager, ILogger $logger) { + $this->connection = $connection; + $this->userManager = $userManager; + $this->logger = $logger; + } + + public function registerMounts(IUser $user, array $mounts) { + // filter out non-proper storages coming from unit tests + $mounts = array_filter($mounts, function (IMountPoint $mount) { + return $mount instanceof SharedMount || $mount->getStorage() && $mount->getStorage()->getCache(); + }); + /** @var ICachedMountInfo[] $newMounts */ + $newMounts = array_map(function (IMountPoint $mount) use ($user) { + // filter out any storages which aren't scanned yet since we aren't interested in files from those storages (yet) + if ($mount->getStorageRootId() === -1) { + return null; + } else { + return new LazyStorageMountInfo($user, $mount); + } + }, $mounts); + $newMounts = array_values(array_filter($newMounts)); + + $cachedMounts = $this->getMountsForUser($user); + $mountDiff = function (ICachedMountInfo $mount1, ICachedMountInfo $mount2) { + // since we are only looking for mounts for a specific user comparing on root id is enough + return $mount1->getRootId() - $mount2->getRootId(); + }; + + /** @var ICachedMountInfo[] $addedMounts */ + $addedMounts = array_udiff($newMounts, $cachedMounts, $mountDiff); + /** @var ICachedMountInfo[] $removedMounts */ + $removedMounts = array_udiff($cachedMounts, $newMounts, $mountDiff); + + $changedMounts = array_uintersect($newMounts, $cachedMounts, function (ICachedMountInfo $mount1, ICachedMountInfo $mount2) { + // filter mounts with the same root id and different mountpoints + if ($mount1->getRootId() !== $mount2->getRootId()) { + return -1; + } + return ($mount1->getMountPoint() !== $mount2->getMountPoint()) ? 0 : 1; + }); + + foreach ($addedMounts as $mount) { + $this->addToCache($mount); + $this->mountsForUsers[$user->getUID()][] = $mount; + } + foreach ($removedMounts as $mount) { + $this->removeFromCache($mount); + $index = array_search($mount, $this->mountsForUsers[$user->getUID()]); + unset($this->mountsForUsers[$user->getUID()][$index]); + } + foreach ($changedMounts as $mount) { + $this->setMountPoint($mount); + } + } + + private function addToCache(ICachedMountInfo $mount) { + $this->connection->insertIfNotExist('*PREFIX*mounts', [ + 'storage_id' => $mount->getStorageId(), + 'root_id' => $mount->getRootId(), + 'user_id' => $mount->getUser()->getUID(), + 'mount_point' => $mount->getMountPoint() + ], ['root_id', 'user_id']); + } + + private function setMountPoint(ICachedMountInfo $mount) { + $builder = $this->connection->getQueryBuilder(); + + $query = $builder->update('mounts') + ->set('mount_point', $builder->createNamedParameter($mount->getMountPoint())) + ->where($builder->expr()->eq('user_id', $builder->createNamedParameter($mount->getUser()->getUID()))) + ->andWhere($builder->expr()->eq('root_id', $builder->createNamedParameter($mount->getRootId(), IQueryBuilder::PARAM_INT))); + + $query->execute(); + } + + private function removeFromCache(ICachedMountInfo $mount) { + $builder = $this->connection->getQueryBuilder(); + + $query = $builder->delete('mounts') + ->where($builder->expr()->eq('user_id', $builder->createNamedParameter($mount->getUser()->getUID()))) + ->andWhere($builder->expr()->eq('root_id', $builder->createNamedParameter($mount->getRootId(), IQueryBuilder::PARAM_INT))); + $query->execute(); + } + + private function dbRowToMountInfo(array $row) { + $user = $this->userManager->get($row['user_id']); + return new CachedMountInfo($user, (int)$row['storage_id'], (int)$row['root_id'], $row['mount_point']); + } + + /** + * @param IUser $user + * @return ICachedMountInfo[] + */ + public function getMountsForUser(IUser $user) { + if (!isset($this->mountsForUsers[$user->getUID()])) { + $builder = $this->connection->getQueryBuilder(); + $query = $builder->select('storage_id', 'root_id', 'user_id', 'mount_point') + ->from('mounts') + ->where($builder->expr()->eq('user_id', $builder->createPositionalParameter($user->getUID()))); + + $rows = $query->execute()->fetchAll(); + + $this->mountsForUsers[$user->getUID()] = array_map([$this, 'dbRowToMountInfo'], $rows); + } + return $this->mountsForUsers[$user->getUID()]; + } + + /** + * @param int $numericStorageId + * @return CachedMountInfo[] + */ + public function getMountsForStorageId($numericStorageId) { + $builder = $this->connection->getQueryBuilder(); + $query = $builder->select('storage_id', 'root_id', 'user_id', 'mount_point') + ->from('mounts') + ->where($builder->expr()->eq('storage_id', $builder->createPositionalParameter($numericStorageId, IQueryBuilder::PARAM_INT))); + + $rows = $query->execute()->fetchAll(); + + return array_map([$this, 'dbRowToMountInfo'], $rows); + } + + /** + * @param int $rootFileId + * @return CachedMountInfo[] + */ + public function getMountsForRootId($rootFileId) { + $builder = $this->connection->getQueryBuilder(); + $query = $builder->select('storage_id', 'root_id', 'user_id', 'mount_point') + ->from('mounts') + ->where($builder->expr()->eq('root_id', $builder->createPositionalParameter($rootFileId, IQueryBuilder::PARAM_INT))); + + $rows = $query->execute()->fetchAll(); + + return array_map([$this, 'dbRowToMountInfo'], $rows); + } + + /** + * @param $fileId + * @return array + * @throws \OCP\Files\NotFoundException + */ + private function getCacheInfoFromFileId($fileId) { + if (!isset($this->cacheInfoCache[$fileId])) { + $builder = $this->connection->getQueryBuilder(); + $query = $builder->select('storage', 'path') + ->from('filecache') + ->where($builder->expr()->eq('fileid', $builder->createNamedParameter($fileId, IQueryBuilder::PARAM_INT))); + + $row = $query->execute()->fetch(); + if (is_array($row)) { + $this->cacheInfoCache[$fileId] = [ + (int)$row['storage'], + $row['path'] + ]; + } else { + throw new NotFoundException('File with id "' . $fileId . '" not found'); + } + } + return $this->cacheInfoCache[$fileId]; + } + + /** + * @param int $fileId + * @return ICachedMountInfo[] + * @since 9.0.0 + */ + public function getMountsForFileId($fileId) { + try { + list($storageId, $internalPath) = $this->getCacheInfoFromFileId($fileId); + } catch (NotFoundException $e) { + return []; + } + $mountsForStorage = $this->getMountsForStorageId($storageId); + + // filter mounts that are from the same storage but a different directory + return array_filter($mountsForStorage, function (ICachedMountInfo $mount) use ($internalPath, $fileId) { + if ($fileId === $mount->getRootId()) { + return true; + } + try { + list(, $internalMountPath) = $this->getCacheInfoFromFileId($mount->getRootId()); + } catch (NotFoundException $e) { + return false; + } + + return $internalMountPath === '' || substr($internalPath, 0, strlen($internalMountPath) + 1) === $internalMountPath . '/'; + }); + + } + + /** + * Remove all cached mounts for a user + * + * @param IUser $user + */ + public function removeUserMounts(IUser $user) { + $builder = $this->connection->getQueryBuilder(); + + $query = $builder->delete('mounts') + ->where($builder->expr()->eq('user_id', $builder->createNamedParameter($user->getUID()))); + $query->execute(); + } + + public function removeUserStorageMount($storageId, $userId) { + $builder = $this->connection->getQueryBuilder(); + + $query = $builder->delete('mounts') + ->where($builder->expr()->eq('user_id', $builder->createNamedParameter($userId))) + ->andWhere($builder->expr()->eq('storage_id', $builder->createNamedParameter($storageId, IQueryBuilder::PARAM_INT))); + $query->execute(); + } + + public function remoteStorageMounts($storageId) { + $builder = $this->connection->getQueryBuilder(); + + $query = $builder->delete('mounts') + ->where($builder->expr()->eq('storage_id', $builder->createNamedParameter($storageId, IQueryBuilder::PARAM_INT))); + $query->execute(); + } +} diff --git a/lib/private/Files/Config/UserMountCacheListener.php b/lib/private/Files/Config/UserMountCacheListener.php new file mode 100644 index 00000000000..99673cf6285 --- /dev/null +++ b/lib/private/Files/Config/UserMountCacheListener.php @@ -0,0 +1,48 @@ + + * + * @copyright Copyright (c) 2016, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Files\Config; + +use OC\User\Manager; +use OCP\Files\Config\IUserMountCache; + +/** + * Listen to hooks and update the mount cache as needed + */ +class UserMountCacheListener { + /** + * @var IUserMountCache + */ + private $userMountCache; + + /** + * UserMountCacheListener constructor. + * + * @param IUserMountCache $userMountCache + */ + public function __construct(IUserMountCache $userMountCache) { + $this->userMountCache = $userMountCache; + } + + public function listen(Manager $manager) { + $manager->listen('\OC\User', 'postDelete', [$this->userMountCache, 'removeUserMounts']); + } +} diff --git a/lib/private/Files/FileInfo.php b/lib/private/Files/FileInfo.php new file mode 100644 index 00000000000..5f32ad34bb3 --- /dev/null +++ b/lib/private/Files/FileInfo.php @@ -0,0 +1,346 @@ + + * @author Lukas Reschke + * @author Morris Jobke + * @author Robin Appelman + * @author Robin McCorkell + * @author Roeland Jago Douma + * @author tbartenstein + * @author Thomas Müller + * @author Vincent Petry + * + * @copyright Copyright (c) 2016, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Files; + +use OCP\Files\Cache\ICacheEntry; +use OCP\IUser; + +class FileInfo implements \OCP\Files\FileInfo, \ArrayAccess { + /** + * @var array $data + */ + private $data; + + /** + * @var string $path + */ + private $path; + + /** + * @var \OC\Files\Storage\Storage $storage + */ + private $storage; + + /** + * @var string $internalPath + */ + private $internalPath; + + /** + * @var \OCP\Files\Mount\IMountPoint + */ + private $mount; + + /** + * @var IUser + */ + private $owner; + + /** + * @var string[] + */ + private $childEtags = []; + + /** + * @param string|boolean $path + * @param Storage\Storage $storage + * @param string $internalPath + * @param array|ICacheEntry $data + * @param \OCP\Files\Mount\IMountPoint $mount + * @param \OCP\IUser|null $owner + */ + public function __construct($path, $storage, $internalPath, $data, $mount, $owner= null) { + $this->path = $path; + $this->storage = $storage; + $this->internalPath = $internalPath; + $this->data = $data; + $this->mount = $mount; + $this->owner = $owner; + } + + public function offsetSet($offset, $value) { + $this->data[$offset] = $value; + } + + public function offsetExists($offset) { + return isset($this->data[$offset]); + } + + public function offsetUnset($offset) { + unset($this->data[$offset]); + } + + public function offsetGet($offset) { + if ($offset === 'type') { + return $this->getType(); + } else if ($offset === 'etag') { + return $this->getEtag(); + } elseif ($offset === 'permissions') { + return $this->getPermissions(); + } elseif (isset($this->data[$offset])) { + return $this->data[$offset]; + } else { + return null; + } + } + + /** + * @return string + */ + public function getPath() { + return $this->path; + } + + /** + * @return \OCP\Files\Storage + */ + public function getStorage() { + return $this->storage; + } + + /** + * @return string + */ + public function getInternalPath() { + return $this->internalPath; + } + + /** + * @return int + */ + public function getId() { + return $this->data['fileid']; + } + + /** + * @return string + */ + public function getMimetype() { + return $this->data['mimetype']; + } + + /** + * @return string + */ + public function getMimePart() { + return $this->data['mimepart']; + } + + /** + * @return string + */ + public function getName() { + return basename($this->getPath()); + } + + /** + * @return string + */ + public function getEtag() { + if (count($this->childEtags) > 0) { + $combinedEtag = $this->data['etag'] . '::' . implode('::', $this->childEtags); + return md5($combinedEtag); + } else { + return $this->data['etag']; + } + } + + /** + * @return int + */ + public function getSize() { + return isset($this->data['size']) ? $this->data['size'] : 0; + } + + /** + * @return int + */ + public function getMTime() { + return $this->data['mtime']; + } + + /** + * @return bool + */ + public function isEncrypted() { + return $this->data['encrypted']; + } + + /** + * Return the currently version used for the HMAC in the encryption app + * + * @return int + */ + public function getEncryptedVersion() { + return isset($this->data['encryptedVersion']) ? (int) $this->data['encryptedVersion'] : 1; + } + + /** + * @return int + */ + public function getPermissions() { + $perms = $this->data['permissions']; + if (\OCP\Util::isSharingDisabledForUser() || ($this->isShared() && !\OC\Share\Share::isResharingAllowed())) { + $perms = $perms & ~\OCP\Constants::PERMISSION_SHARE; + } + return $perms; + } + + /** + * @return \OCP\Files\FileInfo::TYPE_FILE|\OCP\Files\FileInfo::TYPE_FOLDER + */ + public function getType() { + if (!isset($this->data['type'])) { + $this->data['type'] = ($this->getMimetype() === 'httpd/unix-directory') ? self::TYPE_FOLDER : self::TYPE_FILE; + } + return $this->data['type']; + } + + public function getData() { + return $this->data; + } + + /** + * @param int $permissions + * @return bool + */ + protected function checkPermissions($permissions) { + return ($this->getPermissions() & $permissions) === $permissions; + } + + /** + * @return bool + */ + public function isReadable() { + return $this->checkPermissions(\OCP\Constants::PERMISSION_READ); + } + + /** + * @return bool + */ + public function isUpdateable() { + return $this->checkPermissions(\OCP\Constants::PERMISSION_UPDATE); + } + + /** + * Check whether new files or folders can be created inside this folder + * + * @return bool + */ + public function isCreatable() { + return $this->checkPermissions(\OCP\Constants::PERMISSION_CREATE); + } + + /** + * @return bool + */ + public function isDeletable() { + return $this->checkPermissions(\OCP\Constants::PERMISSION_DELETE); + } + + /** + * @return bool + */ + public function isShareable() { + return $this->checkPermissions(\OCP\Constants::PERMISSION_SHARE); + } + + /** + * Check if a file or folder is shared + * + * @return bool + */ + public function isShared() { + $sid = $this->getStorage()->getId(); + if (!is_null($sid)) { + $sid = explode(':', $sid); + return ($sid[0] === 'shared'); + } + + return false; + } + + public function isMounted() { + $sid = $this->getStorage()->getId(); + if (!is_null($sid)) { + $sid = explode(':', $sid); + return ($sid[0] !== 'home' and $sid[0] !== 'shared'); + } + + return false; + } + + /** + * Get the mountpoint the file belongs to + * + * @return \OCP\Files\Mount\IMountPoint + */ + public function getMountPoint() { + return $this->mount; + } + + /** + * Get the owner of the file + * + * @return \OCP\IUser + */ + public function getOwner() { + return $this->owner; + } + + /** + * Add a cache entry which is the child of this folder + * + * Sets the size, etag and size to for cross-storage childs + * + * @param array $data cache entry for the child + * @param string $entryPath full path of the child entry + */ + public function addSubEntry($data, $entryPath) { + $this->data['size'] += isset($data['size']) ? $data['size'] : 0; + if (isset($data['mtime'])) { + $this->data['mtime'] = max($this->data['mtime'], $data['mtime']); + } + if (isset($data['etag'])) { + // prefix the etag with the relative path of the subentry to propagate etag on mount moves + $relativeEntryPath = substr($entryPath, strlen($this->getPath())); + // attach the permissions to propagate etag on permision changes of submounts + $permissions = isset($data['permissions']) ? $data['permissions'] : 0; + $this->childEtags[] = $relativeEntryPath . '/' . $data['etag'] . $permissions; + } + } + + /** + * @inheritdoc + */ + public function getChecksum() { + return $this->data['checksum']; + } +} diff --git a/lib/private/Files/Filesystem.php b/lib/private/Files/Filesystem.php new file mode 100644 index 00000000000..7283c815c97 --- /dev/null +++ b/lib/private/Files/Filesystem.php @@ -0,0 +1,928 @@ + + * @author Bart Visscher + * @author Christopher Schäpers + * @author Florin Peter + * @author Georg Ehrke + * @author Joas Schilling + * @author Jörn Friedrich Dreyer + * @author Lukas Reschke + * @author Michael Gapczynski + * @author Morris Jobke + * @author Robin Appelman + * @author Robin McCorkell + * @author Roeland Jago Douma + * @author Sam Tuke + * @author Stephan Peijnik + * @author Vincent Petry + * + * @copyright Copyright (c) 2016, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +/** + * Class for abstraction of filesystem functions + * This class won't call any filesystem functions for itself but will pass them to the correct OC_Filestorage object + * this class should also handle all the file permission related stuff + * + * Hooks provided: + * read(path) + * write(path, &run) + * post_write(path) + * create(path, &run) (when a file is created, both create and write will be emitted in that order) + * post_create(path) + * delete(path, &run) + * post_delete(path) + * rename(oldpath,newpath, &run) + * post_rename(oldpath,newpath) + * copy(oldpath,newpath, &run) (if the newpath doesn't exists yes, copy, create and write will be emitted in that order) + * post_rename(oldpath,newpath) + * post_initMountPoints(user, user_dir) + * + * the &run parameter can be set to false to prevent the operation from occurring + */ + +namespace OC\Files; + +use OC\Files\Config\MountProviderCollection; +use OC\Files\Mount\MountPoint; +use OC\Files\Storage\StorageFactory; +use OCP\Files\Config\IMountProvider; +use OCP\Files\Mount\IMountPoint; +use OCP\Files\NotFoundException; +use OCP\IUserManager; + +class Filesystem { + + /** + * @var Mount\Manager $mounts + */ + private static $mounts; + + public static $loaded = false; + /** + * @var \OC\Files\View $defaultInstance + */ + static private $defaultInstance; + + static private $usersSetup = array(); + + static private $normalizedPathCache = array(); + + static private $listeningForProviders = false; + + /** + * classname which used for hooks handling + * used as signalclass in OC_Hooks::emit() + */ + const CLASSNAME = 'OC_Filesystem'; + + /** + * signalname emitted before file renaming + * + * @param string $oldpath + * @param string $newpath + */ + const signal_rename = 'rename'; + + /** + * signal emitted after file renaming + * + * @param string $oldpath + * @param string $newpath + */ + const signal_post_rename = 'post_rename'; + + /** + * signal emitted before file/dir creation + * + * @param string $path + * @param bool $run changing this flag to false in hook handler will cancel event + */ + const signal_create = 'create'; + + /** + * signal emitted after file/dir creation + * + * @param string $path + * @param bool $run changing this flag to false in hook handler will cancel event + */ + const signal_post_create = 'post_create'; + + /** + * signal emits before file/dir copy + * + * @param string $oldpath + * @param string $newpath + * @param bool $run changing this flag to false in hook handler will cancel event + */ + const signal_copy = 'copy'; + + /** + * signal emits after file/dir copy + * + * @param string $oldpath + * @param string $newpath + */ + const signal_post_copy = 'post_copy'; + + /** + * signal emits before file/dir save + * + * @param string $path + * @param bool $run changing this flag to false in hook handler will cancel event + */ + const signal_write = 'write'; + + /** + * signal emits after file/dir save + * + * @param string $path + */ + const signal_post_write = 'post_write'; + + /** + * signal emitted before file/dir update + * + * @param string $path + * @param bool $run changing this flag to false in hook handler will cancel event + */ + const signal_update = 'update'; + + /** + * signal emitted after file/dir update + * + * @param string $path + * @param bool $run changing this flag to false in hook handler will cancel event + */ + const signal_post_update = 'post_update'; + + /** + * signal emits when reading file/dir + * + * @param string $path + */ + const signal_read = 'read'; + + /** + * signal emits when removing file/dir + * + * @param string $path + */ + const signal_delete = 'delete'; + + /** + * parameters definitions for signals + */ + const signal_param_path = 'path'; + const signal_param_oldpath = 'oldpath'; + const signal_param_newpath = 'newpath'; + + /** + * run - changing this flag to false in hook handler will cancel event + */ + const signal_param_run = 'run'; + + const signal_create_mount = 'create_mount'; + const signal_delete_mount = 'delete_mount'; + const signal_param_mount_type = 'mounttype'; + const signal_param_users = 'users'; + + /** + * @var \OC\Files\Storage\StorageFactory $loader + */ + private static $loader; + + /** + * @param string $wrapperName + * @param callable $wrapper + * @param int $priority + */ + public static function addStorageWrapper($wrapperName, $wrapper, $priority = 50) { + $mounts = self::getMountManager()->getAll(); + if (!self::getLoader()->addStorageWrapper($wrapperName, $wrapper, $priority, $mounts)) { + // do not re-wrap if storage with this name already existed + return; + } + } + + /** + * Returns the storage factory + * + * @return \OCP\Files\Storage\IStorageFactory + */ + public static function getLoader() { + if (!self::$loader) { + self::$loader = new StorageFactory(); + } + return self::$loader; + } + + /** + * Returns the mount manager + * + * @return \OC\Files\Mount\Manager + */ + public static function getMountManager($user = '') { + if (!self::$mounts) { + \OC_Util::setupFS($user); + } + return self::$mounts; + } + + /** + * get the mountpoint of the storage object for a path + * ( note: because a storage is not always mounted inside the fakeroot, the + * returned mountpoint is relative to the absolute root of the filesystem + * and doesn't take the chroot into account ) + * + * @param string $path + * @return string + */ + static public function getMountPoint($path) { + if (!self::$mounts) { + \OC_Util::setupFS(); + } + $mount = self::$mounts->find($path); + if ($mount) { + return $mount->getMountPoint(); + } else { + return ''; + } + } + + /** + * get a list of all mount points in a directory + * + * @param string $path + * @return string[] + */ + static public function getMountPoints($path) { + if (!self::$mounts) { + \OC_Util::setupFS(); + } + $result = array(); + $mounts = self::$mounts->findIn($path); + foreach ($mounts as $mount) { + $result[] = $mount->getMountPoint(); + } + return $result; + } + + /** + * get the storage mounted at $mountPoint + * + * @param string $mountPoint + * @return \OC\Files\Storage\Storage + */ + public static function getStorage($mountPoint) { + if (!self::$mounts) { + \OC_Util::setupFS(); + } + $mount = self::$mounts->find($mountPoint); + return $mount->getStorage(); + } + + /** + * @param string $id + * @return Mount\MountPoint[] + */ + public static function getMountByStorageId($id) { + if (!self::$mounts) { + \OC_Util::setupFS(); + } + return self::$mounts->findByStorageId($id); + } + + /** + * @param int $id + * @return Mount\MountPoint[] + */ + public static function getMountByNumericId($id) { + if (!self::$mounts) { + \OC_Util::setupFS(); + } + return self::$mounts->findByNumericId($id); + } + + /** + * resolve a path to a storage and internal path + * + * @param string $path + * @return array an array consisting of the storage and the internal path + */ + static public function resolvePath($path) { + if (!self::$mounts) { + \OC_Util::setupFS(); + } + $mount = self::$mounts->find($path); + if ($mount) { + return array($mount->getStorage(), rtrim($mount->getInternalPath($path), '/')); + } else { + return array(null, null); + } + } + + static public function init($user, $root) { + if (self::$defaultInstance) { + return false; + } + self::getLoader(); + self::$defaultInstance = new View($root); + + if (!self::$mounts) { + self::$mounts = \OC::$server->getMountManager(); + } + + //load custom mount config + self::initMountPoints($user); + + self::$loaded = true; + + return true; + } + + static public function initMountManager() { + if (!self::$mounts) { + self::$mounts = \OC::$server->getMountManager(); + } + } + + /** + * Initialize system and personal mount points for a user + * + * @param string $user + * @throws \OC\User\NoUserException if the user is not available + */ + public static function initMountPoints($user = '') { + if ($user == '') { + $user = \OC_User::getUser(); + } + if ($user === null || $user === false || $user === '') { + throw new \OC\User\NoUserException('Attempted to initialize mount points for null user and no user in session'); + } + if (isset(self::$usersSetup[$user])) { + return; + } + $root = \OC_User::getHome($user); + + $userManager = \OC::$server->getUserManager(); + $userObject = $userManager->get($user); + + if (is_null($userObject)) { + \OCP\Util::writeLog('files', ' Backends provided no user object for ' . $user, \OCP\Util::ERROR); + throw new \OC\User\NoUserException('Backends provided no user object for ' . $user); + } + + self::$usersSetup[$user] = true; + + $homeStorage = \OC::$server->getConfig()->getSystemValue('objectstore'); + if (!empty($homeStorage)) { + // sanity checks + if (empty($homeStorage['class'])) { + \OCP\Util::writeLog('files', 'No class given for objectstore', \OCP\Util::ERROR); + } + if (!isset($homeStorage['arguments'])) { + $homeStorage['arguments'] = array(); + } + // instantiate object store implementation + $homeStorage['arguments']['objectstore'] = new $homeStorage['class']($homeStorage['arguments']); + // mount with home object store implementation + $homeStorage['class'] = '\OC\Files\ObjectStore\HomeObjectStoreStorage'; + } else { + $homeStorage = array( + //default home storage configuration: + 'class' => '\OC\Files\Storage\Home', + 'arguments' => array() + ); + } + $homeStorage['arguments']['user'] = $userObject; + + // check for legacy home id (<= 5.0.12) + if (\OC\Files\Cache\Storage::exists('local::' . $root . '/')) { + $homeStorage['arguments']['legacy'] = true; + } + + $mount = new MountPoint($homeStorage['class'], '/' . $user, $homeStorage['arguments'], self::getLoader()); + self::getMountManager()->addMount($mount); + + $home = \OC\Files\Filesystem::getStorage($user); + + self::mountCacheDir($user); + + // Chance to mount for other storages + /** @var \OC\Files\Config\MountProviderCollection $mountConfigManager */ + $mountConfigManager = \OC::$server->getMountProviderCollection(); + if ($userObject) { + $mounts = $mountConfigManager->getMountsForUser($userObject); + array_walk($mounts, array(self::$mounts, 'addMount')); + $mounts[] = $mount; + $mountConfigManager->registerMounts($userObject, $mounts); + } + + self::listenForNewMountProviders($mountConfigManager, $userManager); + \OC_Hook::emit('OC_Filesystem', 'post_initMountPoints', array('user' => $user, 'user_dir' => $root)); + } + + /** + * Get mounts from mount providers that are registered after setup + * + * @param MountProviderCollection $mountConfigManager + * @param IUserManager $userManager + */ + private static function listenForNewMountProviders(MountProviderCollection $mountConfigManager, IUserManager $userManager) { + if (!self::$listeningForProviders) { + self::$listeningForProviders = true; + $mountConfigManager->listen('\OC\Files\Config', 'registerMountProvider', function (IMountProvider $provider) use ($userManager) { + foreach (Filesystem::$usersSetup as $user => $setup) { + $userObject = $userManager->get($user); + if ($userObject) { + $mounts = $provider->getMountsForUser($userObject, Filesystem::getLoader()); + array_walk($mounts, array(self::$mounts, 'addMount')); + } + } + }); + } + } + + /** + * Mounts the cache directory + * + * @param string $user user name + */ + private static function mountCacheDir($user) { + $cacheBaseDir = \OC::$server->getConfig()->getSystemValue('cache_path', ''); + if ($cacheBaseDir !== '') { + $cacheDir = rtrim($cacheBaseDir, '/') . '/' . $user; + if (!file_exists($cacheDir)) { + mkdir($cacheDir, 0770, true); + } + // mount external cache dir to "/$user/cache" mount point + self::mount('\OC\Files\Storage\Local', array('datadir' => $cacheDir), '/' . $user . '/cache'); + } + } + + /** + * get the default filesystem view + * + * @return View + */ + static public function getView() { + return self::$defaultInstance; + } + + /** + * tear down the filesystem, removing all storage providers + */ + static public function tearDown() { + self::clearMounts(); + self::$defaultInstance = null; + } + + /** + * get the relative path of the root data directory for the current user + * + * @return string + * + * Returns path like /admin/files + */ + static public function getRoot() { + if (!self::$defaultInstance) { + return null; + } + return self::$defaultInstance->getRoot(); + } + + /** + * clear all mounts and storage backends + */ + public static function clearMounts() { + if (self::$mounts) { + self::$usersSetup = array(); + self::$mounts->clear(); + } + } + + /** + * mount an \OC\Files\Storage\Storage in our virtual filesystem + * + * @param \OC\Files\Storage\Storage|string $class + * @param array $arguments + * @param string $mountpoint + */ + static public function mount($class, $arguments, $mountpoint) { + if (!self::$mounts) { + \OC_Util::setupFS(); + } + $mount = new Mount\MountPoint($class, $mountpoint, $arguments, self::getLoader()); + self::$mounts->addMount($mount); + } + + /** + * return the path to a local version of the file + * we need this because we can't know if a file is stored local or not from + * outside the filestorage and for some purposes a local file is needed + * + * @param string $path + * @return string + */ + static public function getLocalFile($path) { + return self::$defaultInstance->getLocalFile($path); + } + + /** + * @param string $path + * @return string + */ + static public function getLocalFolder($path) { + return self::$defaultInstance->getLocalFolder($path); + } + + /** + * return path to file which reflects one visible in browser + * + * @param string $path + * @return string + */ + static public function getLocalPath($path) { + $datadir = \OC_User::getHome(\OC_User::getUser()) . '/files'; + $newpath = $path; + if (strncmp($newpath, $datadir, strlen($datadir)) == 0) { + $newpath = substr($path, strlen($datadir)); + } + return $newpath; + } + + /** + * check if the requested path is valid + * + * @param string $path + * @return bool + */ + static public function isValidPath($path) { + $path = self::normalizePath($path); + if (!$path || $path[0] !== '/') { + $path = '/' . $path; + } + if (strpos($path, '/../') !== false || strrchr($path, '/') === '/..') { + return false; + } + return true; + } + + /** + * checks if a file is blacklisted for storage in the filesystem + * Listens to write and rename hooks + * + * @param array $data from hook + */ + static public function isBlacklisted($data) { + if (isset($data['path'])) { + $path = $data['path']; + } else if (isset($data['newpath'])) { + $path = $data['newpath']; + } + if (isset($path)) { + if (self::isFileBlacklisted($path)) { + $data['run'] = false; + } + } + } + + /** + * @param string $filename + * @return bool + */ + static public function isFileBlacklisted($filename) { + $filename = self::normalizePath($filename); + + $blacklist = \OC::$server->getConfig()->getSystemValue('blacklisted_files', array('.htaccess')); + $filename = strtolower(basename($filename)); + return in_array($filename, $blacklist); + } + + /** + * check if the directory should be ignored when scanning + * NOTE: the special directories . and .. would cause never ending recursion + * + * @param String $dir + * @return boolean + */ + static public function isIgnoredDir($dir) { + if ($dir === '.' || $dir === '..') { + return true; + } + return false; + } + + /** + * following functions are equivalent to their php builtin equivalents for arguments/return values. + */ + static public function mkdir($path) { + return self::$defaultInstance->mkdir($path); + } + + static public function rmdir($path) { + return self::$defaultInstance->rmdir($path); + } + + static public function opendir($path) { + return self::$defaultInstance->opendir($path); + } + + static public function readdir($path) { + return self::$defaultInstance->readdir($path); + } + + static public function is_dir($path) { + return self::$defaultInstance->is_dir($path); + } + + static public function is_file($path) { + return self::$defaultInstance->is_file($path); + } + + static public function stat($path) { + return self::$defaultInstance->stat($path); + } + + static public function filetype($path) { + return self::$defaultInstance->filetype($path); + } + + static public function filesize($path) { + return self::$defaultInstance->filesize($path); + } + + static public function readfile($path) { + return self::$defaultInstance->readfile($path); + } + + static public function isCreatable($path) { + return self::$defaultInstance->isCreatable($path); + } + + static public function isReadable($path) { + return self::$defaultInstance->isReadable($path); + } + + static public function isUpdatable($path) { + return self::$defaultInstance->isUpdatable($path); + } + + static public function isDeletable($path) { + return self::$defaultInstance->isDeletable($path); + } + + static public function isSharable($path) { + return self::$defaultInstance->isSharable($path); + } + + static public function file_exists($path) { + return self::$defaultInstance->file_exists($path); + } + + static public function filemtime($path) { + return self::$defaultInstance->filemtime($path); + } + + static public function touch($path, $mtime = null) { + return self::$defaultInstance->touch($path, $mtime); + } + + /** + * @return string + */ + static public function file_get_contents($path) { + return self::$defaultInstance->file_get_contents($path); + } + + static public function file_put_contents($path, $data) { + return self::$defaultInstance->file_put_contents($path, $data); + } + + static public function unlink($path) { + return self::$defaultInstance->unlink($path); + } + + static public function rename($path1, $path2) { + return self::$defaultInstance->rename($path1, $path2); + } + + static public function copy($path1, $path2) { + return self::$defaultInstance->copy($path1, $path2); + } + + static public function fopen($path, $mode) { + return self::$defaultInstance->fopen($path, $mode); + } + + /** + * @return string + */ + static public function toTmpFile($path) { + return self::$defaultInstance->toTmpFile($path); + } + + static public function fromTmpFile($tmpFile, $path) { + return self::$defaultInstance->fromTmpFile($tmpFile, $path); + } + + static public function getMimeType($path) { + return self::$defaultInstance->getMimeType($path); + } + + static public function hash($type, $path, $raw = false) { + return self::$defaultInstance->hash($type, $path, $raw); + } + + static public function free_space($path = '/') { + return self::$defaultInstance->free_space($path); + } + + static public function search($query) { + return self::$defaultInstance->search($query); + } + + /** + * @param string $query + */ + static public function searchByMime($query) { + return self::$defaultInstance->searchByMime($query); + } + + /** + * @param string|int $tag name or tag id + * @param string $userId owner of the tags + * @return FileInfo[] array or file info + */ + static public function searchByTag($tag, $userId) { + return self::$defaultInstance->searchByTag($tag, $userId); + } + + /** + * check if a file or folder has been updated since $time + * + * @param string $path + * @param int $time + * @return bool + */ + static public function hasUpdated($path, $time) { + return self::$defaultInstance->hasUpdated($path, $time); + } + + /** + * Fix common problems with a file path + * + * @param string $path + * @param bool $stripTrailingSlash + * @param bool $isAbsolutePath + * @return string + */ + public static function normalizePath($path, $stripTrailingSlash = true, $isAbsolutePath = false) { + /** + * FIXME: This is a workaround for existing classes and files which call + * this function with another type than a valid string. This + * conversion should get removed as soon as all existing + * function calls have been fixed. + */ + $path = (string)$path; + + $cacheKey = json_encode([$path, $stripTrailingSlash, $isAbsolutePath]); + + if (isset(self::$normalizedPathCache[$cacheKey])) { + return self::$normalizedPathCache[$cacheKey]; + } + + if ($path == '') { + return '/'; + } + + //normalize unicode if possible + $path = \OC_Util::normalizeUnicode($path); + + //no windows style slashes + $path = str_replace('\\', '/', $path); + + // When normalizing an absolute path, we need to ensure that the drive-letter + // is still at the beginning on windows + $windows_drive_letter = ''; + if ($isAbsolutePath && \OC_Util::runningOnWindows() && preg_match('#^([a-zA-Z])$#', $path[0]) && $path[1] == ':' && $path[2] == '/') { + $windows_drive_letter = substr($path, 0, 2); + $path = substr($path, 2); + } + + //add leading slash + if ($path[0] !== '/') { + $path = '/' . $path; + } + + // remove '/./' + // ugly, but str_replace() can't replace them all in one go + // as the replacement itself is part of the search string + // which will only be found during the next iteration + while (strpos($path, '/./') !== false) { + $path = str_replace('/./', '/', $path); + } + // remove sequences of slashes + $path = preg_replace('#/{2,}#', '/', $path); + + //remove trailing slash + if ($stripTrailingSlash and strlen($path) > 1 and substr($path, -1, 1) === '/') { + $path = substr($path, 0, -1); + } + + // remove trailing '/.' + if (substr($path, -2) == '/.') { + $path = substr($path, 0, -2); + } + + $normalizedPath = $windows_drive_letter . $path; + self::$normalizedPathCache[$cacheKey] = $normalizedPath; + + return $normalizedPath; + } + + /** + * get the filesystem info + * + * @param string $path + * @param boolean $includeMountPoints whether to add mountpoint sizes, + * defaults to true + * @return \OC\Files\FileInfo|bool False if file does not exist + */ + public static function getFileInfo($path, $includeMountPoints = true) { + return self::$defaultInstance->getFileInfo($path, $includeMountPoints); + } + + /** + * change file metadata + * + * @param string $path + * @param array $data + * @return int + * + * returns the fileid of the updated file + */ + public static function putFileInfo($path, $data) { + return self::$defaultInstance->putFileInfo($path, $data); + } + + /** + * get the content of a directory + * + * @param string $directory path under datadirectory + * @param string $mimetype_filter limit returned content to this mimetype or mimepart + * @return \OC\Files\FileInfo[] + */ + public static function getDirectoryContent($directory, $mimetype_filter = '') { + return self::$defaultInstance->getDirectoryContent($directory, $mimetype_filter); + } + + /** + * Get the path of a file by id + * + * Note that the resulting path is not guaranteed to be unique for the id, multiple paths can point to the same file + * + * @param int $id + * @throws NotFoundException + * @return string + */ + public static function getPath($id) { + return self::$defaultInstance->getPath($id); + } + + /** + * Get the owner for a file or folder + * + * @param string $path + * @return string + */ + public static function getOwner($path) { + return self::$defaultInstance->getOwner($path); + } + + /** + * get the ETag for a file or folder + * + * @param string $path + * @return string + */ + static public function getETag($path) { + return self::$defaultInstance->getETag($path); + } +} diff --git a/lib/private/Files/Mount/Manager.php b/lib/private/Files/Mount/Manager.php new file mode 100644 index 00000000000..ba4a7f8d910 --- /dev/null +++ b/lib/private/Files/Mount/Manager.php @@ -0,0 +1,165 @@ + + * @author Morris Jobke + * @author Robin Appelman + * @author Robin McCorkell + * + * @copyright Copyright (c) 2016, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Files\Mount; + +use \OC\Files\Filesystem; +use OCP\Files\Mount\IMountManager; +use OCP\Files\Mount\IMountPoint; + +class Manager implements IMountManager { + /** + * @var MountPoint[] + */ + private $mounts = array(); + + /** + * @param IMountPoint $mount + */ + public function addMount(IMountPoint $mount) { + $this->mounts[$mount->getMountPoint()] = $mount; + } + + /** + * @param string $mountPoint + */ + public function removeMount($mountPoint) { + $mountPoint = Filesystem::normalizePath($mountPoint); + if (strlen($mountPoint) > 1) { + $mountPoint .= '/'; + } + unset($this->mounts[$mountPoint]); + } + + /** + * @param string $mountPoint + * @param string $target + */ + public function moveMount($mountPoint, $target){ + $this->mounts[$target] = $this->mounts[$mountPoint]; + unset($this->mounts[$mountPoint]); + } + + /** + * Find the mount for $path + * + * @param string $path + * @return MountPoint + */ + public function find($path) { + \OC_Util::setupFS(); + $path = $this->formatPath($path); + if (isset($this->mounts[$path])) { + return $this->mounts[$path]; + } + + \OC_Hook::emit('OC_Filesystem', 'get_mountpoint', array('path' => $path)); + $foundMountPoint = ''; + $mountPoints = array_keys($this->mounts); + foreach ($mountPoints as $mountpoint) { + if (strpos($path, $mountpoint) === 0 and strlen($mountpoint) > strlen($foundMountPoint)) { + $foundMountPoint = $mountpoint; + } + } + if (isset($this->mounts[$foundMountPoint])) { + return $this->mounts[$foundMountPoint]; + } else { + return null; + } + } + + /** + * Find all mounts in $path + * + * @param string $path + * @return MountPoint[] + */ + public function findIn($path) { + \OC_Util::setupFS(); + $path = $this->formatPath($path); + $result = array(); + $pathLength = strlen($path); + $mountPoints = array_keys($this->mounts); + foreach ($mountPoints as $mountPoint) { + if (substr($mountPoint, 0, $pathLength) === $path and strlen($mountPoint) > $pathLength) { + $result[] = $this->mounts[$mountPoint]; + } + } + return $result; + } + + public function clear() { + $this->mounts = array(); + } + + /** + * Find mounts by storage id + * + * @param string $id + * @return MountPoint[] + */ + public function findByStorageId($id) { + \OC_Util::setupFS(); + if (strlen($id) > 64) { + $id = md5($id); + } + $result = array(); + foreach ($this->mounts as $mount) { + if ($mount->getStorageId() === $id) { + $result[] = $mount; + } + } + return $result; + } + + /** + * @return MountPoint[] + */ + public function getAll() { + return $this->mounts; + } + + /** + * Find mounts by numeric storage id + * + * @param int $id + * @return MountPoint[] + */ + public function findByNumericId($id) { + $storageId = \OC\Files\Cache\Storage::getStorageId($id); + return $this->findByStorageId($storageId); + } + + /** + * @param string $path + * @return string + */ + private function formatPath($path) { + $path = Filesystem::normalizePath($path); + if (strlen($path) > 1) { + $path .= '/'; + } + return $path; + } +} diff --git a/lib/private/Files/Mount/MountPoint.php b/lib/private/Files/Mount/MountPoint.php new file mode 100644 index 00000000000..7b9294fc1e0 --- /dev/null +++ b/lib/private/Files/Mount/MountPoint.php @@ -0,0 +1,251 @@ + + * @author Jörn Friedrich Dreyer + * @author Morris Jobke + * @author Robin Appelman + * @author Robin McCorkell + * @author Thomas Müller + * @author Vincent Petry + * + * @copyright Copyright (c) 2016, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Files\Mount; + +use \OC\Files\Filesystem; +use OC\Files\Storage\StorageFactory; +use OC\Files\Storage\Storage; +use OCP\Files\Mount\IMountPoint; + +class MountPoint implements IMountPoint { + /** + * @var \OC\Files\Storage\Storage $storage + */ + protected $storage = null; + protected $class; + protected $storageId; + + /** + * Configuration options for the storage backend + * + * @var array + */ + protected $arguments = array(); + protected $mountPoint; + + /** + * Mount specific options + * + * @var array + */ + protected $mountOptions = array(); + + /** + * @var \OC\Files\Storage\StorageFactory $loader + */ + private $loader; + + /** + * Specified whether the storage is invalid after failing to + * instantiate it. + * + * @var bool + */ + private $invalidStorage = false; + + /** + * @param string|\OC\Files\Storage\Storage $storage + * @param string $mountpoint + * @param array $arguments (optional) configuration for the storage backend + * @param \OCP\Files\Storage\IStorageFactory $loader + * @param array $mountOptions mount specific options + */ + public function __construct($storage, $mountpoint, $arguments = null, $loader = null, $mountOptions = null) { + if (is_null($arguments)) { + $arguments = array(); + } + if (is_null($loader)) { + $this->loader = new StorageFactory(); + } else { + $this->loader = $loader; + } + + if (!is_null($mountOptions)) { + $this->mountOptions = $mountOptions; + } + + $mountpoint = $this->formatPath($mountpoint); + $this->mountPoint = $mountpoint; + if ($storage instanceof Storage) { + $this->class = get_class($storage); + $this->storage = $this->loader->wrap($this, $storage); + } else { + // Update old classes to new namespace + if (strpos($storage, 'OC_Filestorage_') !== false) { + $storage = '\OC\Files\Storage\\' . substr($storage, 15); + } + $this->class = $storage; + $this->arguments = $arguments; + } + } + + /** + * get complete path to the mount point, relative to data/ + * + * @return string + */ + public function getMountPoint() { + return $this->mountPoint; + } + + /** + * Sets the mount point path, relative to data/ + * + * @param string $mountPoint new mount point + */ + public function setMountPoint($mountPoint) { + $this->mountPoint = $this->formatPath($mountPoint); + } + + /** + * create the storage that is mounted + * + * @return \OC\Files\Storage\Storage + */ + private function createStorage() { + if ($this->invalidStorage) { + return null; + } + + if (class_exists($this->class)) { + try { + return $this->loader->getInstance($this, $this->class, $this->arguments); + } catch (\Exception $exception) { + $this->invalidStorage = true; + if ($this->mountPoint === '/') { + // the root storage could not be initialized, show the user! + throw new \Exception('The root storage could not be initialized. Please contact your local administrator.', $exception->getCode(), $exception); + } else { + \OCP\Util::writeLog('core', $exception->getMessage(), \OCP\Util::ERROR); + } + return null; + } + } else { + \OCP\Util::writeLog('core', 'storage backend ' . $this->class . ' not found', \OCP\Util::ERROR); + $this->invalidStorage = true; + return null; + } + } + + /** + * @return \OC\Files\Storage\Storage + */ + public function getStorage() { + if (is_null($this->storage)) { + $this->storage = $this->createStorage(); + } + return $this->storage; + } + + /** + * @return string + */ + public function getStorageId() { + if (!$this->storageId) { + if (is_null($this->storage)) { + $storage = $this->createStorage(); //FIXME: start using exceptions + if (is_null($storage)) { + return null; + } + + $this->storage = $storage; + } + $this->storageId = $this->storage->getId(); + if (strlen($this->storageId) > 64) { + $this->storageId = md5($this->storageId); + } + } + return $this->storageId; + } + + /** + * @param string $path + * @return string + */ + public function getInternalPath($path) { + if ($this->mountPoint === $path or $this->mountPoint . '/' === $path) { + $internalPath = ''; + } else { + $internalPath = substr($path, strlen($this->mountPoint)); + } + // substr returns false instead of an empty string, we always want a string + return (string)$internalPath; + } + + /** + * @param string $path + * @return string + */ + private function formatPath($path) { + $path = Filesystem::normalizePath($path); + if (strlen($path) > 1) { + $path .= '/'; + } + return $path; + } + + /** + * @param callable $wrapper + */ + public function wrapStorage($wrapper) { + $storage = $this->getStorage(); + // storage can be null if it couldn't be initialized + if ($storage != null) { + $this->storage = $wrapper($this->mountPoint, $storage, $this); + } + } + + /** + * Get a mount option + * + * @param string $name Name of the mount option to get + * @param mixed $default Default value for the mount option + * @return mixed + */ + public function getOption($name, $default) { + return isset($this->mountOptions[$name]) ? $this->mountOptions[$name] : $default; + } + + /** + * Get all options for the mount + * + * @return array + */ + public function getOptions() { + return $this->mountOptions; + } + + /** + * Get the file id of the root of the storage + * + * @return int + */ + public function getStorageRootId() { + return (int)$this->getStorage()->getCache()->getId(''); + } +} diff --git a/lib/private/Files/Mount/MoveableMount.php b/lib/private/Files/Mount/MoveableMount.php new file mode 100644 index 00000000000..8a1bd7dd9c5 --- /dev/null +++ b/lib/private/Files/Mount/MoveableMount.php @@ -0,0 +1,44 @@ + + * @author Robin Appelman + * + * @copyright Copyright (c) 2016, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Files\Mount; + +/** + * Defines the mount point to be (re)moved by the user + */ +interface MoveableMount { + /** + * Move the mount point to $target + * + * @param string $target the target mount point + * @return bool + */ + public function moveMount($target); + + /** + * Remove the mount points + * + * @return mixed + * @return bool + */ + public function removeMount(); +} diff --git a/lib/private/Files/Node/File.php b/lib/private/Files/Node/File.php new file mode 100644 index 00000000000..9e0014abb0b --- /dev/null +++ b/lib/private/Files/Node/File.php @@ -0,0 +1,176 @@ + + * @author Morris Jobke + * @author Robin Appelman + * @author Roeland Jago Douma + * @author Vincent Petry + * + * @copyright Copyright (c) 2016, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Files\Node; + +use OCP\Files\NotPermittedException; + +class File extends Node implements \OCP\Files\File { + /** + * @return string + * @throws \OCP\Files\NotPermittedException + */ + public function getContent() { + if ($this->checkPermissions(\OCP\Constants::PERMISSION_READ)) { + /** + * @var \OC\Files\Storage\Storage $storage; + */ + return $this->view->file_get_contents($this->path); + } else { + throw new NotPermittedException(); + } + } + + /** + * @param string $data + * @throws \OCP\Files\NotPermittedException + */ + public function putContent($data) { + if ($this->checkPermissions(\OCP\Constants::PERMISSION_UPDATE)) { + $this->sendHooks(array('preWrite')); + $this->view->file_put_contents($this->path, $data); + $this->fileInfo = null; + $this->sendHooks(array('postWrite')); + } else { + throw new NotPermittedException(); + } + } + + /** + * @param string $mode + * @return resource + * @throws \OCP\Files\NotPermittedException + */ + public function fopen($mode) { + $preHooks = array(); + $postHooks = array(); + $requiredPermissions = \OCP\Constants::PERMISSION_READ; + switch ($mode) { + case 'r+': + case 'rb+': + case 'w+': + case 'wb+': + case 'x+': + case 'xb+': + case 'a+': + case 'ab+': + case 'w': + case 'wb': + case 'x': + case 'xb': + case 'a': + case 'ab': + $preHooks[] = 'preWrite'; + $postHooks[] = 'postWrite'; + $requiredPermissions |= \OCP\Constants::PERMISSION_UPDATE; + break; + } + + if ($this->checkPermissions($requiredPermissions)) { + $this->sendHooks($preHooks); + $result = $this->view->fopen($this->path, $mode); + $this->sendHooks($postHooks); + return $result; + } else { + throw new NotPermittedException(); + } + } + + public function delete() { + if ($this->checkPermissions(\OCP\Constants::PERMISSION_DELETE)) { + $this->sendHooks(array('preDelete')); + $fileInfo = $this->getFileInfo(); + $this->view->unlink($this->path); + $nonExisting = new NonExistingFile($this->root, $this->view, $this->path, $fileInfo); + $this->root->emit('\OC\Files', 'postDelete', array($nonExisting)); + $this->exists = false; + $this->fileInfo = null; + } else { + throw new NotPermittedException(); + } + } + + /** + * @param string $targetPath + * @throws \OCP\Files\NotPermittedException + * @return \OC\Files\Node\Node + */ + public function copy($targetPath) { + $targetPath = $this->normalizePath($targetPath); + $parent = $this->root->get(dirname($targetPath)); + if ($parent instanceof Folder and $this->isValidPath($targetPath) and $parent->isCreatable()) { + $nonExisting = new NonExistingFile($this->root, $this->view, $targetPath); + $this->root->emit('\OC\Files', 'preCopy', array($this, $nonExisting)); + $this->root->emit('\OC\Files', 'preWrite', array($nonExisting)); + $this->view->copy($this->path, $targetPath); + $targetNode = $this->root->get($targetPath); + $this->root->emit('\OC\Files', 'postCopy', array($this, $targetNode)); + $this->root->emit('\OC\Files', 'postWrite', array($targetNode)); + return $targetNode; + } else { + throw new NotPermittedException(); + } + } + + /** + * @param string $targetPath + * @throws \OCP\Files\NotPermittedException + * @return \OC\Files\Node\Node + */ + public function move($targetPath) { + $targetPath = $this->normalizePath($targetPath); + $parent = $this->root->get(dirname($targetPath)); + if ($parent instanceof Folder and $this->isValidPath($targetPath) and $parent->isCreatable()) { + $nonExisting = new NonExistingFile($this->root, $this->view, $targetPath); + $this->root->emit('\OC\Files', 'preRename', array($this, $nonExisting)); + $this->root->emit('\OC\Files', 'preWrite', array($nonExisting)); + $this->view->rename($this->path, $targetPath); + $targetNode = $this->root->get($targetPath); + $this->root->emit('\OC\Files', 'postRename', array($this, $targetNode)); + $this->root->emit('\OC\Files', 'postWrite', array($targetNode)); + $this->path = $targetPath; + $this->fileInfo = null; + return $targetNode; + } else { + throw new NotPermittedException(); + } + } + + /** + * @param string $type + * @param bool $raw + * @return string + */ + public function hash($type, $raw = false) { + return $this->view->hash($type, $this->path, $raw); + } + + /** + * @inheritdoc + */ + public function getChecksum() { + return $this->getFileInfo()->getChecksum(); + } +} diff --git a/lib/private/Files/Node/Folder.php b/lib/private/Files/Node/Folder.php new file mode 100644 index 00000000000..f4d7dae20a3 --- /dev/null +++ b/lib/private/Files/Node/Folder.php @@ -0,0 +1,360 @@ + + * @author Morris Jobke + * @author Robin Appelman + * @author Robin McCorkell + * @author Vincent Petry + * + * @copyright Copyright (c) 2016, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Files\Node; + +use OCP\Files\FileInfo; +use OCP\Files\NotFoundException; +use OCP\Files\NotPermittedException; + +class Folder extends Node implements \OCP\Files\Folder { + /** + * @param string $path path relative to the folder + * @return string + * @throws \OCP\Files\NotPermittedException + */ + public function getFullPath($path) { + if (!$this->isValidPath($path)) { + throw new NotPermittedException(); + } + return $this->path . $this->normalizePath($path); + } + + /** + * @param string $path + * @return string + */ + public function getRelativePath($path) { + if ($this->path === '' or $this->path === '/') { + return $this->normalizePath($path); + } + if ($path === $this->path) { + return '/'; + } else if (strpos($path, $this->path . '/') !== 0) { + return null; + } else { + $path = substr($path, strlen($this->path)); + return $this->normalizePath($path); + } + } + + /** + * check if a node is a (grand-)child of the folder + * + * @param \OC\Files\Node\Node $node + * @return bool + */ + public function isSubNode($node) { + return strpos($node->getPath(), $this->path . '/') === 0; + } + + /** + * get the content of this directory + * + * @throws \OCP\Files\NotFoundException + * @return Node[] + */ + public function getDirectoryListing() { + $folderContent = $this->view->getDirectoryContent($this->path); + + return array_map(function(FileInfo $info) { + if ($info->getMimetype() === 'httpd/unix-directory') { + return new Folder($this->root, $this->view, $info->getPath(), $info); + } else { + return new File($this->root, $this->view, $info->getPath(), $info); + } + }, $folderContent); + } + + /** + * @param string $path + * @param FileInfo $info + * @return File|Folder + */ + protected function createNode($path, FileInfo $info = null) { + if (is_null($info)) { + $isDir = $this->view->is_dir($path); + } else { + $isDir = $info->getType() === FileInfo::TYPE_FOLDER; + } + if ($isDir) { + return new Folder($this->root, $this->view, $path, $info); + } else { + return new File($this->root, $this->view, $path, $info); + } + } + + /** + * Get the node at $path + * + * @param string $path + * @return \OC\Files\Node\Node + * @throws \OCP\Files\NotFoundException + */ + public function get($path) { + return $this->root->get($this->getFullPath($path)); + } + + /** + * @param string $path + * @return bool + */ + public function nodeExists($path) { + try { + $this->get($path); + return true; + } catch (NotFoundException $e) { + return false; + } + } + + /** + * @param string $path + * @return \OC\Files\Node\Folder + * @throws \OCP\Files\NotPermittedException + */ + public function newFolder($path) { + if ($this->checkPermissions(\OCP\Constants::PERMISSION_CREATE)) { + $fullPath = $this->getFullPath($path); + $nonExisting = new NonExistingFolder($this->root, $this->view, $fullPath); + $this->root->emit('\OC\Files', 'preWrite', array($nonExisting)); + $this->root->emit('\OC\Files', 'preCreate', array($nonExisting)); + $this->view->mkdir($fullPath); + $node = new Folder($this->root, $this->view, $fullPath); + $this->root->emit('\OC\Files', 'postWrite', array($node)); + $this->root->emit('\OC\Files', 'postCreate', array($node)); + return $node; + } else { + throw new NotPermittedException(); + } + } + + /** + * @param string $path + * @return \OC\Files\Node\File + * @throws \OCP\Files\NotPermittedException + */ + public function newFile($path) { + if ($this->checkPermissions(\OCP\Constants::PERMISSION_CREATE)) { + $fullPath = $this->getFullPath($path); + $nonExisting = new NonExistingFile($this->root, $this->view, $fullPath); + $this->root->emit('\OC\Files', 'preWrite', array($nonExisting)); + $this->root->emit('\OC\Files', 'preCreate', array($nonExisting)); + $this->view->touch($fullPath); + $node = new File($this->root, $this->view, $fullPath); + $this->root->emit('\OC\Files', 'postWrite', array($node)); + $this->root->emit('\OC\Files', 'postCreate', array($node)); + return $node; + } else { + throw new NotPermittedException(); + } + } + + /** + * search for files with the name matching $query + * + * @param string $query + * @return \OC\Files\Node\Node[] + */ + public function search($query) { + return $this->searchCommon('search', array('%' . $query . '%')); + } + + /** + * search for files by mimetype + * + * @param string $mimetype + * @return Node[] + */ + public function searchByMime($mimetype) { + return $this->searchCommon('searchByMime', array($mimetype)); + } + + /** + * search for files by tag + * + * @param string|int $tag name or tag id + * @param string $userId owner of the tags + * @return Node[] + */ + public function searchByTag($tag, $userId) { + return $this->searchCommon('searchByTag', array($tag, $userId)); + } + + /** + * @param string $method cache method + * @param array $args call args + * @return \OC\Files\Node\Node[] + */ + private function searchCommon($method, $args) { + $files = array(); + $rootLength = strlen($this->path); + $mount = $this->root->getMount($this->path); + $storage = $mount->getStorage(); + $internalPath = $mount->getInternalPath($this->path); + $internalPath = rtrim($internalPath, '/'); + if ($internalPath !== '') { + $internalPath = $internalPath . '/'; + } + $internalRootLength = strlen($internalPath); + + $cache = $storage->getCache(''); + + $results = call_user_func_array(array($cache, $method), $args); + foreach ($results as $result) { + if ($internalRootLength === 0 or substr($result['path'], 0, $internalRootLength) === $internalPath) { + $result['internalPath'] = $result['path']; + $result['path'] = substr($result['path'], $internalRootLength); + $result['storage'] = $storage; + $files[] = new \OC\Files\FileInfo($this->path . '/' . $result['path'], $storage, $result['internalPath'], $result, $mount); + } + } + + $mounts = $this->root->getMountsIn($this->path); + foreach ($mounts as $mount) { + $storage = $mount->getStorage(); + if ($storage) { + $cache = $storage->getCache(''); + + $relativeMountPoint = substr($mount->getMountPoint(), $rootLength); + $results = call_user_func_array(array($cache, $method), $args); + foreach ($results as $result) { + $result['internalPath'] = $result['path']; + $result['path'] = $relativeMountPoint . $result['path']; + $result['storage'] = $storage; + $files[] = new \OC\Files\FileInfo($this->path . '/' . $result['path'], $storage, $result['internalPath'], $result, $mount); + } + } + } + + return array_map(function(FileInfo $file) { + return $this->createNode($file->getPath(), $file); + }, $files); + } + + /** + * @param int $id + * @return \OC\Files\Node\Node[] + */ + public function getById($id) { + $mounts = $this->root->getMountsIn($this->path); + $mounts[] = $this->root->getMount($this->path); + // reverse the array so we start with the storage this view is in + // which is the most likely to contain the file we're looking for + $mounts = array_reverse($mounts); + + $nodes = array(); + foreach ($mounts as $mount) { + /** + * @var \OC\Files\Mount\MountPoint $mount + */ + if ($mount->getStorage()) { + $cache = $mount->getStorage()->getCache(); + $internalPath = $cache->getPathById($id); + if (is_string($internalPath)) { + $fullPath = $mount->getMountPoint() . $internalPath; + if (!is_null($path = $this->getRelativePath($fullPath))) { + $nodes[] = $this->get($path); + } + } + } + } + return $nodes; + } + + public function getFreeSpace() { + return $this->view->free_space($this->path); + } + + public function delete() { + if ($this->checkPermissions(\OCP\Constants::PERMISSION_DELETE)) { + $this->sendHooks(array('preDelete')); + $fileInfo = $this->getFileInfo(); + $this->view->rmdir($this->path); + $nonExisting = new NonExistingFolder($this->root, $this->view, $this->path, $fileInfo); + $this->root->emit('\OC\Files', 'postDelete', array($nonExisting)); + $this->exists = false; + } else { + throw new NotPermittedException(); + } + } + + /** + * @param string $targetPath + * @throws \OCP\Files\NotPermittedException + * @return \OC\Files\Node\Node + */ + public function copy($targetPath) { + $targetPath = $this->normalizePath($targetPath); + $parent = $this->root->get(dirname($targetPath)); + if ($parent instanceof Folder and $this->isValidPath($targetPath) and $parent->isCreatable()) { + $nonExisting = new NonExistingFolder($this->root, $this->view, $targetPath); + $this->root->emit('\OC\Files', 'preCopy', array($this, $nonExisting)); + $this->root->emit('\OC\Files', 'preWrite', array($nonExisting)); + $this->view->copy($this->path, $targetPath); + $targetNode = $this->root->get($targetPath); + $this->root->emit('\OC\Files', 'postCopy', array($this, $targetNode)); + $this->root->emit('\OC\Files', 'postWrite', array($targetNode)); + return $targetNode; + } else { + throw new NotPermittedException(); + } + } + + /** + * @param string $targetPath + * @throws \OCP\Files\NotPermittedException + * @return \OC\Files\Node\Node + */ + public function move($targetPath) { + $targetPath = $this->normalizePath($targetPath); + $parent = $this->root->get(dirname($targetPath)); + if ($parent instanceof Folder and $this->isValidPath($targetPath) and $parent->isCreatable()) { + $nonExisting = new NonExistingFolder($this->root, $this->view, $targetPath); + $this->root->emit('\OC\Files', 'preRename', array($this, $nonExisting)); + $this->root->emit('\OC\Files', 'preWrite', array($nonExisting)); + $this->view->rename($this->path, $targetPath); + $targetNode = $this->root->get($targetPath); + $this->root->emit('\OC\Files', 'postRename', array($this, $targetNode)); + $this->root->emit('\OC\Files', 'postWrite', array($targetNode)); + $this->path = $targetPath; + return $targetNode; + } else { + throw new NotPermittedException(); + } + } + + /** + * Add a suffix to the name in case the file exists + * + * @param string $name + * @return string + * @throws NotPermittedException + */ + public function getNonExistingName($name) { + $uniqueName = \OC_Helper::buildNotExistingFileNameForView($this->getPath(), $name, $this->view); + return trim($this->getRelativePath($uniqueName), '/'); + } +} diff --git a/lib/private/Files/Node/HookConnector.php b/lib/private/Files/Node/HookConnector.php new file mode 100644 index 00000000000..5c36ca3848e --- /dev/null +++ b/lib/private/Files/Node/HookConnector.php @@ -0,0 +1,164 @@ + + * + * @copyright Copyright (c) 2016, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Files\Node; + +use OCP\Files\FileInfo; +use OC\Files\Filesystem; +use OC\Files\View; +use OCP\Util; + +class HookConnector { + /** + * @var Root + */ + private $root; + + /** + * @var View + */ + private $view; + + /** + * @var FileInfo[] + */ + private $deleteMetaCache = []; + + /** + * HookConnector constructor. + * + * @param Root $root + * @param View $view + */ + public function __construct(Root $root, View $view) { + $this->root = $root; + $this->view = $view; + } + + public function viewToNode() { + Util::connectHook('OC_Filesystem', 'write', $this, 'write'); + Util::connectHook('OC_Filesystem', 'post_write', $this, 'postWrite'); + + Util::connectHook('OC_Filesystem', 'create', $this, 'create'); + Util::connectHook('OC_Filesystem', 'post_create', $this, 'postCreate'); + + Util::connectHook('OC_Filesystem', 'delete', $this, 'delete'); + Util::connectHook('OC_Filesystem', 'post_delete', $this, 'postDelete'); + + Util::connectHook('OC_Filesystem', 'rename', $this, 'rename'); + Util::connectHook('OC_Filesystem', 'post_rename', $this, 'postRename'); + + Util::connectHook('OC_Filesystem', 'copy', $this, 'copy'); + Util::connectHook('OC_Filesystem', 'post_copy', $this, 'postCopy'); + + Util::connectHook('OC_Filesystem', 'touch', $this, 'touch'); + Util::connectHook('OC_Filesystem', 'post_touch', $this, 'postTouch'); + } + + public function write($arguments) { + $node = $this->getNodeForPath($arguments['path']); + $this->root->emit('\OC\Files', 'preWrite', [$node]); + } + + public function postWrite($arguments) { + $node = $this->getNodeForPath($arguments['path']); + $this->root->emit('\OC\Files', 'postWrite', [$node]); + } + + public function create($arguments) { + $node = $this->getNodeForPath($arguments['path']); + $this->root->emit('\OC\Files', 'preCreate', [$node]); + } + + public function postCreate($arguments) { + $node = $this->getNodeForPath($arguments['path']); + $this->root->emit('\OC\Files', 'postCreate', [$node]); + } + + public function delete($arguments) { + $node = $this->getNodeForPath($arguments['path']); + $this->deleteMetaCache[$node->getPath()] = $node->getFileInfo(); + $this->root->emit('\OC\Files', 'preDelete', [$node]); + } + + public function postDelete($arguments) { + $node = $this->getNodeForPath($arguments['path']); + unset($this->deleteMetaCache[$node->getPath()]); + $this->root->emit('\OC\Files', 'postDelete', [$node]); + } + + public function touch($arguments) { + $node = $this->getNodeForPath($arguments['path']); + $this->root->emit('\OC\Files', 'preTouch', [$node]); + } + + public function postTouch($arguments) { + $node = $this->getNodeForPath($arguments['path']); + $this->root->emit('\OC\Files', 'postTouch', [$node]); + } + + public function rename($arguments) { + $source = $this->getNodeForPath($arguments['oldpath']); + $target = $this->getNodeForPath($arguments['newpath']); + $this->root->emit('\OC\Files', 'preRename', [$source, $target]); + } + + public function postRename($arguments) { + $source = $this->getNodeForPath($arguments['oldpath']); + $target = $this->getNodeForPath($arguments['newpath']); + $this->root->emit('\OC\Files', 'postRename', [$source, $target]); + } + + public function copy($arguments) { + $source = $this->getNodeForPath($arguments['oldpath']); + $target = $this->getNodeForPath($arguments['newpath']); + $this->root->emit('\OC\Files', 'preCopy', [$source, $target]); + } + + public function postCopy($arguments) { + $source = $this->getNodeForPath($arguments['oldpath']); + $target = $this->getNodeForPath($arguments['newpath']); + $this->root->emit('\OC\Files', 'postCopy', [$source, $target]); + } + + private function getNodeForPath($path) { + $info = Filesystem::getView()->getFileInfo($path); + if (!$info) { + + $fullPath = Filesystem::getView()->getAbsolutePath($path); + if (isset($this->deleteMetaCache[$fullPath])) { + $info = $this->deleteMetaCache[$fullPath]; + } else { + $info = null; + } + if (Filesystem::is_dir($path)) { + return new NonExistingFolder($this->root, $this->view, $fullPath, $info); + } else { + return new NonExistingFile($this->root, $this->view, $fullPath, $info); + } + } + if ($info->getType() === FileInfo::TYPE_FILE) { + return new File($this->root, $this->view, $info->getPath(), $info); + } else { + return new Folder($this->root, $this->view, $info->getPath(), $info); + } + } +} diff --git a/lib/private/Files/Node/LazyRoot.php b/lib/private/Files/Node/LazyRoot.php new file mode 100644 index 00000000000..9661f036579 --- /dev/null +++ b/lib/private/Files/Node/LazyRoot.php @@ -0,0 +1,474 @@ + + * + * @copyright Copyright (c) 2016, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ +namespace OC\Files\Node; + +use OC\Files\Mount\MountPoint; +use OCP\Files\IRootFolder; +use OCP\Files\NotPermittedException; + +/** + * Class LazyRoot + * + * This is a lazy wrapper around the root. So only + * once it is needed this will get initialized. + * + * @package OC\Files\Node + */ +class LazyRoot implements IRootFolder { + /** @var \Closure */ + private $rootFolderClosure; + + /** @var IRootFolder */ + private $rootFolder; + + /** + * LazyRoot constructor. + * + * @param \Closure $rootFolderClosure + */ + public function __construct(\Closure $rootFolderClosure) { + $this->rootFolderClosure = $rootFolderClosure; + } + + /** + * Magic method to first get the real rootFolder and then + * call $method with $args on it + * + * @param $method + * @param $args + * @return mixed + */ + public function __call($method, $args) { + if ($this->rootFolder === null) { + $this->rootFolder = call_user_func($this->rootFolderClosure); + } + + return call_user_func_array([$this->rootFolder, $method], $args); + } + + /** + * @inheritDoc + */ + public function getUser() { + return $this->__call(__FUNCTION__, func_get_args()); + } + + /** + * @inheritDoc + */ + public function listen($scope, $method, callable $callback) { + $this->__call(__FUNCTION__, func_get_args()); + } + + /** + * @inheritDoc + */ + public function removeListener($scope = null, $method = null, callable $callback = null) { + $this->__call(__FUNCTION__, func_get_args()); + } + + /** + * @inheritDoc + */ + public function emit($scope, $method, $arguments = array()) { + $this->__call(__FUNCTION__, func_get_args()); + } + + /** + * @inheritDoc + */ + public function mount($storage, $mountPoint, $arguments = array()) { + $this->__call(__FUNCTION__, func_get_args()); + } + + /** + * @inheritDoc + */ + public function getMount($mountPoint) { + return $this->__call(__FUNCTION__, func_get_args()); + } + + /** + * @inheritDoc + */ + public function getMountsIn($mountPoint) { + return $this->__call(__FUNCTION__, func_get_args()); + } + + /** + * @inheritDoc + */ + public function getMountByStorageId($storageId) { + return $this->__call(__FUNCTION__, func_get_args()); + } + + /** + * @inheritDoc + */ + public function getMountByNumericStorageId($numericId) { + return $this->__call(__FUNCTION__, func_get_args()); + } + + /** + * @inheritDoc + */ + public function unMount($mount) { + $this->__call(__FUNCTION__, func_get_args()); + } + + /** + * @inheritDoc + */ + public function get($path) { + $this->__call(__FUNCTION__, func_get_args()); + } + + /** + * @inheritDoc + */ + public function rename($targetPath) { + return $this->__call(__FUNCTION__, func_get_args()); + } + + /** + * @inheritDoc + */ + public function delete() { + return $this->__call(__FUNCTION__, func_get_args()); + } + + /** + * @inheritDoc + */ + public function copy($targetPath) { + return $this->__call(__FUNCTION__, func_get_args()); + } + + /** + * @inheritDoc + */ + public function touch($mtime = null) { + $this->__call(__FUNCTION__, func_get_args()); + } + + /** + * @inheritDoc + */ + public function getStorage() { + return $this->__call(__FUNCTION__, func_get_args()); + } + + /** + * @inheritDoc + */ + public function getPath() { + return $this->__call(__FUNCTION__, func_get_args()); + } + + /** + * @inheritDoc + */ + public function getInternalPath() { + return $this->__call(__FUNCTION__, func_get_args()); + } + + /** + * @inheritDoc + */ + public function getId() { + return $this->__call(__FUNCTION__, func_get_args()); + } + + /** + * @inheritDoc + */ + public function stat() { + return $this->__call(__FUNCTION__, func_get_args()); + } + + /** + * @inheritDoc + */ + public function getMTime() { + return $this->__call(__FUNCTION__, func_get_args()); + } + + /** + * @inheritDoc + */ + public function getSize() { + return $this->__call(__FUNCTION__, func_get_args()); + } + + /** + * @inheritDoc + */ + public function getEtag() { + return $this->__call(__FUNCTION__, func_get_args()); + } + + /** + * @inheritDoc + */ + public function getPermissions() { + return $this->__call(__FUNCTION__, func_get_args()); + } + + /** + * @inheritDoc + */ + public function isReadable() { + return $this->__call(__FUNCTION__, func_get_args()); + } + + /** + * @inheritDoc + */ + public function isUpdateable() { + return $this->__call(__FUNCTION__, func_get_args()); + } + + /** + * @inheritDoc + */ + public function isDeletable() { + return $this->__call(__FUNCTION__, func_get_args()); + } + + /** + * @inheritDoc + */ + public function isShareable() { + return $this->__call(__FUNCTION__, func_get_args()); + } + + /** + * @inheritDoc + */ + public function getParent() { + return $this->__call(__FUNCTION__, func_get_args()); + } + + /** + * @inheritDoc + */ + public function getName() { + return $this->__call(__FUNCTION__, func_get_args()); + } + + /** + * @inheritDoc + */ + public function getUserFolder($userId) { + return $this->__call(__FUNCTION__, func_get_args()); + } + + /** + * @inheritDoc + */ + public function getMimetype() { + return $this->__call(__FUNCTION__, func_get_args()); + } + + /** + * @inheritDoc + */ + public function getMimePart() { + return $this->__call(__FUNCTION__, func_get_args()); + } + + /** + * @inheritDoc + */ + public function isEncrypted() { + return $this->__call(__FUNCTION__, func_get_args()); + } + + /** + * @inheritDoc + */ + public function getType() { + return $this->__call(__FUNCTION__, func_get_args()); + } + + /** + * @inheritDoc + */ + public function isShared() { + return $this->__call(__FUNCTION__, func_get_args()); + } + + /** + * @inheritDoc + */ + public function isMounted() { + return $this->__call(__FUNCTION__, func_get_args()); + } + + /** + * @inheritDoc + */ + public function getMountPoint() { + return $this->__call(__FUNCTION__, func_get_args()); + } + + /** + * @inheritDoc + */ + public function getOwner() { + return $this->__call(__FUNCTION__, func_get_args()); + } + + /** + * @inheritDoc + */ + public function getChecksum() { + return $this->__call(__FUNCTION__, func_get_args()); + } + + /** + * @inheritDoc + */ + public function getFullPath($path) { + return $this->__call(__FUNCTION__, func_get_args()); + } + + /** + * @inheritDoc + */ + public function getRelativePath($path) { + return $this->__call(__FUNCTION__, func_get_args()); + } + + /** + * @inheritDoc + */ + public function isSubNode($node) { + return $this->__call(__FUNCTION__, func_get_args()); + } + + /** + * @inheritDoc + */ + public function getDirectoryListing() { + return $this->__call(__FUNCTION__, func_get_args()); + } + + /** + * @inheritDoc + */ + public function nodeExists($path) { + return $this->__call(__FUNCTION__, func_get_args()); + } + + /** + * @inheritDoc + */ + public function newFolder($path) { + return $this->__call(__FUNCTION__, func_get_args()); + } + + /** + * @inheritDoc + */ + public function newFile($path) { + return $this->__call(__FUNCTION__, func_get_args()); + } + + /** + * @inheritDoc + */ + public function search($query) { + return $this->__call(__FUNCTION__, func_get_args()); + } + + /** + * @inheritDoc + */ + public function searchByMime($mimetype) { + return $this->__call(__FUNCTION__, func_get_args()); + } + + /** + * @inheritDoc + */ + public function searchByTag($tag, $userId) { + return $this->__call(__FUNCTION__, func_get_args()); + } + + /** + * @inheritDoc + */ + public function getById($id) { + return $this->__call(__FUNCTION__, func_get_args()); + } + + /** + * @inheritDoc + */ + public function getFreeSpace() { + return $this->__call(__FUNCTION__, func_get_args()); + } + + /** + * @inheritDoc + */ + public function isCreatable() { + return $this->__call(__FUNCTION__, func_get_args()); + } + + /** + * @inheritDoc + */ + public function getNonExistingName($name) { + return $this->__call(__FUNCTION__, func_get_args()); + } + + /** + * @inheritDoc + */ + public function move($targetPath) { + return $this->__call(__FUNCTION__, func_get_args()); + } + + /** + * @inheritDoc + */ + public function lock($type) { + return $this->__call(__FUNCTION__, func_get_args()); + } + + /** + * @inheritDoc + */ + public function changeLock($targetType) { + return $this->__call(__FUNCTION__, func_get_args()); + } + + /** + * @inheritDoc + */ + public function unlock($type) { + return $this->__call(__FUNCTION__, func_get_args()); + } + + +} diff --git a/lib/private/Files/Node/Node.php b/lib/private/Files/Node/Node.php new file mode 100644 index 00000000000..c4fabfc2e2e --- /dev/null +++ b/lib/private/Files/Node/Node.php @@ -0,0 +1,383 @@ + + * @author Joas Schilling + * @author Morris Jobke + * @author Robin Appelman + * @author Roeland Jago Douma + * @author Vincent Petry + * + * @copyright Copyright (c) 2016, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Files\Node; + +use OC\Files\Filesystem; +use OCP\Files\FileInfo; +use OCP\Files\InvalidPathException; +use OCP\Files\NotFoundException; +use OCP\Files\NotPermittedException; + +class Node implements \OCP\Files\Node { + /** + * @var \OC\Files\View $view + */ + protected $view; + + /** + * @var \OC\Files\Node\Root $root + */ + protected $root; + + /** + * @var string $path + */ + protected $path; + + /** + * @var \OCP\Files\FileInfo + */ + protected $fileInfo; + + /** + * @param \OC\Files\View $view + * @param \OC\Files\Node\Root $root + * @param string $path + * @param FileInfo $fileInfo + */ + public function __construct($root, $view, $path, $fileInfo = null) { + $this->view = $view; + $this->root = $root; + $this->path = $path; + $this->fileInfo = $fileInfo; + } + + /** + * Returns the matching file info + * + * @return FileInfo + * @throws InvalidPathException + * @throws NotFoundException + */ + public function getFileInfo() { + if (!Filesystem::isValidPath($this->path)) { + throw new InvalidPathException(); + } + if (!$this->fileInfo) { + $fileInfo = $this->view->getFileInfo($this->path); + if ($fileInfo instanceof FileInfo) { + $this->fileInfo = $fileInfo; + } else { + throw new NotFoundException(); + } + } + return $this->fileInfo; + } + + /** + * @param string[] $hooks + */ + protected function sendHooks($hooks) { + foreach ($hooks as $hook) { + $this->root->emit('\OC\Files', $hook, array($this)); + } + } + + /** + * @param int $permissions + * @return bool + */ + protected function checkPermissions($permissions) { + return ($this->getPermissions() & $permissions) === $permissions; + } + + /** + * @param string $targetPath + * @throws \OCP\Files\NotPermittedException + * @return \OC\Files\Node\Node + */ + public function move($targetPath) { + return; + } + + public function delete() { + return; + } + + /** + * @param string $targetPath + * @return \OC\Files\Node\Node + */ + public function copy($targetPath) { + return; + } + + /** + * @param int $mtime + * @throws \OCP\Files\NotPermittedException + */ + public function touch($mtime = null) { + if ($this->checkPermissions(\OCP\Constants::PERMISSION_UPDATE)) { + $this->sendHooks(array('preTouch')); + $this->view->touch($this->path, $mtime); + $this->sendHooks(array('postTouch')); + if ($this->fileInfo) { + if (is_null($mtime)) { + $mtime = time(); + } + $this->fileInfo['mtime'] = $mtime; + } + } else { + throw new NotPermittedException(); + } + } + + /** + * @return \OC\Files\Storage\Storage + * @throws \OCP\Files\NotFoundException + */ + public function getStorage() { + list($storage,) = $this->view->resolvePath($this->path); + return $storage; + } + + /** + * @return string + */ + public function getPath() { + return $this->path; + } + + /** + * @return string + */ + public function getInternalPath() { + list(, $internalPath) = $this->view->resolvePath($this->path); + return $internalPath; + } + + /** + * @return int + * @throws InvalidPathException + * @throws NotFoundException + */ + public function getId() { + return $this->getFileInfo()->getId(); + } + + /** + * @return array + */ + public function stat() { + return $this->view->stat($this->path); + } + + /** + * @return int + * @throws InvalidPathException + * @throws NotFoundException + */ + public function getMTime() { + return $this->getFileInfo()->getMTime(); + } + + /** + * @return int + * @throws InvalidPathException + * @throws NotFoundException + */ + public function getSize() { + return $this->getFileInfo()->getSize(); + } + + /** + * @return string + * @throws InvalidPathException + * @throws NotFoundException + */ + public function getEtag() { + return $this->getFileInfo()->getEtag(); + } + + /** + * @return int + * @throws InvalidPathException + * @throws NotFoundException + */ + public function getPermissions() { + return $this->getFileInfo()->getPermissions(); + } + + /** + * @return bool + * @throws InvalidPathException + * @throws NotFoundException + */ + public function isReadable() { + return $this->getFileInfo()->isReadable(); + } + + /** + * @return bool + * @throws InvalidPathException + * @throws NotFoundException + */ + public function isUpdateable() { + return $this->getFileInfo()->isUpdateable(); + } + + /** + * @return bool + * @throws InvalidPathException + * @throws NotFoundException + */ + public function isDeletable() { + return $this->getFileInfo()->isDeletable(); + } + + /** + * @return bool + * @throws InvalidPathException + * @throws NotFoundException + */ + public function isShareable() { + return $this->getFileInfo()->isShareable(); + } + + /** + * @return bool + * @throws InvalidPathException + * @throws NotFoundException + */ + public function isCreatable() { + return $this->getFileInfo()->isCreatable(); + } + + /** + * @return Node + */ + public function getParent() { + return $this->root->get(dirname($this->path)); + } + + /** + * @return string + */ + public function getName() { + return basename($this->path); + } + + /** + * @param string $path + * @return string + */ + protected function normalizePath($path) { + if ($path === '' or $path === '/') { + return '/'; + } + //no windows style slashes + $path = str_replace('\\', '/', $path); + //add leading slash + if ($path[0] !== '/') { + $path = '/' . $path; + } + //remove duplicate slashes + while (strpos($path, '//') !== false) { + $path = str_replace('//', '/', $path); + } + //remove trailing slash + $path = rtrim($path, '/'); + + return $path; + } + + /** + * check if the requested path is valid + * + * @param string $path + * @return bool + */ + public function isValidPath($path) { + if (!$path || $path[0] !== '/') { + $path = '/' . $path; + } + if (strstr($path, '/../') || strrchr($path, '/') === '/..') { + return false; + } + return true; + } + + public function isMounted() { + return $this->getFileInfo()->isMounted(); + } + + public function isShared() { + return $this->getFileInfo()->isShared(); + } + + public function getMimeType() { + return $this->getFileInfo()->getMimetype(); + } + + public function getMimePart() { + return $this->getFileInfo()->getMimePart(); + } + + public function getType() { + return $this->getFileInfo()->getType(); + } + + public function isEncrypted() { + return $this->getFileInfo()->isEncrypted(); + } + + public function getMountPoint() { + return $this->getFileInfo()->getMountPoint(); + } + + public function getOwner() { + return $this->getFileInfo()->getOwner(); + } + + public function getChecksum() { + return; + } + + /** + * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE + * @throws \OCP\Lock\LockedException + */ + public function lock($type) { + $this->view->lockFile($this->path, $type); + } + + /** + * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE + * @throws \OCP\Lock\LockedException + */ + public function changeLock($type) { + $this->view->changeLock($this->path, $type); + } + + /** + * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE + * @throws \OCP\Lock\LockedException + */ + public function unlock($type) { + $this->view->unlockFile($this->path, $type); + } +} diff --git a/lib/private/Files/Node/NonExistingFile.php b/lib/private/Files/Node/NonExistingFile.php new file mode 100644 index 00000000000..c1d09bcc491 --- /dev/null +++ b/lib/private/Files/Node/NonExistingFile.php @@ -0,0 +1,143 @@ + + * @author Robin Appelman + * + * @copyright Copyright (c) 2016, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Files\Node; + +use OCP\Files\NotFoundException; + +class NonExistingFile extends File { + /** + * @param string $newPath + * @throws \OCP\Files\NotFoundException + */ + public function rename($newPath) { + throw new NotFoundException(); + } + + public function delete() { + throw new NotFoundException(); + } + + public function copy($newPath) { + throw new NotFoundException(); + } + + public function touch($mtime = null) { + throw new NotFoundException(); + } + + public function getId() { + if ($this->fileInfo) { + return parent::getId(); + } else { + throw new NotFoundException(); + } + } + + public function stat() { + throw new NotFoundException(); + } + + public function getMTime() { + if ($this->fileInfo) { + return parent::getMTime(); + } else { + throw new NotFoundException(); + } + } + + public function getSize() { + if ($this->fileInfo) { + return parent::getSize(); + } else { + throw new NotFoundException(); + } + } + + public function getEtag() { + if ($this->fileInfo) { + return parent::getEtag(); + } else { + throw new NotFoundException(); + } + } + + public function getPermissions() { + if ($this->fileInfo) { + return parent::getPermissions(); + } else { + throw new NotFoundException(); + } + } + + public function isReadable() { + if ($this->fileInfo) { + return parent::isReadable(); + } else { + throw new NotFoundException(); + } + } + + public function isUpdateable() { + if ($this->fileInfo) { + return parent::isUpdateable(); + } else { + throw new NotFoundException(); + } + } + + public function isDeletable() { + if ($this->fileInfo) { + return parent::isDeletable(); + } else { + throw new NotFoundException(); + } + } + + public function isShareable() { + if ($this->fileInfo) { + return parent::isShareable(); + } else { + throw new NotFoundException(); + } + } + + public function getContent() { + throw new NotFoundException(); + } + + public function putContent($data) { + throw new NotFoundException(); + } + + public function getMimeType() { + if ($this->fileInfo) { + return parent::getMimeType(); + } else { + throw new NotFoundException(); + } + } + + public function fopen($mode) { + throw new NotFoundException(); + } +} diff --git a/lib/private/Files/Node/NonExistingFolder.php b/lib/private/Files/Node/NonExistingFolder.php new file mode 100644 index 00000000000..7d6576f1bd6 --- /dev/null +++ b/lib/private/Files/Node/NonExistingFolder.php @@ -0,0 +1,172 @@ + + * @author Robin Appelman + * @author Vincent Petry + * + * @copyright Copyright (c) 2016, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Files\Node; + +use OCP\Files\NotFoundException; + +class NonExistingFolder extends Folder { + /** + * @param string $newPath + * @throws \OCP\Files\NotFoundException + */ + public function rename($newPath) { + throw new NotFoundException(); + } + + public function delete() { + throw new NotFoundException(); + } + + public function copy($newPath) { + throw new NotFoundException(); + } + + public function touch($mtime = null) { + throw new NotFoundException(); + } + + public function getId() { + if ($this->fileInfo) { + return parent::getId(); + } else { + throw new NotFoundException(); + } + } + + public function stat() { + throw new NotFoundException(); + } + + public function getMTime() { + if ($this->fileInfo) { + return parent::getMTime(); + } else { + throw new NotFoundException(); + } + } + + public function getSize() { + if ($this->fileInfo) { + return parent::getSize(); + } else { + throw new NotFoundException(); + } + } + + public function getEtag() { + if ($this->fileInfo) { + return parent::getEtag(); + } else { + throw new NotFoundException(); + } + } + + public function getPermissions() { + if ($this->fileInfo) { + return parent::getPermissions(); + } else { + throw new NotFoundException(); + } + } + + public function isReadable() { + if ($this->fileInfo) { + return parent::isReadable(); + } else { + throw new NotFoundException(); + } + } + + public function isUpdateable() { + if ($this->fileInfo) { + return parent::isUpdateable(); + } else { + throw new NotFoundException(); + } + } + + public function isDeletable() { + if ($this->fileInfo) { + return parent::isDeletable(); + } else { + throw new NotFoundException(); + } + } + + public function isShareable() { + if ($this->fileInfo) { + return parent::isShareable(); + } else { + throw new NotFoundException(); + } + } + + public function get($path) { + throw new NotFoundException(); + } + + public function getDirectoryListing() { + throw new NotFoundException(); + } + + public function nodeExists($path) { + return false; + } + + public function newFolder($path) { + throw new NotFoundException(); + } + + public function newFile($path) { + throw new NotFoundException(); + } + + public function search($pattern) { + throw new NotFoundException(); + } + + public function searchByMime($mime) { + throw new NotFoundException(); + } + + public function searchByTag($tag, $userId) { + throw new NotFoundException(); + } + + public function getById($id) { + throw new NotFoundException(); + } + + public function getFreeSpace() { + throw new NotFoundException(); + } + + public function isCreatable() { + if ($this->fileInfo) { + return parent::isCreatable(); + } else { + throw new NotFoundException(); + } + } +} diff --git a/lib/private/Files/Node/Root.php b/lib/private/Files/Node/Root.php new file mode 100644 index 00000000000..04866e60b87 --- /dev/null +++ b/lib/private/Files/Node/Root.php @@ -0,0 +1,357 @@ + + * @author Joas Schilling + * @author Jörn Friedrich Dreyer + * @author Morris Jobke + * @author Robin Appelman + * @author Roeland Jago Douma + * + * @copyright Copyright (c) 2016, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Files\Node; + +use OC\Files\Mount\Manager; +use OC\Files\Mount\MountPoint; +use OCP\Files\NotFoundException; +use OCP\Files\NotPermittedException; +use OC\Hooks\PublicEmitter; +use OCP\Files\IRootFolder; + +/** + * Class Root + * + * Hooks available in scope \OC\Files + * - preWrite(\OCP\Files\Node $node) + * - postWrite(\OCP\Files\Node $node) + * - preCreate(\OCP\Files\Node $node) + * - postCreate(\OCP\Files\Node $node) + * - preDelete(\OCP\Files\Node $node) + * - postDelete(\OCP\Files\Node $node) + * - preTouch(\OC\FilesP\Node $node, int $mtime) + * - postTouch(\OCP\Files\Node $node) + * - preCopy(\OCP\Files\Node $source, \OCP\Files\Node $target) + * - postCopy(\OCP\Files\Node $source, \OCP\Files\Node $target) + * - preRename(\OCP\Files\Node $source, \OCP\Files\Node $target) + * - postRename(\OCP\Files\Node $source, \OCP\Files\Node $target) + * + * @package OC\Files\Node + */ +class Root extends Folder implements IRootFolder { + + /** + * @var \OC\Files\Mount\Manager $mountManager + */ + private $mountManager; + + /** + * @var \OC\Hooks\PublicEmitter + */ + private $emitter; + + /** + * @var \OC\User\User $user + */ + private $user; + + /** + * @param \OC\Files\Mount\Manager $manager + * @param \OC\Files\View $view + * @param \OC\User\User|null $user + */ + public function __construct($manager, $view, $user) { + parent::__construct($this, $view, ''); + $this->mountManager = $manager; + $this->user = $user; + $this->emitter = new PublicEmitter(); + } + + /** + * Get the user for which the filesystem is setup + * + * @return \OC\User\User + */ + public function getUser() { + return $this->user; + } + + /** + * @param string $scope + * @param string $method + * @param callable $callback + */ + public function listen($scope, $method, callable $callback) { + $this->emitter->listen($scope, $method, $callback); + } + + /** + * @param string $scope optional + * @param string $method optional + * @param callable $callback optional + */ + public function removeListener($scope = null, $method = null, callable $callback = null) { + $this->emitter->removeListener($scope, $method, $callback); + } + + /** + * @param string $scope + * @param string $method + * @param Node[] $arguments + */ + public function emit($scope, $method, $arguments = array()) { + $this->emitter->emit($scope, $method, $arguments); + } + + /** + * @param \OC\Files\Storage\Storage $storage + * @param string $mountPoint + * @param array $arguments + */ + public function mount($storage, $mountPoint, $arguments = array()) { + $mount = new MountPoint($storage, $mountPoint, $arguments); + $this->mountManager->addMount($mount); + } + + /** + * @param string $mountPoint + * @return \OC\Files\Mount\MountPoint + */ + public function getMount($mountPoint) { + return $this->mountManager->find($mountPoint); + } + + /** + * @param string $mountPoint + * @return \OC\Files\Mount\MountPoint[] + */ + public function getMountsIn($mountPoint) { + return $this->mountManager->findIn($mountPoint); + } + + /** + * @param string $storageId + * @return \OC\Files\Mount\MountPoint[] + */ + public function getMountByStorageId($storageId) { + return $this->mountManager->findByStorageId($storageId); + } + + /** + * @param int $numericId + * @return MountPoint[] + */ + public function getMountByNumericStorageId($numericId) { + return $this->mountManager->findByNumericId($numericId); + } + + /** + * @param \OC\Files\Mount\MountPoint $mount + */ + public function unMount($mount) { + $this->mountManager->remove($mount); + } + + /** + * @param string $path + * @throws \OCP\Files\NotFoundException + * @throws \OCP\Files\NotPermittedException + * @return string + */ + public function get($path) { + $path = $this->normalizePath($path); + if ($this->isValidPath($path)) { + $fullPath = $this->getFullPath($path); + $fileInfo = $this->view->getFileInfo($fullPath); + if ($fileInfo) { + return $this->createNode($fullPath, $fileInfo); + } else { + throw new NotFoundException($path); + } + } else { + throw new NotPermittedException(); + } + } + + //most operations can't be done on the root + + /** + * @param string $targetPath + * @throws \OCP\Files\NotPermittedException + * @return \OC\Files\Node\Node + */ + public function rename($targetPath) { + throw new NotPermittedException(); + } + + public function delete() { + throw new NotPermittedException(); + } + + /** + * @param string $targetPath + * @throws \OCP\Files\NotPermittedException + * @return \OC\Files\Node\Node + */ + public function copy($targetPath) { + throw new NotPermittedException(); + } + + /** + * @param int $mtime + * @throws \OCP\Files\NotPermittedException + */ + public function touch($mtime = null) { + throw new NotPermittedException(); + } + + /** + * @return \OC\Files\Storage\Storage + * @throws \OCP\Files\NotFoundException + */ + public function getStorage() { + throw new NotFoundException(); + } + + /** + * @return string + */ + public function getPath() { + return '/'; + } + + /** + * @return string + */ + public function getInternalPath() { + return ''; + } + + /** + * @return int + */ + public function getId() { + return null; + } + + /** + * @return array + */ + public function stat() { + return null; + } + + /** + * @return int + */ + public function getMTime() { + return null; + } + + /** + * @return int + */ + public function getSize() { + return null; + } + + /** + * @return string + */ + public function getEtag() { + return null; + } + + /** + * @return int + */ + public function getPermissions() { + return \OCP\Constants::PERMISSION_CREATE; + } + + /** + * @return bool + */ + public function isReadable() { + return false; + } + + /** + * @return bool + */ + public function isUpdateable() { + return false; + } + + /** + * @return bool + */ + public function isDeletable() { + return false; + } + + /** + * @return bool + */ + public function isShareable() { + return false; + } + + /** + * @return Node + * @throws \OCP\Files\NotFoundException + */ + public function getParent() { + throw new NotFoundException(); + } + + /** + * @return string + */ + public function getName() { + return ''; + } + + /** + * Returns a view to user's files folder + * + * @param String $userId user ID + * @return \OCP\Files\Folder + */ + public function getUserFolder($userId) { + \OC\Files\Filesystem::initMountPoints($userId); + $dir = '/' . $userId; + $folder = null; + + try { + $folder = $this->get($dir); + } catch (NotFoundException $e) { + $folder = $this->newFolder($dir); + } + + $dir = '/files'; + try { + $folder = $folder->get($dir); + } catch (NotFoundException $e) { + $folder = $folder->newFolder($dir); + \OC_Util::copySkeleton($userId, $folder); + } + + return $folder; + + } +} diff --git a/lib/private/Files/ObjectStore/HomeObjectStoreStorage.php b/lib/private/Files/ObjectStore/HomeObjectStoreStorage.php new file mode 100644 index 00000000000..6a330e2dab3 --- /dev/null +++ b/lib/private/Files/ObjectStore/HomeObjectStoreStorage.php @@ -0,0 +1,68 @@ + + * @author Jörn Friedrich Dreyer + * @author Morris Jobke + * + * @copyright Copyright (c) 2016, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Files\ObjectStore; + +use OC\User\User; + +class HomeObjectStoreStorage extends ObjectStoreStorage implements \OCP\Files\IHomeStorage { + + /** + * The home user storage requires a user object to create a unique storage id + * @param array $params + */ + public function __construct($params) { + if ( ! isset($params['user']) || ! $params['user'] instanceof User) { + throw new \Exception('missing user object in parameters'); + } + $this->user = $params['user']; + parent::__construct($params); + } + + public function getId () { + return 'object::user:' . $this->user->getUID(); + } + + /** + * get the owner of a path + * + * @param string $path The path to get the owner + * @return false|string uid + */ + public function getOwner($path) { + if (is_object($this->user)) { + return $this->user->getUID(); + } + return false; + } + + /** + * @param string $path, optional + * @return \OC\User\User + */ + public function getUser($path = null) { + return $this->user; + } + + +} \ No newline at end of file diff --git a/lib/private/Files/ObjectStore/NoopScanner.php b/lib/private/Files/ObjectStore/NoopScanner.php new file mode 100644 index 00000000000..f5316175ecf --- /dev/null +++ b/lib/private/Files/ObjectStore/NoopScanner.php @@ -0,0 +1,79 @@ + + * @author Jörn Friedrich Dreyer + * @author Morris Jobke + * @author Robin Appelman + * + * @copyright Copyright (c) 2016, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Files\ObjectStore; +use \OC\Files\Cache\Scanner; +use \OC\Files\Storage\Storage; + +class NoopScanner extends Scanner { + + public function __construct(Storage $storage) { + //we don't need the storage, so do nothing here + } + + /** + * scan a single file and store it in the cache + * + * @param string $file + * @param int $reuseExisting + * @param int $parentId + * @param array|null $cacheData existing data in the cache for the file to be scanned + * @return array an array of metadata of the scanned file + */ + public function scanFile($file, $reuseExisting = 0, $parentId = -1, $cacheData = null, $lock = true) { + return array(); + } + + /** + * scan a folder and all it's children + * + * @param string $path + * @param bool $recursive + * @param int $reuse + * @return array with the meta data of the scanned file or folder + */ + public function scan($path, $recursive = self::SCAN_RECURSIVE, $reuse = -1, $lock = true) { + return array(); + } + + /** + * scan all the files and folders in a folder + * + * @param string $path + * @param bool $recursive + * @param int $reuse + * @param array $folderData existing cache data for the folder to be scanned + * @return int the size of the scanned folder or -1 if the size is unknown at this stage + */ + protected function scanChildren($path, $recursive = self::SCAN_RECURSIVE, $reuse = -1, $folderData = null, $lock = true) { + return 0; + } + + /** + * walk over any folders that are not fully scanned yet and scan them + */ + public function backgroundScan() { + //noop + } +} diff --git a/lib/private/Files/ObjectStore/ObjectStoreStorage.php b/lib/private/Files/ObjectStore/ObjectStoreStorage.php new file mode 100644 index 00000000000..8c643dbdcc7 --- /dev/null +++ b/lib/private/Files/ObjectStore/ObjectStoreStorage.php @@ -0,0 +1,407 @@ + + * @author Jörn Friedrich Dreyer + * @author Morris Jobke + * @author Robin Appelman + * + * @copyright Copyright (c) 2016, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Files\ObjectStore; + +use Icewind\Streams\IteratorDirectory; +use OC\Files\Cache\CacheEntry; +use OCP\Files\ObjectStore\IObjectStore; + +class ObjectStoreStorage extends \OC\Files\Storage\Common { + + /** + * @var array + */ + private static $tmpFiles = array(); + /** + * @var \OCP\Files\ObjectStore\IObjectStore $objectStore + */ + protected $objectStore; + /** + * @var string $id + */ + protected $id; + /** + * @var \OC\User\User $user + */ + protected $user; + + public function __construct($params) { + if (isset($params['objectstore']) && $params['objectstore'] instanceof IObjectStore) { + $this->objectStore = $params['objectstore']; + } else { + throw new \Exception('missing IObjectStore instance'); + } + if (isset($params['storageid'])) { + $this->id = 'object::store:' . $params['storageid']; + } else { + $this->id = 'object::store:' . $this->objectStore->getStorageId(); + } + //initialize cache with root directory in cache + if (!$this->is_dir('/')) { + $this->mkdir('/'); + } + } + + public function mkdir($path) { + $path = $this->normalizePath($path); + + if ($this->file_exists($path)) { + return false; + } + + $mTime = time(); + $data = [ + 'mimetype' => 'httpd/unix-directory', + 'size' => 0, + 'mtime' => $mTime, + 'storage_mtime' => $mTime, + 'permissions' => \OCP\Constants::PERMISSION_ALL, + ]; + if ($path === '') { + //create root on the fly + $data['etag'] = $this->getETag(''); + $this->getCache()->put('', $data); + return true; + } else { + // if parent does not exist, create it + $parent = $this->normalizePath(dirname($path)); + $parentType = $this->filetype($parent); + if ($parentType === false) { + if (!$this->mkdir($parent)) { + // something went wrong + return false; + } + } else if ($parentType === 'file') { + // parent is a file + return false; + } + // finally create the new dir + $mTime = time(); // update mtime + $data['mtime'] = $mTime; + $data['storage_mtime'] = $mTime; + $data['etag'] = $this->getETag($path); + $this->getCache()->put($path, $data); + return true; + } + } + + /** + * @param string $path + * @return string + */ + private function normalizePath($path) { + $path = trim($path, '/'); + //FIXME why do we sometimes get a path like 'files//username'? + $path = str_replace('//', '/', $path); + + // dirname('/folder') returns '.' but internally (in the cache) we store the root as '' + if (!$path || $path === '.') { + $path = ''; + } + + return $path; + } + + /** + * Object Stores use a NoopScanner because metadata is directly stored in + * the file cache and cannot really scan the filesystem. The storage passed in is not used anywhere. + * + * @param string $path + * @param \OC\Files\Storage\Storage (optional) the storage to pass to the scanner + * @return \OC\Files\ObjectStore\NoopScanner + */ + public function getScanner($path = '', $storage = null) { + if (!$storage) { + $storage = $this; + } + if (!isset($this->scanner)) { + $this->scanner = new NoopScanner($storage); + } + return $this->scanner; + } + + public function getId() { + return $this->id; + } + + public function rmdir($path) { + $path = $this->normalizePath($path); + + if (!$this->is_dir($path)) { + return false; + } + + $this->rmObjects($path); + + $this->getCache()->remove($path); + + return true; + } + + private function rmObjects($path) { + $children = $this->getCache()->getFolderContents($path); + foreach ($children as $child) { + if ($child['mimetype'] === 'httpd/unix-directory') { + $this->rmObjects($child['path']); + } else { + $this->unlink($child['path']); + } + } + } + + public function unlink($path) { + $path = $this->normalizePath($path); + $stat = $this->stat($path); + + if ($stat && isset($stat['fileid'])) { + if ($stat['mimetype'] === 'httpd/unix-directory') { + return $this->rmdir($path); + } + try { + $this->objectStore->deleteObject($this->getURN($stat['fileid'])); + } catch (\Exception $ex) { + if ($ex->getCode() !== 404) { + \OCP\Util::writeLog('objectstore', 'Could not delete object: ' . $ex->getMessage(), \OCP\Util::ERROR); + return false; + } else { + //removing from cache is ok as it does not exist in the objectstore anyway + } + } + $this->getCache()->remove($path); + return true; + } + return false; + } + + public function stat($path) { + $path = $this->normalizePath($path); + $cacheEntry = $this->getCache()->get($path); + if ($cacheEntry instanceof CacheEntry) { + return $cacheEntry->getData(); + } else { + return false; + } + } + + /** + * Override this method if you need a different unique resource identifier for your object storage implementation. + * The default implementations just appends the fileId to 'urn:oid:'. Make sure the URN is unique over all users. + * You may need a mapping table to store your URN if it cannot be generated from the fileid. + * + * @param int $fileId the fileid + * @return null|string the unified resource name used to identify the object + */ + protected function getURN($fileId) { + if (is_numeric($fileId)) { + return 'urn:oid:' . $fileId; + } + return null; + } + + public function opendir($path) { + $path = $this->normalizePath($path); + + try { + $files = array(); + $folderContents = $this->getCache()->getFolderContents($path); + foreach ($folderContents as $file) { + $files[] = $file['name']; + } + + return IteratorDirectory::wrap($files); + } catch (\Exception $e) { + \OCP\Util::writeLog('objectstore', $e->getMessage(), \OCP\Util::ERROR); + return false; + } + } + + public function filetype($path) { + $path = $this->normalizePath($path); + $stat = $this->stat($path); + if ($stat) { + if ($stat['mimetype'] === 'httpd/unix-directory') { + return 'dir'; + } + return 'file'; + } else { + return false; + } + } + + public function fopen($path, $mode) { + $path = $this->normalizePath($path); + + switch ($mode) { + case 'r': + case 'rb': + $stat = $this->stat($path); + if (is_array($stat)) { + try { + return $this->objectStore->readObject($this->getURN($stat['fileid'])); + } catch (\Exception $ex) { + \OCP\Util::writeLog('objectstore', 'Could not get object: ' . $ex->getMessage(), \OCP\Util::ERROR); + return false; + } + } else { + return false; + } + case 'w': + case 'wb': + case 'a': + case 'ab': + case 'r+': + case 'w+': + case 'wb+': + case 'a+': + case 'x': + case 'x+': + case 'c': + case 'c+': + if (strrpos($path, '.') !== false) { + $ext = substr($path, strrpos($path, '.')); + } else { + $ext = ''; + } + $tmpFile = \OC::$server->getTempManager()->getTemporaryFile($ext); + \OC\Files\Stream\Close::registerCallback($tmpFile, array($this, 'writeBack')); + if ($this->file_exists($path)) { + $source = $this->fopen($path, 'r'); + file_put_contents($tmpFile, $source); + } + self::$tmpFiles[$tmpFile] = $path; + + return fopen('close://' . $tmpFile, $mode); + } + return false; + } + + public function file_exists($path) { + $path = $this->normalizePath($path); + return (bool)$this->stat($path); + } + + public function rename($source, $target) { + $source = $this->normalizePath($source); + $target = $this->normalizePath($target); + $this->remove($target); + $this->getCache()->move($source, $target); + $this->touch(dirname($target)); + return true; + } + + public function getMimeType($path) { + $path = $this->normalizePath($path); + $stat = $this->stat($path); + if (is_array($stat)) { + return $stat['mimetype']; + } else { + return false; + } + } + + public function touch($path, $mtime = null) { + if (is_null($mtime)) { + $mtime = time(); + } + + $path = $this->normalizePath($path); + $dirName = dirname($path); + $parentExists = $this->is_dir($dirName); + if (!$parentExists) { + return false; + } + + $stat = $this->stat($path); + if (is_array($stat)) { + // update existing mtime in db + $stat['mtime'] = $mtime; + $this->getCache()->update($stat['fileid'], $stat); + } else { + $mimeType = \OC::$server->getMimeTypeDetector()->detectPath($path); + // create new file + $stat = array( + 'etag' => $this->getETag($path), + 'mimetype' => $mimeType, + 'size' => 0, + 'mtime' => $mtime, + 'storage_mtime' => $mtime, + 'permissions' => \OCP\Constants::PERMISSION_ALL - \OCP\Constants::PERMISSION_CREATE, + ); + $fileId = $this->getCache()->put($path, $stat); + try { + //read an empty file from memory + $this->objectStore->writeObject($this->getURN($fileId), fopen('php://memory', 'r')); + } catch (\Exception $ex) { + $this->getCache()->remove($path); + \OCP\Util::writeLog('objectstore', 'Could not create object: ' . $ex->getMessage(), \OCP\Util::ERROR); + return false; + } + } + return true; + } + + public function writeBack($tmpFile) { + if (!isset(self::$tmpFiles[$tmpFile])) { + return; + } + + $path = self::$tmpFiles[$tmpFile]; + $stat = $this->stat($path); + if (empty($stat)) { + // create new file + $stat = array( + 'permissions' => \OCP\Constants::PERMISSION_ALL - \OCP\Constants::PERMISSION_CREATE, + ); + } + // update stat with new data + $mTime = time(); + $stat['size'] = filesize($tmpFile); + $stat['mtime'] = $mTime; + $stat['storage_mtime'] = $mTime; + $stat['mimetype'] = \OC::$server->getMimeTypeDetector()->detect($tmpFile); + $stat['etag'] = $this->getETag($path); + + $fileId = $this->getCache()->put($path, $stat); + try { + //upload to object storage + $this->objectStore->writeObject($this->getURN($fileId), fopen($tmpFile, 'r')); + } catch (\Exception $ex) { + $this->getCache()->remove($path); + \OCP\Util::writeLog('objectstore', 'Could not create object: ' . $ex->getMessage(), \OCP\Util::ERROR); + throw $ex; // make this bubble up + } + } + + /** + * external changes are not supported, exclusive access to the object storage is assumed + * + * @param string $path + * @param int $time + * @return false + */ + public function hasUpdated($path, $time) { + return false; + } +} diff --git a/lib/private/Files/ObjectStore/Swift.php b/lib/private/Files/ObjectStore/Swift.php new file mode 100644 index 00000000000..4af09dca254 --- /dev/null +++ b/lib/private/Files/ObjectStore/Swift.php @@ -0,0 +1,154 @@ + + * @author Morris Jobke + * @author Robin Appelman + * + * @copyright Copyright (c) 2016, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Files\ObjectStore; + +use Guzzle\Http\Exception\ClientErrorResponseException; +use OCP\Files\ObjectStore\IObjectStore; +use OpenCloud\OpenStack; +use OpenCloud\Rackspace; + +class Swift implements IObjectStore { + /** + * @var \OpenCloud\OpenStack + */ + private $client; + + /** + * @var array + */ + private $params; + + /** + * @var \OpenCloud\ObjectStore\Service + */ + private $objectStoreService; + + /** + * @var \OpenCloud\ObjectStore\Resource\Container + */ + private $container; + + public function __construct($params) { + if (!isset($params['container'])) { + $params['container'] = 'owncloud'; + } + if (!isset($params['autocreate'])) { + // should only be true for tests + $params['autocreate'] = false; + } + + if (isset($params['apiKey'])) { + $this->client = new Rackspace($params['url'], $params); + } else { + $this->client = new OpenStack($params['url'], $params); + } + $this->params = $params; + } + + protected function init() { + if ($this->container) { + return; + } + + // the OpenCloud client library will default to 'cloudFiles' if $serviceName is null + $serviceName = null; + if (isset($this->params['serviceName'])) { + $serviceName = $this->params['serviceName']; + } + + // the OpenCloud client library will default to 'publicURL' if $urlType is null + $urlType = null; + if (isset($this->params['urlType'])) { + $urlType = $this->params['urlType']; + } + $this->objectStoreService = $this->client->objectStoreService($serviceName, $this->params['region'], $urlType); + + try { + $this->container = $this->objectStoreService->getContainer($this->params['container']); + } catch (ClientErrorResponseException $ex) { + // if the container does not exist and autocreate is true try to create the container on the fly + if (isset($this->params['autocreate']) && $this->params['autocreate'] === true) { + $this->container = $this->objectStoreService->createContainer($this->params['container']); + } else { + throw $ex; + } + } + } + + /** + * @return string the container name where objects are stored + */ + public function getStorageId() { + return $this->params['container']; + } + + /** + * @param string $urn the unified resource name used to identify the object + * @param resource $stream stream with the data to write + * @throws Exception from openstack lib when something goes wrong + */ + public function writeObject($urn, $stream) { + $this->init(); + $this->container->uploadObject($urn, $stream); + } + + /** + * @param string $urn the unified resource name used to identify the object + * @return resource stream with the read data + * @throws Exception from openstack lib when something goes wrong + */ + public function readObject($urn) { + $this->init(); + $object = $this->container->getObject($urn); + + // we need to keep a reference to objectContent or + // the stream will be closed before we can do anything with it + /** @var $objectContent \Guzzle\Http\EntityBody * */ + $objectContent = $object->getContent(); + $objectContent->rewind(); + + $stream = $objectContent->getStream(); + // save the object content in the context of the stream to prevent it being gc'd until the stream is closed + stream_context_set_option($stream, 'swift','content', $objectContent); + + return $stream; + } + + /** + * @param string $urn Unified Resource Name + * @return void + * @throws Exception from openstack lib when something goes wrong + */ + public function deleteObject($urn) { + $this->init(); + // see https://github.com/rackspace/php-opencloud/issues/243#issuecomment-30032242 + $this->container->dataObject()->setName($urn)->delete(); + } + + public function deleteContainer($recursive = false) { + $this->init(); + $this->container->delete($recursive); + } + +} diff --git a/lib/private/Files/Storage/Common.php b/lib/private/Files/Storage/Common.php new file mode 100644 index 00000000000..3a811b312c6 --- /dev/null +++ b/lib/private/Files/Storage/Common.php @@ -0,0 +1,697 @@ + + * @author Bart Visscher + * @author Björn Schießle + * @author hkjolhede + * @author Joas Schilling + * @author Jörn Friedrich Dreyer + * @author Lukas Reschke + * @author Martin Mattel + * @author Michael Gapczynski + * @author Morris Jobke + * @author Robin Appelman + * @author Robin McCorkell + * @author Sam Tuke + * @author scambra + * @author Thomas Müller + * @author Vincent Petry + * + * @copyright Copyright (c) 2016, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Files\Storage; + +use OC\Files\Cache\Cache; +use OC\Files\Cache\Propagator; +use OC\Files\Cache\Scanner; +use OC\Files\Cache\Updater; +use OC\Files\Filesystem; +use OC\Files\Cache\Watcher; +use OCP\Files\FileNameTooLongException; +use OCP\Files\InvalidCharacterInPathException; +use OCP\Files\InvalidPathException; +use OCP\Files\ReservedWordException; +use OCP\Files\Storage\ILockingStorage; +use OCP\Lock\ILockingProvider; + +/** + * Storage backend class for providing common filesystem operation methods + * which are not storage-backend specific. + * + * \OC\Files\Storage\Common is never used directly; it is extended by all other + * storage backends, where its methods may be overridden, and additional + * (backend-specific) methods are defined. + * + * Some \OC\Files\Storage\Common methods call functions which are first defined + * in classes which extend it, e.g. $this->stat() . + */ +abstract class Common implements Storage, ILockingStorage { + + use LocalTempFileTrait; + + protected $cache; + protected $scanner; + protected $watcher; + protected $propagator; + protected $storageCache; + protected $updater; + + protected $mountOptions = []; + protected $owner = null; + + public function __construct($parameters) { + } + + /** + * Remove a file or folder + * + * @param string $path + * @return bool + */ + protected function remove($path) { + if ($this->is_dir($path)) { + return $this->rmdir($path); + } else if ($this->is_file($path)) { + return $this->unlink($path); + } else { + return false; + } + } + + public function is_dir($path) { + return $this->filetype($path) === 'dir'; + } + + public function is_file($path) { + return $this->filetype($path) === 'file'; + } + + public function filesize($path) { + if ($this->is_dir($path)) { + return 0; //by definition + } else { + $stat = $this->stat($path); + if (isset($stat['size'])) { + return $stat['size']; + } else { + return 0; + } + } + } + + public function isReadable($path) { + // at least check whether it exists + // subclasses might want to implement this more thoroughly + return $this->file_exists($path); + } + + public function isUpdatable($path) { + // at least check whether it exists + // subclasses might want to implement this more thoroughly + // a non-existing file/folder isn't updatable + return $this->file_exists($path); + } + + public function isCreatable($path) { + if ($this->is_dir($path) && $this->isUpdatable($path)) { + return true; + } + return false; + } + + public function isDeletable($path) { + if ($path === '' || $path === '/') { + return false; + } + $parent = dirname($path); + return $this->isUpdatable($parent) && $this->isUpdatable($path); + } + + public function isSharable($path) { + return $this->isReadable($path); + } + + public function getPermissions($path) { + $permissions = 0; + if ($this->isCreatable($path)) { + $permissions |= \OCP\Constants::PERMISSION_CREATE; + } + if ($this->isReadable($path)) { + $permissions |= \OCP\Constants::PERMISSION_READ; + } + if ($this->isUpdatable($path)) { + $permissions |= \OCP\Constants::PERMISSION_UPDATE; + } + if ($this->isDeletable($path)) { + $permissions |= \OCP\Constants::PERMISSION_DELETE; + } + if ($this->isSharable($path)) { + $permissions |= \OCP\Constants::PERMISSION_SHARE; + } + return $permissions; + } + + public function filemtime($path) { + $stat = $this->stat($path); + if (isset($stat['mtime']) && $stat['mtime'] > 0) { + return $stat['mtime']; + } else { + return 0; + } + } + + public function file_get_contents($path) { + $handle = $this->fopen($path, "r"); + if (!$handle) { + return false; + } + $data = stream_get_contents($handle); + fclose($handle); + return $data; + } + + public function file_put_contents($path, $data) { + $handle = $this->fopen($path, "w"); + $this->removeCachedFile($path); + $count = fwrite($handle, $data); + fclose($handle); + return $count; + } + + public function rename($path1, $path2) { + $this->remove($path2); + + $this->removeCachedFile($path1); + return $this->copy($path1, $path2) and $this->remove($path1); + } + + public function copy($path1, $path2) { + if ($this->is_dir($path1)) { + $this->remove($path2); + $dir = $this->opendir($path1); + $this->mkdir($path2); + while ($file = readdir($dir)) { + if (!Filesystem::isIgnoredDir($file)) { + if (!$this->copy($path1 . '/' . $file, $path2 . '/' . $file)) { + return false; + } + } + } + closedir($dir); + return true; + } else { + $source = $this->fopen($path1, 'r'); + $target = $this->fopen($path2, 'w'); + list(, $result) = \OC_Helper::streamCopy($source, $target); + $this->removeCachedFile($path2); + return $result; + } + } + + public function getMimeType($path) { + if ($this->is_dir($path)) { + return 'httpd/unix-directory'; + } elseif ($this->file_exists($path)) { + return \OC::$server->getMimeTypeDetector()->detectPath($path); + } else { + return false; + } + } + + public function hash($type, $path, $raw = false) { + $fh = $this->fopen($path, 'rb'); + $ctx = hash_init($type); + hash_update_stream($ctx, $fh); + fclose($fh); + return hash_final($ctx, $raw); + } + + public function search($query) { + return $this->searchInDir($query); + } + + public function getLocalFile($path) { + return $this->getCachedFile($path); + } + + /** + * @param string $path + * @param string $target + */ + private function addLocalFolder($path, $target) { + $dh = $this->opendir($path); + if (is_resource($dh)) { + while (($file = readdir($dh)) !== false) { + if (!\OC\Files\Filesystem::isIgnoredDir($file)) { + if ($this->is_dir($path . '/' . $file)) { + mkdir($target . '/' . $file); + $this->addLocalFolder($path . '/' . $file, $target . '/' . $file); + } else { + $tmp = $this->toTmpFile($path . '/' . $file); + rename($tmp, $target . '/' . $file); + } + } + } + } + } + + /** + * @param string $query + * @param string $dir + * @return array + */ + protected function searchInDir($query, $dir = '') { + $files = array(); + $dh = $this->opendir($dir); + if (is_resource($dh)) { + while (($item = readdir($dh)) !== false) { + if (\OC\Files\Filesystem::isIgnoredDir($item)) continue; + if (strstr(strtolower($item), strtolower($query)) !== false) { + $files[] = $dir . '/' . $item; + } + if ($this->is_dir($dir . '/' . $item)) { + $files = array_merge($files, $this->searchInDir($query, $dir . '/' . $item)); + } + } + } + closedir($dh); + return $files; + } + + /** + * check if a file or folder has been updated since $time + * + * The method is only used to check if the cache needs to be updated. Storage backends that don't support checking + * the mtime should always return false here. As a result storage implementations that always return false expect + * exclusive access to the backend and will not pick up files that have been added in a way that circumvents + * ownClouds filesystem. + * + * @param string $path + * @param int $time + * @return bool + */ + public function hasUpdated($path, $time) { + return $this->filemtime($path) > $time; + } + + public function getCache($path = '', $storage = null) { + if (!$storage) { + $storage = $this; + } + if (!isset($storage->cache)) { + $storage->cache = new Cache($storage); + } + return $storage->cache; + } + + public function getScanner($path = '', $storage = null) { + if (!$storage) { + $storage = $this; + } + if (!isset($storage->scanner)) { + $storage->scanner = new Scanner($storage); + } + return $storage->scanner; + } + + public function getWatcher($path = '', $storage = null) { + if (!$storage) { + $storage = $this; + } + if (!isset($this->watcher)) { + $this->watcher = new Watcher($storage); + $globalPolicy = \OC::$server->getConfig()->getSystemValue('filesystem_check_changes', Watcher::CHECK_NEVER); + $this->watcher->setPolicy((int)$this->getMountOption('filesystem_check_changes', $globalPolicy)); + } + return $this->watcher; + } + + /** + * get a propagator instance for the cache + * + * @param \OC\Files\Storage\Storage (optional) the storage to pass to the watcher + * @return \OC\Files\Cache\Propagator + */ + public function getPropagator($storage = null) { + if (!$storage) { + $storage = $this; + } + if (!isset($storage->propagator)) { + $storage->propagator = new Propagator($storage); + } + return $storage->propagator; + } + + public function getUpdater($storage = null) { + if (!$storage) { + $storage = $this; + } + if (!isset($storage->updater)) { + $storage->updater = new Updater($storage); + } + return $storage->updater; + } + + public function getStorageCache($storage = null) { + if (!$storage) { + $storage = $this; + } + if (!isset($this->storageCache)) { + $this->storageCache = new \OC\Files\Cache\Storage($storage); + } + return $this->storageCache; + } + + /** + * get the owner of a path + * + * @param string $path The path to get the owner + * @return string|false uid or false + */ + public function getOwner($path) { + if ($this->owner === null) { + $this->owner = \OC_User::getUser(); + } + + return $this->owner; + } + + /** + * get the ETag for a file or folder + * + * @param string $path + * @return string + */ + public function getETag($path) { + return uniqid(); + } + + /** + * clean a path, i.e. remove all redundant '.' and '..' + * making sure that it can't point to higher than '/' + * + * @param string $path The path to clean + * @return string cleaned path + */ + public function cleanPath($path) { + if (strlen($path) == 0 or $path[0] != '/') { + $path = '/' . $path; + } + + $output = array(); + foreach (explode('/', $path) as $chunk) { + if ($chunk == '..') { + array_pop($output); + } else if ($chunk == '.') { + } else { + $output[] = $chunk; + } + } + return implode('/', $output); + } + + /** + * Test a storage for availability + * + * @return bool + */ + public function test() { + if ($this->stat('')) { + return true; + } + return false; + } + + /** + * get the free space in the storage + * + * @param string $path + * @return int|false + */ + public function free_space($path) { + return \OCP\Files\FileInfo::SPACE_UNKNOWN; + } + + /** + * {@inheritdoc} + */ + public function isLocal() { + // the common implementation returns a temporary file by + // default, which is not local + return false; + } + + /** + * Check if the storage is an instance of $class or is a wrapper for a storage that is an instance of $class + * + * @param string $class + * @return bool + */ + public function instanceOfStorage($class) { + return is_a($this, $class); + } + + /** + * A custom storage implementation can return an url for direct download of a give file. + * + * For now the returned array can hold the parameter url - in future more attributes might follow. + * + * @param string $path + * @return array|false + */ + public function getDirectDownload($path) { + return []; + } + + /** + * @inheritdoc + */ + public function verifyPath($path, $fileName) { + if (isset($fileName[255])) { + throw new FileNameTooLongException(); + } + + // NOTE: $path will remain unverified for now + if (\OC_Util::runningOnWindows()) { + $this->verifyWindowsPath($fileName); + } else { + $this->verifyPosixPath($fileName); + } + } + + /** + * https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247%28v=vs.85%29.aspx + * @param string $fileName + * @throws InvalidPathException + */ + protected function verifyWindowsPath($fileName) { + $fileName = trim($fileName); + $this->scanForInvalidCharacters($fileName, "\\/<>:\"|?*"); + $reservedNames = ['CON', 'PRN', 'AUX', 'NUL', 'COM1', 'COM2', 'COM3', 'COM4', 'COM5', 'COM6', 'COM7', 'COM8', 'COM9', 'LPT1', 'LPT2', 'LPT3', 'LPT4', 'LPT5', 'LPT6', 'LPT7', 'LPT8', 'LPT9']; + if (in_array(strtoupper($fileName), $reservedNames)) { + throw new ReservedWordException(); + } + } + + /** + * @param string $fileName + * @throws InvalidPathException + */ + protected function verifyPosixPath($fileName) { + $fileName = trim($fileName); + $this->scanForInvalidCharacters($fileName, "\\/"); + $reservedNames = ['*']; + if (in_array($fileName, $reservedNames)) { + throw new ReservedWordException(); + } + } + + /** + * @param string $fileName + * @param string $invalidChars + * @throws InvalidPathException + */ + private function scanForInvalidCharacters($fileName, $invalidChars) { + foreach (str_split($invalidChars) as $char) { + if (strpos($fileName, $char) !== false) { + throw new InvalidCharacterInPathException(); + } + } + + $sanitizedFileName = filter_var($fileName, FILTER_UNSAFE_RAW, FILTER_FLAG_STRIP_LOW); + if ($sanitizedFileName !== $fileName) { + throw new InvalidCharacterInPathException(); + } + } + + /** + * @param array $options + */ + public function setMountOptions(array $options) { + $this->mountOptions = $options; + } + + /** + * @param string $name + * @param mixed $default + * @return mixed + */ + public function getMountOption($name, $default = null) { + return isset($this->mountOptions[$name]) ? $this->mountOptions[$name] : $default; + } + + /** + * @param \OCP\Files\Storage $sourceStorage + * @param string $sourceInternalPath + * @param string $targetInternalPath + * @param bool $preserveMtime + * @return bool + */ + public function copyFromStorage(\OCP\Files\Storage $sourceStorage, $sourceInternalPath, $targetInternalPath, $preserveMtime = false) { + if ($sourceStorage === $this) { + return $this->copy($sourceInternalPath, $targetInternalPath); + } + + if ($sourceStorage->is_dir($sourceInternalPath)) { + $dh = $sourceStorage->opendir($sourceInternalPath); + $result = $this->mkdir($targetInternalPath); + if (is_resource($dh)) { + while ($result and ($file = readdir($dh)) !== false) { + if (!Filesystem::isIgnoredDir($file)) { + $result &= $this->copyFromStorage($sourceStorage, $sourceInternalPath . '/' . $file, $targetInternalPath . '/' . $file); + } + } + } + } else { + $source = $sourceStorage->fopen($sourceInternalPath, 'r'); + // TODO: call fopen in a way that we execute again all storage wrappers + // to avoid that we bypass storage wrappers which perform important actions + // for this operation. Same is true for all other operations which + // are not the same as the original one.Once this is fixed we also + // need to adjust the encryption wrapper. + $target = $this->fopen($targetInternalPath, 'w'); + list(, $result) = \OC_Helper::streamCopy($source, $target); + if ($result and $preserveMtime) { + $this->touch($targetInternalPath, $sourceStorage->filemtime($sourceInternalPath)); + } + fclose($source); + fclose($target); + + if (!$result) { + // delete partially written target file + $this->unlink($targetInternalPath); + // delete cache entry that was created by fopen + $this->getCache()->remove($targetInternalPath); + } + } + return (bool)$result; + } + + /** + * @param \OCP\Files\Storage $sourceStorage + * @param string $sourceInternalPath + * @param string $targetInternalPath + * @return bool + */ + public function moveFromStorage(\OCP\Files\Storage $sourceStorage, $sourceInternalPath, $targetInternalPath) { + if ($sourceStorage === $this) { + return $this->rename($sourceInternalPath, $targetInternalPath); + } + + if (!$sourceStorage->isDeletable($sourceInternalPath)) { + return false; + } + + $result = $this->copyFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath, true); + if ($result) { + if ($sourceStorage->is_dir($sourceInternalPath)) { + $result &= $sourceStorage->rmdir($sourceInternalPath); + } else { + $result &= $sourceStorage->unlink($sourceInternalPath); + } + } + return $result; + } + + /** + * @inheritdoc + */ + public function getMetaData($path) { + $permissions = $this->getPermissions($path); + if (!$permissions & \OCP\Constants::PERMISSION_READ) { + //can't read, nothing we can do + return null; + } + + $data = []; + $data['mimetype'] = $this->getMimeType($path); + $data['mtime'] = $this->filemtime($path); + if ($data['mimetype'] == 'httpd/unix-directory') { + $data['size'] = -1; //unknown + } else { + $data['size'] = $this->filesize($path); + } + $data['etag'] = $this->getETag($path); + $data['storage_mtime'] = $data['mtime']; + $data['permissions'] = $permissions; + + return $data; + } + + /** + * @param string $path + * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE + * @param \OCP\Lock\ILockingProvider $provider + * @throws \OCP\Lock\LockedException + */ + public function acquireLock($path, $type, ILockingProvider $provider) { + $provider->acquireLock('files/' . md5($this->getId() . '::' . trim($path, '/')), $type); + } + + /** + * @param string $path + * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE + * @param \OCP\Lock\ILockingProvider $provider + */ + public function releaseLock($path, $type, ILockingProvider $provider) { + $provider->releaseLock('files/' . md5($this->getId() . '::' . trim($path, '/')), $type); + } + + /** + * @param string $path + * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE + * @param \OCP\Lock\ILockingProvider $provider + */ + public function changeLock($path, $type, ILockingProvider $provider) { + $provider->changeLock('files/' . md5($this->getId() . '::' . trim($path, '/')), $type); + } + + /** + * @return array [ available, last_checked ] + */ + public function getAvailability() { + return $this->getStorageCache()->getAvailability(); + } + + /** + * @param bool $isAvailable + */ + public function setAvailability($isAvailable) { + $this->getStorageCache()->setAvailability($isAvailable); + } +} diff --git a/lib/private/Files/Storage/CommonTest.php b/lib/private/Files/Storage/CommonTest.php new file mode 100644 index 00000000000..0047a51169c --- /dev/null +++ b/lib/private/Files/Storage/CommonTest.php @@ -0,0 +1,84 @@ + + * @author Christopher Schäpers + * @author Felix Moeller + * @author Michael Gapczynski + * @author Morris Jobke + * @author Robin Appelman + * @author Thomas Müller + * + * @copyright Copyright (c) 2016, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +/** + * test implementation for \OC\Files\Storage\Common with \OC\Files\Storage\Local + */ + +namespace OC\Files\Storage; + +class CommonTest extends \OC\Files\Storage\Common{ + /** + * underlying local storage used for missing functions + * @var \OC\Files\Storage\Local + */ + private $storage; + + public function __construct($params) { + $this->storage=new \OC\Files\Storage\Local($params); + } + + public function getId(){ + return 'test::'.$this->storage->getId(); + } + public function mkdir($path) { + return $this->storage->mkdir($path); + } + public function rmdir($path) { + return $this->storage->rmdir($path); + } + public function opendir($path) { + return $this->storage->opendir($path); + } + public function stat($path) { + return $this->storage->stat($path); + } + public function filetype($path) { + return @$this->storage->filetype($path); + } + public function isReadable($path) { + return $this->storage->isReadable($path); + } + public function isUpdatable($path) { + return $this->storage->isUpdatable($path); + } + public function file_exists($path) { + return $this->storage->file_exists($path); + } + public function unlink($path) { + return $this->storage->unlink($path); + } + public function fopen($path, $mode) { + return $this->storage->fopen($path, $mode); + } + public function free_space($path) { + return $this->storage->free_space($path); + } + public function touch($path, $mtime=null) { + return $this->storage->touch($path, $mtime); + } +} diff --git a/lib/private/Files/Storage/DAV.php b/lib/private/Files/Storage/DAV.php new file mode 100644 index 00000000000..8eebea1f3ba --- /dev/null +++ b/lib/private/Files/Storage/DAV.php @@ -0,0 +1,817 @@ + + * @author Björn Schießle + * @author Carlos Cerrillo + * @author Felix Moeller + * @author Jörn Friedrich Dreyer + * @author Lukas Reschke + * @author Michael Gapczynski + * @author Morris Jobke + * @author Philipp Kapfer + * @author Robin Appelman + * @author Thomas Müller + * @author Vincent Petry + * + * @copyright Copyright (c) 2016, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Files\Storage; + +use Exception; +use GuzzleHttp\Exception\RequestException; +use GuzzleHttp\Message\ResponseInterface; +use OC\Files\Filesystem; +use OC\Files\Stream\Close; +use Icewind\Streams\IteratorDirectory; +use OC\MemCache\ArrayCache; +use OCP\AppFramework\Http; +use OCP\Constants; +use OCP\Files; +use OCP\Files\FileInfo; +use OCP\Files\StorageInvalidException; +use OCP\Files\StorageNotAvailableException; +use OCP\Util; +use Sabre\DAV\Client; +use Sabre\DAV\Exception\NotFound; +use Sabre\DAV\Xml\Property\ResourceType; +use Sabre\HTTP\ClientException; +use Sabre\HTTP\ClientHttpException; + +/** + * Class DAV + * + * @package OC\Files\Storage + */ +class DAV extends Common { + /** @var string */ + protected $password; + /** @var string */ + protected $user; + /** @var string */ + protected $host; + /** @var bool */ + protected $secure; + /** @var string */ + protected $root; + /** @var string */ + protected $certPath; + /** @var bool */ + protected $ready; + /** @var Client */ + private $client; + /** @var ArrayCache */ + private $statCache; + /** @var array */ + private static $tempFiles = []; + /** @var \OCP\Http\Client\IClientService */ + private $httpClientService; + + /** + * @param array $params + * @throws \Exception + */ + public function __construct($params) { + $this->statCache = new ArrayCache(); + $this->httpClientService = \OC::$server->getHTTPClientService(); + if (isset($params['host']) && isset($params['user']) && isset($params['password'])) { + $host = $params['host']; + //remove leading http[s], will be generated in createBaseUri() + if (substr($host, 0, 8) == "https://") $host = substr($host, 8); + else if (substr($host, 0, 7) == "http://") $host = substr($host, 7); + $this->host = $host; + $this->user = $params['user']; + $this->password = $params['password']; + if (isset($params['secure'])) { + if (is_string($params['secure'])) { + $this->secure = ($params['secure'] === 'true'); + } else { + $this->secure = (bool)$params['secure']; + } + } else { + $this->secure = false; + } + if ($this->secure === true) { + // inject mock for testing + $certPath = \OC_User::getHome(\OC_User::getUser()) . '/files_external/rootcerts.crt'; + if (file_exists($certPath)) { + $this->certPath = $certPath; + } + } + $this->root = isset($params['root']) ? $params['root'] : '/'; + if (!$this->root || $this->root[0] != '/') { + $this->root = '/' . $this->root; + } + if (substr($this->root, -1, 1) != '/') { + $this->root .= '/'; + } + } else { + throw new \Exception('Invalid webdav storage configuration'); + } + } + + private function init() { + if ($this->ready) { + return; + } + $this->ready = true; + + $settings = array( + 'baseUri' => $this->createBaseUri(), + 'userName' => $this->user, + 'password' => $this->password, + ); + + $proxy = \OC::$server->getConfig()->getSystemValue('proxy', ''); + if($proxy !== '') { + $settings['proxy'] = $proxy; + } + + $this->client = new Client($settings); + $this->client->setThrowExceptions(true); + if ($this->secure === true && $this->certPath) { + $this->client->addCurlSetting(CURLOPT_CAINFO, $this->certPath); + } + } + + /** + * Clear the stat cache + */ + public function clearStatCache() { + $this->statCache->clear(); + } + + /** {@inheritdoc} */ + public function getId() { + return 'webdav::' . $this->user . '@' . $this->host . '/' . $this->root; + } + + /** {@inheritdoc} */ + public function createBaseUri() { + $baseUri = 'http'; + if ($this->secure) { + $baseUri .= 's'; + } + $baseUri .= '://' . $this->host . $this->root; + return $baseUri; + } + + /** {@inheritdoc} */ + public function mkdir($path) { + $this->init(); + $path = $this->cleanPath($path); + $result = $this->simpleResponse('MKCOL', $path, null, 201); + if ($result) { + $this->statCache->set($path, true); + } + return $result; + } + + /** {@inheritdoc} */ + public function rmdir($path) { + $this->init(); + $path = $this->cleanPath($path); + // FIXME: some WebDAV impl return 403 when trying to DELETE + // a non-empty folder + $result = $this->simpleResponse('DELETE', $path . '/', null, 204); + $this->statCache->clear($path . '/'); + $this->statCache->remove($path); + return $result; + } + + /** {@inheritdoc} */ + public function opendir($path) { + $this->init(); + $path = $this->cleanPath($path); + try { + $response = $this->client->propfind( + $this->encodePath($path), + array(), + 1 + ); + $id = md5('webdav' . $this->root . $path); + $content = array(); + $files = array_keys($response); + array_shift($files); //the first entry is the current directory + + if (!$this->statCache->hasKey($path)) { + $this->statCache->set($path, true); + } + foreach ($files as $file) { + $file = urldecode($file); + // do not store the real entry, we might not have all properties + if (!$this->statCache->hasKey($path)) { + $this->statCache->set($file, true); + } + $file = basename($file); + $content[] = $file; + } + return IteratorDirectory::wrap($content); + } catch (ClientHttpException $e) { + if ($e->getHttpStatus() === 404) { + $this->statCache->clear($path . '/'); + $this->statCache->set($path, false); + return false; + } + $this->convertException($e, $path); + } catch (\Exception $e) { + $this->convertException($e, $path); + } + return false; + } + + /** + * Propfind call with cache handling. + * + * First checks if information is cached. + * If not, request it from the server then store to cache. + * + * @param string $path path to propfind + * + * @return array propfind response + * + * @throws NotFound + */ + protected function propfind($path) { + $path = $this->cleanPath($path); + $cachedResponse = $this->statCache->get($path); + if ($cachedResponse === false) { + // we know it didn't exist + throw new NotFound(); + } + // we either don't know it, or we know it exists but need more details + if (is_null($cachedResponse) || $cachedResponse === true) { + $this->init(); + try { + $response = $this->client->propfind( + $this->encodePath($path), + array( + '{DAV:}getlastmodified', + '{DAV:}getcontentlength', + '{DAV:}getcontenttype', + '{http://owncloud.org/ns}permissions', + '{http://open-collaboration-services.org/ns}share-permissions', + '{DAV:}resourcetype', + '{DAV:}getetag', + ) + ); + $this->statCache->set($path, $response); + } catch (NotFound $e) { + // remember that this path did not exist + $this->statCache->clear($path . '/'); + $this->statCache->set($path, false); + throw $e; + } + } else { + $response = $cachedResponse; + } + return $response; + } + + /** {@inheritdoc} */ + public function filetype($path) { + try { + $response = $this->propfind($path); + $responseType = array(); + if (isset($response["{DAV:}resourcetype"])) { + /** @var ResourceType[] $response */ + $responseType = $response["{DAV:}resourcetype"]->getValue(); + } + return (count($responseType) > 0 and $responseType[0] == "{DAV:}collection") ? 'dir' : 'file'; + } catch (ClientHttpException $e) { + if ($e->getHttpStatus() === 404) { + return false; + } + $this->convertException($e, $path); + } catch (\Exception $e) { + $this->convertException($e, $path); + } + return false; + } + + /** {@inheritdoc} */ + public function file_exists($path) { + try { + $path = $this->cleanPath($path); + $cachedState = $this->statCache->get($path); + if ($cachedState === false) { + // we know the file doesn't exist + return false; + } else if (!is_null($cachedState)) { + return true; + } + // need to get from server + $this->propfind($path); + return true; //no 404 exception + } catch (ClientHttpException $e) { + if ($e->getHttpStatus() === 404) { + return false; + } + $this->convertException($e, $path); + } catch (\Exception $e) { + $this->convertException($e, $path); + } + return false; + } + + /** {@inheritdoc} */ + public function unlink($path) { + $this->init(); + $path = $this->cleanPath($path); + $result = $this->simpleResponse('DELETE', $path, null, 204); + $this->statCache->clear($path . '/'); + $this->statCache->remove($path); + return $result; + } + + /** {@inheritdoc} */ + public function fopen($path, $mode) { + $this->init(); + $path = $this->cleanPath($path); + switch ($mode) { + case 'r': + case 'rb': + try { + $response = $this->httpClientService + ->newClient() + ->get($this->createBaseUri() . $this->encodePath($path), [ + 'auth' => [$this->user, $this->password], + 'stream' => true + ]); + } catch (RequestException $e) { + if ($e->getResponse() instanceof ResponseInterface + && $e->getResponse()->getStatusCode() === 404) { + return false; + } else { + throw $e; + } + } + + if ($response->getStatusCode() !== Http::STATUS_OK) { + if ($response->getStatusCode() === Http::STATUS_LOCKED) { + throw new \OCP\Lock\LockedException($path); + } else { + Util::writeLog("webdav client", 'Guzzle get returned status code ' . $response->getStatusCode(), Util::ERROR); + } + } + + return $response->getBody(); + case 'w': + case 'wb': + case 'a': + case 'ab': + case 'r+': + case 'w+': + case 'wb+': + case 'a+': + case 'x': + case 'x+': + case 'c': + case 'c+': + //emulate these + $tempManager = \OC::$server->getTempManager(); + if (strrpos($path, '.') !== false) { + $ext = substr($path, strrpos($path, '.')); + } else { + $ext = ''; + } + if ($this->file_exists($path)) { + if (!$this->isUpdatable($path)) { + return false; + } + if ($mode === 'w' or $mode === 'w+') { + $tmpFile = $tempManager->getTemporaryFile($ext); + } else { + $tmpFile = $this->getCachedFile($path); + } + } else { + if (!$this->isCreatable(dirname($path))) { + return false; + } + $tmpFile = $tempManager->getTemporaryFile($ext); + } + Close::registerCallback($tmpFile, array($this, 'writeBack')); + self::$tempFiles[$tmpFile] = $path; + return fopen('close://' . $tmpFile, $mode); + } + } + + /** + * @param string $tmpFile + */ + public function writeBack($tmpFile) { + if (isset(self::$tempFiles[$tmpFile])) { + $this->uploadFile($tmpFile, self::$tempFiles[$tmpFile]); + unlink($tmpFile); + } + } + + /** {@inheritdoc} */ + public function free_space($path) { + $this->init(); + $path = $this->cleanPath($path); + try { + // TODO: cacheable ? + $response = $this->client->propfind($this->encodePath($path), array('{DAV:}quota-available-bytes')); + if (isset($response['{DAV:}quota-available-bytes'])) { + return (int)$response['{DAV:}quota-available-bytes']; + } else { + return FileInfo::SPACE_UNKNOWN; + } + } catch (\Exception $e) { + return FileInfo::SPACE_UNKNOWN; + } + } + + /** {@inheritdoc} */ + public function touch($path, $mtime = null) { + $this->init(); + if (is_null($mtime)) { + $mtime = time(); + } + $path = $this->cleanPath($path); + + // if file exists, update the mtime, else create a new empty file + if ($this->file_exists($path)) { + try { + $this->statCache->remove($path); + $this->client->proppatch($this->encodePath($path), array('{DAV:}lastmodified' => $mtime)); + } catch (ClientHttpException $e) { + if ($e->getHttpStatus() === 501) { + return false; + } + $this->convertException($e, $path); + return false; + } catch (\Exception $e) { + $this->convertException($e, $path); + return false; + } + } else { + $this->file_put_contents($path, ''); + } + return true; + } + + /** + * @param string $path + * @param string $data + * @return int + */ + public function file_put_contents($path, $data) { + $path = $this->cleanPath($path); + $result = parent::file_put_contents($path, $data); + $this->statCache->remove($path); + return $result; + } + + /** + * @param string $path + * @param string $target + */ + protected function uploadFile($path, $target) { + $this->init(); + + // invalidate + $target = $this->cleanPath($target); + $this->statCache->remove($target); + $source = fopen($path, 'r'); + + $this->httpClientService + ->newClient() + ->put($this->createBaseUri() . $this->encodePath($target), [ + 'body' => $source, + 'auth' => [$this->user, $this->password] + ]); + + $this->removeCachedFile($target); + } + + /** {@inheritdoc} */ + public function rename($path1, $path2) { + $this->init(); + $path1 = $this->cleanPath($path1); + $path2 = $this->cleanPath($path2); + try { + $this->client->request( + 'MOVE', + $this->encodePath($path1), + null, + array( + 'Destination' => $this->createBaseUri() . $this->encodePath($path2) + ) + ); + $this->statCache->clear($path1 . '/'); + $this->statCache->clear($path2 . '/'); + $this->statCache->set($path1, false); + $this->statCache->set($path2, true); + $this->removeCachedFile($path1); + $this->removeCachedFile($path2); + return true; + } catch (\Exception $e) { + $this->convertException($e); + } + return false; + } + + /** {@inheritdoc} */ + public function copy($path1, $path2) { + $this->init(); + $path1 = $this->encodePath($this->cleanPath($path1)); + $path2 = $this->createBaseUri() . $this->encodePath($this->cleanPath($path2)); + try { + $this->client->request('COPY', $path1, null, array('Destination' => $path2)); + $this->statCache->clear($path2 . '/'); + $this->statCache->set($path2, true); + $this->removeCachedFile($path2); + return true; + } catch (\Exception $e) { + $this->convertException($e); + } + return false; + } + + /** {@inheritdoc} */ + public function stat($path) { + try { + $response = $this->propfind($path); + return array( + 'mtime' => strtotime($response['{DAV:}getlastmodified']), + 'size' => (int)isset($response['{DAV:}getcontentlength']) ? $response['{DAV:}getcontentlength'] : 0, + ); + } catch (ClientHttpException $e) { + if ($e->getHttpStatus() === 404) { + return array(); + } + $this->convertException($e, $path); + } catch (\Exception $e) { + $this->convertException($e, $path); + } + return array(); + } + + /** {@inheritdoc} */ + public function getMimeType($path) { + try { + $response = $this->propfind($path); + $responseType = array(); + if (isset($response["{DAV:}resourcetype"])) { + /** @var ResourceType[] $response */ + $responseType = $response["{DAV:}resourcetype"]->getValue(); + } + $type = (count($responseType) > 0 and $responseType[0] == "{DAV:}collection") ? 'dir' : 'file'; + if ($type == 'dir') { + return 'httpd/unix-directory'; + } elseif (isset($response['{DAV:}getcontenttype'])) { + return $response['{DAV:}getcontenttype']; + } else { + return false; + } + } catch (ClientHttpException $e) { + if ($e->getHttpStatus() === 404) { + return false; + } + $this->convertException($e, $path); + } catch (\Exception $e) { + $this->convertException($e, $path); + } + return false; + } + + /** + * @param string $path + * @return string + */ + public function cleanPath($path) { + if ($path === '') { + return $path; + } + $path = Filesystem::normalizePath($path); + // remove leading slash + return substr($path, 1); + } + + /** + * URL encodes the given path but keeps the slashes + * + * @param string $path to encode + * @return string encoded path + */ + private function encodePath($path) { + // slashes need to stay + return str_replace('%2F', '/', rawurlencode($path)); + } + + /** + * @param string $method + * @param string $path + * @param string|resource|null $body + * @param int $expected + * @return bool + * @throws StorageInvalidException + * @throws StorageNotAvailableException + */ + private function simpleResponse($method, $path, $body, $expected) { + $path = $this->cleanPath($path); + try { + $response = $this->client->request($method, $this->encodePath($path), $body); + return $response['statusCode'] == $expected; + } catch (ClientHttpException $e) { + if ($e->getHttpStatus() === 404 && $method === 'DELETE') { + $this->statCache->clear($path . '/'); + $this->statCache->set($path, false); + return false; + } + + $this->convertException($e, $path); + } catch (\Exception $e) { + $this->convertException($e, $path); + } + return false; + } + + /** + * check if curl is installed + */ + public static function checkDependencies() { + return true; + } + + /** {@inheritdoc} */ + public function isUpdatable($path) { + return (bool)($this->getPermissions($path) & Constants::PERMISSION_UPDATE); + } + + /** {@inheritdoc} */ + public function isCreatable($path) { + return (bool)($this->getPermissions($path) & Constants::PERMISSION_CREATE); + } + + /** {@inheritdoc} */ + public function isSharable($path) { + return (bool)($this->getPermissions($path) & Constants::PERMISSION_SHARE); + } + + /** {@inheritdoc} */ + public function isDeletable($path) { + return (bool)($this->getPermissions($path) & Constants::PERMISSION_DELETE); + } + + /** {@inheritdoc} */ + public function getPermissions($path) { + $this->init(); + $path = $this->cleanPath($path); + $response = $this->propfind($path); + if (isset($response['{http://owncloud.org/ns}permissions'])) { + return $this->parsePermissions($response['{http://owncloud.org/ns}permissions']); + } else if ($this->is_dir($path)) { + return Constants::PERMISSION_ALL; + } else if ($this->file_exists($path)) { + return Constants::PERMISSION_ALL - Constants::PERMISSION_CREATE; + } else { + return 0; + } + } + + /** {@inheritdoc} */ + public function getETag($path) { + $this->init(); + $path = $this->cleanPath($path); + $response = $this->propfind($path); + if (isset($response['{DAV:}getetag'])) { + return trim($response['{DAV:}getetag'], '"'); + } + return parent::getEtag($path); + } + + /** + * @param string $permissionsString + * @return int + */ + protected function parsePermissions($permissionsString) { + $permissions = Constants::PERMISSION_READ; + if (strpos($permissionsString, 'R') !== false) { + $permissions |= Constants::PERMISSION_SHARE; + } + if (strpos($permissionsString, 'D') !== false) { + $permissions |= Constants::PERMISSION_DELETE; + } + if (strpos($permissionsString, 'W') !== false) { + $permissions |= Constants::PERMISSION_UPDATE; + } + if (strpos($permissionsString, 'CK') !== false) { + $permissions |= Constants::PERMISSION_CREATE; + $permissions |= Constants::PERMISSION_UPDATE; + } + return $permissions; + } + + /** + * check if a file or folder has been updated since $time + * + * @param string $path + * @param int $time + * @throws \OCP\Files\StorageNotAvailableException + * @return bool + */ + public function hasUpdated($path, $time) { + $this->init(); + $path = $this->cleanPath($path); + try { + // force refresh for $path + $this->statCache->remove($path); + $response = $this->propfind($path); + if (isset($response['{DAV:}getetag'])) { + $cachedData = $this->getCache()->get($path); + $etag = null; + if (isset($response['{DAV:}getetag'])) { + $etag = trim($response['{DAV:}getetag'], '"'); + } + if (!empty($etag) && $cachedData['etag'] !== $etag) { + return true; + } else if (isset($response['{http://open-collaboration-services.org/ns}share-permissions'])) { + $sharePermissions = (int)$response['{http://open-collaboration-services.org/ns}share-permissions']; + return $sharePermissions !== $cachedData['permissions']; + } else if (isset($response['{http://owncloud.org/ns}permissions'])) { + $permissions = $this->parsePermissions($response['{http://owncloud.org/ns}permissions']); + return $permissions !== $cachedData['permissions']; + } else { + return false; + } + } else { + $remoteMtime = strtotime($response['{DAV:}getlastmodified']); + return $remoteMtime > $time; + } + } catch (ClientHttpException $e) { + if ($e->getHttpStatus() === 404 || $e->getHttpStatus() === 405) { + if ($path === '') { + // if root is gone it means the storage is not available + throw new StorageNotAvailableException(get_class($e) . ': ' . $e->getMessage()); + } + return false; + } + $this->convertException($e, $path); + return false; + } catch (\Exception $e) { + $this->convertException($e, $path); + return false; + } + } + + /** + * Interpret the given exception and decide whether it is due to an + * unavailable storage, invalid storage or other. + * This will either throw StorageInvalidException, StorageNotAvailableException + * or do nothing. + * + * @param Exception $e sabre exception + * @param string $path optional path from the operation + * + * @throws StorageInvalidException if the storage is invalid, for example + * when the authentication expired or is invalid + * @throws StorageNotAvailableException if the storage is not available, + * which might be temporary + */ + private function convertException(Exception $e, $path = '') { + Util::writeLog('files_external', $e->getMessage(), Util::ERROR); + if ($e instanceof ClientHttpException) { + if ($e->getHttpStatus() === 423) { + throw new \OCP\Lock\LockedException($path); + } + if ($e->getHttpStatus() === 401) { + // either password was changed or was invalid all along + throw new StorageInvalidException(get_class($e) . ': ' . $e->getMessage()); + } else if ($e->getHttpStatus() === 405) { + // ignore exception for MethodNotAllowed, false will be returned + return; + } + throw new StorageNotAvailableException(get_class($e) . ': ' . $e->getMessage()); + } else if ($e instanceof ClientException) { + // connection timeout or refused, server could be temporarily down + throw new StorageNotAvailableException(get_class($e) . ': ' . $e->getMessage()); + } else if ($e instanceof \InvalidArgumentException) { + // parse error because the server returned HTML instead of XML, + // possibly temporarily down + throw new StorageNotAvailableException(get_class($e) . ': ' . $e->getMessage()); + } else if (($e instanceof StorageNotAvailableException) || ($e instanceof StorageInvalidException)) { + // rethrow + throw $e; + } + + // TODO: only log for now, but in the future need to wrap/rethrow exception + } +} + diff --git a/lib/private/Files/Storage/FailedStorage.php b/lib/private/Files/Storage/FailedStorage.php new file mode 100644 index 00000000000..df7f76856d5 --- /dev/null +++ b/lib/private/Files/Storage/FailedStorage.php @@ -0,0 +1,215 @@ + + * @author Robin McCorkell + * + * @copyright Copyright (c) 2016, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Files\Storage; + +use OC\Files\Cache\FailedCache; +use \OCP\Lock\ILockingProvider; +use \OCP\Files\StorageNotAvailableException; + +/** + * Storage placeholder to represent a missing precondition, storage unavailable + */ +class FailedStorage extends Common { + + /** @var \Exception */ + protected $e; + + /** + * @param array $params ['exception' => \Exception] + */ + public function __construct($params) { + $this->e = $params['exception']; + if (!$this->e) { + throw new \InvalidArgumentException('Missing "exception" argument in FailedStorage constructor'); + } + } + + public function getId() { + // we can't return anything sane here + return 'failedstorage'; + } + + public function mkdir($path) { + throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e); + } + + public function rmdir($path) { + throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e); + } + + public function opendir($path) { + throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e); + } + + public function is_dir($path) { + throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e); + } + + public function is_file($path) { + throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e); + } + + public function stat($path) { + throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e); + } + + public function filetype($path) { + throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e); + } + + public function filesize($path) { + throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e); + } + + public function isCreatable($path) { + throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e); + } + + public function isReadable($path) { + throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e); + } + + public function isUpdatable($path) { + throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e); + } + + public function isDeletable($path) { + throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e); + } + + public function isSharable($path) { + throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e); + } + + public function getPermissions($path) { + throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e); + } + + public function file_exists($path) { + throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e); + } + + public function filemtime($path) { + throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e); + } + + public function file_get_contents($path) { + throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e); + } + + public function file_put_contents($path, $data) { + throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e); + } + + public function unlink($path) { + throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e); + } + + public function rename($path1, $path2) { + throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e); + } + + public function copy($path1, $path2) { + throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e); + } + + public function fopen($path, $mode) { + throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e); + } + + public function getMimeType($path) { + throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e); + } + + public function hash($type, $path, $raw = false) { + throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e); + } + + public function free_space($path) { + throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e); + } + + public function search($query) { + throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e); + } + + public function touch($path, $mtime = null) { + throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e); + } + + public function getLocalFile($path) { + throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e); + } + + public function getLocalFolder($path) { + throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e); + } + + public function hasUpdated($path, $time) { + throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e); + } + + public function getETag($path) { + throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e); + } + + public function getDirectDownload($path) { + throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e); + } + + public function verifyPath($path, $fileName) { + return true; + } + + public function copyFromStorage(\OCP\Files\Storage $sourceStorage, $sourceInternalPath, $targetInternalPath) { + throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e); + } + + public function moveFromStorage(\OCP\Files\Storage $sourceStorage, $sourceInternalPath, $targetInternalPath) { + throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e); + } + + public function acquireLock($path, $type, ILockingProvider $provider) { + throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e); + } + + public function releaseLock($path, $type, ILockingProvider $provider) { + throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e); + } + + public function changeLock($path, $type, ILockingProvider $provider) { + throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e); + } + + public function getAvailability() { + throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e); + } + + public function setAvailability($isAvailable) { + throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e); + } + + public function getCache($path = '', $storage = null) { + return new FailedCache(); + } +} diff --git a/lib/private/Files/Storage/Flysystem.php b/lib/private/Files/Storage/Flysystem.php new file mode 100644 index 00000000000..608639b71a6 --- /dev/null +++ b/lib/private/Files/Storage/Flysystem.php @@ -0,0 +1,256 @@ + + * + * @copyright Copyright (c) 2016, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Files\Storage; + +use Icewind\Streams\CallbackWrapper; +use Icewind\Streams\IteratorDirectory; +use League\Flysystem\AdapterInterface; +use League\Flysystem\FileNotFoundException; +use League\Flysystem\Filesystem; +use League\Flysystem\Plugin\GetWithMetadata; + +/** + * Generic adapter between flysystem adapters and owncloud's storage system + * + * To use: subclass and call $this->buildFlysystem with the flysystem adapter of choice + */ +abstract class Flysystem extends Common { + /** + * @var Filesystem + */ + protected $flysystem; + + /** + * @var string + */ + protected $root = ''; + + /** + * Initialize the storage backend with a flyssytem adapter + * + * @param \League\Flysystem\AdapterInterface $adapter + */ + protected function buildFlySystem(AdapterInterface $adapter) { + $this->flysystem = new Filesystem($adapter); + $this->flysystem->addPlugin(new GetWithMetadata()); + } + + protected function buildPath($path) { + $fullPath = \OC\Files\Filesystem::normalizePath($this->root . '/' . $path); + return ltrim($fullPath, '/'); + } + + /** + * {@inheritdoc} + */ + public function file_get_contents($path) { + return $this->flysystem->read($this->buildPath($path)); + } + + /** + * {@inheritdoc} + */ + public function file_put_contents($path, $data) { + return $this->flysystem->put($this->buildPath($path), $data); + } + + /** + * {@inheritdoc} + */ + public function file_exists($path) { + return $this->flysystem->has($this->buildPath($path)); + } + + /** + * {@inheritdoc} + */ + public function unlink($path) { + if ($this->is_dir($path)) { + return $this->rmdir($path); + } + try { + return $this->flysystem->delete($this->buildPath($path)); + } catch (FileNotFoundException $e) { + return false; + } + } + + /** + * {@inheritdoc} + */ + public function rename($source, $target) { + if ($this->file_exists($target)) { + $this->unlink($target); + } + return $this->flysystem->rename($this->buildPath($source), $this->buildPath($target)); + } + + /** + * {@inheritdoc} + */ + public function copy($source, $target) { + if ($this->file_exists($target)) { + $this->unlink($target); + } + return $this->flysystem->copy($this->buildPath($source), $this->buildPath($target)); + } + + /** + * {@inheritdoc} + */ + public function filesize($path) { + if ($this->is_dir($path)) { + return 0; + } else { + return $this->flysystem->getSize($this->buildPath($path)); + } + } + + /** + * {@inheritdoc} + */ + public function mkdir($path) { + if ($this->file_exists($path)) { + return false; + } + return $this->flysystem->createDir($this->buildPath($path)); + } + + /** + * {@inheritdoc} + */ + public function filemtime($path) { + return $this->flysystem->getTimestamp($this->buildPath($path)); + } + + /** + * {@inheritdoc} + */ + public function rmdir($path) { + try { + return @$this->flysystem->deleteDir($this->buildPath($path)); + } catch (FileNotFoundException $e) { + return false; + } + } + + /** + * {@inheritdoc} + */ + public function opendir($path) { + try { + $content = $this->flysystem->listContents($this->buildPath($path)); + } catch (FileNotFoundException $e) { + return false; + } + $names = array_map(function ($object) { + return $object['basename']; + }, $content); + return IteratorDirectory::wrap($names); + } + + /** + * {@inheritdoc} + */ + public function fopen($path, $mode) { + $fullPath = $this->buildPath($path); + $useExisting = true; + switch ($mode) { + case 'r': + case 'rb': + try { + return $this->flysystem->readStream($fullPath); + } catch (FileNotFoundException $e) { + return false; + } + case 'w': + case 'w+': + case 'wb': + case 'wb+': + $useExisting = false; + case 'a': + case 'ab': + case 'r+': + case 'a+': + case 'x': + case 'x+': + case 'c': + case 'c+': + //emulate these + if ($useExisting and $this->file_exists($path)) { + if (!$this->isUpdatable($path)) { + return false; + } + $tmpFile = $this->getCachedFile($path); + } else { + if (!$this->isCreatable(dirname($path))) { + return false; + } + $tmpFile = \OCP\Files::tmpFile(); + } + $source = fopen($tmpFile, $mode); + return CallbackWrapper::wrap($source, null, null, function () use ($tmpFile, $fullPath) { + $this->flysystem->putStream($fullPath, fopen($tmpFile, 'r')); + unlink($tmpFile); + }); + } + return false; + } + + /** + * {@inheritdoc} + */ + public function touch($path, $mtime = null) { + if ($this->file_exists($path)) { + return false; + } else { + $this->file_put_contents($path, ''); + return true; + } + } + + /** + * {@inheritdoc} + */ + public function stat($path) { + $info = $this->flysystem->getWithMetadata($this->buildPath($path), ['timestamp', 'size']); + return [ + 'mtime' => $info['timestamp'], + 'size' => $info['size'] + ]; + } + + /** + * {@inheritdoc} + */ + public function filetype($path) { + if ($path === '' or $path === '/' or $path === '.') { + return 'dir'; + } + try { + $info = $this->flysystem->getMetadata($this->buildPath($path)); + } catch (FileNotFoundException $e) { + return false; + } + return $info['type']; + } +} diff --git a/lib/private/Files/Storage/Home.php b/lib/private/Files/Storage/Home.php new file mode 100644 index 00000000000..9b98f2f7e12 --- /dev/null +++ b/lib/private/Files/Storage/Home.php @@ -0,0 +1,114 @@ + + * @author Morris Jobke + * @author Robin Appelman + * @author Thomas Müller + * @author Vincent Petry + * + * @copyright Copyright (c) 2016, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Files\Storage; +use OC\Files\Cache\HomePropagator; + +/** + * Specialized version of Local storage for home directory usage + */ +class Home extends Local implements \OCP\Files\IHomeStorage { + /** + * @var string + */ + protected $id; + + /** + * @var \OC\User\User $user + */ + protected $user; + + /** + * Construct a Home storage instance + * @param array $arguments array with "user" containing the + * storage owner and "legacy" containing "true" if the storage is + * a legacy storage with "local::" URL instead of the new "home::" one. + */ + public function __construct($arguments) { + $this->user = $arguments['user']; + $datadir = $this->user->getHome(); + if (isset($arguments['legacy']) && $arguments['legacy']) { + // legacy home id (<= 5.0.12) + $this->id = 'local::' . $datadir . '/'; + } + else { + $this->id = 'home::' . $this->user->getUID(); + } + + parent::__construct(array('datadir' => $datadir)); + } + + public function getId() { + return $this->id; + } + + /** + * @return \OC\Files\Cache\HomeCache + */ + public function getCache($path = '', $storage = null) { + if (!$storage) { + $storage = $this; + } + if (!isset($this->cache)) { + $this->cache = new \OC\Files\Cache\HomeCache($storage); + } + return $this->cache; + } + + /** + * get a propagator instance for the cache + * + * @param \OC\Files\Storage\Storage (optional) the storage to pass to the watcher + * @return \OC\Files\Cache\Propagator + */ + public function getPropagator($storage = null) { + if (!$storage) { + $storage = $this; + } + if (!isset($this->propagator)) { + $this->propagator = new HomePropagator($storage); + } + return $this->propagator; + } + + + /** + * Returns the owner of this home storage + * @return \OC\User\User owner of this home storage + */ + public function getUser() { + return $this->user; + } + + /** + * get the owner of a path + * + * @param string $path The path to get the owner + * @return string uid or false + */ + public function getOwner($path) { + return $this->user->getUID(); + } +} diff --git a/lib/private/Files/Storage/Local.php b/lib/private/Files/Storage/Local.php new file mode 100644 index 00000000000..25b202af5f8 --- /dev/null +++ b/lib/private/Files/Storage/Local.php @@ -0,0 +1,404 @@ + + * @author Brice Maron + * @author Jakob Sack + * @author Joas Schilling + * @author Klaas Freitag + * @author Martin Mattel + * @author Michael Gapczynski + * @author Morris Jobke + * @author Robin Appelman + * @author Sjors van der Pluijm + * @author Thomas Müller + * @author Tigran Mkrtchyan + * @author Vincent Petry + * + * @copyright Copyright (c) 2016, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Files\Storage; +/** + * for local filestore, we only have to map the paths + */ +class Local extends \OC\Files\Storage\Common { + protected $datadir; + + public function __construct($arguments) { + $this->datadir = $arguments['datadir']; + if (substr($this->datadir, -1) !== '/') { + $this->datadir .= '/'; + } + } + + public function __destruct() { + } + + public function getId() { + return 'local::' . $this->datadir; + } + + public function mkdir($path) { + return @mkdir($this->getSourcePath($path), 0777, true); + } + + public function rmdir($path) { + if (!$this->isDeletable($path)) { + return false; + } + try { + $it = new \RecursiveIteratorIterator( + new \RecursiveDirectoryIterator($this->getSourcePath($path)), + \RecursiveIteratorIterator::CHILD_FIRST + ); + /** + * RecursiveDirectoryIterator on an NFS path isn't iterable with foreach + * This bug is fixed in PHP 5.5.9 or before + * See #8376 + */ + $it->rewind(); + while ($it->valid()) { + /** + * @var \SplFileInfo $file + */ + $file = $it->current(); + if (in_array($file->getBasename(), array('.', '..'))) { + $it->next(); + continue; + } elseif ($file->isDir()) { + rmdir($file->getPathname()); + } elseif ($file->isFile() || $file->isLink()) { + unlink($file->getPathname()); + } + $it->next(); + } + return rmdir($this->getSourcePath($path)); + } catch (\UnexpectedValueException $e) { + return false; + } + } + + public function opendir($path) { + return opendir($this->getSourcePath($path)); + } + + public function is_dir($path) { + if (substr($path, -1) == '/') { + $path = substr($path, 0, -1); + } + return is_dir($this->getSourcePath($path)); + } + + public function is_file($path) { + return is_file($this->getSourcePath($path)); + } + + public function stat($path) { + clearstatcache(); + $fullPath = $this->getSourcePath($path); + $statResult = stat($fullPath); + if (PHP_INT_SIZE === 4 && !$this->is_dir($path)) { + $filesize = $this->filesize($path); + $statResult['size'] = $filesize; + $statResult[7] = $filesize; + } + return $statResult; + } + + public function filetype($path) { + $filetype = filetype($this->getSourcePath($path)); + if ($filetype == 'link') { + $filetype = filetype(realpath($this->getSourcePath($path))); + } + return $filetype; + } + + public function filesize($path) { + if ($this->is_dir($path)) { + return 0; + } + $fullPath = $this->getSourcePath($path); + if (PHP_INT_SIZE === 4) { + $helper = new \OC\LargeFileHelper; + return $helper->getFilesize($fullPath); + } + return filesize($fullPath); + } + + public function isReadable($path) { + return is_readable($this->getSourcePath($path)); + } + + public function isUpdatable($path) { + return is_writable($this->getSourcePath($path)); + } + + public function file_exists($path) { + return file_exists($this->getSourcePath($path)); + } + + public function filemtime($path) { + clearstatcache($this->getSourcePath($path)); + return filemtime($this->getSourcePath($path)); + } + + public function touch($path, $mtime = null) { + // sets the modification time of the file to the given value. + // If mtime is nil the current time is set. + // note that the access time of the file always changes to the current time. + if ($this->file_exists($path) and !$this->isUpdatable($path)) { + return false; + } + if (!is_null($mtime)) { + $result = touch($this->getSourcePath($path), $mtime); + } else { + $result = touch($this->getSourcePath($path)); + } + if ($result) { + clearstatcache(true, $this->getSourcePath($path)); + } + + return $result; + } + + public function file_get_contents($path) { + // file_get_contents() has a memory leak: https://bugs.php.net/bug.php?id=61961 + $fileName = $this->getSourcePath($path); + + $fileSize = filesize($fileName); + if ($fileSize === 0) { + return ''; + } + + $handle = fopen($fileName,'rb'); + $content = fread($handle, $fileSize); + fclose($handle); + return $content; + } + + public function file_put_contents($path, $data) { + return file_put_contents($this->getSourcePath($path), $data); + } + + public function unlink($path) { + if ($this->is_dir($path)) { + return $this->rmdir($path); + } else if ($this->is_file($path)) { + return unlink($this->getSourcePath($path)); + } else { + return false; + } + + } + + public function rename($path1, $path2) { + $srcParent = dirname($path1); + $dstParent = dirname($path2); + + if (!$this->isUpdatable($srcParent)) { + \OCP\Util::writeLog('core', 'unable to rename, source directory is not writable : ' . $srcParent, \OCP\Util::ERROR); + return false; + } + + if (!$this->isUpdatable($dstParent)) { + \OCP\Util::writeLog('core', 'unable to rename, destination directory is not writable : ' . $dstParent, \OCP\Util::ERROR); + return false; + } + + if (!$this->file_exists($path1)) { + \OCP\Util::writeLog('core', 'unable to rename, file does not exists : ' . $path1, \OCP\Util::ERROR); + return false; + } + + if ($this->is_dir($path2)) { + $this->rmdir($path2); + } else if ($this->is_file($path2)) { + $this->unlink($path2); + } + + if ($this->is_dir($path1)) { + // we can't move folders across devices, use copy instead + $stat1 = stat(dirname($this->getSourcePath($path1))); + $stat2 = stat(dirname($this->getSourcePath($path2))); + if ($stat1['dev'] !== $stat2['dev']) { + $result = $this->copy($path1, $path2); + if ($result) { + $result &= $this->rmdir($path1); + } + return $result; + } + } + + return rename($this->getSourcePath($path1), $this->getSourcePath($path2)); + } + + public function copy($path1, $path2) { + if ($this->is_dir($path1)) { + return parent::copy($path1, $path2); + } else { + return copy($this->getSourcePath($path1), $this->getSourcePath($path2)); + } + } + + public function fopen($path, $mode) { + return fopen($this->getSourcePath($path), $mode); + } + + public function hash($type, $path, $raw = false) { + return hash_file($type, $this->getSourcePath($path), $raw); + } + + public function free_space($path) { + $sourcePath = $this->getSourcePath($path); + // using !is_dir because $sourcePath might be a part file or + // non-existing file, so we'd still want to use the parent dir + // in such cases + if (!is_dir($sourcePath)) { + // disk_free_space doesn't work on files + $sourcePath = dirname($sourcePath); + } + $space = @disk_free_space($sourcePath); + if ($space === false || is_null($space)) { + return \OCP\Files\FileInfo::SPACE_UNKNOWN; + } + return $space; + } + + public function search($query) { + return $this->searchInDir($query); + } + + public function getLocalFile($path) { + return $this->getSourcePath($path); + } + + public function getLocalFolder($path) { + return $this->getSourcePath($path); + } + + /** + * @param string $query + * @param string $dir + * @return array + */ + protected function searchInDir($query, $dir = '') { + $files = array(); + $physicalDir = $this->getSourcePath($dir); + foreach (scandir($physicalDir) as $item) { + if (\OC\Files\Filesystem::isIgnoredDir($item)) + continue; + $physicalItem = $physicalDir . '/' . $item; + + if (strstr(strtolower($item), strtolower($query)) !== false) { + $files[] = $dir . '/' . $item; + } + if (is_dir($physicalItem)) { + $files = array_merge($files, $this->searchInDir($query, $dir . '/' . $item)); + } + } + return $files; + } + + /** + * check if a file or folder has been updated since $time + * + * @param string $path + * @param int $time + * @return bool + */ + public function hasUpdated($path, $time) { + if ($this->file_exists($path)) { + return $this->filemtime($path) > $time; + } else { + return true; + } + } + + /** + * Get the source path (on disk) of a given path + * + * @param string $path + * @return string + */ + public function getSourcePath($path) { + $fullPath = $this->datadir . $path; + return $fullPath; + } + + /** + * {@inheritdoc} + */ + public function isLocal() { + return true; + } + + /** + * get the ETag for a file or folder + * + * @param string $path + * @return string + */ + public function getETag($path) { + if ($this->is_file($path)) { + $stat = $this->stat($path); + return md5( + $stat['mtime'] . + $stat['ino'] . + $stat['dev'] . + $stat['size'] + ); + } else { + return parent::getETag($path); + } + } + + /** + * @param \OCP\Files\Storage $sourceStorage + * @param string $sourceInternalPath + * @param string $targetInternalPath + * @return bool + */ + public function copyFromStorage(\OCP\Files\Storage $sourceStorage, $sourceInternalPath, $targetInternalPath) { + if($sourceStorage->instanceOfStorage('\OC\Files\Storage\Local')){ + /** + * @var \OC\Files\Storage\Local $sourceStorage + */ + $rootStorage = new Local(['datadir' => '/']); + return $rootStorage->copy($sourceStorage->getSourcePath($sourceInternalPath), $this->getSourcePath($targetInternalPath)); + } else { + return parent::copyFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath); + } + } + + /** + * @param \OCP\Files\Storage $sourceStorage + * @param string $sourceInternalPath + * @param string $targetInternalPath + * @return bool + */ + public function moveFromStorage(\OCP\Files\Storage $sourceStorage, $sourceInternalPath, $targetInternalPath) { + if ($sourceStorage->instanceOfStorage('\OC\Files\Storage\Local')) { + /** + * @var \OC\Files\Storage\Local $sourceStorage + */ + $rootStorage = new Local(['datadir' => '/']); + return $rootStorage->rename($sourceStorage->getSourcePath($sourceInternalPath), $this->getSourcePath($targetInternalPath)); + } else { + return parent::moveFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath); + } + } +} diff --git a/lib/private/Files/Storage/LocalTempFileTrait.php b/lib/private/Files/Storage/LocalTempFileTrait.php new file mode 100644 index 00000000000..88f11e4e752 --- /dev/null +++ b/lib/private/Files/Storage/LocalTempFileTrait.php @@ -0,0 +1,80 @@ + + * @author Morris Jobke + * @author Thomas Müller + * + * @copyright Copyright (c) 2016, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Files\Storage; + +/** + * Storage backend class for providing common filesystem operation methods + * which are not storage-backend specific. + * + * \OC\Files\Storage\Common is never used directly; it is extended by all other + * storage backends, where its methods may be overridden, and additional + * (backend-specific) methods are defined. + * + * Some \OC\Files\Storage\Common methods call functions which are first defined + * in classes which extend it, e.g. $this->stat() . + */ +trait LocalTempFileTrait { + + /** @var string[] */ + protected $cachedFiles = []; + + /** + * @param string $path + * @return string + */ + protected function getCachedFile($path) { + if (!isset($this->cachedFiles[$path])) { + $this->cachedFiles[$path] = $this->toTmpFile($path); + } + return $this->cachedFiles[$path]; + } + + /** + * @param string $path + */ + protected function removeCachedFile($path) { + unset($this->cachedFiles[$path]); + } + + /** + * @param string $path + * @return string + */ + protected function toTmpFile($path) { //no longer in the storage api, still useful here + $source = $this->fopen($path, 'r'); + if (!$source) { + return false; + } + if ($pos = strrpos($path, '.')) { + $extension = substr($path, $pos); + } else { + $extension = ''; + } + $tmpFile = \OC::$server->getTempManager()->getTemporaryFile($extension); + $target = fopen($tmpFile, 'w'); + \OC_Helper::streamCopy($source, $target); + fclose($target); + return $tmpFile; + } +} diff --git a/lib/private/Files/Storage/PolyFill/CopyDirectory.php b/lib/private/Files/Storage/PolyFill/CopyDirectory.php new file mode 100644 index 00000000000..d4cac117ade --- /dev/null +++ b/lib/private/Files/Storage/PolyFill/CopyDirectory.php @@ -0,0 +1,103 @@ + + * @author Robin Appelman + * + * @copyright Copyright (c) 2016, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Files\Storage\PolyFill; + +trait CopyDirectory { + /** + * Check if a path is a directory + * + * @param string $path + * @return bool + */ + abstract public function is_dir($path); + + /** + * Check if a file or folder exists + * + * @param string $path + * @return bool + */ + abstract public function file_exists($path); + + /** + * Delete a file or folder + * + * @param string $path + * @return bool + */ + abstract public function unlink($path); + + /** + * Open a directory handle for a folder + * + * @param string $path + * @return resource | bool + */ + abstract public function opendir($path); + + /** + * Create a new folder + * + * @param string $path + * @return bool + */ + abstract public function mkdir($path); + + public function copy($source, $target) { + if ($this->is_dir($source)) { + if ($this->file_exists($target)) { + $this->unlink($target); + } + $this->mkdir($target); + return $this->copyRecursive($source, $target); + } else { + return parent::copy($source, $target); + } + } + + /** + * For adapters that don't support copying folders natively + * + * @param $source + * @param $target + * @return bool + */ + protected function copyRecursive($source, $target) { + $dh = $this->opendir($source); + $result = true; + while ($file = readdir($dh)) { + if (!\OC\Files\Filesystem::isIgnoredDir($file)) { + if ($this->is_dir($source . '/' . $file)) { + $this->mkdir($target . '/' . $file); + $result = $this->copyRecursive($source . '/' . $file, $target . '/' . $file); + } else { + $result = parent::copy($source . '/' . $file, $target . '/' . $file); + } + if (!$result) { + break; + } + } + } + return $result; + } +} diff --git a/lib/private/Files/Storage/Storage.php b/lib/private/Files/Storage/Storage.php new file mode 100644 index 00000000000..c066336d4b8 --- /dev/null +++ b/lib/private/Files/Storage/Storage.php @@ -0,0 +1,119 @@ + + * @author Robin Appelman + * @author Thomas Müller + * + * @copyright Copyright (c) 2016, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Files\Storage; +use OCP\Lock\ILockingProvider; + +/** + * Provide a common interface to all different storage options + * + * All paths passed to the storage are relative to the storage and should NOT have a leading slash. + */ +interface Storage extends \OCP\Files\Storage { + + /** + * get a cache instance for the storage + * + * @param string $path + * @param \OC\Files\Storage\Storage (optional) the storage to pass to the cache + * @return \OC\Files\Cache\Cache + */ + public function getCache($path = '', $storage = null); + + /** + * get a scanner instance for the storage + * + * @param string $path + * @param \OC\Files\Storage\Storage (optional) the storage to pass to the scanner + * @return \OC\Files\Cache\Scanner + */ + public function getScanner($path = '', $storage = null); + + + /** + * get the user id of the owner of a file or folder + * + * @param string $path + * @return string + */ + public function getOwner($path); + + /** + * get a watcher instance for the cache + * + * @param string $path + * @param \OC\Files\Storage\Storage (optional) the storage to pass to the watcher + * @return \OC\Files\Cache\Watcher + */ + public function getWatcher($path = '', $storage = null); + + /** + * get a propagator instance for the cache + * + * @param \OC\Files\Storage\Storage (optional) the storage to pass to the watcher + * @return \OC\Files\Cache\Propagator + */ + public function getPropagator($storage = null); + + /** + * get a updater instance for the cache + * + * @param \OC\Files\Storage\Storage (optional) the storage to pass to the watcher + * @return \OC\Files\Cache\Updater + */ + public function getUpdater($storage = null); + + /** + * @return \OC\Files\Cache\Storage + */ + public function getStorageCache(); + + /** + * @param string $path + * @return array + */ + public function getMetaData($path); + + /** + * @param string $path The path of the file to acquire the lock for + * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE + * @param \OCP\Lock\ILockingProvider $provider + * @throws \OCP\Lock\LockedException + */ + public function acquireLock($path, $type, ILockingProvider $provider); + + /** + * @param string $path The path of the file to release the lock for + * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE + * @param \OCP\Lock\ILockingProvider $provider + */ + public function releaseLock($path, $type, ILockingProvider $provider); + + /** + * @param string $path The path of the file to change the lock for + * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE + * @param \OCP\Lock\ILockingProvider $provider + * @throws \OCP\Lock\LockedException + */ + public function changeLock($path, $type, ILockingProvider $provider); +} diff --git a/lib/private/Files/Storage/StorageFactory.php b/lib/private/Files/Storage/StorageFactory.php new file mode 100644 index 00000000000..84362cabecc --- /dev/null +++ b/lib/private/Files/Storage/StorageFactory.php @@ -0,0 +1,107 @@ + + * @author Morris Jobke + * @author Robin Appelman + * @author Vincent Petry + * + * @copyright Copyright (c) 2016, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Files\Storage; + +use OCP\Files\Mount\IMountPoint; +use OCP\Files\Storage\IStorageFactory; + +class StorageFactory implements IStorageFactory { + /** + * @var array[] [$name=>['priority'=>$priority, 'wrapper'=>$callable] $storageWrappers + */ + private $storageWrappers = []; + + /** + * allow modifier storage behaviour by adding wrappers around storages + * + * $callback should be a function of type (string $mountPoint, Storage $storage) => Storage + * + * @param string $wrapperName name of the wrapper + * @param callable $callback callback + * @param int $priority wrappers with the lower priority are applied last (meaning they get called first) + * @param \OCP\Files\Mount\IMountPoint[] $existingMounts existing mount points to apply the wrapper to + * @return bool true if the wrapper was added, false if there was already a wrapper with this + * name registered + */ + public function addStorageWrapper($wrapperName, $callback, $priority = 50, $existingMounts = []) { + if (isset($this->storageWrappers[$wrapperName])) { + return false; + } + + // apply to existing mounts before registering it to prevent applying it double in MountPoint::createStorage + foreach ($existingMounts as $mount) { + $mount->wrapStorage($callback); + } + + $this->storageWrappers[$wrapperName] = ['wrapper' => $callback, 'priority' => $priority]; + return true; + } + + /** + * Remove a storage wrapper by name. + * Note: internal method only to be used for cleanup + * + * @param string $wrapperName name of the wrapper + * @internal + */ + public function removeStorageWrapper($wrapperName) { + unset($this->storageWrappers[$wrapperName]); + } + + /** + * Create an instance of a storage and apply the registered storage wrappers + * + * @param \OCP\Files\Mount\IMountPoint $mountPoint + * @param string $class + * @param array $arguments + * @return \OCP\Files\Storage + */ + public function getInstance(IMountPoint $mountPoint, $class, $arguments) { + return $this->wrap($mountPoint, new $class($arguments)); + } + + /** + * @param \OCP\Files\Mount\IMountPoint $mountPoint + * @param \OCP\Files\Storage $storage + * @return \OCP\Files\Storage + */ + public function wrap(IMountPoint $mountPoint, $storage) { + $wrappers = array_values($this->storageWrappers); + usort($wrappers, function ($a, $b) { + return $b['priority'] - $a['priority']; + }); + /** @var callable[] $wrappers */ + $wrappers = array_map(function ($wrapper) { + return $wrapper['wrapper']; + }, $wrappers); + foreach ($wrappers as $wrapper) { + $storage = $wrapper($mountPoint->getMountPoint(), $storage, $mountPoint); + if (!($storage instanceof \OCP\Files\Storage)) { + throw new \Exception('Invalid result from storage wrapper'); + } + } + return $storage; + } +} diff --git a/lib/private/Files/Storage/Temporary.php b/lib/private/Files/Storage/Temporary.php new file mode 100644 index 00000000000..2d8e84c2d52 --- /dev/null +++ b/lib/private/Files/Storage/Temporary.php @@ -0,0 +1,47 @@ + + * @author Robin Appelman + * @author Thomas Müller + * @author Vincent Petry + * + * @copyright Copyright (c) 2016, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Files\Storage; + +/** + * local storage backend in temporary folder for testing purpose + */ +class Temporary extends Local{ + public function __construct($arguments = null) { + parent::__construct(array('datadir' => \OC::$server->getTempManager()->getTemporaryFolder())); + } + + public function cleanUp() { + \OC_Helper::rmdirr($this->datadir); + } + + public function __destruct() { + parent::__destruct(); + $this->cleanUp(); + } + + public function getDataDir() { + return $this->datadir; + } +} diff --git a/lib/private/Files/Storage/Wrapper/Availability.php b/lib/private/Files/Storage/Wrapper/Availability.php new file mode 100644 index 00000000000..0ed31ba854a --- /dev/null +++ b/lib/private/Files/Storage/Wrapper/Availability.php @@ -0,0 +1,461 @@ + + * @author Robin McCorkell + * + * @copyright Copyright (c) 2016, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ +namespace OC\Files\Storage\Wrapper; + +/** + * Availability checker for storages + * + * Throws a StorageNotAvailableException for storages with known failures + */ +class Availability extends Wrapper { + const RECHECK_TTL_SEC = 600; // 10 minutes + + public static function shouldRecheck($availability) { + if (!$availability['available']) { + // trigger a recheck if TTL reached + if ((time() - $availability['last_checked']) > self::RECHECK_TTL_SEC) { + return true; + } + } + return false; + } + + /** + * @return bool + */ + private function updateAvailability() { + try { + $result = $this->test(); + } catch (\Exception $e) { + $result = false; + } + $this->setAvailability($result); + return $result; + } + + /** + * @return bool + */ + private function isAvailable() { + $availability = $this->getAvailability(); + if (self::shouldRecheck($availability)) { + return $this->updateAvailability(); + } + return $availability['available']; + } + + /** + * @throws \OCP\Files\StorageNotAvailableException + */ + private function checkAvailability() { + if (!$this->isAvailable()) { + throw new \OCP\Files\StorageNotAvailableException(); + } + } + + /** {@inheritdoc} */ + public function mkdir($path) { + $this->checkAvailability(); + try { + return parent::mkdir($path); + } catch (\OCP\Files\StorageNotAvailableException $e) { + $this->setAvailability(false); + throw $e; + } + } + + /** {@inheritdoc} */ + public function rmdir($path) { + $this->checkAvailability(); + try { + return parent::rmdir($path); + } catch (\OCP\Files\StorageNotAvailableException $e) { + $this->setAvailability(false); + throw $e; + } + } + + /** {@inheritdoc} */ + public function opendir($path) { + $this->checkAvailability(); + try { + return parent::opendir($path); + } catch (\OCP\Files\StorageNotAvailableException $e) { + $this->setAvailability(false); + throw $e; + } + } + + /** {@inheritdoc} */ + public function is_dir($path) { + $this->checkAvailability(); + try { + return parent::is_dir($path); + } catch (\OCP\Files\StorageNotAvailableException $e) { + $this->setAvailability(false); + throw $e; + } + } + + /** {@inheritdoc} */ + public function is_file($path) { + $this->checkAvailability(); + try { + return parent::is_file($path); + } catch (\OCP\Files\StorageNotAvailableException $e) { + $this->setAvailability(false); + throw $e; + } + } + + /** {@inheritdoc} */ + public function stat($path) { + $this->checkAvailability(); + try { + return parent::stat($path); + } catch (\OCP\Files\StorageNotAvailableException $e) { + $this->setAvailability(false); + throw $e; + } + } + + /** {@inheritdoc} */ + public function filetype($path) { + $this->checkAvailability(); + try { + return parent::filetype($path); + } catch (\OCP\Files\StorageNotAvailableException $e) { + $this->setAvailability(false); + throw $e; + } + } + + /** {@inheritdoc} */ + public function filesize($path) { + $this->checkAvailability(); + try { + return parent::filesize($path); + } catch (\OCP\Files\StorageNotAvailableException $e) { + $this->setAvailability(false); + throw $e; + } + } + + /** {@inheritdoc} */ + public function isCreatable($path) { + $this->checkAvailability(); + try { + return parent::isCreatable($path); + } catch (\OCP\Files\StorageNotAvailableException $e) { + $this->setAvailability(false); + throw $e; + } + } + + /** {@inheritdoc} */ + public function isReadable($path) { + $this->checkAvailability(); + try { + return parent::isReadable($path); + } catch (\OCP\Files\StorageNotAvailableException $e) { + $this->setAvailability(false); + throw $e; + } + } + + /** {@inheritdoc} */ + public function isUpdatable($path) { + $this->checkAvailability(); + try { + return parent::isUpdatable($path); + } catch (\OCP\Files\StorageNotAvailableException $e) { + $this->setAvailability(false); + throw $e; + } + } + + /** {@inheritdoc} */ + public function isDeletable($path) { + $this->checkAvailability(); + try { + return parent::isDeletable($path); + } catch (\OCP\Files\StorageNotAvailableException $e) { + $this->setAvailability(false); + throw $e; + } + } + + /** {@inheritdoc} */ + public function isSharable($path) { + $this->checkAvailability(); + try { + return parent::isSharable($path); + } catch (\OCP\Files\StorageNotAvailableException $e) { + $this->setAvailability(false); + throw $e; + } + } + + /** {@inheritdoc} */ + public function getPermissions($path) { + $this->checkAvailability(); + try { + return parent::getPermissions($path); + } catch (\OCP\Files\StorageNotAvailableException $e) { + $this->setAvailability(false); + throw $e; + } + } + + /** {@inheritdoc} */ + public function file_exists($path) { + if ($path === '') { + return true; + } + $this->checkAvailability(); + try { + return parent::file_exists($path); + } catch (\OCP\Files\StorageNotAvailableException $e) { + $this->setAvailability(false); + throw $e; + } + } + + /** {@inheritdoc} */ + public function filemtime($path) { + $this->checkAvailability(); + try { + return parent::filemtime($path); + } catch (\OCP\Files\StorageNotAvailableException $e) { + $this->setAvailability(false); + throw $e; + } + } + + /** {@inheritdoc} */ + public function file_get_contents($path) { + $this->checkAvailability(); + try { + return parent::file_get_contents($path); + } catch (\OCP\Files\StorageNotAvailableException $e) { + $this->setAvailability(false); + throw $e; + } + } + + /** {@inheritdoc} */ + public function file_put_contents($path, $data) { + $this->checkAvailability(); + try { + return parent::file_put_contents($path, $data); + } catch (\OCP\Files\StorageNotAvailableException $e) { + $this->setAvailability(false); + throw $e; + } + } + + /** {@inheritdoc} */ + public function unlink($path) { + $this->checkAvailability(); + try { + return parent::unlink($path); + } catch (\OCP\Files\StorageNotAvailableException $e) { + $this->setAvailability(false); + throw $e; + } + } + + /** {@inheritdoc} */ + public function rename($path1, $path2) { + $this->checkAvailability(); + try { + return parent::rename($path1, $path2); + } catch (\OCP\Files\StorageNotAvailableException $e) { + $this->setAvailability(false); + throw $e; + } + } + + /** {@inheritdoc} */ + public function copy($path1, $path2) { + $this->checkAvailability(); + try { + return parent::copy($path1, $path2); + } catch (\OCP\Files\StorageNotAvailableException $e) { + $this->setAvailability(false); + throw $e; + } + } + + /** {@inheritdoc} */ + public function fopen($path, $mode) { + $this->checkAvailability(); + try { + return parent::fopen($path, $mode); + } catch (\OCP\Files\StorageNotAvailableException $e) { + $this->setAvailability(false); + throw $e; + } + } + + /** {@inheritdoc} */ + public function getMimeType($path) { + $this->checkAvailability(); + try { + return parent::getMimeType($path); + } catch (\OCP\Files\StorageNotAvailableException $e) { + $this->setAvailability(false); + throw $e; + } + } + + /** {@inheritdoc} */ + public function hash($type, $path, $raw = false) { + $this->checkAvailability(); + try { + return parent::hash($type, $path, $raw); + } catch (\OCP\Files\StorageNotAvailableException $e) { + $this->setAvailability(false); + throw $e; + } + } + + /** {@inheritdoc} */ + public function free_space($path) { + $this->checkAvailability(); + try { + return parent::free_space($path); + } catch (\OCP\Files\StorageNotAvailableException $e) { + $this->setAvailability(false); + throw $e; + } + } + + /** {@inheritdoc} */ + public function search($query) { + $this->checkAvailability(); + try { + return parent::search($query); + } catch (\OCP\Files\StorageNotAvailableException $e) { + $this->setAvailability(false); + throw $e; + } + } + + /** {@inheritdoc} */ + public function touch($path, $mtime = null) { + $this->checkAvailability(); + try { + return parent::touch($path, $mtime); + } catch (\OCP\Files\StorageNotAvailableException $e) { + $this->setAvailability(false); + throw $e; + } + } + + /** {@inheritdoc} */ + public function getLocalFile($path) { + $this->checkAvailability(); + try { + return parent::getLocalFile($path); + } catch (\OCP\Files\StorageNotAvailableException $e) { + $this->setAvailability(false); + throw $e; + } + } + + /** {@inheritdoc} */ + public function hasUpdated($path, $time) { + $this->checkAvailability(); + try { + return parent::hasUpdated($path, $time); + } catch (\OCP\Files\StorageNotAvailableException $e) { + $this->setAvailability(false); + throw $e; + } + } + + /** {@inheritdoc} */ + public function getOwner($path) { + try { + return parent::getOwner($path); + } catch (\OCP\Files\StorageNotAvailableException $e) { + $this->setAvailability(false); + throw $e; + } + } + + /** {@inheritdoc} */ + public function getETag($path) { + $this->checkAvailability(); + try { + return parent::getETag($path); + } catch (\OCP\Files\StorageNotAvailableException $e) { + $this->setAvailability(false); + throw $e; + } + } + + /** {@inheritdoc} */ + public function getDirectDownload($path) { + $this->checkAvailability(); + try { + return parent::getDirectDownload($path); + } catch (\OCP\Files\StorageNotAvailableException $e) { + $this->setAvailability(false); + throw $e; + } + } + + /** {@inheritdoc} */ + public function copyFromStorage(\OCP\Files\Storage $sourceStorage, $sourceInternalPath, $targetInternalPath) { + $this->checkAvailability(); + try { + return parent::copyFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath); + } catch (\OCP\Files\StorageNotAvailableException $e) { + $this->setAvailability(false); + throw $e; + } + } + + /** {@inheritdoc} */ + public function moveFromStorage(\OCP\Files\Storage $sourceStorage, $sourceInternalPath, $targetInternalPath) { + $this->checkAvailability(); + try { + return parent::moveFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath); + } catch (\OCP\Files\StorageNotAvailableException $e) { + $this->setAvailability(false); + throw $e; + } + } + + /** {@inheritdoc} */ + public function getMetaData($path) { + $this->checkAvailability(); + try { + return parent::getMetaData($path); + } catch (\OCP\Files\StorageNotAvailableException $e) { + $this->setAvailability(false); + throw $e; + } + } +} diff --git a/lib/private/Files/Storage/Wrapper/Encryption.php b/lib/private/Files/Storage/Wrapper/Encryption.php new file mode 100644 index 00000000000..02da978a700 --- /dev/null +++ b/lib/private/Files/Storage/Wrapper/Encryption.php @@ -0,0 +1,993 @@ + + * @author Joas Schilling + * @author Lukas Reschke + * @author Robin Appelman + * @author Thomas Müller + * @author Vincent Petry + * + * @copyright Copyright (c) 2016, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Files\Storage\Wrapper; + +use OC\Encryption\Exceptions\ModuleDoesNotExistsException; +use OC\Encryption\Update; +use OC\Encryption\Util; +use OC\Files\Cache\CacheEntry; +use OC\Files\Filesystem; +use OC\Files\Mount\Manager; +use OC\Files\Storage\LocalTempFileTrait; +use OC\Memcache\ArrayCache; +use OCP\Encryption\Exceptions\GenericEncryptionException; +use OCP\Encryption\IFile; +use OCP\Encryption\IManager; +use OCP\Encryption\Keys\IStorage; +use OCP\Files\Mount\IMountPoint; +use OCP\Files\Storage; +use OCP\ILogger; +use OCP\Files\Cache\ICacheEntry; + +class Encryption extends Wrapper { + + use LocalTempFileTrait; + + /** @var string */ + private $mountPoint; + + /** @var \OC\Encryption\Util */ + private $util; + + /** @var \OCP\Encryption\IManager */ + private $encryptionManager; + + /** @var \OCP\ILogger */ + private $logger; + + /** @var string */ + private $uid; + + /** @var array */ + protected $unencryptedSize; + + /** @var \OCP\Encryption\IFile */ + private $fileHelper; + + /** @var IMountPoint */ + private $mount; + + /** @var IStorage */ + private $keyStorage; + + /** @var Update */ + private $update; + + /** @var Manager */ + private $mountManager; + + /** @var array remember for which path we execute the repair step to avoid recursions */ + private $fixUnencryptedSizeOf = array(); + + /** @var ArrayCache */ + private $arrayCache; + + /** + * @param array $parameters + * @param IManager $encryptionManager + * @param Util $util + * @param ILogger $logger + * @param IFile $fileHelper + * @param string $uid + * @param IStorage $keyStorage + * @param Update $update + * @param Manager $mountManager + * @param ArrayCache $arrayCache + */ + public function __construct( + $parameters, + IManager $encryptionManager = null, + Util $util = null, + ILogger $logger = null, + IFile $fileHelper = null, + $uid = null, + IStorage $keyStorage = null, + Update $update = null, + Manager $mountManager = null, + ArrayCache $arrayCache = null + ) { + + $this->mountPoint = $parameters['mountPoint']; + $this->mount = $parameters['mount']; + $this->encryptionManager = $encryptionManager; + $this->util = $util; + $this->logger = $logger; + $this->uid = $uid; + $this->fileHelper = $fileHelper; + $this->keyStorage = $keyStorage; + $this->unencryptedSize = array(); + $this->update = $update; + $this->mountManager = $mountManager; + $this->arrayCache = $arrayCache; + parent::__construct($parameters); + } + + /** + * see http://php.net/manual/en/function.filesize.php + * The result for filesize when called on a folder is required to be 0 + * + * @param string $path + * @return int + */ + public function filesize($path) { + $fullPath = $this->getFullPath($path); + + /** @var CacheEntry $info */ + $info = $this->getCache()->get($path); + if (isset($this->unencryptedSize[$fullPath])) { + $size = $this->unencryptedSize[$fullPath]; + // update file cache + if ($info instanceof ICacheEntry) { + $info = $info->getData(); + $info['encrypted'] = $info['encryptedVersion']; + } else { + if (!is_array($info)) { + $info = []; + } + $info['encrypted'] = true; + } + + $info['size'] = $size; + $this->getCache()->put($path, $info); + + return $size; + } + + if (isset($info['fileid']) && $info['encrypted']) { + return $this->verifyUnencryptedSize($path, $info['size']); + } + + return $this->storage->filesize($path); + } + + /** + * @param string $path + * @return array + */ + public function getMetaData($path) { + $data = $this->storage->getMetaData($path); + if (is_null($data)) { + return null; + } + $fullPath = $this->getFullPath($path); + $info = $this->getCache()->get($path); + + if (isset($this->unencryptedSize[$fullPath])) { + $data['encrypted'] = true; + $data['size'] = $this->unencryptedSize[$fullPath]; + } else { + if (isset($info['fileid']) && $info['encrypted']) { + $data['size'] = $this->verifyUnencryptedSize($path, $info['size']); + $data['encrypted'] = true; + } + } + + if (isset($info['encryptedVersion']) && $info['encryptedVersion'] > 1) { + $data['encryptedVersion'] = $info['encryptedVersion']; + } + + return $data; + } + + /** + * see http://php.net/manual/en/function.file_get_contents.php + * + * @param string $path + * @return string + */ + public function file_get_contents($path) { + + $encryptionModule = $this->getEncryptionModule($path); + + if ($encryptionModule) { + $handle = $this->fopen($path, "r"); + if (!$handle) { + return false; + } + $data = stream_get_contents($handle); + fclose($handle); + return $data; + } + return $this->storage->file_get_contents($path); + } + + /** + * see http://php.net/manual/en/function.file_put_contents.php + * + * @param string $path + * @param string $data + * @return bool + */ + public function file_put_contents($path, $data) { + // file put content will always be translated to a stream write + $handle = $this->fopen($path, 'w'); + if (is_resource($handle)) { + $written = fwrite($handle, $data); + fclose($handle); + return $written; + } + + return false; + } + + /** + * see http://php.net/manual/en/function.unlink.php + * + * @param string $path + * @return bool + */ + public function unlink($path) { + $fullPath = $this->getFullPath($path); + if ($this->util->isExcluded($fullPath)) { + return $this->storage->unlink($path); + } + + $encryptionModule = $this->getEncryptionModule($path); + if ($encryptionModule) { + $this->keyStorage->deleteAllFileKeys($this->getFullPath($path)); + } + + return $this->storage->unlink($path); + } + + /** + * see http://php.net/manual/en/function.rename.php + * + * @param string $path1 + * @param string $path2 + * @return bool + */ + public function rename($path1, $path2) { + + $result = $this->storage->rename($path1, $path2); + + if ($result && + // versions always use the keys from the original file, so we can skip + // this step for versions + $this->isVersion($path2) === false && + $this->encryptionManager->isEnabled()) { + $source = $this->getFullPath($path1); + if (!$this->util->isExcluded($source)) { + $target = $this->getFullPath($path2); + if (isset($this->unencryptedSize[$source])) { + $this->unencryptedSize[$target] = $this->unencryptedSize[$source]; + } + $this->keyStorage->renameKeys($source, $target); + $module = $this->getEncryptionModule($path2); + if ($module) { + $module->update($target, $this->uid, []); + } + } + } + + return $result; + } + + /** + * see http://php.net/manual/en/function.rmdir.php + * + * @param string $path + * @return bool + */ + public function rmdir($path) { + $result = $this->storage->rmdir($path); + $fullPath = $this->getFullPath($path); + if ($result && + $this->util->isExcluded($fullPath) === false && + $this->encryptionManager->isEnabled() + ) { + $this->keyStorage->deleteAllFileKeys($fullPath); + } + + return $result; + } + + /** + * check if a file can be read + * + * @param string $path + * @return bool + */ + public function isReadable($path) { + + $isReadable = true; + + $metaData = $this->getMetaData($path); + if ( + !$this->is_dir($path) && + isset($metaData['encrypted']) && + $metaData['encrypted'] === true + ) { + $fullPath = $this->getFullPath($path); + $module = $this->getEncryptionModule($path); + $isReadable = $module->isReadable($fullPath, $this->uid); + } + + return $this->storage->isReadable($path) && $isReadable; + } + + /** + * see http://php.net/manual/en/function.copy.php + * + * @param string $path1 + * @param string $path2 + * @return bool + */ + public function copy($path1, $path2) { + + $source = $this->getFullPath($path1); + + if ($this->util->isExcluded($source)) { + return $this->storage->copy($path1, $path2); + } + + // need to stream copy file by file in case we copy between a encrypted + // and a unencrypted storage + $this->unlink($path2); + $result = $this->copyFromStorage($this, $path1, $path2); + + return $result; + } + + /** + * see http://php.net/manual/en/function.fopen.php + * + * @param string $path + * @param string $mode + * @return resource|bool + * @throws GenericEncryptionException + * @throws ModuleDoesNotExistsException + */ + public function fopen($path, $mode) { + + // check if the file is stored in the array cache, this means that we + // copy a file over to the versions folder, in this case we don't want to + // decrypt it + if ($this->arrayCache->hasKey('encryption_copy_version_' . $path)) { + $this->arrayCache->remove('encryption_copy_version_' . $path); + return $this->storage->fopen($path, $mode); + } + + $encryptionEnabled = $this->encryptionManager->isEnabled(); + $shouldEncrypt = false; + $encryptionModule = null; + $header = $this->getHeader($path); + $signed = (isset($header['signed']) && $header['signed'] === 'true') ? true : false; + $fullPath = $this->getFullPath($path); + $encryptionModuleId = $this->util->getEncryptionModuleId($header); + + if ($this->util->isExcluded($fullPath) === false) { + + $size = $unencryptedSize = 0; + $realFile = $this->util->stripPartialFileExtension($path); + $targetExists = $this->file_exists($realFile) || $this->file_exists($path); + $targetIsEncrypted = false; + if ($targetExists) { + // in case the file exists we require the explicit module as + // specified in the file header - otherwise we need to fail hard to + // prevent data loss on client side + if (!empty($encryptionModuleId)) { + $targetIsEncrypted = true; + $encryptionModule = $this->encryptionManager->getEncryptionModule($encryptionModuleId); + } + + if ($this->file_exists($path)) { + $size = $this->storage->filesize($path); + $unencryptedSize = $this->filesize($path); + } else { + $size = $unencryptedSize = 0; + } + } + + try { + + if ( + $mode === 'w' + || $mode === 'w+' + || $mode === 'wb' + || $mode === 'wb+' + ) { + // don't overwrite encrypted files if encryption is not enabled + if ($targetIsEncrypted && $encryptionEnabled === false) { + throw new GenericEncryptionException('Tried to access encrypted file but encryption is not enabled'); + } + if ($encryptionEnabled) { + // if $encryptionModuleId is empty, the default module will be used + $encryptionModule = $this->encryptionManager->getEncryptionModule($encryptionModuleId); + $shouldEncrypt = $encryptionModule->shouldEncrypt($fullPath); + $signed = true; + } + } else { + $info = $this->getCache()->get($path); + // only get encryption module if we found one in the header + // or if file should be encrypted according to the file cache + if (!empty($encryptionModuleId)) { + $encryptionModule = $this->encryptionManager->getEncryptionModule($encryptionModuleId); + $shouldEncrypt = true; + } else if (empty($encryptionModuleId) && $info['encrypted'] === true) { + // we come from a old installation. No header and/or no module defined + // but the file is encrypted. In this case we need to use the + // OC_DEFAULT_MODULE to read the file + $encryptionModule = $this->encryptionManager->getEncryptionModule('OC_DEFAULT_MODULE'); + $shouldEncrypt = true; + $targetIsEncrypted = true; + } + } + } catch (ModuleDoesNotExistsException $e) { + $this->logger->warning('Encryption module "' . $encryptionModuleId . + '" not found, file will be stored unencrypted (' . $e->getMessage() . ')'); + } + + // encryption disabled on write of new file and write to existing unencrypted file -> don't encrypt + if (!$encryptionEnabled || !$this->mount->getOption('encrypt', true)) { + if (!$targetExists || !$targetIsEncrypted) { + $shouldEncrypt = false; + } + } + + if ($shouldEncrypt === true && $encryptionModule !== null) { + $headerSize = $this->getHeaderSize($path); + $source = $this->storage->fopen($path, $mode); + if (!is_resource($source)) { + return false; + } + $handle = \OC\Files\Stream\Encryption::wrap($source, $path, $fullPath, $header, + $this->uid, $encryptionModule, $this->storage, $this, $this->util, $this->fileHelper, $mode, + $size, $unencryptedSize, $headerSize, $signed); + return $handle; + } + + } + + return $this->storage->fopen($path, $mode); + } + + + /** + * perform some plausibility checks if the the unencrypted size is correct. + * If not, we calculate the correct unencrypted size and return it + * + * @param string $path internal path relative to the storage root + * @param int $unencryptedSize size of the unencrypted file + * + * @return int unencrypted size + */ + protected function verifyUnencryptedSize($path, $unencryptedSize) { + + $size = $this->storage->filesize($path); + $result = $unencryptedSize; + + if ($unencryptedSize < 0 || + ($size > 0 && $unencryptedSize === $size) + ) { + // check if we already calculate the unencrypted size for the + // given path to avoid recursions + if (isset($this->fixUnencryptedSizeOf[$this->getFullPath($path)]) === false) { + $this->fixUnencryptedSizeOf[$this->getFullPath($path)] = true; + try { + $result = $this->fixUnencryptedSize($path, $size, $unencryptedSize); + } catch (\Exception $e) { + $this->logger->error('Couldn\'t re-calculate unencrypted size for '. $path); + $this->logger->logException($e); + } + unset($this->fixUnencryptedSizeOf[$this->getFullPath($path)]); + } + } + + return $result; + } + + /** + * calculate the unencrypted size + * + * @param string $path internal path relative to the storage root + * @param int $size size of the physical file + * @param int $unencryptedSize size of the unencrypted file + * + * @return int calculated unencrypted size + */ + protected function fixUnencryptedSize($path, $size, $unencryptedSize) { + + $headerSize = $this->getHeaderSize($path); + $header = $this->getHeader($path); + $encryptionModule = $this->getEncryptionModule($path); + + $stream = $this->storage->fopen($path, 'r'); + + // if we couldn't open the file we return the old unencrypted size + if (!is_resource($stream)) { + $this->logger->error('Could not open ' . $path . '. Recalculation of unencrypted size aborted.'); + return $unencryptedSize; + } + + $newUnencryptedSize = 0; + $size -= $headerSize; + $blockSize = $this->util->getBlockSize(); + + // if a header exists we skip it + if ($headerSize > 0) { + fread($stream, $headerSize); + } + + // fast path, else the calculation for $lastChunkNr is bogus + if ($size === 0) { + return 0; + } + + $signed = (isset($header['signed']) && $header['signed'] === 'true') ? true : false; + $unencryptedBlockSize = $encryptionModule->getUnencryptedBlockSize($signed); + + // calculate last chunk nr + // next highest is end of chunks, one subtracted is last one + // we have to read the last chunk, we can't just calculate it (because of padding etc) + + $lastChunkNr = ceil($size/ $blockSize)-1; + // calculate last chunk position + $lastChunkPos = ($lastChunkNr * $blockSize); + // try to fseek to the last chunk, if it fails we have to read the whole file + if (@fseek($stream, $lastChunkPos, SEEK_CUR) === 0) { + $newUnencryptedSize += $lastChunkNr * $unencryptedBlockSize; + } + + $lastChunkContentEncrypted=''; + $count = $blockSize; + + while ($count > 0) { + $data=fread($stream, $blockSize); + $count=strlen($data); + $lastChunkContentEncrypted .= $data; + if(strlen($lastChunkContentEncrypted) > $blockSize) { + $newUnencryptedSize += $unencryptedBlockSize; + $lastChunkContentEncrypted=substr($lastChunkContentEncrypted, $blockSize); + } + } + + fclose($stream); + + // we have to decrypt the last chunk to get it actual size + $encryptionModule->begin($this->getFullPath($path), $this->uid, 'r', $header, []); + $decryptedLastChunk = $encryptionModule->decrypt($lastChunkContentEncrypted, $lastChunkNr . 'end'); + $decryptedLastChunk .= $encryptionModule->end($this->getFullPath($path), $lastChunkNr . 'end'); + + // calc the real file size with the size of the last chunk + $newUnencryptedSize += strlen($decryptedLastChunk); + + $this->updateUnencryptedSize($this->getFullPath($path), $newUnencryptedSize); + + // write to cache if applicable + $cache = $this->storage->getCache(); + if ($cache) { + $entry = $cache->get($path); + $cache->update($entry['fileid'], ['size' => $newUnencryptedSize]); + } + + return $newUnencryptedSize; + } + + /** + * @param Storage $sourceStorage + * @param string $sourceInternalPath + * @param string $targetInternalPath + * @param bool $preserveMtime + * @return bool + */ + public function moveFromStorage(Storage $sourceStorage, $sourceInternalPath, $targetInternalPath, $preserveMtime = true) { + if ($sourceStorage === $this) { + return $this->rename($sourceInternalPath, $targetInternalPath); + } + + // TODO clean this up once the underlying moveFromStorage in OC\Files\Storage\Wrapper\Common is fixed: + // - call $this->storage->moveFromStorage() instead of $this->copyBetweenStorage + // - copy the file cache update from $this->copyBetweenStorage to this method + // - copy the copyKeys() call from $this->copyBetweenStorage to this method + // - remove $this->copyBetweenStorage + + if (!$sourceStorage->isDeletable($sourceInternalPath)) { + return false; + } + + $result = $this->copyBetweenStorage($sourceStorage, $sourceInternalPath, $targetInternalPath, $preserveMtime, true); + if ($result) { + if ($sourceStorage->is_dir($sourceInternalPath)) { + $result &= $sourceStorage->rmdir($sourceInternalPath); + } else { + $result &= $sourceStorage->unlink($sourceInternalPath); + } + } + return $result; + } + + + /** + * @param Storage $sourceStorage + * @param string $sourceInternalPath + * @param string $targetInternalPath + * @param bool $preserveMtime + * @param bool $isRename + * @return bool + */ + public function copyFromStorage(Storage $sourceStorage, $sourceInternalPath, $targetInternalPath, $preserveMtime = false, $isRename = false) { + + // TODO clean this up once the underlying moveFromStorage in OC\Files\Storage\Wrapper\Common is fixed: + // - call $this->storage->copyFromStorage() instead of $this->copyBetweenStorage + // - copy the file cache update from $this->copyBetweenStorage to this method + // - copy the copyKeys() call from $this->copyBetweenStorage to this method + // - remove $this->copyBetweenStorage + + return $this->copyBetweenStorage($sourceStorage, $sourceInternalPath, $targetInternalPath, $preserveMtime, $isRename); + } + + /** + * Update the encrypted cache version in the database + * + * @param Storage $sourceStorage + * @param string $sourceInternalPath + * @param string $targetInternalPath + * @param bool $isRename + */ + private function updateEncryptedVersion(Storage $sourceStorage, $sourceInternalPath, $targetInternalPath, $isRename) { + $isEncrypted = $this->encryptionManager->isEnabled() && $this->mount->getOption('encrypt', true) ? 1 : 0; + $cacheInformation = [ + 'encrypted' => (bool)$isEncrypted, + ]; + if($isEncrypted === 1) { + $encryptedVersion = $sourceStorage->getCache()->get($sourceInternalPath)['encryptedVersion']; + + // In case of a move operation from an unencrypted to an encrypted + // storage the old encrypted version would stay with "0" while the + // correct value would be "1". Thus we manually set the value to "1" + // for those cases. + // See also https://github.com/owncloud/core/issues/23078 + if($encryptedVersion === 0) { + $encryptedVersion = 1; + } + + $cacheInformation['encryptedVersion'] = $encryptedVersion; + } + + // in case of a rename we need to manipulate the source cache because + // this information will be kept for the new target + if ($isRename) { + $sourceStorage->getCache()->put($sourceInternalPath, $cacheInformation); + } else { + $this->getCache()->put($targetInternalPath, $cacheInformation); + } + } + + /** + * copy file between two storages + * + * @param Storage $sourceStorage + * @param string $sourceInternalPath + * @param string $targetInternalPath + * @param bool $preserveMtime + * @param bool $isRename + * @return bool + * @throws \Exception + */ + private function copyBetweenStorage(Storage $sourceStorage, $sourceInternalPath, $targetInternalPath, $preserveMtime, $isRename) { + + // for versions we have nothing to do, because versions should always use the + // key from the original file. Just create a 1:1 copy and done + if ($this->isVersion($targetInternalPath) || + $this->isVersion($sourceInternalPath)) { + // remember that we try to create a version so that we can detect it during + // fopen($sourceInternalPath) and by-pass the encryption in order to + // create a 1:1 copy of the file + $this->arrayCache->set('encryption_copy_version_' . $sourceInternalPath, true); + $result = $this->storage->copyFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath); + $this->arrayCache->remove('encryption_copy_version_' . $sourceInternalPath); + if ($result) { + $info = $this->getCache('', $sourceStorage)->get($sourceInternalPath); + // make sure that we update the unencrypted size for the version + if (isset($info['encrypted']) && $info['encrypted'] === true) { + $this->updateUnencryptedSize( + $this->getFullPath($targetInternalPath), + $info['size'] + ); + } + $this->updateEncryptedVersion($sourceStorage, $sourceInternalPath, $targetInternalPath, $isRename); + } + return $result; + } + + // first copy the keys that we reuse the existing file key on the target location + // and don't create a new one which would break versions for example. + $mount = $this->mountManager->findByStorageId($sourceStorage->getId()); + if (count($mount) === 1) { + $mountPoint = $mount[0]->getMountPoint(); + $source = $mountPoint . '/' . $sourceInternalPath; + $target = $this->getFullPath($targetInternalPath); + $this->copyKeys($source, $target); + } else { + $this->logger->error('Could not find mount point, can\'t keep encryption keys'); + } + + if ($sourceStorage->is_dir($sourceInternalPath)) { + $dh = $sourceStorage->opendir($sourceInternalPath); + $result = $this->mkdir($targetInternalPath); + if (is_resource($dh)) { + while ($result and ($file = readdir($dh)) !== false) { + if (!Filesystem::isIgnoredDir($file)) { + $result &= $this->copyFromStorage($sourceStorage, $sourceInternalPath . '/' . $file, $targetInternalPath . '/' . $file, false, $isRename); + } + } + } + } else { + try { + $source = $sourceStorage->fopen($sourceInternalPath, 'r'); + $target = $this->fopen($targetInternalPath, 'w'); + list(, $result) = \OC_Helper::streamCopy($source, $target); + fclose($source); + fclose($target); + } catch (\Exception $e) { + fclose($source); + fclose($target); + throw $e; + } + if($result) { + if ($preserveMtime) { + $this->touch($targetInternalPath, $sourceStorage->filemtime($sourceInternalPath)); + } + $this->updateEncryptedVersion($sourceStorage, $sourceInternalPath, $targetInternalPath, $isRename); + } else { + // delete partially written target file + $this->unlink($targetInternalPath); + // delete cache entry that was created by fopen + $this->getCache()->remove($targetInternalPath); + } + } + return (bool)$result; + + } + + /** + * get the path to a local version of the file. + * The local version of the file can be temporary and doesn't have to be persistent across requests + * + * @param string $path + * @return string + */ + public function getLocalFile($path) { + if ($this->encryptionManager->isEnabled()) { + $cachedFile = $this->getCachedFile($path); + if (is_string($cachedFile)) { + return $cachedFile; + } + } + return $this->storage->getLocalFile($path); + } + + /** + * Returns the wrapped storage's value for isLocal() + * + * @return bool wrapped storage's isLocal() value + */ + public function isLocal() { + if ($this->encryptionManager->isEnabled()) { + return false; + } + return $this->storage->isLocal(); + } + + /** + * see http://php.net/manual/en/function.stat.php + * only the following keys are required in the result: size and mtime + * + * @param string $path + * @return array + */ + public function stat($path) { + $stat = $this->storage->stat($path); + $fileSize = $this->filesize($path); + $stat['size'] = $fileSize; + $stat[7] = $fileSize; + return $stat; + } + + /** + * see http://php.net/manual/en/function.hash.php + * + * @param string $type + * @param string $path + * @param bool $raw + * @return string + */ + public function hash($type, $path, $raw = false) { + $fh = $this->fopen($path, 'rb'); + $ctx = hash_init($type); + hash_update_stream($ctx, $fh); + fclose($fh); + return hash_final($ctx, $raw); + } + + /** + * return full path, including mount point + * + * @param string $path relative to mount point + * @return string full path including mount point + */ + protected function getFullPath($path) { + return Filesystem::normalizePath($this->mountPoint . '/' . $path); + } + + /** + * read first block of encrypted file, typically this will contain the + * encryption header + * + * @param string $path + * @return string + */ + protected function readFirstBlock($path) { + $firstBlock = ''; + if ($this->storage->file_exists($path)) { + $handle = $this->storage->fopen($path, 'r'); + $firstBlock = fread($handle, $this->util->getHeaderSize()); + fclose($handle); + } + return $firstBlock; + } + + /** + * return header size of given file + * + * @param string $path + * @return int + */ + protected function getHeaderSize($path) { + $headerSize = 0; + $realFile = $this->util->stripPartialFileExtension($path); + if ($this->storage->file_exists($realFile)) { + $path = $realFile; + } + $firstBlock = $this->readFirstBlock($path); + + if (substr($firstBlock, 0, strlen(Util::HEADER_START)) === Util::HEADER_START) { + $headerSize = $this->util->getHeaderSize(); + } + + return $headerSize; + } + + /** + * parse raw header to array + * + * @param string $rawHeader + * @return array + */ + protected function parseRawHeader($rawHeader) { + $result = array(); + if (substr($rawHeader, 0, strlen(Util::HEADER_START)) === Util::HEADER_START) { + $header = $rawHeader; + $endAt = strpos($header, Util::HEADER_END); + if ($endAt !== false) { + $header = substr($header, 0, $endAt + strlen(Util::HEADER_END)); + + // +1 to not start with an ':' which would result in empty element at the beginning + $exploded = explode(':', substr($header, strlen(Util::HEADER_START)+1)); + + $element = array_shift($exploded); + while ($element !== Util::HEADER_END) { + $result[$element] = array_shift($exploded); + $element = array_shift($exploded); + } + } + } + + return $result; + } + + /** + * read header from file + * + * @param string $path + * @return array + */ + protected function getHeader($path) { + $realFile = $this->util->stripPartialFileExtension($path); + if ($this->storage->file_exists($realFile)) { + $path = $realFile; + } + + $firstBlock = $this->readFirstBlock($path); + $result = $this->parseRawHeader($firstBlock); + + // if the header doesn't contain a encryption module we check if it is a + // legacy file. If true, we add the default encryption module + if (!isset($result[Util::HEADER_ENCRYPTION_MODULE_KEY])) { + if (!empty($result)) { + $result[Util::HEADER_ENCRYPTION_MODULE_KEY] = 'OC_DEFAULT_MODULE'; + } else { + // if the header was empty we have to check first if it is a encrypted file at all + $info = $this->getCache()->get($path); + if (isset($info['encrypted']) && $info['encrypted'] === true) { + $result[Util::HEADER_ENCRYPTION_MODULE_KEY] = 'OC_DEFAULT_MODULE'; + } + } + } + + return $result; + } + + /** + * read encryption module needed to read/write the file located at $path + * + * @param string $path + * @return null|\OCP\Encryption\IEncryptionModule + * @throws ModuleDoesNotExistsException + * @throws \Exception + */ + protected function getEncryptionModule($path) { + $encryptionModule = null; + $header = $this->getHeader($path); + $encryptionModuleId = $this->util->getEncryptionModuleId($header); + if (!empty($encryptionModuleId)) { + try { + $encryptionModule = $this->encryptionManager->getEncryptionModule($encryptionModuleId); + } catch (ModuleDoesNotExistsException $e) { + $this->logger->critical('Encryption module defined in "' . $path . '" not loaded!'); + throw $e; + } + } + return $encryptionModule; + } + + /** + * @param string $path + * @param int $unencryptedSize + */ + public function updateUnencryptedSize($path, $unencryptedSize) { + $this->unencryptedSize[$path] = $unencryptedSize; + } + + /** + * copy keys to new location + * + * @param string $source path relative to data/ + * @param string $target path relative to data/ + * @return bool + */ + protected function copyKeys($source, $target) { + if (!$this->util->isExcluded($source)) { + return $this->keyStorage->copyKeys($source, $target); + } + + return false; + } + + /** + * check if path points to a files version + * + * @param $path + * @return bool + */ + protected function isVersion($path) { + $normalized = Filesystem::normalizePath($path); + return substr($normalized, 0, strlen('/files_versions/')) === '/files_versions/'; + } + +} diff --git a/lib/private/Files/Storage/Wrapper/Jail.php b/lib/private/Files/Storage/Wrapper/Jail.php new file mode 100644 index 00000000000..e8063f670c5 --- /dev/null +++ b/lib/private/Files/Storage/Wrapper/Jail.php @@ -0,0 +1,489 @@ + + * @author Robin Appelman + * + * @copyright Copyright (c) 2016, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Files\Storage\Wrapper; + +use OC\Files\Cache\Wrapper\CacheJail; +use OCP\Lock\ILockingProvider; + +/** + * Jail to a subdirectory of the wrapped storage + * + * This restricts access to a subfolder of the wrapped storage with the subfolder becoming the root folder new storage + */ +class Jail extends Wrapper { + /** + * @var string + */ + protected $rootPath; + + /** + * @param array $arguments ['storage' => $storage, 'mask' => $root] + * + * $storage: The storage that will be wrapper + * $root: The folder in the wrapped storage that will become the root folder of the wrapped storage + */ + public function __construct($arguments) { + parent::__construct($arguments); + $this->rootPath = $arguments['root']; + } + + public function getSourcePath($path) { + if ($path === '') { + return $this->rootPath; + } else { + return $this->rootPath . '/' . $path; + } + } + + public function getId() { + return 'link:' . parent::getId() . ':' . $this->rootPath; + } + + /** + * see http://php.net/manual/en/function.mkdir.php + * + * @param string $path + * @return bool + */ + public function mkdir($path) { + return $this->storage->mkdir($this->getSourcePath($path)); + } + + /** + * see http://php.net/manual/en/function.rmdir.php + * + * @param string $path + * @return bool + */ + public function rmdir($path) { + return $this->storage->rmdir($this->getSourcePath($path)); + } + + /** + * see http://php.net/manual/en/function.opendir.php + * + * @param string $path + * @return resource + */ + public function opendir($path) { + return $this->storage->opendir($this->getSourcePath($path)); + } + + /** + * see http://php.net/manual/en/function.is_dir.php + * + * @param string $path + * @return bool + */ + public function is_dir($path) { + return $this->storage->is_dir($this->getSourcePath($path)); + } + + /** + * see http://php.net/manual/en/function.is_file.php + * + * @param string $path + * @return bool + */ + public function is_file($path) { + return $this->storage->is_file($this->getSourcePath($path)); + } + + /** + * see http://php.net/manual/en/function.stat.php + * only the following keys are required in the result: size and mtime + * + * @param string $path + * @return array + */ + public function stat($path) { + return $this->storage->stat($this->getSourcePath($path)); + } + + /** + * see http://php.net/manual/en/function.filetype.php + * + * @param string $path + * @return bool + */ + public function filetype($path) { + return $this->storage->filetype($this->getSourcePath($path)); + } + + /** + * see http://php.net/manual/en/function.filesize.php + * The result for filesize when called on a folder is required to be 0 + * + * @param string $path + * @return int + */ + public function filesize($path) { + return $this->storage->filesize($this->getSourcePath($path)); + } + + /** + * check if a file can be created in $path + * + * @param string $path + * @return bool + */ + public function isCreatable($path) { + return $this->storage->isCreatable($this->getSourcePath($path)); + } + + /** + * check if a file can be read + * + * @param string $path + * @return bool + */ + public function isReadable($path) { + return $this->storage->isReadable($this->getSourcePath($path)); + } + + /** + * check if a file can be written to + * + * @param string $path + * @return bool + */ + public function isUpdatable($path) { + return $this->storage->isUpdatable($this->getSourcePath($path)); + } + + /** + * check if a file can be deleted + * + * @param string $path + * @return bool + */ + public function isDeletable($path) { + return $this->storage->isDeletable($this->getSourcePath($path)); + } + + /** + * check if a file can be shared + * + * @param string $path + * @return bool + */ + public function isSharable($path) { + return $this->storage->isSharable($this->getSourcePath($path)); + } + + /** + * get the full permissions of a path. + * Should return a combination of the PERMISSION_ constants defined in lib/public/constants.php + * + * @param string $path + * @return int + */ + public function getPermissions($path) { + return $this->storage->getPermissions($this->getSourcePath($path)); + } + + /** + * see http://php.net/manual/en/function.file_exists.php + * + * @param string $path + * @return bool + */ + public function file_exists($path) { + return $this->storage->file_exists($this->getSourcePath($path)); + } + + /** + * see http://php.net/manual/en/function.filemtime.php + * + * @param string $path + * @return int + */ + public function filemtime($path) { + return $this->storage->filemtime($this->getSourcePath($path)); + } + + /** + * see http://php.net/manual/en/function.file_get_contents.php + * + * @param string $path + * @return string + */ + public function file_get_contents($path) { + return $this->storage->file_get_contents($this->getSourcePath($path)); + } + + /** + * see http://php.net/manual/en/function.file_put_contents.php + * + * @param string $path + * @param string $data + * @return bool + */ + public function file_put_contents($path, $data) { + return $this->storage->file_put_contents($this->getSourcePath($path), $data); + } + + /** + * see http://php.net/manual/en/function.unlink.php + * + * @param string $path + * @return bool + */ + public function unlink($path) { + return $this->storage->unlink($this->getSourcePath($path)); + } + + /** + * see http://php.net/manual/en/function.rename.php + * + * @param string $path1 + * @param string $path2 + * @return bool + */ + public function rename($path1, $path2) { + return $this->storage->rename($this->getSourcePath($path1), $this->getSourcePath($path2)); + } + + /** + * see http://php.net/manual/en/function.copy.php + * + * @param string $path1 + * @param string $path2 + * @return bool + */ + public function copy($path1, $path2) { + return $this->storage->copy($this->getSourcePath($path1), $this->getSourcePath($path2)); + } + + /** + * see http://php.net/manual/en/function.fopen.php + * + * @param string $path + * @param string $mode + * @return resource + */ + public function fopen($path, $mode) { + return $this->storage->fopen($this->getSourcePath($path), $mode); + } + + /** + * get the mimetype for a file or folder + * The mimetype for a folder is required to be "httpd/unix-directory" + * + * @param string $path + * @return string + */ + public function getMimeType($path) { + return $this->storage->getMimeType($this->getSourcePath($path)); + } + + /** + * see http://php.net/manual/en/function.hash.php + * + * @param string $type + * @param string $path + * @param bool $raw + * @return string + */ + public function hash($type, $path, $raw = false) { + return $this->storage->hash($type, $this->getSourcePath($path), $raw); + } + + /** + * see http://php.net/manual/en/function.free_space.php + * + * @param string $path + * @return int + */ + public function free_space($path) { + return $this->storage->free_space($this->getSourcePath($path)); + } + + /** + * search for occurrences of $query in file names + * + * @param string $query + * @return array + */ + public function search($query) { + return $this->storage->search($query); + } + + /** + * see http://php.net/manual/en/function.touch.php + * If the backend does not support the operation, false should be returned + * + * @param string $path + * @param int $mtime + * @return bool + */ + public function touch($path, $mtime = null) { + return $this->storage->touch($this->getSourcePath($path), $mtime); + } + + /** + * get the path to a local version of the file. + * The local version of the file can be temporary and doesn't have to be persistent across requests + * + * @param string $path + * @return string + */ + public function getLocalFile($path) { + return $this->storage->getLocalFile($this->getSourcePath($path)); + } + + /** + * check if a file or folder has been updated since $time + * + * @param string $path + * @param int $time + * @return bool + * + * hasUpdated for folders should return at least true if a file inside the folder is add, removed or renamed. + * returning true for other changes in the folder is optional + */ + public function hasUpdated($path, $time) { + return $this->storage->hasUpdated($this->getSourcePath($path), $time); + } + + /** + * get a cache instance for the storage + * + * @param string $path + * @param \OC\Files\Storage\Storage (optional) the storage to pass to the cache + * @return \OC\Files\Cache\Cache + */ + public function getCache($path = '', $storage = null) { + if (!$storage) { + $storage = $this; + } + $sourceCache = $this->storage->getCache($this->getSourcePath($path), $storage); + return new CacheJail($sourceCache, $this->rootPath); + } + + /** + * get the user id of the owner of a file or folder + * + * @param string $path + * @return string + */ + public function getOwner($path) { + return $this->storage->getOwner($this->getSourcePath($path)); + } + + /** + * get a watcher instance for the cache + * + * @param string $path + * @param \OC\Files\Storage\Storage (optional) the storage to pass to the watcher + * @return \OC\Files\Cache\Watcher + */ + public function getWatcher($path = '', $storage = null) { + if (!$storage) { + $storage = $this; + } + return $this->storage->getWatcher($this->getSourcePath($path), $storage); + } + + /** + * get the ETag for a file or folder + * + * @param string $path + * @return string + */ + public function getETag($path) { + return $this->storage->getETag($this->getSourcePath($path)); + } + + /** + * @param string $path + * @return array + */ + public function getMetaData($path) { + return $this->storage->getMetaData($this->getSourcePath($path)); + } + + /** + * @param string $path + * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE + * @param \OCP\Lock\ILockingProvider $provider + * @throws \OCP\Lock\LockedException + */ + public function acquireLock($path, $type, ILockingProvider $provider) { + $this->storage->acquireLock($this->getSourcePath($path), $type, $provider); + } + + /** + * @param string $path + * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE + * @param \OCP\Lock\ILockingProvider $provider + */ + public function releaseLock($path, $type, ILockingProvider $provider) { + $this->storage->releaseLock($this->getSourcePath($path), $type, $provider); + } + + /** + * @param string $path + * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE + * @param \OCP\Lock\ILockingProvider $provider + */ + public function changeLock($path, $type, ILockingProvider $provider) { + $this->storage->changeLock($this->getSourcePath($path), $type, $provider); + } + + /** + * Resolve the path for the source of the share + * + * @param string $path + * @return array + */ + public function resolvePath($path) { + return [$this->storage, $this->getSourcePath($path)]; + } + + /** + * @param \OCP\Files\Storage $sourceStorage + * @param string $sourceInternalPath + * @param string $targetInternalPath + * @return bool + */ + public function copyFromStorage(\OCP\Files\Storage $sourceStorage, $sourceInternalPath, $targetInternalPath) { + if ($sourceStorage === $this) { + return $this->copy($sourceInternalPath, $targetInternalPath); + } + return $this->storage->copyFromStorage($sourceStorage, $sourceInternalPath, $this->getSourcePath($targetInternalPath)); + } + + /** + * @param \OCP\Files\Storage $sourceStorage + * @param string $sourceInternalPath + * @param string $targetInternalPath + * @return bool + */ + public function moveFromStorage(\OCP\Files\Storage $sourceStorage, $sourceInternalPath, $targetInternalPath) { + if ($sourceStorage === $this) { + return $this->rename($sourceInternalPath, $targetInternalPath); + } + return $this->storage->moveFromStorage($sourceStorage, $sourceInternalPath, $this->getSourcePath($targetInternalPath)); + } +} diff --git a/lib/private/Files/Storage/Wrapper/PermissionsMask.php b/lib/private/Files/Storage/Wrapper/PermissionsMask.php new file mode 100644 index 00000000000..01dd78d418c --- /dev/null +++ b/lib/private/Files/Storage/Wrapper/PermissionsMask.php @@ -0,0 +1,131 @@ + + * @author Morris Jobke + * @author Robin Appelman + * @author Robin McCorkell + * + * @copyright Copyright (c) 2016, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Files\Storage\Wrapper; + +use OC\Files\Cache\Wrapper\CachePermissionsMask; +use OCP\Constants; + +/** + * Mask the permissions of a storage + * + * This can be used to restrict update, create, delete and/or share permissions of a storage + * + * Note that the read permissions can't be masked + */ +class PermissionsMask extends Wrapper { + /** + * @var int the permissions bits we want to keep + */ + private $mask; + + /** + * @param array $arguments ['storage' => $storage, 'mask' => $mask] + * + * $storage: The storage the permissions mask should be applied on + * $mask: The permission bits that should be kept, a combination of the \OCP\Constant::PERMISSION_ constants + */ + public function __construct($arguments) { + parent::__construct($arguments); + $this->mask = $arguments['mask']; + } + + private function checkMask($permissions) { + return ($this->mask & $permissions) === $permissions; + } + + public function isUpdatable($path) { + return $this->checkMask(Constants::PERMISSION_UPDATE) and parent::isUpdatable($path); + } + + public function isCreatable($path) { + return $this->checkMask(Constants::PERMISSION_CREATE) and parent::isCreatable($path); + } + + public function isDeletable($path) { + return $this->checkMask(Constants::PERMISSION_DELETE) and parent::isDeletable($path); + } + + public function isSharable($path) { + return $this->checkMask(Constants::PERMISSION_SHARE) and parent::isSharable($path); + } + + public function getPermissions($path) { + return $this->storage->getPermissions($path) & $this->mask; + } + + public function rename($path1, $path2) { + return $this->checkMask(Constants::PERMISSION_UPDATE) and parent::rename($path1, $path2); + } + + public function copy($path1, $path2) { + return $this->checkMask(Constants::PERMISSION_CREATE) and parent::copy($path1, $path2); + } + + public function touch($path, $mtime = null) { + $permissions = $this->file_exists($path) ? Constants::PERMISSION_UPDATE : Constants::PERMISSION_CREATE; + return $this->checkMask($permissions) and parent::touch($path, $mtime); + } + + public function mkdir($path) { + return $this->checkMask(Constants::PERMISSION_CREATE) and parent::mkdir($path); + } + + public function rmdir($path) { + return $this->checkMask(Constants::PERMISSION_DELETE) and parent::rmdir($path); + } + + public function unlink($path) { + return $this->checkMask(Constants::PERMISSION_DELETE) and parent::unlink($path); + } + + public function file_put_contents($path, $data) { + $permissions = $this->file_exists($path) ? Constants::PERMISSION_UPDATE : Constants::PERMISSION_CREATE; + return $this->checkMask($permissions) and parent::file_put_contents($path, $data); + } + + public function fopen($path, $mode) { + if ($mode === 'r' or $mode === 'rb') { + return parent::fopen($path, $mode); + } else { + $permissions = $this->file_exists($path) ? Constants::PERMISSION_UPDATE : Constants::PERMISSION_CREATE; + return $this->checkMask($permissions) ? parent::fopen($path, $mode) : false; + } + } + + /** + * get a cache instance for the storage + * + * @param string $path + * @param \OC\Files\Storage\Storage (optional) the storage to pass to the cache + * @return \OC\Files\Cache\Cache + */ + public function getCache($path = '', $storage = null) { + if (!$storage) { + $storage = $this; + } + $sourceCache = parent::getCache($path, $storage); + return new CachePermissionsMask($sourceCache, $this->mask); + } +} diff --git a/lib/private/Files/Storage/Wrapper/Quota.php b/lib/private/Files/Storage/Wrapper/Quota.php new file mode 100644 index 00000000000..500677b092e --- /dev/null +++ b/lib/private/Files/Storage/Wrapper/Quota.php @@ -0,0 +1,200 @@ + + * @author Morris Jobke + * @author Robin Appelman + * @author Robin McCorkell + * @author Vincent Petry + * + * @copyright Copyright (c) 2016, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Files\Storage\Wrapper; + +use OCP\Files\Cache\ICacheEntry; + +class Quota extends Wrapper { + + /** + * @var int $quota + */ + protected $quota; + + /** + * @var string $sizeRoot + */ + protected $sizeRoot; + + /** + * @param array $parameters + */ + public function __construct($parameters) { + $this->storage = $parameters['storage']; + $this->quota = $parameters['quota']; + $this->sizeRoot = isset($parameters['root']) ? $parameters['root'] : ''; + } + + /** + * @return int quota value + */ + public function getQuota() { + return $this->quota; + } + + /** + * @param string $path + * @param \OC\Files\Storage\Storage $storage + */ + protected function getSize($path, $storage = null) { + if (is_null($storage)) { + $cache = $this->getCache(); + } else { + $cache = $storage->getCache(); + } + $data = $cache->get($path); + if ($data instanceof ICacheEntry and isset($data['size'])) { + return $data['size']; + } else { + return \OCP\Files\FileInfo::SPACE_NOT_COMPUTED; + } + } + + /** + * Get free space as limited by the quota + * + * @param string $path + * @return int + */ + public function free_space($path) { + if ($this->quota < 0) { + return $this->storage->free_space($path); + } else { + $used = $this->getSize($this->sizeRoot); + if ($used < 0) { + return \OCP\Files\FileInfo::SPACE_NOT_COMPUTED; + } else { + $free = $this->storage->free_space($path); + $quotaFree = max($this->quota - $used, 0); + // if free space is known + if ($free >= 0) { + $free = min($free, $quotaFree); + } else { + $free = $quotaFree; + } + return $free; + } + } + } + + /** + * see http://php.net/manual/en/function.file_put_contents.php + * + * @param string $path + * @param string $data + * @return bool + */ + public function file_put_contents($path, $data) { + $free = $this->free_space(''); + if ($free < 0 or strlen($data) < $free) { + return $this->storage->file_put_contents($path, $data); + } else { + return false; + } + } + + /** + * see http://php.net/manual/en/function.copy.php + * + * @param string $source + * @param string $target + * @return bool + */ + public function copy($source, $target) { + $free = $this->free_space(''); + if ($free < 0 or $this->getSize($source) < $free) { + return $this->storage->copy($source, $target); + } else { + return false; + } + } + + /** + * see http://php.net/manual/en/function.fopen.php + * + * @param string $path + * @param string $mode + * @return resource + */ + public function fopen($path, $mode) { + $source = $this->storage->fopen($path, $mode); + + // don't apply quota for part files + if (!$this->isPartFile($path)) { + $free = $this->free_space(''); + if ($source && $free >= 0 && $mode !== 'r' && $mode !== 'rb') { + // only apply quota for files, not metadata, trash or others + if (strpos(ltrim($path, '/'), 'files/') === 0) { + return \OC\Files\Stream\Quota::wrap($source, $free); + } + } + } + return $source; + } + + /** + * Checks whether the given path is a part file + * + * @param string $path Path that may identify a .part file + * @return string File path without .part extension + * @note this is needed for reusing keys + */ + private function isPartFile($path) { + $extension = pathinfo($path, PATHINFO_EXTENSION); + + return ($extension === 'part'); + } + + /** + * @param \OCP\Files\Storage $sourceStorage + * @param string $sourceInternalPath + * @param string $targetInternalPath + * @return bool + */ + public function copyFromStorage(\OCP\Files\Storage $sourceStorage, $sourceInternalPath, $targetInternalPath) { + $free = $this->free_space(''); + if ($free < 0 or $this->getSize($sourceInternalPath, $sourceStorage) < $free) { + return $this->storage->copyFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath); + } else { + return false; + } + } + + /** + * @param \OCP\Files\Storage $sourceStorage + * @param string $sourceInternalPath + * @param string $targetInternalPath + * @return bool + */ + public function moveFromStorage(\OCP\Files\Storage $sourceStorage, $sourceInternalPath, $targetInternalPath) { + $free = $this->free_space(''); + if ($free < 0 or $this->getSize($sourceInternalPath, $sourceStorage) < $free) { + return $this->storage->moveFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath); + } else { + return false; + } + } +} diff --git a/lib/private/Files/Storage/Wrapper/Wrapper.php b/lib/private/Files/Storage/Wrapper/Wrapper.php new file mode 100644 index 00000000000..21d7db1099b --- /dev/null +++ b/lib/private/Files/Storage/Wrapper/Wrapper.php @@ -0,0 +1,608 @@ + + * @author Robin Appelman + * @author Robin McCorkell + * @author Thomas Müller + * @author Vincent Petry + * + * @copyright Copyright (c) 2016, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Files\Storage\Wrapper; + +use OCP\Files\InvalidPathException; +use OCP\Files\Storage\ILockingStorage; +use OCP\Lock\ILockingProvider; + +class Wrapper implements \OC\Files\Storage\Storage, ILockingStorage { + /** + * @var \OC\Files\Storage\Storage $storage + */ + protected $storage; + + public $cache; + public $scanner; + public $watcher; + public $propagator; + public $updater; + + /** + * @param array $parameters + */ + public function __construct($parameters) { + $this->storage = $parameters['storage']; + } + + /** + * @return \OC\Files\Storage\Storage + */ + public function getWrapperStorage() { + return $this->storage; + } + + /** + * Get the identifier for the storage, + * the returned id should be the same for every storage object that is created with the same parameters + * and two storage objects with the same id should refer to two storages that display the same files. + * + * @return string + */ + public function getId() { + return $this->storage->getId(); + } + + /** + * see http://php.net/manual/en/function.mkdir.php + * + * @param string $path + * @return bool + */ + public function mkdir($path) { + return $this->storage->mkdir($path); + } + + /** + * see http://php.net/manual/en/function.rmdir.php + * + * @param string $path + * @return bool + */ + public function rmdir($path) { + return $this->storage->rmdir($path); + } + + /** + * see http://php.net/manual/en/function.opendir.php + * + * @param string $path + * @return resource + */ + public function opendir($path) { + return $this->storage->opendir($path); + } + + /** + * see http://php.net/manual/en/function.is_dir.php + * + * @param string $path + * @return bool + */ + public function is_dir($path) { + return $this->storage->is_dir($path); + } + + /** + * see http://php.net/manual/en/function.is_file.php + * + * @param string $path + * @return bool + */ + public function is_file($path) { + return $this->storage->is_file($path); + } + + /** + * see http://php.net/manual/en/function.stat.php + * only the following keys are required in the result: size and mtime + * + * @param string $path + * @return array + */ + public function stat($path) { + return $this->storage->stat($path); + } + + /** + * see http://php.net/manual/en/function.filetype.php + * + * @param string $path + * @return bool + */ + public function filetype($path) { + return $this->storage->filetype($path); + } + + /** + * see http://php.net/manual/en/function.filesize.php + * The result for filesize when called on a folder is required to be 0 + * + * @param string $path + * @return int + */ + public function filesize($path) { + return $this->storage->filesize($path); + } + + /** + * check if a file can be created in $path + * + * @param string $path + * @return bool + */ + public function isCreatable($path) { + return $this->storage->isCreatable($path); + } + + /** + * check if a file can be read + * + * @param string $path + * @return bool + */ + public function isReadable($path) { + return $this->storage->isReadable($path); + } + + /** + * check if a file can be written to + * + * @param string $path + * @return bool + */ + public function isUpdatable($path) { + return $this->storage->isUpdatable($path); + } + + /** + * check if a file can be deleted + * + * @param string $path + * @return bool + */ + public function isDeletable($path) { + return $this->storage->isDeletable($path); + } + + /** + * check if a file can be shared + * + * @param string $path + * @return bool + */ + public function isSharable($path) { + return $this->storage->isSharable($path); + } + + /** + * get the full permissions of a path. + * Should return a combination of the PERMISSION_ constants defined in lib/public/constants.php + * + * @param string $path + * @return int + */ + public function getPermissions($path) { + return $this->storage->getPermissions($path); + } + + /** + * see http://php.net/manual/en/function.file_exists.php + * + * @param string $path + * @return bool + */ + public function file_exists($path) { + return $this->storage->file_exists($path); + } + + /** + * see http://php.net/manual/en/function.filemtime.php + * + * @param string $path + * @return int + */ + public function filemtime($path) { + return $this->storage->filemtime($path); + } + + /** + * see http://php.net/manual/en/function.file_get_contents.php + * + * @param string $path + * @return string + */ + public function file_get_contents($path) { + return $this->storage->file_get_contents($path); + } + + /** + * see http://php.net/manual/en/function.file_put_contents.php + * + * @param string $path + * @param string $data + * @return bool + */ + public function file_put_contents($path, $data) { + return $this->storage->file_put_contents($path, $data); + } + + /** + * see http://php.net/manual/en/function.unlink.php + * + * @param string $path + * @return bool + */ + public function unlink($path) { + return $this->storage->unlink($path); + } + + /** + * see http://php.net/manual/en/function.rename.php + * + * @param string $path1 + * @param string $path2 + * @return bool + */ + public function rename($path1, $path2) { + return $this->storage->rename($path1, $path2); + } + + /** + * see http://php.net/manual/en/function.copy.php + * + * @param string $path1 + * @param string $path2 + * @return bool + */ + public function copy($path1, $path2) { + return $this->storage->copy($path1, $path2); + } + + /** + * see http://php.net/manual/en/function.fopen.php + * + * @param string $path + * @param string $mode + * @return resource + */ + public function fopen($path, $mode) { + return $this->storage->fopen($path, $mode); + } + + /** + * get the mimetype for a file or folder + * The mimetype for a folder is required to be "httpd/unix-directory" + * + * @param string $path + * @return string + */ + public function getMimeType($path) { + return $this->storage->getMimeType($path); + } + + /** + * see http://php.net/manual/en/function.hash.php + * + * @param string $type + * @param string $path + * @param bool $raw + * @return string + */ + public function hash($type, $path, $raw = false) { + return $this->storage->hash($type, $path, $raw); + } + + /** + * see http://php.net/manual/en/function.free_space.php + * + * @param string $path + * @return int + */ + public function free_space($path) { + return $this->storage->free_space($path); + } + + /** + * search for occurrences of $query in file names + * + * @param string $query + * @return array + */ + public function search($query) { + return $this->storage->search($query); + } + + /** + * see http://php.net/manual/en/function.touch.php + * If the backend does not support the operation, false should be returned + * + * @param string $path + * @param int $mtime + * @return bool + */ + public function touch($path, $mtime = null) { + return $this->storage->touch($path, $mtime); + } + + /** + * get the path to a local version of the file. + * The local version of the file can be temporary and doesn't have to be persistent across requests + * + * @param string $path + * @return string + */ + public function getLocalFile($path) { + return $this->storage->getLocalFile($path); + } + + /** + * check if a file or folder has been updated since $time + * + * @param string $path + * @param int $time + * @return bool + * + * hasUpdated for folders should return at least true if a file inside the folder is add, removed or renamed. + * returning true for other changes in the folder is optional + */ + public function hasUpdated($path, $time) { + return $this->storage->hasUpdated($path, $time); + } + + /** + * get a cache instance for the storage + * + * @param string $path + * @param \OC\Files\Storage\Storage (optional) the storage to pass to the cache + * @return \OC\Files\Cache\Cache + */ + public function getCache($path = '', $storage = null) { + if (!$storage) { + $storage = $this; + } + return $this->storage->getCache($path, $storage); + } + + /** + * get a scanner instance for the storage + * + * @param string $path + * @param \OC\Files\Storage\Storage (optional) the storage to pass to the scanner + * @return \OC\Files\Cache\Scanner + */ + public function getScanner($path = '', $storage = null) { + if (!$storage) { + $storage = $this; + } + return $this->storage->getScanner($path, $storage); + } + + + /** + * get the user id of the owner of a file or folder + * + * @param string $path + * @return string + */ + public function getOwner($path) { + return $this->storage->getOwner($path); + } + + /** + * get a watcher instance for the cache + * + * @param string $path + * @param \OC\Files\Storage\Storage (optional) the storage to pass to the watcher + * @return \OC\Files\Cache\Watcher + */ + public function getWatcher($path = '', $storage = null) { + if (!$storage) { + $storage = $this; + } + return $this->storage->getWatcher($path, $storage); + } + + public function getPropagator($storage = null) { + if (!$storage) { + $storage = $this; + } + return $this->storage->getPropagator($storage); + } + + public function getUpdater($storage = null) { + if (!$storage) { + $storage = $this; + } + return $this->storage->getUpdater($storage); + } + + /** + * @return \OC\Files\Cache\Storage + */ + public function getStorageCache() { + return $this->storage->getStorageCache(); + } + + /** + * get the ETag for a file or folder + * + * @param string $path + * @return string + */ + public function getETag($path) { + return $this->storage->getETag($path); + } + + /** + * Returns true + * + * @return true + */ + public function test() { + return $this->storage->test(); + } + + /** + * Returns the wrapped storage's value for isLocal() + * + * @return bool wrapped storage's isLocal() value + */ + public function isLocal() { + return $this->storage->isLocal(); + } + + /** + * Check if the storage is an instance of $class or is a wrapper for a storage that is an instance of $class + * + * @param string $class + * @return bool + */ + public function instanceOfStorage($class) { + return is_a($this, $class) or $this->storage->instanceOfStorage($class); + } + + /** + * Pass any methods custom to specific storage implementations to the wrapped storage + * + * @param string $method + * @param array $args + * @return mixed + */ + public function __call($method, $args) { + return call_user_func_array(array($this->storage, $method), $args); + } + + /** + * A custom storage implementation can return an url for direct download of a give file. + * + * For now the returned array can hold the parameter url - in future more attributes might follow. + * + * @param string $path + * @return array + */ + public function getDirectDownload($path) { + return $this->storage->getDirectDownload($path); + } + + /** + * Get availability of the storage + * + * @return array [ available, last_checked ] + */ + public function getAvailability() { + return $this->storage->getAvailability(); + } + + /** + * Set availability of the storage + * + * @param bool $isAvailable + */ + public function setAvailability($isAvailable) { + $this->storage->setAvailability($isAvailable); + } + + /** + * @param string $path the path of the target folder + * @param string $fileName the name of the file itself + * @return void + * @throws InvalidPathException + */ + public function verifyPath($path, $fileName) { + $this->storage->verifyPath($path, $fileName); + } + + /** + * @param \OCP\Files\Storage $sourceStorage + * @param string $sourceInternalPath + * @param string $targetInternalPath + * @return bool + */ + public function copyFromStorage(\OCP\Files\Storage $sourceStorage, $sourceInternalPath, $targetInternalPath) { + if ($sourceStorage === $this) { + return $this->copy($sourceInternalPath, $targetInternalPath); + } + + return $this->storage->copyFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath); + } + + /** + * @param \OCP\Files\Storage $sourceStorage + * @param string $sourceInternalPath + * @param string $targetInternalPath + * @return bool + */ + public function moveFromStorage(\OCP\Files\Storage $sourceStorage, $sourceInternalPath, $targetInternalPath) { + if ($sourceStorage === $this) { + return $this->rename($sourceInternalPath, $targetInternalPath); + } + + return $this->storage->moveFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath); + } + + /** + * @param string $path + * @return array + */ + public function getMetaData($path) { + return $this->storage->getMetaData($path); + } + + /** + * @param string $path + * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE + * @param \OCP\Lock\ILockingProvider $provider + * @throws \OCP\Lock\LockedException + */ + public function acquireLock($path, $type, ILockingProvider $provider) { + if ($this->storage->instanceOfStorage('\OCP\Files\Storage\ILockingStorage')) { + $this->storage->acquireLock($path, $type, $provider); + } + } + + /** + * @param string $path + * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE + * @param \OCP\Lock\ILockingProvider $provider + */ + public function releaseLock($path, $type, ILockingProvider $provider) { + if ($this->storage->instanceOfStorage('\OCP\Files\Storage\ILockingStorage')) { + $this->storage->releaseLock($path, $type, $provider); + } + } + + /** + * @param string $path + * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE + * @param \OCP\Lock\ILockingProvider $provider + */ + public function changeLock($path, $type, ILockingProvider $provider) { + if ($this->storage->instanceOfStorage('\OCP\Files\Storage\ILockingStorage')) { + $this->storage->changeLock($path, $type, $provider); + } + } +} diff --git a/lib/private/Files/Stream/Close.php b/lib/private/Files/Stream/Close.php new file mode 100644 index 00000000000..1c9b30705dd --- /dev/null +++ b/lib/private/Files/Stream/Close.php @@ -0,0 +1,118 @@ + + * @author Morris Jobke + * @author Robin Appelman + * + * @copyright Copyright (c) 2016, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Files\Stream; + +/** + * stream wrapper that provides a callback on stream close + */ +class Close { + private static $callBacks = array(); + private $path = ''; + private $source; + private static $open = array(); + + public function stream_open($path, $mode, $options, &$opened_path) { + $path = substr($path, strlen('close://')); + $this->path = $path; + $this->source = fopen($path, $mode); + if (is_resource($this->source)) { + $this->meta = stream_get_meta_data($this->source); + } + self::$open[] = $path; + return is_resource($this->source); + } + + public function stream_seek($offset, $whence = SEEK_SET) { + return fseek($this->source, $offset, $whence) === 0; + } + + public function stream_tell() { + return ftell($this->source); + } + + public function stream_read($count) { + return fread($this->source, $count); + } + + public function stream_write($data) { + return fwrite($this->source, $data); + } + + public function stream_set_option($option, $arg1, $arg2) { + switch ($option) { + case STREAM_OPTION_BLOCKING: + stream_set_blocking($this->source, $arg1); + break; + case STREAM_OPTION_READ_TIMEOUT: + stream_set_timeout($this->source, $arg1, $arg2); + break; + case STREAM_OPTION_WRITE_BUFFER: + stream_set_write_buffer($this->source, $arg1, $arg2); + } + } + + public function stream_stat() { + return fstat($this->source); + } + + public function stream_lock($mode) { + flock($this->source, $mode); + } + + public function stream_flush() { + return fflush($this->source); + } + + public function stream_eof() { + return feof($this->source); + } + + public function url_stat($path) { + $path = substr($path, strlen('close://')); + if (file_exists($path)) { + return stat($path); + } else { + return false; + } + } + + public function stream_close() { + fclose($this->source); + if (isset(self::$callBacks[$this->path])) { + call_user_func(self::$callBacks[$this->path], $this->path); + } + } + + public function unlink($path) { + $path = substr($path, strlen('close://')); + return unlink($path); + } + + /** + * @param string $path + */ + public static function registerCallback($path, $callback) { + self::$callBacks[$path] = $callback; + } +} diff --git a/lib/private/Files/Stream/Dir.php b/lib/private/Files/Stream/Dir.php new file mode 100644 index 00000000000..7489ee683a2 --- /dev/null +++ b/lib/private/Files/Stream/Dir.php @@ -0,0 +1,66 @@ + + * @author Morris Jobke + * @author Robin Appelman + * + * @copyright Copyright (c) 2016, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Files\Stream; + +class Dir { + private static $dirs = array(); + private $name; + private $index; + + public function dir_opendir($path, $options) { + $this->name = substr($path, strlen('fakedir://')); + $this->index = 0; + if (!isset(self::$dirs[$this->name])) { + self::$dirs[$this->name] = array(); + } + return true; + } + + public function dir_readdir() { + if ($this->index >= count(self::$dirs[$this->name])) { + return false; + } + $filename = self::$dirs[$this->name][$this->index]; + $this->index++; + return $filename; + } + + public function dir_closedir() { + $this->name = ''; + return true; + } + + public function dir_rewinddir() { + $this->index = 0; + return true; + } + + /** + * @param string $path + * @param string[] $content + */ + public static function register($path, $content) { + self::$dirs[$path] = $content; + } +} diff --git a/lib/private/Files/Stream/Encryption.php b/lib/private/Files/Stream/Encryption.php new file mode 100644 index 00000000000..772c9769bf7 --- /dev/null +++ b/lib/private/Files/Stream/Encryption.php @@ -0,0 +1,500 @@ + + * @author jknockaert + * @author Lukas Reschke + * @author Roeland Jago Douma + * @author Thomas Müller + * @author Vincent Petry + * + * @copyright Copyright (c) 2016, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Files\Stream; + +use Icewind\Streams\Wrapper; +use OC\Encryption\Exceptions\EncryptionHeaderKeyExistsException; + +class Encryption extends Wrapper { + + /** @var \OC\Encryption\Util */ + protected $util; + + /** @var \OC\Encryption\File */ + protected $file; + + /** @var \OCP\Encryption\IEncryptionModule */ + protected $encryptionModule; + + /** @var \OC\Files\Storage\Storage */ + protected $storage; + + /** @var \OC\Files\Storage\Wrapper\Encryption */ + protected $encryptionStorage; + + /** @var string */ + protected $internalPath; + + /** @var string */ + protected $cache; + + /** @var integer */ + protected $size; + + /** @var integer */ + protected $position; + + /** @var integer */ + protected $unencryptedSize; + + /** @var integer */ + protected $headerSize; + + /** @var integer */ + protected $unencryptedBlockSize; + + /** @var array */ + protected $header; + + /** @var string */ + protected $fullPath; + + /** @var bool */ + protected $signed; + + /** + * header data returned by the encryption module, will be written to the file + * in case of a write operation + * + * @var array + */ + protected $newHeader; + + /** + * user who perform the read/write operation null for public access + * + * @var string + */ + protected $uid; + + /** @var bool */ + protected $readOnly; + + /** @var bool */ + protected $writeFlag; + + /** @var array */ + protected $expectedContextProperties; + + public function __construct() { + $this->expectedContextProperties = array( + 'source', + 'storage', + 'internalPath', + 'fullPath', + 'encryptionModule', + 'header', + 'uid', + 'file', + 'util', + 'size', + 'unencryptedSize', + 'encryptionStorage', + 'headerSize', + 'signed' + ); + } + + + /** + * Wraps a stream with the provided callbacks + * + * @param resource $source + * @param string $internalPath relative to mount point + * @param string $fullPath relative to data/ + * @param array $header + * @param string $uid + * @param \OCP\Encryption\IEncryptionModule $encryptionModule + * @param \OC\Files\Storage\Storage $storage + * @param \OC\Files\Storage\Wrapper\Encryption $encStorage + * @param \OC\Encryption\Util $util + * @param \OC\Encryption\File $file + * @param string $mode + * @param int $size + * @param int $unencryptedSize + * @param int $headerSize + * @param bool $signed + * @param string $wrapper stream wrapper class + * @return resource + * + * @throws \BadMethodCallException + */ + public static function wrap($source, $internalPath, $fullPath, array $header, + $uid, + \OCP\Encryption\IEncryptionModule $encryptionModule, + \OC\Files\Storage\Storage $storage, + \OC\Files\Storage\Wrapper\Encryption $encStorage, + \OC\Encryption\Util $util, + \OC\Encryption\File $file, + $mode, + $size, + $unencryptedSize, + $headerSize, + $signed, + $wrapper = 'OC\Files\Stream\Encryption') { + + $context = stream_context_create(array( + 'ocencryption' => array( + 'source' => $source, + 'storage' => $storage, + 'internalPath' => $internalPath, + 'fullPath' => $fullPath, + 'encryptionModule' => $encryptionModule, + 'header' => $header, + 'uid' => $uid, + 'util' => $util, + 'file' => $file, + 'size' => $size, + 'unencryptedSize' => $unencryptedSize, + 'encryptionStorage' => $encStorage, + 'headerSize' => $headerSize, + 'signed' => $signed + ) + )); + + return self::wrapSource($source, $context, 'ocencryption', $wrapper, $mode); + } + + /** + * add stream wrapper + * + * @param resource $source + * @param string $mode + * @param resource $context + * @param string $protocol + * @param string $class + * @return resource + * @throws \BadMethodCallException + */ + protected static function wrapSource($source, $context, $protocol, $class, $mode = 'r+') { + try { + stream_wrapper_register($protocol, $class); + if (@rewinddir($source) === false) { + $wrapped = fopen($protocol . '://', $mode, false, $context); + } else { + $wrapped = opendir($protocol . '://', $context); + } + } catch (\BadMethodCallException $e) { + stream_wrapper_unregister($protocol); + throw $e; + } + stream_wrapper_unregister($protocol); + return $wrapped; + } + + /** + * Load the source from the stream context and return the context options + * + * @param string $name + * @return array + * @throws \BadMethodCallException + */ + protected function loadContext($name) { + $context = parent::loadContext($name); + + foreach ($this->expectedContextProperties as $property) { + if (array_key_exists($property, $context)) { + $this->{$property} = $context[$property]; + } else { + throw new \BadMethodCallException('Invalid context, "' . $property . '" options not set'); + } + } + return $context; + + } + + public function stream_open($path, $mode, $options, &$opened_path) { + $this->loadContext('ocencryption'); + + $this->position = 0; + $this->cache = ''; + $this->writeFlag = false; + $this->unencryptedBlockSize = $this->encryptionModule->getUnencryptedBlockSize($this->signed); + + if ( + $mode === 'w' + || $mode === 'w+' + || $mode === 'wb' + || $mode === 'wb+' + || $mode === 'r+' + || $mode === 'rb+' + ) { + $this->readOnly = false; + } else { + $this->readOnly = true; + } + + $sharePath = $this->fullPath; + if (!$this->storage->file_exists($this->internalPath)) { + $sharePath = dirname($sharePath); + } + + $accessList = $this->file->getAccessList($sharePath); + $this->newHeader = $this->encryptionModule->begin($this->fullPath, $this->uid, $mode, $this->header, $accessList); + + if ( + $mode === 'w' + || $mode === 'w+' + || $mode === 'wb' + || $mode === 'wb+' + ) { + // We're writing a new file so start write counter with 0 bytes + $this->unencryptedSize = 0; + $this->writeHeader(); + $this->headerSize = $this->util->getHeaderSize(); + $this->size = $this->headerSize; + } else { + $this->skipHeader(); + } + + return true; + + } + + public function stream_eof() { + return $this->position >= $this->unencryptedSize; + } + + public function stream_read($count) { + + $result = ''; + + $count = min($count, $this->unencryptedSize - $this->position); + while ($count > 0) { + $remainingLength = $count; + // update the cache of the current block + $this->readCache(); + // determine the relative position in the current block + $blockPosition = ($this->position % $this->unencryptedBlockSize); + // if entire read inside current block then only position needs to be updated + if ($remainingLength < ($this->unencryptedBlockSize - $blockPosition)) { + $result .= substr($this->cache, $blockPosition, $remainingLength); + $this->position += $remainingLength; + $count = 0; + // otherwise remainder of current block is fetched, the block is flushed and the position updated + } else { + $result .= substr($this->cache, $blockPosition); + $this->flush(); + $this->position += ($this->unencryptedBlockSize - $blockPosition); + $count -= ($this->unencryptedBlockSize - $blockPosition); + } + } + return $result; + + } + + public function stream_write($data) { + + $length = 0; + // loop over $data to fit it in 6126 sized unencrypted blocks + while (isset($data[0])) { + $remainingLength = strlen($data); + + // set the cache to the current 6126 block + $this->readCache(); + + // for seekable streams the pointer is moved back to the beginning of the encrypted block + // flush will start writing there when the position moves to another block + $positionInFile = (int)floor($this->position / $this->unencryptedBlockSize) * + $this->util->getBlockSize() + $this->headerSize; + $resultFseek = $this->parentStreamSeek($positionInFile); + + // only allow writes on seekable streams, or at the end of the encrypted stream + if (!($this->readOnly) && ($resultFseek || $positionInFile === $this->size)) { + + // switch the writeFlag so flush() will write the block + $this->writeFlag = true; + + // determine the relative position in the current block + $blockPosition = ($this->position % $this->unencryptedBlockSize); + // check if $data fits in current block + // if so, overwrite existing data (if any) + // update position and liberate $data + if ($remainingLength < ($this->unencryptedBlockSize - $blockPosition)) { + $this->cache = substr($this->cache, 0, $blockPosition) + . $data . substr($this->cache, $blockPosition + $remainingLength); + $this->position += $remainingLength; + $length += $remainingLength; + $data = ''; + // if $data doesn't fit the current block, the fill the current block and reiterate + // after the block is filled, it is flushed and $data is updatedxxx + } else { + $this->cache = substr($this->cache, 0, $blockPosition) . + substr($data, 0, $this->unencryptedBlockSize - $blockPosition); + $this->flush(); + $this->position += ($this->unencryptedBlockSize - $blockPosition); + $length += ($this->unencryptedBlockSize - $blockPosition); + $data = substr($data, $this->unencryptedBlockSize - $blockPosition); + } + } else { + $data = ''; + } + $this->unencryptedSize = max($this->unencryptedSize, $this->position); + } + return $length; + } + + public function stream_tell() { + return $this->position; + } + + public function stream_seek($offset, $whence = SEEK_SET) { + + $return = false; + + switch ($whence) { + case SEEK_SET: + $newPosition = $offset; + break; + case SEEK_CUR: + $newPosition = $this->position + $offset; + break; + case SEEK_END: + $newPosition = $this->unencryptedSize + $offset; + break; + default: + return $return; + } + + if ($newPosition > $this->unencryptedSize || $newPosition < 0) { + return $return; + } + + $newFilePosition = floor($newPosition / $this->unencryptedBlockSize) + * $this->util->getBlockSize() + $this->headerSize; + + $oldFilePosition = parent::stream_tell(); + if ($this->parentStreamSeek($newFilePosition)) { + $this->parentStreamSeek($oldFilePosition); + $this->flush(); + $this->parentStreamSeek($newFilePosition); + $this->position = $newPosition; + $return = true; + } + return $return; + + } + + public function stream_close() { + $this->flush('end'); + $position = (int)floor($this->position/$this->unencryptedBlockSize); + $remainingData = $this->encryptionModule->end($this->fullPath, $position . 'end'); + if ($this->readOnly === false) { + if(!empty($remainingData)) { + parent::stream_write($remainingData); + } + $this->encryptionStorage->updateUnencryptedSize($this->fullPath, $this->unencryptedSize); + } + return parent::stream_close(); + } + + /** + * write block to file + * @param string $positionPrefix + */ + protected function flush($positionPrefix = '') { + // write to disk only when writeFlag was set to 1 + if ($this->writeFlag) { + // Disable the file proxies so that encryption is not + // automatically attempted when the file is written to disk - + // we are handling that separately here and we don't want to + // get into an infinite loop + $position = (int)floor($this->position/$this->unencryptedBlockSize); + $encrypted = $this->encryptionModule->encrypt($this->cache, $position . $positionPrefix); + $bytesWritten = parent::stream_write($encrypted); + $this->writeFlag = false; + // Check whether the write concerns the last block + // If so then update the encrypted filesize + // Note that the unencrypted pointer and filesize are NOT yet updated when flush() is called + // We recalculate the encrypted filesize as we do not know the context of calling flush() + $completeBlocksInFile=(int)floor($this->unencryptedSize/$this->unencryptedBlockSize); + if ($completeBlocksInFile === (int)floor($this->position/$this->unencryptedBlockSize)) { + $this->size = $this->util->getBlockSize() * $completeBlocksInFile; + $this->size += $bytesWritten; + $this->size += $this->headerSize; + } + } + // always empty the cache (otherwise readCache() will not fill it with the new block) + $this->cache = ''; + } + + /** + * read block to file + */ + protected function readCache() { + // cache should always be empty string when this function is called + // don't try to fill the cache when trying to write at the end of the unencrypted file when it coincides with new block + if ($this->cache === '' && !($this->position === $this->unencryptedSize && ($this->position % $this->unencryptedBlockSize) === 0)) { + // Get the data from the file handle + $data = parent::stream_read($this->util->getBlockSize()); + $position = (int)floor($this->position/$this->unencryptedBlockSize); + $numberOfChunks = (int)($this->unencryptedSize / $this->unencryptedBlockSize); + if($numberOfChunks === $position) { + $position .= 'end'; + } + $this->cache = $this->encryptionModule->decrypt($data, $position); + } + } + + /** + * write header at beginning of encrypted file + * + * @return integer + * @throws EncryptionHeaderKeyExistsException if header key is already in use + */ + protected function writeHeader() { + $header = $this->util->createHeader($this->newHeader, $this->encryptionModule); + return parent::stream_write($header); + } + + /** + * read first block to skip the header + */ + protected function skipHeader() { + parent::stream_read($this->headerSize); + } + + /** + * call stream_seek() from parent class + * + * @param integer $position + * @return bool + */ + protected function parentStreamSeek($position) { + return parent::stream_seek($position); + } + + /** + * @param string $path + * @param array $options + * @return bool + */ + public function dir_opendir($path, $options) { + return false; + } + +} diff --git a/lib/private/Files/Stream/OC.php b/lib/private/Files/Stream/OC.php new file mode 100644 index 00000000000..8439770e8fa --- /dev/null +++ b/lib/private/Files/Stream/OC.php @@ -0,0 +1,153 @@ + + * @author Morris Jobke + * @author Robin Appelman + * @author Robin McCorkell + * + * @copyright Copyright (c) 2016, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Files\Stream; + +/** + * a stream wrappers for ownCloud's virtual filesystem + */ +class OC { + /** + * @var \OC\Files\View + */ + static private $rootView; + + private $path; + + /** + * @var resource + */ + private $dirSource; + + /** + * @var resource + */ + private $fileSource; + private $meta; + + private function setup(){ + if (!self::$rootView) { + self::$rootView = new \OC\Files\View(''); + } + } + + public function stream_open($path, $mode, $options, &$opened_path) { + $this->setup(); + $path = substr($path, strlen('oc://')); + $this->path = $path; + $this->fileSource = self::$rootView->fopen($path, $mode); + if (is_resource($this->fileSource)) { + $this->meta = stream_get_meta_data($this->fileSource); + } + return is_resource($this->fileSource); + } + + public function stream_seek($offset, $whence = SEEK_SET) { + return fseek($this->fileSource, $offset, $whence) === 0; + } + + public function stream_tell() { + return ftell($this->fileSource); + } + + public function stream_read($count) { + return fread($this->fileSource, $count); + } + + public function stream_write($data) { + return fwrite($this->fileSource, $data); + } + + public function stream_set_option($option, $arg1, $arg2) { + switch ($option) { + case STREAM_OPTION_BLOCKING: + stream_set_blocking($this->fileSource, $arg1); + break; + case STREAM_OPTION_READ_TIMEOUT: + stream_set_timeout($this->fileSource, $arg1, $arg2); + break; + case STREAM_OPTION_WRITE_BUFFER: + stream_set_write_buffer($this->fileSource, $arg1, $arg2); + } + } + + public function stream_stat() { + return fstat($this->fileSource); + } + + public function stream_lock($mode) { + flock($this->fileSource, $mode); + } + + public function stream_flush() { + return fflush($this->fileSource); + } + + public function stream_eof() { + return feof($this->fileSource); + } + + public function url_stat($path) { + $this->setup(); + $path = substr($path, strlen('oc://')); + if (self::$rootView->file_exists($path)) { + return self::$rootView->stat($path); + } else { + return false; + } + } + + public function stream_close() { + fclose($this->fileSource); + } + + public function unlink($path) { + $this->setup(); + $path = substr($path, strlen('oc://')); + return self::$rootView->unlink($path); + } + + public function dir_opendir($path, $options) { + $this->setup(); + $path = substr($path, strlen('oc://')); + $this->path = $path; + $this->dirSource = self::$rootView->opendir($path); + if (is_resource($this->dirSource)) { + $this->meta = stream_get_meta_data($this->dirSource); + } + return is_resource($this->dirSource); + } + + public function dir_readdir() { + return readdir($this->dirSource); + } + + public function dir_closedir() { + closedir($this->dirSource); + } + + public function dir_rewinddir() { + rewinddir($this->dirSource); + } +} diff --git a/lib/private/Files/Stream/Quota.php b/lib/private/Files/Stream/Quota.php new file mode 100644 index 00000000000..8d27575c568 --- /dev/null +++ b/lib/private/Files/Stream/Quota.php @@ -0,0 +1,156 @@ + + * @author Morris Jobke + * @author Robin Appelman + * @author Vincent Petry + * + * @copyright Copyright (c) 2016, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Files\Stream; + +/** + * stream wrapper limits the amount of data that can be written to a stream + * + * usage: void \OC\Files\Stream\Quota::register($id, $stream, $limit) + * or: resource \OC\Files\Stream\Quota::wrap($stream, $limit) + */ +class Quota { + private static $streams = array(); + + /** + * @var resource $source + */ + private $source; + + /** + * @var int $limit + */ + private $limit; + + /** + * @param string $id + * @param resource $stream + * @param int $limit + */ + public static function register($id, $stream, $limit) { + self::$streams[$id] = array($stream, $limit); + } + + /** + * remove all registered streams + */ + public static function clear() { + self::$streams = array(); + } + + /** + * @param resource $stream + * @param int $limit + * @return resource + */ + static public function wrap($stream, $limit) { + $id = uniqid(); + self::register($id, $stream, $limit); + $meta = stream_get_meta_data($stream); + return fopen('quota://' . $id, $meta['mode']); + } + + public function stream_open($path, $mode, $options, &$opened_path) { + $id = substr($path, strlen('quota://')); + if (isset(self::$streams[$id])) { + list($this->source, $this->limit) = self::$streams[$id]; + return true; + } else { + return false; + } + } + + public function stream_seek($offset, $whence = SEEK_SET) { + if ($whence === SEEK_END){ + // go to the end to find out last position's offset + $oldOffset = $this->stream_tell(); + if (fseek($this->source, 0, $whence) !== 0){ + return false; + } + $whence = SEEK_SET; + $offset = $this->stream_tell() + $offset; + $this->limit += $oldOffset - $offset; + } + else if ($whence === SEEK_SET) { + $this->limit += $this->stream_tell() - $offset; + } else { + $this->limit -= $offset; + } + // this wrapper needs to return "true" for success. + // the fseek call itself returns 0 on succeess + return fseek($this->source, $offset, $whence) === 0; + } + + public function stream_tell() { + return ftell($this->source); + } + + public function stream_read($count) { + $this->limit -= $count; + return fread($this->source, $count); + } + + public function stream_write($data) { + $size = strlen($data); + if ($size > $this->limit) { + $data = substr($data, 0, $this->limit); + $size = $this->limit; + } + $this->limit -= $size; + return fwrite($this->source, $data); + } + + public function stream_set_option($option, $arg1, $arg2) { + switch ($option) { + case STREAM_OPTION_BLOCKING: + stream_set_blocking($this->source, $arg1); + break; + case STREAM_OPTION_READ_TIMEOUT: + stream_set_timeout($this->source, $arg1, $arg2); + break; + case STREAM_OPTION_WRITE_BUFFER: + stream_set_write_buffer($this->source, $arg1, $arg2); + } + } + + public function stream_stat() { + return fstat($this->source); + } + + public function stream_lock($mode) { + return flock($this->source, $mode); + } + + public function stream_flush() { + return fflush($this->source); + } + + public function stream_eof() { + return feof($this->source); + } + + public function stream_close() { + fclose($this->source); + } +} diff --git a/lib/private/Files/Stream/StaticStream.php b/lib/private/Files/Stream/StaticStream.php new file mode 100644 index 00000000000..7aacf7a9fe4 --- /dev/null +++ b/lib/private/Files/Stream/StaticStream.php @@ -0,0 +1,170 @@ + + * @author Robin Appelman + * + * @copyright Copyright (c) 2016, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Files\Stream; + +class StaticStream { + const MODE_FILE = 0100000; + + public $context; + protected static $data = array(); + + protected $path = ''; + protected $pointer = 0; + protected $writable = false; + + public function stream_close() { + } + + public function stream_eof() { + return $this->pointer >= strlen(self::$data[$this->path]); + } + + public function stream_flush() { + } + + public static function clear() { + self::$data = array(); + } + + public function stream_open($path, $mode, $options, &$opened_path) { + switch ($mode[0]) { + case 'r': + if (!isset(self::$data[$path])) return false; + $this->path = $path; + $this->writable = isset($mode[1]) && $mode[1] == '+'; + break; + case 'w': + self::$data[$path] = ''; + $this->path = $path; + $this->writable = true; + break; + case 'a': + if (!isset(self::$data[$path])) self::$data[$path] = ''; + $this->path = $path; + $this->writable = true; + $this->pointer = strlen(self::$data[$path]); + break; + case 'x': + if (isset(self::$data[$path])) return false; + $this->path = $path; + $this->writable = true; + break; + case 'c': + if (!isset(self::$data[$path])) self::$data[$path] = ''; + $this->path = $path; + $this->writable = true; + break; + default: + return false; + } + $opened_path = $this->path; + return true; + } + + public function stream_read($count) { + $bytes = min(strlen(self::$data[$this->path]) - $this->pointer, $count); + $data = substr(self::$data[$this->path], $this->pointer, $bytes); + $this->pointer += $bytes; + return $data; + } + + public function stream_seek($offset, $whence = SEEK_SET) { + $len = strlen(self::$data[$this->path]); + switch ($whence) { + case SEEK_SET: + if ($offset <= $len) { + $this->pointer = $offset; + return true; + } + break; + case SEEK_CUR: + if ($this->pointer + $offset <= $len) { + $this->pointer += $offset; + return true; + } + break; + case SEEK_END: + if ($len + $offset <= $len) { + $this->pointer = $len + $offset; + return true; + } + break; + } + return false; + } + + public function stream_stat() { + return $this->url_stat($this->path); + } + + public function stream_tell() { + return $this->pointer; + } + + public function stream_write($data) { + if (!$this->writable) return 0; + $size = strlen($data); + if ($this->stream_eof()) { + self::$data[$this->path] .= $data; + } else { + self::$data[$this->path] = substr_replace( + self::$data[$this->path], + $data, + $this->pointer + ); + } + $this->pointer += $size; + return $size; + } + + public function unlink($path) { + if (isset(self::$data[$path])) { + unset(self::$data[$path]); + } + return true; + } + + public function url_stat($path) { + if (isset(self::$data[$path])) { + $size = strlen(self::$data[$path]); + $time = time(); + $data = array( + 'dev' => 0, + 'ino' => 0, + 'mode' => self::MODE_FILE | 0777, + 'nlink' => 1, + 'uid' => 0, + 'gid' => 0, + 'rdev' => '', + 'size' => $size, + 'atime' => $time, + 'mtime' => $time, + 'ctime' => $time, + 'blksize' => -1, + 'blocks' => -1, + ); + return array_values($data) + $data; + } + return false; + } +} diff --git a/lib/private/Files/Type/Detection.php b/lib/private/Files/Type/Detection.php new file mode 100644 index 00000000000..f106a98064f --- /dev/null +++ b/lib/private/Files/Type/Detection.php @@ -0,0 +1,318 @@ + + * @author Jens-Christian Fischer + * @author Lukas Reschke + * @author Morris Jobke + * @author Robin Appelman + * @author Robin McCorkell + * @author Roeland Jago Douma + * @author Thomas Tanghus + * + * @copyright Copyright (c) 2016, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Files\Type; + +use OCP\Files\IMimeTypeDetector; +use OCP\IURLGenerator; + +/** + * Class Detection + * + * Mimetype detection + * + * @package OC\Files\Type + */ +class Detection implements IMimeTypeDetector { + protected $mimetypes = []; + protected $secureMimeTypes = []; + + protected $mimetypeIcons = []; + /** @var string[] */ + protected $mimeTypeAlias = []; + + /** @var IURLGenerator */ + private $urlGenerator; + + /** @var string */ + private $customConfigDir; + + /** @var string */ + private $defaultConfigDir; + + /** + * @param IURLGenerator $urlGenerator + * @param string $customConfigDir + * @param string $defaultConfigDir + */ + public function __construct(IURLGenerator $urlGenerator, + $customConfigDir, + $defaultConfigDir) { + $this->urlGenerator = $urlGenerator; + $this->customConfigDir = $customConfigDir; + $this->defaultConfigDir = $defaultConfigDir; + } + + /** + * Add an extension -> mimetype mapping + * + * $mimetype is the assumed correct mime type + * The optional $secureMimeType is an alternative to send to send + * to avoid potential XSS. + * + * @param string $extension + * @param string $mimetype + * @param string|null $secureMimeType + */ + public function registerType($extension, + $mimetype, + $secureMimeType = null) { + $this->mimetypes[$extension] = array($mimetype, $secureMimeType); + $this->secureMimeTypes[$mimetype] = $secureMimeType ?: $mimetype; + } + + /** + * Add an array of extension -> mimetype mappings + * + * The mimetype value is in itself an array where the first index is + * the assumed correct mimetype and the second is either a secure alternative + * or null if the correct is considered secure. + * + * @param array $types + */ + public function registerTypeArray($types) { + $this->mimetypes = array_merge($this->mimetypes, $types); + + // Update the alternative mimetypes to avoid having to look them up each time. + foreach ($this->mimetypes as $mimeType) { + $this->secureMimeTypes[$mimeType[0]] = isset($mimeType[1]) ? $mimeType[1]: $mimeType[0]; + } + } + + /** + * Add the mimetype aliases if they are not yet present + */ + private function loadAliases() { + if (!empty($this->mimeTypeAlias)) { + return; + } + + $this->mimeTypeAlias = json_decode(file_get_contents($this->defaultConfigDir . '/mimetypealiases.dist.json'), true); + + if (file_exists($this->customConfigDir . '/mimetypealiases.json')) { + $custom = json_decode(file_get_contents($this->customConfigDir . '/mimetypealiases.json'), true); + $this->mimeTypeAlias = array_merge($this->mimeTypeAlias, $custom); + } + } + + /** + * @return string[] + */ + public function getAllAliases() { + $this->loadAliases(); + return $this->mimeTypeAlias; + } + + /** + * Add mimetype mappings if they are not yet present + */ + private function loadMappings() { + if (!empty($this->mimetypes)) { + return; + } + + $mimetypeMapping = json_decode(file_get_contents($this->defaultConfigDir . '/mimetypemapping.dist.json'), true); + + //Check if need to load custom mappings + if (file_exists($this->customConfigDir . '/mimetypemapping.json')) { + $custom = json_decode(file_get_contents($this->customConfigDir . '/mimetypemapping.json'), true); + $mimetypeMapping = array_merge($mimetypeMapping, $custom); + } + + $this->registerTypeArray($mimetypeMapping); + } + + /** + * @return array + */ + public function getAllMappings() { + $this->loadMappings(); + return $this->mimetypes; + } + + /** + * detect mimetype only based on filename, content of file is not used + * + * @param string $path + * @return string + */ + public function detectPath($path) { + $this->loadMappings(); + + if (strpos($path, '.')) { + //try to guess the type by the file extension + $extension = strtolower(strrchr(basename($path), ".")); + $extension = substr($extension, 1); //remove leading . + return (isset($this->mimetypes[$extension]) && isset($this->mimetypes[$extension][0])) + ? $this->mimetypes[$extension][0] + : 'application/octet-stream'; + } else { + return 'application/octet-stream'; + } + } + + /** + * detect mimetype based on both filename and content + * + * @param string $path + * @return string + */ + public function detect($path) { + $this->loadMappings(); + + if (@is_dir($path)) { + // directories are easy + return "httpd/unix-directory"; + } + + $mimeType = $this->detectPath($path); + + if ($mimeType === 'application/octet-stream' and function_exists('finfo_open') + and function_exists('finfo_file') and $finfo = finfo_open(FILEINFO_MIME) + ) { + $info = @strtolower(finfo_file($finfo, $path)); + finfo_close($finfo); + if ($info) { + $mimeType = substr($info, 0, strpos($info, ';')); + return empty($mimeType) ? 'application/octet-stream' : $mimeType; + } + + } + $isWrapped = (strpos($path, '://') !== false) and (substr($path, 0, 7) === 'file://'); + if (!$isWrapped and $mimeType === 'application/octet-stream' && function_exists("mime_content_type")) { + // use mime magic extension if available + $mimeType = mime_content_type($path); + } + if (!$isWrapped and $mimeType === 'application/octet-stream' && \OC_Helper::canExecute("file")) { + // it looks like we have a 'file' command, + // lets see if it does have mime support + $path = escapeshellarg($path); + $fp = popen("file -b --mime-type $path 2>/dev/null", "r"); + $reply = fgets($fp); + pclose($fp); + + //trim the newline + $mimeType = trim($reply); + + if (empty($mimeType)) { + $mimeType = 'application/octet-stream'; + } + + } + return $mimeType; + } + + /** + * detect mimetype based on the content of a string + * + * @param string $data + * @return string + */ + public function detectString($data) { + if (function_exists('finfo_open') and function_exists('finfo_file')) { + $finfo = finfo_open(FILEINFO_MIME); + return finfo_buffer($finfo, $data); + } else { + $tmpFile = \OC::$server->getTempManager()->getTemporaryFile(); + $fh = fopen($tmpFile, 'wb'); + fwrite($fh, $data, 8024); + fclose($fh); + $mime = $this->detect($tmpFile); + unset($tmpFile); + return $mime; + } + } + + /** + * Get a secure mimetype that won't expose potential XSS. + * + * @param string $mimeType + * @return string + */ + public function getSecureMimeType($mimeType) { + $this->loadMappings(); + + return isset($this->secureMimeTypes[$mimeType]) + ? $this->secureMimeTypes[$mimeType] + : 'application/octet-stream'; + } + + /** + * Get path to the icon of a file type + * @param string $mimetype the MIME type + * @return string the url + */ + public function mimeTypeIcon($mimetype) { + $this->loadAliases(); + + while (isset($this->mimeTypeAlias[$mimetype])) { + $mimetype = $this->mimeTypeAlias[$mimetype]; + } + if (isset($this->mimetypeIcons[$mimetype])) { + return $this->mimetypeIcons[$mimetype]; + } + + // Replace slash and backslash with a minus + $icon = str_replace('/', '-', $mimetype); + $icon = str_replace('\\', '-', $icon); + + // Is it a dir? + if ($mimetype === 'dir') { + $this->mimetypeIcons[$mimetype] = $this->urlGenerator->imagePath('core', 'filetypes/folder.png'); + return $this->mimetypeIcons[$mimetype]; + } + if ($mimetype === 'dir-shared') { + $this->mimetypeIcons[$mimetype] = $this->urlGenerator->imagePath('core', 'filetypes/folder-shared.png'); + return $this->mimetypeIcons[$mimetype]; + } + if ($mimetype === 'dir-external') { + $this->mimetypeIcons[$mimetype] = $this->urlGenerator->imagePath('core', 'filetypes/folder-external.png'); + return $this->mimetypeIcons[$mimetype]; + } + + // Icon exists? + try { + $this->mimetypeIcons[$mimetype] = $this->urlGenerator->imagePath('core', 'filetypes/' . $icon . '.png'); + return $this->mimetypeIcons[$mimetype]; + } catch (\RuntimeException $e) { + // Specified image not found + } + + // Try only the first part of the filetype + $mimePart = substr($icon, 0, strpos($icon, '-')); + try { + $this->mimetypeIcons[$mimetype] = $this->urlGenerator->imagePath('core', 'filetypes/' . $mimePart . '.png'); + return $this->mimetypeIcons[$mimetype]; + } catch (\RuntimeException $e) { + // Image for the first part of the mimetype not found + } + + $this->mimetypeIcons[$mimetype] = $this->urlGenerator->imagePath('core', 'filetypes/file.png'); + return $this->mimetypeIcons[$mimetype]; + } +} diff --git a/lib/private/Files/Type/Loader.php b/lib/private/Files/Type/Loader.php new file mode 100644 index 00000000000..95ba7597257 --- /dev/null +++ b/lib/private/Files/Type/Loader.php @@ -0,0 +1,173 @@ + + * + * @copyright Copyright (c) 2016, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Files\Type; + +use OCP\Files\IMimeTypeLoader; +use OCP\IDBConnection; + +use Doctrine\DBAL\Exception\UniqueConstraintViolationException; + +/** + * Mimetype database loader + * + * @package OC\Files\Type + */ +class Loader implements IMimeTypeLoader { + + /** @var IDBConnection */ + private $dbConnection; + + /** @var array [id => mimetype] */ + protected $mimetypes; + + /** @var array [mimetype => id] */ + protected $mimetypeIds; + + /** + * @param IDBConnection $dbConnection + */ + public function __construct(IDBConnection $dbConnection) { + $this->dbConnection = $dbConnection; + $this->mimetypes = []; + $this->mimetypeIds = []; + } + + /** + * Get a mimetype from its ID + * + * @param int $id + * @return string|null + */ + public function getMimetypeById($id) { + if (!$this->mimetypes) { + $this->loadMimetypes(); + } + if (isset($this->mimetypes[$id])) { + return $this->mimetypes[$id]; + } + return null; + } + + /** + * Get a mimetype ID, adding the mimetype to the DB if it does not exist + * + * @param string $mimetype + * @return int + */ + public function getId($mimetype) { + if (!$this->mimetypeIds) { + $this->loadMimetypes(); + } + if (isset($this->mimetypeIds[$mimetype])) { + return $this->mimetypeIds[$mimetype]; + } + return $this->store($mimetype); + } + + /** + * Test if a mimetype exists in the database + * + * @param string $mimetype + * @return bool + */ + public function exists($mimetype) { + if (!$this->mimetypeIds) { + $this->loadMimetypes(); + } + return isset($this->mimetypeIds[$mimetype]); + } + + /** + * Clear all loaded mimetypes, allow for re-loading + */ + public function reset() { + $this->mimetypes = []; + $this->mimetypeIds = []; + } + + /** + * Store a mimetype in the DB + * + * @param string $mimetype + * @param int inserted ID + */ + protected function store($mimetype) { + try { + $qb = $this->dbConnection->getQueryBuilder(); + $qb->insert('mimetypes') + ->values([ + 'mimetype' => $qb->createNamedParameter($mimetype) + ]); + $qb->execute(); + } catch (UniqueConstraintViolationException $e) { + // something inserted it before us + } + + $fetch = $this->dbConnection->getQueryBuilder(); + $fetch->select('id') + ->from('mimetypes') + ->where( + $fetch->expr()->eq('mimetype', $fetch->createNamedParameter($mimetype) + )); + $row = $fetch->execute()->fetch(); + + $this->mimetypes[$row['id']] = $mimetype; + $this->mimetypeIds[$mimetype] = $row['id']; + return $row['id']; + } + + /** + * Load all mimetypes from DB + */ + private function loadMimetypes() { + $qb = $this->dbConnection->getQueryBuilder(); + $qb->select('id', 'mimetype') + ->from('mimetypes'); + $results = $qb->execute()->fetchAll(); + + foreach ($results as $row) { + $this->mimetypes[$row['id']] = $row['mimetype']; + $this->mimetypeIds[$row['mimetype']] = $row['id']; + } + } + + /** + * Update filecache mimetype based on file extension + * + * @param string $ext file extension + * @param int $mimetypeId + * @return int number of changed rows + */ + public function updateFilecache($ext, $mimetypeId) { + $update = $this->dbConnection->getQueryBuilder(); + $update->update('filecache') + ->set('mimetype', $update->createNamedParameter($mimetypeId)) + ->where($update->expr()->neq( + 'mimetype', $update->createNamedParameter($mimetypeId) + )) + ->andWhere($update->expr()->like( + $update->createFunction('LOWER(`name`)'), $update->createNamedParameter($ext) + )); + return $update->execute(); + } + +} diff --git a/lib/private/Files/Type/TemplateManager.php b/lib/private/Files/Type/TemplateManager.php new file mode 100644 index 00000000000..363fb7a2a6c --- /dev/null +++ b/lib/private/Files/Type/TemplateManager.php @@ -0,0 +1,61 @@ + + * @author Robin Appelman + * @author Robin McCorkell + * + * @copyright Copyright (c) 2016, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Files\Type; + +class TemplateManager { + protected $templates = array(); + + public function registerTemplate($mimetype, $path) { + $this->templates[$mimetype] = $path; + } + + /** + * get the path of the template for a mimetype + * + * @param string $mimetype + * @return string|null + */ + public function getTemplatePath($mimetype) { + if (isset($this->templates[$mimetype])) { + return $this->templates[$mimetype]; + } else { + return null; + } + } + + /** + * get the template content for a mimetype + * + * @param string $mimetype + * @return string + */ + public function getTemplate($mimetype) { + $path = $this->getTemplatePath($mimetype); + if ($path) { + return file_get_contents($path); + } else { + return ''; + } + } +} diff --git a/lib/private/Files/Utils/Scanner.php b/lib/private/Files/Utils/Scanner.php new file mode 100644 index 00000000000..06526583899 --- /dev/null +++ b/lib/private/Files/Utils/Scanner.php @@ -0,0 +1,172 @@ + + * @author Morris Jobke + * @author Olivier Paroz + * @author Robin Appelman + * @author Thomas Müller + * + * @copyright Copyright (c) 2016, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Files\Utils; + +use OC\Files\Filesystem; +use OC\ForbiddenException; +use OC\Hooks\PublicEmitter; +use OC\Lock\DBLockingProvider; +use OCP\Files\StorageNotAvailableException; +use OCP\ILogger; + +/** + * Class Scanner + * + * Hooks available in scope \OC\Utils\Scanner + * - scanFile(string $absolutePath) + * - scanFolder(string $absolutePath) + * + * @package OC\Files\Utils + */ +class Scanner extends PublicEmitter { + /** + * @var string $user + */ + private $user; + + /** + * @var \OCP\IDBConnection + */ + protected $db; + + /** + * @var ILogger + */ + protected $logger; + + /** + * @param string $user + * @param \OCP\IDBConnection $db + * @param ILogger $logger + */ + public function __construct($user, $db, ILogger $logger) { + $this->logger = $logger; + $this->user = $user; + $this->db = $db; + } + + /** + * get all storages for $dir + * + * @param string $dir + * @return \OC\Files\Mount\MountPoint[] + */ + protected function getMounts($dir) { + //TODO: move to the node based fileapi once that's done + \OC_Util::tearDownFS(); + \OC_Util::setupFS($this->user); + + $mountManager = Filesystem::getMountManager(); + $mounts = $mountManager->findIn($dir); + $mounts[] = $mountManager->find($dir); + $mounts = array_reverse($mounts); //start with the mount of $dir + + return $mounts; + } + + /** + * attach listeners to the scanner + * + * @param \OC\Files\Mount\MountPoint $mount + */ + protected function attachListener($mount) { + $scanner = $mount->getStorage()->getScanner(); + $emitter = $this; + $scanner->listen('\OC\Files\Cache\Scanner', 'scanFile', function ($path) use ($mount, $emitter) { + $emitter->emit('\OC\Files\Utils\Scanner', 'scanFile', array($mount->getMountPoint() . $path)); + }); + $scanner->listen('\OC\Files\Cache\Scanner', 'scanFolder', function ($path) use ($mount, $emitter) { + $emitter->emit('\OC\Files\Utils\Scanner', 'scanFolder', array($mount->getMountPoint() . $path)); + }); + $scanner->listen('\OC\Files\Cache\Scanner', 'postScanFile', function ($path) use ($mount, $emitter) { + $emitter->emit('\OC\Files\Utils\Scanner', 'postScanFile', array($mount->getMountPoint() . $path)); + }); + $scanner->listen('\OC\Files\Cache\Scanner', 'postScanFolder', function ($path) use ($mount, $emitter) { + $emitter->emit('\OC\Files\Utils\Scanner', 'postScanFolder', array($mount->getMountPoint() . $path)); + }); + } + + /** + * @param string $dir + */ + public function backgroundScan($dir) { + $mounts = $this->getMounts($dir); + foreach ($mounts as $mount) { + if (is_null($mount->getStorage())) { + continue; + } + // don't scan the root storage + if ($mount->getStorage()->instanceOfStorage('\OC\Files\Storage\Local') && $mount->getMountPoint() === '/') { + continue; + } + $scanner = $mount->getStorage()->getScanner(); + $this->attachListener($mount); + $scanner->backgroundScan(); + } + } + + /** + * @param string $dir + * @throws \OC\ForbiddenException + */ + public function scan($dir = '') { + if (!Filesystem::isValidPath($dir)) { + throw new \InvalidArgumentException('Invalid path to scan'); + } + $mounts = $this->getMounts($dir); + foreach ($mounts as $mount) { + if (is_null($mount->getStorage())) { + continue; + } + $storage = $mount->getStorage(); + // if the home storage isn't writable then the scanner is run as the wrong user + if ($storage->instanceOfStorage('\OC\Files\Storage\Home') and + (!$storage->isCreatable('') or !$storage->isCreatable('files')) + ) { + throw new ForbiddenException(); + } + $relativePath = $mount->getInternalPath($dir); + $scanner = $storage->getScanner(); + $scanner->setUseTransactions(false); + $this->attachListener($mount); + $isDbLocking = \OC::$server->getLockingProvider() instanceof DBLockingProvider; + if (!$isDbLocking) { + $this->db->beginTransaction(); + } + try { + $scanner->scan($relativePath, \OC\Files\Cache\Scanner::SCAN_RECURSIVE, \OC\Files\Cache\Scanner::REUSE_ETAG | \OC\Files\Cache\Scanner::REUSE_SIZE); + } catch (StorageNotAvailableException $e) { + $this->logger->error('Storage ' . $storage->getId() . ' not available'); + $this->logger->logException($e); + $this->emit('\OC\Files\Utils\Scanner', 'StorageNotAvailable', [$e]); + } + if (!$isDbLocking) { + $this->db->commit(); + } + } + } +} + diff --git a/lib/private/Files/View.php b/lib/private/Files/View.php new file mode 100644 index 00000000000..aac33a4598c --- /dev/null +++ b/lib/private/Files/View.php @@ -0,0 +1,2058 @@ + + * @author Bart Visscher + * @author Björn Schießle + * @author cmeh + * @author Florin Peter + * @author Jesús Macias + * @author Joas Schilling + * @author Jörn Friedrich Dreyer + * @author Klaas Freitag + * @author Lukas Reschke + * @author Luke Policinski + * @author Martin Mattel + * @author Michael Gapczynski + * @author Morris Jobke + * @author Robin Appelman + * @author Robin McCorkell + * @author Roeland Jago Douma + * @author Sam Tuke + * @author Thomas Müller + * @author Thomas Tanghus + * @author Vincent Petry + * + * @copyright Copyright (c) 2016, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + + +namespace OC\Files; + +use Icewind\Streams\CallbackWrapper; +use OC\Files\Mount\MoveableMount; +use OC\Files\Storage\Storage; +use OC\User\User; +use OCP\Constants; +use OCP\Files\Cache\ICacheEntry; +use OCP\Files\FileNameTooLongException; +use OCP\Files\InvalidCharacterInPathException; +use OCP\Files\InvalidPathException; +use OCP\Files\NotFoundException; +use OCP\Files\ReservedWordException; +use OCP\Files\Storage\ILockingStorage; +use OCP\IUser; +use OCP\Lock\ILockingProvider; +use OCP\Lock\LockedException; + +/** + * Class to provide access to ownCloud filesystem via a "view", and methods for + * working with files within that view (e.g. read, write, delete, etc.). Each + * view is restricted to a set of directories via a virtual root. The default view + * uses the currently logged in user's data directory as root (parts of + * OC_Filesystem are merely a wrapper for OC\Files\View). + * + * Apps that need to access files outside of the user data folders (to modify files + * belonging to a user other than the one currently logged in, for example) should + * use this class directly rather than using OC_Filesystem, or making use of PHP's + * built-in file manipulation functions. This will ensure all hooks and proxies + * are triggered correctly. + * + * Filesystem functions are not called directly; they are passed to the correct + * \OC\Files\Storage\Storage object + */ +class View { + /** @var string */ + private $fakeRoot = ''; + + /** + * @var \OCP\Lock\ILockingProvider + */ + private $lockingProvider; + + private $lockingEnabled; + + private $updaterEnabled = true; + + private $userManager; + + /** + * @param string $root + * @throws \Exception If $root contains an invalid path + */ + public function __construct($root = '') { + if (is_null($root)) { + throw new \InvalidArgumentException('Root can\'t be null'); + } + if (!Filesystem::isValidPath($root)) { + throw new \Exception(); + } + + $this->fakeRoot = $root; + $this->lockingProvider = \OC::$server->getLockingProvider(); + $this->lockingEnabled = !($this->lockingProvider instanceof \OC\Lock\NoopLockingProvider); + $this->userManager = \OC::$server->getUserManager(); + } + + public function getAbsolutePath($path = '/') { + if ($path === null) { + return null; + } + $this->assertPathLength($path); + if ($path === '') { + $path = '/'; + } + if ($path[0] !== '/') { + $path = '/' . $path; + } + return $this->fakeRoot . $path; + } + + /** + * change the root to a fake root + * + * @param string $fakeRoot + * @return boolean|null + */ + public function chroot($fakeRoot) { + if (!$fakeRoot == '') { + if ($fakeRoot[0] !== '/') { + $fakeRoot = '/' . $fakeRoot; + } + } + $this->fakeRoot = $fakeRoot; + } + + /** + * get the fake root + * + * @return string + */ + public function getRoot() { + return $this->fakeRoot; + } + + /** + * get path relative to the root of the view + * + * @param string $path + * @return string + */ + public function getRelativePath($path) { + $this->assertPathLength($path); + if ($this->fakeRoot == '') { + return $path; + } + + if (rtrim($path, '/') === rtrim($this->fakeRoot, '/')) { + return '/'; + } + + // missing slashes can cause wrong matches! + $root = rtrim($this->fakeRoot, '/') . '/'; + + if (strpos($path, $root) !== 0) { + return null; + } else { + $path = substr($path, strlen($this->fakeRoot)); + if (strlen($path) === 0) { + return '/'; + } else { + return $path; + } + } + } + + /** + * get the mountpoint of the storage object for a path + * ( note: because a storage is not always mounted inside the fakeroot, the + * returned mountpoint is relative to the absolute root of the filesystem + * and does not take the chroot into account ) + * + * @param string $path + * @return string + */ + public function getMountPoint($path) { + return Filesystem::getMountPoint($this->getAbsolutePath($path)); + } + + /** + * get the mountpoint of the storage object for a path + * ( note: because a storage is not always mounted inside the fakeroot, the + * returned mountpoint is relative to the absolute root of the filesystem + * and does not take the chroot into account ) + * + * @param string $path + * @return \OCP\Files\Mount\IMountPoint + */ + public function getMount($path) { + return Filesystem::getMountManager()->find($this->getAbsolutePath($path)); + } + + /** + * resolve a path to a storage and internal path + * + * @param string $path + * @return array an array consisting of the storage and the internal path + */ + public function resolvePath($path) { + $a = $this->getAbsolutePath($path); + $p = Filesystem::normalizePath($a); + return Filesystem::resolvePath($p); + } + + /** + * return the path to a local version of the file + * we need this because we can't know if a file is stored local or not from + * outside the filestorage and for some purposes a local file is needed + * + * @param string $path + * @return string + */ + public function getLocalFile($path) { + $parent = substr($path, 0, strrpos($path, '/')); + $path = $this->getAbsolutePath($path); + list($storage, $internalPath) = Filesystem::resolvePath($path); + if (Filesystem::isValidPath($parent) and $storage) { + return $storage->getLocalFile($internalPath); + } else { + return null; + } + } + + /** + * @param string $path + * @return string + */ + public function getLocalFolder($path) { + $parent = substr($path, 0, strrpos($path, '/')); + $path = $this->getAbsolutePath($path); + list($storage, $internalPath) = Filesystem::resolvePath($path); + if (Filesystem::isValidPath($parent) and $storage) { + return $storage->getLocalFolder($internalPath); + } else { + return null; + } + } + + /** + * the following functions operate with arguments and return values identical + * to those of their PHP built-in equivalents. Mostly they are merely wrappers + * for \OC\Files\Storage\Storage via basicOperation(). + */ + public function mkdir($path) { + return $this->basicOperation('mkdir', $path, array('create', 'write')); + } + + /** + * remove mount point + * + * @param \OC\Files\Mount\MoveableMount $mount + * @param string $path relative to data/ + * @return boolean + */ + protected function removeMount($mount, $path) { + if ($mount instanceof MoveableMount) { + // cut of /user/files to get the relative path to data/user/files + $pathParts = explode('/', $path, 4); + $relPath = '/' . $pathParts[3]; + $this->lockFile($relPath, ILockingProvider::LOCK_SHARED, true); + \OC_Hook::emit( + Filesystem::CLASSNAME, "umount", + array(Filesystem::signal_param_path => $relPath) + ); + $this->changeLock($relPath, ILockingProvider::LOCK_EXCLUSIVE, true); + $result = $mount->removeMount(); + $this->changeLock($relPath, ILockingProvider::LOCK_SHARED, true); + if ($result) { + \OC_Hook::emit( + Filesystem::CLASSNAME, "post_umount", + array(Filesystem::signal_param_path => $relPath) + ); + } + $this->unlockFile($relPath, ILockingProvider::LOCK_SHARED, true); + return $result; + } else { + // do not allow deleting the storage's root / the mount point + // because for some storages it might delete the whole contents + // but isn't supposed to work that way + return false; + } + } + + public function disableCacheUpdate() { + $this->updaterEnabled = false; + } + + public function enableCacheUpdate() { + $this->updaterEnabled = true; + } + + protected function writeUpdate(Storage $storage, $internalPath, $time = null) { + if ($this->updaterEnabled) { + if (is_null($time)) { + $time = time(); + } + $storage->getUpdater()->update($internalPath, $time); + } + } + + protected function removeUpdate(Storage $storage, $internalPath) { + if ($this->updaterEnabled) { + $storage->getUpdater()->remove($internalPath); + } + } + + protected function renameUpdate(Storage $sourceStorage, Storage $targetStorage, $sourceInternalPath, $targetInternalPath) { + if ($this->updaterEnabled) { + $targetStorage->getUpdater()->renameFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath); + } + } + + /** + * @param string $path + * @return bool|mixed + */ + public function rmdir($path) { + $absolutePath = $this->getAbsolutePath($path); + $mount = Filesystem::getMountManager()->find($absolutePath); + if ($mount->getInternalPath($absolutePath) === '') { + return $this->removeMount($mount, $absolutePath); + } + if ($this->is_dir($path)) { + return $this->basicOperation('rmdir', $path, array('delete')); + } else { + return false; + } + } + + /** + * @param string $path + * @return resource + */ + public function opendir($path) { + return $this->basicOperation('opendir', $path, array('read')); + } + + /** + * @param $handle + * @return mixed + */ + public function readdir($handle) { + $fsLocal = new Storage\Local(array('datadir' => '/')); + return $fsLocal->readdir($handle); + } + + /** + * @param string $path + * @return bool|mixed + */ + public function is_dir($path) { + if ($path == '/') { + return true; + } + return $this->basicOperation('is_dir', $path); + } + + /** + * @param string $path + * @return bool|mixed + */ + public function is_file($path) { + if ($path == '/') { + return false; + } + return $this->basicOperation('is_file', $path); + } + + /** + * @param string $path + * @return mixed + */ + public function stat($path) { + return $this->basicOperation('stat', $path); + } + + /** + * @param string $path + * @return mixed + */ + public function filetype($path) { + return $this->basicOperation('filetype', $path); + } + + /** + * @param string $path + * @return mixed + */ + public function filesize($path) { + return $this->basicOperation('filesize', $path); + } + + /** + * @param string $path + * @return bool|mixed + * @throws \OCP\Files\InvalidPathException + */ + public function readfile($path) { + $this->assertPathLength($path); + @ob_end_clean(); + $handle = $this->fopen($path, 'rb'); + if ($handle) { + $chunkSize = 8192; // 8 kB chunks + while (!feof($handle)) { + echo fread($handle, $chunkSize); + flush(); + } + $size = $this->filesize($path); + return $size; + } + return false; + } + + /** + * @param string $path + * @return mixed + */ + public function isCreatable($path) { + return $this->basicOperation('isCreatable', $path); + } + + /** + * @param string $path + * @return mixed + */ + public function isReadable($path) { + return $this->basicOperation('isReadable', $path); + } + + /** + * @param string $path + * @return mixed + */ + public function isUpdatable($path) { + return $this->basicOperation('isUpdatable', $path); + } + + /** + * @param string $path + * @return bool|mixed + */ + public function isDeletable($path) { + $absolutePath = $this->getAbsolutePath($path); + $mount = Filesystem::getMountManager()->find($absolutePath); + if ($mount->getInternalPath($absolutePath) === '') { + return $mount instanceof MoveableMount; + } + return $this->basicOperation('isDeletable', $path); + } + + /** + * @param string $path + * @return mixed + */ + public function isSharable($path) { + return $this->basicOperation('isSharable', $path); + } + + /** + * @param string $path + * @return bool|mixed + */ + public function file_exists($path) { + if ($path == '/') { + return true; + } + return $this->basicOperation('file_exists', $path); + } + + /** + * @param string $path + * @return mixed + */ + public function filemtime($path) { + return $this->basicOperation('filemtime', $path); + } + + /** + * @param string $path + * @param int|string $mtime + * @return bool + */ + public function touch($path, $mtime = null) { + if (!is_null($mtime) and !is_numeric($mtime)) { + $mtime = strtotime($mtime); + } + + $hooks = array('touch'); + + if (!$this->file_exists($path)) { + $hooks[] = 'create'; + $hooks[] = 'write'; + } + $result = $this->basicOperation('touch', $path, $hooks, $mtime); + if (!$result) { + // If create file fails because of permissions on external storage like SMB folders, + // check file exists and return false if not. + if (!$this->file_exists($path)) { + return false; + } + if (is_null($mtime)) { + $mtime = time(); + } + //if native touch fails, we emulate it by changing the mtime in the cache + $this->putFileInfo($path, array('mtime' => $mtime)); + } + return true; + } + + /** + * @param string $path + * @return mixed + */ + public function file_get_contents($path) { + return $this->basicOperation('file_get_contents', $path, array('read')); + } + + /** + * @param bool $exists + * @param string $path + * @param bool $run + */ + protected function emit_file_hooks_pre($exists, $path, &$run) { + if (!$exists) { + \OC_Hook::emit(Filesystem::CLASSNAME, Filesystem::signal_create, array( + Filesystem::signal_param_path => $this->getHookPath($path), + Filesystem::signal_param_run => &$run, + )); + } else { + \OC_Hook::emit(Filesystem::CLASSNAME, Filesystem::signal_update, array( + Filesystem::signal_param_path => $this->getHookPath($path), + Filesystem::signal_param_run => &$run, + )); + } + \OC_Hook::emit(Filesystem::CLASSNAME, Filesystem::signal_write, array( + Filesystem::signal_param_path => $this->getHookPath($path), + Filesystem::signal_param_run => &$run, + )); + } + + /** + * @param bool $exists + * @param string $path + */ + protected function emit_file_hooks_post($exists, $path) { + if (!$exists) { + \OC_Hook::emit(Filesystem::CLASSNAME, Filesystem::signal_post_create, array( + Filesystem::signal_param_path => $this->getHookPath($path), + )); + } else { + \OC_Hook::emit(Filesystem::CLASSNAME, Filesystem::signal_post_update, array( + Filesystem::signal_param_path => $this->getHookPath($path), + )); + } + \OC_Hook::emit(Filesystem::CLASSNAME, Filesystem::signal_post_write, array( + Filesystem::signal_param_path => $this->getHookPath($path), + )); + } + + /** + * @param string $path + * @param mixed $data + * @return bool|mixed + * @throws \Exception + */ + public function file_put_contents($path, $data) { + if (is_resource($data)) { //not having to deal with streams in file_put_contents makes life easier + $absolutePath = Filesystem::normalizePath($this->getAbsolutePath($path)); + if (Filesystem::isValidPath($path) + and !Filesystem::isFileBlacklisted($path) + ) { + $path = $this->getRelativePath($absolutePath); + + $this->lockFile($path, ILockingProvider::LOCK_SHARED); + + $exists = $this->file_exists($path); + $run = true; + if ($this->shouldEmitHooks($path)) { + $this->emit_file_hooks_pre($exists, $path, $run); + } + if (!$run) { + $this->unlockFile($path, ILockingProvider::LOCK_SHARED); + return false; + } + + $this->changeLock($path, ILockingProvider::LOCK_EXCLUSIVE); + + /** @var \OC\Files\Storage\Storage $storage */ + list($storage, $internalPath) = $this->resolvePath($path); + $target = $storage->fopen($internalPath, 'w'); + if ($target) { + list (, $result) = \OC_Helper::streamCopy($data, $target); + fclose($target); + fclose($data); + + $this->writeUpdate($storage, $internalPath); + + $this->changeLock($path, ILockingProvider::LOCK_SHARED); + + if ($this->shouldEmitHooks($path) && $result !== false) { + $this->emit_file_hooks_post($exists, $path); + } + $this->unlockFile($path, ILockingProvider::LOCK_SHARED); + return $result; + } else { + $this->unlockFile($path, ILockingProvider::LOCK_EXCLUSIVE); + return false; + } + } else { + return false; + } + } else { + $hooks = ($this->file_exists($path)) ? array('update', 'write') : array('create', 'write'); + return $this->basicOperation('file_put_contents', $path, $hooks, $data); + } + } + + /** + * @param string $path + * @return bool|mixed + */ + public function unlink($path) { + if ($path === '' || $path === '/') { + // do not allow deleting the root + return false; + } + $postFix = (substr($path, -1, 1) === '/') ? '/' : ''; + $absolutePath = Filesystem::normalizePath($this->getAbsolutePath($path)); + $mount = Filesystem::getMountManager()->find($absolutePath . $postFix); + if ($mount and $mount->getInternalPath($absolutePath) === '') { + return $this->removeMount($mount, $absolutePath); + } + return $this->basicOperation('unlink', $path, array('delete')); + } + + /** + * @param string $directory + * @return bool|mixed + */ + public function deleteAll($directory) { + return $this->rmdir($directory); + } + + /** + * Rename/move a file or folder from the source path to target path. + * + * @param string $path1 source path + * @param string $path2 target path + * + * @return bool|mixed + */ + public function rename($path1, $path2) { + $absolutePath1 = Filesystem::normalizePath($this->getAbsolutePath($path1)); + $absolutePath2 = Filesystem::normalizePath($this->getAbsolutePath($path2)); + $result = false; + if ( + Filesystem::isValidPath($path2) + and Filesystem::isValidPath($path1) + and !Filesystem::isFileBlacklisted($path2) + ) { + $path1 = $this->getRelativePath($absolutePath1); + $path2 = $this->getRelativePath($absolutePath2); + $exists = $this->file_exists($path2); + + if ($path1 == null or $path2 == null) { + return false; + } + + $this->lockFile($path1, ILockingProvider::LOCK_SHARED, true); + try { + $this->lockFile($path2, ILockingProvider::LOCK_SHARED, true); + } catch (LockedException $e) { + $this->unlockFile($path1, ILockingProvider::LOCK_SHARED); + throw $e; + } + + $run = true; + if ($this->shouldEmitHooks($path1) && (Cache\Scanner::isPartialFile($path1) && !Cache\Scanner::isPartialFile($path2))) { + // if it was a rename from a part file to a regular file it was a write and not a rename operation + $this->emit_file_hooks_pre($exists, $path2, $run); + } elseif ($this->shouldEmitHooks($path1)) { + \OC_Hook::emit( + Filesystem::CLASSNAME, Filesystem::signal_rename, + array( + Filesystem::signal_param_oldpath => $this->getHookPath($path1), + Filesystem::signal_param_newpath => $this->getHookPath($path2), + Filesystem::signal_param_run => &$run + ) + ); + } + if ($run) { + $this->verifyPath(dirname($path2), basename($path2)); + + $manager = Filesystem::getMountManager(); + $mount1 = $this->getMount($path1); + $mount2 = $this->getMount($path2); + $storage1 = $mount1->getStorage(); + $storage2 = $mount2->getStorage(); + $internalPath1 = $mount1->getInternalPath($absolutePath1); + $internalPath2 = $mount2->getInternalPath($absolutePath2); + + $this->changeLock($path1, ILockingProvider::LOCK_EXCLUSIVE, true); + $this->changeLock($path2, ILockingProvider::LOCK_EXCLUSIVE, true); + + if ($internalPath1 === '' and $mount1 instanceof MoveableMount) { + if ($this->isTargetAllowed($absolutePath2)) { + /** + * @var \OC\Files\Mount\MountPoint | \OC\Files\Mount\MoveableMount $mount1 + */ + $sourceMountPoint = $mount1->getMountPoint(); + $result = $mount1->moveMount($absolutePath2); + $manager->moveMount($sourceMountPoint, $mount1->getMountPoint()); + } else { + $result = false; + } + // moving a file/folder within the same mount point + } elseif ($storage1 == $storage2) { + if ($storage1) { + $result = $storage1->rename($internalPath1, $internalPath2); + } else { + $result = false; + } + // moving a file/folder between storages (from $storage1 to $storage2) + } else { + $result = $storage2->moveFromStorage($storage1, $internalPath1, $internalPath2); + } + + if ((Cache\Scanner::isPartialFile($path1) && !Cache\Scanner::isPartialFile($path2)) && $result !== false) { + // if it was a rename from a part file to a regular file it was a write and not a rename operation + + $this->writeUpdate($storage2, $internalPath2); + } else if ($result) { + if ($internalPath1 !== '') { // don't do a cache update for moved mounts + $this->renameUpdate($storage1, $storage2, $internalPath1, $internalPath2); + } + } + + $this->changeLock($path1, ILockingProvider::LOCK_SHARED, true); + $this->changeLock($path2, ILockingProvider::LOCK_SHARED, true); + + if ((Cache\Scanner::isPartialFile($path1) && !Cache\Scanner::isPartialFile($path2)) && $result !== false) { + if ($this->shouldEmitHooks()) { + $this->emit_file_hooks_post($exists, $path2); + } + } elseif ($result) { + if ($this->shouldEmitHooks($path1) and $this->shouldEmitHooks($path2)) { + \OC_Hook::emit( + Filesystem::CLASSNAME, + Filesystem::signal_post_rename, + array( + Filesystem::signal_param_oldpath => $this->getHookPath($path1), + Filesystem::signal_param_newpath => $this->getHookPath($path2) + ) + ); + } + } + } + $this->unlockFile($path1, ILockingProvider::LOCK_SHARED, true); + $this->unlockFile($path2, ILockingProvider::LOCK_SHARED, true); + } + return $result; + } + + /** + * Copy a file/folder from the source path to target path + * + * @param string $path1 source path + * @param string $path2 target path + * @param bool $preserveMtime whether to preserve mtime on the copy + * + * @return bool|mixed + */ + public function copy($path1, $path2, $preserveMtime = false) { + $absolutePath1 = Filesystem::normalizePath($this->getAbsolutePath($path1)); + $absolutePath2 = Filesystem::normalizePath($this->getAbsolutePath($path2)); + $result = false; + if ( + Filesystem::isValidPath($path2) + and Filesystem::isValidPath($path1) + and !Filesystem::isFileBlacklisted($path2) + ) { + $path1 = $this->getRelativePath($absolutePath1); + $path2 = $this->getRelativePath($absolutePath2); + + if ($path1 == null or $path2 == null) { + return false; + } + $run = true; + + $this->lockFile($path2, ILockingProvider::LOCK_SHARED); + $this->lockFile($path1, ILockingProvider::LOCK_SHARED); + $lockTypePath1 = ILockingProvider::LOCK_SHARED; + $lockTypePath2 = ILockingProvider::LOCK_SHARED; + + try { + + $exists = $this->file_exists($path2); + if ($this->shouldEmitHooks()) { + \OC_Hook::emit( + Filesystem::CLASSNAME, + Filesystem::signal_copy, + array( + Filesystem::signal_param_oldpath => $this->getHookPath($path1), + Filesystem::signal_param_newpath => $this->getHookPath($path2), + Filesystem::signal_param_run => &$run + ) + ); + $this->emit_file_hooks_pre($exists, $path2, $run); + } + if ($run) { + $mount1 = $this->getMount($path1); + $mount2 = $this->getMount($path2); + $storage1 = $mount1->getStorage(); + $internalPath1 = $mount1->getInternalPath($absolutePath1); + $storage2 = $mount2->getStorage(); + $internalPath2 = $mount2->getInternalPath($absolutePath2); + + $this->changeLock($path2, ILockingProvider::LOCK_EXCLUSIVE); + $lockTypePath2 = ILockingProvider::LOCK_EXCLUSIVE; + + if ($mount1->getMountPoint() == $mount2->getMountPoint()) { + if ($storage1) { + $result = $storage1->copy($internalPath1, $internalPath2); + } else { + $result = false; + } + } else { + $result = $storage2->copyFromStorage($storage1, $internalPath1, $internalPath2); + } + + $this->writeUpdate($storage2, $internalPath2); + + $this->changeLock($path2, ILockingProvider::LOCK_SHARED); + $lockTypePath2 = ILockingProvider::LOCK_SHARED; + + if ($this->shouldEmitHooks() && $result !== false) { + \OC_Hook::emit( + Filesystem::CLASSNAME, + Filesystem::signal_post_copy, + array( + Filesystem::signal_param_oldpath => $this->getHookPath($path1), + Filesystem::signal_param_newpath => $this->getHookPath($path2) + ) + ); + $this->emit_file_hooks_post($exists, $path2); + } + + } + } catch (\Exception $e) { + $this->unlockFile($path2, $lockTypePath2); + $this->unlockFile($path1, $lockTypePath1); + throw $e; + } + + $this->unlockFile($path2, $lockTypePath2); + $this->unlockFile($path1, $lockTypePath1); + + } + return $result; + } + + /** + * @param string $path + * @param string $mode + * @return resource + */ + public function fopen($path, $mode) { + $hooks = array(); + switch ($mode) { + case 'r': + case 'rb': + $hooks[] = 'read'; + break; + case 'r+': + case 'rb+': + case 'w+': + case 'wb+': + case 'x+': + case 'xb+': + case 'a+': + case 'ab+': + $hooks[] = 'read'; + $hooks[] = 'write'; + break; + case 'w': + case 'wb': + case 'x': + case 'xb': + case 'a': + case 'ab': + $hooks[] = 'write'; + break; + default: + \OCP\Util::writeLog('core', 'invalid mode (' . $mode . ') for ' . $path, \OCP\Util::ERROR); + } + + return $this->basicOperation('fopen', $path, $hooks, $mode); + } + + /** + * @param string $path + * @return bool|string + * @throws \OCP\Files\InvalidPathException + */ + public function toTmpFile($path) { + $this->assertPathLength($path); + if (Filesystem::isValidPath($path)) { + $source = $this->fopen($path, 'r'); + if ($source) { + $extension = pathinfo($path, PATHINFO_EXTENSION); + $tmpFile = \OC::$server->getTempManager()->getTemporaryFile($extension); + file_put_contents($tmpFile, $source); + return $tmpFile; + } else { + return false; + } + } else { + return false; + } + } + + /** + * @param string $tmpFile + * @param string $path + * @return bool|mixed + * @throws \OCP\Files\InvalidPathException + */ + public function fromTmpFile($tmpFile, $path) { + $this->assertPathLength($path); + if (Filesystem::isValidPath($path)) { + + // Get directory that the file is going into + $filePath = dirname($path); + + // Create the directories if any + if (!$this->file_exists($filePath)) { + $this->mkdir($filePath); + } + + $source = fopen($tmpFile, 'r'); + if ($source) { + $result = $this->file_put_contents($path, $source); + // $this->file_put_contents() might have already closed + // the resource, so we check it, before trying to close it + // to avoid messages in the error log. + if (is_resource($source)) { + fclose($source); + } + unlink($tmpFile); + return $result; + } else { + return false; + } + } else { + return false; + } + } + + + /** + * @param string $path + * @return mixed + * @throws \OCP\Files\InvalidPathException + */ + public function getMimeType($path) { + $this->assertPathLength($path); + return $this->basicOperation('getMimeType', $path); + } + + /** + * @param string $type + * @param string $path + * @param bool $raw + * @return bool|null|string + */ + public function hash($type, $path, $raw = false) { + $postFix = (substr($path, -1, 1) === '/') ? '/' : ''; + $absolutePath = Filesystem::normalizePath($this->getAbsolutePath($path)); + if (Filesystem::isValidPath($path)) { + $path = $this->getRelativePath($absolutePath); + if ($path == null) { + return false; + } + if ($this->shouldEmitHooks($path)) { + \OC_Hook::emit( + Filesystem::CLASSNAME, + Filesystem::signal_read, + array(Filesystem::signal_param_path => $this->getHookPath($path)) + ); + } + list($storage, $internalPath) = Filesystem::resolvePath($absolutePath . $postFix); + if ($storage) { + $result = $storage->hash($type, $internalPath, $raw); + return $result; + } + } + return null; + } + + /** + * @param string $path + * @return mixed + * @throws \OCP\Files\InvalidPathException + */ + public function free_space($path = '/') { + $this->assertPathLength($path); + return $this->basicOperation('free_space', $path); + } + + /** + * abstraction layer for basic filesystem functions: wrapper for \OC\Files\Storage\Storage + * + * @param string $operation + * @param string $path + * @param array $hooks (optional) + * @param mixed $extraParam (optional) + * @return mixed + * @throws \Exception + * + * This method takes requests for basic filesystem functions (e.g. reading & writing + * files), processes hooks and proxies, sanitises paths, and finally passes them on to + * \OC\Files\Storage\Storage for delegation to a storage backend for execution + */ + private function basicOperation($operation, $path, $hooks = [], $extraParam = null) { + $postFix = (substr($path, -1, 1) === '/') ? '/' : ''; + $absolutePath = Filesystem::normalizePath($this->getAbsolutePath($path)); + if (Filesystem::isValidPath($path) + and !Filesystem::isFileBlacklisted($path) + ) { + $path = $this->getRelativePath($absolutePath); + if ($path == null) { + return false; + } + + if (in_array('write', $hooks) || in_array('delete', $hooks) || in_array('read', $hooks)) { + // always a shared lock during pre-hooks so the hook can read the file + $this->lockFile($path, ILockingProvider::LOCK_SHARED); + } + + $run = $this->runHooks($hooks, $path); + /** @var \OC\Files\Storage\Storage $storage */ + list($storage, $internalPath) = Filesystem::resolvePath($absolutePath . $postFix); + if ($run and $storage) { + if (in_array('write', $hooks) || in_array('delete', $hooks)) { + $this->changeLock($path, ILockingProvider::LOCK_EXCLUSIVE); + } + try { + if (!is_null($extraParam)) { + $result = $storage->$operation($internalPath, $extraParam); + } else { + $result = $storage->$operation($internalPath); + } + } catch (\Exception $e) { + if (in_array('write', $hooks) || in_array('delete', $hooks)) { + $this->unlockFile($path, ILockingProvider::LOCK_EXCLUSIVE); + } else if (in_array('read', $hooks)) { + $this->unlockFile($path, ILockingProvider::LOCK_SHARED); + } + throw $e; + } + + if (in_array('delete', $hooks) and $result) { + $this->removeUpdate($storage, $internalPath); + } + if (in_array('write', $hooks) and $operation !== 'fopen') { + $this->writeUpdate($storage, $internalPath); + } + if (in_array('touch', $hooks)) { + $this->writeUpdate($storage, $internalPath, $extraParam); + } + + if ((in_array('write', $hooks) || in_array('delete', $hooks)) && ($operation !== 'fopen' || $result === false)) { + $this->changeLock($path, ILockingProvider::LOCK_SHARED); + } + + $unlockLater = false; + if ($this->lockingEnabled && $operation === 'fopen' && is_resource($result)) { + $unlockLater = true; + $result = CallbackWrapper::wrap($result, null, null, function () use ($hooks, $path) { + if (in_array('write', $hooks)) { + $this->unlockFile($path, ILockingProvider::LOCK_EXCLUSIVE); + } else if (in_array('read', $hooks)) { + $this->unlockFile($path, ILockingProvider::LOCK_SHARED); + } + }); + } + + if ($this->shouldEmitHooks($path) && $result !== false) { + if ($operation != 'fopen') { //no post hooks for fopen, the file stream is still open + $this->runHooks($hooks, $path, true); + } + } + + if (!$unlockLater + && (in_array('write', $hooks) || in_array('delete', $hooks) || in_array('read', $hooks)) + ) { + $this->unlockFile($path, ILockingProvider::LOCK_SHARED); + } + return $result; + } else { + $this->unlockFile($path, ILockingProvider::LOCK_SHARED); + } + } + return null; + } + + /** + * get the path relative to the default root for hook usage + * + * @param string $path + * @return string + */ + private function getHookPath($path) { + if (!Filesystem::getView()) { + return $path; + } + return Filesystem::getView()->getRelativePath($this->getAbsolutePath($path)); + } + + private function shouldEmitHooks($path = '') { + if ($path && Cache\Scanner::isPartialFile($path)) { + return false; + } + if (!Filesystem::$loaded) { + return false; + } + $defaultRoot = Filesystem::getRoot(); + if ($defaultRoot === null) { + return false; + } + if ($this->fakeRoot === $defaultRoot) { + return true; + } + $fullPath = $this->getAbsolutePath($path); + + if ($fullPath === $defaultRoot) { + return true; + } + + return (strlen($fullPath) > strlen($defaultRoot)) && (substr($fullPath, 0, strlen($defaultRoot) + 1) === $defaultRoot . '/'); + } + + /** + * @param string[] $hooks + * @param string $path + * @param bool $post + * @return bool + */ + private function runHooks($hooks, $path, $post = false) { + $relativePath = $path; + $path = $this->getHookPath($path); + $prefix = ($post) ? 'post_' : ''; + $run = true; + if ($this->shouldEmitHooks($relativePath)) { + foreach ($hooks as $hook) { + if ($hook != 'read') { + \OC_Hook::emit( + Filesystem::CLASSNAME, + $prefix . $hook, + array( + Filesystem::signal_param_run => &$run, + Filesystem::signal_param_path => $path + ) + ); + } elseif (!$post) { + \OC_Hook::emit( + Filesystem::CLASSNAME, + $prefix . $hook, + array( + Filesystem::signal_param_path => $path + ) + ); + } + } + } + return $run; + } + + /** + * check if a file or folder has been updated since $time + * + * @param string $path + * @param int $time + * @return bool + */ + public function hasUpdated($path, $time) { + return $this->basicOperation('hasUpdated', $path, array(), $time); + } + + /** + * @param string $ownerId + * @return \OC\User\User + */ + private function getUserObjectForOwner($ownerId) { + $owner = $this->userManager->get($ownerId); + if ($owner instanceof IUser) { + return $owner; + } else { + return new User($ownerId, null); + } + } + + /** + * Get file info from cache + * + * If the file is not in cached it will be scanned + * If the file has changed on storage the cache will be updated + * + * @param \OC\Files\Storage\Storage $storage + * @param string $internalPath + * @param string $relativePath + * @return array|bool + */ + private function getCacheEntry($storage, $internalPath, $relativePath) { + $cache = $storage->getCache($internalPath); + $data = $cache->get($internalPath); + $watcher = $storage->getWatcher($internalPath); + + try { + // if the file is not in the cache or needs to be updated, trigger the scanner and reload the data + if (!$data || $data['size'] === -1) { + $this->lockFile($relativePath, ILockingProvider::LOCK_SHARED); + if (!$storage->file_exists($internalPath)) { + $this->unlockFile($relativePath, ILockingProvider::LOCK_SHARED); + return false; + } + $scanner = $storage->getScanner($internalPath); + $scanner->scan($internalPath, Cache\Scanner::SCAN_SHALLOW); + $data = $cache->get($internalPath); + $this->unlockFile($relativePath, ILockingProvider::LOCK_SHARED); + } else if (!Cache\Scanner::isPartialFile($internalPath) && $watcher->needsUpdate($internalPath, $data)) { + $this->lockFile($relativePath, ILockingProvider::LOCK_SHARED); + $watcher->update($internalPath, $data); + $storage->getPropagator()->propagateChange($internalPath, time()); + $data = $cache->get($internalPath); + $this->unlockFile($relativePath, ILockingProvider::LOCK_SHARED); + } + } catch (LockedException $e) { + // if the file is locked we just use the old cache info + } + + return $data; + } + + /** + * get the filesystem info + * + * @param string $path + * @param boolean|string $includeMountPoints true to add mountpoint sizes, + * 'ext' to add only ext storage mount point sizes. Defaults to true. + * defaults to true + * @return \OC\Files\FileInfo|false False if file does not exist + */ + public function getFileInfo($path, $includeMountPoints = true) { + $this->assertPathLength($path); + if (!Filesystem::isValidPath($path)) { + return false; + } + if (Cache\Scanner::isPartialFile($path)) { + return $this->getPartFileInfo($path); + } + $relativePath = $path; + $path = Filesystem::normalizePath($this->fakeRoot . '/' . $path); + + $mount = Filesystem::getMountManager()->find($path); + $storage = $mount->getStorage(); + $internalPath = $mount->getInternalPath($path); + if ($storage) { + $data = $this->getCacheEntry($storage, $internalPath, $relativePath); + + if (!$data instanceof ICacheEntry) { + return false; + } + + if ($mount instanceof MoveableMount && $internalPath === '') { + $data['permissions'] |= \OCP\Constants::PERMISSION_DELETE; + } + + $owner = $this->getUserObjectForOwner($storage->getOwner($internalPath)); + $info = new FileInfo($path, $storage, $internalPath, $data, $mount, $owner); + + if ($data and isset($data['fileid'])) { + if ($includeMountPoints and $data['mimetype'] === 'httpd/unix-directory') { + //add the sizes of other mount points to the folder + $extOnly = ($includeMountPoints === 'ext'); + $mounts = Filesystem::getMountManager()->findIn($path); + foreach ($mounts as $mount) { + $subStorage = $mount->getStorage(); + if ($subStorage) { + // exclude shared storage ? + if ($extOnly && $subStorage instanceof \OC\Files\Storage\Shared) { + continue; + } + $subCache = $subStorage->getCache(''); + $rootEntry = $subCache->get(''); + $info->addSubEntry($rootEntry, $mount->getMountPoint()); + } + } + } + } + + return $info; + } + + return false; + } + + /** + * get the content of a directory + * + * @param string $directory path under datadirectory + * @param string $mimetype_filter limit returned content to this mimetype or mimepart + * @return FileInfo[] + */ + public function getDirectoryContent($directory, $mimetype_filter = '') { + $this->assertPathLength($directory); + if (!Filesystem::isValidPath($directory)) { + return []; + } + $path = $this->getAbsolutePath($directory); + $path = Filesystem::normalizePath($path); + $mount = $this->getMount($directory); + $storage = $mount->getStorage(); + $internalPath = $mount->getInternalPath($path); + if ($storage) { + $cache = $storage->getCache($internalPath); + $user = \OC_User::getUser(); + + $data = $this->getCacheEntry($storage, $internalPath, $directory); + + if (!$data instanceof ICacheEntry || !isset($data['fileid']) || !($data->getPermissions() && Constants::PERMISSION_READ)) { + return []; + } + + $folderId = $data['fileid']; + $contents = $cache->getFolderContentsById($folderId); //TODO: mimetype_filter + + $sharingDisabled = \OCP\Util::isSharingDisabledForUser(); + /** + * @var \OC\Files\FileInfo[] $files + */ + $files = array_map(function (ICacheEntry $content) use ($path, $storage, $mount, $sharingDisabled) { + if ($sharingDisabled) { + $content['permissions'] = $content['permissions'] & ~\OCP\Constants::PERMISSION_SHARE; + } + $owner = $this->getUserObjectForOwner($storage->getOwner($content['path'])); + return new FileInfo($path . '/' . $content['name'], $storage, $content['path'], $content, $mount, $owner); + }, $contents); + + //add a folder for any mountpoint in this directory and add the sizes of other mountpoints to the folders + $mounts = Filesystem::getMountManager()->findIn($path); + $dirLength = strlen($path); + foreach ($mounts as $mount) { + $mountPoint = $mount->getMountPoint(); + $subStorage = $mount->getStorage(); + if ($subStorage) { + $subCache = $subStorage->getCache(''); + + $rootEntry = $subCache->get(''); + if (!$rootEntry) { + $subScanner = $subStorage->getScanner(''); + try { + $subScanner->scanFile(''); + } catch (\OCP\Files\StorageNotAvailableException $e) { + continue; + } catch (\OCP\Files\StorageInvalidException $e) { + continue; + } catch (\Exception $e) { + // sometimes when the storage is not available it can be any exception + \OCP\Util::writeLog( + 'core', + 'Exception while scanning storage "' . $subStorage->getId() . '": ' . + get_class($e) . ': ' . $e->getMessage(), + \OCP\Util::ERROR + ); + continue; + } + $rootEntry = $subCache->get(''); + } + + if ($rootEntry && ($rootEntry->getPermissions() && Constants::PERMISSION_READ)) { + $relativePath = trim(substr($mountPoint, $dirLength), '/'); + if ($pos = strpos($relativePath, '/')) { + //mountpoint inside subfolder add size to the correct folder + $entryName = substr($relativePath, 0, $pos); + foreach ($files as &$entry) { + if ($entry->getName() === $entryName) { + $entry->addSubEntry($rootEntry, $mountPoint); + } + } + } else { //mountpoint in this folder, add an entry for it + $rootEntry['name'] = $relativePath; + $rootEntry['type'] = $rootEntry['mimetype'] === 'httpd/unix-directory' ? 'dir' : 'file'; + $permissions = $rootEntry['permissions']; + // do not allow renaming/deleting the mount point if they are not shared files/folders + // for shared files/folders we use the permissions given by the owner + if ($mount instanceof MoveableMount) { + $rootEntry['permissions'] = $permissions | \OCP\Constants::PERMISSION_UPDATE | \OCP\Constants::PERMISSION_DELETE; + } else { + $rootEntry['permissions'] = $permissions & (\OCP\Constants::PERMISSION_ALL - (\OCP\Constants::PERMISSION_UPDATE | \OCP\Constants::PERMISSION_DELETE)); + } + + //remove any existing entry with the same name + foreach ($files as $i => $file) { + if ($file['name'] === $rootEntry['name']) { + unset($files[$i]); + break; + } + } + $rootEntry['path'] = substr(Filesystem::normalizePath($path . '/' . $rootEntry['name']), strlen($user) + 2); // full path without /$user/ + + // if sharing was disabled for the user we remove the share permissions + if (\OCP\Util::isSharingDisabledForUser()) { + $rootEntry['permissions'] = $rootEntry['permissions'] & ~\OCP\Constants::PERMISSION_SHARE; + } + + $owner = $this->getUserObjectForOwner($subStorage->getOwner('')); + $files[] = new FileInfo($path . '/' . $rootEntry['name'], $subStorage, '', $rootEntry, $mount, $owner); + } + } + } + } + + if ($mimetype_filter) { + $files = array_filter($files, function (FileInfo $file) use ($mimetype_filter) { + if (strpos($mimetype_filter, '/')) { + return $file->getMimetype() === $mimetype_filter; + } else { + return $file->getMimePart() === $mimetype_filter; + } + }); + } + + return $files; + } else { + return []; + } + } + + /** + * change file metadata + * + * @param string $path + * @param array|\OCP\Files\FileInfo $data + * @return int + * + * returns the fileid of the updated file + */ + public function putFileInfo($path, $data) { + $this->assertPathLength($path); + if ($data instanceof FileInfo) { + $data = $data->getData(); + } + $path = Filesystem::normalizePath($this->fakeRoot . '/' . $path); + /** + * @var \OC\Files\Storage\Storage $storage + * @var string $internalPath + */ + list($storage, $internalPath) = Filesystem::resolvePath($path); + if ($storage) { + $cache = $storage->getCache($path); + + if (!$cache->inCache($internalPath)) { + $scanner = $storage->getScanner($internalPath); + $scanner->scan($internalPath, Cache\Scanner::SCAN_SHALLOW); + } + + return $cache->put($internalPath, $data); + } else { + return -1; + } + } + + /** + * search for files with the name matching $query + * + * @param string $query + * @return FileInfo[] + */ + public function search($query) { + return $this->searchCommon('search', array('%' . $query . '%')); + } + + /** + * search for files with the name matching $query + * + * @param string $query + * @return FileInfo[] + */ + public function searchRaw($query) { + return $this->searchCommon('search', array($query)); + } + + /** + * search for files by mimetype + * + * @param string $mimetype + * @return FileInfo[] + */ + public function searchByMime($mimetype) { + return $this->searchCommon('searchByMime', array($mimetype)); + } + + /** + * search for files by tag + * + * @param string|int $tag name or tag id + * @param string $userId owner of the tags + * @return FileInfo[] + */ + public function searchByTag($tag, $userId) { + return $this->searchCommon('searchByTag', array($tag, $userId)); + } + + /** + * @param string $method cache method + * @param array $args + * @return FileInfo[] + */ + private function searchCommon($method, $args) { + $files = array(); + $rootLength = strlen($this->fakeRoot); + + $mount = $this->getMount(''); + $mountPoint = $mount->getMountPoint(); + $storage = $mount->getStorage(); + if ($storage) { + $cache = $storage->getCache(''); + + $results = call_user_func_array(array($cache, $method), $args); + foreach ($results as $result) { + if (substr($mountPoint . $result['path'], 0, $rootLength + 1) === $this->fakeRoot . '/') { + $internalPath = $result['path']; + $path = $mountPoint . $result['path']; + $result['path'] = substr($mountPoint . $result['path'], $rootLength); + $owner = \OC::$server->getUserManager()->get($storage->getOwner($internalPath)); + $files[] = new FileInfo($path, $storage, $internalPath, $result, $mount, $owner); + } + } + + $mounts = Filesystem::getMountManager()->findIn($this->fakeRoot); + foreach ($mounts as $mount) { + $mountPoint = $mount->getMountPoint(); + $storage = $mount->getStorage(); + if ($storage) { + $cache = $storage->getCache(''); + + $relativeMountPoint = substr($mountPoint, $rootLength); + $results = call_user_func_array(array($cache, $method), $args); + if ($results) { + foreach ($results as $result) { + $internalPath = $result['path']; + $result['path'] = rtrim($relativeMountPoint . $result['path'], '/'); + $path = rtrim($mountPoint . $internalPath, '/'); + $owner = \OC::$server->getUserManager()->get($storage->getOwner($internalPath)); + $files[] = new FileInfo($path, $storage, $internalPath, $result, $mount, $owner); + } + } + } + } + } + return $files; + } + + /** + * Get the owner for a file or folder + * + * @param string $path + * @return string the user id of the owner + * @throws NotFoundException + */ + public function getOwner($path) { + $info = $this->getFileInfo($path); + if (!$info) { + throw new NotFoundException($path . ' not found while trying to get owner'); + } + return $info->getOwner()->getUID(); + } + + /** + * get the ETag for a file or folder + * + * @param string $path + * @return string + */ + public function getETag($path) { + /** + * @var Storage\Storage $storage + * @var string $internalPath + */ + list($storage, $internalPath) = $this->resolvePath($path); + if ($storage) { + return $storage->getETag($internalPath); + } else { + return null; + } + } + + /** + * Get the path of a file by id, relative to the view + * + * Note that the resulting path is not guarantied to be unique for the id, multiple paths can point to the same file + * + * @param int $id + * @throws NotFoundException + * @return string + */ + public function getPath($id) { + $id = (int)$id; + $manager = Filesystem::getMountManager(); + $mounts = $manager->findIn($this->fakeRoot); + $mounts[] = $manager->find($this->fakeRoot); + // reverse the array so we start with the storage this view is in + // which is the most likely to contain the file we're looking for + $mounts = array_reverse($mounts); + foreach ($mounts as $mount) { + /** + * @var \OC\Files\Mount\MountPoint $mount + */ + if ($mount->getStorage()) { + $cache = $mount->getStorage()->getCache(); + $internalPath = $cache->getPathById($id); + if (is_string($internalPath)) { + $fullPath = $mount->getMountPoint() . $internalPath; + if (!is_null($path = $this->getRelativePath($fullPath))) { + return $path; + } + } + } + } + throw new NotFoundException(sprintf('File with id "%s" has not been found.', $id)); + } + + /** + * @param string $path + * @throws InvalidPathException + */ + private function assertPathLength($path) { + $maxLen = min(PHP_MAXPATHLEN, 4000); + // Check for the string length - performed using isset() instead of strlen() + // because isset() is about 5x-40x faster. + if (isset($path[$maxLen])) { + $pathLen = strlen($path); + throw new \OCP\Files\InvalidPathException("Path length($pathLen) exceeds max path length($maxLen): $path"); + } + } + + /** + * check if it is allowed to move a mount point to a given target. + * It is not allowed to move a mount point into a different mount point or + * into an already shared folder + * + * @param string $target path + * @return boolean + */ + private function isTargetAllowed($target) { + + list($targetStorage, $targetInternalPath) = \OC\Files\Filesystem::resolvePath($target); + if (!$targetStorage->instanceOfStorage('\OCP\Files\IHomeStorage')) { + \OCP\Util::writeLog('files', + 'It is not allowed to move one mount point into another one', + \OCP\Util::DEBUG); + return false; + } + + // note: cannot use the view because the target is already locked + $fileId = (int)$targetStorage->getCache()->getId($targetInternalPath); + if ($fileId === -1) { + // target might not exist, need to check parent instead + $fileId = (int)$targetStorage->getCache()->getId(dirname($targetInternalPath)); + } + + // check if any of the parents were shared by the current owner (include collections) + $shares = \OCP\Share::getItemShared( + 'folder', + $fileId, + \OCP\Share::FORMAT_NONE, + null, + true + ); + + if (count($shares) > 0) { + \OCP\Util::writeLog('files', + 'It is not allowed to move one mount point into a shared folder', + \OCP\Util::DEBUG); + return false; + } + + return true; + } + + /** + * Get a fileinfo object for files that are ignored in the cache (part files) + * + * @param string $path + * @return \OCP\Files\FileInfo + */ + private function getPartFileInfo($path) { + $mount = $this->getMount($path); + $storage = $mount->getStorage(); + $internalPath = $mount->getInternalPath($this->getAbsolutePath($path)); + $owner = \OC::$server->getUserManager()->get($storage->getOwner($internalPath)); + return new FileInfo( + $this->getAbsolutePath($path), + $storage, + $internalPath, + [ + 'fileid' => null, + 'mimetype' => $storage->getMimeType($internalPath), + 'name' => basename($path), + 'etag' => null, + 'size' => $storage->filesize($internalPath), + 'mtime' => $storage->filemtime($internalPath), + 'encrypted' => false, + 'permissions' => \OCP\Constants::PERMISSION_ALL + ], + $mount, + $owner + ); + } + + /** + * @param string $path + * @param string $fileName + * @throws InvalidPathException + */ + public function verifyPath($path, $fileName) { + + $l10n = \OC::$server->getL10N('lib'); + + // verify empty and dot files + $trimmed = trim($fileName); + if ($trimmed === '') { + throw new InvalidPathException($l10n->t('Empty filename is not allowed')); + } + if (\OC\Files\Filesystem::isIgnoredDir($trimmed)) { + throw new InvalidPathException($l10n->t('Dot files are not allowed')); + } + + // verify database - e.g. mysql only 3-byte chars + if (preg_match('%(?: + \xF0[\x90-\xBF][\x80-\xBF]{2} # planes 1-3 + | [\xF1-\xF3][\x80-\xBF]{3} # planes 4-15 + | \xF4[\x80-\x8F][\x80-\xBF]{2} # plane 16 +)%xs', $fileName)) { + throw new InvalidPathException($l10n->t('4-byte characters are not supported in file names')); + } + + try { + /** @type \OCP\Files\Storage $storage */ + list($storage, $internalPath) = $this->resolvePath($path); + $storage->verifyPath($internalPath, $fileName); + } catch (ReservedWordException $ex) { + throw new InvalidPathException($l10n->t('File name is a reserved word')); + } catch (InvalidCharacterInPathException $ex) { + throw new InvalidPathException($l10n->t('File name contains at least one invalid character')); + } catch (FileNameTooLongException $ex) { + throw new InvalidPathException($l10n->t('File name is too long')); + } + } + + /** + * get all parent folders of $path + * + * @param string $path + * @return string[] + */ + private function getParents($path) { + $path = trim($path, '/'); + if (!$path) { + return []; + } + + $parts = explode('/', $path); + + // remove the single file + array_pop($parts); + $result = array('/'); + $resultPath = ''; + foreach ($parts as $part) { + if ($part) { + $resultPath .= '/' . $part; + $result[] = $resultPath; + } + } + return $result; + } + + /** + * Returns the mount point for which to lock + * + * @param string $absolutePath absolute path + * @param bool $useParentMount true to return parent mount instead of whatever + * is mounted directly on the given path, false otherwise + * @return \OC\Files\Mount\MountPoint mount point for which to apply locks + */ + private function getMountForLock($absolutePath, $useParentMount = false) { + $results = []; + $mount = Filesystem::getMountManager()->find($absolutePath); + if (!$mount) { + return $results; + } + + if ($useParentMount) { + // find out if something is mounted directly on the path + $internalPath = $mount->getInternalPath($absolutePath); + if ($internalPath === '') { + // resolve the parent mount instead + $mount = Filesystem::getMountManager()->find(dirname($absolutePath)); + } + } + + return $mount; + } + + /** + * Lock the given path + * + * @param string $path the path of the file to lock, relative to the view + * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE + * @param bool $lockMountPoint true to lock the mount point, false to lock the attached mount/storage + * + * @return bool False if the path is excluded from locking, true otherwise + * @throws \OCP\Lock\LockedException if the path is already locked + */ + private function lockPath($path, $type, $lockMountPoint = false) { + $absolutePath = $this->getAbsolutePath($path); + $absolutePath = Filesystem::normalizePath($absolutePath); + if (!$this->shouldLockFile($absolutePath)) { + return false; + } + + $mount = $this->getMountForLock($absolutePath, $lockMountPoint); + if ($mount) { + try { + $storage = $mount->getStorage(); + if ($storage->instanceOfStorage('\OCP\Files\Storage\ILockingStorage')) { + $storage->acquireLock( + $mount->getInternalPath($absolutePath), + $type, + $this->lockingProvider + ); + } + } catch (\OCP\Lock\LockedException $e) { + // rethrow with the a human-readable path + throw new \OCP\Lock\LockedException( + $this->getPathRelativeToFiles($absolutePath), + $e + ); + } + } + + return true; + } + + /** + * Change the lock type + * + * @param string $path the path of the file to lock, relative to the view + * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE + * @param bool $lockMountPoint true to lock the mount point, false to lock the attached mount/storage + * + * @return bool False if the path is excluded from locking, true otherwise + * @throws \OCP\Lock\LockedException if the path is already locked + */ + public function changeLock($path, $type, $lockMountPoint = false) { + $path = Filesystem::normalizePath($path); + $absolutePath = $this->getAbsolutePath($path); + $absolutePath = Filesystem::normalizePath($absolutePath); + if (!$this->shouldLockFile($absolutePath)) { + return false; + } + + $mount = $this->getMountForLock($absolutePath, $lockMountPoint); + if ($mount) { + try { + $storage = $mount->getStorage(); + if ($storage->instanceOfStorage('\OCP\Files\Storage\ILockingStorage')) { + $storage->changeLock( + $mount->getInternalPath($absolutePath), + $type, + $this->lockingProvider + ); + } + } catch (\OCP\Lock\LockedException $e) { + // rethrow with the a human-readable path + throw new \OCP\Lock\LockedException( + $this->getPathRelativeToFiles($absolutePath), + $e + ); + } + } + + return true; + } + + /** + * Unlock the given path + * + * @param string $path the path of the file to unlock, relative to the view + * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE + * @param bool $lockMountPoint true to lock the mount point, false to lock the attached mount/storage + * + * @return bool False if the path is excluded from locking, true otherwise + */ + private function unlockPath($path, $type, $lockMountPoint = false) { + $absolutePath = $this->getAbsolutePath($path); + $absolutePath = Filesystem::normalizePath($absolutePath); + if (!$this->shouldLockFile($absolutePath)) { + return false; + } + + $mount = $this->getMountForLock($absolutePath, $lockMountPoint); + if ($mount) { + $storage = $mount->getStorage(); + if ($storage->instanceOfStorage('\OCP\Files\Storage\ILockingStorage')) { + $storage->releaseLock( + $mount->getInternalPath($absolutePath), + $type, + $this->lockingProvider + ); + } + } + + return true; + } + + /** + * Lock a path and all its parents up to the root of the view + * + * @param string $path the path of the file to lock relative to the view + * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE + * @param bool $lockMountPoint true to lock the mount point, false to lock the attached mount/storage + * + * @return bool False if the path is excluded from locking, true otherwise + */ + public function lockFile($path, $type, $lockMountPoint = false) { + $absolutePath = $this->getAbsolutePath($path); + $absolutePath = Filesystem::normalizePath($absolutePath); + if (!$this->shouldLockFile($absolutePath)) { + return false; + } + + $this->lockPath($path, $type, $lockMountPoint); + + $parents = $this->getParents($path); + foreach ($parents as $parent) { + $this->lockPath($parent, ILockingProvider::LOCK_SHARED); + } + + return true; + } + + /** + * Unlock a path and all its parents up to the root of the view + * + * @param string $path the path of the file to lock relative to the view + * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE + * @param bool $lockMountPoint true to lock the mount point, false to lock the attached mount/storage + * + * @return bool False if the path is excluded from locking, true otherwise + */ + public function unlockFile($path, $type, $lockMountPoint = false) { + $absolutePath = $this->getAbsolutePath($path); + $absolutePath = Filesystem::normalizePath($absolutePath); + if (!$this->shouldLockFile($absolutePath)) { + return false; + } + + $this->unlockPath($path, $type, $lockMountPoint); + + $parents = $this->getParents($path); + foreach ($parents as $parent) { + $this->unlockPath($parent, ILockingProvider::LOCK_SHARED); + } + + return true; + } + + /** + * Only lock files in data/user/files/ + * + * @param string $path Absolute path to the file/folder we try to (un)lock + * @return bool + */ + protected function shouldLockFile($path) { + $path = Filesystem::normalizePath($path); + + $pathSegments = explode('/', $path); + if (isset($pathSegments[2])) { + // E.g.: /username/files/path-to-file + return ($pathSegments[2] === 'files') && (count($pathSegments) > 3); + } + + return true; + } + + /** + * Shortens the given absolute path to be relative to + * "$user/files". + * + * @param string $absolutePath absolute path which is under "files" + * + * @return string path relative to "files" with trimmed slashes or null + * if the path was NOT relative to files + * + * @throws \InvalidArgumentException if the given path was not under "files" + * @since 8.1.0 + */ + public function getPathRelativeToFiles($absolutePath) { + $path = Filesystem::normalizePath($absolutePath); + $parts = explode('/', trim($path, '/'), 3); + // "$user", "files", "path/to/dir" + if (!isset($parts[1]) || $parts[1] !== 'files') { + throw new \InvalidArgumentException('$absolutePath must be relative to "files"'); + } + if (isset($parts[2])) { + return $parts[2]; + } + return ''; + } + + /** + * @param string $filename + * @return array + * @throws \OC\User\NoUserException + * @throws NotFoundException + */ + public function getUidAndFilename($filename) { + $info = $this->getFileInfo($filename); + if (!$info instanceof \OCP\Files\FileInfo) { + throw new NotFoundException($this->getAbsolutePath($filename) . ' not found'); + } + $uid = $info->getOwner()->getUID(); + if ($uid != \OCP\User::getUser()) { + Filesystem::initMountPoints($uid); + $ownerView = new View('/' . $uid . '/files'); + try { + $filename = $ownerView->getPath($info['fileid']); + } catch (NotFoundException $e) { + throw new NotFoundException('File with id ' . $info['fileid'] . ' not found for user ' . $uid); + } + } + return [$uid, $filename]; + } +} diff --git a/lib/private/files/cache/cache.php b/lib/private/files/cache/cache.php deleted file mode 100644 index 53467c278d2..00000000000 --- a/lib/private/files/cache/cache.php +++ /dev/null @@ -1,837 +0,0 @@ - - * @author Björn Schießle - * @author Florin Peter - * @author Jens-Christian Fischer - * @author Joas Schilling - * @author Jörn Friedrich Dreyer - * @author Lukas Reschke - * @author Michael Gapczynski - * @author Morris Jobke - * @author Robin Appelman - * @author Robin McCorkell - * @author Roeland Jago Douma - * @author TheSFReader - * @author Thomas Müller - * @author Vincent Petry - * - * @copyright Copyright (c) 2016, ownCloud, Inc. - * @license AGPL-3.0 - * - * This code is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License, version 3, - * along with this program. If not, see - * - */ - -namespace OC\Files\Cache; - -use OCP\Files\Cache\ICache; -use OCP\Files\Cache\ICacheEntry; -use \OCP\Files\IMimeTypeLoader; -use OCP\IDBConnection; - -/** - * Metadata cache for a storage - * - * The cache stores the metadata for all files and folders in a storage and is kept up to date trough the following mechanisms: - * - * - Scanner: scans the storage and updates the cache where needed - * - Watcher: checks for changes made to the filesystem outside of the ownCloud instance and rescans files and folder when a change is detected - * - Updater: listens to changes made to the filesystem inside of the ownCloud instance and updates the cache where needed - * - ChangePropagator: updates the mtime and etags of parent folders whenever a change to the cache is made to the cache by the updater - */ -class Cache implements ICache { - use MoveFromCacheTrait { - MoveFromCacheTrait::moveFromCache as moveFromCacheFallback; - } - - /** - * @var array partial data for the cache - */ - protected $partial = array(); - - /** - * @var string - */ - protected $storageId; - - /** - * @var Storage $storageCache - */ - protected $storageCache; - - /** @var IMimeTypeLoader */ - protected $mimetypeLoader; - - /** - * @var IDBConnection - */ - protected $connection; - - /** - * @param \OC\Files\Storage\Storage|string $storage - */ - public function __construct($storage) { - if ($storage instanceof \OC\Files\Storage\Storage) { - $this->storageId = $storage->getId(); - } else { - $this->storageId = $storage; - } - if (strlen($this->storageId) > 64) { - $this->storageId = md5($this->storageId); - } - - $this->storageCache = new Storage($storage); - $this->mimetypeLoader = \OC::$server->getMimeTypeLoader(); - $this->connection = \OC::$server->getDatabaseConnection(); - } - - /** - * Get the numeric storage id for this cache's storage - * - * @return int - */ - public function getNumericStorageId() { - return $this->storageCache->getNumericId(); - } - - /** - * get the stored metadata of a file or folder - * - * @param string | int $file either the path of a file or folder or the file id for a file or folder - * @return ICacheEntry|false the cache entry as array of false if the file is not found in the cache - */ - public function get($file) { - if (is_string($file) or $file == '') { - // normalize file - $file = $this->normalize($file); - - $where = 'WHERE `storage` = ? AND `path_hash` = ?'; - $params = array($this->getNumericStorageId(), md5($file)); - } else { //file id - $where = 'WHERE `fileid` = ?'; - $params = array($file); - } - $sql = 'SELECT `fileid`, `storage`, `path`, `parent`, `name`, `mimetype`, `mimepart`, `size`, `mtime`, - `storage_mtime`, `encrypted`, `etag`, `permissions`, `checksum` - FROM `*PREFIX*filecache` ' . $where; - $result = $this->connection->executeQuery($sql, $params); - $data = $result->fetch(); - - //FIXME hide this HACK in the next database layer, or just use doctrine and get rid of MDB2 and PDO - //PDO returns false, MDB2 returns null, oracle always uses MDB2, so convert null to false - if ($data === null) { - $data = false; - } - - //merge partial data - if (!$data and is_string($file)) { - if (isset($this->partial[$file])) { - $data = $this->partial[$file]; - } - return $data; - } else { - //fix types - $data['fileid'] = (int)$data['fileid']; - $data['parent'] = (int)$data['parent']; - $data['size'] = 0 + $data['size']; - $data['mtime'] = (int)$data['mtime']; - $data['storage_mtime'] = (int)$data['storage_mtime']; - $data['encryptedVersion'] = (int)$data['encrypted']; - $data['encrypted'] = (bool)$data['encrypted']; - $data['storage'] = $this->storageId; - $data['mimetype'] = $this->mimetypeLoader->getMimetypeById($data['mimetype']); - $data['mimepart'] = $this->mimetypeLoader->getMimetypeById($data['mimepart']); - if ($data['storage_mtime'] == 0) { - $data['storage_mtime'] = $data['mtime']; - } - $data['permissions'] = (int)$data['permissions']; - return new CacheEntry($data); - } - } - - /** - * get the metadata of all files stored in $folder - * - * @param string $folder - * @return ICacheEntry[] - */ - public function getFolderContents($folder) { - $fileId = $this->getId($folder); - return $this->getFolderContentsById($fileId); - } - - /** - * get the metadata of all files stored in $folder - * - * @param int $fileId the file id of the folder - * @return ICacheEntry[] - */ - public function getFolderContentsById($fileId) { - if ($fileId > -1) { - $sql = 'SELECT `fileid`, `storage`, `path`, `parent`, `name`, `mimetype`, `mimepart`, `size`, `mtime`, - `storage_mtime`, `encrypted`, `etag`, `permissions`, `checksum` - FROM `*PREFIX*filecache` WHERE `parent` = ? ORDER BY `name` ASC'; - $result = $this->connection->executeQuery($sql, [$fileId]); - $files = $result->fetchAll(); - foreach ($files as &$file) { - $file['mimetype'] = $this->mimetypeLoader->getMimetypeById($file['mimetype']); - $file['mimepart'] = $this->mimetypeLoader->getMimetypeById($file['mimepart']); - if ($file['storage_mtime'] == 0) { - $file['storage_mtime'] = $file['mtime']; - } - $file['permissions'] = (int)$file['permissions']; - $file['mtime'] = (int)$file['mtime']; - $file['storage_mtime'] = (int)$file['storage_mtime']; - $file['size'] = 0 + $file['size']; - } - return array_map(function (array $data) { - return new CacheEntry($data); - }, $files); - } else { - return array(); - } - } - - /** - * insert or update meta data for a file or folder - * - * @param string $file - * @param array $data - * - * @return int file id - * @throws \RuntimeException - */ - public function put($file, array $data) { - if (($id = $this->getId($file)) > -1) { - $this->update($id, $data); - return $id; - } else { - return $this->insert($file, $data); - } - } - - /** - * insert meta data for a new file or folder - * - * @param string $file - * @param array $data - * - * @return int file id - * @throws \RuntimeException - */ - public function insert($file, array $data) { - // normalize file - $file = $this->normalize($file); - - if (isset($this->partial[$file])) { //add any saved partial data - $data = array_merge($this->partial[$file], $data); - unset($this->partial[$file]); - } - - $requiredFields = array('size', 'mtime', 'mimetype'); - foreach ($requiredFields as $field) { - if (!isset($data[$field])) { //data not complete save as partial and return - $this->partial[$file] = $data; - return -1; - } - } - - $data['path'] = $file; - $data['parent'] = $this->getParentId($file); - $data['name'] = \OC_Util::basename($file); - - list($queryParts, $params) = $this->buildParts($data); - $queryParts[] = '`storage`'; - $params[] = $this->getNumericStorageId(); - - $queryParts = array_map(function ($item) { - return trim($item, "`"); - }, $queryParts); - $values = array_combine($queryParts, $params); - if (\OC::$server->getDatabaseConnection()->insertIfNotExist('*PREFIX*filecache', $values, [ - 'storage', - 'path_hash', - ]) - ) { - return (int)$this->connection->lastInsertId('*PREFIX*filecache'); - } - - // The file was created in the mean time - if (($id = $this->getId($file)) > -1) { - $this->update($id, $data); - return $id; - } else { - throw new \RuntimeException('File entry could not be inserted with insertIfNotExist() but could also not be selected with getId() in order to perform an update. Please try again.'); - } - } - - /** - * update the metadata of an existing file or folder in the cache - * - * @param int $id the fileid of the existing file or folder - * @param array $data [$key => $value] the metadata to update, only the fields provided in the array will be updated, non-provided values will remain unchanged - */ - public function update($id, array $data) { - - if (isset($data['path'])) { - // normalize path - $data['path'] = $this->normalize($data['path']); - } - - if (isset($data['name'])) { - // normalize path - $data['name'] = $this->normalize($data['name']); - } - - list($queryParts, $params) = $this->buildParts($data); - // duplicate $params because we need the parts twice in the SQL statement - // once for the SET part, once in the WHERE clause - $params = array_merge($params, $params); - $params[] = $id; - - // don't update if the data we try to set is the same as the one in the record - // some databases (Postgres) don't like superfluous updates - $sql = 'UPDATE `*PREFIX*filecache` SET ' . implode(' = ?, ', $queryParts) . '=? ' . - 'WHERE (' . - implode(' <> ? OR ', $queryParts) . ' <> ? OR ' . - implode(' IS NULL OR ', $queryParts) . ' IS NULL' . - ') AND `fileid` = ? '; - $this->connection->executeQuery($sql, $params); - - } - - /** - * extract query parts and params array from data array - * - * @param array $data - * @return array [$queryParts, $params] - * $queryParts: string[], the (escaped) column names to be set in the query - * $params: mixed[], the new values for the columns, to be passed as params to the query - */ - protected function buildParts(array $data) { - $fields = array( - 'path', 'parent', 'name', 'mimetype', 'size', 'mtime', 'storage_mtime', 'encrypted', - 'etag', 'permissions', 'checksum'); - - $doNotCopyStorageMTime = false; - if (array_key_exists('mtime', $data) && $data['mtime'] === null) { - // this horrific magic tells it to not copy storage_mtime to mtime - unset($data['mtime']); - $doNotCopyStorageMTime = true; - } - - $params = array(); - $queryParts = array(); - foreach ($data as $name => $value) { - if (array_search($name, $fields) !== false) { - if ($name === 'path') { - $params[] = md5($value); - $queryParts[] = '`path_hash`'; - } elseif ($name === 'mimetype') { - $params[] = $this->mimetypeLoader->getId(substr($value, 0, strpos($value, '/'))); - $queryParts[] = '`mimepart`'; - $value = $this->mimetypeLoader->getId($value); - } elseif ($name === 'storage_mtime') { - if (!$doNotCopyStorageMTime && !isset($data['mtime'])) { - $params[] = $value; - $queryParts[] = '`mtime`'; - } - } elseif ($name === 'encrypted') { - if(isset($data['encryptedVersion'])) { - $value = $data['encryptedVersion']; - } else { - // Boolean to integer conversion - $value = $value ? 1 : 0; - } - } - $params[] = $value; - $queryParts[] = '`' . $name . '`'; - } - } - return array($queryParts, $params); - } - - /** - * get the file id for a file - * - * A file id is a numeric id for a file or folder that's unique within an owncloud instance which stays the same for the lifetime of a file - * - * File ids are easiest way for apps to store references to a file since unlike paths they are not affected by renames or sharing - * - * @param string $file - * @return int - */ - public function getId($file) { - // normalize file - $file = $this->normalize($file); - - $pathHash = md5($file); - - $sql = 'SELECT `fileid` FROM `*PREFIX*filecache` WHERE `storage` = ? AND `path_hash` = ?'; - $result = $this->connection->executeQuery($sql, array($this->getNumericStorageId(), $pathHash)); - if ($row = $result->fetch()) { - return $row['fileid']; - } else { - return -1; - } - } - - /** - * get the id of the parent folder of a file - * - * @param string $file - * @return int - */ - public function getParentId($file) { - if ($file === '') { - return -1; - } else { - $parent = $this->getParentPath($file); - return (int)$this->getId($parent); - } - } - - private function getParentPath($path) { - $parent = dirname($path); - if ($parent === '.') { - $parent = ''; - } - return $parent; - } - - /** - * check if a file is available in the cache - * - * @param string $file - * @return bool - */ - public function inCache($file) { - return $this->getId($file) != -1; - } - - /** - * remove a file or folder from the cache - * - * when removing a folder from the cache all files and folders inside the folder will be removed as well - * - * @param string $file - */ - public function remove($file) { - $entry = $this->get($file); - $sql = 'DELETE FROM `*PREFIX*filecache` WHERE `fileid` = ?'; - $this->connection->executeQuery($sql, array($entry['fileid'])); - if ($entry['mimetype'] === 'httpd/unix-directory') { - $this->removeChildren($entry); - } - } - - /** - * Get all sub folders of a folder - * - * @param array $entry the cache entry of the folder to get the subfolders for - * @return array[] the cache entries for the subfolders - */ - private function getSubFolders($entry) { - $children = $this->getFolderContentsById($entry['fileid']); - return array_filter($children, function ($child) { - return $child['mimetype'] === 'httpd/unix-directory'; - }); - } - - /** - * Recursively remove all children of a folder - * - * @param array $entry the cache entry of the folder to remove the children of - * @throws \OC\DatabaseException - */ - private function removeChildren($entry) { - $subFolders = $this->getSubFolders($entry); - foreach ($subFolders as $folder) { - $this->removeChildren($folder); - } - $sql = 'DELETE FROM `*PREFIX*filecache` WHERE `parent` = ?'; - $this->connection->executeQuery($sql, array($entry['fileid'])); - } - - /** - * Move a file or folder in the cache - * - * @param string $source - * @param string $target - */ - public function move($source, $target) { - $this->moveFromCache($this, $source, $target); - } - - /** - * Get the storage id and path needed for a move - * - * @param string $path - * @return array [$storageId, $internalPath] - */ - protected function getMoveInfo($path) { - return [$this->getNumericStorageId(), $path]; - } - - /** - * Move a file or folder in the cache - * - * @param \OCP\Files\Cache\ICache $sourceCache - * @param string $sourcePath - * @param string $targetPath - * @throws \OC\DatabaseException - */ - public function moveFromCache(ICache $sourceCache, $sourcePath, $targetPath) { - if ($sourceCache instanceof Cache) { - // normalize source and target - $sourcePath = $this->normalize($sourcePath); - $targetPath = $this->normalize($targetPath); - - $sourceData = $sourceCache->get($sourcePath); - $sourceId = $sourceData['fileid']; - $newParentId = $this->getParentId($targetPath); - - list($sourceStorageId, $sourcePath) = $sourceCache->getMoveInfo($sourcePath); - list($targetStorageId, $targetPath) = $this->getMoveInfo($targetPath); - - // sql for final update - $moveSql = 'UPDATE `*PREFIX*filecache` SET `storage` = ?, `path` = ?, `path_hash` = ?, `name` = ?, `parent` =? WHERE `fileid` = ?'; - - if ($sourceData['mimetype'] === 'httpd/unix-directory') { - //find all child entries - $sql = 'SELECT `path`, `fileid` FROM `*PREFIX*filecache` WHERE `storage` = ? AND `path` LIKE ?'; - $result = $this->connection->executeQuery($sql, [$sourceStorageId, $this->connection->escapeLikeParameter($sourcePath) . '/%']); - $childEntries = $result->fetchAll(); - $sourceLength = strlen($sourcePath); - $this->connection->beginTransaction(); - $query = $this->connection->prepare('UPDATE `*PREFIX*filecache` SET `storage` = ?, `path` = ?, `path_hash` = ? WHERE `fileid` = ?'); - - foreach ($childEntries as $child) { - $newTargetPath = $targetPath . substr($child['path'], $sourceLength); - $query->execute([$targetStorageId, $newTargetPath, md5($newTargetPath), $child['fileid']]); - } - $this->connection->executeQuery($moveSql, [$targetStorageId, $targetPath, md5($targetPath), basename($targetPath), $newParentId, $sourceId]); - $this->connection->commit(); - } else { - $this->connection->executeQuery($moveSql, [$targetStorageId, $targetPath, md5($targetPath), basename($targetPath), $newParentId, $sourceId]); - } - } else { - $this->moveFromCacheFallback($sourceCache, $sourcePath, $targetPath); - } - } - - /** - * remove all entries for files that are stored on the storage from the cache - */ - public function clear() { - $sql = 'DELETE FROM `*PREFIX*filecache` WHERE `storage` = ?'; - $this->connection->executeQuery($sql, array($this->getNumericStorageId())); - - $sql = 'DELETE FROM `*PREFIX*storages` WHERE `id` = ?'; - $this->connection->executeQuery($sql, array($this->storageId)); - } - - /** - * Get the scan status of a file - * - * - Cache::NOT_FOUND: File is not in the cache - * - Cache::PARTIAL: File is not stored in the cache but some incomplete data is known - * - Cache::SHALLOW: The folder and it's direct children are in the cache but not all sub folders are fully scanned - * - Cache::COMPLETE: The file or folder, with all it's children) are fully scanned - * - * @param string $file - * - * @return int Cache::NOT_FOUND, Cache::PARTIAL, Cache::SHALLOW or Cache::COMPLETE - */ - public function getStatus($file) { - // normalize file - $file = $this->normalize($file); - - $pathHash = md5($file); - $sql = 'SELECT `size` FROM `*PREFIX*filecache` WHERE `storage` = ? AND `path_hash` = ?'; - $result = $this->connection->executeQuery($sql, array($this->getNumericStorageId(), $pathHash)); - if ($row = $result->fetch()) { - if ((int)$row['size'] === -1) { - return self::SHALLOW; - } else { - return self::COMPLETE; - } - } else { - if (isset($this->partial[$file])) { - return self::PARTIAL; - } else { - return self::NOT_FOUND; - } - } - } - - /** - * search for files matching $pattern - * - * @param string $pattern the search pattern using SQL search syntax (e.g. '%searchstring%') - * @return ICacheEntry[] an array of cache entries where the name matches the search pattern - */ - public function search($pattern) { - // normalize pattern - $pattern = $this->normalize($pattern); - - - $sql = ' - SELECT `fileid`, `storage`, `path`, `parent`, `name`, - `mimetype`, `mimepart`, `size`, `mtime`, `encrypted`, - `etag`, `permissions`, `checksum` - FROM `*PREFIX*filecache` - WHERE `storage` = ? AND `name` ILIKE ?'; - $result = $this->connection->executeQuery($sql, - [$this->getNumericStorageId(), $pattern] - ); - - $files = []; - while ($row = $result->fetch()) { - $row['mimetype'] = $this->mimetypeLoader->getMimetypeById($row['mimetype']); - $row['mimepart'] = $this->mimetypeLoader->getMimetypeById($row['mimepart']); - $files[] = $row; - } - return array_map(function(array $data) { - return new CacheEntry($data); - }, $files); - } - - /** - * search for files by mimetype - * - * @param string $mimetype either a full mimetype to search ('text/plain') or only the first part of a mimetype ('image') - * where it will search for all mimetypes in the group ('image/*') - * @return ICacheEntry[] an array of cache entries where the mimetype matches the search - */ - public function searchByMime($mimetype) { - if (strpos($mimetype, '/')) { - $where = '`mimetype` = ?'; - } else { - $where = '`mimepart` = ?'; - } - $sql = 'SELECT `fileid`, `storage`, `path`, `parent`, `name`, `mimetype`, `mimepart`, `size`, `mtime`, `encrypted`, `etag`, `permissions`, `checksum` - FROM `*PREFIX*filecache` WHERE ' . $where . ' AND `storage` = ?'; - $mimetype = $this->mimetypeLoader->getId($mimetype); - $result = $this->connection->executeQuery($sql, array($mimetype, $this->getNumericStorageId())); - $files = array(); - while ($row = $result->fetch()) { - $row['mimetype'] = $this->mimetypeLoader->getMimetypeById($row['mimetype']); - $row['mimepart'] = $this->mimetypeLoader->getMimetypeById($row['mimepart']); - $files[] = $row; - } - return array_map(function (array $data) { - return new CacheEntry($data); - }, $files); - } - - /** - * Search for files by tag of a given users. - * - * Note that every user can tag files differently. - * - * @param string|int $tag name or tag id - * @param string $userId owner of the tags - * @return ICacheEntry[] file data - */ - public function searchByTag($tag, $userId) { - $sql = 'SELECT `fileid`, `storage`, `path`, `parent`, `name`, ' . - '`mimetype`, `mimepart`, `size`, `mtime`, ' . - '`encrypted`, `etag`, `permissions`, `checksum` ' . - 'FROM `*PREFIX*filecache` `file`, ' . - '`*PREFIX*vcategory_to_object` `tagmap`, ' . - '`*PREFIX*vcategory` `tag` ' . - // JOIN filecache to vcategory_to_object - 'WHERE `file`.`fileid` = `tagmap`.`objid` ' . - // JOIN vcategory_to_object to vcategory - 'AND `tagmap`.`type` = `tag`.`type` ' . - 'AND `tagmap`.`categoryid` = `tag`.`id` ' . - // conditions - 'AND `file`.`storage` = ? ' . - 'AND `tag`.`type` = \'files\' ' . - 'AND `tag`.`uid` = ? '; - if (is_int($tag)) { - $sql .= 'AND `tag`.`id` = ? '; - } else { - $sql .= 'AND `tag`.`category` = ? '; - } - $result = $this->connection->executeQuery( - $sql, - [ - $this->getNumericStorageId(), - $userId, - $tag - ] - ); - $files = array(); - while ($row = $result->fetch()) { - $files[] = $row; - } - return array_map(function (array $data) { - return new CacheEntry($data); - }, $files); - } - - /** - * Re-calculate the folder size and the size of all parent folders - * - * @param string|boolean $path - * @param array $data (optional) meta data of the folder - */ - public function correctFolderSize($path, $data = null) { - $this->calculateFolderSize($path, $data); - if ($path !== '') { - $parent = dirname($path); - if ($parent === '.' or $parent === '/') { - $parent = ''; - } - $this->correctFolderSize($parent); - } - } - - /** - * calculate the size of a folder and set it in the cache - * - * @param string $path - * @param array $entry (optional) meta data of the folder - * @return int - */ - public function calculateFolderSize($path, $entry = null) { - $totalSize = 0; - if (is_null($entry) or !isset($entry['fileid'])) { - $entry = $this->get($path); - } - if (isset($entry['mimetype']) && $entry['mimetype'] === 'httpd/unix-directory') { - $id = $entry['fileid']; - $sql = 'SELECT SUM(`size`) AS f1, MIN(`size`) AS f2 ' . - 'FROM `*PREFIX*filecache` ' . - 'WHERE `parent` = ? AND `storage` = ?'; - $result = $this->connection->executeQuery($sql, array($id, $this->getNumericStorageId())); - if ($row = $result->fetch()) { - $result->closeCursor(); - list($sum, $min) = array_values($row); - $sum = 0 + $sum; - $min = 0 + $min; - if ($min === -1) { - $totalSize = $min; - } else { - $totalSize = $sum; - } - $update = array(); - if ($entry['size'] !== $totalSize) { - $update['size'] = $totalSize; - } - if (count($update) > 0) { - $this->update($id, $update); - } - } else { - $result->closeCursor(); - } - } - return $totalSize; - } - - /** - * get all file ids on the files on the storage - * - * @return int[] - */ - public function getAll() { - $sql = 'SELECT `fileid` FROM `*PREFIX*filecache` WHERE `storage` = ?'; - $result = $this->connection->executeQuery($sql, array($this->getNumericStorageId())); - $ids = array(); - while ($row = $result->fetch()) { - $ids[] = $row['fileid']; - } - return $ids; - } - - /** - * find a folder in the cache which has not been fully scanned - * - * If multiple incomplete folders are in the cache, the one with the highest id will be returned, - * use the one with the highest id gives the best result with the background scanner, since that is most - * likely the folder where we stopped scanning previously - * - * @return string|bool the path of the folder or false when no folder matched - */ - public function getIncomplete() { - $query = $this->connection->prepare('SELECT `path` FROM `*PREFIX*filecache`' - . ' WHERE `storage` = ? AND `size` = -1 ORDER BY `fileid` DESC', 1); - $query->execute([$this->getNumericStorageId()]); - if ($row = $query->fetch()) { - return $row['path']; - } else { - return false; - } - } - - /** - * get the path of a file on this storage by it's file id - * - * @param int $id the file id of the file or folder to search - * @return string|null the path of the file (relative to the storage) or null if a file with the given id does not exists within this cache - */ - public function getPathById($id) { - $sql = 'SELECT `path` FROM `*PREFIX*filecache` WHERE `fileid` = ? AND `storage` = ?'; - $result = $this->connection->executeQuery($sql, array($id, $this->getNumericStorageId())); - if ($row = $result->fetch()) { - // Oracle stores empty strings as null... - if ($row['path'] === null) { - return ''; - } - return $row['path']; - } else { - return null; - } - } - - /** - * get the storage id of the storage for a file and the internal path of the file - * unlike getPathById this does not limit the search to files on this storage and - * instead does a global search in the cache table - * - * @param int $id - * @deprecated use getPathById() instead - * @return array first element holding the storage id, second the path - */ - static public function getById($id) { - $connection = \OC::$server->getDatabaseConnection(); - $sql = 'SELECT `storage`, `path` FROM `*PREFIX*filecache` WHERE `fileid` = ?'; - $result = $connection->executeQuery($sql, array($id)); - if ($row = $result->fetch()) { - $numericId = $row['storage']; - $path = $row['path']; - } else { - return null; - } - - if ($id = Storage::getStorageId($numericId)) { - return array($id, $path); - } else { - return null; - } - } - - /** - * normalize the given path - * - * @param string $path - * @return string - */ - public function normalize($path) { - - return trim(\OC_Util::normalizeUnicode($path), '/'); - } -} diff --git a/lib/private/files/cache/cacheentry.php b/lib/private/files/cache/cacheentry.php deleted file mode 100644 index 6d3c5d5b089..00000000000 --- a/lib/private/files/cache/cacheentry.php +++ /dev/null @@ -1,114 +0,0 @@ - - * - * @copyright Copyright (c) 2016, ownCloud, Inc. - * @license AGPL-3.0 - * - * This code is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License, version 3, - * along with this program. If not, see - * - */ - -namespace OC\Files\Cache; - -use OCP\Files\Cache\ICacheEntry; - -/** - * meta data for a file or folder - */ -class CacheEntry implements ICacheEntry, \ArrayAccess { - /** - * @var array - */ - private $data; - - public function __construct(array $data) { - $this->data = $data; - } - - public function offsetSet($offset, $value) { - $this->data[$offset] = $value; - } - - public function offsetExists($offset) { - return isset($this->data[$offset]); - } - - public function offsetUnset($offset) { - unset($this->data[$offset]); - } - - public function offsetGet($offset) { - if (isset($this->data[$offset])) { - return $this->data[$offset]; - } else { - return null; - } - } - - public function getId() { - return (int)$this->data['fileid']; - } - - public function getStorageId() { - return $this->data['storage']; - } - - - public function getPath() { - return $this->data['path']; - } - - - public function getName() { - return $this->data['name']; - } - - - public function getMimeType() { - return $this->data['mimetype']; - } - - - public function getMimePart() { - return $this->data['mimepart']; - } - - public function getSize() { - return $this->data['size']; - } - - public function getMTime() { - return $this->data['mtime']; - } - - public function getStorageMTime() { - return $this->data['storage_mtime']; - } - - public function getEtag() { - return $this->data['etag']; - } - - public function getPermissions() { - return $this->data['permissions']; - } - - public function isEncrypted() { - return isset($this->data['encrypted']) && $this->data['encrypted']; - } - - public function getData() { - return $this->data; - } -} diff --git a/lib/private/files/cache/failedcache.php b/lib/private/files/cache/failedcache.php deleted file mode 100644 index 0386ba3ca32..00000000000 --- a/lib/private/files/cache/failedcache.php +++ /dev/null @@ -1,142 +0,0 @@ - - * - * @copyright Copyright (c) 2016, ownCloud, Inc. - * @license AGPL-3.0 - * - * This code is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License, version 3, - * along with this program. If not, see - * - */ - -namespace OC\Files\Cache; - -use OCP\Constants; -use OCP\Files\Cache\ICache; - -/** - * Storage placeholder to represent a missing precondition, storage unavailable - */ -class FailedCache implements ICache { - /** @var bool whether to show the failed storage in the ui */ - private $visible; - - /** - * FailedCache constructor. - * - * @param bool $visible - */ - public function __construct($visible = true) { - $this->visible = $visible; - } - - - public function getNumericStorageId() { - return -1; - } - - public function get($file) { - if ($file === '') { - return new CacheEntry([ - 'fileid' => -1, - 'size' => 0, - 'mimetype' => 'httpd/unix-directory', - 'mimepart' => 'httpd', - 'permissions' => $this->visible ? Constants::PERMISSION_READ : 0, - 'mtime' => time() - ]); - } else { - return false; - } - } - - public function getFolderContents($folder) { - return []; - } - - public function getFolderContentsById($fileId) { - return []; - } - - public function put($file, array $data) { - return; - } - - public function insert($file, array $data) { - return; - } - - public function update($id, array $data) { - return; - } - - public function getId($file) { - return -1; - } - - public function getParentId($file) { - return -1; - } - - public function inCache($file) { - return false; - } - - public function remove($file) { - return; - } - - public function move($source, $target) { - return; - } - - public function moveFromCache(ICache $sourceCache, $sourcePath, $targetPath) { - return; - } - - public function clear() { - return; - } - - public function getStatus($file) { - return ICache::NOT_FOUND; - } - - public function search($pattern) { - return []; - } - - public function searchByMime($mimetype) { - return []; - } - - public function searchByTag($tag, $userId) { - return []; - } - - public function getAll() { - return []; - } - - public function getIncomplete() { - return []; - } - - public function getPathById($id) { - return null; - } - - public function normalize($path) { - return $path; - } -} diff --git a/lib/private/files/cache/homecache.php b/lib/private/files/cache/homecache.php deleted file mode 100644 index ae92504ddd6..00000000000 --- a/lib/private/files/cache/homecache.php +++ /dev/null @@ -1,86 +0,0 @@ - - * @author Björn Schießle - * @author Jörn Friedrich Dreyer - * @author Morris Jobke - * @author Robin Appelman - * @author Vincent Petry - * - * @copyright Copyright (c) 2016, ownCloud, Inc. - * @license AGPL-3.0 - * - * This code is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License, version 3, - * along with this program. If not, see - * - */ - -namespace OC\Files\Cache; - -use OCP\Files\Cache\ICacheEntry; - -class HomeCache extends Cache { - /** - * get the size of a folder and set it in the cache - * - * @param string $path - * @param array $entry (optional) meta data of the folder - * @return int - */ - public function calculateFolderSize($path, $entry = null) { - if ($path !== '/' and $path !== '' and $path !== 'files' and $path !== 'files_trashbin' and $path !== 'files_versions') { - return parent::calculateFolderSize($path, $entry); - } elseif ($path === '' or $path === '/') { - // since the size of / isn't used (the size of /files is used instead) there is no use in calculating it - return 0; - } - - $totalSize = 0; - if (is_null($entry)) { - $entry = $this->get($path); - } - if ($entry && $entry['mimetype'] === 'httpd/unix-directory') { - $id = $entry['fileid']; - $sql = 'SELECT SUM(`size`) AS f1 ' . - 'FROM `*PREFIX*filecache` ' . - 'WHERE `parent` = ? AND `storage` = ? AND `size` >= 0'; - $result = \OC_DB::executeAudited($sql, array($id, $this->getNumericStorageId())); - if ($row = $result->fetchRow()) { - $result->closeCursor(); - list($sum) = array_values($row); - $totalSize = 0 + $sum; - $entry['size'] += 0; - if ($entry['size'] !== $totalSize) { - $this->update($id, array('size' => $totalSize)); - } - } - } - return $totalSize; - } - - /** - * @param string $path - * @return ICacheEntry - */ - public function get($path) { - $data = parent::get($path); - if ($path === '' or $path === '/') { - // only the size of the "files" dir counts - $filesData = parent::get('files'); - - if (isset($filesData['size'])) { - $data['size'] = $filesData['size']; - } - } - return $data; - } -} diff --git a/lib/private/files/cache/homepropagator.php b/lib/private/files/cache/homepropagator.php deleted file mode 100644 index 8edca9c0c87..00000000000 --- a/lib/private/files/cache/homepropagator.php +++ /dev/null @@ -1,50 +0,0 @@ - - * - * @copyright Copyright (c) 2016, ownCloud, Inc. - * @license AGPL-3.0 - * - * This code is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License, version 3, - * along with this program. If not, see - * - */ - -namespace OC\Files\Cache; - -class HomePropagator extends Propagator { - private $ignoredBaseFolders; - - /** - * @param \OC\Files\Storage\Storage $storage - */ - public function __construct(\OC\Files\Storage\Storage $storage) { - parent::__construct($storage); - $this->ignoredBaseFolders = ['files_encryption']; - } - - - /** - * @param string $internalPath - * @param int $time - * @param int $sizeDifference number of bytes the file has grown - * @return array[] all propagated entries - */ - public function propagateChange($internalPath, $time, $sizeDifference = 0) { - list($baseFolder) = explode('/', $internalPath, 2); - if (in_array($baseFolder, $this->ignoredBaseFolders)) { - return []; - } else { - return parent::propagateChange($internalPath, $time, $sizeDifference); - } - } -} diff --git a/lib/private/files/cache/movefromcachetrait.php b/lib/private/files/cache/movefromcachetrait.php deleted file mode 100644 index 7d8ed7b5d21..00000000000 --- a/lib/private/files/cache/movefromcachetrait.php +++ /dev/null @@ -1,87 +0,0 @@ - - * - * @copyright Copyright (c) 2016, ownCloud, Inc. - * @license AGPL-3.0 - * - * This code is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License, version 3, - * along with this program. If not, see - * - */ - -namespace OC\Files\Cache; - -use OCP\Files\Cache\ICache; -use OCP\Files\Cache\ICacheEntry; - -/** - * Fallback implementation for moveFromCache - */ -trait MoveFromCacheTrait { - /** - * store meta data for a file or folder - * - * @param string $file - * @param array $data - * - * @return int file id - * @throws \RuntimeException - */ - abstract public function put($file, array $data); - - /** - * Move a file or folder in the cache - * - * @param \OCP\Files\Cache\ICache $sourceCache - * @param string $sourcePath - * @param string $targetPath - */ - public function moveFromCache(ICache $sourceCache, $sourcePath, $targetPath) { - $sourceEntry = $sourceCache->get($sourcePath); - - $this->copyFromCache($sourceCache, $sourceEntry, $targetPath); - - $sourceCache->remove($sourcePath); - } - - /** - * Copy a file or folder in the cache - * - * @param \OCP\Files\Cache\ICache $sourceCache - * @param ICacheEntry $sourceEntry - * @param string $targetPath - */ - public function copyFromCache(ICache $sourceCache, ICacheEntry $sourceEntry, $targetPath) { - $this->put($targetPath, $this->cacheEntryToArray($sourceEntry)); - if ($sourceEntry->getMimeType() === ICacheEntry::DIRECTORY_MIMETYPE) { - $folderContent = $sourceCache->getFolderContentsById($sourceEntry->getId()); - foreach ($folderContent as $subEntry) { - $subTargetPath = $targetPath . '/' . $subEntry->getName(); - $this->copyFromCache($sourceCache, $subEntry, $subTargetPath); - } - } - } - - private function cacheEntryToArray(ICacheEntry $entry) { - return [ - 'size' => $entry->getSize(), - 'mtime' => $entry->getMTime(), - 'storage_mtime' => $entry->getStorageMTime(), - 'mimetype' => $entry->getMimeType(), - 'mimepart' => $entry->getMimePart(), - 'etag' => $entry->getEtag(), - 'permissions' => $entry->getPermissions(), - 'encrypted' => $entry->isEncrypted() - ]; - } -} diff --git a/lib/private/files/cache/propagator.php b/lib/private/files/cache/propagator.php deleted file mode 100644 index 50264e54d44..00000000000 --- a/lib/private/files/cache/propagator.php +++ /dev/null @@ -1,74 +0,0 @@ - - * - * @copyright Copyright (c) 2016, ownCloud, Inc. - * @license AGPL-3.0 - * - * This code is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License, version 3, - * along with this program. If not, see - * - */ - -namespace OC\Files\Cache; - -use OCP\Files\Cache\IPropagator; - -/** - * Propagate etags and mtimes within the storage - */ -class Propagator implements IPropagator { - /** - * @var \OC\Files\Storage\Storage - */ - protected $storage; - - /** - * @param \OC\Files\Storage\Storage $storage - */ - public function __construct(\OC\Files\Storage\Storage $storage) { - $this->storage = $storage; - } - - - /** - * @param string $internalPath - * @param int $time - * @param int $sizeDifference number of bytes the file has grown - * @return array[] all propagated entries - */ - public function propagateChange($internalPath, $time, $sizeDifference = 0) { - $cache = $this->storage->getCache($internalPath); - - $parentId = $cache->getParentId($internalPath); - $propagatedEntries = []; - while ($parentId !== -1) { - $entry = $cache->get($parentId); - $propagatedEntries[] = $entry; - if (!$entry) { - return $propagatedEntries; - } - $mtime = max($time, $entry['mtime']); - - if ($entry['size'] === -1) { - $newSize = -1; - } else { - $newSize = $entry['size'] + $sizeDifference; - } - $cache->update($parentId, ['mtime' => $mtime, 'etag' => $this->storage->getETag($entry['path']), 'size' => $newSize]); - - $parentId = $entry['parent']; - } - - return $propagatedEntries; - } -} diff --git a/lib/private/files/cache/scanner.php b/lib/private/files/cache/scanner.php deleted file mode 100644 index 8730707f1c2..00000000000 --- a/lib/private/files/cache/scanner.php +++ /dev/null @@ -1,503 +0,0 @@ - - * @author Björn Schießle - * @author Joas Schilling - * @author Jörn Friedrich Dreyer - * @author Lukas Reschke - * @author Martin Mattel - * @author Michael Gapczynski - * @author Morris Jobke - * @author Owen Winkler - * @author Robin Appelman - * @author Robin McCorkell - * @author Thomas Müller - * @author Vincent Petry - * - * @copyright Copyright (c) 2016, ownCloud, Inc. - * @license AGPL-3.0 - * - * This code is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License, version 3, - * along with this program. If not, see - * - */ - -namespace OC\Files\Cache; - -use OC\Files\Filesystem; -use OC\Hooks\BasicEmitter; -use OCP\Config; -use OCP\Files\Cache\IScanner; -use OCP\Files\Storage\ILockingStorage; -use OCP\Lock\ILockingProvider; - -/** - * Class Scanner - * - * Hooks available in scope \OC\Files\Cache\Scanner: - * - scanFile(string $path, string $storageId) - * - scanFolder(string $path, string $storageId) - * - postScanFile(string $path, string $storageId) - * - postScanFolder(string $path, string $storageId) - * - * @package OC\Files\Cache - */ -class Scanner extends BasicEmitter implements IScanner { - /** - * @var \OC\Files\Storage\Storage $storage - */ - protected $storage; - - /** - * @var string $storageId - */ - protected $storageId; - - /** - * @var \OC\Files\Cache\Cache $cache - */ - protected $cache; - - /** - * @var boolean $cacheActive If true, perform cache operations, if false, do not affect cache - */ - protected $cacheActive; - - /** - * @var bool $useTransactions whether to use transactions - */ - protected $useTransactions = true; - - /** - * @var \OCP\Lock\ILockingProvider - */ - protected $lockingProvider; - - public function __construct(\OC\Files\Storage\Storage $storage) { - $this->storage = $storage; - $this->storageId = $this->storage->getId(); - $this->cache = $storage->getCache(); - $this->cacheActive = !Config::getSystemValue('filesystem_cache_readonly', false); - $this->lockingProvider = \OC::$server->getLockingProvider(); - } - - /** - * Whether to wrap the scanning of a folder in a database transaction - * On default transactions are used - * - * @param bool $useTransactions - */ - public function setUseTransactions($useTransactions) { - $this->useTransactions = $useTransactions; - } - - /** - * get all the metadata of a file or folder - * * - * - * @param string $path - * @return array an array of metadata of the file - */ - protected function getData($path) { - $data = $this->storage->getMetaData($path); - if (is_null($data)) { - \OCP\Util::writeLog('OC\Files\Cache\Scanner', "!!! Path '$path' is not accessible or present !!!", \OCP\Util::DEBUG); - } - return $data; - } - - /** - * scan a single file and store it in the cache - * - * @param string $file - * @param int $reuseExisting - * @param int $parentId - * @param array | null $cacheData existing data in the cache for the file to be scanned - * @param bool $lock set to false to disable getting an additional read lock during scanning - * @return array an array of metadata of the scanned file - * @throws \OC\ServerNotAvailableException - * @throws \OCP\Lock\LockedException - */ - public function scanFile($file, $reuseExisting = 0, $parentId = -1, $cacheData = null, $lock = true) { - - // only proceed if $file is not a partial file nor a blacklisted file - if (!self::isPartialFile($file) and !Filesystem::isFileBlacklisted($file)) { - - //acquire a lock - if ($lock) { - if ($this->storage->instanceOfStorage('\OCP\Files\Storage\ILockingStorage')) { - $this->storage->acquireLock($file, ILockingProvider::LOCK_SHARED, $this->lockingProvider); - } - } - - $data = $this->getData($file); - - if ($data) { - - // pre-emit only if it was a file. By that we avoid counting/treating folders as files - if ($data['mimetype'] !== 'httpd/unix-directory') { - $this->emit('\OC\Files\Cache\Scanner', 'scanFile', array($file, $this->storageId)); - \OC_Hook::emit('\OC\Files\Cache\Scanner', 'scan_file', array('path' => $file, 'storage' => $this->storageId)); - } - - $parent = dirname($file); - if ($parent === '.' or $parent === '/') { - $parent = ''; - } - if ($parentId === -1) { - $parentId = $this->cache->getId($parent); - } - - // scan the parent if it's not in the cache (id -1) and the current file is not the root folder - if ($file and $parentId === -1) { - $parentData = $this->scanFile($parent); - $parentId = $parentData['fileid']; - } - if ($parent) { - $data['parent'] = $parentId; - } - if (is_null($cacheData)) { - /** @var CacheEntry $cacheData */ - $cacheData = $this->cache->get($file); - } - if ($cacheData and $reuseExisting and isset($cacheData['fileid'])) { - // prevent empty etag - if (empty($cacheData['etag'])) { - $etag = $data['etag']; - } else { - $etag = $cacheData['etag']; - } - $fileId = $cacheData['fileid']; - $data['fileid'] = $fileId; - // only reuse data if the file hasn't explicitly changed - if (isset($data['storage_mtime']) && isset($cacheData['storage_mtime']) && $data['storage_mtime'] === $cacheData['storage_mtime']) { - $data['mtime'] = $cacheData['mtime']; - if (($reuseExisting & self::REUSE_SIZE) && ($data['size'] === -1)) { - $data['size'] = $cacheData['size']; - } - if ($reuseExisting & self::REUSE_ETAG) { - $data['etag'] = $etag; - } - } - // Only update metadata that has changed - $newData = array_diff_assoc($data, $cacheData->getData()); - } else { - $newData = $data; - $fileId = -1; - } - if (!empty($newData)) { - // Reset the checksum if the data has changed - $newData['checksum'] = ''; - $data['fileid'] = $this->addToCache($file, $newData, $fileId); - } - if (isset($cacheData['size'])) { - $data['oldSize'] = $cacheData['size']; - } else { - $data['oldSize'] = 0; - } - - if (isset($cacheData['encrypted'])) { - $data['encrypted'] = $cacheData['encrypted']; - } - - // post-emit only if it was a file. By that we avoid counting/treating folders as files - if ($data['mimetype'] !== 'httpd/unix-directory') { - $this->emit('\OC\Files\Cache\Scanner', 'postScanFile', array($file, $this->storageId)); - \OC_Hook::emit('\OC\Files\Cache\Scanner', 'post_scan_file', array('path' => $file, 'storage' => $this->storageId)); - } - - } else { - $this->removeFromCache($file); - } - - //release the acquired lock - if ($lock) { - if ($this->storage->instanceOfStorage('\OCP\Files\Storage\ILockingStorage')) { - $this->storage->releaseLock($file, ILockingProvider::LOCK_SHARED, $this->lockingProvider); - } - } - - if ($data && !isset($data['encrypted'])) { - $data['encrypted'] = false; - } - return $data; - } - - return null; - } - - protected function removeFromCache($path) { - \OC_Hook::emit('Scanner', 'removeFromCache', array('file' => $path)); - $this->emit('\OC\Files\Cache\Scanner', 'removeFromCache', array($path)); - if ($this->cacheActive) { - $this->cache->remove($path); - } - } - - /** - * @param string $path - * @param array $data - * @param int $fileId - * @return int the id of the added file - */ - protected function addToCache($path, $data, $fileId = -1) { - \OC_Hook::emit('Scanner', 'addToCache', array('file' => $path, 'data' => $data)); - $this->emit('\OC\Files\Cache\Scanner', 'addToCache', array($path, $this->storageId, $data)); - if ($this->cacheActive) { - if ($fileId !== -1) { - $this->cache->update($fileId, $data); - return $fileId; - } else { - return $this->cache->put($path, $data); - } - } else { - return -1; - } - } - - /** - * @param string $path - * @param array $data - * @param int $fileId - */ - protected function updateCache($path, $data, $fileId = -1) { - \OC_Hook::emit('Scanner', 'addToCache', array('file' => $path, 'data' => $data)); - $this->emit('\OC\Files\Cache\Scanner', 'updateCache', array($path, $this->storageId, $data)); - if ($this->cacheActive) { - if ($fileId !== -1) { - $this->cache->update($fileId, $data); - } else { - $this->cache->put($path, $data); - } - } - } - - /** - * scan a folder and all it's children - * - * @param string $path - * @param bool $recursive - * @param int $reuse - * @param bool $lock set to false to disable getting an additional read lock during scanning - * @return array an array of the meta data of the scanned file or folder - */ - public function scan($path, $recursive = self::SCAN_RECURSIVE, $reuse = -1, $lock = true) { - if ($reuse === -1) { - $reuse = ($recursive === self::SCAN_SHALLOW) ? self::REUSE_ETAG | self::REUSE_SIZE : self::REUSE_ETAG; - } - if ($lock) { - if ($this->storage->instanceOfStorage('\OCP\Files\Storage\ILockingStorage')) { - $this->storage->acquireLock($path, ILockingProvider::LOCK_SHARED, $this->lockingProvider); - } - } - $data = $this->scanFile($path, $reuse, -1, null, $lock); - if ($data and $data['mimetype'] === 'httpd/unix-directory') { - $size = $this->scanChildren($path, $recursive, $reuse, $data, $lock); - $data['size'] = $size; - } - if ($lock) { - if ($this->storage->instanceOfStorage('\OCP\Files\Storage\ILockingStorage')) { - $this->storage->releaseLock($path, ILockingProvider::LOCK_SHARED, $this->lockingProvider); - } - } - return $data; - } - - /** - * Get the children currently in the cache - * - * @param int $folderId - * @return array[] - */ - protected function getExistingChildren($folderId) { - $existingChildren = array(); - $children = $this->cache->getFolderContentsById($folderId); - foreach ($children as $child) { - $existingChildren[$child['name']] = $child; - } - return $existingChildren; - } - - /** - * Get the children from the storage - * - * @param string $folder - * @return string[] - */ - protected function getNewChildren($folder) { - $children = array(); - if ($dh = $this->storage->opendir($folder)) { - if (is_resource($dh)) { - while (($file = readdir($dh)) !== false) { - if (!Filesystem::isIgnoredDir($file)) { - $children[] = $file; - } - } - } - } - return $children; - } - - /** - * scan all the files and folders in a folder - * - * @param string $path - * @param bool $recursive - * @param int $reuse - * @param array $folderData existing cache data for the folder to be scanned - * @param bool $lock set to false to disable getting an additional read lock during scanning - * @return int the size of the scanned folder or -1 if the size is unknown at this stage - */ - protected function scanChildren($path, $recursive = self::SCAN_RECURSIVE, $reuse = -1, $folderData = null, $lock = true) { - if ($reuse === -1) { - $reuse = ($recursive === self::SCAN_SHALLOW) ? self::REUSE_ETAG | self::REUSE_SIZE : self::REUSE_ETAG; - } - $this->emit('\OC\Files\Cache\Scanner', 'scanFolder', array($path, $this->storageId)); - $size = 0; - $childQueue = array(); - if (is_array($folderData) and isset($folderData['fileid'])) { - $folderId = $folderData['fileid']; - } else { - $folderId = $this->cache->getId($path); - } - $existingChildren = $this->getExistingChildren($folderId); - $newChildren = $this->getNewChildren($path); - - if ($this->useTransactions) { - \OC::$server->getDatabaseConnection()->beginTransaction(); - } - $exceptionOccurred = false; - foreach ($newChildren as $file) { - $child = ($path) ? $path . '/' . $file : $file; - try { - $existingData = isset($existingChildren[$file]) ? $existingChildren[$file] : null; - $data = $this->scanFile($child, $reuse, $folderId, $existingData, $lock); - if ($data) { - if ($data['mimetype'] === 'httpd/unix-directory' and $recursive === self::SCAN_RECURSIVE) { - $childQueue[$child] = $data; - } else if ($data['size'] === -1) { - $size = -1; - } else if ($size !== -1) { - $size += $data['size']; - } - } - } catch (\Doctrine\DBAL\DBALException $ex) { - // might happen if inserting duplicate while a scanning - // process is running in parallel - // log and ignore - \OCP\Util::writeLog('core', 'Exception while scanning file "' . $child . '": ' . $ex->getMessage(), \OCP\Util::DEBUG); - $exceptionOccurred = true; - } catch (\OCP\Lock\LockedException $e) { - if ($this->useTransactions) { - \OC::$server->getDatabaseConnection()->rollback(); - } - throw $e; - } - } - $removedChildren = \array_diff(array_keys($existingChildren), $newChildren); - foreach ($removedChildren as $childName) { - $child = ($path) ? $path . '/' . $childName : $childName; - $this->removeFromCache($child); - } - if ($this->useTransactions) { - \OC::$server->getDatabaseConnection()->commit(); - } - if ($exceptionOccurred) { - // It might happen that the parallel scan process has already - // inserted mimetypes but those weren't available yet inside the transaction - // To make sure to have the updated mime types in such cases, - // we reload them here - \OC::$server->getMimeTypeLoader()->reset(); - } - - foreach ($childQueue as $child => $childData) { - $childSize = $this->scanChildren($child, self::SCAN_RECURSIVE, $reuse, $childData, $lock); - if ($childSize === -1) { - $size = -1; - } else if ($size !== -1) { - $size += $childSize; - } - } - if (!is_array($folderData) or !isset($folderData['size']) or $folderData['size'] !== $size) { - $this->updateCache($path, array('size' => $size), $folderId); - } - $this->emit('\OC\Files\Cache\Scanner', 'postScanFolder', array($path, $this->storageId)); - return $size; - } - - /** - * check if the file should be ignored when scanning - * NOTE: files with a '.part' extension are ignored as well! - * prevents unfinished put requests to be scanned - * - * @param string $file - * @return boolean - */ - public static function isPartialFile($file) { - if (pathinfo($file, PATHINFO_EXTENSION) === 'part') { - return true; - } - if (strpos($file, '.part/') !== false) { - return true; - } - - return false; - } - - /** - * walk over any folders that are not fully scanned yet and scan them - */ - public function backgroundScan() { - if (!$this->cache->inCache('')) { - $this->runBackgroundScanJob(function () { - $this->scan('', self::SCAN_RECURSIVE, self::REUSE_ETAG); - }, ''); - } else { - $lastPath = null; - while (($path = $this->cache->getIncomplete()) !== false && $path !== $lastPath) { - $this->runBackgroundScanJob(function() use ($path) { - $this->scan($path, self::SCAN_RECURSIVE, self::REUSE_ETAG); - }, $path); - // FIXME: this won't proceed with the next item, needs revamping of getIncomplete() - // to make this possible - $lastPath = $path; - } - } - } - - private function runBackgroundScanJob(callable $callback, $path) { - try { - $callback(); - \OC_Hook::emit('Scanner', 'correctFolderSize', array('path' => $path)); - if ($this->cacheActive) { - $this->cache->correctFolderSize($path); - } - } catch (\OCP\Files\StorageInvalidException $e) { - // skip unavailable storages - } catch (\OCP\Files\StorageNotAvailableException $e) { - // skip unavailable storages - } catch (\OCP\Files\ForbiddenException $e) { - // skip forbidden storages - } catch (\OCP\Lock\LockedException $e) { - // skip unavailable storages - } - } - - /** - * Set whether the cache is affected by scan operations - * - * @param boolean $active The active state of the cache - */ - public function setCacheActive($active) { - $this->cacheActive = $active; - } -} diff --git a/lib/private/files/cache/storage.php b/lib/private/files/cache/storage.php deleted file mode 100644 index 90c451ecc21..00000000000 --- a/lib/private/files/cache/storage.php +++ /dev/null @@ -1,189 +0,0 @@ - - * @author Jörn Friedrich Dreyer - * @author Morris Jobke - * @author Robin Appelman - * @author Robin McCorkell - * @author Thomas Müller - * @author Vincent Petry - * - * @copyright Copyright (c) 2016, ownCloud, Inc. - * @license AGPL-3.0 - * - * This code is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License, version 3, - * along with this program. If not, see - * - */ - -namespace OC\Files\Cache; - -/** - * Handle the mapping between the string and numeric storage ids - * - * Each storage has 2 different ids - * a string id which is generated by the storage backend and reflects the configuration of the storage (e.g. 'smb://user@host/share') - * and a numeric storage id which is referenced in the file cache - * - * A mapping between the two storage ids is stored in the database and accessible trough this class - * - * @package OC\Files\Cache - */ -class Storage { - private $storageId; - private $numericId; - - /** - * @param \OC\Files\Storage\Storage|string $storage - * @param bool $isAvailable - * @throws \RuntimeException - */ - public function __construct($storage, $isAvailable = true) { - if ($storage instanceof \OC\Files\Storage\Storage) { - $this->storageId = $storage->getId(); - } else { - $this->storageId = $storage; - } - $this->storageId = self::adjustStorageId($this->storageId); - - if ($row = self::getStorageById($this->storageId)) { - $this->numericId = $row['numeric_id']; - } else { - $connection = \OC::$server->getDatabaseConnection(); - $available = $isAvailable ? 1 : 0; - if ($connection->insertIfNotExist('*PREFIX*storages', ['id' => $this->storageId, 'available' => $available])) { - $this->numericId = $connection->lastInsertId('*PREFIX*storages'); - } else { - if ($row = self::getStorageById($this->storageId)) { - $this->numericId = $row['numeric_id']; - } else { - throw new \RuntimeException('Storage could neither be inserted nor be selected from the database'); - } - } - } - } - - /** - * @param string $storageId - * @return array|null - */ - public static function getStorageById($storageId) { - $sql = 'SELECT * FROM `*PREFIX*storages` WHERE `id` = ?'; - $result = \OC_DB::executeAudited($sql, array($storageId)); - return $result->fetchRow(); - } - - /** - * Adjusts the storage id to use md5 if too long - * @param string $storageId storage id - * @return string unchanged $storageId if its length is less than 64 characters, - * else returns the md5 of $storageId - */ - public static function adjustStorageId($storageId) { - if (strlen($storageId) > 64) { - return md5($storageId); - } - return $storageId; - } - - /** - * Get the numeric id for the storage - * - * @return int - */ - public function getNumericId() { - return $this->numericId; - } - - /** - * Get the string id for the storage - * - * @param int $numericId - * @return string|null either the storage id string or null if the numeric id is not known - */ - public static function getStorageId($numericId) { - - $sql = 'SELECT `id` FROM `*PREFIX*storages` WHERE `numeric_id` = ?'; - $result = \OC_DB::executeAudited($sql, array($numericId)); - if ($row = $result->fetchRow()) { - return $row['id']; - } else { - return null; - } - } - - /** - * Get the numeric of the storage with the provided string id - * - * @param $storageId - * @return int|null either the numeric storage id or null if the storage id is not knwon - */ - public static function getNumericStorageId($storageId) { - $storageId = self::adjustStorageId($storageId); - - if ($row = self::getStorageById($storageId)) { - return $row['numeric_id']; - } else { - return null; - } - } - - /** - * @return array|null [ available, last_checked ] - */ - public function getAvailability() { - if ($row = self::getStorageById($this->storageId)) { - return [ - 'available' => ((int)$row['available'] === 1), - 'last_checked' => $row['last_checked'] - ]; - } else { - return null; - } - } - - /** - * @param bool $isAvailable - */ - public function setAvailability($isAvailable) { - $sql = 'UPDATE `*PREFIX*storages` SET `available` = ?, `last_checked` = ? WHERE `id` = ?'; - $available = $isAvailable ? 1 : 0; - \OC_DB::executeAudited($sql, array($available, time(), $this->storageId)); - } - - /** - * Check if a string storage id is known - * - * @param string $storageId - * @return bool - */ - public static function exists($storageId) { - return !is_null(self::getNumericStorageId($storageId)); - } - - /** - * remove the entry for the storage - * - * @param string $storageId - */ - public static function remove($storageId) { - $storageId = self::adjustStorageId($storageId); - $numericId = self::getNumericStorageId($storageId); - $sql = 'DELETE FROM `*PREFIX*storages` WHERE `id` = ?'; - \OC_DB::executeAudited($sql, array($storageId)); - - if (!is_null($numericId)) { - $sql = 'DELETE FROM `*PREFIX*filecache` WHERE `storage` = ?'; - \OC_DB::executeAudited($sql, array($numericId)); - } - } -} diff --git a/lib/private/files/cache/updater.php b/lib/private/files/cache/updater.php deleted file mode 100644 index 3f80f2b6167..00000000000 --- a/lib/private/files/cache/updater.php +++ /dev/null @@ -1,228 +0,0 @@ - - * @author Michael Gapczynski - * @author Morris Jobke - * @author Robin Appelman - * @author Vincent Petry - * - * @copyright Copyright (c) 2016, ownCloud, Inc. - * @license AGPL-3.0 - * - * This code is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License, version 3, - * along with this program. If not, see - * - */ - -namespace OC\Files\Cache; -use OCP\Files\Cache\IUpdater; -use OCP\Files\Storage\IStorage; - -/** - * Update the cache and propagate changes - * - */ -class Updater implements IUpdater { - /** - * @var bool - */ - protected $enabled = true; - - /** - * @var \OC\Files\Storage\Storage - */ - protected $storage; - - /** - * @var \OC\Files\Cache\Propagator - */ - protected $propagator; - - /** - * @var Scanner - */ - protected $scanner; - - /** - * @var Cache - */ - protected $cache; - - /** - * @param \OC\Files\Storage\Storage $storage - */ - public function __construct(\OC\Files\Storage\Storage $storage) { - $this->storage = $storage; - $this->propagator = $storage->getPropagator(); - $this->scanner = $storage->getScanner(); - $this->cache = $storage->getCache(); - } - - /** - * Disable updating the cache trough this updater - */ - public function disable() { - $this->enabled = false; - } - - /** - * Re-enable the updating of the cache trough this updater - */ - public function enable() { - $this->enabled = true; - } - - /** - * Get the propagator for etags and mtime for the view the updater works on - * - * @return Propagator - */ - public function getPropagator() { - return $this->propagator; - } - - /** - * Propagate etag and mtime changes for the parent folders of $path up to the root of the filesystem - * - * @param string $path the path of the file to propagate the changes for - * @param int|null $time the timestamp to set as mtime for the parent folders, if left out the current time is used - */ - public function propagate($path, $time = null) { - if (Scanner::isPartialFile($path)) { - return; - } - $this->propagator->propagateChange($path, $time); - } - - /** - * Update the cache for $path and update the size, etag and mtime of the parent folders - * - * @param string $path - * @param int $time - */ - public function update($path, $time = null) { - if (!$this->enabled or Scanner::isPartialFile($path)) { - return; - } - if (is_null($time)) { - $time = time(); - } - - $data = $this->scanner->scan($path, Scanner::SCAN_SHALLOW, -1, false); - if ( - isset($data['oldSize']) && isset($data['size']) && - !$data['encrypted'] // encryption is a pita and touches the cache itself - ) { - $sizeDifference = $data['size'] - $data['oldSize']; - } else { - // scanner didn't provide size info, fallback to full size calculation - $sizeDifference = 0; - $this->cache->correctFolderSize($path, $data); - } - $this->correctParentStorageMtime($path); - $this->propagator->propagateChange($path, $time, $sizeDifference); - } - - /** - * Remove $path from the cache and update the size, etag and mtime of the parent folders - * - * @param string $path - */ - public function remove($path) { - if (!$this->enabled or Scanner::isPartialFile($path)) { - return; - } - - $parent = dirname($path); - if ($parent === '.') { - $parent = ''; - } - - $this->cache->remove($path); - $this->cache->correctFolderSize($parent); - $this->correctParentStorageMtime($path); - $this->propagator->propagateChange($path, time()); - } - - /** - * Rename a file or folder in the cache and update the size, etag and mtime of the parent folders - * - * @param IStorage $sourceStorage - * @param string $source - * @param string $target - */ - public function renameFromStorage(IStorage $sourceStorage, $source, $target) { - if (!$this->enabled or Scanner::isPartialFile($source) or Scanner::isPartialFile($target)) { - return; - } - - $time = time(); - - $sourceCache = $sourceStorage->getCache(); - $sourceUpdater = $sourceStorage->getUpdater(); - $sourcePropagator = $sourceStorage->getPropagator(); - - if ($sourceCache->inCache($source)) { - if ($this->cache->inCache($target)) { - $this->cache->remove($target); - } - - if ($sourceStorage === $this->storage) { - $this->cache->move($source, $target); - } else { - $this->cache->moveFromCache($sourceCache, $source, $target); - } - } - - if (pathinfo($source, PATHINFO_EXTENSION) !== pathinfo($target, PATHINFO_EXTENSION)) { - // handle mime type change - $mimeType = $this->storage->getMimeType($target); - $fileId = $this->cache->getId($target); - $this->cache->update($fileId, ['mimetype' => $mimeType]); - } - - $sourceCache->correctFolderSize($source); - $this->cache->correctFolderSize($target); - if ($sourceUpdater instanceof Updater) { - $sourceUpdater->correctParentStorageMtime($source); - } - $this->correctParentStorageMtime($target); - $this->updateStorageMTimeOnly($target); - $sourcePropagator->propagateChange($source, $time); - $this->propagator->propagateChange($target, $time); - } - - private function updateStorageMTimeOnly($internalPath) { - $fileId = $this->cache->getId($internalPath); - if ($fileId !== -1) { - $this->cache->update( - $fileId, [ - 'mtime' => null, // this magic tells it to not overwrite mtime - 'storage_mtime' => $this->storage->filemtime($internalPath) - ] - ); - } - } - - /** - * update the storage_mtime of the direct parent in the cache to the mtime from the storage - * - * @param string $internalPath - */ - private function correctParentStorageMtime($internalPath) { - $parentId = $this->cache->getParentId($internalPath); - $parent = dirname($internalPath); - if ($parentId != -1) { - $this->cache->update($parentId, array('storage_mtime' => $this->storage->filemtime($parent))); - } - } -} diff --git a/lib/private/files/cache/watcher.php b/lib/private/files/cache/watcher.php deleted file mode 100644 index a00e875a2d4..00000000000 --- a/lib/private/files/cache/watcher.php +++ /dev/null @@ -1,140 +0,0 @@ - - * @author Robin Appelman - * @author Vincent Petry - * - * @copyright Copyright (c) 2016, ownCloud, Inc. - * @license AGPL-3.0 - * - * This code is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License, version 3, - * along with this program. If not, see - * - */ - -namespace OC\Files\Cache; -use OCP\Files\Cache\ICacheEntry; -use OCP\Files\Cache\IWatcher; - -/** - * check the storage backends for updates and change the cache accordingly - */ -class Watcher implements IWatcher { - - protected $watchPolicy = self::CHECK_ONCE; - - protected $checkedPaths = array(); - - /** - * @var \OC\Files\Storage\Storage $storage - */ - protected $storage; - - /** - * @var Cache $cache - */ - protected $cache; - - /** - * @var Scanner $scanner ; - */ - protected $scanner; - - /** - * @param \OC\Files\Storage\Storage $storage - */ - public function __construct(\OC\Files\Storage\Storage $storage) { - $this->storage = $storage; - $this->cache = $storage->getCache(); - $this->scanner = $storage->getScanner(); - } - - /** - * @param int $policy either \OC\Files\Cache\Watcher::CHECK_NEVER, \OC\Files\Cache\Watcher::CHECK_ONCE, \OC\Files\Cache\Watcher::CHECK_ALWAYS - */ - public function setPolicy($policy) { - $this->watchPolicy = $policy; - } - - /** - * @return int either \OC\Files\Cache\Watcher::CHECK_NEVER, \OC\Files\Cache\Watcher::CHECK_ONCE, \OC\Files\Cache\Watcher::CHECK_ALWAYS - */ - public function getPolicy() { - return $this->watchPolicy; - } - - /** - * check $path for updates and update if needed - * - * @param string $path - * @param ICacheEntry|null $cachedEntry - * @return boolean true if path was updated - */ - public function checkUpdate($path, $cachedEntry = null) { - if (is_null($cachedEntry)) { - $cachedEntry = $this->cache->get($path); - } - if ($this->needsUpdate($path, $cachedEntry)) { - $this->update($path, $cachedEntry); - return true; - } else { - return false; - } - } - - /** - * Update the cache for changes to $path - * - * @param string $path - * @param ICacheEntry $cachedData - */ - public function update($path, $cachedData) { - if ($this->storage->is_dir($path)) { - $this->scanner->scan($path, Scanner::SCAN_SHALLOW); - } else { - $this->scanner->scanFile($path); - } - if ($cachedData['mimetype'] === 'httpd/unix-directory') { - $this->cleanFolder($path); - } - $this->cache->correctFolderSize($path); - } - - /** - * Check if the cache for $path needs to be updated - * - * @param string $path - * @param ICacheEntry $cachedData - * @return bool - */ - public function needsUpdate($path, $cachedData) { - if ($this->watchPolicy === self::CHECK_ALWAYS or ($this->watchPolicy === self::CHECK_ONCE and array_search($path, $this->checkedPaths) === false)) { - $this->checkedPaths[] = $path; - return $this->storage->hasUpdated($path, $cachedData['storage_mtime']); - } - return false; - } - - /** - * remove deleted files in $path from the cache - * - * @param string $path - */ - public function cleanFolder($path) { - $cachedContent = $this->cache->getFolderContents($path); - foreach ($cachedContent as $entry) { - if (!$this->storage->file_exists($entry['path'])) { - $this->cache->remove($entry['path']); - } - } - } -} diff --git a/lib/private/files/cache/wrapper/cachejail.php b/lib/private/files/cache/wrapper/cachejail.php deleted file mode 100644 index 88b0f23a1fc..00000000000 --- a/lib/private/files/cache/wrapper/cachejail.php +++ /dev/null @@ -1,300 +0,0 @@ - - * @author Robin Appelman - * @author Robin McCorkell - * @author Vincent Petry - * - * @copyright Copyright (c) 2016, ownCloud, Inc. - * @license AGPL-3.0 - * - * This code is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License, version 3, - * along with this program. If not, see - * - */ - -namespace OC\Files\Cache\Wrapper; - -/** - * Jail to a subdirectory of the wrapped cache - */ -class CacheJail extends CacheWrapper { - /** - * @var string - */ - protected $root; - - /** - * @param \OCP\Files\Cache\ICache $cache - * @param string $root - */ - public function __construct($cache, $root) { - parent::__construct($cache); - $this->root = $root; - } - - protected function getSourcePath($path) { - if ($path === '') { - return $this->root; - } else { - return $this->root . '/' . ltrim($path, '/'); - } - } - - /** - * @param string $path - * @return null|string the jailed path or null if the path is outside the jail - */ - protected function getJailedPath($path) { - $rootLength = strlen($this->root) + 1; - if ($path === $this->root) { - return ''; - } else if (substr($path, 0, $rootLength) === $this->root . '/') { - return substr($path, $rootLength); - } else { - return null; - } - } - - /** - * @param array $entry - * @return array - */ - protected function formatCacheEntry($entry) { - if (isset($entry['path'])) { - $entry['path'] = $this->getJailedPath($entry['path']); - } - return $entry; - } - - protected function filterCacheEntry($entry) { - $rootLength = strlen($this->root) + 1; - return ($entry['path'] === $this->root) or (substr($entry['path'], 0, $rootLength) === $this->root . '/'); - } - - /** - * get the stored metadata of a file or folder - * - * @param string /int $file - * @return array|false - */ - public function get($file) { - if (is_string($file) or $file == '') { - $file = $this->getSourcePath($file); - } - return parent::get($file); - } - - /** - * insert meta data for a new file or folder - * - * @param string $file - * @param array $data - * - * @return int file id - * @throws \RuntimeException - */ - public function insert($file, array $data) { - return $this->cache->insert($this->getSourcePath($file), $data); - } - - /** - * update the metadata in the cache - * - * @param int $id - * @param array $data - */ - public function update($id, array $data) { - $this->cache->update($id, $data); - } - - /** - * get the file id for a file - * - * @param string $file - * @return int - */ - public function getId($file) { - return $this->cache->getId($this->getSourcePath($file)); - } - - /** - * get the id of the parent folder of a file - * - * @param string $file - * @return int - */ - public function getParentId($file) { - if ($file === '') { - return -1; - } else { - return $this->cache->getParentId($this->getSourcePath($file)); - } - } - - /** - * check if a file is available in the cache - * - * @param string $file - * @return bool - */ - public function inCache($file) { - return $this->cache->inCache($this->getSourcePath($file)); - } - - /** - * remove a file or folder from the cache - * - * @param string $file - */ - public function remove($file) { - $this->cache->remove($this->getSourcePath($file)); - } - - /** - * Move a file or folder in the cache - * - * @param string $source - * @param string $target - */ - public function move($source, $target) { - $this->cache->move($this->getSourcePath($source), $this->getSourcePath($target)); - } - - /** - * remove all entries for files that are stored on the storage from the cache - */ - public function clear() { - $this->cache->remove($this->root); - } - - /** - * @param string $file - * - * @return int Cache::NOT_FOUND, Cache::PARTIAL, Cache::SHALLOW or Cache::COMPLETE - */ - public function getStatus($file) { - return $this->cache->getStatus($this->getSourcePath($file)); - } - - private function formatSearchResults($results) { - $results = array_filter($results, array($this, 'filterCacheEntry')); - $results = array_values($results); - return array_map(array($this, 'formatCacheEntry'), $results); - } - - /** - * search for files matching $pattern - * - * @param string $pattern - * @return array an array of file data - */ - public function search($pattern) { - $results = $this->cache->search($pattern); - return $this->formatSearchResults($results); - } - - /** - * search for files by mimetype - * - * @param string $mimetype - * @return array - */ - public function searchByMime($mimetype) { - $results = $this->cache->searchByMime($mimetype); - return $this->formatSearchResults($results); - } - - /** - * search for files by mimetype - * - * @param string|int $tag name or tag id - * @param string $userId owner of the tags - * @return array - */ - public function searchByTag($tag, $userId) { - $results = $this->cache->searchByTag($tag, $userId); - return $this->formatSearchResults($results); - } - - /** - * update the folder size and the size of all parent folders - * - * @param string|boolean $path - * @param array $data (optional) meta data of the folder - */ - public function correctFolderSize($path, $data = null) { - $this->cache->correctFolderSize($this->getSourcePath($path), $data); - } - - /** - * get the size of a folder and set it in the cache - * - * @param string $path - * @param array $entry (optional) meta data of the folder - * @return int - */ - public function calculateFolderSize($path, $entry = null) { - return $this->cache->calculateFolderSize($this->getSourcePath($path), $entry); - } - - /** - * get all file ids on the files on the storage - * - * @return int[] - */ - public function getAll() { - // not supported - return array(); - } - - /** - * find a folder in the cache which has not been fully scanned - * - * If multiply incomplete folders are in the cache, the one with the highest id will be returned, - * use the one with the highest id gives the best result with the background scanner, since that is most - * likely the folder where we stopped scanning previously - * - * @return string|bool the path of the folder or false when no folder matched - */ - public function getIncomplete() { - // not supported - return false; - } - - /** - * get the path of a file on this storage by it's id - * - * @param int $id - * @return string|null - */ - public function getPathById($id) { - $path = $this->cache->getPathById($id); - return $this->getJailedPath($path); - } - - /** - * Move a file or folder in the cache - * - * Note that this should make sure the entries are removed from the source cache - * - * @param \OCP\Files\Cache\ICache $sourceCache - * @param string $sourcePath - * @param string $targetPath - */ - public function moveFromCache(\OCP\Files\Cache\ICache $sourceCache, $sourcePath, $targetPath) { - if ($sourceCache === $this) { - return $this->move($sourcePath, $targetPath); - } - return $this->cache->moveFromCache($sourceCache, $sourcePath, $targetPath); - } -} diff --git a/lib/private/files/cache/wrapper/cachepermissionsmask.php b/lib/private/files/cache/wrapper/cachepermissionsmask.php deleted file mode 100644 index b3a7bcb3a73..00000000000 --- a/lib/private/files/cache/wrapper/cachepermissionsmask.php +++ /dev/null @@ -1,46 +0,0 @@ - - * @author Robin Appelman - * - * @copyright Copyright (c) 2016, ownCloud, Inc. - * @license AGPL-3.0 - * - * This code is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License, version 3, - * along with this program. If not, see - * - */ - -namespace OC\Files\Cache\Wrapper; - -class CachePermissionsMask extends CacheWrapper { - /** - * @var int - */ - protected $mask; - - /** - * @param \OCP\Files\Cache\ICache $cache - * @param int $mask - */ - public function __construct($cache, $mask) { - parent::__construct($cache); - $this->mask = $mask; - } - - protected function formatCacheEntry($entry) { - if (isset($entry['permissions'])) { - $entry['permissions'] &= $this->mask; - } - return $entry; - } -} diff --git a/lib/private/files/cache/wrapper/cachewrapper.php b/lib/private/files/cache/wrapper/cachewrapper.php deleted file mode 100644 index 8c77e3c340e..00000000000 --- a/lib/private/files/cache/wrapper/cachewrapper.php +++ /dev/null @@ -1,309 +0,0 @@ - - * @author Robin Appelman - * @author Robin McCorkell - * @author Vincent Petry - * - * @copyright Copyright (c) 2016, ownCloud, Inc. - * @license AGPL-3.0 - * - * This code is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License, version 3, - * along with this program. If not, see - * - */ - -namespace OC\Files\Cache\Wrapper; - -use OC\Files\Cache\Cache; -use OCP\Files\Cache\ICacheEntry; -use OCP\Files\Cache\ICache; - -class CacheWrapper extends Cache { - /** - * @var \OCP\Files\Cache\ICache - */ - protected $cache; - - /** - * @param \OCP\Files\Cache\ICache $cache - */ - public function __construct($cache) { - $this->cache = $cache; - } - - /** - * Make it easy for wrappers to modify every returned cache entry - * - * @param ICacheEntry $entry - * @return ICacheEntry - */ - protected function formatCacheEntry($entry) { - return $entry; - } - - /** - * get the stored metadata of a file or folder - * - * @param string|int $file - * @return ICacheEntry|false - */ - public function get($file) { - $result = $this->cache->get($file); - if ($result) { - $result = $this->formatCacheEntry($result); - } - return $result; - } - - /** - * get the metadata of all files stored in $folder - * - * @param string $folder - * @return ICacheEntry[] - */ - public function getFolderContents($folder) { - // can't do a simple $this->cache->.... call here since getFolderContentsById needs to be called on this - // and not the wrapped cache - $fileId = $this->getId($folder); - return $this->getFolderContentsById($fileId); - } - - /** - * get the metadata of all files stored in $folder - * - * @param int $fileId the file id of the folder - * @return array - */ - public function getFolderContentsById($fileId) { - $results = $this->cache->getFolderContentsById($fileId); - return array_map(array($this, 'formatCacheEntry'), $results); - } - - /** - * insert or update meta data for a file or folder - * - * @param string $file - * @param array $data - * - * @return int file id - * @throws \RuntimeException - */ - public function put($file, array $data) { - if (($id = $this->getId($file)) > -1) { - $this->update($id, $data); - return $id; - } else { - return $this->insert($file, $data); - } - } - - /** - * insert meta data for a new file or folder - * - * @param string $file - * @param array $data - * - * @return int file id - * @throws \RuntimeException - */ - public function insert($file, array $data) { - return $this->cache->insert($file, $data); - } - - /** - * update the metadata in the cache - * - * @param int $id - * @param array $data - */ - public function update($id, array $data) { - $this->cache->update($id, $data); - } - - /** - * get the file id for a file - * - * @param string $file - * @return int - */ - public function getId($file) { - return $this->cache->getId($file); - } - - /** - * get the id of the parent folder of a file - * - * @param string $file - * @return int - */ - public function getParentId($file) { - return $this->cache->getParentId($file); - } - - /** - * check if a file is available in the cache - * - * @param string $file - * @return bool - */ - public function inCache($file) { - return $this->cache->inCache($file); - } - - /** - * remove a file or folder from the cache - * - * @param string $file - */ - public function remove($file) { - $this->cache->remove($file); - } - - /** - * Move a file or folder in the cache - * - * @param string $source - * @param string $target - */ - public function move($source, $target) { - $this->cache->move($source, $target); - } - - public function moveFromCache(ICache $sourceCache, $sourcePath, $targetPath) { - $this->cache->moveFromCache($sourceCache, $sourcePath, $targetPath); - } - - /** - * remove all entries for files that are stored on the storage from the cache - */ - public function clear() { - $this->cache->clear(); - } - - /** - * @param string $file - * - * @return int Cache::NOT_FOUND, Cache::PARTIAL, Cache::SHALLOW or Cache::COMPLETE - */ - public function getStatus($file) { - return $this->cache->getStatus($file); - } - - /** - * search for files matching $pattern - * - * @param string $pattern - * @return ICacheEntry[] an array of file data - */ - public function search($pattern) { - $results = $this->cache->search($pattern); - return array_map(array($this, 'formatCacheEntry'), $results); - } - - /** - * search for files by mimetype - * - * @param string $mimetype - * @return ICacheEntry[] - */ - public function searchByMime($mimetype) { - $results = $this->cache->searchByMime($mimetype); - return array_map(array($this, 'formatCacheEntry'), $results); - } - - /** - * search for files by tag - * - * @param string|int $tag name or tag id - * @param string $userId owner of the tags - * @return ICacheEntry[] file data - */ - public function searchByTag($tag, $userId) { - $results = $this->cache->searchByTag($tag, $userId); - return array_map(array($this, 'formatCacheEntry'), $results); - } - - /** - * update the folder size and the size of all parent folders - * - * @param string|boolean $path - * @param array $data (optional) meta data of the folder - */ - public function correctFolderSize($path, $data = null) { - $this->cache->correctFolderSize($path, $data); - } - - /** - * get the size of a folder and set it in the cache - * - * @param string $path - * @param array $entry (optional) meta data of the folder - * @return int - */ - public function calculateFolderSize($path, $entry = null) { - return $this->cache->calculateFolderSize($path, $entry); - } - - /** - * get all file ids on the files on the storage - * - * @return int[] - */ - public function getAll() { - return $this->cache->getAll(); - } - - /** - * find a folder in the cache which has not been fully scanned - * - * If multiple incomplete folders are in the cache, the one with the highest id will be returned, - * use the one with the highest id gives the best result with the background scanner, since that is most - * likely the folder where we stopped scanning previously - * - * @return string|bool the path of the folder or false when no folder matched - */ - public function getIncomplete() { - return $this->cache->getIncomplete(); - } - - /** - * get the path of a file on this storage by it's id - * - * @param int $id - * @return string|null - */ - public function getPathById($id) { - return $this->cache->getPathById($id); - } - - /** - * Returns the numeric storage id - * - * @return int - */ - public function getNumericStorageId() { - return $this->cache->getNumericStorageId(); - } - - /** - * get the storage id of the storage for a file and the internal path of the file - * unlike getPathById this does not limit the search to files on this storage and - * instead does a global search in the cache table - * - * @param int $id - * @return array first element holding the storage id, second the path - */ - static public function getById($id) { - return parent::getById($id); - } -} diff --git a/lib/private/files/config/cachedmountinfo.php b/lib/private/files/config/cachedmountinfo.php deleted file mode 100644 index ce75cb66896..00000000000 --- a/lib/private/files/config/cachedmountinfo.php +++ /dev/null @@ -1,107 +0,0 @@ - - * - * @copyright Copyright (c) 2016, ownCloud, Inc. - * @license AGPL-3.0 - * - * This code is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License, version 3, - * along with this program. If not, see - * - */ - -namespace OC\Files\Config; - -use OC\Files\Filesystem; -use OCP\Files\Config\ICachedMountInfo; -use OCP\Files\Node; -use OCP\IUser; - -class CachedMountInfo implements ICachedMountInfo { - /** - * @var IUser - */ - protected $user; - - /** - * @var int - */ - protected $storageId; - - /** - * @var int - */ - protected $rootId; - - /** - * @var string - */ - protected $mountPoint; - - /** - * CachedMountInfo constructor. - * - * @param IUser $user - * @param int $storageId - * @param int $rootId - * @param string $mountPoint - */ - public function __construct(IUser $user, $storageId, $rootId, $mountPoint) { - $this->user = $user; - $this->storageId = $storageId; - $this->rootId = $rootId; - $this->mountPoint = $mountPoint; - } - - /** - * @return IUser - */ - public function getUser() { - return $this->user; - } - - /** - * @return int the numeric storage id of the mount - */ - public function getStorageId() { - return $this->storageId; - } - - /** - * @return int the fileid of the root of the mount - */ - public function getRootId() { - return $this->rootId; - } - - /** - * @return Node the root node of the mount - */ - public function getMountPointNode() { - // TODO injection etc - Filesystem::initMountPoints($this->getUser()->getUID()); - $userNode = \OC::$server->getUserFolder($this->getUser()->getUID()); - $nodes = $userNode->getById($this->getRootId()); - if (count($nodes) > 0) { - return $nodes[0]; - } else { - return null; - } - } - - /** - * @return string the mount point of the mount for the user - */ - public function getMountPoint() { - return $this->mountPoint; - } -} diff --git a/lib/private/files/config/lazystoragemountinfo.php b/lib/private/files/config/lazystoragemountinfo.php deleted file mode 100644 index 654c5b2b23e..00000000000 --- a/lib/private/files/config/lazystoragemountinfo.php +++ /dev/null @@ -1,74 +0,0 @@ - - * - * @copyright Copyright (c) 2016, ownCloud, Inc. - * @license AGPL-3.0 - * - * This code is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License, version 3, - * along with this program. If not, see - * - */ - -namespace OC\Files\Config; - -use OC\Files\Filesystem; -use OCP\Files\Config\ICachedMountInfo; -use OCP\Files\Mount\IMountPoint; -use OCP\Files\Node; -use OCP\IUser; - -class LazyStorageMountInfo extends CachedMountInfo { - /** @var IMountPoint */ - private $mount; - - /** - * CachedMountInfo constructor. - * - * @param IUser $user - * @param IMountPoint $mount - */ - public function __construct(IUser $user, IMountPoint $mount) { - $this->user = $user; - $this->mount = $mount; - } - - /** - * @return int the numeric storage id of the mount - */ - public function getStorageId() { - if (!$this->storageId) { - $this->storageId = $this->mount->getStorage()->getStorageCache()->getNumericId(); - } - return parent::getStorageId(); - } - - /** - * @return int the fileid of the root of the mount - */ - public function getRootId() { - if (!$this->rootId) { - $this->rootId = $this->mount->getStorageRootId(); - } - return parent::getRootId(); - } - - /** - * @return string the mount point of the mount for the user - */ - public function getMountPoint() { - if (!$this->mountPoint) { - $this->mountPoint = $this->mount->getMountPoint(); - } - return parent::getMountPoint(); - } -} diff --git a/lib/private/files/config/mountprovidercollection.php b/lib/private/files/config/mountprovidercollection.php deleted file mode 100644 index 499fa576fbc..00000000000 --- a/lib/private/files/config/mountprovidercollection.php +++ /dev/null @@ -1,108 +0,0 @@ - - * @author Robin Appelman - * - * @copyright Copyright (c) 2016, ownCloud, Inc. - * @license AGPL-3.0 - * - * This code is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License, version 3, - * along with this program. If not, see - * - */ - -namespace OC\Files\Config; - -use OC\Hooks\Emitter; -use OC\Hooks\EmitterTrait; -use OCP\Files\Config\IMountProviderCollection; -use OCP\Files\Config\IMountProvider; -use OCP\Files\Config\IUserMountCache; -use OCP\Files\Mount\IMountPoint; -use OCP\Files\Storage\IStorageFactory; -use OCP\IUser; - -class MountProviderCollection implements IMountProviderCollection, Emitter { - use EmitterTrait; - - /** - * @var \OCP\Files\Config\IMountProvider[] - */ - private $providers = array(); - - /** - * @var \OCP\Files\Storage\IStorageFactory - */ - private $loader; - - /** - * @var \OCP\Files\Config\IUserMountCache - */ - private $mountCache; - - /** - * @param \OCP\Files\Storage\IStorageFactory $loader - * @param IUserMountCache $mountCache - */ - public function __construct(IStorageFactory $loader, IUserMountCache $mountCache) { - $this->loader = $loader; - $this->mountCache = $mountCache; - } - - /** - * Get all configured mount points for the user - * - * @param \OCP\IUser $user - * @return \OCP\Files\Mount\IMountPoint[] - */ - public function getMountsForUser(IUser $user) { - $loader = $this->loader; - $mounts = array_map(function (IMountProvider $provider) use ($user, $loader) { - return $provider->getMountsForUser($user, $loader); - }, $this->providers); - $mounts = array_filter($mounts, function ($result) { - return is_array($result); - }); - return array_reduce($mounts, function (array $mounts, array $providerMounts) { - return array_merge($mounts, $providerMounts); - }, array()); - } - - /** - * Add a provider for mount points - * - * @param \OCP\Files\Config\IMountProvider $provider - */ - public function registerProvider(IMountProvider $provider) { - $this->providers[] = $provider; - $this->emit('\OC\Files\Config', 'registerMountProvider', [$provider]); - } - - /** - * Cache mounts for user - * - * @param IUser $user - * @param IMountPoint[] $mountPoints - */ - public function registerMounts(IUser $user, array $mountPoints) { - $this->mountCache->registerMounts($user, $mountPoints); - } - - /** - * Get the mount cache which can be used to search for mounts without setting up the filesystem - * - * @return IUserMountCache - */ - public function getMountCache() { - return $this->mountCache; - } -} diff --git a/lib/private/files/config/usermountcache.php b/lib/private/files/config/usermountcache.php deleted file mode 100644 index 05ca146f4be..00000000000 --- a/lib/private/files/config/usermountcache.php +++ /dev/null @@ -1,290 +0,0 @@ - - * @author Robin Appelman - * - * @copyright Copyright (c) 2016, ownCloud, Inc. - * @license AGPL-3.0 - * - * This code is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License, version 3, - * along with this program. If not, see - * - */ - -namespace OC\Files\Config; - -use Doctrine\DBAL\Exception\UniqueConstraintViolationException; -use OC\Files\Filesystem; -use OCA\Files_Sharing\SharedMount; -use OCP\DB\QueryBuilder\IQueryBuilder; -use OCP\Files\Config\ICachedMountInfo; -use OCP\Files\Config\IUserMountCache; -use OCP\Files\Mount\IMountPoint; -use OCP\Files\NotFoundException; -use OCP\ICache; -use OCP\IDBConnection; -use OCP\ILogger; -use OCP\IUser; -use OCP\IUserManager; - -/** - * Cache mounts points per user in the cache so we can easilly look them up - */ -class UserMountCache implements IUserMountCache { - /** - * @var IDBConnection - */ - private $connection; - - /** - * @var IUserManager - */ - private $userManager; - - /** @var ICachedMountInfo[][] [$userId => [$cachedMountInfo, ....], ...] */ - private $mountsForUsers = []; - - /** - * @var ILogger - */ - private $logger; - - private $cacheInfoCache = []; - - /** - * UserMountCache constructor. - * - * @param IDBConnection $connection - * @param IUserManager $userManager - * @param ILogger $logger - */ - public function __construct(IDBConnection $connection, IUserManager $userManager, ILogger $logger) { - $this->connection = $connection; - $this->userManager = $userManager; - $this->logger = $logger; - } - - public function registerMounts(IUser $user, array $mounts) { - // filter out non-proper storages coming from unit tests - $mounts = array_filter($mounts, function (IMountPoint $mount) { - return $mount instanceof SharedMount || $mount->getStorage() && $mount->getStorage()->getCache(); - }); - /** @var ICachedMountInfo[] $newMounts */ - $newMounts = array_map(function (IMountPoint $mount) use ($user) { - // filter out any storages which aren't scanned yet since we aren't interested in files from those storages (yet) - if ($mount->getStorageRootId() === -1) { - return null; - } else { - return new LazyStorageMountInfo($user, $mount); - } - }, $mounts); - $newMounts = array_values(array_filter($newMounts)); - - $cachedMounts = $this->getMountsForUser($user); - $mountDiff = function (ICachedMountInfo $mount1, ICachedMountInfo $mount2) { - // since we are only looking for mounts for a specific user comparing on root id is enough - return $mount1->getRootId() - $mount2->getRootId(); - }; - - /** @var ICachedMountInfo[] $addedMounts */ - $addedMounts = array_udiff($newMounts, $cachedMounts, $mountDiff); - /** @var ICachedMountInfo[] $removedMounts */ - $removedMounts = array_udiff($cachedMounts, $newMounts, $mountDiff); - - $changedMounts = array_uintersect($newMounts, $cachedMounts, function (ICachedMountInfo $mount1, ICachedMountInfo $mount2) { - // filter mounts with the same root id and different mountpoints - if ($mount1->getRootId() !== $mount2->getRootId()) { - return -1; - } - return ($mount1->getMountPoint() !== $mount2->getMountPoint()) ? 0 : 1; - }); - - foreach ($addedMounts as $mount) { - $this->addToCache($mount); - $this->mountsForUsers[$user->getUID()][] = $mount; - } - foreach ($removedMounts as $mount) { - $this->removeFromCache($mount); - $index = array_search($mount, $this->mountsForUsers[$user->getUID()]); - unset($this->mountsForUsers[$user->getUID()][$index]); - } - foreach ($changedMounts as $mount) { - $this->setMountPoint($mount); - } - } - - private function addToCache(ICachedMountInfo $mount) { - $this->connection->insertIfNotExist('*PREFIX*mounts', [ - 'storage_id' => $mount->getStorageId(), - 'root_id' => $mount->getRootId(), - 'user_id' => $mount->getUser()->getUID(), - 'mount_point' => $mount->getMountPoint() - ], ['root_id', 'user_id']); - } - - private function setMountPoint(ICachedMountInfo $mount) { - $builder = $this->connection->getQueryBuilder(); - - $query = $builder->update('mounts') - ->set('mount_point', $builder->createNamedParameter($mount->getMountPoint())) - ->where($builder->expr()->eq('user_id', $builder->createNamedParameter($mount->getUser()->getUID()))) - ->andWhere($builder->expr()->eq('root_id', $builder->createNamedParameter($mount->getRootId(), IQueryBuilder::PARAM_INT))); - - $query->execute(); - } - - private function removeFromCache(ICachedMountInfo $mount) { - $builder = $this->connection->getQueryBuilder(); - - $query = $builder->delete('mounts') - ->where($builder->expr()->eq('user_id', $builder->createNamedParameter($mount->getUser()->getUID()))) - ->andWhere($builder->expr()->eq('root_id', $builder->createNamedParameter($mount->getRootId(), IQueryBuilder::PARAM_INT))); - $query->execute(); - } - - private function dbRowToMountInfo(array $row) { - $user = $this->userManager->get($row['user_id']); - return new CachedMountInfo($user, (int)$row['storage_id'], (int)$row['root_id'], $row['mount_point']); - } - - /** - * @param IUser $user - * @return ICachedMountInfo[] - */ - public function getMountsForUser(IUser $user) { - if (!isset($this->mountsForUsers[$user->getUID()])) { - $builder = $this->connection->getQueryBuilder(); - $query = $builder->select('storage_id', 'root_id', 'user_id', 'mount_point') - ->from('mounts') - ->where($builder->expr()->eq('user_id', $builder->createPositionalParameter($user->getUID()))); - - $rows = $query->execute()->fetchAll(); - - $this->mountsForUsers[$user->getUID()] = array_map([$this, 'dbRowToMountInfo'], $rows); - } - return $this->mountsForUsers[$user->getUID()]; - } - - /** - * @param int $numericStorageId - * @return CachedMountInfo[] - */ - public function getMountsForStorageId($numericStorageId) { - $builder = $this->connection->getQueryBuilder(); - $query = $builder->select('storage_id', 'root_id', 'user_id', 'mount_point') - ->from('mounts') - ->where($builder->expr()->eq('storage_id', $builder->createPositionalParameter($numericStorageId, IQueryBuilder::PARAM_INT))); - - $rows = $query->execute()->fetchAll(); - - return array_map([$this, 'dbRowToMountInfo'], $rows); - } - - /** - * @param int $rootFileId - * @return CachedMountInfo[] - */ - public function getMountsForRootId($rootFileId) { - $builder = $this->connection->getQueryBuilder(); - $query = $builder->select('storage_id', 'root_id', 'user_id', 'mount_point') - ->from('mounts') - ->where($builder->expr()->eq('root_id', $builder->createPositionalParameter($rootFileId, IQueryBuilder::PARAM_INT))); - - $rows = $query->execute()->fetchAll(); - - return array_map([$this, 'dbRowToMountInfo'], $rows); - } - - /** - * @param $fileId - * @return array - * @throws \OCP\Files\NotFoundException - */ - private function getCacheInfoFromFileId($fileId) { - if (!isset($this->cacheInfoCache[$fileId])) { - $builder = $this->connection->getQueryBuilder(); - $query = $builder->select('storage', 'path') - ->from('filecache') - ->where($builder->expr()->eq('fileid', $builder->createNamedParameter($fileId, IQueryBuilder::PARAM_INT))); - - $row = $query->execute()->fetch(); - if (is_array($row)) { - $this->cacheInfoCache[$fileId] = [ - (int)$row['storage'], - $row['path'] - ]; - } else { - throw new NotFoundException('File with id "' . $fileId . '" not found'); - } - } - return $this->cacheInfoCache[$fileId]; - } - - /** - * @param int $fileId - * @return ICachedMountInfo[] - * @since 9.0.0 - */ - public function getMountsForFileId($fileId) { - try { - list($storageId, $internalPath) = $this->getCacheInfoFromFileId($fileId); - } catch (NotFoundException $e) { - return []; - } - $mountsForStorage = $this->getMountsForStorageId($storageId); - - // filter mounts that are from the same storage but a different directory - return array_filter($mountsForStorage, function (ICachedMountInfo $mount) use ($internalPath, $fileId) { - if ($fileId === $mount->getRootId()) { - return true; - } - try { - list(, $internalMountPath) = $this->getCacheInfoFromFileId($mount->getRootId()); - } catch (NotFoundException $e) { - return false; - } - - return $internalMountPath === '' || substr($internalPath, 0, strlen($internalMountPath) + 1) === $internalMountPath . '/'; - }); - - } - - /** - * Remove all cached mounts for a user - * - * @param IUser $user - */ - public function removeUserMounts(IUser $user) { - $builder = $this->connection->getQueryBuilder(); - - $query = $builder->delete('mounts') - ->where($builder->expr()->eq('user_id', $builder->createNamedParameter($user->getUID()))); - $query->execute(); - } - - public function removeUserStorageMount($storageId, $userId) { - $builder = $this->connection->getQueryBuilder(); - - $query = $builder->delete('mounts') - ->where($builder->expr()->eq('user_id', $builder->createNamedParameter($userId))) - ->andWhere($builder->expr()->eq('storage_id', $builder->createNamedParameter($storageId, IQueryBuilder::PARAM_INT))); - $query->execute(); - } - - public function remoteStorageMounts($storageId) { - $builder = $this->connection->getQueryBuilder(); - - $query = $builder->delete('mounts') - ->where($builder->expr()->eq('storage_id', $builder->createNamedParameter($storageId, IQueryBuilder::PARAM_INT))); - $query->execute(); - } -} diff --git a/lib/private/files/config/usermountcachelistener.php b/lib/private/files/config/usermountcachelistener.php deleted file mode 100644 index 99673cf6285..00000000000 --- a/lib/private/files/config/usermountcachelistener.php +++ /dev/null @@ -1,48 +0,0 @@ - - * - * @copyright Copyright (c) 2016, ownCloud, Inc. - * @license AGPL-3.0 - * - * This code is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License, version 3, - * along with this program. If not, see - * - */ - -namespace OC\Files\Config; - -use OC\User\Manager; -use OCP\Files\Config\IUserMountCache; - -/** - * Listen to hooks and update the mount cache as needed - */ -class UserMountCacheListener { - /** - * @var IUserMountCache - */ - private $userMountCache; - - /** - * UserMountCacheListener constructor. - * - * @param IUserMountCache $userMountCache - */ - public function __construct(IUserMountCache $userMountCache) { - $this->userMountCache = $userMountCache; - } - - public function listen(Manager $manager) { - $manager->listen('\OC\User', 'postDelete', [$this->userMountCache, 'removeUserMounts']); - } -} diff --git a/lib/private/files/fileinfo.php b/lib/private/files/fileinfo.php deleted file mode 100644 index 5f32ad34bb3..00000000000 --- a/lib/private/files/fileinfo.php +++ /dev/null @@ -1,346 +0,0 @@ - - * @author Lukas Reschke - * @author Morris Jobke - * @author Robin Appelman - * @author Robin McCorkell - * @author Roeland Jago Douma - * @author tbartenstein - * @author Thomas Müller - * @author Vincent Petry - * - * @copyright Copyright (c) 2016, ownCloud, Inc. - * @license AGPL-3.0 - * - * This code is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License, version 3, - * along with this program. If not, see - * - */ - -namespace OC\Files; - -use OCP\Files\Cache\ICacheEntry; -use OCP\IUser; - -class FileInfo implements \OCP\Files\FileInfo, \ArrayAccess { - /** - * @var array $data - */ - private $data; - - /** - * @var string $path - */ - private $path; - - /** - * @var \OC\Files\Storage\Storage $storage - */ - private $storage; - - /** - * @var string $internalPath - */ - private $internalPath; - - /** - * @var \OCP\Files\Mount\IMountPoint - */ - private $mount; - - /** - * @var IUser - */ - private $owner; - - /** - * @var string[] - */ - private $childEtags = []; - - /** - * @param string|boolean $path - * @param Storage\Storage $storage - * @param string $internalPath - * @param array|ICacheEntry $data - * @param \OCP\Files\Mount\IMountPoint $mount - * @param \OCP\IUser|null $owner - */ - public function __construct($path, $storage, $internalPath, $data, $mount, $owner= null) { - $this->path = $path; - $this->storage = $storage; - $this->internalPath = $internalPath; - $this->data = $data; - $this->mount = $mount; - $this->owner = $owner; - } - - public function offsetSet($offset, $value) { - $this->data[$offset] = $value; - } - - public function offsetExists($offset) { - return isset($this->data[$offset]); - } - - public function offsetUnset($offset) { - unset($this->data[$offset]); - } - - public function offsetGet($offset) { - if ($offset === 'type') { - return $this->getType(); - } else if ($offset === 'etag') { - return $this->getEtag(); - } elseif ($offset === 'permissions') { - return $this->getPermissions(); - } elseif (isset($this->data[$offset])) { - return $this->data[$offset]; - } else { - return null; - } - } - - /** - * @return string - */ - public function getPath() { - return $this->path; - } - - /** - * @return \OCP\Files\Storage - */ - public function getStorage() { - return $this->storage; - } - - /** - * @return string - */ - public function getInternalPath() { - return $this->internalPath; - } - - /** - * @return int - */ - public function getId() { - return $this->data['fileid']; - } - - /** - * @return string - */ - public function getMimetype() { - return $this->data['mimetype']; - } - - /** - * @return string - */ - public function getMimePart() { - return $this->data['mimepart']; - } - - /** - * @return string - */ - public function getName() { - return basename($this->getPath()); - } - - /** - * @return string - */ - public function getEtag() { - if (count($this->childEtags) > 0) { - $combinedEtag = $this->data['etag'] . '::' . implode('::', $this->childEtags); - return md5($combinedEtag); - } else { - return $this->data['etag']; - } - } - - /** - * @return int - */ - public function getSize() { - return isset($this->data['size']) ? $this->data['size'] : 0; - } - - /** - * @return int - */ - public function getMTime() { - return $this->data['mtime']; - } - - /** - * @return bool - */ - public function isEncrypted() { - return $this->data['encrypted']; - } - - /** - * Return the currently version used for the HMAC in the encryption app - * - * @return int - */ - public function getEncryptedVersion() { - return isset($this->data['encryptedVersion']) ? (int) $this->data['encryptedVersion'] : 1; - } - - /** - * @return int - */ - public function getPermissions() { - $perms = $this->data['permissions']; - if (\OCP\Util::isSharingDisabledForUser() || ($this->isShared() && !\OC\Share\Share::isResharingAllowed())) { - $perms = $perms & ~\OCP\Constants::PERMISSION_SHARE; - } - return $perms; - } - - /** - * @return \OCP\Files\FileInfo::TYPE_FILE|\OCP\Files\FileInfo::TYPE_FOLDER - */ - public function getType() { - if (!isset($this->data['type'])) { - $this->data['type'] = ($this->getMimetype() === 'httpd/unix-directory') ? self::TYPE_FOLDER : self::TYPE_FILE; - } - return $this->data['type']; - } - - public function getData() { - return $this->data; - } - - /** - * @param int $permissions - * @return bool - */ - protected function checkPermissions($permissions) { - return ($this->getPermissions() & $permissions) === $permissions; - } - - /** - * @return bool - */ - public function isReadable() { - return $this->checkPermissions(\OCP\Constants::PERMISSION_READ); - } - - /** - * @return bool - */ - public function isUpdateable() { - return $this->checkPermissions(\OCP\Constants::PERMISSION_UPDATE); - } - - /** - * Check whether new files or folders can be created inside this folder - * - * @return bool - */ - public function isCreatable() { - return $this->checkPermissions(\OCP\Constants::PERMISSION_CREATE); - } - - /** - * @return bool - */ - public function isDeletable() { - return $this->checkPermissions(\OCP\Constants::PERMISSION_DELETE); - } - - /** - * @return bool - */ - public function isShareable() { - return $this->checkPermissions(\OCP\Constants::PERMISSION_SHARE); - } - - /** - * Check if a file or folder is shared - * - * @return bool - */ - public function isShared() { - $sid = $this->getStorage()->getId(); - if (!is_null($sid)) { - $sid = explode(':', $sid); - return ($sid[0] === 'shared'); - } - - return false; - } - - public function isMounted() { - $sid = $this->getStorage()->getId(); - if (!is_null($sid)) { - $sid = explode(':', $sid); - return ($sid[0] !== 'home' and $sid[0] !== 'shared'); - } - - return false; - } - - /** - * Get the mountpoint the file belongs to - * - * @return \OCP\Files\Mount\IMountPoint - */ - public function getMountPoint() { - return $this->mount; - } - - /** - * Get the owner of the file - * - * @return \OCP\IUser - */ - public function getOwner() { - return $this->owner; - } - - /** - * Add a cache entry which is the child of this folder - * - * Sets the size, etag and size to for cross-storage childs - * - * @param array $data cache entry for the child - * @param string $entryPath full path of the child entry - */ - public function addSubEntry($data, $entryPath) { - $this->data['size'] += isset($data['size']) ? $data['size'] : 0; - if (isset($data['mtime'])) { - $this->data['mtime'] = max($this->data['mtime'], $data['mtime']); - } - if (isset($data['etag'])) { - // prefix the etag with the relative path of the subentry to propagate etag on mount moves - $relativeEntryPath = substr($entryPath, strlen($this->getPath())); - // attach the permissions to propagate etag on permision changes of submounts - $permissions = isset($data['permissions']) ? $data['permissions'] : 0; - $this->childEtags[] = $relativeEntryPath . '/' . $data['etag'] . $permissions; - } - } - - /** - * @inheritdoc - */ - public function getChecksum() { - return $this->data['checksum']; - } -} diff --git a/lib/private/files/filesystem.php b/lib/private/files/filesystem.php deleted file mode 100644 index 7283c815c97..00000000000 --- a/lib/private/files/filesystem.php +++ /dev/null @@ -1,928 +0,0 @@ - - * @author Bart Visscher - * @author Christopher Schäpers - * @author Florin Peter - * @author Georg Ehrke - * @author Joas Schilling - * @author Jörn Friedrich Dreyer - * @author Lukas Reschke - * @author Michael Gapczynski - * @author Morris Jobke - * @author Robin Appelman - * @author Robin McCorkell - * @author Roeland Jago Douma - * @author Sam Tuke - * @author Stephan Peijnik - * @author Vincent Petry - * - * @copyright Copyright (c) 2016, ownCloud, Inc. - * @license AGPL-3.0 - * - * This code is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License, version 3, - * along with this program. If not, see - * - */ - -/** - * Class for abstraction of filesystem functions - * This class won't call any filesystem functions for itself but will pass them to the correct OC_Filestorage object - * this class should also handle all the file permission related stuff - * - * Hooks provided: - * read(path) - * write(path, &run) - * post_write(path) - * create(path, &run) (when a file is created, both create and write will be emitted in that order) - * post_create(path) - * delete(path, &run) - * post_delete(path) - * rename(oldpath,newpath, &run) - * post_rename(oldpath,newpath) - * copy(oldpath,newpath, &run) (if the newpath doesn't exists yes, copy, create and write will be emitted in that order) - * post_rename(oldpath,newpath) - * post_initMountPoints(user, user_dir) - * - * the &run parameter can be set to false to prevent the operation from occurring - */ - -namespace OC\Files; - -use OC\Files\Config\MountProviderCollection; -use OC\Files\Mount\MountPoint; -use OC\Files\Storage\StorageFactory; -use OCP\Files\Config\IMountProvider; -use OCP\Files\Mount\IMountPoint; -use OCP\Files\NotFoundException; -use OCP\IUserManager; - -class Filesystem { - - /** - * @var Mount\Manager $mounts - */ - private static $mounts; - - public static $loaded = false; - /** - * @var \OC\Files\View $defaultInstance - */ - static private $defaultInstance; - - static private $usersSetup = array(); - - static private $normalizedPathCache = array(); - - static private $listeningForProviders = false; - - /** - * classname which used for hooks handling - * used as signalclass in OC_Hooks::emit() - */ - const CLASSNAME = 'OC_Filesystem'; - - /** - * signalname emitted before file renaming - * - * @param string $oldpath - * @param string $newpath - */ - const signal_rename = 'rename'; - - /** - * signal emitted after file renaming - * - * @param string $oldpath - * @param string $newpath - */ - const signal_post_rename = 'post_rename'; - - /** - * signal emitted before file/dir creation - * - * @param string $path - * @param bool $run changing this flag to false in hook handler will cancel event - */ - const signal_create = 'create'; - - /** - * signal emitted after file/dir creation - * - * @param string $path - * @param bool $run changing this flag to false in hook handler will cancel event - */ - const signal_post_create = 'post_create'; - - /** - * signal emits before file/dir copy - * - * @param string $oldpath - * @param string $newpath - * @param bool $run changing this flag to false in hook handler will cancel event - */ - const signal_copy = 'copy'; - - /** - * signal emits after file/dir copy - * - * @param string $oldpath - * @param string $newpath - */ - const signal_post_copy = 'post_copy'; - - /** - * signal emits before file/dir save - * - * @param string $path - * @param bool $run changing this flag to false in hook handler will cancel event - */ - const signal_write = 'write'; - - /** - * signal emits after file/dir save - * - * @param string $path - */ - const signal_post_write = 'post_write'; - - /** - * signal emitted before file/dir update - * - * @param string $path - * @param bool $run changing this flag to false in hook handler will cancel event - */ - const signal_update = 'update'; - - /** - * signal emitted after file/dir update - * - * @param string $path - * @param bool $run changing this flag to false in hook handler will cancel event - */ - const signal_post_update = 'post_update'; - - /** - * signal emits when reading file/dir - * - * @param string $path - */ - const signal_read = 'read'; - - /** - * signal emits when removing file/dir - * - * @param string $path - */ - const signal_delete = 'delete'; - - /** - * parameters definitions for signals - */ - const signal_param_path = 'path'; - const signal_param_oldpath = 'oldpath'; - const signal_param_newpath = 'newpath'; - - /** - * run - changing this flag to false in hook handler will cancel event - */ - const signal_param_run = 'run'; - - const signal_create_mount = 'create_mount'; - const signal_delete_mount = 'delete_mount'; - const signal_param_mount_type = 'mounttype'; - const signal_param_users = 'users'; - - /** - * @var \OC\Files\Storage\StorageFactory $loader - */ - private static $loader; - - /** - * @param string $wrapperName - * @param callable $wrapper - * @param int $priority - */ - public static function addStorageWrapper($wrapperName, $wrapper, $priority = 50) { - $mounts = self::getMountManager()->getAll(); - if (!self::getLoader()->addStorageWrapper($wrapperName, $wrapper, $priority, $mounts)) { - // do not re-wrap if storage with this name already existed - return; - } - } - - /** - * Returns the storage factory - * - * @return \OCP\Files\Storage\IStorageFactory - */ - public static function getLoader() { - if (!self::$loader) { - self::$loader = new StorageFactory(); - } - return self::$loader; - } - - /** - * Returns the mount manager - * - * @return \OC\Files\Mount\Manager - */ - public static function getMountManager($user = '') { - if (!self::$mounts) { - \OC_Util::setupFS($user); - } - return self::$mounts; - } - - /** - * get the mountpoint of the storage object for a path - * ( note: because a storage is not always mounted inside the fakeroot, the - * returned mountpoint is relative to the absolute root of the filesystem - * and doesn't take the chroot into account ) - * - * @param string $path - * @return string - */ - static public function getMountPoint($path) { - if (!self::$mounts) { - \OC_Util::setupFS(); - } - $mount = self::$mounts->find($path); - if ($mount) { - return $mount->getMountPoint(); - } else { - return ''; - } - } - - /** - * get a list of all mount points in a directory - * - * @param string $path - * @return string[] - */ - static public function getMountPoints($path) { - if (!self::$mounts) { - \OC_Util::setupFS(); - } - $result = array(); - $mounts = self::$mounts->findIn($path); - foreach ($mounts as $mount) { - $result[] = $mount->getMountPoint(); - } - return $result; - } - - /** - * get the storage mounted at $mountPoint - * - * @param string $mountPoint - * @return \OC\Files\Storage\Storage - */ - public static function getStorage($mountPoint) { - if (!self::$mounts) { - \OC_Util::setupFS(); - } - $mount = self::$mounts->find($mountPoint); - return $mount->getStorage(); - } - - /** - * @param string $id - * @return Mount\MountPoint[] - */ - public static function getMountByStorageId($id) { - if (!self::$mounts) { - \OC_Util::setupFS(); - } - return self::$mounts->findByStorageId($id); - } - - /** - * @param int $id - * @return Mount\MountPoint[] - */ - public static function getMountByNumericId($id) { - if (!self::$mounts) { - \OC_Util::setupFS(); - } - return self::$mounts->findByNumericId($id); - } - - /** - * resolve a path to a storage and internal path - * - * @param string $path - * @return array an array consisting of the storage and the internal path - */ - static public function resolvePath($path) { - if (!self::$mounts) { - \OC_Util::setupFS(); - } - $mount = self::$mounts->find($path); - if ($mount) { - return array($mount->getStorage(), rtrim($mount->getInternalPath($path), '/')); - } else { - return array(null, null); - } - } - - static public function init($user, $root) { - if (self::$defaultInstance) { - return false; - } - self::getLoader(); - self::$defaultInstance = new View($root); - - if (!self::$mounts) { - self::$mounts = \OC::$server->getMountManager(); - } - - //load custom mount config - self::initMountPoints($user); - - self::$loaded = true; - - return true; - } - - static public function initMountManager() { - if (!self::$mounts) { - self::$mounts = \OC::$server->getMountManager(); - } - } - - /** - * Initialize system and personal mount points for a user - * - * @param string $user - * @throws \OC\User\NoUserException if the user is not available - */ - public static function initMountPoints($user = '') { - if ($user == '') { - $user = \OC_User::getUser(); - } - if ($user === null || $user === false || $user === '') { - throw new \OC\User\NoUserException('Attempted to initialize mount points for null user and no user in session'); - } - if (isset(self::$usersSetup[$user])) { - return; - } - $root = \OC_User::getHome($user); - - $userManager = \OC::$server->getUserManager(); - $userObject = $userManager->get($user); - - if (is_null($userObject)) { - \OCP\Util::writeLog('files', ' Backends provided no user object for ' . $user, \OCP\Util::ERROR); - throw new \OC\User\NoUserException('Backends provided no user object for ' . $user); - } - - self::$usersSetup[$user] = true; - - $homeStorage = \OC::$server->getConfig()->getSystemValue('objectstore'); - if (!empty($homeStorage)) { - // sanity checks - if (empty($homeStorage['class'])) { - \OCP\Util::writeLog('files', 'No class given for objectstore', \OCP\Util::ERROR); - } - if (!isset($homeStorage['arguments'])) { - $homeStorage['arguments'] = array(); - } - // instantiate object store implementation - $homeStorage['arguments']['objectstore'] = new $homeStorage['class']($homeStorage['arguments']); - // mount with home object store implementation - $homeStorage['class'] = '\OC\Files\ObjectStore\HomeObjectStoreStorage'; - } else { - $homeStorage = array( - //default home storage configuration: - 'class' => '\OC\Files\Storage\Home', - 'arguments' => array() - ); - } - $homeStorage['arguments']['user'] = $userObject; - - // check for legacy home id (<= 5.0.12) - if (\OC\Files\Cache\Storage::exists('local::' . $root . '/')) { - $homeStorage['arguments']['legacy'] = true; - } - - $mount = new MountPoint($homeStorage['class'], '/' . $user, $homeStorage['arguments'], self::getLoader()); - self::getMountManager()->addMount($mount); - - $home = \OC\Files\Filesystem::getStorage($user); - - self::mountCacheDir($user); - - // Chance to mount for other storages - /** @var \OC\Files\Config\MountProviderCollection $mountConfigManager */ - $mountConfigManager = \OC::$server->getMountProviderCollection(); - if ($userObject) { - $mounts = $mountConfigManager->getMountsForUser($userObject); - array_walk($mounts, array(self::$mounts, 'addMount')); - $mounts[] = $mount; - $mountConfigManager->registerMounts($userObject, $mounts); - } - - self::listenForNewMountProviders($mountConfigManager, $userManager); - \OC_Hook::emit('OC_Filesystem', 'post_initMountPoints', array('user' => $user, 'user_dir' => $root)); - } - - /** - * Get mounts from mount providers that are registered after setup - * - * @param MountProviderCollection $mountConfigManager - * @param IUserManager $userManager - */ - private static function listenForNewMountProviders(MountProviderCollection $mountConfigManager, IUserManager $userManager) { - if (!self::$listeningForProviders) { - self::$listeningForProviders = true; - $mountConfigManager->listen('\OC\Files\Config', 'registerMountProvider', function (IMountProvider $provider) use ($userManager) { - foreach (Filesystem::$usersSetup as $user => $setup) { - $userObject = $userManager->get($user); - if ($userObject) { - $mounts = $provider->getMountsForUser($userObject, Filesystem::getLoader()); - array_walk($mounts, array(self::$mounts, 'addMount')); - } - } - }); - } - } - - /** - * Mounts the cache directory - * - * @param string $user user name - */ - private static function mountCacheDir($user) { - $cacheBaseDir = \OC::$server->getConfig()->getSystemValue('cache_path', ''); - if ($cacheBaseDir !== '') { - $cacheDir = rtrim($cacheBaseDir, '/') . '/' . $user; - if (!file_exists($cacheDir)) { - mkdir($cacheDir, 0770, true); - } - // mount external cache dir to "/$user/cache" mount point - self::mount('\OC\Files\Storage\Local', array('datadir' => $cacheDir), '/' . $user . '/cache'); - } - } - - /** - * get the default filesystem view - * - * @return View - */ - static public function getView() { - return self::$defaultInstance; - } - - /** - * tear down the filesystem, removing all storage providers - */ - static public function tearDown() { - self::clearMounts(); - self::$defaultInstance = null; - } - - /** - * get the relative path of the root data directory for the current user - * - * @return string - * - * Returns path like /admin/files - */ - static public function getRoot() { - if (!self::$defaultInstance) { - return null; - } - return self::$defaultInstance->getRoot(); - } - - /** - * clear all mounts and storage backends - */ - public static function clearMounts() { - if (self::$mounts) { - self::$usersSetup = array(); - self::$mounts->clear(); - } - } - - /** - * mount an \OC\Files\Storage\Storage in our virtual filesystem - * - * @param \OC\Files\Storage\Storage|string $class - * @param array $arguments - * @param string $mountpoint - */ - static public function mount($class, $arguments, $mountpoint) { - if (!self::$mounts) { - \OC_Util::setupFS(); - } - $mount = new Mount\MountPoint($class, $mountpoint, $arguments, self::getLoader()); - self::$mounts->addMount($mount); - } - - /** - * return the path to a local version of the file - * we need this because we can't know if a file is stored local or not from - * outside the filestorage and for some purposes a local file is needed - * - * @param string $path - * @return string - */ - static public function getLocalFile($path) { - return self::$defaultInstance->getLocalFile($path); - } - - /** - * @param string $path - * @return string - */ - static public function getLocalFolder($path) { - return self::$defaultInstance->getLocalFolder($path); - } - - /** - * return path to file which reflects one visible in browser - * - * @param string $path - * @return string - */ - static public function getLocalPath($path) { - $datadir = \OC_User::getHome(\OC_User::getUser()) . '/files'; - $newpath = $path; - if (strncmp($newpath, $datadir, strlen($datadir)) == 0) { - $newpath = substr($path, strlen($datadir)); - } - return $newpath; - } - - /** - * check if the requested path is valid - * - * @param string $path - * @return bool - */ - static public function isValidPath($path) { - $path = self::normalizePath($path); - if (!$path || $path[0] !== '/') { - $path = '/' . $path; - } - if (strpos($path, '/../') !== false || strrchr($path, '/') === '/..') { - return false; - } - return true; - } - - /** - * checks if a file is blacklisted for storage in the filesystem - * Listens to write and rename hooks - * - * @param array $data from hook - */ - static public function isBlacklisted($data) { - if (isset($data['path'])) { - $path = $data['path']; - } else if (isset($data['newpath'])) { - $path = $data['newpath']; - } - if (isset($path)) { - if (self::isFileBlacklisted($path)) { - $data['run'] = false; - } - } - } - - /** - * @param string $filename - * @return bool - */ - static public function isFileBlacklisted($filename) { - $filename = self::normalizePath($filename); - - $blacklist = \OC::$server->getConfig()->getSystemValue('blacklisted_files', array('.htaccess')); - $filename = strtolower(basename($filename)); - return in_array($filename, $blacklist); - } - - /** - * check if the directory should be ignored when scanning - * NOTE: the special directories . and .. would cause never ending recursion - * - * @param String $dir - * @return boolean - */ - static public function isIgnoredDir($dir) { - if ($dir === '.' || $dir === '..') { - return true; - } - return false; - } - - /** - * following functions are equivalent to their php builtin equivalents for arguments/return values. - */ - static public function mkdir($path) { - return self::$defaultInstance->mkdir($path); - } - - static public function rmdir($path) { - return self::$defaultInstance->rmdir($path); - } - - static public function opendir($path) { - return self::$defaultInstance->opendir($path); - } - - static public function readdir($path) { - return self::$defaultInstance->readdir($path); - } - - static public function is_dir($path) { - return self::$defaultInstance->is_dir($path); - } - - static public function is_file($path) { - return self::$defaultInstance->is_file($path); - } - - static public function stat($path) { - return self::$defaultInstance->stat($path); - } - - static public function filetype($path) { - return self::$defaultInstance->filetype($path); - } - - static public function filesize($path) { - return self::$defaultInstance->filesize($path); - } - - static public function readfile($path) { - return self::$defaultInstance->readfile($path); - } - - static public function isCreatable($path) { - return self::$defaultInstance->isCreatable($path); - } - - static public function isReadable($path) { - return self::$defaultInstance->isReadable($path); - } - - static public function isUpdatable($path) { - return self::$defaultInstance->isUpdatable($path); - } - - static public function isDeletable($path) { - return self::$defaultInstance->isDeletable($path); - } - - static public function isSharable($path) { - return self::$defaultInstance->isSharable($path); - } - - static public function file_exists($path) { - return self::$defaultInstance->file_exists($path); - } - - static public function filemtime($path) { - return self::$defaultInstance->filemtime($path); - } - - static public function touch($path, $mtime = null) { - return self::$defaultInstance->touch($path, $mtime); - } - - /** - * @return string - */ - static public function file_get_contents($path) { - return self::$defaultInstance->file_get_contents($path); - } - - static public function file_put_contents($path, $data) { - return self::$defaultInstance->file_put_contents($path, $data); - } - - static public function unlink($path) { - return self::$defaultInstance->unlink($path); - } - - static public function rename($path1, $path2) { - return self::$defaultInstance->rename($path1, $path2); - } - - static public function copy($path1, $path2) { - return self::$defaultInstance->copy($path1, $path2); - } - - static public function fopen($path, $mode) { - return self::$defaultInstance->fopen($path, $mode); - } - - /** - * @return string - */ - static public function toTmpFile($path) { - return self::$defaultInstance->toTmpFile($path); - } - - static public function fromTmpFile($tmpFile, $path) { - return self::$defaultInstance->fromTmpFile($tmpFile, $path); - } - - static public function getMimeType($path) { - return self::$defaultInstance->getMimeType($path); - } - - static public function hash($type, $path, $raw = false) { - return self::$defaultInstance->hash($type, $path, $raw); - } - - static public function free_space($path = '/') { - return self::$defaultInstance->free_space($path); - } - - static public function search($query) { - return self::$defaultInstance->search($query); - } - - /** - * @param string $query - */ - static public function searchByMime($query) { - return self::$defaultInstance->searchByMime($query); - } - - /** - * @param string|int $tag name or tag id - * @param string $userId owner of the tags - * @return FileInfo[] array or file info - */ - static public function searchByTag($tag, $userId) { - return self::$defaultInstance->searchByTag($tag, $userId); - } - - /** - * check if a file or folder has been updated since $time - * - * @param string $path - * @param int $time - * @return bool - */ - static public function hasUpdated($path, $time) { - return self::$defaultInstance->hasUpdated($path, $time); - } - - /** - * Fix common problems with a file path - * - * @param string $path - * @param bool $stripTrailingSlash - * @param bool $isAbsolutePath - * @return string - */ - public static function normalizePath($path, $stripTrailingSlash = true, $isAbsolutePath = false) { - /** - * FIXME: This is a workaround for existing classes and files which call - * this function with another type than a valid string. This - * conversion should get removed as soon as all existing - * function calls have been fixed. - */ - $path = (string)$path; - - $cacheKey = json_encode([$path, $stripTrailingSlash, $isAbsolutePath]); - - if (isset(self::$normalizedPathCache[$cacheKey])) { - return self::$normalizedPathCache[$cacheKey]; - } - - if ($path == '') { - return '/'; - } - - //normalize unicode if possible - $path = \OC_Util::normalizeUnicode($path); - - //no windows style slashes - $path = str_replace('\\', '/', $path); - - // When normalizing an absolute path, we need to ensure that the drive-letter - // is still at the beginning on windows - $windows_drive_letter = ''; - if ($isAbsolutePath && \OC_Util::runningOnWindows() && preg_match('#^([a-zA-Z])$#', $path[0]) && $path[1] == ':' && $path[2] == '/') { - $windows_drive_letter = substr($path, 0, 2); - $path = substr($path, 2); - } - - //add leading slash - if ($path[0] !== '/') { - $path = '/' . $path; - } - - // remove '/./' - // ugly, but str_replace() can't replace them all in one go - // as the replacement itself is part of the search string - // which will only be found during the next iteration - while (strpos($path, '/./') !== false) { - $path = str_replace('/./', '/', $path); - } - // remove sequences of slashes - $path = preg_replace('#/{2,}#', '/', $path); - - //remove trailing slash - if ($stripTrailingSlash and strlen($path) > 1 and substr($path, -1, 1) === '/') { - $path = substr($path, 0, -1); - } - - // remove trailing '/.' - if (substr($path, -2) == '/.') { - $path = substr($path, 0, -2); - } - - $normalizedPath = $windows_drive_letter . $path; - self::$normalizedPathCache[$cacheKey] = $normalizedPath; - - return $normalizedPath; - } - - /** - * get the filesystem info - * - * @param string $path - * @param boolean $includeMountPoints whether to add mountpoint sizes, - * defaults to true - * @return \OC\Files\FileInfo|bool False if file does not exist - */ - public static function getFileInfo($path, $includeMountPoints = true) { - return self::$defaultInstance->getFileInfo($path, $includeMountPoints); - } - - /** - * change file metadata - * - * @param string $path - * @param array $data - * @return int - * - * returns the fileid of the updated file - */ - public static function putFileInfo($path, $data) { - return self::$defaultInstance->putFileInfo($path, $data); - } - - /** - * get the content of a directory - * - * @param string $directory path under datadirectory - * @param string $mimetype_filter limit returned content to this mimetype or mimepart - * @return \OC\Files\FileInfo[] - */ - public static function getDirectoryContent($directory, $mimetype_filter = '') { - return self::$defaultInstance->getDirectoryContent($directory, $mimetype_filter); - } - - /** - * Get the path of a file by id - * - * Note that the resulting path is not guaranteed to be unique for the id, multiple paths can point to the same file - * - * @param int $id - * @throws NotFoundException - * @return string - */ - public static function getPath($id) { - return self::$defaultInstance->getPath($id); - } - - /** - * Get the owner for a file or folder - * - * @param string $path - * @return string - */ - public static function getOwner($path) { - return self::$defaultInstance->getOwner($path); - } - - /** - * get the ETag for a file or folder - * - * @param string $path - * @return string - */ - static public function getETag($path) { - return self::$defaultInstance->getETag($path); - } -} diff --git a/lib/private/files/mount/manager.php b/lib/private/files/mount/manager.php deleted file mode 100644 index ba4a7f8d910..00000000000 --- a/lib/private/files/mount/manager.php +++ /dev/null @@ -1,165 +0,0 @@ - - * @author Morris Jobke - * @author Robin Appelman - * @author Robin McCorkell - * - * @copyright Copyright (c) 2016, ownCloud, Inc. - * @license AGPL-3.0 - * - * This code is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License, version 3, - * along with this program. If not, see - * - */ - -namespace OC\Files\Mount; - -use \OC\Files\Filesystem; -use OCP\Files\Mount\IMountManager; -use OCP\Files\Mount\IMountPoint; - -class Manager implements IMountManager { - /** - * @var MountPoint[] - */ - private $mounts = array(); - - /** - * @param IMountPoint $mount - */ - public function addMount(IMountPoint $mount) { - $this->mounts[$mount->getMountPoint()] = $mount; - } - - /** - * @param string $mountPoint - */ - public function removeMount($mountPoint) { - $mountPoint = Filesystem::normalizePath($mountPoint); - if (strlen($mountPoint) > 1) { - $mountPoint .= '/'; - } - unset($this->mounts[$mountPoint]); - } - - /** - * @param string $mountPoint - * @param string $target - */ - public function moveMount($mountPoint, $target){ - $this->mounts[$target] = $this->mounts[$mountPoint]; - unset($this->mounts[$mountPoint]); - } - - /** - * Find the mount for $path - * - * @param string $path - * @return MountPoint - */ - public function find($path) { - \OC_Util::setupFS(); - $path = $this->formatPath($path); - if (isset($this->mounts[$path])) { - return $this->mounts[$path]; - } - - \OC_Hook::emit('OC_Filesystem', 'get_mountpoint', array('path' => $path)); - $foundMountPoint = ''; - $mountPoints = array_keys($this->mounts); - foreach ($mountPoints as $mountpoint) { - if (strpos($path, $mountpoint) === 0 and strlen($mountpoint) > strlen($foundMountPoint)) { - $foundMountPoint = $mountpoint; - } - } - if (isset($this->mounts[$foundMountPoint])) { - return $this->mounts[$foundMountPoint]; - } else { - return null; - } - } - - /** - * Find all mounts in $path - * - * @param string $path - * @return MountPoint[] - */ - public function findIn($path) { - \OC_Util::setupFS(); - $path = $this->formatPath($path); - $result = array(); - $pathLength = strlen($path); - $mountPoints = array_keys($this->mounts); - foreach ($mountPoints as $mountPoint) { - if (substr($mountPoint, 0, $pathLength) === $path and strlen($mountPoint) > $pathLength) { - $result[] = $this->mounts[$mountPoint]; - } - } - return $result; - } - - public function clear() { - $this->mounts = array(); - } - - /** - * Find mounts by storage id - * - * @param string $id - * @return MountPoint[] - */ - public function findByStorageId($id) { - \OC_Util::setupFS(); - if (strlen($id) > 64) { - $id = md5($id); - } - $result = array(); - foreach ($this->mounts as $mount) { - if ($mount->getStorageId() === $id) { - $result[] = $mount; - } - } - return $result; - } - - /** - * @return MountPoint[] - */ - public function getAll() { - return $this->mounts; - } - - /** - * Find mounts by numeric storage id - * - * @param int $id - * @return MountPoint[] - */ - public function findByNumericId($id) { - $storageId = \OC\Files\Cache\Storage::getStorageId($id); - return $this->findByStorageId($storageId); - } - - /** - * @param string $path - * @return string - */ - private function formatPath($path) { - $path = Filesystem::normalizePath($path); - if (strlen($path) > 1) { - $path .= '/'; - } - return $path; - } -} diff --git a/lib/private/files/mount/mountpoint.php b/lib/private/files/mount/mountpoint.php deleted file mode 100644 index 7b9294fc1e0..00000000000 --- a/lib/private/files/mount/mountpoint.php +++ /dev/null @@ -1,251 +0,0 @@ - - * @author Jörn Friedrich Dreyer - * @author Morris Jobke - * @author Robin Appelman - * @author Robin McCorkell - * @author Thomas Müller - * @author Vincent Petry - * - * @copyright Copyright (c) 2016, ownCloud, Inc. - * @license AGPL-3.0 - * - * This code is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License, version 3, - * along with this program. If not, see - * - */ - -namespace OC\Files\Mount; - -use \OC\Files\Filesystem; -use OC\Files\Storage\StorageFactory; -use OC\Files\Storage\Storage; -use OCP\Files\Mount\IMountPoint; - -class MountPoint implements IMountPoint { - /** - * @var \OC\Files\Storage\Storage $storage - */ - protected $storage = null; - protected $class; - protected $storageId; - - /** - * Configuration options for the storage backend - * - * @var array - */ - protected $arguments = array(); - protected $mountPoint; - - /** - * Mount specific options - * - * @var array - */ - protected $mountOptions = array(); - - /** - * @var \OC\Files\Storage\StorageFactory $loader - */ - private $loader; - - /** - * Specified whether the storage is invalid after failing to - * instantiate it. - * - * @var bool - */ - private $invalidStorage = false; - - /** - * @param string|\OC\Files\Storage\Storage $storage - * @param string $mountpoint - * @param array $arguments (optional) configuration for the storage backend - * @param \OCP\Files\Storage\IStorageFactory $loader - * @param array $mountOptions mount specific options - */ - public function __construct($storage, $mountpoint, $arguments = null, $loader = null, $mountOptions = null) { - if (is_null($arguments)) { - $arguments = array(); - } - if (is_null($loader)) { - $this->loader = new StorageFactory(); - } else { - $this->loader = $loader; - } - - if (!is_null($mountOptions)) { - $this->mountOptions = $mountOptions; - } - - $mountpoint = $this->formatPath($mountpoint); - $this->mountPoint = $mountpoint; - if ($storage instanceof Storage) { - $this->class = get_class($storage); - $this->storage = $this->loader->wrap($this, $storage); - } else { - // Update old classes to new namespace - if (strpos($storage, 'OC_Filestorage_') !== false) { - $storage = '\OC\Files\Storage\\' . substr($storage, 15); - } - $this->class = $storage; - $this->arguments = $arguments; - } - } - - /** - * get complete path to the mount point, relative to data/ - * - * @return string - */ - public function getMountPoint() { - return $this->mountPoint; - } - - /** - * Sets the mount point path, relative to data/ - * - * @param string $mountPoint new mount point - */ - public function setMountPoint($mountPoint) { - $this->mountPoint = $this->formatPath($mountPoint); - } - - /** - * create the storage that is mounted - * - * @return \OC\Files\Storage\Storage - */ - private function createStorage() { - if ($this->invalidStorage) { - return null; - } - - if (class_exists($this->class)) { - try { - return $this->loader->getInstance($this, $this->class, $this->arguments); - } catch (\Exception $exception) { - $this->invalidStorage = true; - if ($this->mountPoint === '/') { - // the root storage could not be initialized, show the user! - throw new \Exception('The root storage could not be initialized. Please contact your local administrator.', $exception->getCode(), $exception); - } else { - \OCP\Util::writeLog('core', $exception->getMessage(), \OCP\Util::ERROR); - } - return null; - } - } else { - \OCP\Util::writeLog('core', 'storage backend ' . $this->class . ' not found', \OCP\Util::ERROR); - $this->invalidStorage = true; - return null; - } - } - - /** - * @return \OC\Files\Storage\Storage - */ - public function getStorage() { - if (is_null($this->storage)) { - $this->storage = $this->createStorage(); - } - return $this->storage; - } - - /** - * @return string - */ - public function getStorageId() { - if (!$this->storageId) { - if (is_null($this->storage)) { - $storage = $this->createStorage(); //FIXME: start using exceptions - if (is_null($storage)) { - return null; - } - - $this->storage = $storage; - } - $this->storageId = $this->storage->getId(); - if (strlen($this->storageId) > 64) { - $this->storageId = md5($this->storageId); - } - } - return $this->storageId; - } - - /** - * @param string $path - * @return string - */ - public function getInternalPath($path) { - if ($this->mountPoint === $path or $this->mountPoint . '/' === $path) { - $internalPath = ''; - } else { - $internalPath = substr($path, strlen($this->mountPoint)); - } - // substr returns false instead of an empty string, we always want a string - return (string)$internalPath; - } - - /** - * @param string $path - * @return string - */ - private function formatPath($path) { - $path = Filesystem::normalizePath($path); - if (strlen($path) > 1) { - $path .= '/'; - } - return $path; - } - - /** - * @param callable $wrapper - */ - public function wrapStorage($wrapper) { - $storage = $this->getStorage(); - // storage can be null if it couldn't be initialized - if ($storage != null) { - $this->storage = $wrapper($this->mountPoint, $storage, $this); - } - } - - /** - * Get a mount option - * - * @param string $name Name of the mount option to get - * @param mixed $default Default value for the mount option - * @return mixed - */ - public function getOption($name, $default) { - return isset($this->mountOptions[$name]) ? $this->mountOptions[$name] : $default; - } - - /** - * Get all options for the mount - * - * @return array - */ - public function getOptions() { - return $this->mountOptions; - } - - /** - * Get the file id of the root of the storage - * - * @return int - */ - public function getStorageRootId() { - return (int)$this->getStorage()->getCache()->getId(''); - } -} diff --git a/lib/private/files/mount/moveablemount.php b/lib/private/files/mount/moveablemount.php deleted file mode 100644 index 8a1bd7dd9c5..00000000000 --- a/lib/private/files/mount/moveablemount.php +++ /dev/null @@ -1,44 +0,0 @@ - - * @author Robin Appelman - * - * @copyright Copyright (c) 2016, ownCloud, Inc. - * @license AGPL-3.0 - * - * This code is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License, version 3, - * along with this program. If not, see - * - */ - -namespace OC\Files\Mount; - -/** - * Defines the mount point to be (re)moved by the user - */ -interface MoveableMount { - /** - * Move the mount point to $target - * - * @param string $target the target mount point - * @return bool - */ - public function moveMount($target); - - /** - * Remove the mount points - * - * @return mixed - * @return bool - */ - public function removeMount(); -} diff --git a/lib/private/files/node/file.php b/lib/private/files/node/file.php deleted file mode 100644 index 9e0014abb0b..00000000000 --- a/lib/private/files/node/file.php +++ /dev/null @@ -1,176 +0,0 @@ - - * @author Morris Jobke - * @author Robin Appelman - * @author Roeland Jago Douma - * @author Vincent Petry - * - * @copyright Copyright (c) 2016, ownCloud, Inc. - * @license AGPL-3.0 - * - * This code is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License, version 3, - * along with this program. If not, see - * - */ - -namespace OC\Files\Node; - -use OCP\Files\NotPermittedException; - -class File extends Node implements \OCP\Files\File { - /** - * @return string - * @throws \OCP\Files\NotPermittedException - */ - public function getContent() { - if ($this->checkPermissions(\OCP\Constants::PERMISSION_READ)) { - /** - * @var \OC\Files\Storage\Storage $storage; - */ - return $this->view->file_get_contents($this->path); - } else { - throw new NotPermittedException(); - } - } - - /** - * @param string $data - * @throws \OCP\Files\NotPermittedException - */ - public function putContent($data) { - if ($this->checkPermissions(\OCP\Constants::PERMISSION_UPDATE)) { - $this->sendHooks(array('preWrite')); - $this->view->file_put_contents($this->path, $data); - $this->fileInfo = null; - $this->sendHooks(array('postWrite')); - } else { - throw new NotPermittedException(); - } - } - - /** - * @param string $mode - * @return resource - * @throws \OCP\Files\NotPermittedException - */ - public function fopen($mode) { - $preHooks = array(); - $postHooks = array(); - $requiredPermissions = \OCP\Constants::PERMISSION_READ; - switch ($mode) { - case 'r+': - case 'rb+': - case 'w+': - case 'wb+': - case 'x+': - case 'xb+': - case 'a+': - case 'ab+': - case 'w': - case 'wb': - case 'x': - case 'xb': - case 'a': - case 'ab': - $preHooks[] = 'preWrite'; - $postHooks[] = 'postWrite'; - $requiredPermissions |= \OCP\Constants::PERMISSION_UPDATE; - break; - } - - if ($this->checkPermissions($requiredPermissions)) { - $this->sendHooks($preHooks); - $result = $this->view->fopen($this->path, $mode); - $this->sendHooks($postHooks); - return $result; - } else { - throw new NotPermittedException(); - } - } - - public function delete() { - if ($this->checkPermissions(\OCP\Constants::PERMISSION_DELETE)) { - $this->sendHooks(array('preDelete')); - $fileInfo = $this->getFileInfo(); - $this->view->unlink($this->path); - $nonExisting = new NonExistingFile($this->root, $this->view, $this->path, $fileInfo); - $this->root->emit('\OC\Files', 'postDelete', array($nonExisting)); - $this->exists = false; - $this->fileInfo = null; - } else { - throw new NotPermittedException(); - } - } - - /** - * @param string $targetPath - * @throws \OCP\Files\NotPermittedException - * @return \OC\Files\Node\Node - */ - public function copy($targetPath) { - $targetPath = $this->normalizePath($targetPath); - $parent = $this->root->get(dirname($targetPath)); - if ($parent instanceof Folder and $this->isValidPath($targetPath) and $parent->isCreatable()) { - $nonExisting = new NonExistingFile($this->root, $this->view, $targetPath); - $this->root->emit('\OC\Files', 'preCopy', array($this, $nonExisting)); - $this->root->emit('\OC\Files', 'preWrite', array($nonExisting)); - $this->view->copy($this->path, $targetPath); - $targetNode = $this->root->get($targetPath); - $this->root->emit('\OC\Files', 'postCopy', array($this, $targetNode)); - $this->root->emit('\OC\Files', 'postWrite', array($targetNode)); - return $targetNode; - } else { - throw new NotPermittedException(); - } - } - - /** - * @param string $targetPath - * @throws \OCP\Files\NotPermittedException - * @return \OC\Files\Node\Node - */ - public function move($targetPath) { - $targetPath = $this->normalizePath($targetPath); - $parent = $this->root->get(dirname($targetPath)); - if ($parent instanceof Folder and $this->isValidPath($targetPath) and $parent->isCreatable()) { - $nonExisting = new NonExistingFile($this->root, $this->view, $targetPath); - $this->root->emit('\OC\Files', 'preRename', array($this, $nonExisting)); - $this->root->emit('\OC\Files', 'preWrite', array($nonExisting)); - $this->view->rename($this->path, $targetPath); - $targetNode = $this->root->get($targetPath); - $this->root->emit('\OC\Files', 'postRename', array($this, $targetNode)); - $this->root->emit('\OC\Files', 'postWrite', array($targetNode)); - $this->path = $targetPath; - $this->fileInfo = null; - return $targetNode; - } else { - throw new NotPermittedException(); - } - } - - /** - * @param string $type - * @param bool $raw - * @return string - */ - public function hash($type, $raw = false) { - return $this->view->hash($type, $this->path, $raw); - } - - /** - * @inheritdoc - */ - public function getChecksum() { - return $this->getFileInfo()->getChecksum(); - } -} diff --git a/lib/private/files/node/folder.php b/lib/private/files/node/folder.php deleted file mode 100644 index f4d7dae20a3..00000000000 --- a/lib/private/files/node/folder.php +++ /dev/null @@ -1,360 +0,0 @@ - - * @author Morris Jobke - * @author Robin Appelman - * @author Robin McCorkell - * @author Vincent Petry - * - * @copyright Copyright (c) 2016, ownCloud, Inc. - * @license AGPL-3.0 - * - * This code is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License, version 3, - * along with this program. If not, see - * - */ - -namespace OC\Files\Node; - -use OCP\Files\FileInfo; -use OCP\Files\NotFoundException; -use OCP\Files\NotPermittedException; - -class Folder extends Node implements \OCP\Files\Folder { - /** - * @param string $path path relative to the folder - * @return string - * @throws \OCP\Files\NotPermittedException - */ - public function getFullPath($path) { - if (!$this->isValidPath($path)) { - throw new NotPermittedException(); - } - return $this->path . $this->normalizePath($path); - } - - /** - * @param string $path - * @return string - */ - public function getRelativePath($path) { - if ($this->path === '' or $this->path === '/') { - return $this->normalizePath($path); - } - if ($path === $this->path) { - return '/'; - } else if (strpos($path, $this->path . '/') !== 0) { - return null; - } else { - $path = substr($path, strlen($this->path)); - return $this->normalizePath($path); - } - } - - /** - * check if a node is a (grand-)child of the folder - * - * @param \OC\Files\Node\Node $node - * @return bool - */ - public function isSubNode($node) { - return strpos($node->getPath(), $this->path . '/') === 0; - } - - /** - * get the content of this directory - * - * @throws \OCP\Files\NotFoundException - * @return Node[] - */ - public function getDirectoryListing() { - $folderContent = $this->view->getDirectoryContent($this->path); - - return array_map(function(FileInfo $info) { - if ($info->getMimetype() === 'httpd/unix-directory') { - return new Folder($this->root, $this->view, $info->getPath(), $info); - } else { - return new File($this->root, $this->view, $info->getPath(), $info); - } - }, $folderContent); - } - - /** - * @param string $path - * @param FileInfo $info - * @return File|Folder - */ - protected function createNode($path, FileInfo $info = null) { - if (is_null($info)) { - $isDir = $this->view->is_dir($path); - } else { - $isDir = $info->getType() === FileInfo::TYPE_FOLDER; - } - if ($isDir) { - return new Folder($this->root, $this->view, $path, $info); - } else { - return new File($this->root, $this->view, $path, $info); - } - } - - /** - * Get the node at $path - * - * @param string $path - * @return \OC\Files\Node\Node - * @throws \OCP\Files\NotFoundException - */ - public function get($path) { - return $this->root->get($this->getFullPath($path)); - } - - /** - * @param string $path - * @return bool - */ - public function nodeExists($path) { - try { - $this->get($path); - return true; - } catch (NotFoundException $e) { - return false; - } - } - - /** - * @param string $path - * @return \OC\Files\Node\Folder - * @throws \OCP\Files\NotPermittedException - */ - public function newFolder($path) { - if ($this->checkPermissions(\OCP\Constants::PERMISSION_CREATE)) { - $fullPath = $this->getFullPath($path); - $nonExisting = new NonExistingFolder($this->root, $this->view, $fullPath); - $this->root->emit('\OC\Files', 'preWrite', array($nonExisting)); - $this->root->emit('\OC\Files', 'preCreate', array($nonExisting)); - $this->view->mkdir($fullPath); - $node = new Folder($this->root, $this->view, $fullPath); - $this->root->emit('\OC\Files', 'postWrite', array($node)); - $this->root->emit('\OC\Files', 'postCreate', array($node)); - return $node; - } else { - throw new NotPermittedException(); - } - } - - /** - * @param string $path - * @return \OC\Files\Node\File - * @throws \OCP\Files\NotPermittedException - */ - public function newFile($path) { - if ($this->checkPermissions(\OCP\Constants::PERMISSION_CREATE)) { - $fullPath = $this->getFullPath($path); - $nonExisting = new NonExistingFile($this->root, $this->view, $fullPath); - $this->root->emit('\OC\Files', 'preWrite', array($nonExisting)); - $this->root->emit('\OC\Files', 'preCreate', array($nonExisting)); - $this->view->touch($fullPath); - $node = new File($this->root, $this->view, $fullPath); - $this->root->emit('\OC\Files', 'postWrite', array($node)); - $this->root->emit('\OC\Files', 'postCreate', array($node)); - return $node; - } else { - throw new NotPermittedException(); - } - } - - /** - * search for files with the name matching $query - * - * @param string $query - * @return \OC\Files\Node\Node[] - */ - public function search($query) { - return $this->searchCommon('search', array('%' . $query . '%')); - } - - /** - * search for files by mimetype - * - * @param string $mimetype - * @return Node[] - */ - public function searchByMime($mimetype) { - return $this->searchCommon('searchByMime', array($mimetype)); - } - - /** - * search for files by tag - * - * @param string|int $tag name or tag id - * @param string $userId owner of the tags - * @return Node[] - */ - public function searchByTag($tag, $userId) { - return $this->searchCommon('searchByTag', array($tag, $userId)); - } - - /** - * @param string $method cache method - * @param array $args call args - * @return \OC\Files\Node\Node[] - */ - private function searchCommon($method, $args) { - $files = array(); - $rootLength = strlen($this->path); - $mount = $this->root->getMount($this->path); - $storage = $mount->getStorage(); - $internalPath = $mount->getInternalPath($this->path); - $internalPath = rtrim($internalPath, '/'); - if ($internalPath !== '') { - $internalPath = $internalPath . '/'; - } - $internalRootLength = strlen($internalPath); - - $cache = $storage->getCache(''); - - $results = call_user_func_array(array($cache, $method), $args); - foreach ($results as $result) { - if ($internalRootLength === 0 or substr($result['path'], 0, $internalRootLength) === $internalPath) { - $result['internalPath'] = $result['path']; - $result['path'] = substr($result['path'], $internalRootLength); - $result['storage'] = $storage; - $files[] = new \OC\Files\FileInfo($this->path . '/' . $result['path'], $storage, $result['internalPath'], $result, $mount); - } - } - - $mounts = $this->root->getMountsIn($this->path); - foreach ($mounts as $mount) { - $storage = $mount->getStorage(); - if ($storage) { - $cache = $storage->getCache(''); - - $relativeMountPoint = substr($mount->getMountPoint(), $rootLength); - $results = call_user_func_array(array($cache, $method), $args); - foreach ($results as $result) { - $result['internalPath'] = $result['path']; - $result['path'] = $relativeMountPoint . $result['path']; - $result['storage'] = $storage; - $files[] = new \OC\Files\FileInfo($this->path . '/' . $result['path'], $storage, $result['internalPath'], $result, $mount); - } - } - } - - return array_map(function(FileInfo $file) { - return $this->createNode($file->getPath(), $file); - }, $files); - } - - /** - * @param int $id - * @return \OC\Files\Node\Node[] - */ - public function getById($id) { - $mounts = $this->root->getMountsIn($this->path); - $mounts[] = $this->root->getMount($this->path); - // reverse the array so we start with the storage this view is in - // which is the most likely to contain the file we're looking for - $mounts = array_reverse($mounts); - - $nodes = array(); - foreach ($mounts as $mount) { - /** - * @var \OC\Files\Mount\MountPoint $mount - */ - if ($mount->getStorage()) { - $cache = $mount->getStorage()->getCache(); - $internalPath = $cache->getPathById($id); - if (is_string($internalPath)) { - $fullPath = $mount->getMountPoint() . $internalPath; - if (!is_null($path = $this->getRelativePath($fullPath))) { - $nodes[] = $this->get($path); - } - } - } - } - return $nodes; - } - - public function getFreeSpace() { - return $this->view->free_space($this->path); - } - - public function delete() { - if ($this->checkPermissions(\OCP\Constants::PERMISSION_DELETE)) { - $this->sendHooks(array('preDelete')); - $fileInfo = $this->getFileInfo(); - $this->view->rmdir($this->path); - $nonExisting = new NonExistingFolder($this->root, $this->view, $this->path, $fileInfo); - $this->root->emit('\OC\Files', 'postDelete', array($nonExisting)); - $this->exists = false; - } else { - throw new NotPermittedException(); - } - } - - /** - * @param string $targetPath - * @throws \OCP\Files\NotPermittedException - * @return \OC\Files\Node\Node - */ - public function copy($targetPath) { - $targetPath = $this->normalizePath($targetPath); - $parent = $this->root->get(dirname($targetPath)); - if ($parent instanceof Folder and $this->isValidPath($targetPath) and $parent->isCreatable()) { - $nonExisting = new NonExistingFolder($this->root, $this->view, $targetPath); - $this->root->emit('\OC\Files', 'preCopy', array($this, $nonExisting)); - $this->root->emit('\OC\Files', 'preWrite', array($nonExisting)); - $this->view->copy($this->path, $targetPath); - $targetNode = $this->root->get($targetPath); - $this->root->emit('\OC\Files', 'postCopy', array($this, $targetNode)); - $this->root->emit('\OC\Files', 'postWrite', array($targetNode)); - return $targetNode; - } else { - throw new NotPermittedException(); - } - } - - /** - * @param string $targetPath - * @throws \OCP\Files\NotPermittedException - * @return \OC\Files\Node\Node - */ - public function move($targetPath) { - $targetPath = $this->normalizePath($targetPath); - $parent = $this->root->get(dirname($targetPath)); - if ($parent instanceof Folder and $this->isValidPath($targetPath) and $parent->isCreatable()) { - $nonExisting = new NonExistingFolder($this->root, $this->view, $targetPath); - $this->root->emit('\OC\Files', 'preRename', array($this, $nonExisting)); - $this->root->emit('\OC\Files', 'preWrite', array($nonExisting)); - $this->view->rename($this->path, $targetPath); - $targetNode = $this->root->get($targetPath); - $this->root->emit('\OC\Files', 'postRename', array($this, $targetNode)); - $this->root->emit('\OC\Files', 'postWrite', array($targetNode)); - $this->path = $targetPath; - return $targetNode; - } else { - throw new NotPermittedException(); - } - } - - /** - * Add a suffix to the name in case the file exists - * - * @param string $name - * @return string - * @throws NotPermittedException - */ - public function getNonExistingName($name) { - $uniqueName = \OC_Helper::buildNotExistingFileNameForView($this->getPath(), $name, $this->view); - return trim($this->getRelativePath($uniqueName), '/'); - } -} diff --git a/lib/private/files/node/hookconnector.php b/lib/private/files/node/hookconnector.php deleted file mode 100644 index 5c36ca3848e..00000000000 --- a/lib/private/files/node/hookconnector.php +++ /dev/null @@ -1,164 +0,0 @@ - - * - * @copyright Copyright (c) 2016, ownCloud, Inc. - * @license AGPL-3.0 - * - * This code is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License, version 3, - * along with this program. If not, see - * - */ - -namespace OC\Files\Node; - -use OCP\Files\FileInfo; -use OC\Files\Filesystem; -use OC\Files\View; -use OCP\Util; - -class HookConnector { - /** - * @var Root - */ - private $root; - - /** - * @var View - */ - private $view; - - /** - * @var FileInfo[] - */ - private $deleteMetaCache = []; - - /** - * HookConnector constructor. - * - * @param Root $root - * @param View $view - */ - public function __construct(Root $root, View $view) { - $this->root = $root; - $this->view = $view; - } - - public function viewToNode() { - Util::connectHook('OC_Filesystem', 'write', $this, 'write'); - Util::connectHook('OC_Filesystem', 'post_write', $this, 'postWrite'); - - Util::connectHook('OC_Filesystem', 'create', $this, 'create'); - Util::connectHook('OC_Filesystem', 'post_create', $this, 'postCreate'); - - Util::connectHook('OC_Filesystem', 'delete', $this, 'delete'); - Util::connectHook('OC_Filesystem', 'post_delete', $this, 'postDelete'); - - Util::connectHook('OC_Filesystem', 'rename', $this, 'rename'); - Util::connectHook('OC_Filesystem', 'post_rename', $this, 'postRename'); - - Util::connectHook('OC_Filesystem', 'copy', $this, 'copy'); - Util::connectHook('OC_Filesystem', 'post_copy', $this, 'postCopy'); - - Util::connectHook('OC_Filesystem', 'touch', $this, 'touch'); - Util::connectHook('OC_Filesystem', 'post_touch', $this, 'postTouch'); - } - - public function write($arguments) { - $node = $this->getNodeForPath($arguments['path']); - $this->root->emit('\OC\Files', 'preWrite', [$node]); - } - - public function postWrite($arguments) { - $node = $this->getNodeForPath($arguments['path']); - $this->root->emit('\OC\Files', 'postWrite', [$node]); - } - - public function create($arguments) { - $node = $this->getNodeForPath($arguments['path']); - $this->root->emit('\OC\Files', 'preCreate', [$node]); - } - - public function postCreate($arguments) { - $node = $this->getNodeForPath($arguments['path']); - $this->root->emit('\OC\Files', 'postCreate', [$node]); - } - - public function delete($arguments) { - $node = $this->getNodeForPath($arguments['path']); - $this->deleteMetaCache[$node->getPath()] = $node->getFileInfo(); - $this->root->emit('\OC\Files', 'preDelete', [$node]); - } - - public function postDelete($arguments) { - $node = $this->getNodeForPath($arguments['path']); - unset($this->deleteMetaCache[$node->getPath()]); - $this->root->emit('\OC\Files', 'postDelete', [$node]); - } - - public function touch($arguments) { - $node = $this->getNodeForPath($arguments['path']); - $this->root->emit('\OC\Files', 'preTouch', [$node]); - } - - public function postTouch($arguments) { - $node = $this->getNodeForPath($arguments['path']); - $this->root->emit('\OC\Files', 'postTouch', [$node]); - } - - public function rename($arguments) { - $source = $this->getNodeForPath($arguments['oldpath']); - $target = $this->getNodeForPath($arguments['newpath']); - $this->root->emit('\OC\Files', 'preRename', [$source, $target]); - } - - public function postRename($arguments) { - $source = $this->getNodeForPath($arguments['oldpath']); - $target = $this->getNodeForPath($arguments['newpath']); - $this->root->emit('\OC\Files', 'postRename', [$source, $target]); - } - - public function copy($arguments) { - $source = $this->getNodeForPath($arguments['oldpath']); - $target = $this->getNodeForPath($arguments['newpath']); - $this->root->emit('\OC\Files', 'preCopy', [$source, $target]); - } - - public function postCopy($arguments) { - $source = $this->getNodeForPath($arguments['oldpath']); - $target = $this->getNodeForPath($arguments['newpath']); - $this->root->emit('\OC\Files', 'postCopy', [$source, $target]); - } - - private function getNodeForPath($path) { - $info = Filesystem::getView()->getFileInfo($path); - if (!$info) { - - $fullPath = Filesystem::getView()->getAbsolutePath($path); - if (isset($this->deleteMetaCache[$fullPath])) { - $info = $this->deleteMetaCache[$fullPath]; - } else { - $info = null; - } - if (Filesystem::is_dir($path)) { - return new NonExistingFolder($this->root, $this->view, $fullPath, $info); - } else { - return new NonExistingFile($this->root, $this->view, $fullPath, $info); - } - } - if ($info->getType() === FileInfo::TYPE_FILE) { - return new File($this->root, $this->view, $info->getPath(), $info); - } else { - return new Folder($this->root, $this->view, $info->getPath(), $info); - } - } -} diff --git a/lib/private/files/node/lazyroot.php b/lib/private/files/node/lazyroot.php deleted file mode 100644 index 9661f036579..00000000000 --- a/lib/private/files/node/lazyroot.php +++ /dev/null @@ -1,474 +0,0 @@ - - * - * @copyright Copyright (c) 2016, ownCloud, Inc. - * @license AGPL-3.0 - * - * This code is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License, version 3, - * along with this program. If not, see - * - */ -namespace OC\Files\Node; - -use OC\Files\Mount\MountPoint; -use OCP\Files\IRootFolder; -use OCP\Files\NotPermittedException; - -/** - * Class LazyRoot - * - * This is a lazy wrapper around the root. So only - * once it is needed this will get initialized. - * - * @package OC\Files\Node - */ -class LazyRoot implements IRootFolder { - /** @var \Closure */ - private $rootFolderClosure; - - /** @var IRootFolder */ - private $rootFolder; - - /** - * LazyRoot constructor. - * - * @param \Closure $rootFolderClosure - */ - public function __construct(\Closure $rootFolderClosure) { - $this->rootFolderClosure = $rootFolderClosure; - } - - /** - * Magic method to first get the real rootFolder and then - * call $method with $args on it - * - * @param $method - * @param $args - * @return mixed - */ - public function __call($method, $args) { - if ($this->rootFolder === null) { - $this->rootFolder = call_user_func($this->rootFolderClosure); - } - - return call_user_func_array([$this->rootFolder, $method], $args); - } - - /** - * @inheritDoc - */ - public function getUser() { - return $this->__call(__FUNCTION__, func_get_args()); - } - - /** - * @inheritDoc - */ - public function listen($scope, $method, callable $callback) { - $this->__call(__FUNCTION__, func_get_args()); - } - - /** - * @inheritDoc - */ - public function removeListener($scope = null, $method = null, callable $callback = null) { - $this->__call(__FUNCTION__, func_get_args()); - } - - /** - * @inheritDoc - */ - public function emit($scope, $method, $arguments = array()) { - $this->__call(__FUNCTION__, func_get_args()); - } - - /** - * @inheritDoc - */ - public function mount($storage, $mountPoint, $arguments = array()) { - $this->__call(__FUNCTION__, func_get_args()); - } - - /** - * @inheritDoc - */ - public function getMount($mountPoint) { - return $this->__call(__FUNCTION__, func_get_args()); - } - - /** - * @inheritDoc - */ - public function getMountsIn($mountPoint) { - return $this->__call(__FUNCTION__, func_get_args()); - } - - /** - * @inheritDoc - */ - public function getMountByStorageId($storageId) { - return $this->__call(__FUNCTION__, func_get_args()); - } - - /** - * @inheritDoc - */ - public function getMountByNumericStorageId($numericId) { - return $this->__call(__FUNCTION__, func_get_args()); - } - - /** - * @inheritDoc - */ - public function unMount($mount) { - $this->__call(__FUNCTION__, func_get_args()); - } - - /** - * @inheritDoc - */ - public function get($path) { - $this->__call(__FUNCTION__, func_get_args()); - } - - /** - * @inheritDoc - */ - public function rename($targetPath) { - return $this->__call(__FUNCTION__, func_get_args()); - } - - /** - * @inheritDoc - */ - public function delete() { - return $this->__call(__FUNCTION__, func_get_args()); - } - - /** - * @inheritDoc - */ - public function copy($targetPath) { - return $this->__call(__FUNCTION__, func_get_args()); - } - - /** - * @inheritDoc - */ - public function touch($mtime = null) { - $this->__call(__FUNCTION__, func_get_args()); - } - - /** - * @inheritDoc - */ - public function getStorage() { - return $this->__call(__FUNCTION__, func_get_args()); - } - - /** - * @inheritDoc - */ - public function getPath() { - return $this->__call(__FUNCTION__, func_get_args()); - } - - /** - * @inheritDoc - */ - public function getInternalPath() { - return $this->__call(__FUNCTION__, func_get_args()); - } - - /** - * @inheritDoc - */ - public function getId() { - return $this->__call(__FUNCTION__, func_get_args()); - } - - /** - * @inheritDoc - */ - public function stat() { - return $this->__call(__FUNCTION__, func_get_args()); - } - - /** - * @inheritDoc - */ - public function getMTime() { - return $this->__call(__FUNCTION__, func_get_args()); - } - - /** - * @inheritDoc - */ - public function getSize() { - return $this->__call(__FUNCTION__, func_get_args()); - } - - /** - * @inheritDoc - */ - public function getEtag() { - return $this->__call(__FUNCTION__, func_get_args()); - } - - /** - * @inheritDoc - */ - public function getPermissions() { - return $this->__call(__FUNCTION__, func_get_args()); - } - - /** - * @inheritDoc - */ - public function isReadable() { - return $this->__call(__FUNCTION__, func_get_args()); - } - - /** - * @inheritDoc - */ - public function isUpdateable() { - return $this->__call(__FUNCTION__, func_get_args()); - } - - /** - * @inheritDoc - */ - public function isDeletable() { - return $this->__call(__FUNCTION__, func_get_args()); - } - - /** - * @inheritDoc - */ - public function isShareable() { - return $this->__call(__FUNCTION__, func_get_args()); - } - - /** - * @inheritDoc - */ - public function getParent() { - return $this->__call(__FUNCTION__, func_get_args()); - } - - /** - * @inheritDoc - */ - public function getName() { - return $this->__call(__FUNCTION__, func_get_args()); - } - - /** - * @inheritDoc - */ - public function getUserFolder($userId) { - return $this->__call(__FUNCTION__, func_get_args()); - } - - /** - * @inheritDoc - */ - public function getMimetype() { - return $this->__call(__FUNCTION__, func_get_args()); - } - - /** - * @inheritDoc - */ - public function getMimePart() { - return $this->__call(__FUNCTION__, func_get_args()); - } - - /** - * @inheritDoc - */ - public function isEncrypted() { - return $this->__call(__FUNCTION__, func_get_args()); - } - - /** - * @inheritDoc - */ - public function getType() { - return $this->__call(__FUNCTION__, func_get_args()); - } - - /** - * @inheritDoc - */ - public function isShared() { - return $this->__call(__FUNCTION__, func_get_args()); - } - - /** - * @inheritDoc - */ - public function isMounted() { - return $this->__call(__FUNCTION__, func_get_args()); - } - - /** - * @inheritDoc - */ - public function getMountPoint() { - return $this->__call(__FUNCTION__, func_get_args()); - } - - /** - * @inheritDoc - */ - public function getOwner() { - return $this->__call(__FUNCTION__, func_get_args()); - } - - /** - * @inheritDoc - */ - public function getChecksum() { - return $this->__call(__FUNCTION__, func_get_args()); - } - - /** - * @inheritDoc - */ - public function getFullPath($path) { - return $this->__call(__FUNCTION__, func_get_args()); - } - - /** - * @inheritDoc - */ - public function getRelativePath($path) { - return $this->__call(__FUNCTION__, func_get_args()); - } - - /** - * @inheritDoc - */ - public function isSubNode($node) { - return $this->__call(__FUNCTION__, func_get_args()); - } - - /** - * @inheritDoc - */ - public function getDirectoryListing() { - return $this->__call(__FUNCTION__, func_get_args()); - } - - /** - * @inheritDoc - */ - public function nodeExists($path) { - return $this->__call(__FUNCTION__, func_get_args()); - } - - /** - * @inheritDoc - */ - public function newFolder($path) { - return $this->__call(__FUNCTION__, func_get_args()); - } - - /** - * @inheritDoc - */ - public function newFile($path) { - return $this->__call(__FUNCTION__, func_get_args()); - } - - /** - * @inheritDoc - */ - public function search($query) { - return $this->__call(__FUNCTION__, func_get_args()); - } - - /** - * @inheritDoc - */ - public function searchByMime($mimetype) { - return $this->__call(__FUNCTION__, func_get_args()); - } - - /** - * @inheritDoc - */ - public function searchByTag($tag, $userId) { - return $this->__call(__FUNCTION__, func_get_args()); - } - - /** - * @inheritDoc - */ - public function getById($id) { - return $this->__call(__FUNCTION__, func_get_args()); - } - - /** - * @inheritDoc - */ - public function getFreeSpace() { - return $this->__call(__FUNCTION__, func_get_args()); - } - - /** - * @inheritDoc - */ - public function isCreatable() { - return $this->__call(__FUNCTION__, func_get_args()); - } - - /** - * @inheritDoc - */ - public function getNonExistingName($name) { - return $this->__call(__FUNCTION__, func_get_args()); - } - - /** - * @inheritDoc - */ - public function move($targetPath) { - return $this->__call(__FUNCTION__, func_get_args()); - } - - /** - * @inheritDoc - */ - public function lock($type) { - return $this->__call(__FUNCTION__, func_get_args()); - } - - /** - * @inheritDoc - */ - public function changeLock($targetType) { - return $this->__call(__FUNCTION__, func_get_args()); - } - - /** - * @inheritDoc - */ - public function unlock($type) { - return $this->__call(__FUNCTION__, func_get_args()); - } - - -} diff --git a/lib/private/files/node/node.php b/lib/private/files/node/node.php deleted file mode 100644 index c4fabfc2e2e..00000000000 --- a/lib/private/files/node/node.php +++ /dev/null @@ -1,383 +0,0 @@ - - * @author Joas Schilling - * @author Morris Jobke - * @author Robin Appelman - * @author Roeland Jago Douma - * @author Vincent Petry - * - * @copyright Copyright (c) 2016, ownCloud, Inc. - * @license AGPL-3.0 - * - * This code is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License, version 3, - * along with this program. If not, see - * - */ - -namespace OC\Files\Node; - -use OC\Files\Filesystem; -use OCP\Files\FileInfo; -use OCP\Files\InvalidPathException; -use OCP\Files\NotFoundException; -use OCP\Files\NotPermittedException; - -class Node implements \OCP\Files\Node { - /** - * @var \OC\Files\View $view - */ - protected $view; - - /** - * @var \OC\Files\Node\Root $root - */ - protected $root; - - /** - * @var string $path - */ - protected $path; - - /** - * @var \OCP\Files\FileInfo - */ - protected $fileInfo; - - /** - * @param \OC\Files\View $view - * @param \OC\Files\Node\Root $root - * @param string $path - * @param FileInfo $fileInfo - */ - public function __construct($root, $view, $path, $fileInfo = null) { - $this->view = $view; - $this->root = $root; - $this->path = $path; - $this->fileInfo = $fileInfo; - } - - /** - * Returns the matching file info - * - * @return FileInfo - * @throws InvalidPathException - * @throws NotFoundException - */ - public function getFileInfo() { - if (!Filesystem::isValidPath($this->path)) { - throw new InvalidPathException(); - } - if (!$this->fileInfo) { - $fileInfo = $this->view->getFileInfo($this->path); - if ($fileInfo instanceof FileInfo) { - $this->fileInfo = $fileInfo; - } else { - throw new NotFoundException(); - } - } - return $this->fileInfo; - } - - /** - * @param string[] $hooks - */ - protected function sendHooks($hooks) { - foreach ($hooks as $hook) { - $this->root->emit('\OC\Files', $hook, array($this)); - } - } - - /** - * @param int $permissions - * @return bool - */ - protected function checkPermissions($permissions) { - return ($this->getPermissions() & $permissions) === $permissions; - } - - /** - * @param string $targetPath - * @throws \OCP\Files\NotPermittedException - * @return \OC\Files\Node\Node - */ - public function move($targetPath) { - return; - } - - public function delete() { - return; - } - - /** - * @param string $targetPath - * @return \OC\Files\Node\Node - */ - public function copy($targetPath) { - return; - } - - /** - * @param int $mtime - * @throws \OCP\Files\NotPermittedException - */ - public function touch($mtime = null) { - if ($this->checkPermissions(\OCP\Constants::PERMISSION_UPDATE)) { - $this->sendHooks(array('preTouch')); - $this->view->touch($this->path, $mtime); - $this->sendHooks(array('postTouch')); - if ($this->fileInfo) { - if (is_null($mtime)) { - $mtime = time(); - } - $this->fileInfo['mtime'] = $mtime; - } - } else { - throw new NotPermittedException(); - } - } - - /** - * @return \OC\Files\Storage\Storage - * @throws \OCP\Files\NotFoundException - */ - public function getStorage() { - list($storage,) = $this->view->resolvePath($this->path); - return $storage; - } - - /** - * @return string - */ - public function getPath() { - return $this->path; - } - - /** - * @return string - */ - public function getInternalPath() { - list(, $internalPath) = $this->view->resolvePath($this->path); - return $internalPath; - } - - /** - * @return int - * @throws InvalidPathException - * @throws NotFoundException - */ - public function getId() { - return $this->getFileInfo()->getId(); - } - - /** - * @return array - */ - public function stat() { - return $this->view->stat($this->path); - } - - /** - * @return int - * @throws InvalidPathException - * @throws NotFoundException - */ - public function getMTime() { - return $this->getFileInfo()->getMTime(); - } - - /** - * @return int - * @throws InvalidPathException - * @throws NotFoundException - */ - public function getSize() { - return $this->getFileInfo()->getSize(); - } - - /** - * @return string - * @throws InvalidPathException - * @throws NotFoundException - */ - public function getEtag() { - return $this->getFileInfo()->getEtag(); - } - - /** - * @return int - * @throws InvalidPathException - * @throws NotFoundException - */ - public function getPermissions() { - return $this->getFileInfo()->getPermissions(); - } - - /** - * @return bool - * @throws InvalidPathException - * @throws NotFoundException - */ - public function isReadable() { - return $this->getFileInfo()->isReadable(); - } - - /** - * @return bool - * @throws InvalidPathException - * @throws NotFoundException - */ - public function isUpdateable() { - return $this->getFileInfo()->isUpdateable(); - } - - /** - * @return bool - * @throws InvalidPathException - * @throws NotFoundException - */ - public function isDeletable() { - return $this->getFileInfo()->isDeletable(); - } - - /** - * @return bool - * @throws InvalidPathException - * @throws NotFoundException - */ - public function isShareable() { - return $this->getFileInfo()->isShareable(); - } - - /** - * @return bool - * @throws InvalidPathException - * @throws NotFoundException - */ - public function isCreatable() { - return $this->getFileInfo()->isCreatable(); - } - - /** - * @return Node - */ - public function getParent() { - return $this->root->get(dirname($this->path)); - } - - /** - * @return string - */ - public function getName() { - return basename($this->path); - } - - /** - * @param string $path - * @return string - */ - protected function normalizePath($path) { - if ($path === '' or $path === '/') { - return '/'; - } - //no windows style slashes - $path = str_replace('\\', '/', $path); - //add leading slash - if ($path[0] !== '/') { - $path = '/' . $path; - } - //remove duplicate slashes - while (strpos($path, '//') !== false) { - $path = str_replace('//', '/', $path); - } - //remove trailing slash - $path = rtrim($path, '/'); - - return $path; - } - - /** - * check if the requested path is valid - * - * @param string $path - * @return bool - */ - public function isValidPath($path) { - if (!$path || $path[0] !== '/') { - $path = '/' . $path; - } - if (strstr($path, '/../') || strrchr($path, '/') === '/..') { - return false; - } - return true; - } - - public function isMounted() { - return $this->getFileInfo()->isMounted(); - } - - public function isShared() { - return $this->getFileInfo()->isShared(); - } - - public function getMimeType() { - return $this->getFileInfo()->getMimetype(); - } - - public function getMimePart() { - return $this->getFileInfo()->getMimePart(); - } - - public function getType() { - return $this->getFileInfo()->getType(); - } - - public function isEncrypted() { - return $this->getFileInfo()->isEncrypted(); - } - - public function getMountPoint() { - return $this->getFileInfo()->getMountPoint(); - } - - public function getOwner() { - return $this->getFileInfo()->getOwner(); - } - - public function getChecksum() { - return; - } - - /** - * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE - * @throws \OCP\Lock\LockedException - */ - public function lock($type) { - $this->view->lockFile($this->path, $type); - } - - /** - * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE - * @throws \OCP\Lock\LockedException - */ - public function changeLock($type) { - $this->view->changeLock($this->path, $type); - } - - /** - * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE - * @throws \OCP\Lock\LockedException - */ - public function unlock($type) { - $this->view->unlockFile($this->path, $type); - } -} diff --git a/lib/private/files/node/nonexistingfile.php b/lib/private/files/node/nonexistingfile.php deleted file mode 100644 index c1d09bcc491..00000000000 --- a/lib/private/files/node/nonexistingfile.php +++ /dev/null @@ -1,143 +0,0 @@ - - * @author Robin Appelman - * - * @copyright Copyright (c) 2016, ownCloud, Inc. - * @license AGPL-3.0 - * - * This code is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License, version 3, - * along with this program. If not, see - * - */ - -namespace OC\Files\Node; - -use OCP\Files\NotFoundException; - -class NonExistingFile extends File { - /** - * @param string $newPath - * @throws \OCP\Files\NotFoundException - */ - public function rename($newPath) { - throw new NotFoundException(); - } - - public function delete() { - throw new NotFoundException(); - } - - public function copy($newPath) { - throw new NotFoundException(); - } - - public function touch($mtime = null) { - throw new NotFoundException(); - } - - public function getId() { - if ($this->fileInfo) { - return parent::getId(); - } else { - throw new NotFoundException(); - } - } - - public function stat() { - throw new NotFoundException(); - } - - public function getMTime() { - if ($this->fileInfo) { - return parent::getMTime(); - } else { - throw new NotFoundException(); - } - } - - public function getSize() { - if ($this->fileInfo) { - return parent::getSize(); - } else { - throw new NotFoundException(); - } - } - - public function getEtag() { - if ($this->fileInfo) { - return parent::getEtag(); - } else { - throw new NotFoundException(); - } - } - - public function getPermissions() { - if ($this->fileInfo) { - return parent::getPermissions(); - } else { - throw new NotFoundException(); - } - } - - public function isReadable() { - if ($this->fileInfo) { - return parent::isReadable(); - } else { - throw new NotFoundException(); - } - } - - public function isUpdateable() { - if ($this->fileInfo) { - return parent::isUpdateable(); - } else { - throw new NotFoundException(); - } - } - - public function isDeletable() { - if ($this->fileInfo) { - return parent::isDeletable(); - } else { - throw new NotFoundException(); - } - } - - public function isShareable() { - if ($this->fileInfo) { - return parent::isShareable(); - } else { - throw new NotFoundException(); - } - } - - public function getContent() { - throw new NotFoundException(); - } - - public function putContent($data) { - throw new NotFoundException(); - } - - public function getMimeType() { - if ($this->fileInfo) { - return parent::getMimeType(); - } else { - throw new NotFoundException(); - } - } - - public function fopen($mode) { - throw new NotFoundException(); - } -} diff --git a/lib/private/files/node/nonexistingfolder.php b/lib/private/files/node/nonexistingfolder.php deleted file mode 100644 index 7d6576f1bd6..00000000000 --- a/lib/private/files/node/nonexistingfolder.php +++ /dev/null @@ -1,172 +0,0 @@ - - * @author Robin Appelman - * @author Vincent Petry - * - * @copyright Copyright (c) 2016, ownCloud, Inc. - * @license AGPL-3.0 - * - * This code is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License, version 3, - * along with this program. If not, see - * - */ - -namespace OC\Files\Node; - -use OCP\Files\NotFoundException; - -class NonExistingFolder extends Folder { - /** - * @param string $newPath - * @throws \OCP\Files\NotFoundException - */ - public function rename($newPath) { - throw new NotFoundException(); - } - - public function delete() { - throw new NotFoundException(); - } - - public function copy($newPath) { - throw new NotFoundException(); - } - - public function touch($mtime = null) { - throw new NotFoundException(); - } - - public function getId() { - if ($this->fileInfo) { - return parent::getId(); - } else { - throw new NotFoundException(); - } - } - - public function stat() { - throw new NotFoundException(); - } - - public function getMTime() { - if ($this->fileInfo) { - return parent::getMTime(); - } else { - throw new NotFoundException(); - } - } - - public function getSize() { - if ($this->fileInfo) { - return parent::getSize(); - } else { - throw new NotFoundException(); - } - } - - public function getEtag() { - if ($this->fileInfo) { - return parent::getEtag(); - } else { - throw new NotFoundException(); - } - } - - public function getPermissions() { - if ($this->fileInfo) { - return parent::getPermissions(); - } else { - throw new NotFoundException(); - } - } - - public function isReadable() { - if ($this->fileInfo) { - return parent::isReadable(); - } else { - throw new NotFoundException(); - } - } - - public function isUpdateable() { - if ($this->fileInfo) { - return parent::isUpdateable(); - } else { - throw new NotFoundException(); - } - } - - public function isDeletable() { - if ($this->fileInfo) { - return parent::isDeletable(); - } else { - throw new NotFoundException(); - } - } - - public function isShareable() { - if ($this->fileInfo) { - return parent::isShareable(); - } else { - throw new NotFoundException(); - } - } - - public function get($path) { - throw new NotFoundException(); - } - - public function getDirectoryListing() { - throw new NotFoundException(); - } - - public function nodeExists($path) { - return false; - } - - public function newFolder($path) { - throw new NotFoundException(); - } - - public function newFile($path) { - throw new NotFoundException(); - } - - public function search($pattern) { - throw new NotFoundException(); - } - - public function searchByMime($mime) { - throw new NotFoundException(); - } - - public function searchByTag($tag, $userId) { - throw new NotFoundException(); - } - - public function getById($id) { - throw new NotFoundException(); - } - - public function getFreeSpace() { - throw new NotFoundException(); - } - - public function isCreatable() { - if ($this->fileInfo) { - return parent::isCreatable(); - } else { - throw new NotFoundException(); - } - } -} diff --git a/lib/private/files/node/root.php b/lib/private/files/node/root.php deleted file mode 100644 index 04866e60b87..00000000000 --- a/lib/private/files/node/root.php +++ /dev/null @@ -1,357 +0,0 @@ - - * @author Joas Schilling - * @author Jörn Friedrich Dreyer - * @author Morris Jobke - * @author Robin Appelman - * @author Roeland Jago Douma - * - * @copyright Copyright (c) 2016, ownCloud, Inc. - * @license AGPL-3.0 - * - * This code is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License, version 3, - * along with this program. If not, see - * - */ - -namespace OC\Files\Node; - -use OC\Files\Mount\Manager; -use OC\Files\Mount\MountPoint; -use OCP\Files\NotFoundException; -use OCP\Files\NotPermittedException; -use OC\Hooks\PublicEmitter; -use OCP\Files\IRootFolder; - -/** - * Class Root - * - * Hooks available in scope \OC\Files - * - preWrite(\OCP\Files\Node $node) - * - postWrite(\OCP\Files\Node $node) - * - preCreate(\OCP\Files\Node $node) - * - postCreate(\OCP\Files\Node $node) - * - preDelete(\OCP\Files\Node $node) - * - postDelete(\OCP\Files\Node $node) - * - preTouch(\OC\FilesP\Node $node, int $mtime) - * - postTouch(\OCP\Files\Node $node) - * - preCopy(\OCP\Files\Node $source, \OCP\Files\Node $target) - * - postCopy(\OCP\Files\Node $source, \OCP\Files\Node $target) - * - preRename(\OCP\Files\Node $source, \OCP\Files\Node $target) - * - postRename(\OCP\Files\Node $source, \OCP\Files\Node $target) - * - * @package OC\Files\Node - */ -class Root extends Folder implements IRootFolder { - - /** - * @var \OC\Files\Mount\Manager $mountManager - */ - private $mountManager; - - /** - * @var \OC\Hooks\PublicEmitter - */ - private $emitter; - - /** - * @var \OC\User\User $user - */ - private $user; - - /** - * @param \OC\Files\Mount\Manager $manager - * @param \OC\Files\View $view - * @param \OC\User\User|null $user - */ - public function __construct($manager, $view, $user) { - parent::__construct($this, $view, ''); - $this->mountManager = $manager; - $this->user = $user; - $this->emitter = new PublicEmitter(); - } - - /** - * Get the user for which the filesystem is setup - * - * @return \OC\User\User - */ - public function getUser() { - return $this->user; - } - - /** - * @param string $scope - * @param string $method - * @param callable $callback - */ - public function listen($scope, $method, callable $callback) { - $this->emitter->listen($scope, $method, $callback); - } - - /** - * @param string $scope optional - * @param string $method optional - * @param callable $callback optional - */ - public function removeListener($scope = null, $method = null, callable $callback = null) { - $this->emitter->removeListener($scope, $method, $callback); - } - - /** - * @param string $scope - * @param string $method - * @param Node[] $arguments - */ - public function emit($scope, $method, $arguments = array()) { - $this->emitter->emit($scope, $method, $arguments); - } - - /** - * @param \OC\Files\Storage\Storage $storage - * @param string $mountPoint - * @param array $arguments - */ - public function mount($storage, $mountPoint, $arguments = array()) { - $mount = new MountPoint($storage, $mountPoint, $arguments); - $this->mountManager->addMount($mount); - } - - /** - * @param string $mountPoint - * @return \OC\Files\Mount\MountPoint - */ - public function getMount($mountPoint) { - return $this->mountManager->find($mountPoint); - } - - /** - * @param string $mountPoint - * @return \OC\Files\Mount\MountPoint[] - */ - public function getMountsIn($mountPoint) { - return $this->mountManager->findIn($mountPoint); - } - - /** - * @param string $storageId - * @return \OC\Files\Mount\MountPoint[] - */ - public function getMountByStorageId($storageId) { - return $this->mountManager->findByStorageId($storageId); - } - - /** - * @param int $numericId - * @return MountPoint[] - */ - public function getMountByNumericStorageId($numericId) { - return $this->mountManager->findByNumericId($numericId); - } - - /** - * @param \OC\Files\Mount\MountPoint $mount - */ - public function unMount($mount) { - $this->mountManager->remove($mount); - } - - /** - * @param string $path - * @throws \OCP\Files\NotFoundException - * @throws \OCP\Files\NotPermittedException - * @return string - */ - public function get($path) { - $path = $this->normalizePath($path); - if ($this->isValidPath($path)) { - $fullPath = $this->getFullPath($path); - $fileInfo = $this->view->getFileInfo($fullPath); - if ($fileInfo) { - return $this->createNode($fullPath, $fileInfo); - } else { - throw new NotFoundException($path); - } - } else { - throw new NotPermittedException(); - } - } - - //most operations can't be done on the root - - /** - * @param string $targetPath - * @throws \OCP\Files\NotPermittedException - * @return \OC\Files\Node\Node - */ - public function rename($targetPath) { - throw new NotPermittedException(); - } - - public function delete() { - throw new NotPermittedException(); - } - - /** - * @param string $targetPath - * @throws \OCP\Files\NotPermittedException - * @return \OC\Files\Node\Node - */ - public function copy($targetPath) { - throw new NotPermittedException(); - } - - /** - * @param int $mtime - * @throws \OCP\Files\NotPermittedException - */ - public function touch($mtime = null) { - throw new NotPermittedException(); - } - - /** - * @return \OC\Files\Storage\Storage - * @throws \OCP\Files\NotFoundException - */ - public function getStorage() { - throw new NotFoundException(); - } - - /** - * @return string - */ - public function getPath() { - return '/'; - } - - /** - * @return string - */ - public function getInternalPath() { - return ''; - } - - /** - * @return int - */ - public function getId() { - return null; - } - - /** - * @return array - */ - public function stat() { - return null; - } - - /** - * @return int - */ - public function getMTime() { - return null; - } - - /** - * @return int - */ - public function getSize() { - return null; - } - - /** - * @return string - */ - public function getEtag() { - return null; - } - - /** - * @return int - */ - public function getPermissions() { - return \OCP\Constants::PERMISSION_CREATE; - } - - /** - * @return bool - */ - public function isReadable() { - return false; - } - - /** - * @return bool - */ - public function isUpdateable() { - return false; - } - - /** - * @return bool - */ - public function isDeletable() { - return false; - } - - /** - * @return bool - */ - public function isShareable() { - return false; - } - - /** - * @return Node - * @throws \OCP\Files\NotFoundException - */ - public function getParent() { - throw new NotFoundException(); - } - - /** - * @return string - */ - public function getName() { - return ''; - } - - /** - * Returns a view to user's files folder - * - * @param String $userId user ID - * @return \OCP\Files\Folder - */ - public function getUserFolder($userId) { - \OC\Files\Filesystem::initMountPoints($userId); - $dir = '/' . $userId; - $folder = null; - - try { - $folder = $this->get($dir); - } catch (NotFoundException $e) { - $folder = $this->newFolder($dir); - } - - $dir = '/files'; - try { - $folder = $folder->get($dir); - } catch (NotFoundException $e) { - $folder = $folder->newFolder($dir); - \OC_Util::copySkeleton($userId, $folder); - } - - return $folder; - - } -} diff --git a/lib/private/files/objectstore/homeobjectstorestorage.php b/lib/private/files/objectstore/homeobjectstorestorage.php deleted file mode 100644 index 6a330e2dab3..00000000000 --- a/lib/private/files/objectstore/homeobjectstorestorage.php +++ /dev/null @@ -1,68 +0,0 @@ - - * @author Jörn Friedrich Dreyer - * @author Morris Jobke - * - * @copyright Copyright (c) 2016, ownCloud, Inc. - * @license AGPL-3.0 - * - * This code is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License, version 3, - * along with this program. If not, see - * - */ - -namespace OC\Files\ObjectStore; - -use OC\User\User; - -class HomeObjectStoreStorage extends ObjectStoreStorage implements \OCP\Files\IHomeStorage { - - /** - * The home user storage requires a user object to create a unique storage id - * @param array $params - */ - public function __construct($params) { - if ( ! isset($params['user']) || ! $params['user'] instanceof User) { - throw new \Exception('missing user object in parameters'); - } - $this->user = $params['user']; - parent::__construct($params); - } - - public function getId () { - return 'object::user:' . $this->user->getUID(); - } - - /** - * get the owner of a path - * - * @param string $path The path to get the owner - * @return false|string uid - */ - public function getOwner($path) { - if (is_object($this->user)) { - return $this->user->getUID(); - } - return false; - } - - /** - * @param string $path, optional - * @return \OC\User\User - */ - public function getUser($path = null) { - return $this->user; - } - - -} \ No newline at end of file diff --git a/lib/private/files/objectstore/noopscanner.php b/lib/private/files/objectstore/noopscanner.php deleted file mode 100644 index f5316175ecf..00000000000 --- a/lib/private/files/objectstore/noopscanner.php +++ /dev/null @@ -1,79 +0,0 @@ - - * @author Jörn Friedrich Dreyer - * @author Morris Jobke - * @author Robin Appelman - * - * @copyright Copyright (c) 2016, ownCloud, Inc. - * @license AGPL-3.0 - * - * This code is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License, version 3, - * along with this program. If not, see - * - */ - -namespace OC\Files\ObjectStore; -use \OC\Files\Cache\Scanner; -use \OC\Files\Storage\Storage; - -class NoopScanner extends Scanner { - - public function __construct(Storage $storage) { - //we don't need the storage, so do nothing here - } - - /** - * scan a single file and store it in the cache - * - * @param string $file - * @param int $reuseExisting - * @param int $parentId - * @param array|null $cacheData existing data in the cache for the file to be scanned - * @return array an array of metadata of the scanned file - */ - public function scanFile($file, $reuseExisting = 0, $parentId = -1, $cacheData = null, $lock = true) { - return array(); - } - - /** - * scan a folder and all it's children - * - * @param string $path - * @param bool $recursive - * @param int $reuse - * @return array with the meta data of the scanned file or folder - */ - public function scan($path, $recursive = self::SCAN_RECURSIVE, $reuse = -1, $lock = true) { - return array(); - } - - /** - * scan all the files and folders in a folder - * - * @param string $path - * @param bool $recursive - * @param int $reuse - * @param array $folderData existing cache data for the folder to be scanned - * @return int the size of the scanned folder or -1 if the size is unknown at this stage - */ - protected function scanChildren($path, $recursive = self::SCAN_RECURSIVE, $reuse = -1, $folderData = null, $lock = true) { - return 0; - } - - /** - * walk over any folders that are not fully scanned yet and scan them - */ - public function backgroundScan() { - //noop - } -} diff --git a/lib/private/files/objectstore/objectstorestorage.php b/lib/private/files/objectstore/objectstorestorage.php deleted file mode 100644 index 8c643dbdcc7..00000000000 --- a/lib/private/files/objectstore/objectstorestorage.php +++ /dev/null @@ -1,407 +0,0 @@ - - * @author Jörn Friedrich Dreyer - * @author Morris Jobke - * @author Robin Appelman - * - * @copyright Copyright (c) 2016, ownCloud, Inc. - * @license AGPL-3.0 - * - * This code is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License, version 3, - * along with this program. If not, see - * - */ - -namespace OC\Files\ObjectStore; - -use Icewind\Streams\IteratorDirectory; -use OC\Files\Cache\CacheEntry; -use OCP\Files\ObjectStore\IObjectStore; - -class ObjectStoreStorage extends \OC\Files\Storage\Common { - - /** - * @var array - */ - private static $tmpFiles = array(); - /** - * @var \OCP\Files\ObjectStore\IObjectStore $objectStore - */ - protected $objectStore; - /** - * @var string $id - */ - protected $id; - /** - * @var \OC\User\User $user - */ - protected $user; - - public function __construct($params) { - if (isset($params['objectstore']) && $params['objectstore'] instanceof IObjectStore) { - $this->objectStore = $params['objectstore']; - } else { - throw new \Exception('missing IObjectStore instance'); - } - if (isset($params['storageid'])) { - $this->id = 'object::store:' . $params['storageid']; - } else { - $this->id = 'object::store:' . $this->objectStore->getStorageId(); - } - //initialize cache with root directory in cache - if (!$this->is_dir('/')) { - $this->mkdir('/'); - } - } - - public function mkdir($path) { - $path = $this->normalizePath($path); - - if ($this->file_exists($path)) { - return false; - } - - $mTime = time(); - $data = [ - 'mimetype' => 'httpd/unix-directory', - 'size' => 0, - 'mtime' => $mTime, - 'storage_mtime' => $mTime, - 'permissions' => \OCP\Constants::PERMISSION_ALL, - ]; - if ($path === '') { - //create root on the fly - $data['etag'] = $this->getETag(''); - $this->getCache()->put('', $data); - return true; - } else { - // if parent does not exist, create it - $parent = $this->normalizePath(dirname($path)); - $parentType = $this->filetype($parent); - if ($parentType === false) { - if (!$this->mkdir($parent)) { - // something went wrong - return false; - } - } else if ($parentType === 'file') { - // parent is a file - return false; - } - // finally create the new dir - $mTime = time(); // update mtime - $data['mtime'] = $mTime; - $data['storage_mtime'] = $mTime; - $data['etag'] = $this->getETag($path); - $this->getCache()->put($path, $data); - return true; - } - } - - /** - * @param string $path - * @return string - */ - private function normalizePath($path) { - $path = trim($path, '/'); - //FIXME why do we sometimes get a path like 'files//username'? - $path = str_replace('//', '/', $path); - - // dirname('/folder') returns '.' but internally (in the cache) we store the root as '' - if (!$path || $path === '.') { - $path = ''; - } - - return $path; - } - - /** - * Object Stores use a NoopScanner because metadata is directly stored in - * the file cache and cannot really scan the filesystem. The storage passed in is not used anywhere. - * - * @param string $path - * @param \OC\Files\Storage\Storage (optional) the storage to pass to the scanner - * @return \OC\Files\ObjectStore\NoopScanner - */ - public function getScanner($path = '', $storage = null) { - if (!$storage) { - $storage = $this; - } - if (!isset($this->scanner)) { - $this->scanner = new NoopScanner($storage); - } - return $this->scanner; - } - - public function getId() { - return $this->id; - } - - public function rmdir($path) { - $path = $this->normalizePath($path); - - if (!$this->is_dir($path)) { - return false; - } - - $this->rmObjects($path); - - $this->getCache()->remove($path); - - return true; - } - - private function rmObjects($path) { - $children = $this->getCache()->getFolderContents($path); - foreach ($children as $child) { - if ($child['mimetype'] === 'httpd/unix-directory') { - $this->rmObjects($child['path']); - } else { - $this->unlink($child['path']); - } - } - } - - public function unlink($path) { - $path = $this->normalizePath($path); - $stat = $this->stat($path); - - if ($stat && isset($stat['fileid'])) { - if ($stat['mimetype'] === 'httpd/unix-directory') { - return $this->rmdir($path); - } - try { - $this->objectStore->deleteObject($this->getURN($stat['fileid'])); - } catch (\Exception $ex) { - if ($ex->getCode() !== 404) { - \OCP\Util::writeLog('objectstore', 'Could not delete object: ' . $ex->getMessage(), \OCP\Util::ERROR); - return false; - } else { - //removing from cache is ok as it does not exist in the objectstore anyway - } - } - $this->getCache()->remove($path); - return true; - } - return false; - } - - public function stat($path) { - $path = $this->normalizePath($path); - $cacheEntry = $this->getCache()->get($path); - if ($cacheEntry instanceof CacheEntry) { - return $cacheEntry->getData(); - } else { - return false; - } - } - - /** - * Override this method if you need a different unique resource identifier for your object storage implementation. - * The default implementations just appends the fileId to 'urn:oid:'. Make sure the URN is unique over all users. - * You may need a mapping table to store your URN if it cannot be generated from the fileid. - * - * @param int $fileId the fileid - * @return null|string the unified resource name used to identify the object - */ - protected function getURN($fileId) { - if (is_numeric($fileId)) { - return 'urn:oid:' . $fileId; - } - return null; - } - - public function opendir($path) { - $path = $this->normalizePath($path); - - try { - $files = array(); - $folderContents = $this->getCache()->getFolderContents($path); - foreach ($folderContents as $file) { - $files[] = $file['name']; - } - - return IteratorDirectory::wrap($files); - } catch (\Exception $e) { - \OCP\Util::writeLog('objectstore', $e->getMessage(), \OCP\Util::ERROR); - return false; - } - } - - public function filetype($path) { - $path = $this->normalizePath($path); - $stat = $this->stat($path); - if ($stat) { - if ($stat['mimetype'] === 'httpd/unix-directory') { - return 'dir'; - } - return 'file'; - } else { - return false; - } - } - - public function fopen($path, $mode) { - $path = $this->normalizePath($path); - - switch ($mode) { - case 'r': - case 'rb': - $stat = $this->stat($path); - if (is_array($stat)) { - try { - return $this->objectStore->readObject($this->getURN($stat['fileid'])); - } catch (\Exception $ex) { - \OCP\Util::writeLog('objectstore', 'Could not get object: ' . $ex->getMessage(), \OCP\Util::ERROR); - return false; - } - } else { - return false; - } - case 'w': - case 'wb': - case 'a': - case 'ab': - case 'r+': - case 'w+': - case 'wb+': - case 'a+': - case 'x': - case 'x+': - case 'c': - case 'c+': - if (strrpos($path, '.') !== false) { - $ext = substr($path, strrpos($path, '.')); - } else { - $ext = ''; - } - $tmpFile = \OC::$server->getTempManager()->getTemporaryFile($ext); - \OC\Files\Stream\Close::registerCallback($tmpFile, array($this, 'writeBack')); - if ($this->file_exists($path)) { - $source = $this->fopen($path, 'r'); - file_put_contents($tmpFile, $source); - } - self::$tmpFiles[$tmpFile] = $path; - - return fopen('close://' . $tmpFile, $mode); - } - return false; - } - - public function file_exists($path) { - $path = $this->normalizePath($path); - return (bool)$this->stat($path); - } - - public function rename($source, $target) { - $source = $this->normalizePath($source); - $target = $this->normalizePath($target); - $this->remove($target); - $this->getCache()->move($source, $target); - $this->touch(dirname($target)); - return true; - } - - public function getMimeType($path) { - $path = $this->normalizePath($path); - $stat = $this->stat($path); - if (is_array($stat)) { - return $stat['mimetype']; - } else { - return false; - } - } - - public function touch($path, $mtime = null) { - if (is_null($mtime)) { - $mtime = time(); - } - - $path = $this->normalizePath($path); - $dirName = dirname($path); - $parentExists = $this->is_dir($dirName); - if (!$parentExists) { - return false; - } - - $stat = $this->stat($path); - if (is_array($stat)) { - // update existing mtime in db - $stat['mtime'] = $mtime; - $this->getCache()->update($stat['fileid'], $stat); - } else { - $mimeType = \OC::$server->getMimeTypeDetector()->detectPath($path); - // create new file - $stat = array( - 'etag' => $this->getETag($path), - 'mimetype' => $mimeType, - 'size' => 0, - 'mtime' => $mtime, - 'storage_mtime' => $mtime, - 'permissions' => \OCP\Constants::PERMISSION_ALL - \OCP\Constants::PERMISSION_CREATE, - ); - $fileId = $this->getCache()->put($path, $stat); - try { - //read an empty file from memory - $this->objectStore->writeObject($this->getURN($fileId), fopen('php://memory', 'r')); - } catch (\Exception $ex) { - $this->getCache()->remove($path); - \OCP\Util::writeLog('objectstore', 'Could not create object: ' . $ex->getMessage(), \OCP\Util::ERROR); - return false; - } - } - return true; - } - - public function writeBack($tmpFile) { - if (!isset(self::$tmpFiles[$tmpFile])) { - return; - } - - $path = self::$tmpFiles[$tmpFile]; - $stat = $this->stat($path); - if (empty($stat)) { - // create new file - $stat = array( - 'permissions' => \OCP\Constants::PERMISSION_ALL - \OCP\Constants::PERMISSION_CREATE, - ); - } - // update stat with new data - $mTime = time(); - $stat['size'] = filesize($tmpFile); - $stat['mtime'] = $mTime; - $stat['storage_mtime'] = $mTime; - $stat['mimetype'] = \OC::$server->getMimeTypeDetector()->detect($tmpFile); - $stat['etag'] = $this->getETag($path); - - $fileId = $this->getCache()->put($path, $stat); - try { - //upload to object storage - $this->objectStore->writeObject($this->getURN($fileId), fopen($tmpFile, 'r')); - } catch (\Exception $ex) { - $this->getCache()->remove($path); - \OCP\Util::writeLog('objectstore', 'Could not create object: ' . $ex->getMessage(), \OCP\Util::ERROR); - throw $ex; // make this bubble up - } - } - - /** - * external changes are not supported, exclusive access to the object storage is assumed - * - * @param string $path - * @param int $time - * @return false - */ - public function hasUpdated($path, $time) { - return false; - } -} diff --git a/lib/private/files/objectstore/swift.php b/lib/private/files/objectstore/swift.php deleted file mode 100644 index 4af09dca254..00000000000 --- a/lib/private/files/objectstore/swift.php +++ /dev/null @@ -1,154 +0,0 @@ - - * @author Morris Jobke - * @author Robin Appelman - * - * @copyright Copyright (c) 2016, ownCloud, Inc. - * @license AGPL-3.0 - * - * This code is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License, version 3, - * along with this program. If not, see - * - */ - -namespace OC\Files\ObjectStore; - -use Guzzle\Http\Exception\ClientErrorResponseException; -use OCP\Files\ObjectStore\IObjectStore; -use OpenCloud\OpenStack; -use OpenCloud\Rackspace; - -class Swift implements IObjectStore { - /** - * @var \OpenCloud\OpenStack - */ - private $client; - - /** - * @var array - */ - private $params; - - /** - * @var \OpenCloud\ObjectStore\Service - */ - private $objectStoreService; - - /** - * @var \OpenCloud\ObjectStore\Resource\Container - */ - private $container; - - public function __construct($params) { - if (!isset($params['container'])) { - $params['container'] = 'owncloud'; - } - if (!isset($params['autocreate'])) { - // should only be true for tests - $params['autocreate'] = false; - } - - if (isset($params['apiKey'])) { - $this->client = new Rackspace($params['url'], $params); - } else { - $this->client = new OpenStack($params['url'], $params); - } - $this->params = $params; - } - - protected function init() { - if ($this->container) { - return; - } - - // the OpenCloud client library will default to 'cloudFiles' if $serviceName is null - $serviceName = null; - if (isset($this->params['serviceName'])) { - $serviceName = $this->params['serviceName']; - } - - // the OpenCloud client library will default to 'publicURL' if $urlType is null - $urlType = null; - if (isset($this->params['urlType'])) { - $urlType = $this->params['urlType']; - } - $this->objectStoreService = $this->client->objectStoreService($serviceName, $this->params['region'], $urlType); - - try { - $this->container = $this->objectStoreService->getContainer($this->params['container']); - } catch (ClientErrorResponseException $ex) { - // if the container does not exist and autocreate is true try to create the container on the fly - if (isset($this->params['autocreate']) && $this->params['autocreate'] === true) { - $this->container = $this->objectStoreService->createContainer($this->params['container']); - } else { - throw $ex; - } - } - } - - /** - * @return string the container name where objects are stored - */ - public function getStorageId() { - return $this->params['container']; - } - - /** - * @param string $urn the unified resource name used to identify the object - * @param resource $stream stream with the data to write - * @throws Exception from openstack lib when something goes wrong - */ - public function writeObject($urn, $stream) { - $this->init(); - $this->container->uploadObject($urn, $stream); - } - - /** - * @param string $urn the unified resource name used to identify the object - * @return resource stream with the read data - * @throws Exception from openstack lib when something goes wrong - */ - public function readObject($urn) { - $this->init(); - $object = $this->container->getObject($urn); - - // we need to keep a reference to objectContent or - // the stream will be closed before we can do anything with it - /** @var $objectContent \Guzzle\Http\EntityBody * */ - $objectContent = $object->getContent(); - $objectContent->rewind(); - - $stream = $objectContent->getStream(); - // save the object content in the context of the stream to prevent it being gc'd until the stream is closed - stream_context_set_option($stream, 'swift','content', $objectContent); - - return $stream; - } - - /** - * @param string $urn Unified Resource Name - * @return void - * @throws Exception from openstack lib when something goes wrong - */ - public function deleteObject($urn) { - $this->init(); - // see https://github.com/rackspace/php-opencloud/issues/243#issuecomment-30032242 - $this->container->dataObject()->setName($urn)->delete(); - } - - public function deleteContainer($recursive = false) { - $this->init(); - $this->container->delete($recursive); - } - -} diff --git a/lib/private/files/storage/common.php b/lib/private/files/storage/common.php deleted file mode 100644 index 3a811b312c6..00000000000 --- a/lib/private/files/storage/common.php +++ /dev/null @@ -1,697 +0,0 @@ - - * @author Bart Visscher - * @author Björn Schießle - * @author hkjolhede - * @author Joas Schilling - * @author Jörn Friedrich Dreyer - * @author Lukas Reschke - * @author Martin Mattel - * @author Michael Gapczynski - * @author Morris Jobke - * @author Robin Appelman - * @author Robin McCorkell - * @author Sam Tuke - * @author scambra - * @author Thomas Müller - * @author Vincent Petry - * - * @copyright Copyright (c) 2016, ownCloud, Inc. - * @license AGPL-3.0 - * - * This code is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License, version 3, - * along with this program. If not, see - * - */ - -namespace OC\Files\Storage; - -use OC\Files\Cache\Cache; -use OC\Files\Cache\Propagator; -use OC\Files\Cache\Scanner; -use OC\Files\Cache\Updater; -use OC\Files\Filesystem; -use OC\Files\Cache\Watcher; -use OCP\Files\FileNameTooLongException; -use OCP\Files\InvalidCharacterInPathException; -use OCP\Files\InvalidPathException; -use OCP\Files\ReservedWordException; -use OCP\Files\Storage\ILockingStorage; -use OCP\Lock\ILockingProvider; - -/** - * Storage backend class for providing common filesystem operation methods - * which are not storage-backend specific. - * - * \OC\Files\Storage\Common is never used directly; it is extended by all other - * storage backends, where its methods may be overridden, and additional - * (backend-specific) methods are defined. - * - * Some \OC\Files\Storage\Common methods call functions which are first defined - * in classes which extend it, e.g. $this->stat() . - */ -abstract class Common implements Storage, ILockingStorage { - - use LocalTempFileTrait; - - protected $cache; - protected $scanner; - protected $watcher; - protected $propagator; - protected $storageCache; - protected $updater; - - protected $mountOptions = []; - protected $owner = null; - - public function __construct($parameters) { - } - - /** - * Remove a file or folder - * - * @param string $path - * @return bool - */ - protected function remove($path) { - if ($this->is_dir($path)) { - return $this->rmdir($path); - } else if ($this->is_file($path)) { - return $this->unlink($path); - } else { - return false; - } - } - - public function is_dir($path) { - return $this->filetype($path) === 'dir'; - } - - public function is_file($path) { - return $this->filetype($path) === 'file'; - } - - public function filesize($path) { - if ($this->is_dir($path)) { - return 0; //by definition - } else { - $stat = $this->stat($path); - if (isset($stat['size'])) { - return $stat['size']; - } else { - return 0; - } - } - } - - public function isReadable($path) { - // at least check whether it exists - // subclasses might want to implement this more thoroughly - return $this->file_exists($path); - } - - public function isUpdatable($path) { - // at least check whether it exists - // subclasses might want to implement this more thoroughly - // a non-existing file/folder isn't updatable - return $this->file_exists($path); - } - - public function isCreatable($path) { - if ($this->is_dir($path) && $this->isUpdatable($path)) { - return true; - } - return false; - } - - public function isDeletable($path) { - if ($path === '' || $path === '/') { - return false; - } - $parent = dirname($path); - return $this->isUpdatable($parent) && $this->isUpdatable($path); - } - - public function isSharable($path) { - return $this->isReadable($path); - } - - public function getPermissions($path) { - $permissions = 0; - if ($this->isCreatable($path)) { - $permissions |= \OCP\Constants::PERMISSION_CREATE; - } - if ($this->isReadable($path)) { - $permissions |= \OCP\Constants::PERMISSION_READ; - } - if ($this->isUpdatable($path)) { - $permissions |= \OCP\Constants::PERMISSION_UPDATE; - } - if ($this->isDeletable($path)) { - $permissions |= \OCP\Constants::PERMISSION_DELETE; - } - if ($this->isSharable($path)) { - $permissions |= \OCP\Constants::PERMISSION_SHARE; - } - return $permissions; - } - - public function filemtime($path) { - $stat = $this->stat($path); - if (isset($stat['mtime']) && $stat['mtime'] > 0) { - return $stat['mtime']; - } else { - return 0; - } - } - - public function file_get_contents($path) { - $handle = $this->fopen($path, "r"); - if (!$handle) { - return false; - } - $data = stream_get_contents($handle); - fclose($handle); - return $data; - } - - public function file_put_contents($path, $data) { - $handle = $this->fopen($path, "w"); - $this->removeCachedFile($path); - $count = fwrite($handle, $data); - fclose($handle); - return $count; - } - - public function rename($path1, $path2) { - $this->remove($path2); - - $this->removeCachedFile($path1); - return $this->copy($path1, $path2) and $this->remove($path1); - } - - public function copy($path1, $path2) { - if ($this->is_dir($path1)) { - $this->remove($path2); - $dir = $this->opendir($path1); - $this->mkdir($path2); - while ($file = readdir($dir)) { - if (!Filesystem::isIgnoredDir($file)) { - if (!$this->copy($path1 . '/' . $file, $path2 . '/' . $file)) { - return false; - } - } - } - closedir($dir); - return true; - } else { - $source = $this->fopen($path1, 'r'); - $target = $this->fopen($path2, 'w'); - list(, $result) = \OC_Helper::streamCopy($source, $target); - $this->removeCachedFile($path2); - return $result; - } - } - - public function getMimeType($path) { - if ($this->is_dir($path)) { - return 'httpd/unix-directory'; - } elseif ($this->file_exists($path)) { - return \OC::$server->getMimeTypeDetector()->detectPath($path); - } else { - return false; - } - } - - public function hash($type, $path, $raw = false) { - $fh = $this->fopen($path, 'rb'); - $ctx = hash_init($type); - hash_update_stream($ctx, $fh); - fclose($fh); - return hash_final($ctx, $raw); - } - - public function search($query) { - return $this->searchInDir($query); - } - - public function getLocalFile($path) { - return $this->getCachedFile($path); - } - - /** - * @param string $path - * @param string $target - */ - private function addLocalFolder($path, $target) { - $dh = $this->opendir($path); - if (is_resource($dh)) { - while (($file = readdir($dh)) !== false) { - if (!\OC\Files\Filesystem::isIgnoredDir($file)) { - if ($this->is_dir($path . '/' . $file)) { - mkdir($target . '/' . $file); - $this->addLocalFolder($path . '/' . $file, $target . '/' . $file); - } else { - $tmp = $this->toTmpFile($path . '/' . $file); - rename($tmp, $target . '/' . $file); - } - } - } - } - } - - /** - * @param string $query - * @param string $dir - * @return array - */ - protected function searchInDir($query, $dir = '') { - $files = array(); - $dh = $this->opendir($dir); - if (is_resource($dh)) { - while (($item = readdir($dh)) !== false) { - if (\OC\Files\Filesystem::isIgnoredDir($item)) continue; - if (strstr(strtolower($item), strtolower($query)) !== false) { - $files[] = $dir . '/' . $item; - } - if ($this->is_dir($dir . '/' . $item)) { - $files = array_merge($files, $this->searchInDir($query, $dir . '/' . $item)); - } - } - } - closedir($dh); - return $files; - } - - /** - * check if a file or folder has been updated since $time - * - * The method is only used to check if the cache needs to be updated. Storage backends that don't support checking - * the mtime should always return false here. As a result storage implementations that always return false expect - * exclusive access to the backend and will not pick up files that have been added in a way that circumvents - * ownClouds filesystem. - * - * @param string $path - * @param int $time - * @return bool - */ - public function hasUpdated($path, $time) { - return $this->filemtime($path) > $time; - } - - public function getCache($path = '', $storage = null) { - if (!$storage) { - $storage = $this; - } - if (!isset($storage->cache)) { - $storage->cache = new Cache($storage); - } - return $storage->cache; - } - - public function getScanner($path = '', $storage = null) { - if (!$storage) { - $storage = $this; - } - if (!isset($storage->scanner)) { - $storage->scanner = new Scanner($storage); - } - return $storage->scanner; - } - - public function getWatcher($path = '', $storage = null) { - if (!$storage) { - $storage = $this; - } - if (!isset($this->watcher)) { - $this->watcher = new Watcher($storage); - $globalPolicy = \OC::$server->getConfig()->getSystemValue('filesystem_check_changes', Watcher::CHECK_NEVER); - $this->watcher->setPolicy((int)$this->getMountOption('filesystem_check_changes', $globalPolicy)); - } - return $this->watcher; - } - - /** - * get a propagator instance for the cache - * - * @param \OC\Files\Storage\Storage (optional) the storage to pass to the watcher - * @return \OC\Files\Cache\Propagator - */ - public function getPropagator($storage = null) { - if (!$storage) { - $storage = $this; - } - if (!isset($storage->propagator)) { - $storage->propagator = new Propagator($storage); - } - return $storage->propagator; - } - - public function getUpdater($storage = null) { - if (!$storage) { - $storage = $this; - } - if (!isset($storage->updater)) { - $storage->updater = new Updater($storage); - } - return $storage->updater; - } - - public function getStorageCache($storage = null) { - if (!$storage) { - $storage = $this; - } - if (!isset($this->storageCache)) { - $this->storageCache = new \OC\Files\Cache\Storage($storage); - } - return $this->storageCache; - } - - /** - * get the owner of a path - * - * @param string $path The path to get the owner - * @return string|false uid or false - */ - public function getOwner($path) { - if ($this->owner === null) { - $this->owner = \OC_User::getUser(); - } - - return $this->owner; - } - - /** - * get the ETag for a file or folder - * - * @param string $path - * @return string - */ - public function getETag($path) { - return uniqid(); - } - - /** - * clean a path, i.e. remove all redundant '.' and '..' - * making sure that it can't point to higher than '/' - * - * @param string $path The path to clean - * @return string cleaned path - */ - public function cleanPath($path) { - if (strlen($path) == 0 or $path[0] != '/') { - $path = '/' . $path; - } - - $output = array(); - foreach (explode('/', $path) as $chunk) { - if ($chunk == '..') { - array_pop($output); - } else if ($chunk == '.') { - } else { - $output[] = $chunk; - } - } - return implode('/', $output); - } - - /** - * Test a storage for availability - * - * @return bool - */ - public function test() { - if ($this->stat('')) { - return true; - } - return false; - } - - /** - * get the free space in the storage - * - * @param string $path - * @return int|false - */ - public function free_space($path) { - return \OCP\Files\FileInfo::SPACE_UNKNOWN; - } - - /** - * {@inheritdoc} - */ - public function isLocal() { - // the common implementation returns a temporary file by - // default, which is not local - return false; - } - - /** - * Check if the storage is an instance of $class or is a wrapper for a storage that is an instance of $class - * - * @param string $class - * @return bool - */ - public function instanceOfStorage($class) { - return is_a($this, $class); - } - - /** - * A custom storage implementation can return an url for direct download of a give file. - * - * For now the returned array can hold the parameter url - in future more attributes might follow. - * - * @param string $path - * @return array|false - */ - public function getDirectDownload($path) { - return []; - } - - /** - * @inheritdoc - */ - public function verifyPath($path, $fileName) { - if (isset($fileName[255])) { - throw new FileNameTooLongException(); - } - - // NOTE: $path will remain unverified for now - if (\OC_Util::runningOnWindows()) { - $this->verifyWindowsPath($fileName); - } else { - $this->verifyPosixPath($fileName); - } - } - - /** - * https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247%28v=vs.85%29.aspx - * @param string $fileName - * @throws InvalidPathException - */ - protected function verifyWindowsPath($fileName) { - $fileName = trim($fileName); - $this->scanForInvalidCharacters($fileName, "\\/<>:\"|?*"); - $reservedNames = ['CON', 'PRN', 'AUX', 'NUL', 'COM1', 'COM2', 'COM3', 'COM4', 'COM5', 'COM6', 'COM7', 'COM8', 'COM9', 'LPT1', 'LPT2', 'LPT3', 'LPT4', 'LPT5', 'LPT6', 'LPT7', 'LPT8', 'LPT9']; - if (in_array(strtoupper($fileName), $reservedNames)) { - throw new ReservedWordException(); - } - } - - /** - * @param string $fileName - * @throws InvalidPathException - */ - protected function verifyPosixPath($fileName) { - $fileName = trim($fileName); - $this->scanForInvalidCharacters($fileName, "\\/"); - $reservedNames = ['*']; - if (in_array($fileName, $reservedNames)) { - throw new ReservedWordException(); - } - } - - /** - * @param string $fileName - * @param string $invalidChars - * @throws InvalidPathException - */ - private function scanForInvalidCharacters($fileName, $invalidChars) { - foreach (str_split($invalidChars) as $char) { - if (strpos($fileName, $char) !== false) { - throw new InvalidCharacterInPathException(); - } - } - - $sanitizedFileName = filter_var($fileName, FILTER_UNSAFE_RAW, FILTER_FLAG_STRIP_LOW); - if ($sanitizedFileName !== $fileName) { - throw new InvalidCharacterInPathException(); - } - } - - /** - * @param array $options - */ - public function setMountOptions(array $options) { - $this->mountOptions = $options; - } - - /** - * @param string $name - * @param mixed $default - * @return mixed - */ - public function getMountOption($name, $default = null) { - return isset($this->mountOptions[$name]) ? $this->mountOptions[$name] : $default; - } - - /** - * @param \OCP\Files\Storage $sourceStorage - * @param string $sourceInternalPath - * @param string $targetInternalPath - * @param bool $preserveMtime - * @return bool - */ - public function copyFromStorage(\OCP\Files\Storage $sourceStorage, $sourceInternalPath, $targetInternalPath, $preserveMtime = false) { - if ($sourceStorage === $this) { - return $this->copy($sourceInternalPath, $targetInternalPath); - } - - if ($sourceStorage->is_dir($sourceInternalPath)) { - $dh = $sourceStorage->opendir($sourceInternalPath); - $result = $this->mkdir($targetInternalPath); - if (is_resource($dh)) { - while ($result and ($file = readdir($dh)) !== false) { - if (!Filesystem::isIgnoredDir($file)) { - $result &= $this->copyFromStorage($sourceStorage, $sourceInternalPath . '/' . $file, $targetInternalPath . '/' . $file); - } - } - } - } else { - $source = $sourceStorage->fopen($sourceInternalPath, 'r'); - // TODO: call fopen in a way that we execute again all storage wrappers - // to avoid that we bypass storage wrappers which perform important actions - // for this operation. Same is true for all other operations which - // are not the same as the original one.Once this is fixed we also - // need to adjust the encryption wrapper. - $target = $this->fopen($targetInternalPath, 'w'); - list(, $result) = \OC_Helper::streamCopy($source, $target); - if ($result and $preserveMtime) { - $this->touch($targetInternalPath, $sourceStorage->filemtime($sourceInternalPath)); - } - fclose($source); - fclose($target); - - if (!$result) { - // delete partially written target file - $this->unlink($targetInternalPath); - // delete cache entry that was created by fopen - $this->getCache()->remove($targetInternalPath); - } - } - return (bool)$result; - } - - /** - * @param \OCP\Files\Storage $sourceStorage - * @param string $sourceInternalPath - * @param string $targetInternalPath - * @return bool - */ - public function moveFromStorage(\OCP\Files\Storage $sourceStorage, $sourceInternalPath, $targetInternalPath) { - if ($sourceStorage === $this) { - return $this->rename($sourceInternalPath, $targetInternalPath); - } - - if (!$sourceStorage->isDeletable($sourceInternalPath)) { - return false; - } - - $result = $this->copyFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath, true); - if ($result) { - if ($sourceStorage->is_dir($sourceInternalPath)) { - $result &= $sourceStorage->rmdir($sourceInternalPath); - } else { - $result &= $sourceStorage->unlink($sourceInternalPath); - } - } - return $result; - } - - /** - * @inheritdoc - */ - public function getMetaData($path) { - $permissions = $this->getPermissions($path); - if (!$permissions & \OCP\Constants::PERMISSION_READ) { - //can't read, nothing we can do - return null; - } - - $data = []; - $data['mimetype'] = $this->getMimeType($path); - $data['mtime'] = $this->filemtime($path); - if ($data['mimetype'] == 'httpd/unix-directory') { - $data['size'] = -1; //unknown - } else { - $data['size'] = $this->filesize($path); - } - $data['etag'] = $this->getETag($path); - $data['storage_mtime'] = $data['mtime']; - $data['permissions'] = $permissions; - - return $data; - } - - /** - * @param string $path - * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE - * @param \OCP\Lock\ILockingProvider $provider - * @throws \OCP\Lock\LockedException - */ - public function acquireLock($path, $type, ILockingProvider $provider) { - $provider->acquireLock('files/' . md5($this->getId() . '::' . trim($path, '/')), $type); - } - - /** - * @param string $path - * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE - * @param \OCP\Lock\ILockingProvider $provider - */ - public function releaseLock($path, $type, ILockingProvider $provider) { - $provider->releaseLock('files/' . md5($this->getId() . '::' . trim($path, '/')), $type); - } - - /** - * @param string $path - * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE - * @param \OCP\Lock\ILockingProvider $provider - */ - public function changeLock($path, $type, ILockingProvider $provider) { - $provider->changeLock('files/' . md5($this->getId() . '::' . trim($path, '/')), $type); - } - - /** - * @return array [ available, last_checked ] - */ - public function getAvailability() { - return $this->getStorageCache()->getAvailability(); - } - - /** - * @param bool $isAvailable - */ - public function setAvailability($isAvailable) { - $this->getStorageCache()->setAvailability($isAvailable); - } -} diff --git a/lib/private/files/storage/commontest.php b/lib/private/files/storage/commontest.php deleted file mode 100644 index 0047a51169c..00000000000 --- a/lib/private/files/storage/commontest.php +++ /dev/null @@ -1,84 +0,0 @@ - - * @author Christopher Schäpers - * @author Felix Moeller - * @author Michael Gapczynski - * @author Morris Jobke - * @author Robin Appelman - * @author Thomas Müller - * - * @copyright Copyright (c) 2016, ownCloud, Inc. - * @license AGPL-3.0 - * - * This code is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License, version 3, - * along with this program. If not, see - * - */ - -/** - * test implementation for \OC\Files\Storage\Common with \OC\Files\Storage\Local - */ - -namespace OC\Files\Storage; - -class CommonTest extends \OC\Files\Storage\Common{ - /** - * underlying local storage used for missing functions - * @var \OC\Files\Storage\Local - */ - private $storage; - - public function __construct($params) { - $this->storage=new \OC\Files\Storage\Local($params); - } - - public function getId(){ - return 'test::'.$this->storage->getId(); - } - public function mkdir($path) { - return $this->storage->mkdir($path); - } - public function rmdir($path) { - return $this->storage->rmdir($path); - } - public function opendir($path) { - return $this->storage->opendir($path); - } - public function stat($path) { - return $this->storage->stat($path); - } - public function filetype($path) { - return @$this->storage->filetype($path); - } - public function isReadable($path) { - return $this->storage->isReadable($path); - } - public function isUpdatable($path) { - return $this->storage->isUpdatable($path); - } - public function file_exists($path) { - return $this->storage->file_exists($path); - } - public function unlink($path) { - return $this->storage->unlink($path); - } - public function fopen($path, $mode) { - return $this->storage->fopen($path, $mode); - } - public function free_space($path) { - return $this->storage->free_space($path); - } - public function touch($path, $mtime=null) { - return $this->storage->touch($path, $mtime); - } -} diff --git a/lib/private/files/storage/dav.php b/lib/private/files/storage/dav.php deleted file mode 100644 index 8eebea1f3ba..00000000000 --- a/lib/private/files/storage/dav.php +++ /dev/null @@ -1,817 +0,0 @@ - - * @author Björn Schießle - * @author Carlos Cerrillo - * @author Felix Moeller - * @author Jörn Friedrich Dreyer - * @author Lukas Reschke - * @author Michael Gapczynski - * @author Morris Jobke - * @author Philipp Kapfer - * @author Robin Appelman - * @author Thomas Müller - * @author Vincent Petry - * - * @copyright Copyright (c) 2016, ownCloud, Inc. - * @license AGPL-3.0 - * - * This code is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License, version 3, - * along with this program. If not, see - * - */ - -namespace OC\Files\Storage; - -use Exception; -use GuzzleHttp\Exception\RequestException; -use GuzzleHttp\Message\ResponseInterface; -use OC\Files\Filesystem; -use OC\Files\Stream\Close; -use Icewind\Streams\IteratorDirectory; -use OC\MemCache\ArrayCache; -use OCP\AppFramework\Http; -use OCP\Constants; -use OCP\Files; -use OCP\Files\FileInfo; -use OCP\Files\StorageInvalidException; -use OCP\Files\StorageNotAvailableException; -use OCP\Util; -use Sabre\DAV\Client; -use Sabre\DAV\Exception\NotFound; -use Sabre\DAV\Xml\Property\ResourceType; -use Sabre\HTTP\ClientException; -use Sabre\HTTP\ClientHttpException; - -/** - * Class DAV - * - * @package OC\Files\Storage - */ -class DAV extends Common { - /** @var string */ - protected $password; - /** @var string */ - protected $user; - /** @var string */ - protected $host; - /** @var bool */ - protected $secure; - /** @var string */ - protected $root; - /** @var string */ - protected $certPath; - /** @var bool */ - protected $ready; - /** @var Client */ - private $client; - /** @var ArrayCache */ - private $statCache; - /** @var array */ - private static $tempFiles = []; - /** @var \OCP\Http\Client\IClientService */ - private $httpClientService; - - /** - * @param array $params - * @throws \Exception - */ - public function __construct($params) { - $this->statCache = new ArrayCache(); - $this->httpClientService = \OC::$server->getHTTPClientService(); - if (isset($params['host']) && isset($params['user']) && isset($params['password'])) { - $host = $params['host']; - //remove leading http[s], will be generated in createBaseUri() - if (substr($host, 0, 8) == "https://") $host = substr($host, 8); - else if (substr($host, 0, 7) == "http://") $host = substr($host, 7); - $this->host = $host; - $this->user = $params['user']; - $this->password = $params['password']; - if (isset($params['secure'])) { - if (is_string($params['secure'])) { - $this->secure = ($params['secure'] === 'true'); - } else { - $this->secure = (bool)$params['secure']; - } - } else { - $this->secure = false; - } - if ($this->secure === true) { - // inject mock for testing - $certPath = \OC_User::getHome(\OC_User::getUser()) . '/files_external/rootcerts.crt'; - if (file_exists($certPath)) { - $this->certPath = $certPath; - } - } - $this->root = isset($params['root']) ? $params['root'] : '/'; - if (!$this->root || $this->root[0] != '/') { - $this->root = '/' . $this->root; - } - if (substr($this->root, -1, 1) != '/') { - $this->root .= '/'; - } - } else { - throw new \Exception('Invalid webdav storage configuration'); - } - } - - private function init() { - if ($this->ready) { - return; - } - $this->ready = true; - - $settings = array( - 'baseUri' => $this->createBaseUri(), - 'userName' => $this->user, - 'password' => $this->password, - ); - - $proxy = \OC::$server->getConfig()->getSystemValue('proxy', ''); - if($proxy !== '') { - $settings['proxy'] = $proxy; - } - - $this->client = new Client($settings); - $this->client->setThrowExceptions(true); - if ($this->secure === true && $this->certPath) { - $this->client->addCurlSetting(CURLOPT_CAINFO, $this->certPath); - } - } - - /** - * Clear the stat cache - */ - public function clearStatCache() { - $this->statCache->clear(); - } - - /** {@inheritdoc} */ - public function getId() { - return 'webdav::' . $this->user . '@' . $this->host . '/' . $this->root; - } - - /** {@inheritdoc} */ - public function createBaseUri() { - $baseUri = 'http'; - if ($this->secure) { - $baseUri .= 's'; - } - $baseUri .= '://' . $this->host . $this->root; - return $baseUri; - } - - /** {@inheritdoc} */ - public function mkdir($path) { - $this->init(); - $path = $this->cleanPath($path); - $result = $this->simpleResponse('MKCOL', $path, null, 201); - if ($result) { - $this->statCache->set($path, true); - } - return $result; - } - - /** {@inheritdoc} */ - public function rmdir($path) { - $this->init(); - $path = $this->cleanPath($path); - // FIXME: some WebDAV impl return 403 when trying to DELETE - // a non-empty folder - $result = $this->simpleResponse('DELETE', $path . '/', null, 204); - $this->statCache->clear($path . '/'); - $this->statCache->remove($path); - return $result; - } - - /** {@inheritdoc} */ - public function opendir($path) { - $this->init(); - $path = $this->cleanPath($path); - try { - $response = $this->client->propfind( - $this->encodePath($path), - array(), - 1 - ); - $id = md5('webdav' . $this->root . $path); - $content = array(); - $files = array_keys($response); - array_shift($files); //the first entry is the current directory - - if (!$this->statCache->hasKey($path)) { - $this->statCache->set($path, true); - } - foreach ($files as $file) { - $file = urldecode($file); - // do not store the real entry, we might not have all properties - if (!$this->statCache->hasKey($path)) { - $this->statCache->set($file, true); - } - $file = basename($file); - $content[] = $file; - } - return IteratorDirectory::wrap($content); - } catch (ClientHttpException $e) { - if ($e->getHttpStatus() === 404) { - $this->statCache->clear($path . '/'); - $this->statCache->set($path, false); - return false; - } - $this->convertException($e, $path); - } catch (\Exception $e) { - $this->convertException($e, $path); - } - return false; - } - - /** - * Propfind call with cache handling. - * - * First checks if information is cached. - * If not, request it from the server then store to cache. - * - * @param string $path path to propfind - * - * @return array propfind response - * - * @throws NotFound - */ - protected function propfind($path) { - $path = $this->cleanPath($path); - $cachedResponse = $this->statCache->get($path); - if ($cachedResponse === false) { - // we know it didn't exist - throw new NotFound(); - } - // we either don't know it, or we know it exists but need more details - if (is_null($cachedResponse) || $cachedResponse === true) { - $this->init(); - try { - $response = $this->client->propfind( - $this->encodePath($path), - array( - '{DAV:}getlastmodified', - '{DAV:}getcontentlength', - '{DAV:}getcontenttype', - '{http://owncloud.org/ns}permissions', - '{http://open-collaboration-services.org/ns}share-permissions', - '{DAV:}resourcetype', - '{DAV:}getetag', - ) - ); - $this->statCache->set($path, $response); - } catch (NotFound $e) { - // remember that this path did not exist - $this->statCache->clear($path . '/'); - $this->statCache->set($path, false); - throw $e; - } - } else { - $response = $cachedResponse; - } - return $response; - } - - /** {@inheritdoc} */ - public function filetype($path) { - try { - $response = $this->propfind($path); - $responseType = array(); - if (isset($response["{DAV:}resourcetype"])) { - /** @var ResourceType[] $response */ - $responseType = $response["{DAV:}resourcetype"]->getValue(); - } - return (count($responseType) > 0 and $responseType[0] == "{DAV:}collection") ? 'dir' : 'file'; - } catch (ClientHttpException $e) { - if ($e->getHttpStatus() === 404) { - return false; - } - $this->convertException($e, $path); - } catch (\Exception $e) { - $this->convertException($e, $path); - } - return false; - } - - /** {@inheritdoc} */ - public function file_exists($path) { - try { - $path = $this->cleanPath($path); - $cachedState = $this->statCache->get($path); - if ($cachedState === false) { - // we know the file doesn't exist - return false; - } else if (!is_null($cachedState)) { - return true; - } - // need to get from server - $this->propfind($path); - return true; //no 404 exception - } catch (ClientHttpException $e) { - if ($e->getHttpStatus() === 404) { - return false; - } - $this->convertException($e, $path); - } catch (\Exception $e) { - $this->convertException($e, $path); - } - return false; - } - - /** {@inheritdoc} */ - public function unlink($path) { - $this->init(); - $path = $this->cleanPath($path); - $result = $this->simpleResponse('DELETE', $path, null, 204); - $this->statCache->clear($path . '/'); - $this->statCache->remove($path); - return $result; - } - - /** {@inheritdoc} */ - public function fopen($path, $mode) { - $this->init(); - $path = $this->cleanPath($path); - switch ($mode) { - case 'r': - case 'rb': - try { - $response = $this->httpClientService - ->newClient() - ->get($this->createBaseUri() . $this->encodePath($path), [ - 'auth' => [$this->user, $this->password], - 'stream' => true - ]); - } catch (RequestException $e) { - if ($e->getResponse() instanceof ResponseInterface - && $e->getResponse()->getStatusCode() === 404) { - return false; - } else { - throw $e; - } - } - - if ($response->getStatusCode() !== Http::STATUS_OK) { - if ($response->getStatusCode() === Http::STATUS_LOCKED) { - throw new \OCP\Lock\LockedException($path); - } else { - Util::writeLog("webdav client", 'Guzzle get returned status code ' . $response->getStatusCode(), Util::ERROR); - } - } - - return $response->getBody(); - case 'w': - case 'wb': - case 'a': - case 'ab': - case 'r+': - case 'w+': - case 'wb+': - case 'a+': - case 'x': - case 'x+': - case 'c': - case 'c+': - //emulate these - $tempManager = \OC::$server->getTempManager(); - if (strrpos($path, '.') !== false) { - $ext = substr($path, strrpos($path, '.')); - } else { - $ext = ''; - } - if ($this->file_exists($path)) { - if (!$this->isUpdatable($path)) { - return false; - } - if ($mode === 'w' or $mode === 'w+') { - $tmpFile = $tempManager->getTemporaryFile($ext); - } else { - $tmpFile = $this->getCachedFile($path); - } - } else { - if (!$this->isCreatable(dirname($path))) { - return false; - } - $tmpFile = $tempManager->getTemporaryFile($ext); - } - Close::registerCallback($tmpFile, array($this, 'writeBack')); - self::$tempFiles[$tmpFile] = $path; - return fopen('close://' . $tmpFile, $mode); - } - } - - /** - * @param string $tmpFile - */ - public function writeBack($tmpFile) { - if (isset(self::$tempFiles[$tmpFile])) { - $this->uploadFile($tmpFile, self::$tempFiles[$tmpFile]); - unlink($tmpFile); - } - } - - /** {@inheritdoc} */ - public function free_space($path) { - $this->init(); - $path = $this->cleanPath($path); - try { - // TODO: cacheable ? - $response = $this->client->propfind($this->encodePath($path), array('{DAV:}quota-available-bytes')); - if (isset($response['{DAV:}quota-available-bytes'])) { - return (int)$response['{DAV:}quota-available-bytes']; - } else { - return FileInfo::SPACE_UNKNOWN; - } - } catch (\Exception $e) { - return FileInfo::SPACE_UNKNOWN; - } - } - - /** {@inheritdoc} */ - public function touch($path, $mtime = null) { - $this->init(); - if (is_null($mtime)) { - $mtime = time(); - } - $path = $this->cleanPath($path); - - // if file exists, update the mtime, else create a new empty file - if ($this->file_exists($path)) { - try { - $this->statCache->remove($path); - $this->client->proppatch($this->encodePath($path), array('{DAV:}lastmodified' => $mtime)); - } catch (ClientHttpException $e) { - if ($e->getHttpStatus() === 501) { - return false; - } - $this->convertException($e, $path); - return false; - } catch (\Exception $e) { - $this->convertException($e, $path); - return false; - } - } else { - $this->file_put_contents($path, ''); - } - return true; - } - - /** - * @param string $path - * @param string $data - * @return int - */ - public function file_put_contents($path, $data) { - $path = $this->cleanPath($path); - $result = parent::file_put_contents($path, $data); - $this->statCache->remove($path); - return $result; - } - - /** - * @param string $path - * @param string $target - */ - protected function uploadFile($path, $target) { - $this->init(); - - // invalidate - $target = $this->cleanPath($target); - $this->statCache->remove($target); - $source = fopen($path, 'r'); - - $this->httpClientService - ->newClient() - ->put($this->createBaseUri() . $this->encodePath($target), [ - 'body' => $source, - 'auth' => [$this->user, $this->password] - ]); - - $this->removeCachedFile($target); - } - - /** {@inheritdoc} */ - public function rename($path1, $path2) { - $this->init(); - $path1 = $this->cleanPath($path1); - $path2 = $this->cleanPath($path2); - try { - $this->client->request( - 'MOVE', - $this->encodePath($path1), - null, - array( - 'Destination' => $this->createBaseUri() . $this->encodePath($path2) - ) - ); - $this->statCache->clear($path1 . '/'); - $this->statCache->clear($path2 . '/'); - $this->statCache->set($path1, false); - $this->statCache->set($path2, true); - $this->removeCachedFile($path1); - $this->removeCachedFile($path2); - return true; - } catch (\Exception $e) { - $this->convertException($e); - } - return false; - } - - /** {@inheritdoc} */ - public function copy($path1, $path2) { - $this->init(); - $path1 = $this->encodePath($this->cleanPath($path1)); - $path2 = $this->createBaseUri() . $this->encodePath($this->cleanPath($path2)); - try { - $this->client->request('COPY', $path1, null, array('Destination' => $path2)); - $this->statCache->clear($path2 . '/'); - $this->statCache->set($path2, true); - $this->removeCachedFile($path2); - return true; - } catch (\Exception $e) { - $this->convertException($e); - } - return false; - } - - /** {@inheritdoc} */ - public function stat($path) { - try { - $response = $this->propfind($path); - return array( - 'mtime' => strtotime($response['{DAV:}getlastmodified']), - 'size' => (int)isset($response['{DAV:}getcontentlength']) ? $response['{DAV:}getcontentlength'] : 0, - ); - } catch (ClientHttpException $e) { - if ($e->getHttpStatus() === 404) { - return array(); - } - $this->convertException($e, $path); - } catch (\Exception $e) { - $this->convertException($e, $path); - } - return array(); - } - - /** {@inheritdoc} */ - public function getMimeType($path) { - try { - $response = $this->propfind($path); - $responseType = array(); - if (isset($response["{DAV:}resourcetype"])) { - /** @var ResourceType[] $response */ - $responseType = $response["{DAV:}resourcetype"]->getValue(); - } - $type = (count($responseType) > 0 and $responseType[0] == "{DAV:}collection") ? 'dir' : 'file'; - if ($type == 'dir') { - return 'httpd/unix-directory'; - } elseif (isset($response['{DAV:}getcontenttype'])) { - return $response['{DAV:}getcontenttype']; - } else { - return false; - } - } catch (ClientHttpException $e) { - if ($e->getHttpStatus() === 404) { - return false; - } - $this->convertException($e, $path); - } catch (\Exception $e) { - $this->convertException($e, $path); - } - return false; - } - - /** - * @param string $path - * @return string - */ - public function cleanPath($path) { - if ($path === '') { - return $path; - } - $path = Filesystem::normalizePath($path); - // remove leading slash - return substr($path, 1); - } - - /** - * URL encodes the given path but keeps the slashes - * - * @param string $path to encode - * @return string encoded path - */ - private function encodePath($path) { - // slashes need to stay - return str_replace('%2F', '/', rawurlencode($path)); - } - - /** - * @param string $method - * @param string $path - * @param string|resource|null $body - * @param int $expected - * @return bool - * @throws StorageInvalidException - * @throws StorageNotAvailableException - */ - private function simpleResponse($method, $path, $body, $expected) { - $path = $this->cleanPath($path); - try { - $response = $this->client->request($method, $this->encodePath($path), $body); - return $response['statusCode'] == $expected; - } catch (ClientHttpException $e) { - if ($e->getHttpStatus() === 404 && $method === 'DELETE') { - $this->statCache->clear($path . '/'); - $this->statCache->set($path, false); - return false; - } - - $this->convertException($e, $path); - } catch (\Exception $e) { - $this->convertException($e, $path); - } - return false; - } - - /** - * check if curl is installed - */ - public static function checkDependencies() { - return true; - } - - /** {@inheritdoc} */ - public function isUpdatable($path) { - return (bool)($this->getPermissions($path) & Constants::PERMISSION_UPDATE); - } - - /** {@inheritdoc} */ - public function isCreatable($path) { - return (bool)($this->getPermissions($path) & Constants::PERMISSION_CREATE); - } - - /** {@inheritdoc} */ - public function isSharable($path) { - return (bool)($this->getPermissions($path) & Constants::PERMISSION_SHARE); - } - - /** {@inheritdoc} */ - public function isDeletable($path) { - return (bool)($this->getPermissions($path) & Constants::PERMISSION_DELETE); - } - - /** {@inheritdoc} */ - public function getPermissions($path) { - $this->init(); - $path = $this->cleanPath($path); - $response = $this->propfind($path); - if (isset($response['{http://owncloud.org/ns}permissions'])) { - return $this->parsePermissions($response['{http://owncloud.org/ns}permissions']); - } else if ($this->is_dir($path)) { - return Constants::PERMISSION_ALL; - } else if ($this->file_exists($path)) { - return Constants::PERMISSION_ALL - Constants::PERMISSION_CREATE; - } else { - return 0; - } - } - - /** {@inheritdoc} */ - public function getETag($path) { - $this->init(); - $path = $this->cleanPath($path); - $response = $this->propfind($path); - if (isset($response['{DAV:}getetag'])) { - return trim($response['{DAV:}getetag'], '"'); - } - return parent::getEtag($path); - } - - /** - * @param string $permissionsString - * @return int - */ - protected function parsePermissions($permissionsString) { - $permissions = Constants::PERMISSION_READ; - if (strpos($permissionsString, 'R') !== false) { - $permissions |= Constants::PERMISSION_SHARE; - } - if (strpos($permissionsString, 'D') !== false) { - $permissions |= Constants::PERMISSION_DELETE; - } - if (strpos($permissionsString, 'W') !== false) { - $permissions |= Constants::PERMISSION_UPDATE; - } - if (strpos($permissionsString, 'CK') !== false) { - $permissions |= Constants::PERMISSION_CREATE; - $permissions |= Constants::PERMISSION_UPDATE; - } - return $permissions; - } - - /** - * check if a file or folder has been updated since $time - * - * @param string $path - * @param int $time - * @throws \OCP\Files\StorageNotAvailableException - * @return bool - */ - public function hasUpdated($path, $time) { - $this->init(); - $path = $this->cleanPath($path); - try { - // force refresh for $path - $this->statCache->remove($path); - $response = $this->propfind($path); - if (isset($response['{DAV:}getetag'])) { - $cachedData = $this->getCache()->get($path); - $etag = null; - if (isset($response['{DAV:}getetag'])) { - $etag = trim($response['{DAV:}getetag'], '"'); - } - if (!empty($etag) && $cachedData['etag'] !== $etag) { - return true; - } else if (isset($response['{http://open-collaboration-services.org/ns}share-permissions'])) { - $sharePermissions = (int)$response['{http://open-collaboration-services.org/ns}share-permissions']; - return $sharePermissions !== $cachedData['permissions']; - } else if (isset($response['{http://owncloud.org/ns}permissions'])) { - $permissions = $this->parsePermissions($response['{http://owncloud.org/ns}permissions']); - return $permissions !== $cachedData['permissions']; - } else { - return false; - } - } else { - $remoteMtime = strtotime($response['{DAV:}getlastmodified']); - return $remoteMtime > $time; - } - } catch (ClientHttpException $e) { - if ($e->getHttpStatus() === 404 || $e->getHttpStatus() === 405) { - if ($path === '') { - // if root is gone it means the storage is not available - throw new StorageNotAvailableException(get_class($e) . ': ' . $e->getMessage()); - } - return false; - } - $this->convertException($e, $path); - return false; - } catch (\Exception $e) { - $this->convertException($e, $path); - return false; - } - } - - /** - * Interpret the given exception and decide whether it is due to an - * unavailable storage, invalid storage or other. - * This will either throw StorageInvalidException, StorageNotAvailableException - * or do nothing. - * - * @param Exception $e sabre exception - * @param string $path optional path from the operation - * - * @throws StorageInvalidException if the storage is invalid, for example - * when the authentication expired or is invalid - * @throws StorageNotAvailableException if the storage is not available, - * which might be temporary - */ - private function convertException(Exception $e, $path = '') { - Util::writeLog('files_external', $e->getMessage(), Util::ERROR); - if ($e instanceof ClientHttpException) { - if ($e->getHttpStatus() === 423) { - throw new \OCP\Lock\LockedException($path); - } - if ($e->getHttpStatus() === 401) { - // either password was changed or was invalid all along - throw new StorageInvalidException(get_class($e) . ': ' . $e->getMessage()); - } else if ($e->getHttpStatus() === 405) { - // ignore exception for MethodNotAllowed, false will be returned - return; - } - throw new StorageNotAvailableException(get_class($e) . ': ' . $e->getMessage()); - } else if ($e instanceof ClientException) { - // connection timeout or refused, server could be temporarily down - throw new StorageNotAvailableException(get_class($e) . ': ' . $e->getMessage()); - } else if ($e instanceof \InvalidArgumentException) { - // parse error because the server returned HTML instead of XML, - // possibly temporarily down - throw new StorageNotAvailableException(get_class($e) . ': ' . $e->getMessage()); - } else if (($e instanceof StorageNotAvailableException) || ($e instanceof StorageInvalidException)) { - // rethrow - throw $e; - } - - // TODO: only log for now, but in the future need to wrap/rethrow exception - } -} - diff --git a/lib/private/files/storage/failedstorage.php b/lib/private/files/storage/failedstorage.php deleted file mode 100644 index df7f76856d5..00000000000 --- a/lib/private/files/storage/failedstorage.php +++ /dev/null @@ -1,215 +0,0 @@ - - * @author Robin McCorkell - * - * @copyright Copyright (c) 2016, ownCloud, Inc. - * @license AGPL-3.0 - * - * This code is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License, version 3, - * along with this program. If not, see - * - */ - -namespace OC\Files\Storage; - -use OC\Files\Cache\FailedCache; -use \OCP\Lock\ILockingProvider; -use \OCP\Files\StorageNotAvailableException; - -/** - * Storage placeholder to represent a missing precondition, storage unavailable - */ -class FailedStorage extends Common { - - /** @var \Exception */ - protected $e; - - /** - * @param array $params ['exception' => \Exception] - */ - public function __construct($params) { - $this->e = $params['exception']; - if (!$this->e) { - throw new \InvalidArgumentException('Missing "exception" argument in FailedStorage constructor'); - } - } - - public function getId() { - // we can't return anything sane here - return 'failedstorage'; - } - - public function mkdir($path) { - throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e); - } - - public function rmdir($path) { - throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e); - } - - public function opendir($path) { - throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e); - } - - public function is_dir($path) { - throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e); - } - - public function is_file($path) { - throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e); - } - - public function stat($path) { - throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e); - } - - public function filetype($path) { - throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e); - } - - public function filesize($path) { - throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e); - } - - public function isCreatable($path) { - throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e); - } - - public function isReadable($path) { - throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e); - } - - public function isUpdatable($path) { - throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e); - } - - public function isDeletable($path) { - throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e); - } - - public function isSharable($path) { - throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e); - } - - public function getPermissions($path) { - throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e); - } - - public function file_exists($path) { - throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e); - } - - public function filemtime($path) { - throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e); - } - - public function file_get_contents($path) { - throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e); - } - - public function file_put_contents($path, $data) { - throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e); - } - - public function unlink($path) { - throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e); - } - - public function rename($path1, $path2) { - throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e); - } - - public function copy($path1, $path2) { - throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e); - } - - public function fopen($path, $mode) { - throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e); - } - - public function getMimeType($path) { - throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e); - } - - public function hash($type, $path, $raw = false) { - throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e); - } - - public function free_space($path) { - throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e); - } - - public function search($query) { - throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e); - } - - public function touch($path, $mtime = null) { - throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e); - } - - public function getLocalFile($path) { - throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e); - } - - public function getLocalFolder($path) { - throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e); - } - - public function hasUpdated($path, $time) { - throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e); - } - - public function getETag($path) { - throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e); - } - - public function getDirectDownload($path) { - throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e); - } - - public function verifyPath($path, $fileName) { - return true; - } - - public function copyFromStorage(\OCP\Files\Storage $sourceStorage, $sourceInternalPath, $targetInternalPath) { - throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e); - } - - public function moveFromStorage(\OCP\Files\Storage $sourceStorage, $sourceInternalPath, $targetInternalPath) { - throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e); - } - - public function acquireLock($path, $type, ILockingProvider $provider) { - throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e); - } - - public function releaseLock($path, $type, ILockingProvider $provider) { - throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e); - } - - public function changeLock($path, $type, ILockingProvider $provider) { - throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e); - } - - public function getAvailability() { - throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e); - } - - public function setAvailability($isAvailable) { - throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e); - } - - public function getCache($path = '', $storage = null) { - return new FailedCache(); - } -} diff --git a/lib/private/files/storage/flysystem.php b/lib/private/files/storage/flysystem.php deleted file mode 100644 index 608639b71a6..00000000000 --- a/lib/private/files/storage/flysystem.php +++ /dev/null @@ -1,256 +0,0 @@ - - * - * @copyright Copyright (c) 2016, ownCloud, Inc. - * @license AGPL-3.0 - * - * This code is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License, version 3, - * along with this program. If not, see - * - */ - -namespace OC\Files\Storage; - -use Icewind\Streams\CallbackWrapper; -use Icewind\Streams\IteratorDirectory; -use League\Flysystem\AdapterInterface; -use League\Flysystem\FileNotFoundException; -use League\Flysystem\Filesystem; -use League\Flysystem\Plugin\GetWithMetadata; - -/** - * Generic adapter between flysystem adapters and owncloud's storage system - * - * To use: subclass and call $this->buildFlysystem with the flysystem adapter of choice - */ -abstract class Flysystem extends Common { - /** - * @var Filesystem - */ - protected $flysystem; - - /** - * @var string - */ - protected $root = ''; - - /** - * Initialize the storage backend with a flyssytem adapter - * - * @param \League\Flysystem\AdapterInterface $adapter - */ - protected function buildFlySystem(AdapterInterface $adapter) { - $this->flysystem = new Filesystem($adapter); - $this->flysystem->addPlugin(new GetWithMetadata()); - } - - protected function buildPath($path) { - $fullPath = \OC\Files\Filesystem::normalizePath($this->root . '/' . $path); - return ltrim($fullPath, '/'); - } - - /** - * {@inheritdoc} - */ - public function file_get_contents($path) { - return $this->flysystem->read($this->buildPath($path)); - } - - /** - * {@inheritdoc} - */ - public function file_put_contents($path, $data) { - return $this->flysystem->put($this->buildPath($path), $data); - } - - /** - * {@inheritdoc} - */ - public function file_exists($path) { - return $this->flysystem->has($this->buildPath($path)); - } - - /** - * {@inheritdoc} - */ - public function unlink($path) { - if ($this->is_dir($path)) { - return $this->rmdir($path); - } - try { - return $this->flysystem->delete($this->buildPath($path)); - } catch (FileNotFoundException $e) { - return false; - } - } - - /** - * {@inheritdoc} - */ - public function rename($source, $target) { - if ($this->file_exists($target)) { - $this->unlink($target); - } - return $this->flysystem->rename($this->buildPath($source), $this->buildPath($target)); - } - - /** - * {@inheritdoc} - */ - public function copy($source, $target) { - if ($this->file_exists($target)) { - $this->unlink($target); - } - return $this->flysystem->copy($this->buildPath($source), $this->buildPath($target)); - } - - /** - * {@inheritdoc} - */ - public function filesize($path) { - if ($this->is_dir($path)) { - return 0; - } else { - return $this->flysystem->getSize($this->buildPath($path)); - } - } - - /** - * {@inheritdoc} - */ - public function mkdir($path) { - if ($this->file_exists($path)) { - return false; - } - return $this->flysystem->createDir($this->buildPath($path)); - } - - /** - * {@inheritdoc} - */ - public function filemtime($path) { - return $this->flysystem->getTimestamp($this->buildPath($path)); - } - - /** - * {@inheritdoc} - */ - public function rmdir($path) { - try { - return @$this->flysystem->deleteDir($this->buildPath($path)); - } catch (FileNotFoundException $e) { - return false; - } - } - - /** - * {@inheritdoc} - */ - public function opendir($path) { - try { - $content = $this->flysystem->listContents($this->buildPath($path)); - } catch (FileNotFoundException $e) { - return false; - } - $names = array_map(function ($object) { - return $object['basename']; - }, $content); - return IteratorDirectory::wrap($names); - } - - /** - * {@inheritdoc} - */ - public function fopen($path, $mode) { - $fullPath = $this->buildPath($path); - $useExisting = true; - switch ($mode) { - case 'r': - case 'rb': - try { - return $this->flysystem->readStream($fullPath); - } catch (FileNotFoundException $e) { - return false; - } - case 'w': - case 'w+': - case 'wb': - case 'wb+': - $useExisting = false; - case 'a': - case 'ab': - case 'r+': - case 'a+': - case 'x': - case 'x+': - case 'c': - case 'c+': - //emulate these - if ($useExisting and $this->file_exists($path)) { - if (!$this->isUpdatable($path)) { - return false; - } - $tmpFile = $this->getCachedFile($path); - } else { - if (!$this->isCreatable(dirname($path))) { - return false; - } - $tmpFile = \OCP\Files::tmpFile(); - } - $source = fopen($tmpFile, $mode); - return CallbackWrapper::wrap($source, null, null, function () use ($tmpFile, $fullPath) { - $this->flysystem->putStream($fullPath, fopen($tmpFile, 'r')); - unlink($tmpFile); - }); - } - return false; - } - - /** - * {@inheritdoc} - */ - public function touch($path, $mtime = null) { - if ($this->file_exists($path)) { - return false; - } else { - $this->file_put_contents($path, ''); - return true; - } - } - - /** - * {@inheritdoc} - */ - public function stat($path) { - $info = $this->flysystem->getWithMetadata($this->buildPath($path), ['timestamp', 'size']); - return [ - 'mtime' => $info['timestamp'], - 'size' => $info['size'] - ]; - } - - /** - * {@inheritdoc} - */ - public function filetype($path) { - if ($path === '' or $path === '/' or $path === '.') { - return 'dir'; - } - try { - $info = $this->flysystem->getMetadata($this->buildPath($path)); - } catch (FileNotFoundException $e) { - return false; - } - return $info['type']; - } -} diff --git a/lib/private/files/storage/home.php b/lib/private/files/storage/home.php deleted file mode 100644 index 9b98f2f7e12..00000000000 --- a/lib/private/files/storage/home.php +++ /dev/null @@ -1,114 +0,0 @@ - - * @author Morris Jobke - * @author Robin Appelman - * @author Thomas Müller - * @author Vincent Petry - * - * @copyright Copyright (c) 2016, ownCloud, Inc. - * @license AGPL-3.0 - * - * This code is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License, version 3, - * along with this program. If not, see - * - */ - -namespace OC\Files\Storage; -use OC\Files\Cache\HomePropagator; - -/** - * Specialized version of Local storage for home directory usage - */ -class Home extends Local implements \OCP\Files\IHomeStorage { - /** - * @var string - */ - protected $id; - - /** - * @var \OC\User\User $user - */ - protected $user; - - /** - * Construct a Home storage instance - * @param array $arguments array with "user" containing the - * storage owner and "legacy" containing "true" if the storage is - * a legacy storage with "local::" URL instead of the new "home::" one. - */ - public function __construct($arguments) { - $this->user = $arguments['user']; - $datadir = $this->user->getHome(); - if (isset($arguments['legacy']) && $arguments['legacy']) { - // legacy home id (<= 5.0.12) - $this->id = 'local::' . $datadir . '/'; - } - else { - $this->id = 'home::' . $this->user->getUID(); - } - - parent::__construct(array('datadir' => $datadir)); - } - - public function getId() { - return $this->id; - } - - /** - * @return \OC\Files\Cache\HomeCache - */ - public function getCache($path = '', $storage = null) { - if (!$storage) { - $storage = $this; - } - if (!isset($this->cache)) { - $this->cache = new \OC\Files\Cache\HomeCache($storage); - } - return $this->cache; - } - - /** - * get a propagator instance for the cache - * - * @param \OC\Files\Storage\Storage (optional) the storage to pass to the watcher - * @return \OC\Files\Cache\Propagator - */ - public function getPropagator($storage = null) { - if (!$storage) { - $storage = $this; - } - if (!isset($this->propagator)) { - $this->propagator = new HomePropagator($storage); - } - return $this->propagator; - } - - - /** - * Returns the owner of this home storage - * @return \OC\User\User owner of this home storage - */ - public function getUser() { - return $this->user; - } - - /** - * get the owner of a path - * - * @param string $path The path to get the owner - * @return string uid or false - */ - public function getOwner($path) { - return $this->user->getUID(); - } -} diff --git a/lib/private/files/storage/local.php b/lib/private/files/storage/local.php deleted file mode 100644 index 25b202af5f8..00000000000 --- a/lib/private/files/storage/local.php +++ /dev/null @@ -1,404 +0,0 @@ - - * @author Brice Maron - * @author Jakob Sack - * @author Joas Schilling - * @author Klaas Freitag - * @author Martin Mattel - * @author Michael Gapczynski - * @author Morris Jobke - * @author Robin Appelman - * @author Sjors van der Pluijm - * @author Thomas Müller - * @author Tigran Mkrtchyan - * @author Vincent Petry - * - * @copyright Copyright (c) 2016, ownCloud, Inc. - * @license AGPL-3.0 - * - * This code is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License, version 3, - * along with this program. If not, see - * - */ - -namespace OC\Files\Storage; -/** - * for local filestore, we only have to map the paths - */ -class Local extends \OC\Files\Storage\Common { - protected $datadir; - - public function __construct($arguments) { - $this->datadir = $arguments['datadir']; - if (substr($this->datadir, -1) !== '/') { - $this->datadir .= '/'; - } - } - - public function __destruct() { - } - - public function getId() { - return 'local::' . $this->datadir; - } - - public function mkdir($path) { - return @mkdir($this->getSourcePath($path), 0777, true); - } - - public function rmdir($path) { - if (!$this->isDeletable($path)) { - return false; - } - try { - $it = new \RecursiveIteratorIterator( - new \RecursiveDirectoryIterator($this->getSourcePath($path)), - \RecursiveIteratorIterator::CHILD_FIRST - ); - /** - * RecursiveDirectoryIterator on an NFS path isn't iterable with foreach - * This bug is fixed in PHP 5.5.9 or before - * See #8376 - */ - $it->rewind(); - while ($it->valid()) { - /** - * @var \SplFileInfo $file - */ - $file = $it->current(); - if (in_array($file->getBasename(), array('.', '..'))) { - $it->next(); - continue; - } elseif ($file->isDir()) { - rmdir($file->getPathname()); - } elseif ($file->isFile() || $file->isLink()) { - unlink($file->getPathname()); - } - $it->next(); - } - return rmdir($this->getSourcePath($path)); - } catch (\UnexpectedValueException $e) { - return false; - } - } - - public function opendir($path) { - return opendir($this->getSourcePath($path)); - } - - public function is_dir($path) { - if (substr($path, -1) == '/') { - $path = substr($path, 0, -1); - } - return is_dir($this->getSourcePath($path)); - } - - public function is_file($path) { - return is_file($this->getSourcePath($path)); - } - - public function stat($path) { - clearstatcache(); - $fullPath = $this->getSourcePath($path); - $statResult = stat($fullPath); - if (PHP_INT_SIZE === 4 && !$this->is_dir($path)) { - $filesize = $this->filesize($path); - $statResult['size'] = $filesize; - $statResult[7] = $filesize; - } - return $statResult; - } - - public function filetype($path) { - $filetype = filetype($this->getSourcePath($path)); - if ($filetype == 'link') { - $filetype = filetype(realpath($this->getSourcePath($path))); - } - return $filetype; - } - - public function filesize($path) { - if ($this->is_dir($path)) { - return 0; - } - $fullPath = $this->getSourcePath($path); - if (PHP_INT_SIZE === 4) { - $helper = new \OC\LargeFileHelper; - return $helper->getFilesize($fullPath); - } - return filesize($fullPath); - } - - public function isReadable($path) { - return is_readable($this->getSourcePath($path)); - } - - public function isUpdatable($path) { - return is_writable($this->getSourcePath($path)); - } - - public function file_exists($path) { - return file_exists($this->getSourcePath($path)); - } - - public function filemtime($path) { - clearstatcache($this->getSourcePath($path)); - return filemtime($this->getSourcePath($path)); - } - - public function touch($path, $mtime = null) { - // sets the modification time of the file to the given value. - // If mtime is nil the current time is set. - // note that the access time of the file always changes to the current time. - if ($this->file_exists($path) and !$this->isUpdatable($path)) { - return false; - } - if (!is_null($mtime)) { - $result = touch($this->getSourcePath($path), $mtime); - } else { - $result = touch($this->getSourcePath($path)); - } - if ($result) { - clearstatcache(true, $this->getSourcePath($path)); - } - - return $result; - } - - public function file_get_contents($path) { - // file_get_contents() has a memory leak: https://bugs.php.net/bug.php?id=61961 - $fileName = $this->getSourcePath($path); - - $fileSize = filesize($fileName); - if ($fileSize === 0) { - return ''; - } - - $handle = fopen($fileName,'rb'); - $content = fread($handle, $fileSize); - fclose($handle); - return $content; - } - - public function file_put_contents($path, $data) { - return file_put_contents($this->getSourcePath($path), $data); - } - - public function unlink($path) { - if ($this->is_dir($path)) { - return $this->rmdir($path); - } else if ($this->is_file($path)) { - return unlink($this->getSourcePath($path)); - } else { - return false; - } - - } - - public function rename($path1, $path2) { - $srcParent = dirname($path1); - $dstParent = dirname($path2); - - if (!$this->isUpdatable($srcParent)) { - \OCP\Util::writeLog('core', 'unable to rename, source directory is not writable : ' . $srcParent, \OCP\Util::ERROR); - return false; - } - - if (!$this->isUpdatable($dstParent)) { - \OCP\Util::writeLog('core', 'unable to rename, destination directory is not writable : ' . $dstParent, \OCP\Util::ERROR); - return false; - } - - if (!$this->file_exists($path1)) { - \OCP\Util::writeLog('core', 'unable to rename, file does not exists : ' . $path1, \OCP\Util::ERROR); - return false; - } - - if ($this->is_dir($path2)) { - $this->rmdir($path2); - } else if ($this->is_file($path2)) { - $this->unlink($path2); - } - - if ($this->is_dir($path1)) { - // we can't move folders across devices, use copy instead - $stat1 = stat(dirname($this->getSourcePath($path1))); - $stat2 = stat(dirname($this->getSourcePath($path2))); - if ($stat1['dev'] !== $stat2['dev']) { - $result = $this->copy($path1, $path2); - if ($result) { - $result &= $this->rmdir($path1); - } - return $result; - } - } - - return rename($this->getSourcePath($path1), $this->getSourcePath($path2)); - } - - public function copy($path1, $path2) { - if ($this->is_dir($path1)) { - return parent::copy($path1, $path2); - } else { - return copy($this->getSourcePath($path1), $this->getSourcePath($path2)); - } - } - - public function fopen($path, $mode) { - return fopen($this->getSourcePath($path), $mode); - } - - public function hash($type, $path, $raw = false) { - return hash_file($type, $this->getSourcePath($path), $raw); - } - - public function free_space($path) { - $sourcePath = $this->getSourcePath($path); - // using !is_dir because $sourcePath might be a part file or - // non-existing file, so we'd still want to use the parent dir - // in such cases - if (!is_dir($sourcePath)) { - // disk_free_space doesn't work on files - $sourcePath = dirname($sourcePath); - } - $space = @disk_free_space($sourcePath); - if ($space === false || is_null($space)) { - return \OCP\Files\FileInfo::SPACE_UNKNOWN; - } - return $space; - } - - public function search($query) { - return $this->searchInDir($query); - } - - public function getLocalFile($path) { - return $this->getSourcePath($path); - } - - public function getLocalFolder($path) { - return $this->getSourcePath($path); - } - - /** - * @param string $query - * @param string $dir - * @return array - */ - protected function searchInDir($query, $dir = '') { - $files = array(); - $physicalDir = $this->getSourcePath($dir); - foreach (scandir($physicalDir) as $item) { - if (\OC\Files\Filesystem::isIgnoredDir($item)) - continue; - $physicalItem = $physicalDir . '/' . $item; - - if (strstr(strtolower($item), strtolower($query)) !== false) { - $files[] = $dir . '/' . $item; - } - if (is_dir($physicalItem)) { - $files = array_merge($files, $this->searchInDir($query, $dir . '/' . $item)); - } - } - return $files; - } - - /** - * check if a file or folder has been updated since $time - * - * @param string $path - * @param int $time - * @return bool - */ - public function hasUpdated($path, $time) { - if ($this->file_exists($path)) { - return $this->filemtime($path) > $time; - } else { - return true; - } - } - - /** - * Get the source path (on disk) of a given path - * - * @param string $path - * @return string - */ - public function getSourcePath($path) { - $fullPath = $this->datadir . $path; - return $fullPath; - } - - /** - * {@inheritdoc} - */ - public function isLocal() { - return true; - } - - /** - * get the ETag for a file or folder - * - * @param string $path - * @return string - */ - public function getETag($path) { - if ($this->is_file($path)) { - $stat = $this->stat($path); - return md5( - $stat['mtime'] . - $stat['ino'] . - $stat['dev'] . - $stat['size'] - ); - } else { - return parent::getETag($path); - } - } - - /** - * @param \OCP\Files\Storage $sourceStorage - * @param string $sourceInternalPath - * @param string $targetInternalPath - * @return bool - */ - public function copyFromStorage(\OCP\Files\Storage $sourceStorage, $sourceInternalPath, $targetInternalPath) { - if($sourceStorage->instanceOfStorage('\OC\Files\Storage\Local')){ - /** - * @var \OC\Files\Storage\Local $sourceStorage - */ - $rootStorage = new Local(['datadir' => '/']); - return $rootStorage->copy($sourceStorage->getSourcePath($sourceInternalPath), $this->getSourcePath($targetInternalPath)); - } else { - return parent::copyFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath); - } - } - - /** - * @param \OCP\Files\Storage $sourceStorage - * @param string $sourceInternalPath - * @param string $targetInternalPath - * @return bool - */ - public function moveFromStorage(\OCP\Files\Storage $sourceStorage, $sourceInternalPath, $targetInternalPath) { - if ($sourceStorage->instanceOfStorage('\OC\Files\Storage\Local')) { - /** - * @var \OC\Files\Storage\Local $sourceStorage - */ - $rootStorage = new Local(['datadir' => '/']); - return $rootStorage->rename($sourceStorage->getSourcePath($sourceInternalPath), $this->getSourcePath($targetInternalPath)); - } else { - return parent::moveFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath); - } - } -} diff --git a/lib/private/files/storage/localtempfiletrait.php b/lib/private/files/storage/localtempfiletrait.php deleted file mode 100644 index 88f11e4e752..00000000000 --- a/lib/private/files/storage/localtempfiletrait.php +++ /dev/null @@ -1,80 +0,0 @@ - - * @author Morris Jobke - * @author Thomas Müller - * - * @copyright Copyright (c) 2016, ownCloud, Inc. - * @license AGPL-3.0 - * - * This code is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License, version 3, - * along with this program. If not, see - * - */ - -namespace OC\Files\Storage; - -/** - * Storage backend class for providing common filesystem operation methods - * which are not storage-backend specific. - * - * \OC\Files\Storage\Common is never used directly; it is extended by all other - * storage backends, where its methods may be overridden, and additional - * (backend-specific) methods are defined. - * - * Some \OC\Files\Storage\Common methods call functions which are first defined - * in classes which extend it, e.g. $this->stat() . - */ -trait LocalTempFileTrait { - - /** @var string[] */ - protected $cachedFiles = []; - - /** - * @param string $path - * @return string - */ - protected function getCachedFile($path) { - if (!isset($this->cachedFiles[$path])) { - $this->cachedFiles[$path] = $this->toTmpFile($path); - } - return $this->cachedFiles[$path]; - } - - /** - * @param string $path - */ - protected function removeCachedFile($path) { - unset($this->cachedFiles[$path]); - } - - /** - * @param string $path - * @return string - */ - protected function toTmpFile($path) { //no longer in the storage api, still useful here - $source = $this->fopen($path, 'r'); - if (!$source) { - return false; - } - if ($pos = strrpos($path, '.')) { - $extension = substr($path, $pos); - } else { - $extension = ''; - } - $tmpFile = \OC::$server->getTempManager()->getTemporaryFile($extension); - $target = fopen($tmpFile, 'w'); - \OC_Helper::streamCopy($source, $target); - fclose($target); - return $tmpFile; - } -} diff --git a/lib/private/files/storage/polyfill/copydirectory.php b/lib/private/files/storage/polyfill/copydirectory.php deleted file mode 100644 index d4cac117ade..00000000000 --- a/lib/private/files/storage/polyfill/copydirectory.php +++ /dev/null @@ -1,103 +0,0 @@ - - * @author Robin Appelman - * - * @copyright Copyright (c) 2016, ownCloud, Inc. - * @license AGPL-3.0 - * - * This code is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License, version 3, - * along with this program. If not, see - * - */ - -namespace OC\Files\Storage\PolyFill; - -trait CopyDirectory { - /** - * Check if a path is a directory - * - * @param string $path - * @return bool - */ - abstract public function is_dir($path); - - /** - * Check if a file or folder exists - * - * @param string $path - * @return bool - */ - abstract public function file_exists($path); - - /** - * Delete a file or folder - * - * @param string $path - * @return bool - */ - abstract public function unlink($path); - - /** - * Open a directory handle for a folder - * - * @param string $path - * @return resource | bool - */ - abstract public function opendir($path); - - /** - * Create a new folder - * - * @param string $path - * @return bool - */ - abstract public function mkdir($path); - - public function copy($source, $target) { - if ($this->is_dir($source)) { - if ($this->file_exists($target)) { - $this->unlink($target); - } - $this->mkdir($target); - return $this->copyRecursive($source, $target); - } else { - return parent::copy($source, $target); - } - } - - /** - * For adapters that don't support copying folders natively - * - * @param $source - * @param $target - * @return bool - */ - protected function copyRecursive($source, $target) { - $dh = $this->opendir($source); - $result = true; - while ($file = readdir($dh)) { - if (!\OC\Files\Filesystem::isIgnoredDir($file)) { - if ($this->is_dir($source . '/' . $file)) { - $this->mkdir($target . '/' . $file); - $result = $this->copyRecursive($source . '/' . $file, $target . '/' . $file); - } else { - $result = parent::copy($source . '/' . $file, $target . '/' . $file); - } - if (!$result) { - break; - } - } - } - return $result; - } -} diff --git a/lib/private/files/storage/storage.php b/lib/private/files/storage/storage.php deleted file mode 100644 index c066336d4b8..00000000000 --- a/lib/private/files/storage/storage.php +++ /dev/null @@ -1,119 +0,0 @@ - - * @author Robin Appelman - * @author Thomas Müller - * - * @copyright Copyright (c) 2016, ownCloud, Inc. - * @license AGPL-3.0 - * - * This code is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License, version 3, - * along with this program. If not, see - * - */ - -namespace OC\Files\Storage; -use OCP\Lock\ILockingProvider; - -/** - * Provide a common interface to all different storage options - * - * All paths passed to the storage are relative to the storage and should NOT have a leading slash. - */ -interface Storage extends \OCP\Files\Storage { - - /** - * get a cache instance for the storage - * - * @param string $path - * @param \OC\Files\Storage\Storage (optional) the storage to pass to the cache - * @return \OC\Files\Cache\Cache - */ - public function getCache($path = '', $storage = null); - - /** - * get a scanner instance for the storage - * - * @param string $path - * @param \OC\Files\Storage\Storage (optional) the storage to pass to the scanner - * @return \OC\Files\Cache\Scanner - */ - public function getScanner($path = '', $storage = null); - - - /** - * get the user id of the owner of a file or folder - * - * @param string $path - * @return string - */ - public function getOwner($path); - - /** - * get a watcher instance for the cache - * - * @param string $path - * @param \OC\Files\Storage\Storage (optional) the storage to pass to the watcher - * @return \OC\Files\Cache\Watcher - */ - public function getWatcher($path = '', $storage = null); - - /** - * get a propagator instance for the cache - * - * @param \OC\Files\Storage\Storage (optional) the storage to pass to the watcher - * @return \OC\Files\Cache\Propagator - */ - public function getPropagator($storage = null); - - /** - * get a updater instance for the cache - * - * @param \OC\Files\Storage\Storage (optional) the storage to pass to the watcher - * @return \OC\Files\Cache\Updater - */ - public function getUpdater($storage = null); - - /** - * @return \OC\Files\Cache\Storage - */ - public function getStorageCache(); - - /** - * @param string $path - * @return array - */ - public function getMetaData($path); - - /** - * @param string $path The path of the file to acquire the lock for - * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE - * @param \OCP\Lock\ILockingProvider $provider - * @throws \OCP\Lock\LockedException - */ - public function acquireLock($path, $type, ILockingProvider $provider); - - /** - * @param string $path The path of the file to release the lock for - * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE - * @param \OCP\Lock\ILockingProvider $provider - */ - public function releaseLock($path, $type, ILockingProvider $provider); - - /** - * @param string $path The path of the file to change the lock for - * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE - * @param \OCP\Lock\ILockingProvider $provider - * @throws \OCP\Lock\LockedException - */ - public function changeLock($path, $type, ILockingProvider $provider); -} diff --git a/lib/private/files/storage/storagefactory.php b/lib/private/files/storage/storagefactory.php deleted file mode 100644 index 84362cabecc..00000000000 --- a/lib/private/files/storage/storagefactory.php +++ /dev/null @@ -1,107 +0,0 @@ - - * @author Morris Jobke - * @author Robin Appelman - * @author Vincent Petry - * - * @copyright Copyright (c) 2016, ownCloud, Inc. - * @license AGPL-3.0 - * - * This code is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License, version 3, - * along with this program. If not, see - * - */ - -namespace OC\Files\Storage; - -use OCP\Files\Mount\IMountPoint; -use OCP\Files\Storage\IStorageFactory; - -class StorageFactory implements IStorageFactory { - /** - * @var array[] [$name=>['priority'=>$priority, 'wrapper'=>$callable] $storageWrappers - */ - private $storageWrappers = []; - - /** - * allow modifier storage behaviour by adding wrappers around storages - * - * $callback should be a function of type (string $mountPoint, Storage $storage) => Storage - * - * @param string $wrapperName name of the wrapper - * @param callable $callback callback - * @param int $priority wrappers with the lower priority are applied last (meaning they get called first) - * @param \OCP\Files\Mount\IMountPoint[] $existingMounts existing mount points to apply the wrapper to - * @return bool true if the wrapper was added, false if there was already a wrapper with this - * name registered - */ - public function addStorageWrapper($wrapperName, $callback, $priority = 50, $existingMounts = []) { - if (isset($this->storageWrappers[$wrapperName])) { - return false; - } - - // apply to existing mounts before registering it to prevent applying it double in MountPoint::createStorage - foreach ($existingMounts as $mount) { - $mount->wrapStorage($callback); - } - - $this->storageWrappers[$wrapperName] = ['wrapper' => $callback, 'priority' => $priority]; - return true; - } - - /** - * Remove a storage wrapper by name. - * Note: internal method only to be used for cleanup - * - * @param string $wrapperName name of the wrapper - * @internal - */ - public function removeStorageWrapper($wrapperName) { - unset($this->storageWrappers[$wrapperName]); - } - - /** - * Create an instance of a storage and apply the registered storage wrappers - * - * @param \OCP\Files\Mount\IMountPoint $mountPoint - * @param string $class - * @param array $arguments - * @return \OCP\Files\Storage - */ - public function getInstance(IMountPoint $mountPoint, $class, $arguments) { - return $this->wrap($mountPoint, new $class($arguments)); - } - - /** - * @param \OCP\Files\Mount\IMountPoint $mountPoint - * @param \OCP\Files\Storage $storage - * @return \OCP\Files\Storage - */ - public function wrap(IMountPoint $mountPoint, $storage) { - $wrappers = array_values($this->storageWrappers); - usort($wrappers, function ($a, $b) { - return $b['priority'] - $a['priority']; - }); - /** @var callable[] $wrappers */ - $wrappers = array_map(function ($wrapper) { - return $wrapper['wrapper']; - }, $wrappers); - foreach ($wrappers as $wrapper) { - $storage = $wrapper($mountPoint->getMountPoint(), $storage, $mountPoint); - if (!($storage instanceof \OCP\Files\Storage)) { - throw new \Exception('Invalid result from storage wrapper'); - } - } - return $storage; - } -} diff --git a/lib/private/files/storage/temporary.php b/lib/private/files/storage/temporary.php deleted file mode 100644 index 2d8e84c2d52..00000000000 --- a/lib/private/files/storage/temporary.php +++ /dev/null @@ -1,47 +0,0 @@ - - * @author Robin Appelman - * @author Thomas Müller - * @author Vincent Petry - * - * @copyright Copyright (c) 2016, ownCloud, Inc. - * @license AGPL-3.0 - * - * This code is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License, version 3, - * along with this program. If not, see - * - */ - -namespace OC\Files\Storage; - -/** - * local storage backend in temporary folder for testing purpose - */ -class Temporary extends Local{ - public function __construct($arguments = null) { - parent::__construct(array('datadir' => \OC::$server->getTempManager()->getTemporaryFolder())); - } - - public function cleanUp() { - \OC_Helper::rmdirr($this->datadir); - } - - public function __destruct() { - parent::__destruct(); - $this->cleanUp(); - } - - public function getDataDir() { - return $this->datadir; - } -} diff --git a/lib/private/files/storage/wrapper/availability.php b/lib/private/files/storage/wrapper/availability.php deleted file mode 100644 index 0ed31ba854a..00000000000 --- a/lib/private/files/storage/wrapper/availability.php +++ /dev/null @@ -1,461 +0,0 @@ - - * @author Robin McCorkell - * - * @copyright Copyright (c) 2016, ownCloud, Inc. - * @license AGPL-3.0 - * - * This code is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License, version 3, - * along with this program. If not, see - * - */ -namespace OC\Files\Storage\Wrapper; - -/** - * Availability checker for storages - * - * Throws a StorageNotAvailableException for storages with known failures - */ -class Availability extends Wrapper { - const RECHECK_TTL_SEC = 600; // 10 minutes - - public static function shouldRecheck($availability) { - if (!$availability['available']) { - // trigger a recheck if TTL reached - if ((time() - $availability['last_checked']) > self::RECHECK_TTL_SEC) { - return true; - } - } - return false; - } - - /** - * @return bool - */ - private function updateAvailability() { - try { - $result = $this->test(); - } catch (\Exception $e) { - $result = false; - } - $this->setAvailability($result); - return $result; - } - - /** - * @return bool - */ - private function isAvailable() { - $availability = $this->getAvailability(); - if (self::shouldRecheck($availability)) { - return $this->updateAvailability(); - } - return $availability['available']; - } - - /** - * @throws \OCP\Files\StorageNotAvailableException - */ - private function checkAvailability() { - if (!$this->isAvailable()) { - throw new \OCP\Files\StorageNotAvailableException(); - } - } - - /** {@inheritdoc} */ - public function mkdir($path) { - $this->checkAvailability(); - try { - return parent::mkdir($path); - } catch (\OCP\Files\StorageNotAvailableException $e) { - $this->setAvailability(false); - throw $e; - } - } - - /** {@inheritdoc} */ - public function rmdir($path) { - $this->checkAvailability(); - try { - return parent::rmdir($path); - } catch (\OCP\Files\StorageNotAvailableException $e) { - $this->setAvailability(false); - throw $e; - } - } - - /** {@inheritdoc} */ - public function opendir($path) { - $this->checkAvailability(); - try { - return parent::opendir($path); - } catch (\OCP\Files\StorageNotAvailableException $e) { - $this->setAvailability(false); - throw $e; - } - } - - /** {@inheritdoc} */ - public function is_dir($path) { - $this->checkAvailability(); - try { - return parent::is_dir($path); - } catch (\OCP\Files\StorageNotAvailableException $e) { - $this->setAvailability(false); - throw $e; - } - } - - /** {@inheritdoc} */ - public function is_file($path) { - $this->checkAvailability(); - try { - return parent::is_file($path); - } catch (\OCP\Files\StorageNotAvailableException $e) { - $this->setAvailability(false); - throw $e; - } - } - - /** {@inheritdoc} */ - public function stat($path) { - $this->checkAvailability(); - try { - return parent::stat($path); - } catch (\OCP\Files\StorageNotAvailableException $e) { - $this->setAvailability(false); - throw $e; - } - } - - /** {@inheritdoc} */ - public function filetype($path) { - $this->checkAvailability(); - try { - return parent::filetype($path); - } catch (\OCP\Files\StorageNotAvailableException $e) { - $this->setAvailability(false); - throw $e; - } - } - - /** {@inheritdoc} */ - public function filesize($path) { - $this->checkAvailability(); - try { - return parent::filesize($path); - } catch (\OCP\Files\StorageNotAvailableException $e) { - $this->setAvailability(false); - throw $e; - } - } - - /** {@inheritdoc} */ - public function isCreatable($path) { - $this->checkAvailability(); - try { - return parent::isCreatable($path); - } catch (\OCP\Files\StorageNotAvailableException $e) { - $this->setAvailability(false); - throw $e; - } - } - - /** {@inheritdoc} */ - public function isReadable($path) { - $this->checkAvailability(); - try { - return parent::isReadable($path); - } catch (\OCP\Files\StorageNotAvailableException $e) { - $this->setAvailability(false); - throw $e; - } - } - - /** {@inheritdoc} */ - public function isUpdatable($path) { - $this->checkAvailability(); - try { - return parent::isUpdatable($path); - } catch (\OCP\Files\StorageNotAvailableException $e) { - $this->setAvailability(false); - throw $e; - } - } - - /** {@inheritdoc} */ - public function isDeletable($path) { - $this->checkAvailability(); - try { - return parent::isDeletable($path); - } catch (\OCP\Files\StorageNotAvailableException $e) { - $this->setAvailability(false); - throw $e; - } - } - - /** {@inheritdoc} */ - public function isSharable($path) { - $this->checkAvailability(); - try { - return parent::isSharable($path); - } catch (\OCP\Files\StorageNotAvailableException $e) { - $this->setAvailability(false); - throw $e; - } - } - - /** {@inheritdoc} */ - public function getPermissions($path) { - $this->checkAvailability(); - try { - return parent::getPermissions($path); - } catch (\OCP\Files\StorageNotAvailableException $e) { - $this->setAvailability(false); - throw $e; - } - } - - /** {@inheritdoc} */ - public function file_exists($path) { - if ($path === '') { - return true; - } - $this->checkAvailability(); - try { - return parent::file_exists($path); - } catch (\OCP\Files\StorageNotAvailableException $e) { - $this->setAvailability(false); - throw $e; - } - } - - /** {@inheritdoc} */ - public function filemtime($path) { - $this->checkAvailability(); - try { - return parent::filemtime($path); - } catch (\OCP\Files\StorageNotAvailableException $e) { - $this->setAvailability(false); - throw $e; - } - } - - /** {@inheritdoc} */ - public function file_get_contents($path) { - $this->checkAvailability(); - try { - return parent::file_get_contents($path); - } catch (\OCP\Files\StorageNotAvailableException $e) { - $this->setAvailability(false); - throw $e; - } - } - - /** {@inheritdoc} */ - public function file_put_contents($path, $data) { - $this->checkAvailability(); - try { - return parent::file_put_contents($path, $data); - } catch (\OCP\Files\StorageNotAvailableException $e) { - $this->setAvailability(false); - throw $e; - } - } - - /** {@inheritdoc} */ - public function unlink($path) { - $this->checkAvailability(); - try { - return parent::unlink($path); - } catch (\OCP\Files\StorageNotAvailableException $e) { - $this->setAvailability(false); - throw $e; - } - } - - /** {@inheritdoc} */ - public function rename($path1, $path2) { - $this->checkAvailability(); - try { - return parent::rename($path1, $path2); - } catch (\OCP\Files\StorageNotAvailableException $e) { - $this->setAvailability(false); - throw $e; - } - } - - /** {@inheritdoc} */ - public function copy($path1, $path2) { - $this->checkAvailability(); - try { - return parent::copy($path1, $path2); - } catch (\OCP\Files\StorageNotAvailableException $e) { - $this->setAvailability(false); - throw $e; - } - } - - /** {@inheritdoc} */ - public function fopen($path, $mode) { - $this->checkAvailability(); - try { - return parent::fopen($path, $mode); - } catch (\OCP\Files\StorageNotAvailableException $e) { - $this->setAvailability(false); - throw $e; - } - } - - /** {@inheritdoc} */ - public function getMimeType($path) { - $this->checkAvailability(); - try { - return parent::getMimeType($path); - } catch (\OCP\Files\StorageNotAvailableException $e) { - $this->setAvailability(false); - throw $e; - } - } - - /** {@inheritdoc} */ - public function hash($type, $path, $raw = false) { - $this->checkAvailability(); - try { - return parent::hash($type, $path, $raw); - } catch (\OCP\Files\StorageNotAvailableException $e) { - $this->setAvailability(false); - throw $e; - } - } - - /** {@inheritdoc} */ - public function free_space($path) { - $this->checkAvailability(); - try { - return parent::free_space($path); - } catch (\OCP\Files\StorageNotAvailableException $e) { - $this->setAvailability(false); - throw $e; - } - } - - /** {@inheritdoc} */ - public function search($query) { - $this->checkAvailability(); - try { - return parent::search($query); - } catch (\OCP\Files\StorageNotAvailableException $e) { - $this->setAvailability(false); - throw $e; - } - } - - /** {@inheritdoc} */ - public function touch($path, $mtime = null) { - $this->checkAvailability(); - try { - return parent::touch($path, $mtime); - } catch (\OCP\Files\StorageNotAvailableException $e) { - $this->setAvailability(false); - throw $e; - } - } - - /** {@inheritdoc} */ - public function getLocalFile($path) { - $this->checkAvailability(); - try { - return parent::getLocalFile($path); - } catch (\OCP\Files\StorageNotAvailableException $e) { - $this->setAvailability(false); - throw $e; - } - } - - /** {@inheritdoc} */ - public function hasUpdated($path, $time) { - $this->checkAvailability(); - try { - return parent::hasUpdated($path, $time); - } catch (\OCP\Files\StorageNotAvailableException $e) { - $this->setAvailability(false); - throw $e; - } - } - - /** {@inheritdoc} */ - public function getOwner($path) { - try { - return parent::getOwner($path); - } catch (\OCP\Files\StorageNotAvailableException $e) { - $this->setAvailability(false); - throw $e; - } - } - - /** {@inheritdoc} */ - public function getETag($path) { - $this->checkAvailability(); - try { - return parent::getETag($path); - } catch (\OCP\Files\StorageNotAvailableException $e) { - $this->setAvailability(false); - throw $e; - } - } - - /** {@inheritdoc} */ - public function getDirectDownload($path) { - $this->checkAvailability(); - try { - return parent::getDirectDownload($path); - } catch (\OCP\Files\StorageNotAvailableException $e) { - $this->setAvailability(false); - throw $e; - } - } - - /** {@inheritdoc} */ - public function copyFromStorage(\OCP\Files\Storage $sourceStorage, $sourceInternalPath, $targetInternalPath) { - $this->checkAvailability(); - try { - return parent::copyFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath); - } catch (\OCP\Files\StorageNotAvailableException $e) { - $this->setAvailability(false); - throw $e; - } - } - - /** {@inheritdoc} */ - public function moveFromStorage(\OCP\Files\Storage $sourceStorage, $sourceInternalPath, $targetInternalPath) { - $this->checkAvailability(); - try { - return parent::moveFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath); - } catch (\OCP\Files\StorageNotAvailableException $e) { - $this->setAvailability(false); - throw $e; - } - } - - /** {@inheritdoc} */ - public function getMetaData($path) { - $this->checkAvailability(); - try { - return parent::getMetaData($path); - } catch (\OCP\Files\StorageNotAvailableException $e) { - $this->setAvailability(false); - throw $e; - } - } -} diff --git a/lib/private/files/storage/wrapper/encryption.php b/lib/private/files/storage/wrapper/encryption.php deleted file mode 100644 index 02da978a700..00000000000 --- a/lib/private/files/storage/wrapper/encryption.php +++ /dev/null @@ -1,993 +0,0 @@ - - * @author Joas Schilling - * @author Lukas Reschke - * @author Robin Appelman - * @author Thomas Müller - * @author Vincent Petry - * - * @copyright Copyright (c) 2016, ownCloud, Inc. - * @license AGPL-3.0 - * - * This code is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License, version 3, - * along with this program. If not, see - * - */ - -namespace OC\Files\Storage\Wrapper; - -use OC\Encryption\Exceptions\ModuleDoesNotExistsException; -use OC\Encryption\Update; -use OC\Encryption\Util; -use OC\Files\Cache\CacheEntry; -use OC\Files\Filesystem; -use OC\Files\Mount\Manager; -use OC\Files\Storage\LocalTempFileTrait; -use OC\Memcache\ArrayCache; -use OCP\Encryption\Exceptions\GenericEncryptionException; -use OCP\Encryption\IFile; -use OCP\Encryption\IManager; -use OCP\Encryption\Keys\IStorage; -use OCP\Files\Mount\IMountPoint; -use OCP\Files\Storage; -use OCP\ILogger; -use OCP\Files\Cache\ICacheEntry; - -class Encryption extends Wrapper { - - use LocalTempFileTrait; - - /** @var string */ - private $mountPoint; - - /** @var \OC\Encryption\Util */ - private $util; - - /** @var \OCP\Encryption\IManager */ - private $encryptionManager; - - /** @var \OCP\ILogger */ - private $logger; - - /** @var string */ - private $uid; - - /** @var array */ - protected $unencryptedSize; - - /** @var \OCP\Encryption\IFile */ - private $fileHelper; - - /** @var IMountPoint */ - private $mount; - - /** @var IStorage */ - private $keyStorage; - - /** @var Update */ - private $update; - - /** @var Manager */ - private $mountManager; - - /** @var array remember for which path we execute the repair step to avoid recursions */ - private $fixUnencryptedSizeOf = array(); - - /** @var ArrayCache */ - private $arrayCache; - - /** - * @param array $parameters - * @param IManager $encryptionManager - * @param Util $util - * @param ILogger $logger - * @param IFile $fileHelper - * @param string $uid - * @param IStorage $keyStorage - * @param Update $update - * @param Manager $mountManager - * @param ArrayCache $arrayCache - */ - public function __construct( - $parameters, - IManager $encryptionManager = null, - Util $util = null, - ILogger $logger = null, - IFile $fileHelper = null, - $uid = null, - IStorage $keyStorage = null, - Update $update = null, - Manager $mountManager = null, - ArrayCache $arrayCache = null - ) { - - $this->mountPoint = $parameters['mountPoint']; - $this->mount = $parameters['mount']; - $this->encryptionManager = $encryptionManager; - $this->util = $util; - $this->logger = $logger; - $this->uid = $uid; - $this->fileHelper = $fileHelper; - $this->keyStorage = $keyStorage; - $this->unencryptedSize = array(); - $this->update = $update; - $this->mountManager = $mountManager; - $this->arrayCache = $arrayCache; - parent::__construct($parameters); - } - - /** - * see http://php.net/manual/en/function.filesize.php - * The result for filesize when called on a folder is required to be 0 - * - * @param string $path - * @return int - */ - public function filesize($path) { - $fullPath = $this->getFullPath($path); - - /** @var CacheEntry $info */ - $info = $this->getCache()->get($path); - if (isset($this->unencryptedSize[$fullPath])) { - $size = $this->unencryptedSize[$fullPath]; - // update file cache - if ($info instanceof ICacheEntry) { - $info = $info->getData(); - $info['encrypted'] = $info['encryptedVersion']; - } else { - if (!is_array($info)) { - $info = []; - } - $info['encrypted'] = true; - } - - $info['size'] = $size; - $this->getCache()->put($path, $info); - - return $size; - } - - if (isset($info['fileid']) && $info['encrypted']) { - return $this->verifyUnencryptedSize($path, $info['size']); - } - - return $this->storage->filesize($path); - } - - /** - * @param string $path - * @return array - */ - public function getMetaData($path) { - $data = $this->storage->getMetaData($path); - if (is_null($data)) { - return null; - } - $fullPath = $this->getFullPath($path); - $info = $this->getCache()->get($path); - - if (isset($this->unencryptedSize[$fullPath])) { - $data['encrypted'] = true; - $data['size'] = $this->unencryptedSize[$fullPath]; - } else { - if (isset($info['fileid']) && $info['encrypted']) { - $data['size'] = $this->verifyUnencryptedSize($path, $info['size']); - $data['encrypted'] = true; - } - } - - if (isset($info['encryptedVersion']) && $info['encryptedVersion'] > 1) { - $data['encryptedVersion'] = $info['encryptedVersion']; - } - - return $data; - } - - /** - * see http://php.net/manual/en/function.file_get_contents.php - * - * @param string $path - * @return string - */ - public function file_get_contents($path) { - - $encryptionModule = $this->getEncryptionModule($path); - - if ($encryptionModule) { - $handle = $this->fopen($path, "r"); - if (!$handle) { - return false; - } - $data = stream_get_contents($handle); - fclose($handle); - return $data; - } - return $this->storage->file_get_contents($path); - } - - /** - * see http://php.net/manual/en/function.file_put_contents.php - * - * @param string $path - * @param string $data - * @return bool - */ - public function file_put_contents($path, $data) { - // file put content will always be translated to a stream write - $handle = $this->fopen($path, 'w'); - if (is_resource($handle)) { - $written = fwrite($handle, $data); - fclose($handle); - return $written; - } - - return false; - } - - /** - * see http://php.net/manual/en/function.unlink.php - * - * @param string $path - * @return bool - */ - public function unlink($path) { - $fullPath = $this->getFullPath($path); - if ($this->util->isExcluded($fullPath)) { - return $this->storage->unlink($path); - } - - $encryptionModule = $this->getEncryptionModule($path); - if ($encryptionModule) { - $this->keyStorage->deleteAllFileKeys($this->getFullPath($path)); - } - - return $this->storage->unlink($path); - } - - /** - * see http://php.net/manual/en/function.rename.php - * - * @param string $path1 - * @param string $path2 - * @return bool - */ - public function rename($path1, $path2) { - - $result = $this->storage->rename($path1, $path2); - - if ($result && - // versions always use the keys from the original file, so we can skip - // this step for versions - $this->isVersion($path2) === false && - $this->encryptionManager->isEnabled()) { - $source = $this->getFullPath($path1); - if (!$this->util->isExcluded($source)) { - $target = $this->getFullPath($path2); - if (isset($this->unencryptedSize[$source])) { - $this->unencryptedSize[$target] = $this->unencryptedSize[$source]; - } - $this->keyStorage->renameKeys($source, $target); - $module = $this->getEncryptionModule($path2); - if ($module) { - $module->update($target, $this->uid, []); - } - } - } - - return $result; - } - - /** - * see http://php.net/manual/en/function.rmdir.php - * - * @param string $path - * @return bool - */ - public function rmdir($path) { - $result = $this->storage->rmdir($path); - $fullPath = $this->getFullPath($path); - if ($result && - $this->util->isExcluded($fullPath) === false && - $this->encryptionManager->isEnabled() - ) { - $this->keyStorage->deleteAllFileKeys($fullPath); - } - - return $result; - } - - /** - * check if a file can be read - * - * @param string $path - * @return bool - */ - public function isReadable($path) { - - $isReadable = true; - - $metaData = $this->getMetaData($path); - if ( - !$this->is_dir($path) && - isset($metaData['encrypted']) && - $metaData['encrypted'] === true - ) { - $fullPath = $this->getFullPath($path); - $module = $this->getEncryptionModule($path); - $isReadable = $module->isReadable($fullPath, $this->uid); - } - - return $this->storage->isReadable($path) && $isReadable; - } - - /** - * see http://php.net/manual/en/function.copy.php - * - * @param string $path1 - * @param string $path2 - * @return bool - */ - public function copy($path1, $path2) { - - $source = $this->getFullPath($path1); - - if ($this->util->isExcluded($source)) { - return $this->storage->copy($path1, $path2); - } - - // need to stream copy file by file in case we copy between a encrypted - // and a unencrypted storage - $this->unlink($path2); - $result = $this->copyFromStorage($this, $path1, $path2); - - return $result; - } - - /** - * see http://php.net/manual/en/function.fopen.php - * - * @param string $path - * @param string $mode - * @return resource|bool - * @throws GenericEncryptionException - * @throws ModuleDoesNotExistsException - */ - public function fopen($path, $mode) { - - // check if the file is stored in the array cache, this means that we - // copy a file over to the versions folder, in this case we don't want to - // decrypt it - if ($this->arrayCache->hasKey('encryption_copy_version_' . $path)) { - $this->arrayCache->remove('encryption_copy_version_' . $path); - return $this->storage->fopen($path, $mode); - } - - $encryptionEnabled = $this->encryptionManager->isEnabled(); - $shouldEncrypt = false; - $encryptionModule = null; - $header = $this->getHeader($path); - $signed = (isset($header['signed']) && $header['signed'] === 'true') ? true : false; - $fullPath = $this->getFullPath($path); - $encryptionModuleId = $this->util->getEncryptionModuleId($header); - - if ($this->util->isExcluded($fullPath) === false) { - - $size = $unencryptedSize = 0; - $realFile = $this->util->stripPartialFileExtension($path); - $targetExists = $this->file_exists($realFile) || $this->file_exists($path); - $targetIsEncrypted = false; - if ($targetExists) { - // in case the file exists we require the explicit module as - // specified in the file header - otherwise we need to fail hard to - // prevent data loss on client side - if (!empty($encryptionModuleId)) { - $targetIsEncrypted = true; - $encryptionModule = $this->encryptionManager->getEncryptionModule($encryptionModuleId); - } - - if ($this->file_exists($path)) { - $size = $this->storage->filesize($path); - $unencryptedSize = $this->filesize($path); - } else { - $size = $unencryptedSize = 0; - } - } - - try { - - if ( - $mode === 'w' - || $mode === 'w+' - || $mode === 'wb' - || $mode === 'wb+' - ) { - // don't overwrite encrypted files if encryption is not enabled - if ($targetIsEncrypted && $encryptionEnabled === false) { - throw new GenericEncryptionException('Tried to access encrypted file but encryption is not enabled'); - } - if ($encryptionEnabled) { - // if $encryptionModuleId is empty, the default module will be used - $encryptionModule = $this->encryptionManager->getEncryptionModule($encryptionModuleId); - $shouldEncrypt = $encryptionModule->shouldEncrypt($fullPath); - $signed = true; - } - } else { - $info = $this->getCache()->get($path); - // only get encryption module if we found one in the header - // or if file should be encrypted according to the file cache - if (!empty($encryptionModuleId)) { - $encryptionModule = $this->encryptionManager->getEncryptionModule($encryptionModuleId); - $shouldEncrypt = true; - } else if (empty($encryptionModuleId) && $info['encrypted'] === true) { - // we come from a old installation. No header and/or no module defined - // but the file is encrypted. In this case we need to use the - // OC_DEFAULT_MODULE to read the file - $encryptionModule = $this->encryptionManager->getEncryptionModule('OC_DEFAULT_MODULE'); - $shouldEncrypt = true; - $targetIsEncrypted = true; - } - } - } catch (ModuleDoesNotExistsException $e) { - $this->logger->warning('Encryption module "' . $encryptionModuleId . - '" not found, file will be stored unencrypted (' . $e->getMessage() . ')'); - } - - // encryption disabled on write of new file and write to existing unencrypted file -> don't encrypt - if (!$encryptionEnabled || !$this->mount->getOption('encrypt', true)) { - if (!$targetExists || !$targetIsEncrypted) { - $shouldEncrypt = false; - } - } - - if ($shouldEncrypt === true && $encryptionModule !== null) { - $headerSize = $this->getHeaderSize($path); - $source = $this->storage->fopen($path, $mode); - if (!is_resource($source)) { - return false; - } - $handle = \OC\Files\Stream\Encryption::wrap($source, $path, $fullPath, $header, - $this->uid, $encryptionModule, $this->storage, $this, $this->util, $this->fileHelper, $mode, - $size, $unencryptedSize, $headerSize, $signed); - return $handle; - } - - } - - return $this->storage->fopen($path, $mode); - } - - - /** - * perform some plausibility checks if the the unencrypted size is correct. - * If not, we calculate the correct unencrypted size and return it - * - * @param string $path internal path relative to the storage root - * @param int $unencryptedSize size of the unencrypted file - * - * @return int unencrypted size - */ - protected function verifyUnencryptedSize($path, $unencryptedSize) { - - $size = $this->storage->filesize($path); - $result = $unencryptedSize; - - if ($unencryptedSize < 0 || - ($size > 0 && $unencryptedSize === $size) - ) { - // check if we already calculate the unencrypted size for the - // given path to avoid recursions - if (isset($this->fixUnencryptedSizeOf[$this->getFullPath($path)]) === false) { - $this->fixUnencryptedSizeOf[$this->getFullPath($path)] = true; - try { - $result = $this->fixUnencryptedSize($path, $size, $unencryptedSize); - } catch (\Exception $e) { - $this->logger->error('Couldn\'t re-calculate unencrypted size for '. $path); - $this->logger->logException($e); - } - unset($this->fixUnencryptedSizeOf[$this->getFullPath($path)]); - } - } - - return $result; - } - - /** - * calculate the unencrypted size - * - * @param string $path internal path relative to the storage root - * @param int $size size of the physical file - * @param int $unencryptedSize size of the unencrypted file - * - * @return int calculated unencrypted size - */ - protected function fixUnencryptedSize($path, $size, $unencryptedSize) { - - $headerSize = $this->getHeaderSize($path); - $header = $this->getHeader($path); - $encryptionModule = $this->getEncryptionModule($path); - - $stream = $this->storage->fopen($path, 'r'); - - // if we couldn't open the file we return the old unencrypted size - if (!is_resource($stream)) { - $this->logger->error('Could not open ' . $path . '. Recalculation of unencrypted size aborted.'); - return $unencryptedSize; - } - - $newUnencryptedSize = 0; - $size -= $headerSize; - $blockSize = $this->util->getBlockSize(); - - // if a header exists we skip it - if ($headerSize > 0) { - fread($stream, $headerSize); - } - - // fast path, else the calculation for $lastChunkNr is bogus - if ($size === 0) { - return 0; - } - - $signed = (isset($header['signed']) && $header['signed'] === 'true') ? true : false; - $unencryptedBlockSize = $encryptionModule->getUnencryptedBlockSize($signed); - - // calculate last chunk nr - // next highest is end of chunks, one subtracted is last one - // we have to read the last chunk, we can't just calculate it (because of padding etc) - - $lastChunkNr = ceil($size/ $blockSize)-1; - // calculate last chunk position - $lastChunkPos = ($lastChunkNr * $blockSize); - // try to fseek to the last chunk, if it fails we have to read the whole file - if (@fseek($stream, $lastChunkPos, SEEK_CUR) === 0) { - $newUnencryptedSize += $lastChunkNr * $unencryptedBlockSize; - } - - $lastChunkContentEncrypted=''; - $count = $blockSize; - - while ($count > 0) { - $data=fread($stream, $blockSize); - $count=strlen($data); - $lastChunkContentEncrypted .= $data; - if(strlen($lastChunkContentEncrypted) > $blockSize) { - $newUnencryptedSize += $unencryptedBlockSize; - $lastChunkContentEncrypted=substr($lastChunkContentEncrypted, $blockSize); - } - } - - fclose($stream); - - // we have to decrypt the last chunk to get it actual size - $encryptionModule->begin($this->getFullPath($path), $this->uid, 'r', $header, []); - $decryptedLastChunk = $encryptionModule->decrypt($lastChunkContentEncrypted, $lastChunkNr . 'end'); - $decryptedLastChunk .= $encryptionModule->end($this->getFullPath($path), $lastChunkNr . 'end'); - - // calc the real file size with the size of the last chunk - $newUnencryptedSize += strlen($decryptedLastChunk); - - $this->updateUnencryptedSize($this->getFullPath($path), $newUnencryptedSize); - - // write to cache if applicable - $cache = $this->storage->getCache(); - if ($cache) { - $entry = $cache->get($path); - $cache->update($entry['fileid'], ['size' => $newUnencryptedSize]); - } - - return $newUnencryptedSize; - } - - /** - * @param Storage $sourceStorage - * @param string $sourceInternalPath - * @param string $targetInternalPath - * @param bool $preserveMtime - * @return bool - */ - public function moveFromStorage(Storage $sourceStorage, $sourceInternalPath, $targetInternalPath, $preserveMtime = true) { - if ($sourceStorage === $this) { - return $this->rename($sourceInternalPath, $targetInternalPath); - } - - // TODO clean this up once the underlying moveFromStorage in OC\Files\Storage\Wrapper\Common is fixed: - // - call $this->storage->moveFromStorage() instead of $this->copyBetweenStorage - // - copy the file cache update from $this->copyBetweenStorage to this method - // - copy the copyKeys() call from $this->copyBetweenStorage to this method - // - remove $this->copyBetweenStorage - - if (!$sourceStorage->isDeletable($sourceInternalPath)) { - return false; - } - - $result = $this->copyBetweenStorage($sourceStorage, $sourceInternalPath, $targetInternalPath, $preserveMtime, true); - if ($result) { - if ($sourceStorage->is_dir($sourceInternalPath)) { - $result &= $sourceStorage->rmdir($sourceInternalPath); - } else { - $result &= $sourceStorage->unlink($sourceInternalPath); - } - } - return $result; - } - - - /** - * @param Storage $sourceStorage - * @param string $sourceInternalPath - * @param string $targetInternalPath - * @param bool $preserveMtime - * @param bool $isRename - * @return bool - */ - public function copyFromStorage(Storage $sourceStorage, $sourceInternalPath, $targetInternalPath, $preserveMtime = false, $isRename = false) { - - // TODO clean this up once the underlying moveFromStorage in OC\Files\Storage\Wrapper\Common is fixed: - // - call $this->storage->copyFromStorage() instead of $this->copyBetweenStorage - // - copy the file cache update from $this->copyBetweenStorage to this method - // - copy the copyKeys() call from $this->copyBetweenStorage to this method - // - remove $this->copyBetweenStorage - - return $this->copyBetweenStorage($sourceStorage, $sourceInternalPath, $targetInternalPath, $preserveMtime, $isRename); - } - - /** - * Update the encrypted cache version in the database - * - * @param Storage $sourceStorage - * @param string $sourceInternalPath - * @param string $targetInternalPath - * @param bool $isRename - */ - private function updateEncryptedVersion(Storage $sourceStorage, $sourceInternalPath, $targetInternalPath, $isRename) { - $isEncrypted = $this->encryptionManager->isEnabled() && $this->mount->getOption('encrypt', true) ? 1 : 0; - $cacheInformation = [ - 'encrypted' => (bool)$isEncrypted, - ]; - if($isEncrypted === 1) { - $encryptedVersion = $sourceStorage->getCache()->get($sourceInternalPath)['encryptedVersion']; - - // In case of a move operation from an unencrypted to an encrypted - // storage the old encrypted version would stay with "0" while the - // correct value would be "1". Thus we manually set the value to "1" - // for those cases. - // See also https://github.com/owncloud/core/issues/23078 - if($encryptedVersion === 0) { - $encryptedVersion = 1; - } - - $cacheInformation['encryptedVersion'] = $encryptedVersion; - } - - // in case of a rename we need to manipulate the source cache because - // this information will be kept for the new target - if ($isRename) { - $sourceStorage->getCache()->put($sourceInternalPath, $cacheInformation); - } else { - $this->getCache()->put($targetInternalPath, $cacheInformation); - } - } - - /** - * copy file between two storages - * - * @param Storage $sourceStorage - * @param string $sourceInternalPath - * @param string $targetInternalPath - * @param bool $preserveMtime - * @param bool $isRename - * @return bool - * @throws \Exception - */ - private function copyBetweenStorage(Storage $sourceStorage, $sourceInternalPath, $targetInternalPath, $preserveMtime, $isRename) { - - // for versions we have nothing to do, because versions should always use the - // key from the original file. Just create a 1:1 copy and done - if ($this->isVersion($targetInternalPath) || - $this->isVersion($sourceInternalPath)) { - // remember that we try to create a version so that we can detect it during - // fopen($sourceInternalPath) and by-pass the encryption in order to - // create a 1:1 copy of the file - $this->arrayCache->set('encryption_copy_version_' . $sourceInternalPath, true); - $result = $this->storage->copyFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath); - $this->arrayCache->remove('encryption_copy_version_' . $sourceInternalPath); - if ($result) { - $info = $this->getCache('', $sourceStorage)->get($sourceInternalPath); - // make sure that we update the unencrypted size for the version - if (isset($info['encrypted']) && $info['encrypted'] === true) { - $this->updateUnencryptedSize( - $this->getFullPath($targetInternalPath), - $info['size'] - ); - } - $this->updateEncryptedVersion($sourceStorage, $sourceInternalPath, $targetInternalPath, $isRename); - } - return $result; - } - - // first copy the keys that we reuse the existing file key on the target location - // and don't create a new one which would break versions for example. - $mount = $this->mountManager->findByStorageId($sourceStorage->getId()); - if (count($mount) === 1) { - $mountPoint = $mount[0]->getMountPoint(); - $source = $mountPoint . '/' . $sourceInternalPath; - $target = $this->getFullPath($targetInternalPath); - $this->copyKeys($source, $target); - } else { - $this->logger->error('Could not find mount point, can\'t keep encryption keys'); - } - - if ($sourceStorage->is_dir($sourceInternalPath)) { - $dh = $sourceStorage->opendir($sourceInternalPath); - $result = $this->mkdir($targetInternalPath); - if (is_resource($dh)) { - while ($result and ($file = readdir($dh)) !== false) { - if (!Filesystem::isIgnoredDir($file)) { - $result &= $this->copyFromStorage($sourceStorage, $sourceInternalPath . '/' . $file, $targetInternalPath . '/' . $file, false, $isRename); - } - } - } - } else { - try { - $source = $sourceStorage->fopen($sourceInternalPath, 'r'); - $target = $this->fopen($targetInternalPath, 'w'); - list(, $result) = \OC_Helper::streamCopy($source, $target); - fclose($source); - fclose($target); - } catch (\Exception $e) { - fclose($source); - fclose($target); - throw $e; - } - if($result) { - if ($preserveMtime) { - $this->touch($targetInternalPath, $sourceStorage->filemtime($sourceInternalPath)); - } - $this->updateEncryptedVersion($sourceStorage, $sourceInternalPath, $targetInternalPath, $isRename); - } else { - // delete partially written target file - $this->unlink($targetInternalPath); - // delete cache entry that was created by fopen - $this->getCache()->remove($targetInternalPath); - } - } - return (bool)$result; - - } - - /** - * get the path to a local version of the file. - * The local version of the file can be temporary and doesn't have to be persistent across requests - * - * @param string $path - * @return string - */ - public function getLocalFile($path) { - if ($this->encryptionManager->isEnabled()) { - $cachedFile = $this->getCachedFile($path); - if (is_string($cachedFile)) { - return $cachedFile; - } - } - return $this->storage->getLocalFile($path); - } - - /** - * Returns the wrapped storage's value for isLocal() - * - * @return bool wrapped storage's isLocal() value - */ - public function isLocal() { - if ($this->encryptionManager->isEnabled()) { - return false; - } - return $this->storage->isLocal(); - } - - /** - * see http://php.net/manual/en/function.stat.php - * only the following keys are required in the result: size and mtime - * - * @param string $path - * @return array - */ - public function stat($path) { - $stat = $this->storage->stat($path); - $fileSize = $this->filesize($path); - $stat['size'] = $fileSize; - $stat[7] = $fileSize; - return $stat; - } - - /** - * see http://php.net/manual/en/function.hash.php - * - * @param string $type - * @param string $path - * @param bool $raw - * @return string - */ - public function hash($type, $path, $raw = false) { - $fh = $this->fopen($path, 'rb'); - $ctx = hash_init($type); - hash_update_stream($ctx, $fh); - fclose($fh); - return hash_final($ctx, $raw); - } - - /** - * return full path, including mount point - * - * @param string $path relative to mount point - * @return string full path including mount point - */ - protected function getFullPath($path) { - return Filesystem::normalizePath($this->mountPoint . '/' . $path); - } - - /** - * read first block of encrypted file, typically this will contain the - * encryption header - * - * @param string $path - * @return string - */ - protected function readFirstBlock($path) { - $firstBlock = ''; - if ($this->storage->file_exists($path)) { - $handle = $this->storage->fopen($path, 'r'); - $firstBlock = fread($handle, $this->util->getHeaderSize()); - fclose($handle); - } - return $firstBlock; - } - - /** - * return header size of given file - * - * @param string $path - * @return int - */ - protected function getHeaderSize($path) { - $headerSize = 0; - $realFile = $this->util->stripPartialFileExtension($path); - if ($this->storage->file_exists($realFile)) { - $path = $realFile; - } - $firstBlock = $this->readFirstBlock($path); - - if (substr($firstBlock, 0, strlen(Util::HEADER_START)) === Util::HEADER_START) { - $headerSize = $this->util->getHeaderSize(); - } - - return $headerSize; - } - - /** - * parse raw header to array - * - * @param string $rawHeader - * @return array - */ - protected function parseRawHeader($rawHeader) { - $result = array(); - if (substr($rawHeader, 0, strlen(Util::HEADER_START)) === Util::HEADER_START) { - $header = $rawHeader; - $endAt = strpos($header, Util::HEADER_END); - if ($endAt !== false) { - $header = substr($header, 0, $endAt + strlen(Util::HEADER_END)); - - // +1 to not start with an ':' which would result in empty element at the beginning - $exploded = explode(':', substr($header, strlen(Util::HEADER_START)+1)); - - $element = array_shift($exploded); - while ($element !== Util::HEADER_END) { - $result[$element] = array_shift($exploded); - $element = array_shift($exploded); - } - } - } - - return $result; - } - - /** - * read header from file - * - * @param string $path - * @return array - */ - protected function getHeader($path) { - $realFile = $this->util->stripPartialFileExtension($path); - if ($this->storage->file_exists($realFile)) { - $path = $realFile; - } - - $firstBlock = $this->readFirstBlock($path); - $result = $this->parseRawHeader($firstBlock); - - // if the header doesn't contain a encryption module we check if it is a - // legacy file. If true, we add the default encryption module - if (!isset($result[Util::HEADER_ENCRYPTION_MODULE_KEY])) { - if (!empty($result)) { - $result[Util::HEADER_ENCRYPTION_MODULE_KEY] = 'OC_DEFAULT_MODULE'; - } else { - // if the header was empty we have to check first if it is a encrypted file at all - $info = $this->getCache()->get($path); - if (isset($info['encrypted']) && $info['encrypted'] === true) { - $result[Util::HEADER_ENCRYPTION_MODULE_KEY] = 'OC_DEFAULT_MODULE'; - } - } - } - - return $result; - } - - /** - * read encryption module needed to read/write the file located at $path - * - * @param string $path - * @return null|\OCP\Encryption\IEncryptionModule - * @throws ModuleDoesNotExistsException - * @throws \Exception - */ - protected function getEncryptionModule($path) { - $encryptionModule = null; - $header = $this->getHeader($path); - $encryptionModuleId = $this->util->getEncryptionModuleId($header); - if (!empty($encryptionModuleId)) { - try { - $encryptionModule = $this->encryptionManager->getEncryptionModule($encryptionModuleId); - } catch (ModuleDoesNotExistsException $e) { - $this->logger->critical('Encryption module defined in "' . $path . '" not loaded!'); - throw $e; - } - } - return $encryptionModule; - } - - /** - * @param string $path - * @param int $unencryptedSize - */ - public function updateUnencryptedSize($path, $unencryptedSize) { - $this->unencryptedSize[$path] = $unencryptedSize; - } - - /** - * copy keys to new location - * - * @param string $source path relative to data/ - * @param string $target path relative to data/ - * @return bool - */ - protected function copyKeys($source, $target) { - if (!$this->util->isExcluded($source)) { - return $this->keyStorage->copyKeys($source, $target); - } - - return false; - } - - /** - * check if path points to a files version - * - * @param $path - * @return bool - */ - protected function isVersion($path) { - $normalized = Filesystem::normalizePath($path); - return substr($normalized, 0, strlen('/files_versions/')) === '/files_versions/'; - } - -} diff --git a/lib/private/files/storage/wrapper/jail.php b/lib/private/files/storage/wrapper/jail.php deleted file mode 100644 index e8063f670c5..00000000000 --- a/lib/private/files/storage/wrapper/jail.php +++ /dev/null @@ -1,489 +0,0 @@ - - * @author Robin Appelman - * - * @copyright Copyright (c) 2016, ownCloud, Inc. - * @license AGPL-3.0 - * - * This code is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License, version 3, - * along with this program. If not, see - * - */ - -namespace OC\Files\Storage\Wrapper; - -use OC\Files\Cache\Wrapper\CacheJail; -use OCP\Lock\ILockingProvider; - -/** - * Jail to a subdirectory of the wrapped storage - * - * This restricts access to a subfolder of the wrapped storage with the subfolder becoming the root folder new storage - */ -class Jail extends Wrapper { - /** - * @var string - */ - protected $rootPath; - - /** - * @param array $arguments ['storage' => $storage, 'mask' => $root] - * - * $storage: The storage that will be wrapper - * $root: The folder in the wrapped storage that will become the root folder of the wrapped storage - */ - public function __construct($arguments) { - parent::__construct($arguments); - $this->rootPath = $arguments['root']; - } - - public function getSourcePath($path) { - if ($path === '') { - return $this->rootPath; - } else { - return $this->rootPath . '/' . $path; - } - } - - public function getId() { - return 'link:' . parent::getId() . ':' . $this->rootPath; - } - - /** - * see http://php.net/manual/en/function.mkdir.php - * - * @param string $path - * @return bool - */ - public function mkdir($path) { - return $this->storage->mkdir($this->getSourcePath($path)); - } - - /** - * see http://php.net/manual/en/function.rmdir.php - * - * @param string $path - * @return bool - */ - public function rmdir($path) { - return $this->storage->rmdir($this->getSourcePath($path)); - } - - /** - * see http://php.net/manual/en/function.opendir.php - * - * @param string $path - * @return resource - */ - public function opendir($path) { - return $this->storage->opendir($this->getSourcePath($path)); - } - - /** - * see http://php.net/manual/en/function.is_dir.php - * - * @param string $path - * @return bool - */ - public function is_dir($path) { - return $this->storage->is_dir($this->getSourcePath($path)); - } - - /** - * see http://php.net/manual/en/function.is_file.php - * - * @param string $path - * @return bool - */ - public function is_file($path) { - return $this->storage->is_file($this->getSourcePath($path)); - } - - /** - * see http://php.net/manual/en/function.stat.php - * only the following keys are required in the result: size and mtime - * - * @param string $path - * @return array - */ - public function stat($path) { - return $this->storage->stat($this->getSourcePath($path)); - } - - /** - * see http://php.net/manual/en/function.filetype.php - * - * @param string $path - * @return bool - */ - public function filetype($path) { - return $this->storage->filetype($this->getSourcePath($path)); - } - - /** - * see http://php.net/manual/en/function.filesize.php - * The result for filesize when called on a folder is required to be 0 - * - * @param string $path - * @return int - */ - public function filesize($path) { - return $this->storage->filesize($this->getSourcePath($path)); - } - - /** - * check if a file can be created in $path - * - * @param string $path - * @return bool - */ - public function isCreatable($path) { - return $this->storage->isCreatable($this->getSourcePath($path)); - } - - /** - * check if a file can be read - * - * @param string $path - * @return bool - */ - public function isReadable($path) { - return $this->storage->isReadable($this->getSourcePath($path)); - } - - /** - * check if a file can be written to - * - * @param string $path - * @return bool - */ - public function isUpdatable($path) { - return $this->storage->isUpdatable($this->getSourcePath($path)); - } - - /** - * check if a file can be deleted - * - * @param string $path - * @return bool - */ - public function isDeletable($path) { - return $this->storage->isDeletable($this->getSourcePath($path)); - } - - /** - * check if a file can be shared - * - * @param string $path - * @return bool - */ - public function isSharable($path) { - return $this->storage->isSharable($this->getSourcePath($path)); - } - - /** - * get the full permissions of a path. - * Should return a combination of the PERMISSION_ constants defined in lib/public/constants.php - * - * @param string $path - * @return int - */ - public function getPermissions($path) { - return $this->storage->getPermissions($this->getSourcePath($path)); - } - - /** - * see http://php.net/manual/en/function.file_exists.php - * - * @param string $path - * @return bool - */ - public function file_exists($path) { - return $this->storage->file_exists($this->getSourcePath($path)); - } - - /** - * see http://php.net/manual/en/function.filemtime.php - * - * @param string $path - * @return int - */ - public function filemtime($path) { - return $this->storage->filemtime($this->getSourcePath($path)); - } - - /** - * see http://php.net/manual/en/function.file_get_contents.php - * - * @param string $path - * @return string - */ - public function file_get_contents($path) { - return $this->storage->file_get_contents($this->getSourcePath($path)); - } - - /** - * see http://php.net/manual/en/function.file_put_contents.php - * - * @param string $path - * @param string $data - * @return bool - */ - public function file_put_contents($path, $data) { - return $this->storage->file_put_contents($this->getSourcePath($path), $data); - } - - /** - * see http://php.net/manual/en/function.unlink.php - * - * @param string $path - * @return bool - */ - public function unlink($path) { - return $this->storage->unlink($this->getSourcePath($path)); - } - - /** - * see http://php.net/manual/en/function.rename.php - * - * @param string $path1 - * @param string $path2 - * @return bool - */ - public function rename($path1, $path2) { - return $this->storage->rename($this->getSourcePath($path1), $this->getSourcePath($path2)); - } - - /** - * see http://php.net/manual/en/function.copy.php - * - * @param string $path1 - * @param string $path2 - * @return bool - */ - public function copy($path1, $path2) { - return $this->storage->copy($this->getSourcePath($path1), $this->getSourcePath($path2)); - } - - /** - * see http://php.net/manual/en/function.fopen.php - * - * @param string $path - * @param string $mode - * @return resource - */ - public function fopen($path, $mode) { - return $this->storage->fopen($this->getSourcePath($path), $mode); - } - - /** - * get the mimetype for a file or folder - * The mimetype for a folder is required to be "httpd/unix-directory" - * - * @param string $path - * @return string - */ - public function getMimeType($path) { - return $this->storage->getMimeType($this->getSourcePath($path)); - } - - /** - * see http://php.net/manual/en/function.hash.php - * - * @param string $type - * @param string $path - * @param bool $raw - * @return string - */ - public function hash($type, $path, $raw = false) { - return $this->storage->hash($type, $this->getSourcePath($path), $raw); - } - - /** - * see http://php.net/manual/en/function.free_space.php - * - * @param string $path - * @return int - */ - public function free_space($path) { - return $this->storage->free_space($this->getSourcePath($path)); - } - - /** - * search for occurrences of $query in file names - * - * @param string $query - * @return array - */ - public function search($query) { - return $this->storage->search($query); - } - - /** - * see http://php.net/manual/en/function.touch.php - * If the backend does not support the operation, false should be returned - * - * @param string $path - * @param int $mtime - * @return bool - */ - public function touch($path, $mtime = null) { - return $this->storage->touch($this->getSourcePath($path), $mtime); - } - - /** - * get the path to a local version of the file. - * The local version of the file can be temporary and doesn't have to be persistent across requests - * - * @param string $path - * @return string - */ - public function getLocalFile($path) { - return $this->storage->getLocalFile($this->getSourcePath($path)); - } - - /** - * check if a file or folder has been updated since $time - * - * @param string $path - * @param int $time - * @return bool - * - * hasUpdated for folders should return at least true if a file inside the folder is add, removed or renamed. - * returning true for other changes in the folder is optional - */ - public function hasUpdated($path, $time) { - return $this->storage->hasUpdated($this->getSourcePath($path), $time); - } - - /** - * get a cache instance for the storage - * - * @param string $path - * @param \OC\Files\Storage\Storage (optional) the storage to pass to the cache - * @return \OC\Files\Cache\Cache - */ - public function getCache($path = '', $storage = null) { - if (!$storage) { - $storage = $this; - } - $sourceCache = $this->storage->getCache($this->getSourcePath($path), $storage); - return new CacheJail($sourceCache, $this->rootPath); - } - - /** - * get the user id of the owner of a file or folder - * - * @param string $path - * @return string - */ - public function getOwner($path) { - return $this->storage->getOwner($this->getSourcePath($path)); - } - - /** - * get a watcher instance for the cache - * - * @param string $path - * @param \OC\Files\Storage\Storage (optional) the storage to pass to the watcher - * @return \OC\Files\Cache\Watcher - */ - public function getWatcher($path = '', $storage = null) { - if (!$storage) { - $storage = $this; - } - return $this->storage->getWatcher($this->getSourcePath($path), $storage); - } - - /** - * get the ETag for a file or folder - * - * @param string $path - * @return string - */ - public function getETag($path) { - return $this->storage->getETag($this->getSourcePath($path)); - } - - /** - * @param string $path - * @return array - */ - public function getMetaData($path) { - return $this->storage->getMetaData($this->getSourcePath($path)); - } - - /** - * @param string $path - * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE - * @param \OCP\Lock\ILockingProvider $provider - * @throws \OCP\Lock\LockedException - */ - public function acquireLock($path, $type, ILockingProvider $provider) { - $this->storage->acquireLock($this->getSourcePath($path), $type, $provider); - } - - /** - * @param string $path - * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE - * @param \OCP\Lock\ILockingProvider $provider - */ - public function releaseLock($path, $type, ILockingProvider $provider) { - $this->storage->releaseLock($this->getSourcePath($path), $type, $provider); - } - - /** - * @param string $path - * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE - * @param \OCP\Lock\ILockingProvider $provider - */ - public function changeLock($path, $type, ILockingProvider $provider) { - $this->storage->changeLock($this->getSourcePath($path), $type, $provider); - } - - /** - * Resolve the path for the source of the share - * - * @param string $path - * @return array - */ - public function resolvePath($path) { - return [$this->storage, $this->getSourcePath($path)]; - } - - /** - * @param \OCP\Files\Storage $sourceStorage - * @param string $sourceInternalPath - * @param string $targetInternalPath - * @return bool - */ - public function copyFromStorage(\OCP\Files\Storage $sourceStorage, $sourceInternalPath, $targetInternalPath) { - if ($sourceStorage === $this) { - return $this->copy($sourceInternalPath, $targetInternalPath); - } - return $this->storage->copyFromStorage($sourceStorage, $sourceInternalPath, $this->getSourcePath($targetInternalPath)); - } - - /** - * @param \OCP\Files\Storage $sourceStorage - * @param string $sourceInternalPath - * @param string $targetInternalPath - * @return bool - */ - public function moveFromStorage(\OCP\Files\Storage $sourceStorage, $sourceInternalPath, $targetInternalPath) { - if ($sourceStorage === $this) { - return $this->rename($sourceInternalPath, $targetInternalPath); - } - return $this->storage->moveFromStorage($sourceStorage, $sourceInternalPath, $this->getSourcePath($targetInternalPath)); - } -} diff --git a/lib/private/files/storage/wrapper/permissionsmask.php b/lib/private/files/storage/wrapper/permissionsmask.php deleted file mode 100644 index 01dd78d418c..00000000000 --- a/lib/private/files/storage/wrapper/permissionsmask.php +++ /dev/null @@ -1,131 +0,0 @@ - - * @author Morris Jobke - * @author Robin Appelman - * @author Robin McCorkell - * - * @copyright Copyright (c) 2016, ownCloud, Inc. - * @license AGPL-3.0 - * - * This code is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License, version 3, - * along with this program. If not, see - * - */ - -namespace OC\Files\Storage\Wrapper; - -use OC\Files\Cache\Wrapper\CachePermissionsMask; -use OCP\Constants; - -/** - * Mask the permissions of a storage - * - * This can be used to restrict update, create, delete and/or share permissions of a storage - * - * Note that the read permissions can't be masked - */ -class PermissionsMask extends Wrapper { - /** - * @var int the permissions bits we want to keep - */ - private $mask; - - /** - * @param array $arguments ['storage' => $storage, 'mask' => $mask] - * - * $storage: The storage the permissions mask should be applied on - * $mask: The permission bits that should be kept, a combination of the \OCP\Constant::PERMISSION_ constants - */ - public function __construct($arguments) { - parent::__construct($arguments); - $this->mask = $arguments['mask']; - } - - private function checkMask($permissions) { - return ($this->mask & $permissions) === $permissions; - } - - public function isUpdatable($path) { - return $this->checkMask(Constants::PERMISSION_UPDATE) and parent::isUpdatable($path); - } - - public function isCreatable($path) { - return $this->checkMask(Constants::PERMISSION_CREATE) and parent::isCreatable($path); - } - - public function isDeletable($path) { - return $this->checkMask(Constants::PERMISSION_DELETE) and parent::isDeletable($path); - } - - public function isSharable($path) { - return $this->checkMask(Constants::PERMISSION_SHARE) and parent::isSharable($path); - } - - public function getPermissions($path) { - return $this->storage->getPermissions($path) & $this->mask; - } - - public function rename($path1, $path2) { - return $this->checkMask(Constants::PERMISSION_UPDATE) and parent::rename($path1, $path2); - } - - public function copy($path1, $path2) { - return $this->checkMask(Constants::PERMISSION_CREATE) and parent::copy($path1, $path2); - } - - public function touch($path, $mtime = null) { - $permissions = $this->file_exists($path) ? Constants::PERMISSION_UPDATE : Constants::PERMISSION_CREATE; - return $this->checkMask($permissions) and parent::touch($path, $mtime); - } - - public function mkdir($path) { - return $this->checkMask(Constants::PERMISSION_CREATE) and parent::mkdir($path); - } - - public function rmdir($path) { - return $this->checkMask(Constants::PERMISSION_DELETE) and parent::rmdir($path); - } - - public function unlink($path) { - return $this->checkMask(Constants::PERMISSION_DELETE) and parent::unlink($path); - } - - public function file_put_contents($path, $data) { - $permissions = $this->file_exists($path) ? Constants::PERMISSION_UPDATE : Constants::PERMISSION_CREATE; - return $this->checkMask($permissions) and parent::file_put_contents($path, $data); - } - - public function fopen($path, $mode) { - if ($mode === 'r' or $mode === 'rb') { - return parent::fopen($path, $mode); - } else { - $permissions = $this->file_exists($path) ? Constants::PERMISSION_UPDATE : Constants::PERMISSION_CREATE; - return $this->checkMask($permissions) ? parent::fopen($path, $mode) : false; - } - } - - /** - * get a cache instance for the storage - * - * @param string $path - * @param \OC\Files\Storage\Storage (optional) the storage to pass to the cache - * @return \OC\Files\Cache\Cache - */ - public function getCache($path = '', $storage = null) { - if (!$storage) { - $storage = $this; - } - $sourceCache = parent::getCache($path, $storage); - return new CachePermissionsMask($sourceCache, $this->mask); - } -} diff --git a/lib/private/files/storage/wrapper/quota.php b/lib/private/files/storage/wrapper/quota.php deleted file mode 100644 index 500677b092e..00000000000 --- a/lib/private/files/storage/wrapper/quota.php +++ /dev/null @@ -1,200 +0,0 @@ - - * @author Morris Jobke - * @author Robin Appelman - * @author Robin McCorkell - * @author Vincent Petry - * - * @copyright Copyright (c) 2016, ownCloud, Inc. - * @license AGPL-3.0 - * - * This code is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License, version 3, - * along with this program. If not, see - * - */ - -namespace OC\Files\Storage\Wrapper; - -use OCP\Files\Cache\ICacheEntry; - -class Quota extends Wrapper { - - /** - * @var int $quota - */ - protected $quota; - - /** - * @var string $sizeRoot - */ - protected $sizeRoot; - - /** - * @param array $parameters - */ - public function __construct($parameters) { - $this->storage = $parameters['storage']; - $this->quota = $parameters['quota']; - $this->sizeRoot = isset($parameters['root']) ? $parameters['root'] : ''; - } - - /** - * @return int quota value - */ - public function getQuota() { - return $this->quota; - } - - /** - * @param string $path - * @param \OC\Files\Storage\Storage $storage - */ - protected function getSize($path, $storage = null) { - if (is_null($storage)) { - $cache = $this->getCache(); - } else { - $cache = $storage->getCache(); - } - $data = $cache->get($path); - if ($data instanceof ICacheEntry and isset($data['size'])) { - return $data['size']; - } else { - return \OCP\Files\FileInfo::SPACE_NOT_COMPUTED; - } - } - - /** - * Get free space as limited by the quota - * - * @param string $path - * @return int - */ - public function free_space($path) { - if ($this->quota < 0) { - return $this->storage->free_space($path); - } else { - $used = $this->getSize($this->sizeRoot); - if ($used < 0) { - return \OCP\Files\FileInfo::SPACE_NOT_COMPUTED; - } else { - $free = $this->storage->free_space($path); - $quotaFree = max($this->quota - $used, 0); - // if free space is known - if ($free >= 0) { - $free = min($free, $quotaFree); - } else { - $free = $quotaFree; - } - return $free; - } - } - } - - /** - * see http://php.net/manual/en/function.file_put_contents.php - * - * @param string $path - * @param string $data - * @return bool - */ - public function file_put_contents($path, $data) { - $free = $this->free_space(''); - if ($free < 0 or strlen($data) < $free) { - return $this->storage->file_put_contents($path, $data); - } else { - return false; - } - } - - /** - * see http://php.net/manual/en/function.copy.php - * - * @param string $source - * @param string $target - * @return bool - */ - public function copy($source, $target) { - $free = $this->free_space(''); - if ($free < 0 or $this->getSize($source) < $free) { - return $this->storage->copy($source, $target); - } else { - return false; - } - } - - /** - * see http://php.net/manual/en/function.fopen.php - * - * @param string $path - * @param string $mode - * @return resource - */ - public function fopen($path, $mode) { - $source = $this->storage->fopen($path, $mode); - - // don't apply quota for part files - if (!$this->isPartFile($path)) { - $free = $this->free_space(''); - if ($source && $free >= 0 && $mode !== 'r' && $mode !== 'rb') { - // only apply quota for files, not metadata, trash or others - if (strpos(ltrim($path, '/'), 'files/') === 0) { - return \OC\Files\Stream\Quota::wrap($source, $free); - } - } - } - return $source; - } - - /** - * Checks whether the given path is a part file - * - * @param string $path Path that may identify a .part file - * @return string File path without .part extension - * @note this is needed for reusing keys - */ - private function isPartFile($path) { - $extension = pathinfo($path, PATHINFO_EXTENSION); - - return ($extension === 'part'); - } - - /** - * @param \OCP\Files\Storage $sourceStorage - * @param string $sourceInternalPath - * @param string $targetInternalPath - * @return bool - */ - public function copyFromStorage(\OCP\Files\Storage $sourceStorage, $sourceInternalPath, $targetInternalPath) { - $free = $this->free_space(''); - if ($free < 0 or $this->getSize($sourceInternalPath, $sourceStorage) < $free) { - return $this->storage->copyFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath); - } else { - return false; - } - } - - /** - * @param \OCP\Files\Storage $sourceStorage - * @param string $sourceInternalPath - * @param string $targetInternalPath - * @return bool - */ - public function moveFromStorage(\OCP\Files\Storage $sourceStorage, $sourceInternalPath, $targetInternalPath) { - $free = $this->free_space(''); - if ($free < 0 or $this->getSize($sourceInternalPath, $sourceStorage) < $free) { - return $this->storage->moveFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath); - } else { - return false; - } - } -} diff --git a/lib/private/files/storage/wrapper/wrapper.php b/lib/private/files/storage/wrapper/wrapper.php deleted file mode 100644 index 21d7db1099b..00000000000 --- a/lib/private/files/storage/wrapper/wrapper.php +++ /dev/null @@ -1,608 +0,0 @@ - - * @author Robin Appelman - * @author Robin McCorkell - * @author Thomas Müller - * @author Vincent Petry - * - * @copyright Copyright (c) 2016, ownCloud, Inc. - * @license AGPL-3.0 - * - * This code is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License, version 3, - * along with this program. If not, see - * - */ - -namespace OC\Files\Storage\Wrapper; - -use OCP\Files\InvalidPathException; -use OCP\Files\Storage\ILockingStorage; -use OCP\Lock\ILockingProvider; - -class Wrapper implements \OC\Files\Storage\Storage, ILockingStorage { - /** - * @var \OC\Files\Storage\Storage $storage - */ - protected $storage; - - public $cache; - public $scanner; - public $watcher; - public $propagator; - public $updater; - - /** - * @param array $parameters - */ - public function __construct($parameters) { - $this->storage = $parameters['storage']; - } - - /** - * @return \OC\Files\Storage\Storage - */ - public function getWrapperStorage() { - return $this->storage; - } - - /** - * Get the identifier for the storage, - * the returned id should be the same for every storage object that is created with the same parameters - * and two storage objects with the same id should refer to two storages that display the same files. - * - * @return string - */ - public function getId() { - return $this->storage->getId(); - } - - /** - * see http://php.net/manual/en/function.mkdir.php - * - * @param string $path - * @return bool - */ - public function mkdir($path) { - return $this->storage->mkdir($path); - } - - /** - * see http://php.net/manual/en/function.rmdir.php - * - * @param string $path - * @return bool - */ - public function rmdir($path) { - return $this->storage->rmdir($path); - } - - /** - * see http://php.net/manual/en/function.opendir.php - * - * @param string $path - * @return resource - */ - public function opendir($path) { - return $this->storage->opendir($path); - } - - /** - * see http://php.net/manual/en/function.is_dir.php - * - * @param string $path - * @return bool - */ - public function is_dir($path) { - return $this->storage->is_dir($path); - } - - /** - * see http://php.net/manual/en/function.is_file.php - * - * @param string $path - * @return bool - */ - public function is_file($path) { - return $this->storage->is_file($path); - } - - /** - * see http://php.net/manual/en/function.stat.php - * only the following keys are required in the result: size and mtime - * - * @param string $path - * @return array - */ - public function stat($path) { - return $this->storage->stat($path); - } - - /** - * see http://php.net/manual/en/function.filetype.php - * - * @param string $path - * @return bool - */ - public function filetype($path) { - return $this->storage->filetype($path); - } - - /** - * see http://php.net/manual/en/function.filesize.php - * The result for filesize when called on a folder is required to be 0 - * - * @param string $path - * @return int - */ - public function filesize($path) { - return $this->storage->filesize($path); - } - - /** - * check if a file can be created in $path - * - * @param string $path - * @return bool - */ - public function isCreatable($path) { - return $this->storage->isCreatable($path); - } - - /** - * check if a file can be read - * - * @param string $path - * @return bool - */ - public function isReadable($path) { - return $this->storage->isReadable($path); - } - - /** - * check if a file can be written to - * - * @param string $path - * @return bool - */ - public function isUpdatable($path) { - return $this->storage->isUpdatable($path); - } - - /** - * check if a file can be deleted - * - * @param string $path - * @return bool - */ - public function isDeletable($path) { - return $this->storage->isDeletable($path); - } - - /** - * check if a file can be shared - * - * @param string $path - * @return bool - */ - public function isSharable($path) { - return $this->storage->isSharable($path); - } - - /** - * get the full permissions of a path. - * Should return a combination of the PERMISSION_ constants defined in lib/public/constants.php - * - * @param string $path - * @return int - */ - public function getPermissions($path) { - return $this->storage->getPermissions($path); - } - - /** - * see http://php.net/manual/en/function.file_exists.php - * - * @param string $path - * @return bool - */ - public function file_exists($path) { - return $this->storage->file_exists($path); - } - - /** - * see http://php.net/manual/en/function.filemtime.php - * - * @param string $path - * @return int - */ - public function filemtime($path) { - return $this->storage->filemtime($path); - } - - /** - * see http://php.net/manual/en/function.file_get_contents.php - * - * @param string $path - * @return string - */ - public function file_get_contents($path) { - return $this->storage->file_get_contents($path); - } - - /** - * see http://php.net/manual/en/function.file_put_contents.php - * - * @param string $path - * @param string $data - * @return bool - */ - public function file_put_contents($path, $data) { - return $this->storage->file_put_contents($path, $data); - } - - /** - * see http://php.net/manual/en/function.unlink.php - * - * @param string $path - * @return bool - */ - public function unlink($path) { - return $this->storage->unlink($path); - } - - /** - * see http://php.net/manual/en/function.rename.php - * - * @param string $path1 - * @param string $path2 - * @return bool - */ - public function rename($path1, $path2) { - return $this->storage->rename($path1, $path2); - } - - /** - * see http://php.net/manual/en/function.copy.php - * - * @param string $path1 - * @param string $path2 - * @return bool - */ - public function copy($path1, $path2) { - return $this->storage->copy($path1, $path2); - } - - /** - * see http://php.net/manual/en/function.fopen.php - * - * @param string $path - * @param string $mode - * @return resource - */ - public function fopen($path, $mode) { - return $this->storage->fopen($path, $mode); - } - - /** - * get the mimetype for a file or folder - * The mimetype for a folder is required to be "httpd/unix-directory" - * - * @param string $path - * @return string - */ - public function getMimeType($path) { - return $this->storage->getMimeType($path); - } - - /** - * see http://php.net/manual/en/function.hash.php - * - * @param string $type - * @param string $path - * @param bool $raw - * @return string - */ - public function hash($type, $path, $raw = false) { - return $this->storage->hash($type, $path, $raw); - } - - /** - * see http://php.net/manual/en/function.free_space.php - * - * @param string $path - * @return int - */ - public function free_space($path) { - return $this->storage->free_space($path); - } - - /** - * search for occurrences of $query in file names - * - * @param string $query - * @return array - */ - public function search($query) { - return $this->storage->search($query); - } - - /** - * see http://php.net/manual/en/function.touch.php - * If the backend does not support the operation, false should be returned - * - * @param string $path - * @param int $mtime - * @return bool - */ - public function touch($path, $mtime = null) { - return $this->storage->touch($path, $mtime); - } - - /** - * get the path to a local version of the file. - * The local version of the file can be temporary and doesn't have to be persistent across requests - * - * @param string $path - * @return string - */ - public function getLocalFile($path) { - return $this->storage->getLocalFile($path); - } - - /** - * check if a file or folder has been updated since $time - * - * @param string $path - * @param int $time - * @return bool - * - * hasUpdated for folders should return at least true if a file inside the folder is add, removed or renamed. - * returning true for other changes in the folder is optional - */ - public function hasUpdated($path, $time) { - return $this->storage->hasUpdated($path, $time); - } - - /** - * get a cache instance for the storage - * - * @param string $path - * @param \OC\Files\Storage\Storage (optional) the storage to pass to the cache - * @return \OC\Files\Cache\Cache - */ - public function getCache($path = '', $storage = null) { - if (!$storage) { - $storage = $this; - } - return $this->storage->getCache($path, $storage); - } - - /** - * get a scanner instance for the storage - * - * @param string $path - * @param \OC\Files\Storage\Storage (optional) the storage to pass to the scanner - * @return \OC\Files\Cache\Scanner - */ - public function getScanner($path = '', $storage = null) { - if (!$storage) { - $storage = $this; - } - return $this->storage->getScanner($path, $storage); - } - - - /** - * get the user id of the owner of a file or folder - * - * @param string $path - * @return string - */ - public function getOwner($path) { - return $this->storage->getOwner($path); - } - - /** - * get a watcher instance for the cache - * - * @param string $path - * @param \OC\Files\Storage\Storage (optional) the storage to pass to the watcher - * @return \OC\Files\Cache\Watcher - */ - public function getWatcher($path = '', $storage = null) { - if (!$storage) { - $storage = $this; - } - return $this->storage->getWatcher($path, $storage); - } - - public function getPropagator($storage = null) { - if (!$storage) { - $storage = $this; - } - return $this->storage->getPropagator($storage); - } - - public function getUpdater($storage = null) { - if (!$storage) { - $storage = $this; - } - return $this->storage->getUpdater($storage); - } - - /** - * @return \OC\Files\Cache\Storage - */ - public function getStorageCache() { - return $this->storage->getStorageCache(); - } - - /** - * get the ETag for a file or folder - * - * @param string $path - * @return string - */ - public function getETag($path) { - return $this->storage->getETag($path); - } - - /** - * Returns true - * - * @return true - */ - public function test() { - return $this->storage->test(); - } - - /** - * Returns the wrapped storage's value for isLocal() - * - * @return bool wrapped storage's isLocal() value - */ - public function isLocal() { - return $this->storage->isLocal(); - } - - /** - * Check if the storage is an instance of $class or is a wrapper for a storage that is an instance of $class - * - * @param string $class - * @return bool - */ - public function instanceOfStorage($class) { - return is_a($this, $class) or $this->storage->instanceOfStorage($class); - } - - /** - * Pass any methods custom to specific storage implementations to the wrapped storage - * - * @param string $method - * @param array $args - * @return mixed - */ - public function __call($method, $args) { - return call_user_func_array(array($this->storage, $method), $args); - } - - /** - * A custom storage implementation can return an url for direct download of a give file. - * - * For now the returned array can hold the parameter url - in future more attributes might follow. - * - * @param string $path - * @return array - */ - public function getDirectDownload($path) { - return $this->storage->getDirectDownload($path); - } - - /** - * Get availability of the storage - * - * @return array [ available, last_checked ] - */ - public function getAvailability() { - return $this->storage->getAvailability(); - } - - /** - * Set availability of the storage - * - * @param bool $isAvailable - */ - public function setAvailability($isAvailable) { - $this->storage->setAvailability($isAvailable); - } - - /** - * @param string $path the path of the target folder - * @param string $fileName the name of the file itself - * @return void - * @throws InvalidPathException - */ - public function verifyPath($path, $fileName) { - $this->storage->verifyPath($path, $fileName); - } - - /** - * @param \OCP\Files\Storage $sourceStorage - * @param string $sourceInternalPath - * @param string $targetInternalPath - * @return bool - */ - public function copyFromStorage(\OCP\Files\Storage $sourceStorage, $sourceInternalPath, $targetInternalPath) { - if ($sourceStorage === $this) { - return $this->copy($sourceInternalPath, $targetInternalPath); - } - - return $this->storage->copyFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath); - } - - /** - * @param \OCP\Files\Storage $sourceStorage - * @param string $sourceInternalPath - * @param string $targetInternalPath - * @return bool - */ - public function moveFromStorage(\OCP\Files\Storage $sourceStorage, $sourceInternalPath, $targetInternalPath) { - if ($sourceStorage === $this) { - return $this->rename($sourceInternalPath, $targetInternalPath); - } - - return $this->storage->moveFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath); - } - - /** - * @param string $path - * @return array - */ - public function getMetaData($path) { - return $this->storage->getMetaData($path); - } - - /** - * @param string $path - * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE - * @param \OCP\Lock\ILockingProvider $provider - * @throws \OCP\Lock\LockedException - */ - public function acquireLock($path, $type, ILockingProvider $provider) { - if ($this->storage->instanceOfStorage('\OCP\Files\Storage\ILockingStorage')) { - $this->storage->acquireLock($path, $type, $provider); - } - } - - /** - * @param string $path - * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE - * @param \OCP\Lock\ILockingProvider $provider - */ - public function releaseLock($path, $type, ILockingProvider $provider) { - if ($this->storage->instanceOfStorage('\OCP\Files\Storage\ILockingStorage')) { - $this->storage->releaseLock($path, $type, $provider); - } - } - - /** - * @param string $path - * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE - * @param \OCP\Lock\ILockingProvider $provider - */ - public function changeLock($path, $type, ILockingProvider $provider) { - if ($this->storage->instanceOfStorage('\OCP\Files\Storage\ILockingStorage')) { - $this->storage->changeLock($path, $type, $provider); - } - } -} diff --git a/lib/private/files/stream/close.php b/lib/private/files/stream/close.php deleted file mode 100644 index 1c9b30705dd..00000000000 --- a/lib/private/files/stream/close.php +++ /dev/null @@ -1,118 +0,0 @@ - - * @author Morris Jobke - * @author Robin Appelman - * - * @copyright Copyright (c) 2016, ownCloud, Inc. - * @license AGPL-3.0 - * - * This code is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License, version 3, - * along with this program. If not, see - * - */ - -namespace OC\Files\Stream; - -/** - * stream wrapper that provides a callback on stream close - */ -class Close { - private static $callBacks = array(); - private $path = ''; - private $source; - private static $open = array(); - - public function stream_open($path, $mode, $options, &$opened_path) { - $path = substr($path, strlen('close://')); - $this->path = $path; - $this->source = fopen($path, $mode); - if (is_resource($this->source)) { - $this->meta = stream_get_meta_data($this->source); - } - self::$open[] = $path; - return is_resource($this->source); - } - - public function stream_seek($offset, $whence = SEEK_SET) { - return fseek($this->source, $offset, $whence) === 0; - } - - public function stream_tell() { - return ftell($this->source); - } - - public function stream_read($count) { - return fread($this->source, $count); - } - - public function stream_write($data) { - return fwrite($this->source, $data); - } - - public function stream_set_option($option, $arg1, $arg2) { - switch ($option) { - case STREAM_OPTION_BLOCKING: - stream_set_blocking($this->source, $arg1); - break; - case STREAM_OPTION_READ_TIMEOUT: - stream_set_timeout($this->source, $arg1, $arg2); - break; - case STREAM_OPTION_WRITE_BUFFER: - stream_set_write_buffer($this->source, $arg1, $arg2); - } - } - - public function stream_stat() { - return fstat($this->source); - } - - public function stream_lock($mode) { - flock($this->source, $mode); - } - - public function stream_flush() { - return fflush($this->source); - } - - public function stream_eof() { - return feof($this->source); - } - - public function url_stat($path) { - $path = substr($path, strlen('close://')); - if (file_exists($path)) { - return stat($path); - } else { - return false; - } - } - - public function stream_close() { - fclose($this->source); - if (isset(self::$callBacks[$this->path])) { - call_user_func(self::$callBacks[$this->path], $this->path); - } - } - - public function unlink($path) { - $path = substr($path, strlen('close://')); - return unlink($path); - } - - /** - * @param string $path - */ - public static function registerCallback($path, $callback) { - self::$callBacks[$path] = $callback; - } -} diff --git a/lib/private/files/stream/dir.php b/lib/private/files/stream/dir.php deleted file mode 100644 index 7489ee683a2..00000000000 --- a/lib/private/files/stream/dir.php +++ /dev/null @@ -1,66 +0,0 @@ - - * @author Morris Jobke - * @author Robin Appelman - * - * @copyright Copyright (c) 2016, ownCloud, Inc. - * @license AGPL-3.0 - * - * This code is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License, version 3, - * along with this program. If not, see - * - */ - -namespace OC\Files\Stream; - -class Dir { - private static $dirs = array(); - private $name; - private $index; - - public function dir_opendir($path, $options) { - $this->name = substr($path, strlen('fakedir://')); - $this->index = 0; - if (!isset(self::$dirs[$this->name])) { - self::$dirs[$this->name] = array(); - } - return true; - } - - public function dir_readdir() { - if ($this->index >= count(self::$dirs[$this->name])) { - return false; - } - $filename = self::$dirs[$this->name][$this->index]; - $this->index++; - return $filename; - } - - public function dir_closedir() { - $this->name = ''; - return true; - } - - public function dir_rewinddir() { - $this->index = 0; - return true; - } - - /** - * @param string $path - * @param string[] $content - */ - public static function register($path, $content) { - self::$dirs[$path] = $content; - } -} diff --git a/lib/private/files/stream/encryption.php b/lib/private/files/stream/encryption.php deleted file mode 100644 index 772c9769bf7..00000000000 --- a/lib/private/files/stream/encryption.php +++ /dev/null @@ -1,500 +0,0 @@ - - * @author jknockaert - * @author Lukas Reschke - * @author Roeland Jago Douma - * @author Thomas Müller - * @author Vincent Petry - * - * @copyright Copyright (c) 2016, ownCloud, Inc. - * @license AGPL-3.0 - * - * This code is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License, version 3, - * along with this program. If not, see - * - */ - -namespace OC\Files\Stream; - -use Icewind\Streams\Wrapper; -use OC\Encryption\Exceptions\EncryptionHeaderKeyExistsException; - -class Encryption extends Wrapper { - - /** @var \OC\Encryption\Util */ - protected $util; - - /** @var \OC\Encryption\File */ - protected $file; - - /** @var \OCP\Encryption\IEncryptionModule */ - protected $encryptionModule; - - /** @var \OC\Files\Storage\Storage */ - protected $storage; - - /** @var \OC\Files\Storage\Wrapper\Encryption */ - protected $encryptionStorage; - - /** @var string */ - protected $internalPath; - - /** @var string */ - protected $cache; - - /** @var integer */ - protected $size; - - /** @var integer */ - protected $position; - - /** @var integer */ - protected $unencryptedSize; - - /** @var integer */ - protected $headerSize; - - /** @var integer */ - protected $unencryptedBlockSize; - - /** @var array */ - protected $header; - - /** @var string */ - protected $fullPath; - - /** @var bool */ - protected $signed; - - /** - * header data returned by the encryption module, will be written to the file - * in case of a write operation - * - * @var array - */ - protected $newHeader; - - /** - * user who perform the read/write operation null for public access - * - * @var string - */ - protected $uid; - - /** @var bool */ - protected $readOnly; - - /** @var bool */ - protected $writeFlag; - - /** @var array */ - protected $expectedContextProperties; - - public function __construct() { - $this->expectedContextProperties = array( - 'source', - 'storage', - 'internalPath', - 'fullPath', - 'encryptionModule', - 'header', - 'uid', - 'file', - 'util', - 'size', - 'unencryptedSize', - 'encryptionStorage', - 'headerSize', - 'signed' - ); - } - - - /** - * Wraps a stream with the provided callbacks - * - * @param resource $source - * @param string $internalPath relative to mount point - * @param string $fullPath relative to data/ - * @param array $header - * @param string $uid - * @param \OCP\Encryption\IEncryptionModule $encryptionModule - * @param \OC\Files\Storage\Storage $storage - * @param \OC\Files\Storage\Wrapper\Encryption $encStorage - * @param \OC\Encryption\Util $util - * @param \OC\Encryption\File $file - * @param string $mode - * @param int $size - * @param int $unencryptedSize - * @param int $headerSize - * @param bool $signed - * @param string $wrapper stream wrapper class - * @return resource - * - * @throws \BadMethodCallException - */ - public static function wrap($source, $internalPath, $fullPath, array $header, - $uid, - \OCP\Encryption\IEncryptionModule $encryptionModule, - \OC\Files\Storage\Storage $storage, - \OC\Files\Storage\Wrapper\Encryption $encStorage, - \OC\Encryption\Util $util, - \OC\Encryption\File $file, - $mode, - $size, - $unencryptedSize, - $headerSize, - $signed, - $wrapper = 'OC\Files\Stream\Encryption') { - - $context = stream_context_create(array( - 'ocencryption' => array( - 'source' => $source, - 'storage' => $storage, - 'internalPath' => $internalPath, - 'fullPath' => $fullPath, - 'encryptionModule' => $encryptionModule, - 'header' => $header, - 'uid' => $uid, - 'util' => $util, - 'file' => $file, - 'size' => $size, - 'unencryptedSize' => $unencryptedSize, - 'encryptionStorage' => $encStorage, - 'headerSize' => $headerSize, - 'signed' => $signed - ) - )); - - return self::wrapSource($source, $context, 'ocencryption', $wrapper, $mode); - } - - /** - * add stream wrapper - * - * @param resource $source - * @param string $mode - * @param resource $context - * @param string $protocol - * @param string $class - * @return resource - * @throws \BadMethodCallException - */ - protected static function wrapSource($source, $context, $protocol, $class, $mode = 'r+') { - try { - stream_wrapper_register($protocol, $class); - if (@rewinddir($source) === false) { - $wrapped = fopen($protocol . '://', $mode, false, $context); - } else { - $wrapped = opendir($protocol . '://', $context); - } - } catch (\BadMethodCallException $e) { - stream_wrapper_unregister($protocol); - throw $e; - } - stream_wrapper_unregister($protocol); - return $wrapped; - } - - /** - * Load the source from the stream context and return the context options - * - * @param string $name - * @return array - * @throws \BadMethodCallException - */ - protected function loadContext($name) { - $context = parent::loadContext($name); - - foreach ($this->expectedContextProperties as $property) { - if (array_key_exists($property, $context)) { - $this->{$property} = $context[$property]; - } else { - throw new \BadMethodCallException('Invalid context, "' . $property . '" options not set'); - } - } - return $context; - - } - - public function stream_open($path, $mode, $options, &$opened_path) { - $this->loadContext('ocencryption'); - - $this->position = 0; - $this->cache = ''; - $this->writeFlag = false; - $this->unencryptedBlockSize = $this->encryptionModule->getUnencryptedBlockSize($this->signed); - - if ( - $mode === 'w' - || $mode === 'w+' - || $mode === 'wb' - || $mode === 'wb+' - || $mode === 'r+' - || $mode === 'rb+' - ) { - $this->readOnly = false; - } else { - $this->readOnly = true; - } - - $sharePath = $this->fullPath; - if (!$this->storage->file_exists($this->internalPath)) { - $sharePath = dirname($sharePath); - } - - $accessList = $this->file->getAccessList($sharePath); - $this->newHeader = $this->encryptionModule->begin($this->fullPath, $this->uid, $mode, $this->header, $accessList); - - if ( - $mode === 'w' - || $mode === 'w+' - || $mode === 'wb' - || $mode === 'wb+' - ) { - // We're writing a new file so start write counter with 0 bytes - $this->unencryptedSize = 0; - $this->writeHeader(); - $this->headerSize = $this->util->getHeaderSize(); - $this->size = $this->headerSize; - } else { - $this->skipHeader(); - } - - return true; - - } - - public function stream_eof() { - return $this->position >= $this->unencryptedSize; - } - - public function stream_read($count) { - - $result = ''; - - $count = min($count, $this->unencryptedSize - $this->position); - while ($count > 0) { - $remainingLength = $count; - // update the cache of the current block - $this->readCache(); - // determine the relative position in the current block - $blockPosition = ($this->position % $this->unencryptedBlockSize); - // if entire read inside current block then only position needs to be updated - if ($remainingLength < ($this->unencryptedBlockSize - $blockPosition)) { - $result .= substr($this->cache, $blockPosition, $remainingLength); - $this->position += $remainingLength; - $count = 0; - // otherwise remainder of current block is fetched, the block is flushed and the position updated - } else { - $result .= substr($this->cache, $blockPosition); - $this->flush(); - $this->position += ($this->unencryptedBlockSize - $blockPosition); - $count -= ($this->unencryptedBlockSize - $blockPosition); - } - } - return $result; - - } - - public function stream_write($data) { - - $length = 0; - // loop over $data to fit it in 6126 sized unencrypted blocks - while (isset($data[0])) { - $remainingLength = strlen($data); - - // set the cache to the current 6126 block - $this->readCache(); - - // for seekable streams the pointer is moved back to the beginning of the encrypted block - // flush will start writing there when the position moves to another block - $positionInFile = (int)floor($this->position / $this->unencryptedBlockSize) * - $this->util->getBlockSize() + $this->headerSize; - $resultFseek = $this->parentStreamSeek($positionInFile); - - // only allow writes on seekable streams, or at the end of the encrypted stream - if (!($this->readOnly) && ($resultFseek || $positionInFile === $this->size)) { - - // switch the writeFlag so flush() will write the block - $this->writeFlag = true; - - // determine the relative position in the current block - $blockPosition = ($this->position % $this->unencryptedBlockSize); - // check if $data fits in current block - // if so, overwrite existing data (if any) - // update position and liberate $data - if ($remainingLength < ($this->unencryptedBlockSize - $blockPosition)) { - $this->cache = substr($this->cache, 0, $blockPosition) - . $data . substr($this->cache, $blockPosition + $remainingLength); - $this->position += $remainingLength; - $length += $remainingLength; - $data = ''; - // if $data doesn't fit the current block, the fill the current block and reiterate - // after the block is filled, it is flushed and $data is updatedxxx - } else { - $this->cache = substr($this->cache, 0, $blockPosition) . - substr($data, 0, $this->unencryptedBlockSize - $blockPosition); - $this->flush(); - $this->position += ($this->unencryptedBlockSize - $blockPosition); - $length += ($this->unencryptedBlockSize - $blockPosition); - $data = substr($data, $this->unencryptedBlockSize - $blockPosition); - } - } else { - $data = ''; - } - $this->unencryptedSize = max($this->unencryptedSize, $this->position); - } - return $length; - } - - public function stream_tell() { - return $this->position; - } - - public function stream_seek($offset, $whence = SEEK_SET) { - - $return = false; - - switch ($whence) { - case SEEK_SET: - $newPosition = $offset; - break; - case SEEK_CUR: - $newPosition = $this->position + $offset; - break; - case SEEK_END: - $newPosition = $this->unencryptedSize + $offset; - break; - default: - return $return; - } - - if ($newPosition > $this->unencryptedSize || $newPosition < 0) { - return $return; - } - - $newFilePosition = floor($newPosition / $this->unencryptedBlockSize) - * $this->util->getBlockSize() + $this->headerSize; - - $oldFilePosition = parent::stream_tell(); - if ($this->parentStreamSeek($newFilePosition)) { - $this->parentStreamSeek($oldFilePosition); - $this->flush(); - $this->parentStreamSeek($newFilePosition); - $this->position = $newPosition; - $return = true; - } - return $return; - - } - - public function stream_close() { - $this->flush('end'); - $position = (int)floor($this->position/$this->unencryptedBlockSize); - $remainingData = $this->encryptionModule->end($this->fullPath, $position . 'end'); - if ($this->readOnly === false) { - if(!empty($remainingData)) { - parent::stream_write($remainingData); - } - $this->encryptionStorage->updateUnencryptedSize($this->fullPath, $this->unencryptedSize); - } - return parent::stream_close(); - } - - /** - * write block to file - * @param string $positionPrefix - */ - protected function flush($positionPrefix = '') { - // write to disk only when writeFlag was set to 1 - if ($this->writeFlag) { - // Disable the file proxies so that encryption is not - // automatically attempted when the file is written to disk - - // we are handling that separately here and we don't want to - // get into an infinite loop - $position = (int)floor($this->position/$this->unencryptedBlockSize); - $encrypted = $this->encryptionModule->encrypt($this->cache, $position . $positionPrefix); - $bytesWritten = parent::stream_write($encrypted); - $this->writeFlag = false; - // Check whether the write concerns the last block - // If so then update the encrypted filesize - // Note that the unencrypted pointer and filesize are NOT yet updated when flush() is called - // We recalculate the encrypted filesize as we do not know the context of calling flush() - $completeBlocksInFile=(int)floor($this->unencryptedSize/$this->unencryptedBlockSize); - if ($completeBlocksInFile === (int)floor($this->position/$this->unencryptedBlockSize)) { - $this->size = $this->util->getBlockSize() * $completeBlocksInFile; - $this->size += $bytesWritten; - $this->size += $this->headerSize; - } - } - // always empty the cache (otherwise readCache() will not fill it with the new block) - $this->cache = ''; - } - - /** - * read block to file - */ - protected function readCache() { - // cache should always be empty string when this function is called - // don't try to fill the cache when trying to write at the end of the unencrypted file when it coincides with new block - if ($this->cache === '' && !($this->position === $this->unencryptedSize && ($this->position % $this->unencryptedBlockSize) === 0)) { - // Get the data from the file handle - $data = parent::stream_read($this->util->getBlockSize()); - $position = (int)floor($this->position/$this->unencryptedBlockSize); - $numberOfChunks = (int)($this->unencryptedSize / $this->unencryptedBlockSize); - if($numberOfChunks === $position) { - $position .= 'end'; - } - $this->cache = $this->encryptionModule->decrypt($data, $position); - } - } - - /** - * write header at beginning of encrypted file - * - * @return integer - * @throws EncryptionHeaderKeyExistsException if header key is already in use - */ - protected function writeHeader() { - $header = $this->util->createHeader($this->newHeader, $this->encryptionModule); - return parent::stream_write($header); - } - - /** - * read first block to skip the header - */ - protected function skipHeader() { - parent::stream_read($this->headerSize); - } - - /** - * call stream_seek() from parent class - * - * @param integer $position - * @return bool - */ - protected function parentStreamSeek($position) { - return parent::stream_seek($position); - } - - /** - * @param string $path - * @param array $options - * @return bool - */ - public function dir_opendir($path, $options) { - return false; - } - -} diff --git a/lib/private/files/stream/oc.php b/lib/private/files/stream/oc.php deleted file mode 100644 index 8439770e8fa..00000000000 --- a/lib/private/files/stream/oc.php +++ /dev/null @@ -1,153 +0,0 @@ - - * @author Morris Jobke - * @author Robin Appelman - * @author Robin McCorkell - * - * @copyright Copyright (c) 2016, ownCloud, Inc. - * @license AGPL-3.0 - * - * This code is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License, version 3, - * along with this program. If not, see - * - */ - -namespace OC\Files\Stream; - -/** - * a stream wrappers for ownCloud's virtual filesystem - */ -class OC { - /** - * @var \OC\Files\View - */ - static private $rootView; - - private $path; - - /** - * @var resource - */ - private $dirSource; - - /** - * @var resource - */ - private $fileSource; - private $meta; - - private function setup(){ - if (!self::$rootView) { - self::$rootView = new \OC\Files\View(''); - } - } - - public function stream_open($path, $mode, $options, &$opened_path) { - $this->setup(); - $path = substr($path, strlen('oc://')); - $this->path = $path; - $this->fileSource = self::$rootView->fopen($path, $mode); - if (is_resource($this->fileSource)) { - $this->meta = stream_get_meta_data($this->fileSource); - } - return is_resource($this->fileSource); - } - - public function stream_seek($offset, $whence = SEEK_SET) { - return fseek($this->fileSource, $offset, $whence) === 0; - } - - public function stream_tell() { - return ftell($this->fileSource); - } - - public function stream_read($count) { - return fread($this->fileSource, $count); - } - - public function stream_write($data) { - return fwrite($this->fileSource, $data); - } - - public function stream_set_option($option, $arg1, $arg2) { - switch ($option) { - case STREAM_OPTION_BLOCKING: - stream_set_blocking($this->fileSource, $arg1); - break; - case STREAM_OPTION_READ_TIMEOUT: - stream_set_timeout($this->fileSource, $arg1, $arg2); - break; - case STREAM_OPTION_WRITE_BUFFER: - stream_set_write_buffer($this->fileSource, $arg1, $arg2); - } - } - - public function stream_stat() { - return fstat($this->fileSource); - } - - public function stream_lock($mode) { - flock($this->fileSource, $mode); - } - - public function stream_flush() { - return fflush($this->fileSource); - } - - public function stream_eof() { - return feof($this->fileSource); - } - - public function url_stat($path) { - $this->setup(); - $path = substr($path, strlen('oc://')); - if (self::$rootView->file_exists($path)) { - return self::$rootView->stat($path); - } else { - return false; - } - } - - public function stream_close() { - fclose($this->fileSource); - } - - public function unlink($path) { - $this->setup(); - $path = substr($path, strlen('oc://')); - return self::$rootView->unlink($path); - } - - public function dir_opendir($path, $options) { - $this->setup(); - $path = substr($path, strlen('oc://')); - $this->path = $path; - $this->dirSource = self::$rootView->opendir($path); - if (is_resource($this->dirSource)) { - $this->meta = stream_get_meta_data($this->dirSource); - } - return is_resource($this->dirSource); - } - - public function dir_readdir() { - return readdir($this->dirSource); - } - - public function dir_closedir() { - closedir($this->dirSource); - } - - public function dir_rewinddir() { - rewinddir($this->dirSource); - } -} diff --git a/lib/private/files/stream/quota.php b/lib/private/files/stream/quota.php deleted file mode 100644 index 8d27575c568..00000000000 --- a/lib/private/files/stream/quota.php +++ /dev/null @@ -1,156 +0,0 @@ - - * @author Morris Jobke - * @author Robin Appelman - * @author Vincent Petry - * - * @copyright Copyright (c) 2016, ownCloud, Inc. - * @license AGPL-3.0 - * - * This code is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License, version 3, - * along with this program. If not, see - * - */ - -namespace OC\Files\Stream; - -/** - * stream wrapper limits the amount of data that can be written to a stream - * - * usage: void \OC\Files\Stream\Quota::register($id, $stream, $limit) - * or: resource \OC\Files\Stream\Quota::wrap($stream, $limit) - */ -class Quota { - private static $streams = array(); - - /** - * @var resource $source - */ - private $source; - - /** - * @var int $limit - */ - private $limit; - - /** - * @param string $id - * @param resource $stream - * @param int $limit - */ - public static function register($id, $stream, $limit) { - self::$streams[$id] = array($stream, $limit); - } - - /** - * remove all registered streams - */ - public static function clear() { - self::$streams = array(); - } - - /** - * @param resource $stream - * @param int $limit - * @return resource - */ - static public function wrap($stream, $limit) { - $id = uniqid(); - self::register($id, $stream, $limit); - $meta = stream_get_meta_data($stream); - return fopen('quota://' . $id, $meta['mode']); - } - - public function stream_open($path, $mode, $options, &$opened_path) { - $id = substr($path, strlen('quota://')); - if (isset(self::$streams[$id])) { - list($this->source, $this->limit) = self::$streams[$id]; - return true; - } else { - return false; - } - } - - public function stream_seek($offset, $whence = SEEK_SET) { - if ($whence === SEEK_END){ - // go to the end to find out last position's offset - $oldOffset = $this->stream_tell(); - if (fseek($this->source, 0, $whence) !== 0){ - return false; - } - $whence = SEEK_SET; - $offset = $this->stream_tell() + $offset; - $this->limit += $oldOffset - $offset; - } - else if ($whence === SEEK_SET) { - $this->limit += $this->stream_tell() - $offset; - } else { - $this->limit -= $offset; - } - // this wrapper needs to return "true" for success. - // the fseek call itself returns 0 on succeess - return fseek($this->source, $offset, $whence) === 0; - } - - public function stream_tell() { - return ftell($this->source); - } - - public function stream_read($count) { - $this->limit -= $count; - return fread($this->source, $count); - } - - public function stream_write($data) { - $size = strlen($data); - if ($size > $this->limit) { - $data = substr($data, 0, $this->limit); - $size = $this->limit; - } - $this->limit -= $size; - return fwrite($this->source, $data); - } - - public function stream_set_option($option, $arg1, $arg2) { - switch ($option) { - case STREAM_OPTION_BLOCKING: - stream_set_blocking($this->source, $arg1); - break; - case STREAM_OPTION_READ_TIMEOUT: - stream_set_timeout($this->source, $arg1, $arg2); - break; - case STREAM_OPTION_WRITE_BUFFER: - stream_set_write_buffer($this->source, $arg1, $arg2); - } - } - - public function stream_stat() { - return fstat($this->source); - } - - public function stream_lock($mode) { - return flock($this->source, $mode); - } - - public function stream_flush() { - return fflush($this->source); - } - - public function stream_eof() { - return feof($this->source); - } - - public function stream_close() { - fclose($this->source); - } -} diff --git a/lib/private/files/stream/staticstream.php b/lib/private/files/stream/staticstream.php deleted file mode 100644 index 7aacf7a9fe4..00000000000 --- a/lib/private/files/stream/staticstream.php +++ /dev/null @@ -1,170 +0,0 @@ - - * @author Robin Appelman - * - * @copyright Copyright (c) 2016, ownCloud, Inc. - * @license AGPL-3.0 - * - * This code is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License, version 3, - * along with this program. If not, see - * - */ - -namespace OC\Files\Stream; - -class StaticStream { - const MODE_FILE = 0100000; - - public $context; - protected static $data = array(); - - protected $path = ''; - protected $pointer = 0; - protected $writable = false; - - public function stream_close() { - } - - public function stream_eof() { - return $this->pointer >= strlen(self::$data[$this->path]); - } - - public function stream_flush() { - } - - public static function clear() { - self::$data = array(); - } - - public function stream_open($path, $mode, $options, &$opened_path) { - switch ($mode[0]) { - case 'r': - if (!isset(self::$data[$path])) return false; - $this->path = $path; - $this->writable = isset($mode[1]) && $mode[1] == '+'; - break; - case 'w': - self::$data[$path] = ''; - $this->path = $path; - $this->writable = true; - break; - case 'a': - if (!isset(self::$data[$path])) self::$data[$path] = ''; - $this->path = $path; - $this->writable = true; - $this->pointer = strlen(self::$data[$path]); - break; - case 'x': - if (isset(self::$data[$path])) return false; - $this->path = $path; - $this->writable = true; - break; - case 'c': - if (!isset(self::$data[$path])) self::$data[$path] = ''; - $this->path = $path; - $this->writable = true; - break; - default: - return false; - } - $opened_path = $this->path; - return true; - } - - public function stream_read($count) { - $bytes = min(strlen(self::$data[$this->path]) - $this->pointer, $count); - $data = substr(self::$data[$this->path], $this->pointer, $bytes); - $this->pointer += $bytes; - return $data; - } - - public function stream_seek($offset, $whence = SEEK_SET) { - $len = strlen(self::$data[$this->path]); - switch ($whence) { - case SEEK_SET: - if ($offset <= $len) { - $this->pointer = $offset; - return true; - } - break; - case SEEK_CUR: - if ($this->pointer + $offset <= $len) { - $this->pointer += $offset; - return true; - } - break; - case SEEK_END: - if ($len + $offset <= $len) { - $this->pointer = $len + $offset; - return true; - } - break; - } - return false; - } - - public function stream_stat() { - return $this->url_stat($this->path); - } - - public function stream_tell() { - return $this->pointer; - } - - public function stream_write($data) { - if (!$this->writable) return 0; - $size = strlen($data); - if ($this->stream_eof()) { - self::$data[$this->path] .= $data; - } else { - self::$data[$this->path] = substr_replace( - self::$data[$this->path], - $data, - $this->pointer - ); - } - $this->pointer += $size; - return $size; - } - - public function unlink($path) { - if (isset(self::$data[$path])) { - unset(self::$data[$path]); - } - return true; - } - - public function url_stat($path) { - if (isset(self::$data[$path])) { - $size = strlen(self::$data[$path]); - $time = time(); - $data = array( - 'dev' => 0, - 'ino' => 0, - 'mode' => self::MODE_FILE | 0777, - 'nlink' => 1, - 'uid' => 0, - 'gid' => 0, - 'rdev' => '', - 'size' => $size, - 'atime' => $time, - 'mtime' => $time, - 'ctime' => $time, - 'blksize' => -1, - 'blocks' => -1, - ); - return array_values($data) + $data; - } - return false; - } -} diff --git a/lib/private/files/type/detection.php b/lib/private/files/type/detection.php deleted file mode 100644 index f106a98064f..00000000000 --- a/lib/private/files/type/detection.php +++ /dev/null @@ -1,318 +0,0 @@ - - * @author Jens-Christian Fischer - * @author Lukas Reschke - * @author Morris Jobke - * @author Robin Appelman - * @author Robin McCorkell - * @author Roeland Jago Douma - * @author Thomas Tanghus - * - * @copyright Copyright (c) 2016, ownCloud, Inc. - * @license AGPL-3.0 - * - * This code is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License, version 3, - * along with this program. If not, see - * - */ - -namespace OC\Files\Type; - -use OCP\Files\IMimeTypeDetector; -use OCP\IURLGenerator; - -/** - * Class Detection - * - * Mimetype detection - * - * @package OC\Files\Type - */ -class Detection implements IMimeTypeDetector { - protected $mimetypes = []; - protected $secureMimeTypes = []; - - protected $mimetypeIcons = []; - /** @var string[] */ - protected $mimeTypeAlias = []; - - /** @var IURLGenerator */ - private $urlGenerator; - - /** @var string */ - private $customConfigDir; - - /** @var string */ - private $defaultConfigDir; - - /** - * @param IURLGenerator $urlGenerator - * @param string $customConfigDir - * @param string $defaultConfigDir - */ - public function __construct(IURLGenerator $urlGenerator, - $customConfigDir, - $defaultConfigDir) { - $this->urlGenerator = $urlGenerator; - $this->customConfigDir = $customConfigDir; - $this->defaultConfigDir = $defaultConfigDir; - } - - /** - * Add an extension -> mimetype mapping - * - * $mimetype is the assumed correct mime type - * The optional $secureMimeType is an alternative to send to send - * to avoid potential XSS. - * - * @param string $extension - * @param string $mimetype - * @param string|null $secureMimeType - */ - public function registerType($extension, - $mimetype, - $secureMimeType = null) { - $this->mimetypes[$extension] = array($mimetype, $secureMimeType); - $this->secureMimeTypes[$mimetype] = $secureMimeType ?: $mimetype; - } - - /** - * Add an array of extension -> mimetype mappings - * - * The mimetype value is in itself an array where the first index is - * the assumed correct mimetype and the second is either a secure alternative - * or null if the correct is considered secure. - * - * @param array $types - */ - public function registerTypeArray($types) { - $this->mimetypes = array_merge($this->mimetypes, $types); - - // Update the alternative mimetypes to avoid having to look them up each time. - foreach ($this->mimetypes as $mimeType) { - $this->secureMimeTypes[$mimeType[0]] = isset($mimeType[1]) ? $mimeType[1]: $mimeType[0]; - } - } - - /** - * Add the mimetype aliases if they are not yet present - */ - private function loadAliases() { - if (!empty($this->mimeTypeAlias)) { - return; - } - - $this->mimeTypeAlias = json_decode(file_get_contents($this->defaultConfigDir . '/mimetypealiases.dist.json'), true); - - if (file_exists($this->customConfigDir . '/mimetypealiases.json')) { - $custom = json_decode(file_get_contents($this->customConfigDir . '/mimetypealiases.json'), true); - $this->mimeTypeAlias = array_merge($this->mimeTypeAlias, $custom); - } - } - - /** - * @return string[] - */ - public function getAllAliases() { - $this->loadAliases(); - return $this->mimeTypeAlias; - } - - /** - * Add mimetype mappings if they are not yet present - */ - private function loadMappings() { - if (!empty($this->mimetypes)) { - return; - } - - $mimetypeMapping = json_decode(file_get_contents($this->defaultConfigDir . '/mimetypemapping.dist.json'), true); - - //Check if need to load custom mappings - if (file_exists($this->customConfigDir . '/mimetypemapping.json')) { - $custom = json_decode(file_get_contents($this->customConfigDir . '/mimetypemapping.json'), true); - $mimetypeMapping = array_merge($mimetypeMapping, $custom); - } - - $this->registerTypeArray($mimetypeMapping); - } - - /** - * @return array - */ - public function getAllMappings() { - $this->loadMappings(); - return $this->mimetypes; - } - - /** - * detect mimetype only based on filename, content of file is not used - * - * @param string $path - * @return string - */ - public function detectPath($path) { - $this->loadMappings(); - - if (strpos($path, '.')) { - //try to guess the type by the file extension - $extension = strtolower(strrchr(basename($path), ".")); - $extension = substr($extension, 1); //remove leading . - return (isset($this->mimetypes[$extension]) && isset($this->mimetypes[$extension][0])) - ? $this->mimetypes[$extension][0] - : 'application/octet-stream'; - } else { - return 'application/octet-stream'; - } - } - - /** - * detect mimetype based on both filename and content - * - * @param string $path - * @return string - */ - public function detect($path) { - $this->loadMappings(); - - if (@is_dir($path)) { - // directories are easy - return "httpd/unix-directory"; - } - - $mimeType = $this->detectPath($path); - - if ($mimeType === 'application/octet-stream' and function_exists('finfo_open') - and function_exists('finfo_file') and $finfo = finfo_open(FILEINFO_MIME) - ) { - $info = @strtolower(finfo_file($finfo, $path)); - finfo_close($finfo); - if ($info) { - $mimeType = substr($info, 0, strpos($info, ';')); - return empty($mimeType) ? 'application/octet-stream' : $mimeType; - } - - } - $isWrapped = (strpos($path, '://') !== false) and (substr($path, 0, 7) === 'file://'); - if (!$isWrapped and $mimeType === 'application/octet-stream' && function_exists("mime_content_type")) { - // use mime magic extension if available - $mimeType = mime_content_type($path); - } - if (!$isWrapped and $mimeType === 'application/octet-stream' && \OC_Helper::canExecute("file")) { - // it looks like we have a 'file' command, - // lets see if it does have mime support - $path = escapeshellarg($path); - $fp = popen("file -b --mime-type $path 2>/dev/null", "r"); - $reply = fgets($fp); - pclose($fp); - - //trim the newline - $mimeType = trim($reply); - - if (empty($mimeType)) { - $mimeType = 'application/octet-stream'; - } - - } - return $mimeType; - } - - /** - * detect mimetype based on the content of a string - * - * @param string $data - * @return string - */ - public function detectString($data) { - if (function_exists('finfo_open') and function_exists('finfo_file')) { - $finfo = finfo_open(FILEINFO_MIME); - return finfo_buffer($finfo, $data); - } else { - $tmpFile = \OC::$server->getTempManager()->getTemporaryFile(); - $fh = fopen($tmpFile, 'wb'); - fwrite($fh, $data, 8024); - fclose($fh); - $mime = $this->detect($tmpFile); - unset($tmpFile); - return $mime; - } - } - - /** - * Get a secure mimetype that won't expose potential XSS. - * - * @param string $mimeType - * @return string - */ - public function getSecureMimeType($mimeType) { - $this->loadMappings(); - - return isset($this->secureMimeTypes[$mimeType]) - ? $this->secureMimeTypes[$mimeType] - : 'application/octet-stream'; - } - - /** - * Get path to the icon of a file type - * @param string $mimetype the MIME type - * @return string the url - */ - public function mimeTypeIcon($mimetype) { - $this->loadAliases(); - - while (isset($this->mimeTypeAlias[$mimetype])) { - $mimetype = $this->mimeTypeAlias[$mimetype]; - } - if (isset($this->mimetypeIcons[$mimetype])) { - return $this->mimetypeIcons[$mimetype]; - } - - // Replace slash and backslash with a minus - $icon = str_replace('/', '-', $mimetype); - $icon = str_replace('\\', '-', $icon); - - // Is it a dir? - if ($mimetype === 'dir') { - $this->mimetypeIcons[$mimetype] = $this->urlGenerator->imagePath('core', 'filetypes/folder.png'); - return $this->mimetypeIcons[$mimetype]; - } - if ($mimetype === 'dir-shared') { - $this->mimetypeIcons[$mimetype] = $this->urlGenerator->imagePath('core', 'filetypes/folder-shared.png'); - return $this->mimetypeIcons[$mimetype]; - } - if ($mimetype === 'dir-external') { - $this->mimetypeIcons[$mimetype] = $this->urlGenerator->imagePath('core', 'filetypes/folder-external.png'); - return $this->mimetypeIcons[$mimetype]; - } - - // Icon exists? - try { - $this->mimetypeIcons[$mimetype] = $this->urlGenerator->imagePath('core', 'filetypes/' . $icon . '.png'); - return $this->mimetypeIcons[$mimetype]; - } catch (\RuntimeException $e) { - // Specified image not found - } - - // Try only the first part of the filetype - $mimePart = substr($icon, 0, strpos($icon, '-')); - try { - $this->mimetypeIcons[$mimetype] = $this->urlGenerator->imagePath('core', 'filetypes/' . $mimePart . '.png'); - return $this->mimetypeIcons[$mimetype]; - } catch (\RuntimeException $e) { - // Image for the first part of the mimetype not found - } - - $this->mimetypeIcons[$mimetype] = $this->urlGenerator->imagePath('core', 'filetypes/file.png'); - return $this->mimetypeIcons[$mimetype]; - } -} diff --git a/lib/private/files/type/loader.php b/lib/private/files/type/loader.php deleted file mode 100644 index 95ba7597257..00000000000 --- a/lib/private/files/type/loader.php +++ /dev/null @@ -1,173 +0,0 @@ - - * - * @copyright Copyright (c) 2016, ownCloud, Inc. - * @license AGPL-3.0 - * - * This code is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License, version 3, - * along with this program. If not, see - * - */ - -namespace OC\Files\Type; - -use OCP\Files\IMimeTypeLoader; -use OCP\IDBConnection; - -use Doctrine\DBAL\Exception\UniqueConstraintViolationException; - -/** - * Mimetype database loader - * - * @package OC\Files\Type - */ -class Loader implements IMimeTypeLoader { - - /** @var IDBConnection */ - private $dbConnection; - - /** @var array [id => mimetype] */ - protected $mimetypes; - - /** @var array [mimetype => id] */ - protected $mimetypeIds; - - /** - * @param IDBConnection $dbConnection - */ - public function __construct(IDBConnection $dbConnection) { - $this->dbConnection = $dbConnection; - $this->mimetypes = []; - $this->mimetypeIds = []; - } - - /** - * Get a mimetype from its ID - * - * @param int $id - * @return string|null - */ - public function getMimetypeById($id) { - if (!$this->mimetypes) { - $this->loadMimetypes(); - } - if (isset($this->mimetypes[$id])) { - return $this->mimetypes[$id]; - } - return null; - } - - /** - * Get a mimetype ID, adding the mimetype to the DB if it does not exist - * - * @param string $mimetype - * @return int - */ - public function getId($mimetype) { - if (!$this->mimetypeIds) { - $this->loadMimetypes(); - } - if (isset($this->mimetypeIds[$mimetype])) { - return $this->mimetypeIds[$mimetype]; - } - return $this->store($mimetype); - } - - /** - * Test if a mimetype exists in the database - * - * @param string $mimetype - * @return bool - */ - public function exists($mimetype) { - if (!$this->mimetypeIds) { - $this->loadMimetypes(); - } - return isset($this->mimetypeIds[$mimetype]); - } - - /** - * Clear all loaded mimetypes, allow for re-loading - */ - public function reset() { - $this->mimetypes = []; - $this->mimetypeIds = []; - } - - /** - * Store a mimetype in the DB - * - * @param string $mimetype - * @param int inserted ID - */ - protected function store($mimetype) { - try { - $qb = $this->dbConnection->getQueryBuilder(); - $qb->insert('mimetypes') - ->values([ - 'mimetype' => $qb->createNamedParameter($mimetype) - ]); - $qb->execute(); - } catch (UniqueConstraintViolationException $e) { - // something inserted it before us - } - - $fetch = $this->dbConnection->getQueryBuilder(); - $fetch->select('id') - ->from('mimetypes') - ->where( - $fetch->expr()->eq('mimetype', $fetch->createNamedParameter($mimetype) - )); - $row = $fetch->execute()->fetch(); - - $this->mimetypes[$row['id']] = $mimetype; - $this->mimetypeIds[$mimetype] = $row['id']; - return $row['id']; - } - - /** - * Load all mimetypes from DB - */ - private function loadMimetypes() { - $qb = $this->dbConnection->getQueryBuilder(); - $qb->select('id', 'mimetype') - ->from('mimetypes'); - $results = $qb->execute()->fetchAll(); - - foreach ($results as $row) { - $this->mimetypes[$row['id']] = $row['mimetype']; - $this->mimetypeIds[$row['mimetype']] = $row['id']; - } - } - - /** - * Update filecache mimetype based on file extension - * - * @param string $ext file extension - * @param int $mimetypeId - * @return int number of changed rows - */ - public function updateFilecache($ext, $mimetypeId) { - $update = $this->dbConnection->getQueryBuilder(); - $update->update('filecache') - ->set('mimetype', $update->createNamedParameter($mimetypeId)) - ->where($update->expr()->neq( - 'mimetype', $update->createNamedParameter($mimetypeId) - )) - ->andWhere($update->expr()->like( - $update->createFunction('LOWER(`name`)'), $update->createNamedParameter($ext) - )); - return $update->execute(); - } - -} diff --git a/lib/private/files/type/templatemanager.php b/lib/private/files/type/templatemanager.php deleted file mode 100644 index 363fb7a2a6c..00000000000 --- a/lib/private/files/type/templatemanager.php +++ /dev/null @@ -1,61 +0,0 @@ - - * @author Robin Appelman - * @author Robin McCorkell - * - * @copyright Copyright (c) 2016, ownCloud, Inc. - * @license AGPL-3.0 - * - * This code is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License, version 3, - * along with this program. If not, see - * - */ - -namespace OC\Files\Type; - -class TemplateManager { - protected $templates = array(); - - public function registerTemplate($mimetype, $path) { - $this->templates[$mimetype] = $path; - } - - /** - * get the path of the template for a mimetype - * - * @param string $mimetype - * @return string|null - */ - public function getTemplatePath($mimetype) { - if (isset($this->templates[$mimetype])) { - return $this->templates[$mimetype]; - } else { - return null; - } - } - - /** - * get the template content for a mimetype - * - * @param string $mimetype - * @return string - */ - public function getTemplate($mimetype) { - $path = $this->getTemplatePath($mimetype); - if ($path) { - return file_get_contents($path); - } else { - return ''; - } - } -} diff --git a/lib/private/files/utils/scanner.php b/lib/private/files/utils/scanner.php deleted file mode 100644 index 06526583899..00000000000 --- a/lib/private/files/utils/scanner.php +++ /dev/null @@ -1,172 +0,0 @@ - - * @author Morris Jobke - * @author Olivier Paroz - * @author Robin Appelman - * @author Thomas Müller - * - * @copyright Copyright (c) 2016, ownCloud, Inc. - * @license AGPL-3.0 - * - * This code is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License, version 3, - * along with this program. If not, see - * - */ - -namespace OC\Files\Utils; - -use OC\Files\Filesystem; -use OC\ForbiddenException; -use OC\Hooks\PublicEmitter; -use OC\Lock\DBLockingProvider; -use OCP\Files\StorageNotAvailableException; -use OCP\ILogger; - -/** - * Class Scanner - * - * Hooks available in scope \OC\Utils\Scanner - * - scanFile(string $absolutePath) - * - scanFolder(string $absolutePath) - * - * @package OC\Files\Utils - */ -class Scanner extends PublicEmitter { - /** - * @var string $user - */ - private $user; - - /** - * @var \OCP\IDBConnection - */ - protected $db; - - /** - * @var ILogger - */ - protected $logger; - - /** - * @param string $user - * @param \OCP\IDBConnection $db - * @param ILogger $logger - */ - public function __construct($user, $db, ILogger $logger) { - $this->logger = $logger; - $this->user = $user; - $this->db = $db; - } - - /** - * get all storages for $dir - * - * @param string $dir - * @return \OC\Files\Mount\MountPoint[] - */ - protected function getMounts($dir) { - //TODO: move to the node based fileapi once that's done - \OC_Util::tearDownFS(); - \OC_Util::setupFS($this->user); - - $mountManager = Filesystem::getMountManager(); - $mounts = $mountManager->findIn($dir); - $mounts[] = $mountManager->find($dir); - $mounts = array_reverse($mounts); //start with the mount of $dir - - return $mounts; - } - - /** - * attach listeners to the scanner - * - * @param \OC\Files\Mount\MountPoint $mount - */ - protected function attachListener($mount) { - $scanner = $mount->getStorage()->getScanner(); - $emitter = $this; - $scanner->listen('\OC\Files\Cache\Scanner', 'scanFile', function ($path) use ($mount, $emitter) { - $emitter->emit('\OC\Files\Utils\Scanner', 'scanFile', array($mount->getMountPoint() . $path)); - }); - $scanner->listen('\OC\Files\Cache\Scanner', 'scanFolder', function ($path) use ($mount, $emitter) { - $emitter->emit('\OC\Files\Utils\Scanner', 'scanFolder', array($mount->getMountPoint() . $path)); - }); - $scanner->listen('\OC\Files\Cache\Scanner', 'postScanFile', function ($path) use ($mount, $emitter) { - $emitter->emit('\OC\Files\Utils\Scanner', 'postScanFile', array($mount->getMountPoint() . $path)); - }); - $scanner->listen('\OC\Files\Cache\Scanner', 'postScanFolder', function ($path) use ($mount, $emitter) { - $emitter->emit('\OC\Files\Utils\Scanner', 'postScanFolder', array($mount->getMountPoint() . $path)); - }); - } - - /** - * @param string $dir - */ - public function backgroundScan($dir) { - $mounts = $this->getMounts($dir); - foreach ($mounts as $mount) { - if (is_null($mount->getStorage())) { - continue; - } - // don't scan the root storage - if ($mount->getStorage()->instanceOfStorage('\OC\Files\Storage\Local') && $mount->getMountPoint() === '/') { - continue; - } - $scanner = $mount->getStorage()->getScanner(); - $this->attachListener($mount); - $scanner->backgroundScan(); - } - } - - /** - * @param string $dir - * @throws \OC\ForbiddenException - */ - public function scan($dir = '') { - if (!Filesystem::isValidPath($dir)) { - throw new \InvalidArgumentException('Invalid path to scan'); - } - $mounts = $this->getMounts($dir); - foreach ($mounts as $mount) { - if (is_null($mount->getStorage())) { - continue; - } - $storage = $mount->getStorage(); - // if the home storage isn't writable then the scanner is run as the wrong user - if ($storage->instanceOfStorage('\OC\Files\Storage\Home') and - (!$storage->isCreatable('') or !$storage->isCreatable('files')) - ) { - throw new ForbiddenException(); - } - $relativePath = $mount->getInternalPath($dir); - $scanner = $storage->getScanner(); - $scanner->setUseTransactions(false); - $this->attachListener($mount); - $isDbLocking = \OC::$server->getLockingProvider() instanceof DBLockingProvider; - if (!$isDbLocking) { - $this->db->beginTransaction(); - } - try { - $scanner->scan($relativePath, \OC\Files\Cache\Scanner::SCAN_RECURSIVE, \OC\Files\Cache\Scanner::REUSE_ETAG | \OC\Files\Cache\Scanner::REUSE_SIZE); - } catch (StorageNotAvailableException $e) { - $this->logger->error('Storage ' . $storage->getId() . ' not available'); - $this->logger->logException($e); - $this->emit('\OC\Files\Utils\Scanner', 'StorageNotAvailable', [$e]); - } - if (!$isDbLocking) { - $this->db->commit(); - } - } - } -} - diff --git a/lib/private/files/view.php b/lib/private/files/view.php deleted file mode 100644 index aac33a4598c..00000000000 --- a/lib/private/files/view.php +++ /dev/null @@ -1,2058 +0,0 @@ - - * @author Bart Visscher - * @author Björn Schießle - * @author cmeh - * @author Florin Peter - * @author Jesús Macias - * @author Joas Schilling - * @author Jörn Friedrich Dreyer - * @author Klaas Freitag - * @author Lukas Reschke - * @author Luke Policinski - * @author Martin Mattel - * @author Michael Gapczynski - * @author Morris Jobke - * @author Robin Appelman - * @author Robin McCorkell - * @author Roeland Jago Douma - * @author Sam Tuke - * @author Thomas Müller - * @author Thomas Tanghus - * @author Vincent Petry - * - * @copyright Copyright (c) 2016, ownCloud, Inc. - * @license AGPL-3.0 - * - * This code is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License, version 3, - * along with this program. If not, see - * - */ - - -namespace OC\Files; - -use Icewind\Streams\CallbackWrapper; -use OC\Files\Mount\MoveableMount; -use OC\Files\Storage\Storage; -use OC\User\User; -use OCP\Constants; -use OCP\Files\Cache\ICacheEntry; -use OCP\Files\FileNameTooLongException; -use OCP\Files\InvalidCharacterInPathException; -use OCP\Files\InvalidPathException; -use OCP\Files\NotFoundException; -use OCP\Files\ReservedWordException; -use OCP\Files\Storage\ILockingStorage; -use OCP\IUser; -use OCP\Lock\ILockingProvider; -use OCP\Lock\LockedException; - -/** - * Class to provide access to ownCloud filesystem via a "view", and methods for - * working with files within that view (e.g. read, write, delete, etc.). Each - * view is restricted to a set of directories via a virtual root. The default view - * uses the currently logged in user's data directory as root (parts of - * OC_Filesystem are merely a wrapper for OC\Files\View). - * - * Apps that need to access files outside of the user data folders (to modify files - * belonging to a user other than the one currently logged in, for example) should - * use this class directly rather than using OC_Filesystem, or making use of PHP's - * built-in file manipulation functions. This will ensure all hooks and proxies - * are triggered correctly. - * - * Filesystem functions are not called directly; they are passed to the correct - * \OC\Files\Storage\Storage object - */ -class View { - /** @var string */ - private $fakeRoot = ''; - - /** - * @var \OCP\Lock\ILockingProvider - */ - private $lockingProvider; - - private $lockingEnabled; - - private $updaterEnabled = true; - - private $userManager; - - /** - * @param string $root - * @throws \Exception If $root contains an invalid path - */ - public function __construct($root = '') { - if (is_null($root)) { - throw new \InvalidArgumentException('Root can\'t be null'); - } - if (!Filesystem::isValidPath($root)) { - throw new \Exception(); - } - - $this->fakeRoot = $root; - $this->lockingProvider = \OC::$server->getLockingProvider(); - $this->lockingEnabled = !($this->lockingProvider instanceof \OC\Lock\NoopLockingProvider); - $this->userManager = \OC::$server->getUserManager(); - } - - public function getAbsolutePath($path = '/') { - if ($path === null) { - return null; - } - $this->assertPathLength($path); - if ($path === '') { - $path = '/'; - } - if ($path[0] !== '/') { - $path = '/' . $path; - } - return $this->fakeRoot . $path; - } - - /** - * change the root to a fake root - * - * @param string $fakeRoot - * @return boolean|null - */ - public function chroot($fakeRoot) { - if (!$fakeRoot == '') { - if ($fakeRoot[0] !== '/') { - $fakeRoot = '/' . $fakeRoot; - } - } - $this->fakeRoot = $fakeRoot; - } - - /** - * get the fake root - * - * @return string - */ - public function getRoot() { - return $this->fakeRoot; - } - - /** - * get path relative to the root of the view - * - * @param string $path - * @return string - */ - public function getRelativePath($path) { - $this->assertPathLength($path); - if ($this->fakeRoot == '') { - return $path; - } - - if (rtrim($path, '/') === rtrim($this->fakeRoot, '/')) { - return '/'; - } - - // missing slashes can cause wrong matches! - $root = rtrim($this->fakeRoot, '/') . '/'; - - if (strpos($path, $root) !== 0) { - return null; - } else { - $path = substr($path, strlen($this->fakeRoot)); - if (strlen($path) === 0) { - return '/'; - } else { - return $path; - } - } - } - - /** - * get the mountpoint of the storage object for a path - * ( note: because a storage is not always mounted inside the fakeroot, the - * returned mountpoint is relative to the absolute root of the filesystem - * and does not take the chroot into account ) - * - * @param string $path - * @return string - */ - public function getMountPoint($path) { - return Filesystem::getMountPoint($this->getAbsolutePath($path)); - } - - /** - * get the mountpoint of the storage object for a path - * ( note: because a storage is not always mounted inside the fakeroot, the - * returned mountpoint is relative to the absolute root of the filesystem - * and does not take the chroot into account ) - * - * @param string $path - * @return \OCP\Files\Mount\IMountPoint - */ - public function getMount($path) { - return Filesystem::getMountManager()->find($this->getAbsolutePath($path)); - } - - /** - * resolve a path to a storage and internal path - * - * @param string $path - * @return array an array consisting of the storage and the internal path - */ - public function resolvePath($path) { - $a = $this->getAbsolutePath($path); - $p = Filesystem::normalizePath($a); - return Filesystem::resolvePath($p); - } - - /** - * return the path to a local version of the file - * we need this because we can't know if a file is stored local or not from - * outside the filestorage and for some purposes a local file is needed - * - * @param string $path - * @return string - */ - public function getLocalFile($path) { - $parent = substr($path, 0, strrpos($path, '/')); - $path = $this->getAbsolutePath($path); - list($storage, $internalPath) = Filesystem::resolvePath($path); - if (Filesystem::isValidPath($parent) and $storage) { - return $storage->getLocalFile($internalPath); - } else { - return null; - } - } - - /** - * @param string $path - * @return string - */ - public function getLocalFolder($path) { - $parent = substr($path, 0, strrpos($path, '/')); - $path = $this->getAbsolutePath($path); - list($storage, $internalPath) = Filesystem::resolvePath($path); - if (Filesystem::isValidPath($parent) and $storage) { - return $storage->getLocalFolder($internalPath); - } else { - return null; - } - } - - /** - * the following functions operate with arguments and return values identical - * to those of their PHP built-in equivalents. Mostly they are merely wrappers - * for \OC\Files\Storage\Storage via basicOperation(). - */ - public function mkdir($path) { - return $this->basicOperation('mkdir', $path, array('create', 'write')); - } - - /** - * remove mount point - * - * @param \OC\Files\Mount\MoveableMount $mount - * @param string $path relative to data/ - * @return boolean - */ - protected function removeMount($mount, $path) { - if ($mount instanceof MoveableMount) { - // cut of /user/files to get the relative path to data/user/files - $pathParts = explode('/', $path, 4); - $relPath = '/' . $pathParts[3]; - $this->lockFile($relPath, ILockingProvider::LOCK_SHARED, true); - \OC_Hook::emit( - Filesystem::CLASSNAME, "umount", - array(Filesystem::signal_param_path => $relPath) - ); - $this->changeLock($relPath, ILockingProvider::LOCK_EXCLUSIVE, true); - $result = $mount->removeMount(); - $this->changeLock($relPath, ILockingProvider::LOCK_SHARED, true); - if ($result) { - \OC_Hook::emit( - Filesystem::CLASSNAME, "post_umount", - array(Filesystem::signal_param_path => $relPath) - ); - } - $this->unlockFile($relPath, ILockingProvider::LOCK_SHARED, true); - return $result; - } else { - // do not allow deleting the storage's root / the mount point - // because for some storages it might delete the whole contents - // but isn't supposed to work that way - return false; - } - } - - public function disableCacheUpdate() { - $this->updaterEnabled = false; - } - - public function enableCacheUpdate() { - $this->updaterEnabled = true; - } - - protected function writeUpdate(Storage $storage, $internalPath, $time = null) { - if ($this->updaterEnabled) { - if (is_null($time)) { - $time = time(); - } - $storage->getUpdater()->update($internalPath, $time); - } - } - - protected function removeUpdate(Storage $storage, $internalPath) { - if ($this->updaterEnabled) { - $storage->getUpdater()->remove($internalPath); - } - } - - protected function renameUpdate(Storage $sourceStorage, Storage $targetStorage, $sourceInternalPath, $targetInternalPath) { - if ($this->updaterEnabled) { - $targetStorage->getUpdater()->renameFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath); - } - } - - /** - * @param string $path - * @return bool|mixed - */ - public function rmdir($path) { - $absolutePath = $this->getAbsolutePath($path); - $mount = Filesystem::getMountManager()->find($absolutePath); - if ($mount->getInternalPath($absolutePath) === '') { - return $this->removeMount($mount, $absolutePath); - } - if ($this->is_dir($path)) { - return $this->basicOperation('rmdir', $path, array('delete')); - } else { - return false; - } - } - - /** - * @param string $path - * @return resource - */ - public function opendir($path) { - return $this->basicOperation('opendir', $path, array('read')); - } - - /** - * @param $handle - * @return mixed - */ - public function readdir($handle) { - $fsLocal = new Storage\Local(array('datadir' => '/')); - return $fsLocal->readdir($handle); - } - - /** - * @param string $path - * @return bool|mixed - */ - public function is_dir($path) { - if ($path == '/') { - return true; - } - return $this->basicOperation('is_dir', $path); - } - - /** - * @param string $path - * @return bool|mixed - */ - public function is_file($path) { - if ($path == '/') { - return false; - } - return $this->basicOperation('is_file', $path); - } - - /** - * @param string $path - * @return mixed - */ - public function stat($path) { - return $this->basicOperation('stat', $path); - } - - /** - * @param string $path - * @return mixed - */ - public function filetype($path) { - return $this->basicOperation('filetype', $path); - } - - /** - * @param string $path - * @return mixed - */ - public function filesize($path) { - return $this->basicOperation('filesize', $path); - } - - /** - * @param string $path - * @return bool|mixed - * @throws \OCP\Files\InvalidPathException - */ - public function readfile($path) { - $this->assertPathLength($path); - @ob_end_clean(); - $handle = $this->fopen($path, 'rb'); - if ($handle) { - $chunkSize = 8192; // 8 kB chunks - while (!feof($handle)) { - echo fread($handle, $chunkSize); - flush(); - } - $size = $this->filesize($path); - return $size; - } - return false; - } - - /** - * @param string $path - * @return mixed - */ - public function isCreatable($path) { - return $this->basicOperation('isCreatable', $path); - } - - /** - * @param string $path - * @return mixed - */ - public function isReadable($path) { - return $this->basicOperation('isReadable', $path); - } - - /** - * @param string $path - * @return mixed - */ - public function isUpdatable($path) { - return $this->basicOperation('isUpdatable', $path); - } - - /** - * @param string $path - * @return bool|mixed - */ - public function isDeletable($path) { - $absolutePath = $this->getAbsolutePath($path); - $mount = Filesystem::getMountManager()->find($absolutePath); - if ($mount->getInternalPath($absolutePath) === '') { - return $mount instanceof MoveableMount; - } - return $this->basicOperation('isDeletable', $path); - } - - /** - * @param string $path - * @return mixed - */ - public function isSharable($path) { - return $this->basicOperation('isSharable', $path); - } - - /** - * @param string $path - * @return bool|mixed - */ - public function file_exists($path) { - if ($path == '/') { - return true; - } - return $this->basicOperation('file_exists', $path); - } - - /** - * @param string $path - * @return mixed - */ - public function filemtime($path) { - return $this->basicOperation('filemtime', $path); - } - - /** - * @param string $path - * @param int|string $mtime - * @return bool - */ - public function touch($path, $mtime = null) { - if (!is_null($mtime) and !is_numeric($mtime)) { - $mtime = strtotime($mtime); - } - - $hooks = array('touch'); - - if (!$this->file_exists($path)) { - $hooks[] = 'create'; - $hooks[] = 'write'; - } - $result = $this->basicOperation('touch', $path, $hooks, $mtime); - if (!$result) { - // If create file fails because of permissions on external storage like SMB folders, - // check file exists and return false if not. - if (!$this->file_exists($path)) { - return false; - } - if (is_null($mtime)) { - $mtime = time(); - } - //if native touch fails, we emulate it by changing the mtime in the cache - $this->putFileInfo($path, array('mtime' => $mtime)); - } - return true; - } - - /** - * @param string $path - * @return mixed - */ - public function file_get_contents($path) { - return $this->basicOperation('file_get_contents', $path, array('read')); - } - - /** - * @param bool $exists - * @param string $path - * @param bool $run - */ - protected function emit_file_hooks_pre($exists, $path, &$run) { - if (!$exists) { - \OC_Hook::emit(Filesystem::CLASSNAME, Filesystem::signal_create, array( - Filesystem::signal_param_path => $this->getHookPath($path), - Filesystem::signal_param_run => &$run, - )); - } else { - \OC_Hook::emit(Filesystem::CLASSNAME, Filesystem::signal_update, array( - Filesystem::signal_param_path => $this->getHookPath($path), - Filesystem::signal_param_run => &$run, - )); - } - \OC_Hook::emit(Filesystem::CLASSNAME, Filesystem::signal_write, array( - Filesystem::signal_param_path => $this->getHookPath($path), - Filesystem::signal_param_run => &$run, - )); - } - - /** - * @param bool $exists - * @param string $path - */ - protected function emit_file_hooks_post($exists, $path) { - if (!$exists) { - \OC_Hook::emit(Filesystem::CLASSNAME, Filesystem::signal_post_create, array( - Filesystem::signal_param_path => $this->getHookPath($path), - )); - } else { - \OC_Hook::emit(Filesystem::CLASSNAME, Filesystem::signal_post_update, array( - Filesystem::signal_param_path => $this->getHookPath($path), - )); - } - \OC_Hook::emit(Filesystem::CLASSNAME, Filesystem::signal_post_write, array( - Filesystem::signal_param_path => $this->getHookPath($path), - )); - } - - /** - * @param string $path - * @param mixed $data - * @return bool|mixed - * @throws \Exception - */ - public function file_put_contents($path, $data) { - if (is_resource($data)) { //not having to deal with streams in file_put_contents makes life easier - $absolutePath = Filesystem::normalizePath($this->getAbsolutePath($path)); - if (Filesystem::isValidPath($path) - and !Filesystem::isFileBlacklisted($path) - ) { - $path = $this->getRelativePath($absolutePath); - - $this->lockFile($path, ILockingProvider::LOCK_SHARED); - - $exists = $this->file_exists($path); - $run = true; - if ($this->shouldEmitHooks($path)) { - $this->emit_file_hooks_pre($exists, $path, $run); - } - if (!$run) { - $this->unlockFile($path, ILockingProvider::LOCK_SHARED); - return false; - } - - $this->changeLock($path, ILockingProvider::LOCK_EXCLUSIVE); - - /** @var \OC\Files\Storage\Storage $storage */ - list($storage, $internalPath) = $this->resolvePath($path); - $target = $storage->fopen($internalPath, 'w'); - if ($target) { - list (, $result) = \OC_Helper::streamCopy($data, $target); - fclose($target); - fclose($data); - - $this->writeUpdate($storage, $internalPath); - - $this->changeLock($path, ILockingProvider::LOCK_SHARED); - - if ($this->shouldEmitHooks($path) && $result !== false) { - $this->emit_file_hooks_post($exists, $path); - } - $this->unlockFile($path, ILockingProvider::LOCK_SHARED); - return $result; - } else { - $this->unlockFile($path, ILockingProvider::LOCK_EXCLUSIVE); - return false; - } - } else { - return false; - } - } else { - $hooks = ($this->file_exists($path)) ? array('update', 'write') : array('create', 'write'); - return $this->basicOperation('file_put_contents', $path, $hooks, $data); - } - } - - /** - * @param string $path - * @return bool|mixed - */ - public function unlink($path) { - if ($path === '' || $path === '/') { - // do not allow deleting the root - return false; - } - $postFix = (substr($path, -1, 1) === '/') ? '/' : ''; - $absolutePath = Filesystem::normalizePath($this->getAbsolutePath($path)); - $mount = Filesystem::getMountManager()->find($absolutePath . $postFix); - if ($mount and $mount->getInternalPath($absolutePath) === '') { - return $this->removeMount($mount, $absolutePath); - } - return $this->basicOperation('unlink', $path, array('delete')); - } - - /** - * @param string $directory - * @return bool|mixed - */ - public function deleteAll($directory) { - return $this->rmdir($directory); - } - - /** - * Rename/move a file or folder from the source path to target path. - * - * @param string $path1 source path - * @param string $path2 target path - * - * @return bool|mixed - */ - public function rename($path1, $path2) { - $absolutePath1 = Filesystem::normalizePath($this->getAbsolutePath($path1)); - $absolutePath2 = Filesystem::normalizePath($this->getAbsolutePath($path2)); - $result = false; - if ( - Filesystem::isValidPath($path2) - and Filesystem::isValidPath($path1) - and !Filesystem::isFileBlacklisted($path2) - ) { - $path1 = $this->getRelativePath($absolutePath1); - $path2 = $this->getRelativePath($absolutePath2); - $exists = $this->file_exists($path2); - - if ($path1 == null or $path2 == null) { - return false; - } - - $this->lockFile($path1, ILockingProvider::LOCK_SHARED, true); - try { - $this->lockFile($path2, ILockingProvider::LOCK_SHARED, true); - } catch (LockedException $e) { - $this->unlockFile($path1, ILockingProvider::LOCK_SHARED); - throw $e; - } - - $run = true; - if ($this->shouldEmitHooks($path1) && (Cache\Scanner::isPartialFile($path1) && !Cache\Scanner::isPartialFile($path2))) { - // if it was a rename from a part file to a regular file it was a write and not a rename operation - $this->emit_file_hooks_pre($exists, $path2, $run); - } elseif ($this->shouldEmitHooks($path1)) { - \OC_Hook::emit( - Filesystem::CLASSNAME, Filesystem::signal_rename, - array( - Filesystem::signal_param_oldpath => $this->getHookPath($path1), - Filesystem::signal_param_newpath => $this->getHookPath($path2), - Filesystem::signal_param_run => &$run - ) - ); - } - if ($run) { - $this->verifyPath(dirname($path2), basename($path2)); - - $manager = Filesystem::getMountManager(); - $mount1 = $this->getMount($path1); - $mount2 = $this->getMount($path2); - $storage1 = $mount1->getStorage(); - $storage2 = $mount2->getStorage(); - $internalPath1 = $mount1->getInternalPath($absolutePath1); - $internalPath2 = $mount2->getInternalPath($absolutePath2); - - $this->changeLock($path1, ILockingProvider::LOCK_EXCLUSIVE, true); - $this->changeLock($path2, ILockingProvider::LOCK_EXCLUSIVE, true); - - if ($internalPath1 === '' and $mount1 instanceof MoveableMount) { - if ($this->isTargetAllowed($absolutePath2)) { - /** - * @var \OC\Files\Mount\MountPoint | \OC\Files\Mount\MoveableMount $mount1 - */ - $sourceMountPoint = $mount1->getMountPoint(); - $result = $mount1->moveMount($absolutePath2); - $manager->moveMount($sourceMountPoint, $mount1->getMountPoint()); - } else { - $result = false; - } - // moving a file/folder within the same mount point - } elseif ($storage1 == $storage2) { - if ($storage1) { - $result = $storage1->rename($internalPath1, $internalPath2); - } else { - $result = false; - } - // moving a file/folder between storages (from $storage1 to $storage2) - } else { - $result = $storage2->moveFromStorage($storage1, $internalPath1, $internalPath2); - } - - if ((Cache\Scanner::isPartialFile($path1) && !Cache\Scanner::isPartialFile($path2)) && $result !== false) { - // if it was a rename from a part file to a regular file it was a write and not a rename operation - - $this->writeUpdate($storage2, $internalPath2); - } else if ($result) { - if ($internalPath1 !== '') { // don't do a cache update for moved mounts - $this->renameUpdate($storage1, $storage2, $internalPath1, $internalPath2); - } - } - - $this->changeLock($path1, ILockingProvider::LOCK_SHARED, true); - $this->changeLock($path2, ILockingProvider::LOCK_SHARED, true); - - if ((Cache\Scanner::isPartialFile($path1) && !Cache\Scanner::isPartialFile($path2)) && $result !== false) { - if ($this->shouldEmitHooks()) { - $this->emit_file_hooks_post($exists, $path2); - } - } elseif ($result) { - if ($this->shouldEmitHooks($path1) and $this->shouldEmitHooks($path2)) { - \OC_Hook::emit( - Filesystem::CLASSNAME, - Filesystem::signal_post_rename, - array( - Filesystem::signal_param_oldpath => $this->getHookPath($path1), - Filesystem::signal_param_newpath => $this->getHookPath($path2) - ) - ); - } - } - } - $this->unlockFile($path1, ILockingProvider::LOCK_SHARED, true); - $this->unlockFile($path2, ILockingProvider::LOCK_SHARED, true); - } - return $result; - } - - /** - * Copy a file/folder from the source path to target path - * - * @param string $path1 source path - * @param string $path2 target path - * @param bool $preserveMtime whether to preserve mtime on the copy - * - * @return bool|mixed - */ - public function copy($path1, $path2, $preserveMtime = false) { - $absolutePath1 = Filesystem::normalizePath($this->getAbsolutePath($path1)); - $absolutePath2 = Filesystem::normalizePath($this->getAbsolutePath($path2)); - $result = false; - if ( - Filesystem::isValidPath($path2) - and Filesystem::isValidPath($path1) - and !Filesystem::isFileBlacklisted($path2) - ) { - $path1 = $this->getRelativePath($absolutePath1); - $path2 = $this->getRelativePath($absolutePath2); - - if ($path1 == null or $path2 == null) { - return false; - } - $run = true; - - $this->lockFile($path2, ILockingProvider::LOCK_SHARED); - $this->lockFile($path1, ILockingProvider::LOCK_SHARED); - $lockTypePath1 = ILockingProvider::LOCK_SHARED; - $lockTypePath2 = ILockingProvider::LOCK_SHARED; - - try { - - $exists = $this->file_exists($path2); - if ($this->shouldEmitHooks()) { - \OC_Hook::emit( - Filesystem::CLASSNAME, - Filesystem::signal_copy, - array( - Filesystem::signal_param_oldpath => $this->getHookPath($path1), - Filesystem::signal_param_newpath => $this->getHookPath($path2), - Filesystem::signal_param_run => &$run - ) - ); - $this->emit_file_hooks_pre($exists, $path2, $run); - } - if ($run) { - $mount1 = $this->getMount($path1); - $mount2 = $this->getMount($path2); - $storage1 = $mount1->getStorage(); - $internalPath1 = $mount1->getInternalPath($absolutePath1); - $storage2 = $mount2->getStorage(); - $internalPath2 = $mount2->getInternalPath($absolutePath2); - - $this->changeLock($path2, ILockingProvider::LOCK_EXCLUSIVE); - $lockTypePath2 = ILockingProvider::LOCK_EXCLUSIVE; - - if ($mount1->getMountPoint() == $mount2->getMountPoint()) { - if ($storage1) { - $result = $storage1->copy($internalPath1, $internalPath2); - } else { - $result = false; - } - } else { - $result = $storage2->copyFromStorage($storage1, $internalPath1, $internalPath2); - } - - $this->writeUpdate($storage2, $internalPath2); - - $this->changeLock($path2, ILockingProvider::LOCK_SHARED); - $lockTypePath2 = ILockingProvider::LOCK_SHARED; - - if ($this->shouldEmitHooks() && $result !== false) { - \OC_Hook::emit( - Filesystem::CLASSNAME, - Filesystem::signal_post_copy, - array( - Filesystem::signal_param_oldpath => $this->getHookPath($path1), - Filesystem::signal_param_newpath => $this->getHookPath($path2) - ) - ); - $this->emit_file_hooks_post($exists, $path2); - } - - } - } catch (\Exception $e) { - $this->unlockFile($path2, $lockTypePath2); - $this->unlockFile($path1, $lockTypePath1); - throw $e; - } - - $this->unlockFile($path2, $lockTypePath2); - $this->unlockFile($path1, $lockTypePath1); - - } - return $result; - } - - /** - * @param string $path - * @param string $mode - * @return resource - */ - public function fopen($path, $mode) { - $hooks = array(); - switch ($mode) { - case 'r': - case 'rb': - $hooks[] = 'read'; - break; - case 'r+': - case 'rb+': - case 'w+': - case 'wb+': - case 'x+': - case 'xb+': - case 'a+': - case 'ab+': - $hooks[] = 'read'; - $hooks[] = 'write'; - break; - case 'w': - case 'wb': - case 'x': - case 'xb': - case 'a': - case 'ab': - $hooks[] = 'write'; - break; - default: - \OCP\Util::writeLog('core', 'invalid mode (' . $mode . ') for ' . $path, \OCP\Util::ERROR); - } - - return $this->basicOperation('fopen', $path, $hooks, $mode); - } - - /** - * @param string $path - * @return bool|string - * @throws \OCP\Files\InvalidPathException - */ - public function toTmpFile($path) { - $this->assertPathLength($path); - if (Filesystem::isValidPath($path)) { - $source = $this->fopen($path, 'r'); - if ($source) { - $extension = pathinfo($path, PATHINFO_EXTENSION); - $tmpFile = \OC::$server->getTempManager()->getTemporaryFile($extension); - file_put_contents($tmpFile, $source); - return $tmpFile; - } else { - return false; - } - } else { - return false; - } - } - - /** - * @param string $tmpFile - * @param string $path - * @return bool|mixed - * @throws \OCP\Files\InvalidPathException - */ - public function fromTmpFile($tmpFile, $path) { - $this->assertPathLength($path); - if (Filesystem::isValidPath($path)) { - - // Get directory that the file is going into - $filePath = dirname($path); - - // Create the directories if any - if (!$this->file_exists($filePath)) { - $this->mkdir($filePath); - } - - $source = fopen($tmpFile, 'r'); - if ($source) { - $result = $this->file_put_contents($path, $source); - // $this->file_put_contents() might have already closed - // the resource, so we check it, before trying to close it - // to avoid messages in the error log. - if (is_resource($source)) { - fclose($source); - } - unlink($tmpFile); - return $result; - } else { - return false; - } - } else { - return false; - } - } - - - /** - * @param string $path - * @return mixed - * @throws \OCP\Files\InvalidPathException - */ - public function getMimeType($path) { - $this->assertPathLength($path); - return $this->basicOperation('getMimeType', $path); - } - - /** - * @param string $type - * @param string $path - * @param bool $raw - * @return bool|null|string - */ - public function hash($type, $path, $raw = false) { - $postFix = (substr($path, -1, 1) === '/') ? '/' : ''; - $absolutePath = Filesystem::normalizePath($this->getAbsolutePath($path)); - if (Filesystem::isValidPath($path)) { - $path = $this->getRelativePath($absolutePath); - if ($path == null) { - return false; - } - if ($this->shouldEmitHooks($path)) { - \OC_Hook::emit( - Filesystem::CLASSNAME, - Filesystem::signal_read, - array(Filesystem::signal_param_path => $this->getHookPath($path)) - ); - } - list($storage, $internalPath) = Filesystem::resolvePath($absolutePath . $postFix); - if ($storage) { - $result = $storage->hash($type, $internalPath, $raw); - return $result; - } - } - return null; - } - - /** - * @param string $path - * @return mixed - * @throws \OCP\Files\InvalidPathException - */ - public function free_space($path = '/') { - $this->assertPathLength($path); - return $this->basicOperation('free_space', $path); - } - - /** - * abstraction layer for basic filesystem functions: wrapper for \OC\Files\Storage\Storage - * - * @param string $operation - * @param string $path - * @param array $hooks (optional) - * @param mixed $extraParam (optional) - * @return mixed - * @throws \Exception - * - * This method takes requests for basic filesystem functions (e.g. reading & writing - * files), processes hooks and proxies, sanitises paths, and finally passes them on to - * \OC\Files\Storage\Storage for delegation to a storage backend for execution - */ - private function basicOperation($operation, $path, $hooks = [], $extraParam = null) { - $postFix = (substr($path, -1, 1) === '/') ? '/' : ''; - $absolutePath = Filesystem::normalizePath($this->getAbsolutePath($path)); - if (Filesystem::isValidPath($path) - and !Filesystem::isFileBlacklisted($path) - ) { - $path = $this->getRelativePath($absolutePath); - if ($path == null) { - return false; - } - - if (in_array('write', $hooks) || in_array('delete', $hooks) || in_array('read', $hooks)) { - // always a shared lock during pre-hooks so the hook can read the file - $this->lockFile($path, ILockingProvider::LOCK_SHARED); - } - - $run = $this->runHooks($hooks, $path); - /** @var \OC\Files\Storage\Storage $storage */ - list($storage, $internalPath) = Filesystem::resolvePath($absolutePath . $postFix); - if ($run and $storage) { - if (in_array('write', $hooks) || in_array('delete', $hooks)) { - $this->changeLock($path, ILockingProvider::LOCK_EXCLUSIVE); - } - try { - if (!is_null($extraParam)) { - $result = $storage->$operation($internalPath, $extraParam); - } else { - $result = $storage->$operation($internalPath); - } - } catch (\Exception $e) { - if (in_array('write', $hooks) || in_array('delete', $hooks)) { - $this->unlockFile($path, ILockingProvider::LOCK_EXCLUSIVE); - } else if (in_array('read', $hooks)) { - $this->unlockFile($path, ILockingProvider::LOCK_SHARED); - } - throw $e; - } - - if (in_array('delete', $hooks) and $result) { - $this->removeUpdate($storage, $internalPath); - } - if (in_array('write', $hooks) and $operation !== 'fopen') { - $this->writeUpdate($storage, $internalPath); - } - if (in_array('touch', $hooks)) { - $this->writeUpdate($storage, $internalPath, $extraParam); - } - - if ((in_array('write', $hooks) || in_array('delete', $hooks)) && ($operation !== 'fopen' || $result === false)) { - $this->changeLock($path, ILockingProvider::LOCK_SHARED); - } - - $unlockLater = false; - if ($this->lockingEnabled && $operation === 'fopen' && is_resource($result)) { - $unlockLater = true; - $result = CallbackWrapper::wrap($result, null, null, function () use ($hooks, $path) { - if (in_array('write', $hooks)) { - $this->unlockFile($path, ILockingProvider::LOCK_EXCLUSIVE); - } else if (in_array('read', $hooks)) { - $this->unlockFile($path, ILockingProvider::LOCK_SHARED); - } - }); - } - - if ($this->shouldEmitHooks($path) && $result !== false) { - if ($operation != 'fopen') { //no post hooks for fopen, the file stream is still open - $this->runHooks($hooks, $path, true); - } - } - - if (!$unlockLater - && (in_array('write', $hooks) || in_array('delete', $hooks) || in_array('read', $hooks)) - ) { - $this->unlockFile($path, ILockingProvider::LOCK_SHARED); - } - return $result; - } else { - $this->unlockFile($path, ILockingProvider::LOCK_SHARED); - } - } - return null; - } - - /** - * get the path relative to the default root for hook usage - * - * @param string $path - * @return string - */ - private function getHookPath($path) { - if (!Filesystem::getView()) { - return $path; - } - return Filesystem::getView()->getRelativePath($this->getAbsolutePath($path)); - } - - private function shouldEmitHooks($path = '') { - if ($path && Cache\Scanner::isPartialFile($path)) { - return false; - } - if (!Filesystem::$loaded) { - return false; - } - $defaultRoot = Filesystem::getRoot(); - if ($defaultRoot === null) { - return false; - } - if ($this->fakeRoot === $defaultRoot) { - return true; - } - $fullPath = $this->getAbsolutePath($path); - - if ($fullPath === $defaultRoot) { - return true; - } - - return (strlen($fullPath) > strlen($defaultRoot)) && (substr($fullPath, 0, strlen($defaultRoot) + 1) === $defaultRoot . '/'); - } - - /** - * @param string[] $hooks - * @param string $path - * @param bool $post - * @return bool - */ - private function runHooks($hooks, $path, $post = false) { - $relativePath = $path; - $path = $this->getHookPath($path); - $prefix = ($post) ? 'post_' : ''; - $run = true; - if ($this->shouldEmitHooks($relativePath)) { - foreach ($hooks as $hook) { - if ($hook != 'read') { - \OC_Hook::emit( - Filesystem::CLASSNAME, - $prefix . $hook, - array( - Filesystem::signal_param_run => &$run, - Filesystem::signal_param_path => $path - ) - ); - } elseif (!$post) { - \OC_Hook::emit( - Filesystem::CLASSNAME, - $prefix . $hook, - array( - Filesystem::signal_param_path => $path - ) - ); - } - } - } - return $run; - } - - /** - * check if a file or folder has been updated since $time - * - * @param string $path - * @param int $time - * @return bool - */ - public function hasUpdated($path, $time) { - return $this->basicOperation('hasUpdated', $path, array(), $time); - } - - /** - * @param string $ownerId - * @return \OC\User\User - */ - private function getUserObjectForOwner($ownerId) { - $owner = $this->userManager->get($ownerId); - if ($owner instanceof IUser) { - return $owner; - } else { - return new User($ownerId, null); - } - } - - /** - * Get file info from cache - * - * If the file is not in cached it will be scanned - * If the file has changed on storage the cache will be updated - * - * @param \OC\Files\Storage\Storage $storage - * @param string $internalPath - * @param string $relativePath - * @return array|bool - */ - private function getCacheEntry($storage, $internalPath, $relativePath) { - $cache = $storage->getCache($internalPath); - $data = $cache->get($internalPath); - $watcher = $storage->getWatcher($internalPath); - - try { - // if the file is not in the cache or needs to be updated, trigger the scanner and reload the data - if (!$data || $data['size'] === -1) { - $this->lockFile($relativePath, ILockingProvider::LOCK_SHARED); - if (!$storage->file_exists($internalPath)) { - $this->unlockFile($relativePath, ILockingProvider::LOCK_SHARED); - return false; - } - $scanner = $storage->getScanner($internalPath); - $scanner->scan($internalPath, Cache\Scanner::SCAN_SHALLOW); - $data = $cache->get($internalPath); - $this->unlockFile($relativePath, ILockingProvider::LOCK_SHARED); - } else if (!Cache\Scanner::isPartialFile($internalPath) && $watcher->needsUpdate($internalPath, $data)) { - $this->lockFile($relativePath, ILockingProvider::LOCK_SHARED); - $watcher->update($internalPath, $data); - $storage->getPropagator()->propagateChange($internalPath, time()); - $data = $cache->get($internalPath); - $this->unlockFile($relativePath, ILockingProvider::LOCK_SHARED); - } - } catch (LockedException $e) { - // if the file is locked we just use the old cache info - } - - return $data; - } - - /** - * get the filesystem info - * - * @param string $path - * @param boolean|string $includeMountPoints true to add mountpoint sizes, - * 'ext' to add only ext storage mount point sizes. Defaults to true. - * defaults to true - * @return \OC\Files\FileInfo|false False if file does not exist - */ - public function getFileInfo($path, $includeMountPoints = true) { - $this->assertPathLength($path); - if (!Filesystem::isValidPath($path)) { - return false; - } - if (Cache\Scanner::isPartialFile($path)) { - return $this->getPartFileInfo($path); - } - $relativePath = $path; - $path = Filesystem::normalizePath($this->fakeRoot . '/' . $path); - - $mount = Filesystem::getMountManager()->find($path); - $storage = $mount->getStorage(); - $internalPath = $mount->getInternalPath($path); - if ($storage) { - $data = $this->getCacheEntry($storage, $internalPath, $relativePath); - - if (!$data instanceof ICacheEntry) { - return false; - } - - if ($mount instanceof MoveableMount && $internalPath === '') { - $data['permissions'] |= \OCP\Constants::PERMISSION_DELETE; - } - - $owner = $this->getUserObjectForOwner($storage->getOwner($internalPath)); - $info = new FileInfo($path, $storage, $internalPath, $data, $mount, $owner); - - if ($data and isset($data['fileid'])) { - if ($includeMountPoints and $data['mimetype'] === 'httpd/unix-directory') { - //add the sizes of other mount points to the folder - $extOnly = ($includeMountPoints === 'ext'); - $mounts = Filesystem::getMountManager()->findIn($path); - foreach ($mounts as $mount) { - $subStorage = $mount->getStorage(); - if ($subStorage) { - // exclude shared storage ? - if ($extOnly && $subStorage instanceof \OC\Files\Storage\Shared) { - continue; - } - $subCache = $subStorage->getCache(''); - $rootEntry = $subCache->get(''); - $info->addSubEntry($rootEntry, $mount->getMountPoint()); - } - } - } - } - - return $info; - } - - return false; - } - - /** - * get the content of a directory - * - * @param string $directory path under datadirectory - * @param string $mimetype_filter limit returned content to this mimetype or mimepart - * @return FileInfo[] - */ - public function getDirectoryContent($directory, $mimetype_filter = '') { - $this->assertPathLength($directory); - if (!Filesystem::isValidPath($directory)) { - return []; - } - $path = $this->getAbsolutePath($directory); - $path = Filesystem::normalizePath($path); - $mount = $this->getMount($directory); - $storage = $mount->getStorage(); - $internalPath = $mount->getInternalPath($path); - if ($storage) { - $cache = $storage->getCache($internalPath); - $user = \OC_User::getUser(); - - $data = $this->getCacheEntry($storage, $internalPath, $directory); - - if (!$data instanceof ICacheEntry || !isset($data['fileid']) || !($data->getPermissions() && Constants::PERMISSION_READ)) { - return []; - } - - $folderId = $data['fileid']; - $contents = $cache->getFolderContentsById($folderId); //TODO: mimetype_filter - - $sharingDisabled = \OCP\Util::isSharingDisabledForUser(); - /** - * @var \OC\Files\FileInfo[] $files - */ - $files = array_map(function (ICacheEntry $content) use ($path, $storage, $mount, $sharingDisabled) { - if ($sharingDisabled) { - $content['permissions'] = $content['permissions'] & ~\OCP\Constants::PERMISSION_SHARE; - } - $owner = $this->getUserObjectForOwner($storage->getOwner($content['path'])); - return new FileInfo($path . '/' . $content['name'], $storage, $content['path'], $content, $mount, $owner); - }, $contents); - - //add a folder for any mountpoint in this directory and add the sizes of other mountpoints to the folders - $mounts = Filesystem::getMountManager()->findIn($path); - $dirLength = strlen($path); - foreach ($mounts as $mount) { - $mountPoint = $mount->getMountPoint(); - $subStorage = $mount->getStorage(); - if ($subStorage) { - $subCache = $subStorage->getCache(''); - - $rootEntry = $subCache->get(''); - if (!$rootEntry) { - $subScanner = $subStorage->getScanner(''); - try { - $subScanner->scanFile(''); - } catch (\OCP\Files\StorageNotAvailableException $e) { - continue; - } catch (\OCP\Files\StorageInvalidException $e) { - continue; - } catch (\Exception $e) { - // sometimes when the storage is not available it can be any exception - \OCP\Util::writeLog( - 'core', - 'Exception while scanning storage "' . $subStorage->getId() . '": ' . - get_class($e) . ': ' . $e->getMessage(), - \OCP\Util::ERROR - ); - continue; - } - $rootEntry = $subCache->get(''); - } - - if ($rootEntry && ($rootEntry->getPermissions() && Constants::PERMISSION_READ)) { - $relativePath = trim(substr($mountPoint, $dirLength), '/'); - if ($pos = strpos($relativePath, '/')) { - //mountpoint inside subfolder add size to the correct folder - $entryName = substr($relativePath, 0, $pos); - foreach ($files as &$entry) { - if ($entry->getName() === $entryName) { - $entry->addSubEntry($rootEntry, $mountPoint); - } - } - } else { //mountpoint in this folder, add an entry for it - $rootEntry['name'] = $relativePath; - $rootEntry['type'] = $rootEntry['mimetype'] === 'httpd/unix-directory' ? 'dir' : 'file'; - $permissions = $rootEntry['permissions']; - // do not allow renaming/deleting the mount point if they are not shared files/folders - // for shared files/folders we use the permissions given by the owner - if ($mount instanceof MoveableMount) { - $rootEntry['permissions'] = $permissions | \OCP\Constants::PERMISSION_UPDATE | \OCP\Constants::PERMISSION_DELETE; - } else { - $rootEntry['permissions'] = $permissions & (\OCP\Constants::PERMISSION_ALL - (\OCP\Constants::PERMISSION_UPDATE | \OCP\Constants::PERMISSION_DELETE)); - } - - //remove any existing entry with the same name - foreach ($files as $i => $file) { - if ($file['name'] === $rootEntry['name']) { - unset($files[$i]); - break; - } - } - $rootEntry['path'] = substr(Filesystem::normalizePath($path . '/' . $rootEntry['name']), strlen($user) + 2); // full path without /$user/ - - // if sharing was disabled for the user we remove the share permissions - if (\OCP\Util::isSharingDisabledForUser()) { - $rootEntry['permissions'] = $rootEntry['permissions'] & ~\OCP\Constants::PERMISSION_SHARE; - } - - $owner = $this->getUserObjectForOwner($subStorage->getOwner('')); - $files[] = new FileInfo($path . '/' . $rootEntry['name'], $subStorage, '', $rootEntry, $mount, $owner); - } - } - } - } - - if ($mimetype_filter) { - $files = array_filter($files, function (FileInfo $file) use ($mimetype_filter) { - if (strpos($mimetype_filter, '/')) { - return $file->getMimetype() === $mimetype_filter; - } else { - return $file->getMimePart() === $mimetype_filter; - } - }); - } - - return $files; - } else { - return []; - } - } - - /** - * change file metadata - * - * @param string $path - * @param array|\OCP\Files\FileInfo $data - * @return int - * - * returns the fileid of the updated file - */ - public function putFileInfo($path, $data) { - $this->assertPathLength($path); - if ($data instanceof FileInfo) { - $data = $data->getData(); - } - $path = Filesystem::normalizePath($this->fakeRoot . '/' . $path); - /** - * @var \OC\Files\Storage\Storage $storage - * @var string $internalPath - */ - list($storage, $internalPath) = Filesystem::resolvePath($path); - if ($storage) { - $cache = $storage->getCache($path); - - if (!$cache->inCache($internalPath)) { - $scanner = $storage->getScanner($internalPath); - $scanner->scan($internalPath, Cache\Scanner::SCAN_SHALLOW); - } - - return $cache->put($internalPath, $data); - } else { - return -1; - } - } - - /** - * search for files with the name matching $query - * - * @param string $query - * @return FileInfo[] - */ - public function search($query) { - return $this->searchCommon('search', array('%' . $query . '%')); - } - - /** - * search for files with the name matching $query - * - * @param string $query - * @return FileInfo[] - */ - public function searchRaw($query) { - return $this->searchCommon('search', array($query)); - } - - /** - * search for files by mimetype - * - * @param string $mimetype - * @return FileInfo[] - */ - public function searchByMime($mimetype) { - return $this->searchCommon('searchByMime', array($mimetype)); - } - - /** - * search for files by tag - * - * @param string|int $tag name or tag id - * @param string $userId owner of the tags - * @return FileInfo[] - */ - public function searchByTag($tag, $userId) { - return $this->searchCommon('searchByTag', array($tag, $userId)); - } - - /** - * @param string $method cache method - * @param array $args - * @return FileInfo[] - */ - private function searchCommon($method, $args) { - $files = array(); - $rootLength = strlen($this->fakeRoot); - - $mount = $this->getMount(''); - $mountPoint = $mount->getMountPoint(); - $storage = $mount->getStorage(); - if ($storage) { - $cache = $storage->getCache(''); - - $results = call_user_func_array(array($cache, $method), $args); - foreach ($results as $result) { - if (substr($mountPoint . $result['path'], 0, $rootLength + 1) === $this->fakeRoot . '/') { - $internalPath = $result['path']; - $path = $mountPoint . $result['path']; - $result['path'] = substr($mountPoint . $result['path'], $rootLength); - $owner = \OC::$server->getUserManager()->get($storage->getOwner($internalPath)); - $files[] = new FileInfo($path, $storage, $internalPath, $result, $mount, $owner); - } - } - - $mounts = Filesystem::getMountManager()->findIn($this->fakeRoot); - foreach ($mounts as $mount) { - $mountPoint = $mount->getMountPoint(); - $storage = $mount->getStorage(); - if ($storage) { - $cache = $storage->getCache(''); - - $relativeMountPoint = substr($mountPoint, $rootLength); - $results = call_user_func_array(array($cache, $method), $args); - if ($results) { - foreach ($results as $result) { - $internalPath = $result['path']; - $result['path'] = rtrim($relativeMountPoint . $result['path'], '/'); - $path = rtrim($mountPoint . $internalPath, '/'); - $owner = \OC::$server->getUserManager()->get($storage->getOwner($internalPath)); - $files[] = new FileInfo($path, $storage, $internalPath, $result, $mount, $owner); - } - } - } - } - } - return $files; - } - - /** - * Get the owner for a file or folder - * - * @param string $path - * @return string the user id of the owner - * @throws NotFoundException - */ - public function getOwner($path) { - $info = $this->getFileInfo($path); - if (!$info) { - throw new NotFoundException($path . ' not found while trying to get owner'); - } - return $info->getOwner()->getUID(); - } - - /** - * get the ETag for a file or folder - * - * @param string $path - * @return string - */ - public function getETag($path) { - /** - * @var Storage\Storage $storage - * @var string $internalPath - */ - list($storage, $internalPath) = $this->resolvePath($path); - if ($storage) { - return $storage->getETag($internalPath); - } else { - return null; - } - } - - /** - * Get the path of a file by id, relative to the view - * - * Note that the resulting path is not guarantied to be unique for the id, multiple paths can point to the same file - * - * @param int $id - * @throws NotFoundException - * @return string - */ - public function getPath($id) { - $id = (int)$id; - $manager = Filesystem::getMountManager(); - $mounts = $manager->findIn($this->fakeRoot); - $mounts[] = $manager->find($this->fakeRoot); - // reverse the array so we start with the storage this view is in - // which is the most likely to contain the file we're looking for - $mounts = array_reverse($mounts); - foreach ($mounts as $mount) { - /** - * @var \OC\Files\Mount\MountPoint $mount - */ - if ($mount->getStorage()) { - $cache = $mount->getStorage()->getCache(); - $internalPath = $cache->getPathById($id); - if (is_string($internalPath)) { - $fullPath = $mount->getMountPoint() . $internalPath; - if (!is_null($path = $this->getRelativePath($fullPath))) { - return $path; - } - } - } - } - throw new NotFoundException(sprintf('File with id "%s" has not been found.', $id)); - } - - /** - * @param string $path - * @throws InvalidPathException - */ - private function assertPathLength($path) { - $maxLen = min(PHP_MAXPATHLEN, 4000); - // Check for the string length - performed using isset() instead of strlen() - // because isset() is about 5x-40x faster. - if (isset($path[$maxLen])) { - $pathLen = strlen($path); - throw new \OCP\Files\InvalidPathException("Path length($pathLen) exceeds max path length($maxLen): $path"); - } - } - - /** - * check if it is allowed to move a mount point to a given target. - * It is not allowed to move a mount point into a different mount point or - * into an already shared folder - * - * @param string $target path - * @return boolean - */ - private function isTargetAllowed($target) { - - list($targetStorage, $targetInternalPath) = \OC\Files\Filesystem::resolvePath($target); - if (!$targetStorage->instanceOfStorage('\OCP\Files\IHomeStorage')) { - \OCP\Util::writeLog('files', - 'It is not allowed to move one mount point into another one', - \OCP\Util::DEBUG); - return false; - } - - // note: cannot use the view because the target is already locked - $fileId = (int)$targetStorage->getCache()->getId($targetInternalPath); - if ($fileId === -1) { - // target might not exist, need to check parent instead - $fileId = (int)$targetStorage->getCache()->getId(dirname($targetInternalPath)); - } - - // check if any of the parents were shared by the current owner (include collections) - $shares = \OCP\Share::getItemShared( - 'folder', - $fileId, - \OCP\Share::FORMAT_NONE, - null, - true - ); - - if (count($shares) > 0) { - \OCP\Util::writeLog('files', - 'It is not allowed to move one mount point into a shared folder', - \OCP\Util::DEBUG); - return false; - } - - return true; - } - - /** - * Get a fileinfo object for files that are ignored in the cache (part files) - * - * @param string $path - * @return \OCP\Files\FileInfo - */ - private function getPartFileInfo($path) { - $mount = $this->getMount($path); - $storage = $mount->getStorage(); - $internalPath = $mount->getInternalPath($this->getAbsolutePath($path)); - $owner = \OC::$server->getUserManager()->get($storage->getOwner($internalPath)); - return new FileInfo( - $this->getAbsolutePath($path), - $storage, - $internalPath, - [ - 'fileid' => null, - 'mimetype' => $storage->getMimeType($internalPath), - 'name' => basename($path), - 'etag' => null, - 'size' => $storage->filesize($internalPath), - 'mtime' => $storage->filemtime($internalPath), - 'encrypted' => false, - 'permissions' => \OCP\Constants::PERMISSION_ALL - ], - $mount, - $owner - ); - } - - /** - * @param string $path - * @param string $fileName - * @throws InvalidPathException - */ - public function verifyPath($path, $fileName) { - - $l10n = \OC::$server->getL10N('lib'); - - // verify empty and dot files - $trimmed = trim($fileName); - if ($trimmed === '') { - throw new InvalidPathException($l10n->t('Empty filename is not allowed')); - } - if (\OC\Files\Filesystem::isIgnoredDir($trimmed)) { - throw new InvalidPathException($l10n->t('Dot files are not allowed')); - } - - // verify database - e.g. mysql only 3-byte chars - if (preg_match('%(?: - \xF0[\x90-\xBF][\x80-\xBF]{2} # planes 1-3 - | [\xF1-\xF3][\x80-\xBF]{3} # planes 4-15 - | \xF4[\x80-\x8F][\x80-\xBF]{2} # plane 16 -)%xs', $fileName)) { - throw new InvalidPathException($l10n->t('4-byte characters are not supported in file names')); - } - - try { - /** @type \OCP\Files\Storage $storage */ - list($storage, $internalPath) = $this->resolvePath($path); - $storage->verifyPath($internalPath, $fileName); - } catch (ReservedWordException $ex) { - throw new InvalidPathException($l10n->t('File name is a reserved word')); - } catch (InvalidCharacterInPathException $ex) { - throw new InvalidPathException($l10n->t('File name contains at least one invalid character')); - } catch (FileNameTooLongException $ex) { - throw new InvalidPathException($l10n->t('File name is too long')); - } - } - - /** - * get all parent folders of $path - * - * @param string $path - * @return string[] - */ - private function getParents($path) { - $path = trim($path, '/'); - if (!$path) { - return []; - } - - $parts = explode('/', $path); - - // remove the single file - array_pop($parts); - $result = array('/'); - $resultPath = ''; - foreach ($parts as $part) { - if ($part) { - $resultPath .= '/' . $part; - $result[] = $resultPath; - } - } - return $result; - } - - /** - * Returns the mount point for which to lock - * - * @param string $absolutePath absolute path - * @param bool $useParentMount true to return parent mount instead of whatever - * is mounted directly on the given path, false otherwise - * @return \OC\Files\Mount\MountPoint mount point for which to apply locks - */ - private function getMountForLock($absolutePath, $useParentMount = false) { - $results = []; - $mount = Filesystem::getMountManager()->find($absolutePath); - if (!$mount) { - return $results; - } - - if ($useParentMount) { - // find out if something is mounted directly on the path - $internalPath = $mount->getInternalPath($absolutePath); - if ($internalPath === '') { - // resolve the parent mount instead - $mount = Filesystem::getMountManager()->find(dirname($absolutePath)); - } - } - - return $mount; - } - - /** - * Lock the given path - * - * @param string $path the path of the file to lock, relative to the view - * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE - * @param bool $lockMountPoint true to lock the mount point, false to lock the attached mount/storage - * - * @return bool False if the path is excluded from locking, true otherwise - * @throws \OCP\Lock\LockedException if the path is already locked - */ - private function lockPath($path, $type, $lockMountPoint = false) { - $absolutePath = $this->getAbsolutePath($path); - $absolutePath = Filesystem::normalizePath($absolutePath); - if (!$this->shouldLockFile($absolutePath)) { - return false; - } - - $mount = $this->getMountForLock($absolutePath, $lockMountPoint); - if ($mount) { - try { - $storage = $mount->getStorage(); - if ($storage->instanceOfStorage('\OCP\Files\Storage\ILockingStorage')) { - $storage->acquireLock( - $mount->getInternalPath($absolutePath), - $type, - $this->lockingProvider - ); - } - } catch (\OCP\Lock\LockedException $e) { - // rethrow with the a human-readable path - throw new \OCP\Lock\LockedException( - $this->getPathRelativeToFiles($absolutePath), - $e - ); - } - } - - return true; - } - - /** - * Change the lock type - * - * @param string $path the path of the file to lock, relative to the view - * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE - * @param bool $lockMountPoint true to lock the mount point, false to lock the attached mount/storage - * - * @return bool False if the path is excluded from locking, true otherwise - * @throws \OCP\Lock\LockedException if the path is already locked - */ - public function changeLock($path, $type, $lockMountPoint = false) { - $path = Filesystem::normalizePath($path); - $absolutePath = $this->getAbsolutePath($path); - $absolutePath = Filesystem::normalizePath($absolutePath); - if (!$this->shouldLockFile($absolutePath)) { - return false; - } - - $mount = $this->getMountForLock($absolutePath, $lockMountPoint); - if ($mount) { - try { - $storage = $mount->getStorage(); - if ($storage->instanceOfStorage('\OCP\Files\Storage\ILockingStorage')) { - $storage->changeLock( - $mount->getInternalPath($absolutePath), - $type, - $this->lockingProvider - ); - } - } catch (\OCP\Lock\LockedException $e) { - // rethrow with the a human-readable path - throw new \OCP\Lock\LockedException( - $this->getPathRelativeToFiles($absolutePath), - $e - ); - } - } - - return true; - } - - /** - * Unlock the given path - * - * @param string $path the path of the file to unlock, relative to the view - * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE - * @param bool $lockMountPoint true to lock the mount point, false to lock the attached mount/storage - * - * @return bool False if the path is excluded from locking, true otherwise - */ - private function unlockPath($path, $type, $lockMountPoint = false) { - $absolutePath = $this->getAbsolutePath($path); - $absolutePath = Filesystem::normalizePath($absolutePath); - if (!$this->shouldLockFile($absolutePath)) { - return false; - } - - $mount = $this->getMountForLock($absolutePath, $lockMountPoint); - if ($mount) { - $storage = $mount->getStorage(); - if ($storage->instanceOfStorage('\OCP\Files\Storage\ILockingStorage')) { - $storage->releaseLock( - $mount->getInternalPath($absolutePath), - $type, - $this->lockingProvider - ); - } - } - - return true; - } - - /** - * Lock a path and all its parents up to the root of the view - * - * @param string $path the path of the file to lock relative to the view - * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE - * @param bool $lockMountPoint true to lock the mount point, false to lock the attached mount/storage - * - * @return bool False if the path is excluded from locking, true otherwise - */ - public function lockFile($path, $type, $lockMountPoint = false) { - $absolutePath = $this->getAbsolutePath($path); - $absolutePath = Filesystem::normalizePath($absolutePath); - if (!$this->shouldLockFile($absolutePath)) { - return false; - } - - $this->lockPath($path, $type, $lockMountPoint); - - $parents = $this->getParents($path); - foreach ($parents as $parent) { - $this->lockPath($parent, ILockingProvider::LOCK_SHARED); - } - - return true; - } - - /** - * Unlock a path and all its parents up to the root of the view - * - * @param string $path the path of the file to lock relative to the view - * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE - * @param bool $lockMountPoint true to lock the mount point, false to lock the attached mount/storage - * - * @return bool False if the path is excluded from locking, true otherwise - */ - public function unlockFile($path, $type, $lockMountPoint = false) { - $absolutePath = $this->getAbsolutePath($path); - $absolutePath = Filesystem::normalizePath($absolutePath); - if (!$this->shouldLockFile($absolutePath)) { - return false; - } - - $this->unlockPath($path, $type, $lockMountPoint); - - $parents = $this->getParents($path); - foreach ($parents as $parent) { - $this->unlockPath($parent, ILockingProvider::LOCK_SHARED); - } - - return true; - } - - /** - * Only lock files in data/user/files/ - * - * @param string $path Absolute path to the file/folder we try to (un)lock - * @return bool - */ - protected function shouldLockFile($path) { - $path = Filesystem::normalizePath($path); - - $pathSegments = explode('/', $path); - if (isset($pathSegments[2])) { - // E.g.: /username/files/path-to-file - return ($pathSegments[2] === 'files') && (count($pathSegments) > 3); - } - - return true; - } - - /** - * Shortens the given absolute path to be relative to - * "$user/files". - * - * @param string $absolutePath absolute path which is under "files" - * - * @return string path relative to "files" with trimmed slashes or null - * if the path was NOT relative to files - * - * @throws \InvalidArgumentException if the given path was not under "files" - * @since 8.1.0 - */ - public function getPathRelativeToFiles($absolutePath) { - $path = Filesystem::normalizePath($absolutePath); - $parts = explode('/', trim($path, '/'), 3); - // "$user", "files", "path/to/dir" - if (!isset($parts[1]) || $parts[1] !== 'files') { - throw new \InvalidArgumentException('$absolutePath must be relative to "files"'); - } - if (isset($parts[2])) { - return $parts[2]; - } - return ''; - } - - /** - * @param string $filename - * @return array - * @throws \OC\User\NoUserException - * @throws NotFoundException - */ - public function getUidAndFilename($filename) { - $info = $this->getFileInfo($filename); - if (!$info instanceof \OCP\Files\FileInfo) { - throw new NotFoundException($this->getAbsolutePath($filename) . ' not found'); - } - $uid = $info->getOwner()->getUID(); - if ($uid != \OCP\User::getUser()) { - Filesystem::initMountPoints($uid); - $ownerView = new View('/' . $uid . '/files'); - try { - $filename = $ownerView->getPath($info['fileid']); - } catch (NotFoundException $e) { - throw new NotFoundException('File with id ' . $info['fileid'] . ' not found for user ' . $uid); - } - } - return [$uid, $filename]; - } -} -- cgit v1.2.3