From 4576891f10aa41cc146dc6ed421384269eca283a Mon Sep 17 00:00:00 2001 From: Joas Schilling Date: Fri, 13 May 2016 11:46:36 +0200 Subject: Move Lib\Storage to PSR-4 --- apps/files_external/lib/Lib/Storage/AmazonS3.php | 634 ++++++++++++++++++ apps/files_external/lib/Lib/Storage/Dropbox.php | 352 ++++++++++ apps/files_external/lib/Lib/Storage/FTP.php | 155 +++++ apps/files_external/lib/Lib/Storage/Google.php | 710 +++++++++++++++++++++ apps/files_external/lib/Lib/Storage/OwnCloud.php | 74 +++ apps/files_external/lib/Lib/Storage/SFTP.php | 467 ++++++++++++++ apps/files_external/lib/Lib/Storage/SMB.php | 396 ++++++++++++ .../lib/Lib/Storage/StreamWrapper.php | 128 ++++ apps/files_external/lib/Lib/Storage/Swift.php | 599 +++++++++++++++++ apps/files_external/lib/storage/amazons3.php | 634 ------------------ apps/files_external/lib/storage/dropbox.php | 352 ---------- apps/files_external/lib/storage/ftp.php | 155 ----- apps/files_external/lib/storage/google.php | 710 --------------------- apps/files_external/lib/storage/owncloud.php | 74 --- apps/files_external/lib/storage/sftp.php | 467 -------------- apps/files_external/lib/storage/smb.php | 396 ------------ apps/files_external/lib/storage/streamwrapper.php | 128 ---- apps/files_external/lib/storage/swift.php | 599 ----------------- 18 files changed, 3515 insertions(+), 3515 deletions(-) create mode 100644 apps/files_external/lib/Lib/Storage/AmazonS3.php create mode 100644 apps/files_external/lib/Lib/Storage/Dropbox.php create mode 100644 apps/files_external/lib/Lib/Storage/FTP.php create mode 100644 apps/files_external/lib/Lib/Storage/Google.php create mode 100644 apps/files_external/lib/Lib/Storage/OwnCloud.php create mode 100644 apps/files_external/lib/Lib/Storage/SFTP.php create mode 100644 apps/files_external/lib/Lib/Storage/SMB.php create mode 100644 apps/files_external/lib/Lib/Storage/StreamWrapper.php create mode 100644 apps/files_external/lib/Lib/Storage/Swift.php delete mode 100644 apps/files_external/lib/storage/amazons3.php delete mode 100644 apps/files_external/lib/storage/dropbox.php delete mode 100644 apps/files_external/lib/storage/ftp.php delete mode 100644 apps/files_external/lib/storage/google.php delete mode 100644 apps/files_external/lib/storage/owncloud.php delete mode 100644 apps/files_external/lib/storage/sftp.php delete mode 100644 apps/files_external/lib/storage/smb.php delete mode 100644 apps/files_external/lib/storage/streamwrapper.php delete mode 100644 apps/files_external/lib/storage/swift.php (limited to 'apps/files_external/lib') diff --git a/apps/files_external/lib/Lib/Storage/AmazonS3.php b/apps/files_external/lib/Lib/Storage/AmazonS3.php new file mode 100644 index 00000000000..42df1deffb0 --- /dev/null +++ b/apps/files_external/lib/Lib/Storage/AmazonS3.php @@ -0,0 +1,634 @@ + + * @author Arthur Schiwon + * @author Bart Visscher + * @author Christian Berendt + * @author Christopher T. Johnson + * @author Johan Björk + * @author Jörn Friedrich Dreyer + * @author Martin Mattel + * @author Michael Gapczynski + * @author Morris Jobke + * @author Philipp Kapfer + * @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 OCA\Files_External\Lib\Storage; + +set_include_path(get_include_path() . PATH_SEPARATOR . + \OC_App::getAppPath('files_external') . '/3rdparty/aws-sdk-php'); +require 'aws-autoloader.php'; + +use Aws\S3\S3Client; +use Aws\S3\Exception\S3Exception; +use Icewind\Streams\IteratorDirectory; + +class AmazonS3 extends \OC\Files\Storage\Common { + + /** + * @var \Aws\S3\S3Client + */ + private $connection; + /** + * @var string + */ + private $bucket; + /** + * @var array + */ + private static $tmpFiles = array(); + /** + * @var array + */ + private $params; + /** + * @var bool + */ + private $test = false; + /** + * @var int + */ + private $timeout = 15; + /** + * @var int in seconds + */ + private $rescanDelay = 10; + + /** + * @param string $path + * @return string correctly encoded path + */ + private function normalizePath($path) { + $path = trim($path, '/'); + + if (!$path) { + $path = '.'; + } + + return $path; + } + + /** + * when running the tests wait to let the buckets catch up + */ + private function testTimeout() { + if ($this->test) { + sleep($this->timeout); + } + } + + private function isRoot($path) { + return $path === '.'; + } + + private function cleanKey($path) { + if ($this->isRoot($path)) { + return '/'; + } + return $path; + } + + public function __construct($params) { + if (empty($params['key']) || empty($params['secret']) || empty($params['bucket'])) { + throw new \Exception("Access Key, Secret and Bucket have to be configured."); + } + + $this->id = 'amazon::' . $params['bucket']; + $this->updateLegacyId($params); + + $this->bucket = $params['bucket']; + $this->test = isset($params['test']); + $this->timeout = (!isset($params['timeout'])) ? 15 : $params['timeout']; + $this->rescanDelay = (!isset($params['rescanDelay'])) ? 10 : $params['rescanDelay']; + $params['region'] = empty($params['region']) ? 'eu-west-1' : $params['region']; + $params['hostname'] = empty($params['hostname']) ? 's3.amazonaws.com' : $params['hostname']; + if (!isset($params['port']) || $params['port'] === '') { + $params['port'] = ($params['use_ssl'] === false) ? 80 : 443; + } + $this->params = $params; + } + + /** + * Updates old storage ids (v0.2.1 and older) that are based on key and secret to new ones based on the bucket name. + * TODO Do this in an update.php. requires iterating over all users and loading the mount.json from their home + * + * @param array $params + */ + public function updateLegacyId (array $params) { + $oldId = 'amazon::' . $params['key'] . md5($params['secret']); + + // find by old id or bucket + $stmt = \OC::$server->getDatabaseConnection()->prepare( + 'SELECT `numeric_id`, `id` FROM `*PREFIX*storages` WHERE `id` IN (?, ?)' + ); + $stmt->execute(array($oldId, $this->id)); + while ($row = $stmt->fetch()) { + $storages[$row['id']] = $row['numeric_id']; + } + + if (isset($storages[$this->id]) && isset($storages[$oldId])) { + // if both ids exist, delete the old storage and corresponding filecache entries + \OC\Files\Cache\Storage::remove($oldId); + } else if (isset($storages[$oldId])) { + // if only the old id exists do an update + $stmt = \OC::$server->getDatabaseConnection()->prepare( + 'UPDATE `*PREFIX*storages` SET `id` = ? WHERE `id` = ?' + ); + $stmt->execute(array($this->id, $oldId)); + } + // only the bucket based id may exist, do nothing + } + + /** + * Remove a file or folder + * + * @param string $path + * @return bool + */ + protected function remove($path) { + // remember fileType to reduce http calls + $fileType = $this->filetype($path); + if ($fileType === 'dir') { + return $this->rmdir($path); + } else if ($fileType === 'file') { + return $this->unlink($path); + } else { + return false; + } + } + + public function mkdir($path) { + $path = $this->normalizePath($path); + + if ($this->is_dir($path)) { + return false; + } + + try { + $this->getConnection()->putObject(array( + 'Bucket' => $this->bucket, + 'Key' => $path . '/', + 'Body' => '', + 'ContentType' => 'httpd/unix-directory' + )); + $this->testTimeout(); + } catch (S3Exception $e) { + \OCP\Util::logException('files_external', $e); + return false; + } + + return true; + } + + public function file_exists($path) { + return $this->filetype($path) !== false; + } + + + public function rmdir($path) { + $path = $this->normalizePath($path); + + if ($this->isRoot($path)) { + return $this->clearBucket(); + } + + if (!$this->file_exists($path)) { + return false; + } + + return $this->batchDelete($path); + } + + protected function clearBucket() { + try { + $this->getConnection()->clearBucket($this->bucket); + return true; + // clearBucket() is not working with Ceph, so if it fails we try the slower approach + } catch (\Exception $e) { + return $this->batchDelete(); + } + return false; + } + + private function batchDelete ($path = null) { + $params = array( + 'Bucket' => $this->bucket + ); + if ($path !== null) { + $params['Prefix'] = $path . '/'; + } + try { + // Since there are no real directories on S3, we need + // to delete all objects prefixed with the path. + do { + // instead of the iterator, manually loop over the list ... + $objects = $this->getConnection()->listObjects($params); + // ... so we can delete the files in batches + $this->getConnection()->deleteObjects(array( + 'Bucket' => $this->bucket, + 'Objects' => $objects['Contents'] + )); + $this->testTimeout(); + // we reached the end when the list is no longer truncated + } while ($objects['IsTruncated']); + } catch (S3Exception $e) { + \OCP\Util::logException('files_external', $e); + return false; + } + return true; + } + + public function opendir($path) { + $path = $this->normalizePath($path); + + if ($this->isRoot($path)) { + $path = ''; + } else { + $path .= '/'; + } + + try { + $files = array(); + $result = $this->getConnection()->getIterator('ListObjects', array( + 'Bucket' => $this->bucket, + 'Delimiter' => '/', + 'Prefix' => $path + ), array('return_prefixes' => true)); + + foreach ($result as $object) { + if (isset($object['Key']) && $object['Key'] === $path) { + // it's the directory itself, skip + continue; + } + $file = basename( + isset($object['Key']) ? $object['Key'] : $object['Prefix'] + ); + $files[] = $file; + } + + return IteratorDirectory::wrap($files); + } catch (S3Exception $e) { + \OCP\Util::logException('files_external', $e); + return false; + } + } + + public function stat($path) { + $path = $this->normalizePath($path); + + try { + $stat = array(); + if ($this->is_dir($path)) { + //folders don't really exist + $stat['size'] = -1; //unknown + $stat['mtime'] = time() - $this->rescanDelay * 1000; + } else { + $result = $this->getConnection()->headObject(array( + 'Bucket' => $this->bucket, + 'Key' => $path + )); + + $stat['size'] = $result['ContentLength'] ? $result['ContentLength'] : 0; + if ($result['Metadata']['lastmodified']) { + $stat['mtime'] = strtotime($result['Metadata']['lastmodified']); + } else { + $stat['mtime'] = strtotime($result['LastModified']); + } + } + $stat['atime'] = time(); + + return $stat; + } catch(S3Exception $e) { + \OCP\Util::logException('files_external', $e); + return false; + } + } + + public function filetype($path) { + $path = $this->normalizePath($path); + + if ($this->isRoot($path)) { + return 'dir'; + } + + try { + if ($this->getConnection()->doesObjectExist($this->bucket, $path)) { + return 'file'; + } + if ($this->getConnection()->doesObjectExist($this->bucket, $path.'/')) { + return 'dir'; + } + } catch (S3Exception $e) { + \OCP\Util::logException('files_external', $e); + return false; + } + + return false; + } + + public function unlink($path) { + $path = $this->normalizePath($path); + + if ($this->is_dir($path)) { + return $this->rmdir($path); + } + + try { + $this->getConnection()->deleteObject(array( + 'Bucket' => $this->bucket, + 'Key' => $path + )); + $this->testTimeout(); + } catch (S3Exception $e) { + \OCP\Util::logException('files_external', $e); + return false; + } + + return true; + } + + public function fopen($path, $mode) { + $path = $this->normalizePath($path); + + switch ($mode) { + case 'r': + case 'rb': + $tmpFile = \OCP\Files::tmpFile(); + self::$tmpFiles[$tmpFile] = $path; + + try { + $this->getConnection()->getObject(array( + 'Bucket' => $this->bucket, + 'Key' => $path, + 'SaveAs' => $tmpFile + )); + } catch (S3Exception $e) { + \OCP\Util::logException('files_external', $e); + return false; + } + + return fopen($tmpFile, 'r'); + 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 = \OCP\Files::tmpFile($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 touch($path, $mtime = null) { + $path = $this->normalizePath($path); + + $metadata = array(); + if (is_null($mtime)) { + $mtime = time(); + } + $metadata = [ + 'lastmodified' => gmdate(\Aws\Common\Enum\DateFormat::RFC1123, $mtime) + ]; + + $fileType = $this->filetype($path); + try { + if ($fileType !== false) { + if ($fileType === 'dir' && ! $this->isRoot($path)) { + $path .= '/'; + } + $this->getConnection()->copyObject([ + 'Bucket' => $this->bucket, + 'Key' => $this->cleanKey($path), + 'Metadata' => $metadata, + 'CopySource' => $this->bucket . '/' . $path, + 'MetadataDirective' => 'REPLACE', + ]); + $this->testTimeout(); + } else { + $mimeType = \OC::$server->getMimeTypeDetector()->detectPath($path); + $this->getConnection()->putObject([ + 'Bucket' => $this->bucket, + 'Key' => $this->cleanKey($path), + 'Metadata' => $metadata, + 'Body' => '', + 'ContentType' => $mimeType, + 'MetadataDirective' => 'REPLACE', + ]); + $this->testTimeout(); + } + } catch (S3Exception $e) { + \OCP\Util::logException('files_external', $e); + return false; + } + + return true; + } + + public function copy($path1, $path2) { + $path1 = $this->normalizePath($path1); + $path2 = $this->normalizePath($path2); + + if ($this->is_file($path1)) { + try { + $this->getConnection()->copyObject(array( + 'Bucket' => $this->bucket, + 'Key' => $this->cleanKey($path2), + 'CopySource' => S3Client::encodeKey($this->bucket . '/' . $path1) + )); + $this->testTimeout(); + } catch (S3Exception $e) { + \OCP\Util::logException('files_external', $e); + return false; + } + } else { + $this->remove($path2); + + try { + $this->getConnection()->copyObject(array( + 'Bucket' => $this->bucket, + 'Key' => $path2 . '/', + 'CopySource' => S3Client::encodeKey($this->bucket . '/' . $path1 . '/') + )); + $this->testTimeout(); + } catch (S3Exception $e) { + \OCP\Util::logException('files_external', $e); + return false; + } + + $dh = $this->opendir($path1); + if (is_resource($dh)) { + while (($file = readdir($dh)) !== false) { + if (\OC\Files\Filesystem::isIgnoredDir($file)) { + continue; + } + + $source = $path1 . '/' . $file; + $target = $path2 . '/' . $file; + $this->copy($source, $target); + } + } + } + + return true; + } + + public function rename($path1, $path2) { + $path1 = $this->normalizePath($path1); + $path2 = $this->normalizePath($path2); + + if ($this->is_file($path1)) { + + if ($this->copy($path1, $path2) === false) { + return false; + } + + if ($this->unlink($path1) === false) { + $this->unlink($path2); + return false; + } + } else { + + if ($this->copy($path1, $path2) === false) { + return false; + } + + if ($this->rmdir($path1) === false) { + $this->rmdir($path2); + return false; + } + } + + return true; + } + + public function test() { + $test = $this->getConnection()->getBucketAcl(array( + 'Bucket' => $this->bucket, + )); + if (isset($test) && !is_null($test->getPath('Owner/ID'))) { + return true; + } + return false; + } + + public function getId() { + return $this->id; + } + + /** + * Returns the connection + * + * @return S3Client connected client + * @throws \Exception if connection could not be made + */ + public function getConnection() { + if (!is_null($this->connection)) { + return $this->connection; + } + + $scheme = ($this->params['use_ssl'] === false) ? 'http' : 'https'; + $base_url = $scheme . '://' . $this->params['hostname'] . ':' . $this->params['port'] . '/'; + + $this->connection = S3Client::factory(array( + 'key' => $this->params['key'], + 'secret' => $this->params['secret'], + 'base_url' => $base_url, + 'region' => $this->params['region'], + S3Client::COMMAND_PARAMS => [ + 'PathStyle' => $this->params['use_path_style'], + ], + )); + + if (!$this->connection->isValidBucketName($this->bucket)) { + throw new \Exception("The configured bucket name is invalid."); + } + + if (!$this->connection->doesBucketExist($this->bucket)) { + try { + $this->connection->createBucket(array( + 'Bucket' => $this->bucket + )); + $this->connection->waitUntilBucketExists(array( + 'Bucket' => $this->bucket, + 'waiter.interval' => 1, + 'waiter.max_attempts' => 15 + )); + $this->testTimeout(); + } catch (S3Exception $e) { + \OCP\Util::logException('files_external', $e); + throw new \Exception('Creation of bucket failed. '.$e->getMessage()); + } + } + + return $this->connection; + } + + public function writeBack($tmpFile) { + if (!isset(self::$tmpFiles[$tmpFile])) { + return false; + } + + try { + $this->getConnection()->putObject(array( + 'Bucket' => $this->bucket, + 'Key' => $this->cleanKey(self::$tmpFiles[$tmpFile]), + 'SourceFile' => $tmpFile, + 'ContentType' => \OC::$server->getMimeTypeDetector()->detect($tmpFile), + 'ContentLength' => filesize($tmpFile) + )); + $this->testTimeout(); + + unlink($tmpFile); + } catch (S3Exception $e) { + \OCP\Util::logException('files_external', $e); + return false; + } + } + + /** + * check if curl is installed + */ + public static function checkDependencies() { + return true; + } + +} diff --git a/apps/files_external/lib/Lib/Storage/Dropbox.php b/apps/files_external/lib/Lib/Storage/Dropbox.php new file mode 100644 index 00000000000..55ae7146572 --- /dev/null +++ b/apps/files_external/lib/Lib/Storage/Dropbox.php @@ -0,0 +1,352 @@ + + * @author Michael Gapczynski + * @author Morris Jobke + * @author Philipp Kapfer + * @author Robin Appelman + * @author Robin McCorkell + * @author Sascha Schmidt + * @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 OCA\Files_External\Lib\Storage; + +use GuzzleHttp\Exception\RequestException; +use Icewind\Streams\IteratorDirectory; +use Icewind\Streams\RetryWrapper; + +require_once __DIR__ . '/../../3rdparty/Dropbox/autoload.php'; + +class Dropbox extends \OC\Files\Storage\Common { + + private $dropbox; + private $root; + private $id; + private $metaData = array(); + private $oauth; + + private static $tempFiles = array(); + + public function __construct($params) { + if (isset($params['configured']) && $params['configured'] == 'true' + && isset($params['app_key']) + && isset($params['app_secret']) + && isset($params['token']) + && isset($params['token_secret']) + ) { + $this->root = isset($params['root']) ? $params['root'] : ''; + $this->id = 'dropbox::'.$params['app_key'] . $params['token']. '/' . $this->root; + $this->oauth = new \Dropbox_OAuth_Curl($params['app_key'], $params['app_secret']); + $this->oauth->setToken($params['token'], $params['token_secret']); + // note: Dropbox_API connection is lazy + $this->dropbox = new \Dropbox_API($this->oauth, 'auto'); + } else { + throw new \Exception('Creating Dropbox storage failed'); + } + } + + /** + * @param string $path + */ + private function deleteMetaData($path) { + $path = ltrim($this->root.$path, '/'); + if (isset($this->metaData[$path])) { + unset($this->metaData[$path]); + return true; + } + return false; + } + + private function setMetaData($path, $metaData) { + $this->metaData[ltrim($path, '/')] = $metaData; + } + + /** + * Returns the path's metadata + * @param string $path path for which to return the metadata + * @param bool $list if true, also return the directory's contents + * @return mixed directory contents if $list is true, file metadata if $list is + * false, null if the file doesn't exist or "false" if the operation failed + */ + private function getDropBoxMetaData($path, $list = false) { + $path = ltrim($this->root.$path, '/'); + if ( ! $list && isset($this->metaData[$path])) { + return $this->metaData[$path]; + } else { + if ($list) { + try { + $response = $this->dropbox->getMetaData($path); + } catch (\Exception $exception) { + \OCP\Util::writeLog('files_external', $exception->getMessage(), \OCP\Util::ERROR); + return false; + } + $contents = array(); + if ($response && isset($response['contents'])) { + // Cache folder's contents + foreach ($response['contents'] as $file) { + if (!isset($file['is_deleted']) || !$file['is_deleted']) { + $this->setMetaData($path.'/'.basename($file['path']), $file); + $contents[] = $file; + } + } + unset($response['contents']); + } + if (!isset($response['is_deleted']) || !$response['is_deleted']) { + $this->setMetaData($path, $response); + } + // Return contents of folder only + return $contents; + } else { + try { + $requestPath = $path; + if ($path === '.') { + $requestPath = ''; + } + + $response = $this->dropbox->getMetaData($requestPath, 'false'); + if (!isset($response['is_deleted']) || !$response['is_deleted']) { + $this->setMetaData($path, $response); + return $response; + } + return null; + } catch (\Exception $exception) { + if ($exception instanceof \Dropbox_Exception_NotFound) { + // don't log, might be a file_exist check + return false; + } + \OCP\Util::writeLog('files_external', $exception->getMessage(), \OCP\Util::ERROR); + return false; + } + } + } + } + + public function getId(){ + return $this->id; + } + + public function mkdir($path) { + $path = $this->root.$path; + try { + $this->dropbox->createFolder($path); + return true; + } catch (\Exception $exception) { + \OCP\Util::writeLog('files_external', $exception->getMessage(), \OCP\Util::ERROR); + return false; + } + } + + public function rmdir($path) { + return $this->unlink($path); + } + + public function opendir($path) { + $contents = $this->getDropBoxMetaData($path, true); + if ($contents !== false) { + $files = array(); + foreach ($contents as $file) { + $files[] = basename($file['path']); + } + return IteratorDirectory::wrap($files); + } + return false; + } + + public function stat($path) { + $metaData = $this->getDropBoxMetaData($path); + if ($metaData) { + $stat['size'] = $metaData['bytes']; + $stat['atime'] = time(); + $stat['mtime'] = (isset($metaData['modified'])) ? strtotime($metaData['modified']) : time(); + return $stat; + } + return false; + } + + public function filetype($path) { + if ($path == '' || $path == '/') { + return 'dir'; + } else { + $metaData = $this->getDropBoxMetaData($path); + if ($metaData) { + if ($metaData['is_dir'] == 'true') { + return 'dir'; + } else { + return 'file'; + } + } + } + return false; + } + + public function file_exists($path) { + if ($path == '' || $path == '/') { + return true; + } + if ($this->getDropBoxMetaData($path)) { + return true; + } + return false; + } + + public function unlink($path) { + try { + $this->dropbox->delete($this->root.$path); + $this->deleteMetaData($path); + return true; + } catch (\Exception $exception) { + \OCP\Util::writeLog('files_external', $exception->getMessage(), \OCP\Util::ERROR); + return false; + } + } + + public function rename($path1, $path2) { + try { + // overwrite if target file exists and is not a directory + $destMetaData = $this->getDropBoxMetaData($path2); + if (isset($destMetaData) && $destMetaData !== false && !$destMetaData['is_dir']) { + $this->unlink($path2); + } + $this->dropbox->move($this->root.$path1, $this->root.$path2); + $this->deleteMetaData($path1); + return true; + } catch (\Exception $exception) { + \OCP\Util::writeLog('files_external', $exception->getMessage(), \OCP\Util::ERROR); + return false; + } + } + + public function copy($path1, $path2) { + $path1 = $this->root.$path1; + $path2 = $this->root.$path2; + try { + $this->dropbox->copy($path1, $path2); + return true; + } catch (\Exception $exception) { + \OCP\Util::writeLog('files_external', $exception->getMessage(), \OCP\Util::ERROR); + return false; + } + } + + public function fopen($path, $mode) { + $path = $this->root.$path; + switch ($mode) { + case 'r': + case 'rb': + try { + // slashes need to stay + $encodedPath = str_replace('%2F', '/', rawurlencode(trim($path, '/'))); + $downloadUrl = 'https://api-content.dropbox.com/1/files/auto/' . $encodedPath; + $headers = $this->oauth->getOAuthHeader($downloadUrl, [], 'GET'); + + $client = \OC::$server->getHTTPClientService()->newClient(); + try { + $response = $client->get($downloadUrl, [ + 'headers' => $headers, + 'stream' => true, + ]); + } catch (RequestException $e) { + if (!is_null($e->getResponse())) { + if ($e->getResponse()->getStatusCode() === 404) { + return false; + } else { + throw $e; + } + } else { + throw $e; + } + } + + $handle = $response->getBody(); + return RetryWrapper::wrap($handle); + } catch (\Exception $exception) { + \OCP\Util::writeLog('files_external', $exception->getMessage(), \OCP\Util::ERROR); + 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 = \OCP\Files::tmpFile($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::$tempFiles[$tmpFile] = $path; + return fopen('close://'.$tmpFile, $mode); + } + return false; + } + + public function writeBack($tmpFile) { + if (isset(self::$tempFiles[$tmpFile])) { + $handle = fopen($tmpFile, 'r'); + try { + $this->dropbox->putFile(self::$tempFiles[$tmpFile], $handle); + unlink($tmpFile); + $this->deleteMetaData(self::$tempFiles[$tmpFile]); + } catch (\Exception $exception) { + \OCP\Util::writeLog('files_external', $exception->getMessage(), \OCP\Util::ERROR); + } + } + } + + public function free_space($path) { + try { + $info = $this->dropbox->getAccountInfo(); + return $info['quota_info']['quota'] - $info['quota_info']['normal']; + } catch (\Exception $exception) { + \OCP\Util::writeLog('files_external', $exception->getMessage(), \OCP\Util::ERROR); + return false; + } + } + + public function touch($path, $mtime = null) { + if ($this->file_exists($path)) { + return false; + } else { + $this->file_put_contents($path, ''); + } + return true; + } + + /** + * check if curl is installed + */ + public static function checkDependencies() { + return true; + } + +} diff --git a/apps/files_external/lib/Lib/Storage/FTP.php b/apps/files_external/lib/Lib/Storage/FTP.php new file mode 100644 index 00000000000..051c1873009 --- /dev/null +++ b/apps/files_external/lib/Lib/Storage/FTP.php @@ -0,0 +1,155 @@ + + * @author Felix Moeller + * @author Jörn Friedrich Dreyer + * @author Michael Gapczynski + * @author Morris Jobke + * @author Philipp Kapfer + * @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 OCA\Files_External\Lib\Storage; + +use Icewind\Streams\RetryWrapper; + +class FTP extends StreamWrapper{ + private $password; + private $user; + private $host; + private $secure; + private $root; + + private static $tempFiles=array(); + + public function __construct($params) { + if (isset($params['host']) && isset($params['user']) && isset($params['password'])) { + $this->host=$params['host']; + $this->user=$params['user']; + $this->password=$params['password']; + if (isset($params['secure'])) { + $this->secure = $params['secure']; + } else { + $this->secure = false; + } + $this->root=isset($params['root'])?$params['root']:'/'; + if ( ! $this->root || $this->root[0]!='/') { + $this->root='/'.$this->root; + } + if (substr($this->root, -1) !== '/') { + $this->root .= '/'; + } + } else { + throw new \Exception('Creating FTP storage failed'); + } + + } + + public function getId(){ + return 'ftp::' . $this->user . '@' . $this->host . '/' . $this->root; + } + + /** + * construct the ftp url + * @param string $path + * @return string + */ + public function constructUrl($path) { + $url='ftp'; + if ($this->secure) { + $url.='s'; + } + $url.='://'.urlencode($this->user).':'.urlencode($this->password).'@'.$this->host.$this->root.$path; + return $url; + } + + /** + * Unlinks file or directory + * @param string $path + */ + public function unlink($path) { + if ($this->is_dir($path)) { + return $this->rmdir($path); + } + else { + $url = $this->constructUrl($path); + $result = unlink($url); + clearstatcache(true, $url); + return $result; + } + } + public function fopen($path,$mode) { + switch($mode) { + case 'r': + case 'rb': + case 'w': + case 'wb': + case 'a': + case 'ab': + //these are supported by the wrapper + $context = stream_context_create(array('ftp' => array('overwrite' => true))); + $handle = fopen($this->constructUrl($path), $mode, false, $context); + return RetryWrapper::wrap($handle); + case 'r+': + case 'w+': + case 'wb+': + case 'a+': + case 'x': + case 'x+': + case 'c': + case 'c+': + //emulate these + if (strrpos($path, '.')!==false) { + $ext=substr($path, strrpos($path, '.')); + } else { + $ext=''; + } + $tmpFile=\OCP\Files::tmpFile($ext); + \OC\Files\Stream\Close::registerCallback($tmpFile, array($this, 'writeBack')); + if ($this->file_exists($path)) { + $this->getFile($path, $tmpFile); + } + self::$tempFiles[$tmpFile]=$path; + return fopen('close://'.$tmpFile, $mode); + } + return false; + } + + public function writeBack($tmpFile) { + if (isset(self::$tempFiles[$tmpFile])) { + $this->uploadFile($tmpFile, self::$tempFiles[$tmpFile]); + unlink($tmpFile); + } + } + + /** + * check if php-ftp is installed + */ + public static function checkDependencies() { + if (function_exists('ftp_login')) { + return(true); + } else { + return array('ftp'); + } + } + +} diff --git a/apps/files_external/lib/Lib/Storage/Google.php b/apps/files_external/lib/Lib/Storage/Google.php new file mode 100644 index 00000000000..13e89299c22 --- /dev/null +++ b/apps/files_external/lib/Lib/Storage/Google.php @@ -0,0 +1,710 @@ + + * @author Arthur Schiwon + * @author Bart Visscher + * @author Christopher Schäpers + * @author Jörn Friedrich Dreyer + * @author Lukas Reschke + * @author Michael Gapczynski + * @author Morris Jobke + * @author Philipp Kapfer + * @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 OCA\Files_External\Lib\Storage; + +use GuzzleHttp\Exception\RequestException; +use Icewind\Streams\IteratorDirectory; +use Icewind\Streams\RetryWrapper; + +set_include_path(get_include_path().PATH_SEPARATOR. + \OC_App::getAppPath('files_external').'/3rdparty/google-api-php-client/src'); +require_once 'Google/Client.php'; +require_once 'Google/Service/Drive.php'; + +class Google extends \OC\Files\Storage\Common { + + private $client; + private $id; + private $service; + private $driveFiles; + + private static $tempFiles = array(); + + // Google Doc mimetypes + const FOLDER = 'application/vnd.google-apps.folder'; + const DOCUMENT = 'application/vnd.google-apps.document'; + const SPREADSHEET = 'application/vnd.google-apps.spreadsheet'; + const DRAWING = 'application/vnd.google-apps.drawing'; + const PRESENTATION = 'application/vnd.google-apps.presentation'; + + public function __construct($params) { + if (isset($params['configured']) && $params['configured'] === 'true' + && isset($params['client_id']) && isset($params['client_secret']) + && isset($params['token']) + ) { + $this->client = new \Google_Client(); + $this->client->setClientId($params['client_id']); + $this->client->setClientSecret($params['client_secret']); + $this->client->setScopes(array('https://www.googleapis.com/auth/drive')); + $this->client->setAccessToken($params['token']); + // if curl isn't available we're likely to run into + // https://github.com/google/google-api-php-client/issues/59 + // - disable gzip to avoid it. + if (!function_exists('curl_version') || !function_exists('curl_exec')) { + $this->client->setClassConfig("Google_Http_Request", "disable_gzip", true); + } + // note: API connection is lazy + $this->service = new \Google_Service_Drive($this->client); + $token = json_decode($params['token'], true); + $this->id = 'google::'.substr($params['client_id'], 0, 30).$token['created']; + } else { + throw new \Exception('Creating Google storage failed'); + } + } + + public function getId() { + return $this->id; + } + + /** + * Get the Google_Service_Drive_DriveFile object for the specified path. + * Returns false on failure. + * @param string $path + * @return \Google_Service_Drive_DriveFile|false + */ + private function getDriveFile($path) { + // Remove leading and trailing slashes + $path = trim($path, '/'); + if (isset($this->driveFiles[$path])) { + return $this->driveFiles[$path]; + } else if ($path === '') { + $root = $this->service->files->get('root'); + $this->driveFiles[$path] = $root; + return $root; + } else { + // Google Drive SDK does not have methods for retrieving files by path + // Instead we must find the id of the parent folder of the file + $parentId = $this->getDriveFile('')->getId(); + $folderNames = explode('/', $path); + $path = ''; + // Loop through each folder of this path to get to the file + foreach ($folderNames as $name) { + // Reconstruct path from beginning + if ($path === '') { + $path .= $name; + } else { + $path .= '/'.$name; + } + if (isset($this->driveFiles[$path])) { + $parentId = $this->driveFiles[$path]->getId(); + } else { + $q = "title='" . str_replace("'","\\'", $name) . "' and '" . str_replace("'","\\'", $parentId) . "' in parents and trashed = false"; + $result = $this->service->files->listFiles(array('q' => $q))->getItems(); + if (!empty($result)) { + // Google Drive allows files with the same name, ownCloud doesn't + if (count($result) > 1) { + $this->onDuplicateFileDetected($path); + return false; + } else { + $file = current($result); + $this->driveFiles[$path] = $file; + $parentId = $file->getId(); + } + } else { + // Google Docs have no extension in their title, so try without extension + $pos = strrpos($path, '.'); + if ($pos !== false) { + $pathWithoutExt = substr($path, 0, $pos); + $file = $this->getDriveFile($pathWithoutExt); + if ($file) { + // Switch cached Google_Service_Drive_DriveFile to the correct index + unset($this->driveFiles[$pathWithoutExt]); + $this->driveFiles[$path] = $file; + $parentId = $file->getId(); + } else { + return false; + } + } else { + return false; + } + } + } + } + return $this->driveFiles[$path]; + } + } + + /** + * Set the Google_Service_Drive_DriveFile object in the cache + * @param string $path + * @param Google_Service_Drive_DriveFile|false $file + */ + private function setDriveFile($path, $file) { + $path = trim($path, '/'); + $this->driveFiles[$path] = $file; + if ($file === false) { + // Set all child paths as false + $len = strlen($path); + foreach ($this->driveFiles as $key => $file) { + if (substr($key, 0, $len) === $path) { + $this->driveFiles[$key] = false; + } + } + } + } + + /** + * Write a log message to inform about duplicate file names + * @param string $path + */ + private function onDuplicateFileDetected($path) { + $about = $this->service->about->get(); + $user = $about->getName(); + \OCP\Util::writeLog('files_external', + 'Ignoring duplicate file name: '.$path.' on Google Drive for Google user: '.$user, + \OCP\Util::INFO + ); + } + + /** + * Generate file extension for a Google Doc, choosing Open Document formats for download + * @param string $mimetype + * @return string + */ + private function getGoogleDocExtension($mimetype) { + if ($mimetype === self::DOCUMENT) { + return 'odt'; + } else if ($mimetype === self::SPREADSHEET) { + return 'ods'; + } else if ($mimetype === self::DRAWING) { + return 'jpg'; + } else if ($mimetype === self::PRESENTATION) { + // Download as .odp is not available + return 'pdf'; + } else { + return ''; + } + } + + public function mkdir($path) { + if (!$this->is_dir($path)) { + $parentFolder = $this->getDriveFile(dirname($path)); + if ($parentFolder) { + $folder = new \Google_Service_Drive_DriveFile(); + $folder->setTitle(basename($path)); + $folder->setMimeType(self::FOLDER); + $parent = new \Google_Service_Drive_ParentReference(); + $parent->setId($parentFolder->getId()); + $folder->setParents(array($parent)); + $result = $this->service->files->insert($folder); + if ($result) { + $this->setDriveFile($path, $result); + } + return (bool)$result; + } + } + return false; + } + + public function rmdir($path) { + if (!$this->isDeletable($path)) { + return false; + } + if (trim($path, '/') === '') { + $dir = $this->opendir($path); + if(is_resource($dir)) { + while (($file = readdir($dir)) !== false) { + if (!\OC\Files\Filesystem::isIgnoredDir($file)) { + if (!$this->unlink($path.'/'.$file)) { + return false; + } + } + } + closedir($dir); + } + $this->driveFiles = array(); + return true; + } else { + return $this->unlink($path); + } + } + + public function opendir($path) { + $folder = $this->getDriveFile($path); + if ($folder) { + $files = array(); + $duplicates = array(); + $pageToken = true; + while ($pageToken) { + $params = array(); + if ($pageToken !== true) { + $params['pageToken'] = $pageToken; + } + $params['q'] = "'" . str_replace("'","\\'", $folder->getId()) . "' in parents and trashed = false"; + $children = $this->service->files->listFiles($params); + foreach ($children->getItems() as $child) { + $name = $child->getTitle(); + // Check if this is a Google Doc i.e. no extension in name + $extension = $child->getFileExtension(); + if (empty($extension) + && $child->getMimeType() !== self::FOLDER + ) { + $name .= '.'.$this->getGoogleDocExtension($child->getMimeType()); + } + if ($path === '') { + $filepath = $name; + } else { + $filepath = $path.'/'.$name; + } + // Google Drive allows files with the same name, ownCloud doesn't + // Prevent opendir() from returning any duplicate files + $key = array_search($name, $files); + if ($key !== false || isset($duplicates[$filepath])) { + if (!isset($duplicates[$filepath])) { + $duplicates[$filepath] = true; + $this->setDriveFile($filepath, false); + unset($files[$key]); + $this->onDuplicateFileDetected($filepath); + } + } else { + // Cache the Google_Service_Drive_DriveFile for future use + $this->setDriveFile($filepath, $child); + $files[] = $name; + } + } + $pageToken = $children->getNextPageToken(); + } + return IteratorDirectory::wrap($files); + } else { + return false; + } + } + + public function stat($path) { + $file = $this->getDriveFile($path); + if ($file) { + $stat = array(); + if ($this->filetype($path) === 'dir') { + $stat['size'] = 0; + } else { + // Check if this is a Google Doc + if ($this->getMimeType($path) !== $file->getMimeType()) { + // Return unknown file size + $stat['size'] = \OCP\Files\FileInfo::SPACE_UNKNOWN; + } else { + $stat['size'] = $file->getFileSize(); + } + } + $stat['atime'] = strtotime($file->getLastViewedByMeDate()); + $stat['mtime'] = strtotime($file->getModifiedDate()); + $stat['ctime'] = strtotime($file->getCreatedDate()); + return $stat; + } else { + return false; + } + } + + public function filetype($path) { + if ($path === '') { + return 'dir'; + } else { + $file = $this->getDriveFile($path); + if ($file) { + if ($file->getMimeType() === self::FOLDER) { + return 'dir'; + } else { + return 'file'; + } + } else { + return false; + } + } + } + + public function isUpdatable($path) { + $file = $this->getDriveFile($path); + if ($file) { + return $file->getEditable(); + } else { + return false; + } + } + + public function file_exists($path) { + return (bool)$this->getDriveFile($path); + } + + public function unlink($path) { + $file = $this->getDriveFile($path); + if ($file) { + $result = $this->service->files->trash($file->getId()); + if ($result) { + $this->setDriveFile($path, false); + } + return (bool)$result; + } else { + return false; + } + } + + public function rename($path1, $path2) { + $file = $this->getDriveFile($path1); + if ($file) { + $newFile = $this->getDriveFile($path2); + if (dirname($path1) === dirname($path2)) { + if ($newFile) { + // rename to the name of the target file, could be an office file without extension + $file->setTitle($newFile->getTitle()); + } else { + $file->setTitle(basename(($path2))); + } + } else { + // Change file parent + $parentFolder2 = $this->getDriveFile(dirname($path2)); + if ($parentFolder2) { + $parent = new \Google_Service_Drive_ParentReference(); + $parent->setId($parentFolder2->getId()); + $file->setParents(array($parent)); + } else { + return false; + } + } + // We need to get the object for the existing file with the same + // name (if there is one) before we do the patch. If oldfile + // exists and is a directory we have to delete it before we + // do the rename too. + $oldfile = $this->getDriveFile($path2); + if ($oldfile && $this->is_dir($path2)) { + $this->rmdir($path2); + $oldfile = false; + } + $result = $this->service->files->patch($file->getId(), $file); + if ($result) { + $this->setDriveFile($path1, false); + $this->setDriveFile($path2, $result); + if ($oldfile && $newFile) { + // only delete if they have a different id (same id can happen for part files) + if ($newFile->getId() !== $oldfile->getId()) { + $this->service->files->delete($oldfile->getId()); + } + } + } + return (bool)$result; + } else { + return false; + } + } + + public function fopen($path, $mode) { + $pos = strrpos($path, '.'); + if ($pos !== false) { + $ext = substr($path, $pos); + } else { + $ext = ''; + } + switch ($mode) { + case 'r': + case 'rb': + $file = $this->getDriveFile($path); + if ($file) { + $exportLinks = $file->getExportLinks(); + $mimetype = $this->getMimeType($path); + $downloadUrl = null; + if ($exportLinks && isset($exportLinks[$mimetype])) { + $downloadUrl = $exportLinks[$mimetype]; + } else { + $downloadUrl = $file->getDownloadUrl(); + } + if (isset($downloadUrl)) { + $request = new \Google_Http_Request($downloadUrl, 'GET', null, null); + $httpRequest = $this->client->getAuth()->sign($request); + // the library's service doesn't support streaming, so we use Guzzle instead + $client = \OC::$server->getHTTPClientService()->newClient(); + try { + $response = $client->get($downloadUrl, [ + 'headers' => $httpRequest->getRequestHeaders(), + 'stream' => true, + 'verify' => __DIR__ . '/../../3rdparty/google-api-php-client/src/Google/IO/cacerts.pem', + ]); + } catch (RequestException $e) { + if(!is_null($e->getResponse())) { + if ($e->getResponse()->getStatusCode() === 404) { + return false; + } else { + throw $e; + } + } else { + throw $e; + } + } + + $handle = $response->getBody(); + return RetryWrapper::wrap($handle); + } + } + 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+': + $tmpFile = \OCP\Files::tmpFile($ext); + \OC\Files\Stream\Close::registerCallback($tmpFile, array($this, 'writeBack')); + if ($this->file_exists($path)) { + $source = $this->fopen($path, 'rb'); + file_put_contents($tmpFile, $source); + } + self::$tempFiles[$tmpFile] = $path; + return fopen('close://'.$tmpFile, $mode); + } + } + + public function writeBack($tmpFile) { + if (isset(self::$tempFiles[$tmpFile])) { + $path = self::$tempFiles[$tmpFile]; + $parentFolder = $this->getDriveFile(dirname($path)); + if ($parentFolder) { + $mimetype = \OC::$server->getMimeTypeDetector()->detect($tmpFile); + $params = array( + 'mimeType' => $mimetype, + 'uploadType' => 'media' + ); + $result = false; + + $chunkSizeBytes = 10 * 1024 * 1024; + + $useChunking = false; + $size = filesize($tmpFile); + if ($size > $chunkSizeBytes) { + $useChunking = true; + } else { + $params['data'] = file_get_contents($tmpFile); + } + + if ($this->file_exists($path)) { + $file = $this->getDriveFile($path); + $this->client->setDefer($useChunking); + $request = $this->service->files->update($file->getId(), $file, $params); + } else { + $file = new \Google_Service_Drive_DriveFile(); + $file->setTitle(basename($path)); + $file->setMimeType($mimetype); + $parent = new \Google_Service_Drive_ParentReference(); + $parent->setId($parentFolder->getId()); + $file->setParents(array($parent)); + $this->client->setDefer($useChunking); + $request = $this->service->files->insert($file, $params); + } + + if ($useChunking) { + // Create a media file upload to represent our upload process. + $media = new \Google_Http_MediaFileUpload( + $this->client, + $request, + 'text/plain', + null, + true, + $chunkSizeBytes + ); + $media->setFileSize($size); + + // Upload the various chunks. $status will be false until the process is + // complete. + $status = false; + $handle = fopen($tmpFile, 'rb'); + while (!$status && !feof($handle)) { + $chunk = fread($handle, $chunkSizeBytes); + $status = $media->nextChunk($chunk); + } + + // The final value of $status will be the data from the API for the object + // that has been uploaded. + $result = false; + if ($status !== false) { + $result = $status; + } + + fclose($handle); + } else { + $result = $request; + } + + // Reset to the client to execute requests immediately in the future. + $this->client->setDefer(false); + + if ($result) { + $this->setDriveFile($path, $result); + } + } + unlink($tmpFile); + } + } + + public function getMimeType($path) { + $file = $this->getDriveFile($path); + if ($file) { + $mimetype = $file->getMimeType(); + // Convert Google Doc mimetypes, choosing Open Document formats for download + if ($mimetype === self::FOLDER) { + return 'httpd/unix-directory'; + } else if ($mimetype === self::DOCUMENT) { + return 'application/vnd.oasis.opendocument.text'; + } else if ($mimetype === self::SPREADSHEET) { + return 'application/x-vnd.oasis.opendocument.spreadsheet'; + } else if ($mimetype === self::DRAWING) { + return 'image/jpeg'; + } else if ($mimetype === self::PRESENTATION) { + // Download as .odp is not available + return 'application/pdf'; + } else { + // use extension-based detection, could be an encrypted file + return parent::getMimeType($path); + } + } else { + return false; + } + } + + public function free_space($path) { + $about = $this->service->about->get(); + return $about->getQuotaBytesTotal() - $about->getQuotaBytesUsed(); + } + + public function touch($path, $mtime = null) { + $file = $this->getDriveFile($path); + $result = false; + if ($file) { + if (isset($mtime)) { + // This is just RFC3339, but frustratingly, GDrive's API *requires* + // the fractions portion be present, while no handy PHP constant + // for RFC3339 or ISO8601 includes it. So we do it ourselves. + $file->setModifiedDate(date('Y-m-d\TH:i:s.uP', $mtime)); + $result = $this->service->files->patch($file->getId(), $file, array( + 'setModifiedDate' => true, + )); + } else { + $result = $this->service->files->touch($file->getId()); + } + } else { + $parentFolder = $this->getDriveFile(dirname($path)); + if ($parentFolder) { + $file = new \Google_Service_Drive_DriveFile(); + $file->setTitle(basename($path)); + $parent = new \Google_Service_Drive_ParentReference(); + $parent->setId($parentFolder->getId()); + $file->setParents(array($parent)); + $result = $this->service->files->insert($file); + } + } + if ($result) { + $this->setDriveFile($path, $result); + } + return (bool)$result; + } + + public function test() { + if ($this->free_space('')) { + return true; + } + return false; + } + + public function hasUpdated($path, $time) { + $appConfig = \OC::$server->getAppConfig(); + if ($this->is_file($path)) { + return parent::hasUpdated($path, $time); + } else { + // Google Drive doesn't change modified times of folders when files inside are updated + // Instead we use the Changes API to see if folders have been updated, and it's a pain + $folder = $this->getDriveFile($path); + if ($folder) { + $result = false; + $folderId = $folder->getId(); + $startChangeId = $appConfig->getValue('files_external', $this->getId().'cId'); + $params = array( + 'includeDeleted' => true, + 'includeSubscribed' => true, + ); + if (isset($startChangeId)) { + $startChangeId = (int)$startChangeId; + $largestChangeId = $startChangeId; + $params['startChangeId'] = $startChangeId + 1; + } else { + $largestChangeId = 0; + } + $pageToken = true; + while ($pageToken) { + if ($pageToken !== true) { + $params['pageToken'] = $pageToken; + } + $changes = $this->service->changes->listChanges($params); + if ($largestChangeId === 0 || $largestChangeId === $startChangeId) { + $largestChangeId = $changes->getLargestChangeId(); + } + if (isset($startChangeId)) { + // Check if a file in this folder has been updated + // There is no way to filter by folder at the API level... + foreach ($changes->getItems() as $change) { + $file = $change->getFile(); + if ($file) { + foreach ($file->getParents() as $parent) { + if ($parent->getId() === $folderId) { + $result = true; + // Check if there are changes in different folders + } else if ($change->getId() <= $largestChangeId) { + // Decrement id so this change is fetched when called again + $largestChangeId = $change->getId(); + $largestChangeId--; + } + } + } + } + $pageToken = $changes->getNextPageToken(); + } else { + // Assuming the initial scan just occurred and changes are negligible + break; + } + } + $appConfig->setValue('files_external', $this->getId().'cId', $largestChangeId); + return $result; + } + } + return false; + } + + /** + * check if curl is installed + */ + public static function checkDependencies() { + return true; + } + +} diff --git a/apps/files_external/lib/Lib/Storage/OwnCloud.php b/apps/files_external/lib/Lib/Storage/OwnCloud.php new file mode 100644 index 00000000000..22ecb4c806a --- /dev/null +++ b/apps/files_external/lib/Lib/Storage/OwnCloud.php @@ -0,0 +1,74 @@ + + * @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 OCA\Files_External\Lib\Storage; + +/** + * ownCloud backend for external storage based on DAV backend. + * + * The ownCloud URL consists of three parts: + * http://%host/%context/remote.php/webdav/%root + * + */ +class OwnCloud extends \OC\Files\Storage\DAV{ + const OC_URL_SUFFIX = 'remote.php/webdav'; + + public function __construct($params) { + // extract context path from host if specified + // (owncloud install path on host) + $host = $params['host']; + // strip protocol + if (substr($host, 0, 8) == "https://") { + $host = substr($host, 8); + $params['secure'] = true; + } else if (substr($host, 0, 7) == "http://") { + $host = substr($host, 7); + $params['secure'] = false; + } + $contextPath = ''; + $hostSlashPos = strpos($host, '/'); + if ($hostSlashPos !== false){ + $contextPath = substr($host, $hostSlashPos); + $host = substr($host, 0, $hostSlashPos); + } + + if (substr($contextPath, -1) !== '/'){ + $contextPath .= '/'; + } + + if (isset($params['root'])){ + $root = $params['root']; + if (substr($root, 0, 1) !== '/'){ + $root = '/' . $root; + } + } + else{ + $root = '/'; + } + + $params['host'] = $host; + $params['root'] = $contextPath . self::OC_URL_SUFFIX . $root; + + parent::__construct($params); + } +} diff --git a/apps/files_external/lib/Lib/Storage/SFTP.php b/apps/files_external/lib/Lib/Storage/SFTP.php new file mode 100644 index 00000000000..2375f84dcda --- /dev/null +++ b/apps/files_external/lib/Lib/Storage/SFTP.php @@ -0,0 +1,467 @@ + + * @author Bart Visscher + * @author hkjolhede + * @author Jörn Friedrich Dreyer + * @author Lennart Rosam + * @author Lukas Reschke + * @author Morris Jobke + * @author Robin Appelman + * @author Robin McCorkell + * @author Ross Nicoll + * @author SA + * @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 OCA\Files_External\Lib\Storage; +use Icewind\Streams\IteratorDirectory; + +use Icewind\Streams\RetryWrapper; +use phpseclib\Net\SFTP\Stream; + +/** +* Uses phpseclib's Net\SFTP class and the Net\SFTP\Stream stream wrapper to +* provide access to SFTP servers. +*/ +class SFTP extends \OC\Files\Storage\Common { + private $host; + private $user; + private $root; + private $port = 22; + + private $auth; + + /** + * @var SFTP + */ + protected $client; + + /** + * @param string $host protocol://server:port + * @return array [$server, $port] + */ + private function splitHost($host) { + $input = $host; + if (strpos($host, '://') === false) { + // add a protocol to fix parse_url behavior with ipv6 + $host = 'http://' . $host; + } + + $parsed = parse_url($host); + if(is_array($parsed) && isset($parsed['port'])) { + return [$parsed['host'], $parsed['port']]; + } else if (is_array($parsed)) { + return [$parsed['host'], 22]; + } else { + return [$input, 22]; + } + } + + /** + * {@inheritdoc} + */ + public function __construct($params) { + // Register sftp:// + Stream::register(); + + $parsedHost = $this->splitHost($params['host']); + + $this->host = $parsedHost[0]; + $this->port = $parsedHost[1]; + + if (!isset($params['user'])) { + throw new \UnexpectedValueException('no authentication parameters specified'); + } + $this->user = $params['user']; + + if (isset($params['public_key_auth'])) { + $this->auth = $params['public_key_auth']; + } elseif (isset($params['password'])) { + $this->auth = $params['password']; + } else { + throw new \UnexpectedValueException('no authentication parameters specified'); + } + + $this->root + = isset($params['root']) ? $this->cleanPath($params['root']) : '/'; + + if ($this->root[0] != '/') { + $this->root = '/' . $this->root; + } + + if (substr($this->root, -1, 1) != '/') { + $this->root .= '/'; + } + } + + /** + * Returns the connection. + * + * @return \phpseclib\Net\SFTP connected client instance + * @throws \Exception when the connection failed + */ + public function getConnection() { + if (!is_null($this->client)) { + return $this->client; + } + + $hostKeys = $this->readHostKeys(); + $this->client = new \phpseclib\Net\SFTP($this->host, $this->port); + + // The SSH Host Key MUST be verified before login(). + $currentHostKey = $this->client->getServerPublicHostKey(); + if (array_key_exists($this->host, $hostKeys)) { + if ($hostKeys[$this->host] != $currentHostKey) { + throw new \Exception('Host public key does not match known key'); + } + } else { + $hostKeys[$this->host] = $currentHostKey; + $this->writeHostKeys($hostKeys); + } + + if (!$this->client->login($this->user, $this->auth)) { + throw new \Exception('Login failed'); + } + return $this->client; + } + + /** + * {@inheritdoc} + */ + public function test() { + if ( + !isset($this->host) + || !isset($this->user) + ) { + return false; + } + return $this->getConnection()->nlist() !== false; + } + + /** + * {@inheritdoc} + */ + public function getId(){ + $id = 'sftp::' . $this->user . '@' . $this->host; + if ($this->port !== 22) { + $id .= ':' . $this->port; + } + // note: this will double the root slash, + // we should not change it to keep compatible with + // old storage ids + $id .= '/' . $this->root; + return $id; + } + + /** + * @return string + */ + public function getHost() { + return $this->host; + } + + /** + * @return string + */ + public function getRoot() { + return $this->root; + } + + /** + * @return mixed + */ + public function getUser() { + return $this->user; + } + + /** + * @param string $path + * @return string + */ + private function absPath($path) { + return $this->root . $this->cleanPath($path); + } + + /** + * @return string|false + */ + private function hostKeysPath() { + try { + $storage_view = \OCP\Files::getStorage('files_external'); + if ($storage_view) { + return \OC::$server->getConfig()->getSystemValue('datadirectory') . + $storage_view->getAbsolutePath('') . + 'ssh_hostKeys'; + } + } catch (\Exception $e) { + } + return false; + } + + /** + * @param $keys + * @return bool + */ + protected function writeHostKeys($keys) { + try { + $keyPath = $this->hostKeysPath(); + if ($keyPath && file_exists($keyPath)) { + $fp = fopen($keyPath, 'w'); + foreach ($keys as $host => $key) { + fwrite($fp, $host . '::' . $key . "\n"); + } + fclose($fp); + return true; + } + } catch (\Exception $e) { + } + return false; + } + + /** + * @return array + */ + protected function readHostKeys() { + try { + $keyPath = $this->hostKeysPath(); + if (file_exists($keyPath)) { + $hosts = array(); + $keys = array(); + $lines = file($keyPath, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES); + if ($lines) { + foreach ($lines as $line) { + $hostKeyArray = explode("::", $line, 2); + if (count($hostKeyArray) == 2) { + $hosts[] = $hostKeyArray[0]; + $keys[] = $hostKeyArray[1]; + } + } + return array_combine($hosts, $keys); + } + } + } catch (\Exception $e) { + } + return array(); + } + + /** + * {@inheritdoc} + */ + public function mkdir($path) { + try { + return $this->getConnection()->mkdir($this->absPath($path)); + } catch (\Exception $e) { + return false; + } + } + + /** + * {@inheritdoc} + */ + public function rmdir($path) { + try { + $result = $this->getConnection()->delete($this->absPath($path), true); + // workaround: stray stat cache entry when deleting empty folders + // see https://github.com/phpseclib/phpseclib/issues/706 + $this->getConnection()->clearStatCache(); + return $result; + } catch (\Exception $e) { + return false; + } + } + + /** + * {@inheritdoc} + */ + public function opendir($path) { + try { + $list = $this->getConnection()->nlist($this->absPath($path)); + if ($list === false) { + return false; + } + + $id = md5('sftp:' . $path); + $dirStream = array(); + foreach($list as $file) { + if ($file != '.' && $file != '..') { + $dirStream[] = $file; + } + } + return IteratorDirectory::wrap($dirStream); + } catch(\Exception $e) { + return false; + } + } + + /** + * {@inheritdoc} + */ + public function filetype($path) { + try { + $stat = $this->getConnection()->stat($this->absPath($path)); + if ($stat['type'] == NET_SFTP_TYPE_REGULAR) { + return 'file'; + } + + if ($stat['type'] == NET_SFTP_TYPE_DIRECTORY) { + return 'dir'; + } + } catch (\Exception $e) { + + } + return false; + } + + /** + * {@inheritdoc} + */ + public function file_exists($path) { + try { + return $this->getConnection()->stat($this->absPath($path)) !== false; + } catch (\Exception $e) { + return false; + } + } + + /** + * {@inheritdoc} + */ + public function unlink($path) { + try { + return $this->getConnection()->delete($this->absPath($path), true); + } catch (\Exception $e) { + return false; + } + } + + /** + * {@inheritdoc} + */ + public function fopen($path, $mode) { + try { + $absPath = $this->absPath($path); + switch($mode) { + case 'r': + case 'rb': + if ( !$this->file_exists($path)) { + 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+': + $context = stream_context_create(array('sftp' => array('session' => $this->getConnection()))); + $handle = fopen($this->constructUrl($path), $mode, false, $context); + return RetryWrapper::wrap($handle); + } + } catch (\Exception $e) { + } + return false; + } + + /** + * {@inheritdoc} + */ + public function touch($path, $mtime=null) { + try { + if (!is_null($mtime)) { + return false; + } + if (!$this->file_exists($path)) { + $this->getConnection()->put($this->absPath($path), ''); + } else { + return false; + } + } catch (\Exception $e) { + return false; + } + return true; + } + + /** + * @param string $path + * @param string $target + * @throws \Exception + */ + public function getFile($path, $target) { + $this->getConnection()->get($path, $target); + } + + /** + * @param string $path + * @param string $target + * @throws \Exception + */ + public function uploadFile($path, $target) { + $this->getConnection()->put($target, $path, NET_SFTP_LOCAL_FILE); + } + + /** + * {@inheritdoc} + */ + public function rename($source, $target) { + try { + if (!$this->is_dir($target) && $this->file_exists($target)) { + $this->unlink($target); + } + return $this->getConnection()->rename( + $this->absPath($source), + $this->absPath($target) + ); + } catch (\Exception $e) { + return false; + } + } + + /** + * {@inheritdoc} + */ + public function stat($path) { + try { + $stat = $this->getConnection()->stat($this->absPath($path)); + + $mtime = $stat ? $stat['mtime'] : -1; + $size = $stat ? $stat['size'] : 0; + + return array('mtime' => $mtime, 'size' => $size, 'ctime' => -1); + } catch (\Exception $e) { + return false; + } + } + + /** + * @param string $path + * @return string + */ + public function constructUrl($path) { + // Do not pass the password here. We want to use the Net_SFTP object + // supplied via stream context or fail. We only supply username and + // hostname because this might show up in logs (they are not used). + $url = 'sftp://' . urlencode($this->user) . '@' . $this->host . ':' . $this->port . $this->root . $path; + return $url; + } +} diff --git a/apps/files_external/lib/Lib/Storage/SMB.php b/apps/files_external/lib/Lib/Storage/SMB.php new file mode 100644 index 00000000000..868c52a63b4 --- /dev/null +++ b/apps/files_external/lib/Lib/Storage/SMB.php @@ -0,0 +1,396 @@ + + * @author Jesús Macias + * @author Jörn Friedrich Dreyer + * @author Michael Gapczynski + * @author Morris Jobke + * @author Philipp Kapfer + * @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 OCA\Files_External\Lib\Storage; + +use Icewind\SMB\Exception\ConnectException; +use Icewind\SMB\Exception\Exception; +use Icewind\SMB\Exception\ForbiddenException; +use Icewind\SMB\Exception\NotFoundException; +use Icewind\SMB\NativeServer; +use Icewind\SMB\Server; +use Icewind\Streams\CallbackWrapper; +use Icewind\Streams\IteratorDirectory; +use OC\Cache\CappedMemoryCache; +use OC\Files\Filesystem; +use OCP\Files\StorageNotAvailableException; + +class SMB extends \OC\Files\Storage\Common { + /** + * @var \Icewind\SMB\Server + */ + protected $server; + + /** + * @var \Icewind\SMB\Share + */ + protected $share; + + /** + * @var string + */ + protected $root; + + /** + * @var \Icewind\SMB\FileInfo[] + */ + protected $statCache; + + public function __construct($params) { + if (isset($params['host']) && isset($params['user']) && isset($params['password']) && isset($params['share'])) { + if (Server::NativeAvailable()) { + $this->server = new NativeServer($params['host'], $params['user'], $params['password']); + } else { + $this->server = new Server($params['host'], $params['user'], $params['password']); + } + $this->share = $this->server->getShare(trim($params['share'], '/')); + + $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 configuration'); + } + $this->statCache = new CappedMemoryCache(); + } + + /** + * @return string + */ + public function getId() { + // FIXME: double slash to keep compatible with the old storage ids, + // failure to do so will lead to creation of a new storage id and + // loss of shares from the storage + return 'smb::' . $this->server->getUser() . '@' . $this->server->getHost() . '//' . $this->share->getName() . '/' . $this->root; + } + + /** + * @param string $path + * @return string + */ + protected function buildPath($path) { + return Filesystem::normalizePath($this->root . '/' . $path, true, false, true); + } + + /** + * @param string $path + * @return \Icewind\SMB\IFileInfo + * @throws StorageNotAvailableException + */ + protected function getFileInfo($path) { + try { + $path = $this->buildPath($path); + if (!isset($this->statCache[$path])) { + $this->statCache[$path] = $this->share->stat($path); + } + return $this->statCache[$path]; + } catch (ConnectException $e) { + throw new StorageNotAvailableException($e->getMessage(), $e->getCode(), $e); + } + } + + /** + * @param string $path + * @return \Icewind\SMB\IFileInfo[] + * @throws StorageNotAvailableException + */ + protected function getFolderContents($path) { + try { + $path = $this->buildPath($path); + $files = $this->share->dir($path); + foreach ($files as $file) { + $this->statCache[$path . '/' . $file->getName()] = $file; + } + return $files; + } catch (ConnectException $e) { + throw new StorageNotAvailableException($e->getMessage(), $e->getCode(), $e); + } + } + + /** + * @param \Icewind\SMB\IFileInfo $info + * @return array + */ + protected function formatInfo($info) { + return array( + 'size' => $info->getSize(), + 'mtime' => $info->getMTime() + ); + } + + /** + * @param string $path + * @return array + */ + public function stat($path) { + return $this->formatInfo($this->getFileInfo($path)); + } + + /** + * @param string $path + * @return bool + */ + public function unlink($path) { + try { + if ($this->is_dir($path)) { + return $this->rmdir($path); + } else { + $path = $this->buildPath($path); + unset($this->statCache[$path]); + $this->share->del($path); + return true; + } + } catch (NotFoundException $e) { + return false; + } catch (ForbiddenException $e) { + return false; + } catch (ConnectException $e) { + throw new StorageNotAvailableException($e->getMessage(), $e->getCode(), $e); + } + } + + /** + * 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 (!$path and $this->root == '/') { + // mtime doesn't work for shares, but giving the nature of the backend, + // doing a full update is still just fast enough + return true; + } else { + $actualTime = $this->filemtime($path); + return $actualTime > $time; + } + } + + /** + * @param string $path + * @param string $mode + * @return resource + */ + public function fopen($path, $mode) { + $fullPath = $this->buildPath($path); + try { + switch ($mode) { + case 'r': + case 'rb': + if (!$this->file_exists($path)) { + return false; + } + return $this->share->read($fullPath); + case 'w': + case 'wb': + $source = $this->share->write($fullPath); + return CallBackWrapper::wrap($source, null, null, function () use ($fullPath) { + unset($this->statCache[$fullPath]); + }); + case 'a': + case 'ab': + case 'r+': + case 'w+': + case 'wb+': + case 'a+': + case 'x': + case 'x+': + case 'c': + case 'c+': + //emulate these + if (strrpos($path, '.') !== false) { + $ext = substr($path, strrpos($path, '.')); + } else { + $ext = ''; + } + if ($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($ext); + } + $source = fopen($tmpFile, $mode); + $share = $this->share; + return CallbackWrapper::wrap($source, null, null, function () use ($tmpFile, $fullPath, $share) { + unset($this->statCache[$fullPath]); + $share->put($tmpFile, $fullPath); + unlink($tmpFile); + }); + } + return false; + } catch (NotFoundException $e) { + return false; + } catch (ForbiddenException $e) { + return false; + } catch (ConnectException $e) { + throw new StorageNotAvailableException($e->getMessage(), $e->getCode(), $e); + } + } + + public function rmdir($path) { + try { + $this->statCache = array(); + $content = $this->share->dir($this->buildPath($path)); + foreach ($content as $file) { + if ($file->isDirectory()) { + $this->rmdir($path . '/' . $file->getName()); + } else { + $this->share->del($file->getPath()); + } + } + $this->share->rmdir($this->buildPath($path)); + return true; + } catch (NotFoundException $e) { + return false; + } catch (ForbiddenException $e) { + return false; + } catch (ConnectException $e) { + throw new StorageNotAvailableException($e->getMessage(), $e->getCode(), $e); + } + } + + public function touch($path, $time = null) { + try { + if (!$this->file_exists($path)) { + $fh = $this->share->write($this->buildPath($path)); + fclose($fh); + return true; + } + return false; + } catch (ConnectException $e) { + throw new StorageNotAvailableException($e->getMessage(), $e->getCode(), $e); + } + } + + public function opendir($path) { + try { + $files = $this->getFolderContents($path); + } catch (NotFoundException $e) { + return false; + } catch (ForbiddenException $e) { + return false; + } + $names = array_map(function ($info) { + /** @var \Icewind\SMB\IFileInfo $info */ + return $info->getName(); + }, $files); + return IteratorDirectory::wrap($names); + } + + public function filetype($path) { + try { + return $this->getFileInfo($path)->isDirectory() ? 'dir' : 'file'; + } catch (NotFoundException $e) { + return false; + } catch (ForbiddenException $e) { + return false; + } + } + + public function mkdir($path) { + $path = $this->buildPath($path); + try { + $this->share->mkdir($path); + return true; + } catch (ConnectException $e) { + throw new StorageNotAvailableException($e->getMessage(), $e->getCode(), $e); + } catch (Exception $e) { + return false; + } + } + + public function file_exists($path) { + try { + $this->getFileInfo($path); + return true; + } catch (NotFoundException $e) { + return false; + } catch (ForbiddenException $e) { + return false; + } catch (ConnectException $e) { + throw new StorageNotAvailableException($e->getMessage(), $e->getCode(), $e); + } + } + + public function isReadable($path) { + try { + $info = $this->getFileInfo($path); + return !$info->isHidden(); + } catch (NotFoundException $e) { + return false; + } catch (ForbiddenException $e) { + return false; + } + } + + public function isUpdatable($path) { + try { + $info = $this->getFileInfo($path); + return !$info->isHidden() && !$info->isReadOnly(); + } catch (NotFoundException $e) { + return false; + } catch (ForbiddenException $e) { + return false; + } + } + + /** + * check if smbclient is installed + */ + public static function checkDependencies() { + return ( + (bool)\OC_Helper::findBinaryPath('smbclient') + || Server::NativeAvailable() + ) ? true : ['smbclient']; + } + + /** + * Test a storage for availability + * + * @return bool + */ + public function test() { + try { + return parent::test(); + } catch (Exception $e) { + return false; + } + } +} diff --git a/apps/files_external/lib/Lib/Storage/StreamWrapper.php b/apps/files_external/lib/Lib/Storage/StreamWrapper.php new file mode 100644 index 00000000000..0b4dff78c4f --- /dev/null +++ b/apps/files_external/lib/Lib/Storage/StreamWrapper.php @@ -0,0 +1,128 @@ + + * @author Jörn Friedrich Dreyer + * @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 OCA\Files_External\Lib\Storage; + +abstract class StreamWrapper extends \OC\Files\Storage\Common { + + /** + * @param string $path + * @return string|null + */ + abstract public function constructUrl($path); + + public function mkdir($path) { + return mkdir($this->constructUrl($path)); + } + + public function rmdir($path) { + if ($this->is_dir($path) && $this->isDeletable($path)) { + $dh = $this->opendir($path); + if (!is_resource($dh)) { + return false; + } + while (($file = readdir($dh)) !== false) { + if ($this->is_dir($path . '/' . $file)) { + $this->rmdir($path . '/' . $file); + } else { + $this->unlink($path . '/' . $file); + } + } + $url = $this->constructUrl($path); + $success = rmdir($url); + clearstatcache(false, $url); + return $success; + } else { + return false; + } + } + + public function opendir($path) { + return opendir($this->constructUrl($path)); + } + + public function filetype($path) { + return @filetype($this->constructUrl($path)); + } + + public function file_exists($path) { + return file_exists($this->constructUrl($path)); + } + + public function unlink($path) { + $url = $this->constructUrl($path); + $success = unlink($url); + // normally unlink() is supposed to do this implicitly, + // but doing it anyway just to be sure + clearstatcache(false, $url); + return $success; + } + + public function fopen($path, $mode) { + return fopen($this->constructUrl($path), $mode); + } + + public function touch($path, $mtime = null) { + if ($this->file_exists($path)) { + if (is_null($mtime)) { + $fh = $this->fopen($path, 'a'); + fwrite($fh, ''); + fclose($fh); + + return true; + } else { + return false; //not supported + } + } else { + $this->file_put_contents($path, ''); + return true; + } + } + + /** + * @param string $path + * @param string $target + */ + public function getFile($path, $target) { + return copy($this->constructUrl($path), $target); + } + + /** + * @param string $target + */ + public function uploadFile($path, $target) { + return copy($path, $this->constructUrl($target)); + } + + public function rename($path1, $path2) { + return rename($this->constructUrl($path1), $this->constructUrl($path2)); + } + + public function stat($path) { + return stat($this->constructUrl($path)); + } + +} diff --git a/apps/files_external/lib/Lib/Storage/Swift.php b/apps/files_external/lib/Lib/Storage/Swift.php new file mode 100644 index 00000000000..4578cd9a5c7 --- /dev/null +++ b/apps/files_external/lib/Lib/Storage/Swift.php @@ -0,0 +1,599 @@ + + * @author Benjamin Liles + * @author Christian Berendt + * @author Daniel Tosello + * @author Felix Moeller + * @author Jörn Friedrich Dreyer + * @author Martin Mattel + * @author Morris Jobke + * @author Philipp Kapfer + * @author Robin Appelman + * @author Robin McCorkell + * @author Thomas Müller + * @author Tim Dettrick + * @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 OCA\Files_External\Lib\Storage; + +use Guzzle\Http\Url; +use Guzzle\Http\Exception\ClientErrorResponseException; +use Icewind\Streams\IteratorDirectory; +use OpenCloud; +use OpenCloud\Common\Exceptions; +use OpenCloud\OpenStack; +use OpenCloud\Rackspace; +use OpenCloud\ObjectStore\Resource\DataObject; +use OpenCloud\ObjectStore\Exception; + +class Swift extends \OC\Files\Storage\Common { + + /** + * @var \OpenCloud\ObjectStore\Service + */ + private $connection; + /** + * @var \OpenCloud\ObjectStore\Resource\Container + */ + private $container; + /** + * @var \OpenCloud\OpenStack + */ + private $anchor; + /** + * @var string + */ + private $bucket; + /** + * Connection parameters + * + * @var array + */ + private $params; + /** + * @var array + */ + private static $tmpFiles = array(); + + /** + * @param string $path + */ + private function normalizePath($path) { + $path = trim($path, '/'); + + if (!$path) { + $path = '.'; + } + + $path = str_replace('#', '%23', $path); + + return $path; + } + + const SUBCONTAINER_FILE = '.subcontainers'; + + /** + * translate directory path to container name + * + * @param string $path + * @return string + */ + private function getContainerName($path) { + $path = trim(trim($this->root, '/') . "/" . $path, '/.'); + return str_replace('/', '\\', $path); + } + + /** + * @param string $path + */ + private function doesObjectExist($path) { + try { + $this->getContainer()->getPartialObject($path); + return true; + } catch (ClientErrorResponseException $e) { + // Expected response is "404 Not Found", so only log if it isn't + if ($e->getResponse()->getStatusCode() !== 404) { + \OCP\Util::writeLog('files_external', $e->getMessage(), \OCP\Util::ERROR); + } + return false; + } + } + + public function __construct($params) { + if ((empty($params['key']) and empty($params['password'])) + or empty($params['user']) or empty($params['bucket']) + or empty($params['region']) + ) { + throw new \Exception("API Key or password, Username, Bucket and Region have to be configured."); + } + + $this->id = 'swift::' . $params['user'] . md5($params['bucket']); + + $bucketUrl = Url::factory($params['bucket']); + if ($bucketUrl->isAbsolute()) { + $this->bucket = end(($bucketUrl->getPathSegments())); + $params['endpoint_url'] = $bucketUrl->addPath('..')->normalizePath(); + } else { + $this->bucket = $params['bucket']; + } + + if (empty($params['url'])) { + $params['url'] = 'https://identity.api.rackspacecloud.com/v2.0/'; + } + + if (empty($params['service_name'])) { + $params['service_name'] = 'cloudFiles'; + } + + $this->params = $params; + } + + public function mkdir($path) { + $path = $this->normalizePath($path); + + if ($this->is_dir($path)) { + return false; + } + + if ($path !== '.') { + $path .= '/'; + } + + try { + $customHeaders = array('content-type' => 'httpd/unix-directory'); + $metadataHeaders = DataObject::stockHeaders(array()); + $allHeaders = $customHeaders + $metadataHeaders; + $this->getContainer()->uploadObject($path, '', $allHeaders); + } catch (Exceptions\CreateUpdateError $e) { + \OCP\Util::writeLog('files_external', $e->getMessage(), \OCP\Util::ERROR); + return false; + } + + return true; + } + + public function file_exists($path) { + $path = $this->normalizePath($path); + + if ($path !== '.' && $this->is_dir($path)) { + $path .= '/'; + } + + return $this->doesObjectExist($path); + } + + public function rmdir($path) { + $path = $this->normalizePath($path); + + if (!$this->is_dir($path) || !$this->isDeletable($path)) { + return false; + } + + $dh = $this->opendir($path); + while ($file = readdir($dh)) { + if (\OC\Files\Filesystem::isIgnoredDir($file)) { + continue; + } + + if ($this->is_dir($path . '/' . $file)) { + $this->rmdir($path . '/' . $file); + } else { + $this->unlink($path . '/' . $file); + } + } + + try { + $this->getContainer()->dataObject()->setName($path . '/')->delete(); + } catch (Exceptions\DeleteError $e) { + \OCP\Util::writeLog('files_external', $e->getMessage(), \OCP\Util::ERROR); + return false; + } + + return true; + } + + public function opendir($path) { + $path = $this->normalizePath($path); + + if ($path === '.') { + $path = ''; + } else { + $path .= '/'; + } + + $path = str_replace('%23', '#', $path); // the prefix is sent as a query param, so revert the encoding of # + + try { + $files = array(); + /** @var OpenCloud\Common\Collection $objects */ + $objects = $this->getContainer()->objectList(array( + 'prefix' => $path, + 'delimiter' => '/' + )); + + /** @var OpenCloud\ObjectStore\Resource\DataObject $object */ + foreach ($objects as $object) { + $file = basename($object->getName()); + if ($file !== basename($path)) { + $files[] = $file; + } + } + + return IteratorDirectory::wrap($files); + } catch (\Exception $e) { + \OCP\Util::writeLog('files_external', $e->getMessage(), \OCP\Util::ERROR); + return false; + } + + } + + public function stat($path) { + $path = $this->normalizePath($path); + + if ($path === '.') { + $path = ''; + } else if ($this->is_dir($path)) { + $path .= '/'; + } + + try { + /** @var DataObject $object */ + $object = $this->getContainer()->getPartialObject($path); + } catch (ClientErrorResponseException $e) { + \OCP\Util::writeLog('files_external', $e->getMessage(), \OCP\Util::ERROR); + return false; + } + + $dateTime = \DateTime::createFromFormat(\DateTime::RFC1123, $object->getLastModified()); + if ($dateTime !== false) { + $mtime = $dateTime->getTimestamp(); + } else { + $mtime = null; + } + $objectMetadata = $object->getMetadata(); + $metaTimestamp = $objectMetadata->getProperty('timestamp'); + if (isset($metaTimestamp)) { + $mtime = $metaTimestamp; + } + + if (!empty($mtime)) { + $mtime = floor($mtime); + } + + $stat = array(); + $stat['size'] = (int)$object->getContentLength(); + $stat['mtime'] = $mtime; + $stat['atime'] = time(); + return $stat; + } + + public function filetype($path) { + $path = $this->normalizePath($path); + + if ($path !== '.' && $this->doesObjectExist($path)) { + return 'file'; + } + + if ($path !== '.') { + $path .= '/'; + } + + if ($this->doesObjectExist($path)) { + return 'dir'; + } + } + + public function unlink($path) { + $path = $this->normalizePath($path); + + if ($this->is_dir($path)) { + return $this->rmdir($path); + } + + try { + $this->getContainer()->dataObject()->setName($path)->delete(); + } catch (ClientErrorResponseException $e) { + \OCP\Util::writeLog('files_external', $e->getMessage(), \OCP\Util::ERROR); + return false; + } + + return true; + } + + public function fopen($path, $mode) { + $path = $this->normalizePath($path); + + switch ($mode) { + case 'r': + case 'rb': + try { + $c = $this->getContainer(); + $streamFactory = new \Guzzle\Stream\PhpStreamRequestFactory(); + $streamInterface = $streamFactory->fromRequest( + $c->getClient() + ->get($c->getUrl($path))); + $streamInterface->rewind(); + $stream = $streamInterface->getStream(); + stream_context_set_option($stream, 'swift','content', $streamInterface); + if(!strrpos($streamInterface + ->getMetaData('wrapper_data')[0], '404 Not Found')) { + return $stream; + } + return false; + } catch (\Guzzle\Http\Exception\BadResponseException $e) { + \OCP\Util::writeLog('files_external', $e->getMessage(), \OCP\Util::ERROR); + 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 = \OCP\Files::tmpFile($ext); + \OC\Files\Stream\Close::registerCallback($tmpFile, array($this, 'writeBack')); + // Fetch existing file if required + if ($mode[0] !== 'w' && $this->file_exists($path)) { + if ($mode[0] === 'x') { + // File cannot already exist + return false; + } + $source = $this->fopen($path, 'r'); + file_put_contents($tmpFile, $source); + // Seek to end if required + if ($mode[0] === 'a') { + fseek($tmpFile, 0, SEEK_END); + } + } + self::$tmpFiles[$tmpFile] = $path; + + return fopen('close://' . $tmpFile, $mode); + } + } + + public function touch($path, $mtime = null) { + $path = $this->normalizePath($path); + if (is_null($mtime)) { + $mtime = time(); + } + $metadata = array('timestamp' => $mtime); + if ($this->file_exists($path)) { + if ($this->is_dir($path) && $path != '.') { + $path .= '/'; + } + + $object = $this->getContainer()->getPartialObject($path); + $object->saveMetadata($metadata); + return true; + } else { + $mimeType = \OC::$server->getMimeTypeDetector()->detectPath($path); + $customHeaders = array('content-type' => $mimeType); + $metadataHeaders = DataObject::stockHeaders($metadata); + $allHeaders = $customHeaders + $metadataHeaders; + $this->getContainer()->uploadObject($path, '', $allHeaders); + return true; + } + } + + public function copy($path1, $path2) { + $path1 = $this->normalizePath($path1); + $path2 = $this->normalizePath($path2); + + $fileType = $this->filetype($path1); + if ($fileType === 'file') { + + // make way + $this->unlink($path2); + + try { + $source = $this->getContainer()->getPartialObject($path1); + $source->copy($this->bucket . '/' . $path2); + } catch (ClientErrorResponseException $e) { + \OCP\Util::writeLog('files_external', $e->getMessage(), \OCP\Util::ERROR); + return false; + } + + } else if ($fileType === 'dir') { + + // make way + $this->unlink($path2); + + try { + $source = $this->getContainer()->getPartialObject($path1 . '/'); + $source->copy($this->bucket . '/' . $path2 . '/'); + } catch (ClientErrorResponseException $e) { + \OCP\Util::writeLog('files_external', $e->getMessage(), \OCP\Util::ERROR); + return false; + } + + $dh = $this->opendir($path1); + while ($file = readdir($dh)) { + if (\OC\Files\Filesystem::isIgnoredDir($file)) { + continue; + } + + $source = $path1 . '/' . $file; + $target = $path2 . '/' . $file; + $this->copy($source, $target); + } + + } else { + //file does not exist + return false; + } + + return true; + } + + public function rename($path1, $path2) { + $path1 = $this->normalizePath($path1); + $path2 = $this->normalizePath($path2); + + $fileType = $this->filetype($path1); + + if ($fileType === 'dir' || $fileType === 'file') { + + // make way + $this->unlink($path2); + + // copy + if ($this->copy($path1, $path2) === false) { + return false; + } + + // cleanup + if ($this->unlink($path1) === false) { + $this->unlink($path2); + return false; + } + + return true; + } + + return false; + } + + public function getId() { + return $this->id; + } + + /** + * Returns the connection + * + * @return OpenCloud\ObjectStore\Service connected client + * @throws \Exception if connection could not be made + */ + public function getConnection() { + if (!is_null($this->connection)) { + return $this->connection; + } + + $settings = array( + 'username' => $this->params['user'], + ); + + if (!empty($this->params['password'])) { + $settings['password'] = $this->params['password']; + } else if (!empty($this->params['key'])) { + $settings['apiKey'] = $this->params['key']; + } + + if (!empty($this->params['tenant'])) { + $settings['tenantName'] = $this->params['tenant']; + } + + if (!empty($this->params['timeout'])) { + $settings['timeout'] = $this->params['timeout']; + } + + if (isset($settings['apiKey'])) { + $this->anchor = new Rackspace($this->params['url'], $settings); + } else { + $this->anchor = new OpenStack($this->params['url'], $settings); + } + + $connection = $this->anchor->objectStoreService($this->params['service_name'], $this->params['region']); + + if (!empty($this->params['endpoint_url'])) { + $endpoint = $connection->getEndpoint(); + $endpoint->setPublicUrl($this->params['endpoint_url']); + $endpoint->setPrivateUrl($this->params['endpoint_url']); + $connection->setEndpoint($endpoint); + } + + $this->connection = $connection; + + return $this->connection; + } + + /** + * Returns the initialized object store container. + * + * @return OpenCloud\ObjectStore\Resource\Container + */ + public function getContainer() { + if (!is_null($this->container)) { + return $this->container; + } + + try { + $this->container = $this->getConnection()->getContainer($this->bucket); + } catch (ClientErrorResponseException $e) { + $this->container = $this->getConnection()->createContainer($this->bucket); + } + + if (!$this->file_exists('.')) { + $this->mkdir('.'); + } + + return $this->container; + } + + public function writeBack($tmpFile) { + if (!isset(self::$tmpFiles[$tmpFile])) { + return false; + } + $fileData = fopen($tmpFile, 'r'); + $this->getContainer()->uploadObject(self::$tmpFiles[$tmpFile], $fileData); + unlink($tmpFile); + } + + public function hasUpdated($path, $time) { + if ($this->is_file($path)) { + return parent::hasUpdated($path, $time); + } + $path = $this->normalizePath($path); + $dh = $this->opendir($path); + $content = array(); + while (($file = readdir($dh)) !== false) { + $content[] = $file; + } + if ($path === '.') { + $path = ''; + } + $cachedContent = $this->getCache()->getFolderContents($path); + $cachedNames = array_map(function ($content) { + return $content['name']; + }, $cachedContent); + sort($cachedNames); + sort($content); + return $cachedNames != $content; + } + + /** + * check if curl is installed + */ + public static function checkDependencies() { + return true; + } + +} diff --git a/apps/files_external/lib/storage/amazons3.php b/apps/files_external/lib/storage/amazons3.php deleted file mode 100644 index 42df1deffb0..00000000000 --- a/apps/files_external/lib/storage/amazons3.php +++ /dev/null @@ -1,634 +0,0 @@ - - * @author Arthur Schiwon - * @author Bart Visscher - * @author Christian Berendt - * @author Christopher T. Johnson - * @author Johan Björk - * @author Jörn Friedrich Dreyer - * @author Martin Mattel - * @author Michael Gapczynski - * @author Morris Jobke - * @author Philipp Kapfer - * @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 OCA\Files_External\Lib\Storage; - -set_include_path(get_include_path() . PATH_SEPARATOR . - \OC_App::getAppPath('files_external') . '/3rdparty/aws-sdk-php'); -require 'aws-autoloader.php'; - -use Aws\S3\S3Client; -use Aws\S3\Exception\S3Exception; -use Icewind\Streams\IteratorDirectory; - -class AmazonS3 extends \OC\Files\Storage\Common { - - /** - * @var \Aws\S3\S3Client - */ - private $connection; - /** - * @var string - */ - private $bucket; - /** - * @var array - */ - private static $tmpFiles = array(); - /** - * @var array - */ - private $params; - /** - * @var bool - */ - private $test = false; - /** - * @var int - */ - private $timeout = 15; - /** - * @var int in seconds - */ - private $rescanDelay = 10; - - /** - * @param string $path - * @return string correctly encoded path - */ - private function normalizePath($path) { - $path = trim($path, '/'); - - if (!$path) { - $path = '.'; - } - - return $path; - } - - /** - * when running the tests wait to let the buckets catch up - */ - private function testTimeout() { - if ($this->test) { - sleep($this->timeout); - } - } - - private function isRoot($path) { - return $path === '.'; - } - - private function cleanKey($path) { - if ($this->isRoot($path)) { - return '/'; - } - return $path; - } - - public function __construct($params) { - if (empty($params['key']) || empty($params['secret']) || empty($params['bucket'])) { - throw new \Exception("Access Key, Secret and Bucket have to be configured."); - } - - $this->id = 'amazon::' . $params['bucket']; - $this->updateLegacyId($params); - - $this->bucket = $params['bucket']; - $this->test = isset($params['test']); - $this->timeout = (!isset($params['timeout'])) ? 15 : $params['timeout']; - $this->rescanDelay = (!isset($params['rescanDelay'])) ? 10 : $params['rescanDelay']; - $params['region'] = empty($params['region']) ? 'eu-west-1' : $params['region']; - $params['hostname'] = empty($params['hostname']) ? 's3.amazonaws.com' : $params['hostname']; - if (!isset($params['port']) || $params['port'] === '') { - $params['port'] = ($params['use_ssl'] === false) ? 80 : 443; - } - $this->params = $params; - } - - /** - * Updates old storage ids (v0.2.1 and older) that are based on key and secret to new ones based on the bucket name. - * TODO Do this in an update.php. requires iterating over all users and loading the mount.json from their home - * - * @param array $params - */ - public function updateLegacyId (array $params) { - $oldId = 'amazon::' . $params['key'] . md5($params['secret']); - - // find by old id or bucket - $stmt = \OC::$server->getDatabaseConnection()->prepare( - 'SELECT `numeric_id`, `id` FROM `*PREFIX*storages` WHERE `id` IN (?, ?)' - ); - $stmt->execute(array($oldId, $this->id)); - while ($row = $stmt->fetch()) { - $storages[$row['id']] = $row['numeric_id']; - } - - if (isset($storages[$this->id]) && isset($storages[$oldId])) { - // if both ids exist, delete the old storage and corresponding filecache entries - \OC\Files\Cache\Storage::remove($oldId); - } else if (isset($storages[$oldId])) { - // if only the old id exists do an update - $stmt = \OC::$server->getDatabaseConnection()->prepare( - 'UPDATE `*PREFIX*storages` SET `id` = ? WHERE `id` = ?' - ); - $stmt->execute(array($this->id, $oldId)); - } - // only the bucket based id may exist, do nothing - } - - /** - * Remove a file or folder - * - * @param string $path - * @return bool - */ - protected function remove($path) { - // remember fileType to reduce http calls - $fileType = $this->filetype($path); - if ($fileType === 'dir') { - return $this->rmdir($path); - } else if ($fileType === 'file') { - return $this->unlink($path); - } else { - return false; - } - } - - public function mkdir($path) { - $path = $this->normalizePath($path); - - if ($this->is_dir($path)) { - return false; - } - - try { - $this->getConnection()->putObject(array( - 'Bucket' => $this->bucket, - 'Key' => $path . '/', - 'Body' => '', - 'ContentType' => 'httpd/unix-directory' - )); - $this->testTimeout(); - } catch (S3Exception $e) { - \OCP\Util::logException('files_external', $e); - return false; - } - - return true; - } - - public function file_exists($path) { - return $this->filetype($path) !== false; - } - - - public function rmdir($path) { - $path = $this->normalizePath($path); - - if ($this->isRoot($path)) { - return $this->clearBucket(); - } - - if (!$this->file_exists($path)) { - return false; - } - - return $this->batchDelete($path); - } - - protected function clearBucket() { - try { - $this->getConnection()->clearBucket($this->bucket); - return true; - // clearBucket() is not working with Ceph, so if it fails we try the slower approach - } catch (\Exception $e) { - return $this->batchDelete(); - } - return false; - } - - private function batchDelete ($path = null) { - $params = array( - 'Bucket' => $this->bucket - ); - if ($path !== null) { - $params['Prefix'] = $path . '/'; - } - try { - // Since there are no real directories on S3, we need - // to delete all objects prefixed with the path. - do { - // instead of the iterator, manually loop over the list ... - $objects = $this->getConnection()->listObjects($params); - // ... so we can delete the files in batches - $this->getConnection()->deleteObjects(array( - 'Bucket' => $this->bucket, - 'Objects' => $objects['Contents'] - )); - $this->testTimeout(); - // we reached the end when the list is no longer truncated - } while ($objects['IsTruncated']); - } catch (S3Exception $e) { - \OCP\Util::logException('files_external', $e); - return false; - } - return true; - } - - public function opendir($path) { - $path = $this->normalizePath($path); - - if ($this->isRoot($path)) { - $path = ''; - } else { - $path .= '/'; - } - - try { - $files = array(); - $result = $this->getConnection()->getIterator('ListObjects', array( - 'Bucket' => $this->bucket, - 'Delimiter' => '/', - 'Prefix' => $path - ), array('return_prefixes' => true)); - - foreach ($result as $object) { - if (isset($object['Key']) && $object['Key'] === $path) { - // it's the directory itself, skip - continue; - } - $file = basename( - isset($object['Key']) ? $object['Key'] : $object['Prefix'] - ); - $files[] = $file; - } - - return IteratorDirectory::wrap($files); - } catch (S3Exception $e) { - \OCP\Util::logException('files_external', $e); - return false; - } - } - - public function stat($path) { - $path = $this->normalizePath($path); - - try { - $stat = array(); - if ($this->is_dir($path)) { - //folders don't really exist - $stat['size'] = -1; //unknown - $stat['mtime'] = time() - $this->rescanDelay * 1000; - } else { - $result = $this->getConnection()->headObject(array( - 'Bucket' => $this->bucket, - 'Key' => $path - )); - - $stat['size'] = $result['ContentLength'] ? $result['ContentLength'] : 0; - if ($result['Metadata']['lastmodified']) { - $stat['mtime'] = strtotime($result['Metadata']['lastmodified']); - } else { - $stat['mtime'] = strtotime($result['LastModified']); - } - } - $stat['atime'] = time(); - - return $stat; - } catch(S3Exception $e) { - \OCP\Util::logException('files_external', $e); - return false; - } - } - - public function filetype($path) { - $path = $this->normalizePath($path); - - if ($this->isRoot($path)) { - return 'dir'; - } - - try { - if ($this->getConnection()->doesObjectExist($this->bucket, $path)) { - return 'file'; - } - if ($this->getConnection()->doesObjectExist($this->bucket, $path.'/')) { - return 'dir'; - } - } catch (S3Exception $e) { - \OCP\Util::logException('files_external', $e); - return false; - } - - return false; - } - - public function unlink($path) { - $path = $this->normalizePath($path); - - if ($this->is_dir($path)) { - return $this->rmdir($path); - } - - try { - $this->getConnection()->deleteObject(array( - 'Bucket' => $this->bucket, - 'Key' => $path - )); - $this->testTimeout(); - } catch (S3Exception $e) { - \OCP\Util::logException('files_external', $e); - return false; - } - - return true; - } - - public function fopen($path, $mode) { - $path = $this->normalizePath($path); - - switch ($mode) { - case 'r': - case 'rb': - $tmpFile = \OCP\Files::tmpFile(); - self::$tmpFiles[$tmpFile] = $path; - - try { - $this->getConnection()->getObject(array( - 'Bucket' => $this->bucket, - 'Key' => $path, - 'SaveAs' => $tmpFile - )); - } catch (S3Exception $e) { - \OCP\Util::logException('files_external', $e); - return false; - } - - return fopen($tmpFile, 'r'); - 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 = \OCP\Files::tmpFile($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 touch($path, $mtime = null) { - $path = $this->normalizePath($path); - - $metadata = array(); - if (is_null($mtime)) { - $mtime = time(); - } - $metadata = [ - 'lastmodified' => gmdate(\Aws\Common\Enum\DateFormat::RFC1123, $mtime) - ]; - - $fileType = $this->filetype($path); - try { - if ($fileType !== false) { - if ($fileType === 'dir' && ! $this->isRoot($path)) { - $path .= '/'; - } - $this->getConnection()->copyObject([ - 'Bucket' => $this->bucket, - 'Key' => $this->cleanKey($path), - 'Metadata' => $metadata, - 'CopySource' => $this->bucket . '/' . $path, - 'MetadataDirective' => 'REPLACE', - ]); - $this->testTimeout(); - } else { - $mimeType = \OC::$server->getMimeTypeDetector()->detectPath($path); - $this->getConnection()->putObject([ - 'Bucket' => $this->bucket, - 'Key' => $this->cleanKey($path), - 'Metadata' => $metadata, - 'Body' => '', - 'ContentType' => $mimeType, - 'MetadataDirective' => 'REPLACE', - ]); - $this->testTimeout(); - } - } catch (S3Exception $e) { - \OCP\Util::logException('files_external', $e); - return false; - } - - return true; - } - - public function copy($path1, $path2) { - $path1 = $this->normalizePath($path1); - $path2 = $this->normalizePath($path2); - - if ($this->is_file($path1)) { - try { - $this->getConnection()->copyObject(array( - 'Bucket' => $this->bucket, - 'Key' => $this->cleanKey($path2), - 'CopySource' => S3Client::encodeKey($this->bucket . '/' . $path1) - )); - $this->testTimeout(); - } catch (S3Exception $e) { - \OCP\Util::logException('files_external', $e); - return false; - } - } else { - $this->remove($path2); - - try { - $this->getConnection()->copyObject(array( - 'Bucket' => $this->bucket, - 'Key' => $path2 . '/', - 'CopySource' => S3Client::encodeKey($this->bucket . '/' . $path1 . '/') - )); - $this->testTimeout(); - } catch (S3Exception $e) { - \OCP\Util::logException('files_external', $e); - return false; - } - - $dh = $this->opendir($path1); - if (is_resource($dh)) { - while (($file = readdir($dh)) !== false) { - if (\OC\Files\Filesystem::isIgnoredDir($file)) { - continue; - } - - $source = $path1 . '/' . $file; - $target = $path2 . '/' . $file; - $this->copy($source, $target); - } - } - } - - return true; - } - - public function rename($path1, $path2) { - $path1 = $this->normalizePath($path1); - $path2 = $this->normalizePath($path2); - - if ($this->is_file($path1)) { - - if ($this->copy($path1, $path2) === false) { - return false; - } - - if ($this->unlink($path1) === false) { - $this->unlink($path2); - return false; - } - } else { - - if ($this->copy($path1, $path2) === false) { - return false; - } - - if ($this->rmdir($path1) === false) { - $this->rmdir($path2); - return false; - } - } - - return true; - } - - public function test() { - $test = $this->getConnection()->getBucketAcl(array( - 'Bucket' => $this->bucket, - )); - if (isset($test) && !is_null($test->getPath('Owner/ID'))) { - return true; - } - return false; - } - - public function getId() { - return $this->id; - } - - /** - * Returns the connection - * - * @return S3Client connected client - * @throws \Exception if connection could not be made - */ - public function getConnection() { - if (!is_null($this->connection)) { - return $this->connection; - } - - $scheme = ($this->params['use_ssl'] === false) ? 'http' : 'https'; - $base_url = $scheme . '://' . $this->params['hostname'] . ':' . $this->params['port'] . '/'; - - $this->connection = S3Client::factory(array( - 'key' => $this->params['key'], - 'secret' => $this->params['secret'], - 'base_url' => $base_url, - 'region' => $this->params['region'], - S3Client::COMMAND_PARAMS => [ - 'PathStyle' => $this->params['use_path_style'], - ], - )); - - if (!$this->connection->isValidBucketName($this->bucket)) { - throw new \Exception("The configured bucket name is invalid."); - } - - if (!$this->connection->doesBucketExist($this->bucket)) { - try { - $this->connection->createBucket(array( - 'Bucket' => $this->bucket - )); - $this->connection->waitUntilBucketExists(array( - 'Bucket' => $this->bucket, - 'waiter.interval' => 1, - 'waiter.max_attempts' => 15 - )); - $this->testTimeout(); - } catch (S3Exception $e) { - \OCP\Util::logException('files_external', $e); - throw new \Exception('Creation of bucket failed. '.$e->getMessage()); - } - } - - return $this->connection; - } - - public function writeBack($tmpFile) { - if (!isset(self::$tmpFiles[$tmpFile])) { - return false; - } - - try { - $this->getConnection()->putObject(array( - 'Bucket' => $this->bucket, - 'Key' => $this->cleanKey(self::$tmpFiles[$tmpFile]), - 'SourceFile' => $tmpFile, - 'ContentType' => \OC::$server->getMimeTypeDetector()->detect($tmpFile), - 'ContentLength' => filesize($tmpFile) - )); - $this->testTimeout(); - - unlink($tmpFile); - } catch (S3Exception $e) { - \OCP\Util::logException('files_external', $e); - return false; - } - } - - /** - * check if curl is installed - */ - public static function checkDependencies() { - return true; - } - -} diff --git a/apps/files_external/lib/storage/dropbox.php b/apps/files_external/lib/storage/dropbox.php deleted file mode 100644 index 55ae7146572..00000000000 --- a/apps/files_external/lib/storage/dropbox.php +++ /dev/null @@ -1,352 +0,0 @@ - - * @author Michael Gapczynski - * @author Morris Jobke - * @author Philipp Kapfer - * @author Robin Appelman - * @author Robin McCorkell - * @author Sascha Schmidt - * @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 OCA\Files_External\Lib\Storage; - -use GuzzleHttp\Exception\RequestException; -use Icewind\Streams\IteratorDirectory; -use Icewind\Streams\RetryWrapper; - -require_once __DIR__ . '/../../3rdparty/Dropbox/autoload.php'; - -class Dropbox extends \OC\Files\Storage\Common { - - private $dropbox; - private $root; - private $id; - private $metaData = array(); - private $oauth; - - private static $tempFiles = array(); - - public function __construct($params) { - if (isset($params['configured']) && $params['configured'] == 'true' - && isset($params['app_key']) - && isset($params['app_secret']) - && isset($params['token']) - && isset($params['token_secret']) - ) { - $this->root = isset($params['root']) ? $params['root'] : ''; - $this->id = 'dropbox::'.$params['app_key'] . $params['token']. '/' . $this->root; - $this->oauth = new \Dropbox_OAuth_Curl($params['app_key'], $params['app_secret']); - $this->oauth->setToken($params['token'], $params['token_secret']); - // note: Dropbox_API connection is lazy - $this->dropbox = new \Dropbox_API($this->oauth, 'auto'); - } else { - throw new \Exception('Creating Dropbox storage failed'); - } - } - - /** - * @param string $path - */ - private function deleteMetaData($path) { - $path = ltrim($this->root.$path, '/'); - if (isset($this->metaData[$path])) { - unset($this->metaData[$path]); - return true; - } - return false; - } - - private function setMetaData($path, $metaData) { - $this->metaData[ltrim($path, '/')] = $metaData; - } - - /** - * Returns the path's metadata - * @param string $path path for which to return the metadata - * @param bool $list if true, also return the directory's contents - * @return mixed directory contents if $list is true, file metadata if $list is - * false, null if the file doesn't exist or "false" if the operation failed - */ - private function getDropBoxMetaData($path, $list = false) { - $path = ltrim($this->root.$path, '/'); - if ( ! $list && isset($this->metaData[$path])) { - return $this->metaData[$path]; - } else { - if ($list) { - try { - $response = $this->dropbox->getMetaData($path); - } catch (\Exception $exception) { - \OCP\Util::writeLog('files_external', $exception->getMessage(), \OCP\Util::ERROR); - return false; - } - $contents = array(); - if ($response && isset($response['contents'])) { - // Cache folder's contents - foreach ($response['contents'] as $file) { - if (!isset($file['is_deleted']) || !$file['is_deleted']) { - $this->setMetaData($path.'/'.basename($file['path']), $file); - $contents[] = $file; - } - } - unset($response['contents']); - } - if (!isset($response['is_deleted']) || !$response['is_deleted']) { - $this->setMetaData($path, $response); - } - // Return contents of folder only - return $contents; - } else { - try { - $requestPath = $path; - if ($path === '.') { - $requestPath = ''; - } - - $response = $this->dropbox->getMetaData($requestPath, 'false'); - if (!isset($response['is_deleted']) || !$response['is_deleted']) { - $this->setMetaData($path, $response); - return $response; - } - return null; - } catch (\Exception $exception) { - if ($exception instanceof \Dropbox_Exception_NotFound) { - // don't log, might be a file_exist check - return false; - } - \OCP\Util::writeLog('files_external', $exception->getMessage(), \OCP\Util::ERROR); - return false; - } - } - } - } - - public function getId(){ - return $this->id; - } - - public function mkdir($path) { - $path = $this->root.$path; - try { - $this->dropbox->createFolder($path); - return true; - } catch (\Exception $exception) { - \OCP\Util::writeLog('files_external', $exception->getMessage(), \OCP\Util::ERROR); - return false; - } - } - - public function rmdir($path) { - return $this->unlink($path); - } - - public function opendir($path) { - $contents = $this->getDropBoxMetaData($path, true); - if ($contents !== false) { - $files = array(); - foreach ($contents as $file) { - $files[] = basename($file['path']); - } - return IteratorDirectory::wrap($files); - } - return false; - } - - public function stat($path) { - $metaData = $this->getDropBoxMetaData($path); - if ($metaData) { - $stat['size'] = $metaData['bytes']; - $stat['atime'] = time(); - $stat['mtime'] = (isset($metaData['modified'])) ? strtotime($metaData['modified']) : time(); - return $stat; - } - return false; - } - - public function filetype($path) { - if ($path == '' || $path == '/') { - return 'dir'; - } else { - $metaData = $this->getDropBoxMetaData($path); - if ($metaData) { - if ($metaData['is_dir'] == 'true') { - return 'dir'; - } else { - return 'file'; - } - } - } - return false; - } - - public function file_exists($path) { - if ($path == '' || $path == '/') { - return true; - } - if ($this->getDropBoxMetaData($path)) { - return true; - } - return false; - } - - public function unlink($path) { - try { - $this->dropbox->delete($this->root.$path); - $this->deleteMetaData($path); - return true; - } catch (\Exception $exception) { - \OCP\Util::writeLog('files_external', $exception->getMessage(), \OCP\Util::ERROR); - return false; - } - } - - public function rename($path1, $path2) { - try { - // overwrite if target file exists and is not a directory - $destMetaData = $this->getDropBoxMetaData($path2); - if (isset($destMetaData) && $destMetaData !== false && !$destMetaData['is_dir']) { - $this->unlink($path2); - } - $this->dropbox->move($this->root.$path1, $this->root.$path2); - $this->deleteMetaData($path1); - return true; - } catch (\Exception $exception) { - \OCP\Util::writeLog('files_external', $exception->getMessage(), \OCP\Util::ERROR); - return false; - } - } - - public function copy($path1, $path2) { - $path1 = $this->root.$path1; - $path2 = $this->root.$path2; - try { - $this->dropbox->copy($path1, $path2); - return true; - } catch (\Exception $exception) { - \OCP\Util::writeLog('files_external', $exception->getMessage(), \OCP\Util::ERROR); - return false; - } - } - - public function fopen($path, $mode) { - $path = $this->root.$path; - switch ($mode) { - case 'r': - case 'rb': - try { - // slashes need to stay - $encodedPath = str_replace('%2F', '/', rawurlencode(trim($path, '/'))); - $downloadUrl = 'https://api-content.dropbox.com/1/files/auto/' . $encodedPath; - $headers = $this->oauth->getOAuthHeader($downloadUrl, [], 'GET'); - - $client = \OC::$server->getHTTPClientService()->newClient(); - try { - $response = $client->get($downloadUrl, [ - 'headers' => $headers, - 'stream' => true, - ]); - } catch (RequestException $e) { - if (!is_null($e->getResponse())) { - if ($e->getResponse()->getStatusCode() === 404) { - return false; - } else { - throw $e; - } - } else { - throw $e; - } - } - - $handle = $response->getBody(); - return RetryWrapper::wrap($handle); - } catch (\Exception $exception) { - \OCP\Util::writeLog('files_external', $exception->getMessage(), \OCP\Util::ERROR); - 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 = \OCP\Files::tmpFile($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::$tempFiles[$tmpFile] = $path; - return fopen('close://'.$tmpFile, $mode); - } - return false; - } - - public function writeBack($tmpFile) { - if (isset(self::$tempFiles[$tmpFile])) { - $handle = fopen($tmpFile, 'r'); - try { - $this->dropbox->putFile(self::$tempFiles[$tmpFile], $handle); - unlink($tmpFile); - $this->deleteMetaData(self::$tempFiles[$tmpFile]); - } catch (\Exception $exception) { - \OCP\Util::writeLog('files_external', $exception->getMessage(), \OCP\Util::ERROR); - } - } - } - - public function free_space($path) { - try { - $info = $this->dropbox->getAccountInfo(); - return $info['quota_info']['quota'] - $info['quota_info']['normal']; - } catch (\Exception $exception) { - \OCP\Util::writeLog('files_external', $exception->getMessage(), \OCP\Util::ERROR); - return false; - } - } - - public function touch($path, $mtime = null) { - if ($this->file_exists($path)) { - return false; - } else { - $this->file_put_contents($path, ''); - } - return true; - } - - /** - * check if curl is installed - */ - public static function checkDependencies() { - return true; - } - -} diff --git a/apps/files_external/lib/storage/ftp.php b/apps/files_external/lib/storage/ftp.php deleted file mode 100644 index 051c1873009..00000000000 --- a/apps/files_external/lib/storage/ftp.php +++ /dev/null @@ -1,155 +0,0 @@ - - * @author Felix Moeller - * @author Jörn Friedrich Dreyer - * @author Michael Gapczynski - * @author Morris Jobke - * @author Philipp Kapfer - * @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 OCA\Files_External\Lib\Storage; - -use Icewind\Streams\RetryWrapper; - -class FTP extends StreamWrapper{ - private $password; - private $user; - private $host; - private $secure; - private $root; - - private static $tempFiles=array(); - - public function __construct($params) { - if (isset($params['host']) && isset($params['user']) && isset($params['password'])) { - $this->host=$params['host']; - $this->user=$params['user']; - $this->password=$params['password']; - if (isset($params['secure'])) { - $this->secure = $params['secure']; - } else { - $this->secure = false; - } - $this->root=isset($params['root'])?$params['root']:'/'; - if ( ! $this->root || $this->root[0]!='/') { - $this->root='/'.$this->root; - } - if (substr($this->root, -1) !== '/') { - $this->root .= '/'; - } - } else { - throw new \Exception('Creating FTP storage failed'); - } - - } - - public function getId(){ - return 'ftp::' . $this->user . '@' . $this->host . '/' . $this->root; - } - - /** - * construct the ftp url - * @param string $path - * @return string - */ - public function constructUrl($path) { - $url='ftp'; - if ($this->secure) { - $url.='s'; - } - $url.='://'.urlencode($this->user).':'.urlencode($this->password).'@'.$this->host.$this->root.$path; - return $url; - } - - /** - * Unlinks file or directory - * @param string $path - */ - public function unlink($path) { - if ($this->is_dir($path)) { - return $this->rmdir($path); - } - else { - $url = $this->constructUrl($path); - $result = unlink($url); - clearstatcache(true, $url); - return $result; - } - } - public function fopen($path,$mode) { - switch($mode) { - case 'r': - case 'rb': - case 'w': - case 'wb': - case 'a': - case 'ab': - //these are supported by the wrapper - $context = stream_context_create(array('ftp' => array('overwrite' => true))); - $handle = fopen($this->constructUrl($path), $mode, false, $context); - return RetryWrapper::wrap($handle); - case 'r+': - case 'w+': - case 'wb+': - case 'a+': - case 'x': - case 'x+': - case 'c': - case 'c+': - //emulate these - if (strrpos($path, '.')!==false) { - $ext=substr($path, strrpos($path, '.')); - } else { - $ext=''; - } - $tmpFile=\OCP\Files::tmpFile($ext); - \OC\Files\Stream\Close::registerCallback($tmpFile, array($this, 'writeBack')); - if ($this->file_exists($path)) { - $this->getFile($path, $tmpFile); - } - self::$tempFiles[$tmpFile]=$path; - return fopen('close://'.$tmpFile, $mode); - } - return false; - } - - public function writeBack($tmpFile) { - if (isset(self::$tempFiles[$tmpFile])) { - $this->uploadFile($tmpFile, self::$tempFiles[$tmpFile]); - unlink($tmpFile); - } - } - - /** - * check if php-ftp is installed - */ - public static function checkDependencies() { - if (function_exists('ftp_login')) { - return(true); - } else { - return array('ftp'); - } - } - -} diff --git a/apps/files_external/lib/storage/google.php b/apps/files_external/lib/storage/google.php deleted file mode 100644 index 13e89299c22..00000000000 --- a/apps/files_external/lib/storage/google.php +++ /dev/null @@ -1,710 +0,0 @@ - - * @author Arthur Schiwon - * @author Bart Visscher - * @author Christopher Schäpers - * @author Jörn Friedrich Dreyer - * @author Lukas Reschke - * @author Michael Gapczynski - * @author Morris Jobke - * @author Philipp Kapfer - * @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 OCA\Files_External\Lib\Storage; - -use GuzzleHttp\Exception\RequestException; -use Icewind\Streams\IteratorDirectory; -use Icewind\Streams\RetryWrapper; - -set_include_path(get_include_path().PATH_SEPARATOR. - \OC_App::getAppPath('files_external').'/3rdparty/google-api-php-client/src'); -require_once 'Google/Client.php'; -require_once 'Google/Service/Drive.php'; - -class Google extends \OC\Files\Storage\Common { - - private $client; - private $id; - private $service; - private $driveFiles; - - private static $tempFiles = array(); - - // Google Doc mimetypes - const FOLDER = 'application/vnd.google-apps.folder'; - const DOCUMENT = 'application/vnd.google-apps.document'; - const SPREADSHEET = 'application/vnd.google-apps.spreadsheet'; - const DRAWING = 'application/vnd.google-apps.drawing'; - const PRESENTATION = 'application/vnd.google-apps.presentation'; - - public function __construct($params) { - if (isset($params['configured']) && $params['configured'] === 'true' - && isset($params['client_id']) && isset($params['client_secret']) - && isset($params['token']) - ) { - $this->client = new \Google_Client(); - $this->client->setClientId($params['client_id']); - $this->client->setClientSecret($params['client_secret']); - $this->client->setScopes(array('https://www.googleapis.com/auth/drive')); - $this->client->setAccessToken($params['token']); - // if curl isn't available we're likely to run into - // https://github.com/google/google-api-php-client/issues/59 - // - disable gzip to avoid it. - if (!function_exists('curl_version') || !function_exists('curl_exec')) { - $this->client->setClassConfig("Google_Http_Request", "disable_gzip", true); - } - // note: API connection is lazy - $this->service = new \Google_Service_Drive($this->client); - $token = json_decode($params['token'], true); - $this->id = 'google::'.substr($params['client_id'], 0, 30).$token['created']; - } else { - throw new \Exception('Creating Google storage failed'); - } - } - - public function getId() { - return $this->id; - } - - /** - * Get the Google_Service_Drive_DriveFile object for the specified path. - * Returns false on failure. - * @param string $path - * @return \Google_Service_Drive_DriveFile|false - */ - private function getDriveFile($path) { - // Remove leading and trailing slashes - $path = trim($path, '/'); - if (isset($this->driveFiles[$path])) { - return $this->driveFiles[$path]; - } else if ($path === '') { - $root = $this->service->files->get('root'); - $this->driveFiles[$path] = $root; - return $root; - } else { - // Google Drive SDK does not have methods for retrieving files by path - // Instead we must find the id of the parent folder of the file - $parentId = $this->getDriveFile('')->getId(); - $folderNames = explode('/', $path); - $path = ''; - // Loop through each folder of this path to get to the file - foreach ($folderNames as $name) { - // Reconstruct path from beginning - if ($path === '') { - $path .= $name; - } else { - $path .= '/'.$name; - } - if (isset($this->driveFiles[$path])) { - $parentId = $this->driveFiles[$path]->getId(); - } else { - $q = "title='" . str_replace("'","\\'", $name) . "' and '" . str_replace("'","\\'", $parentId) . "' in parents and trashed = false"; - $result = $this->service->files->listFiles(array('q' => $q))->getItems(); - if (!empty($result)) { - // Google Drive allows files with the same name, ownCloud doesn't - if (count($result) > 1) { - $this->onDuplicateFileDetected($path); - return false; - } else { - $file = current($result); - $this->driveFiles[$path] = $file; - $parentId = $file->getId(); - } - } else { - // Google Docs have no extension in their title, so try without extension - $pos = strrpos($path, '.'); - if ($pos !== false) { - $pathWithoutExt = substr($path, 0, $pos); - $file = $this->getDriveFile($pathWithoutExt); - if ($file) { - // Switch cached Google_Service_Drive_DriveFile to the correct index - unset($this->driveFiles[$pathWithoutExt]); - $this->driveFiles[$path] = $file; - $parentId = $file->getId(); - } else { - return false; - } - } else { - return false; - } - } - } - } - return $this->driveFiles[$path]; - } - } - - /** - * Set the Google_Service_Drive_DriveFile object in the cache - * @param string $path - * @param Google_Service_Drive_DriveFile|false $file - */ - private function setDriveFile($path, $file) { - $path = trim($path, '/'); - $this->driveFiles[$path] = $file; - if ($file === false) { - // Set all child paths as false - $len = strlen($path); - foreach ($this->driveFiles as $key => $file) { - if (substr($key, 0, $len) === $path) { - $this->driveFiles[$key] = false; - } - } - } - } - - /** - * Write a log message to inform about duplicate file names - * @param string $path - */ - private function onDuplicateFileDetected($path) { - $about = $this->service->about->get(); - $user = $about->getName(); - \OCP\Util::writeLog('files_external', - 'Ignoring duplicate file name: '.$path.' on Google Drive for Google user: '.$user, - \OCP\Util::INFO - ); - } - - /** - * Generate file extension for a Google Doc, choosing Open Document formats for download - * @param string $mimetype - * @return string - */ - private function getGoogleDocExtension($mimetype) { - if ($mimetype === self::DOCUMENT) { - return 'odt'; - } else if ($mimetype === self::SPREADSHEET) { - return 'ods'; - } else if ($mimetype === self::DRAWING) { - return 'jpg'; - } else if ($mimetype === self::PRESENTATION) { - // Download as .odp is not available - return 'pdf'; - } else { - return ''; - } - } - - public function mkdir($path) { - if (!$this->is_dir($path)) { - $parentFolder = $this->getDriveFile(dirname($path)); - if ($parentFolder) { - $folder = new \Google_Service_Drive_DriveFile(); - $folder->setTitle(basename($path)); - $folder->setMimeType(self::FOLDER); - $parent = new \Google_Service_Drive_ParentReference(); - $parent->setId($parentFolder->getId()); - $folder->setParents(array($parent)); - $result = $this->service->files->insert($folder); - if ($result) { - $this->setDriveFile($path, $result); - } - return (bool)$result; - } - } - return false; - } - - public function rmdir($path) { - if (!$this->isDeletable($path)) { - return false; - } - if (trim($path, '/') === '') { - $dir = $this->opendir($path); - if(is_resource($dir)) { - while (($file = readdir($dir)) !== false) { - if (!\OC\Files\Filesystem::isIgnoredDir($file)) { - if (!$this->unlink($path.'/'.$file)) { - return false; - } - } - } - closedir($dir); - } - $this->driveFiles = array(); - return true; - } else { - return $this->unlink($path); - } - } - - public function opendir($path) { - $folder = $this->getDriveFile($path); - if ($folder) { - $files = array(); - $duplicates = array(); - $pageToken = true; - while ($pageToken) { - $params = array(); - if ($pageToken !== true) { - $params['pageToken'] = $pageToken; - } - $params['q'] = "'" . str_replace("'","\\'", $folder->getId()) . "' in parents and trashed = false"; - $children = $this->service->files->listFiles($params); - foreach ($children->getItems() as $child) { - $name = $child->getTitle(); - // Check if this is a Google Doc i.e. no extension in name - $extension = $child->getFileExtension(); - if (empty($extension) - && $child->getMimeType() !== self::FOLDER - ) { - $name .= '.'.$this->getGoogleDocExtension($child->getMimeType()); - } - if ($path === '') { - $filepath = $name; - } else { - $filepath = $path.'/'.$name; - } - // Google Drive allows files with the same name, ownCloud doesn't - // Prevent opendir() from returning any duplicate files - $key = array_search($name, $files); - if ($key !== false || isset($duplicates[$filepath])) { - if (!isset($duplicates[$filepath])) { - $duplicates[$filepath] = true; - $this->setDriveFile($filepath, false); - unset($files[$key]); - $this->onDuplicateFileDetected($filepath); - } - } else { - // Cache the Google_Service_Drive_DriveFile for future use - $this->setDriveFile($filepath, $child); - $files[] = $name; - } - } - $pageToken = $children->getNextPageToken(); - } - return IteratorDirectory::wrap($files); - } else { - return false; - } - } - - public function stat($path) { - $file = $this->getDriveFile($path); - if ($file) { - $stat = array(); - if ($this->filetype($path) === 'dir') { - $stat['size'] = 0; - } else { - // Check if this is a Google Doc - if ($this->getMimeType($path) !== $file->getMimeType()) { - // Return unknown file size - $stat['size'] = \OCP\Files\FileInfo::SPACE_UNKNOWN; - } else { - $stat['size'] = $file->getFileSize(); - } - } - $stat['atime'] = strtotime($file->getLastViewedByMeDate()); - $stat['mtime'] = strtotime($file->getModifiedDate()); - $stat['ctime'] = strtotime($file->getCreatedDate()); - return $stat; - } else { - return false; - } - } - - public function filetype($path) { - if ($path === '') { - return 'dir'; - } else { - $file = $this->getDriveFile($path); - if ($file) { - if ($file->getMimeType() === self::FOLDER) { - return 'dir'; - } else { - return 'file'; - } - } else { - return false; - } - } - } - - public function isUpdatable($path) { - $file = $this->getDriveFile($path); - if ($file) { - return $file->getEditable(); - } else { - return false; - } - } - - public function file_exists($path) { - return (bool)$this->getDriveFile($path); - } - - public function unlink($path) { - $file = $this->getDriveFile($path); - if ($file) { - $result = $this->service->files->trash($file->getId()); - if ($result) { - $this->setDriveFile($path, false); - } - return (bool)$result; - } else { - return false; - } - } - - public function rename($path1, $path2) { - $file = $this->getDriveFile($path1); - if ($file) { - $newFile = $this->getDriveFile($path2); - if (dirname($path1) === dirname($path2)) { - if ($newFile) { - // rename to the name of the target file, could be an office file without extension - $file->setTitle($newFile->getTitle()); - } else { - $file->setTitle(basename(($path2))); - } - } else { - // Change file parent - $parentFolder2 = $this->getDriveFile(dirname($path2)); - if ($parentFolder2) { - $parent = new \Google_Service_Drive_ParentReference(); - $parent->setId($parentFolder2->getId()); - $file->setParents(array($parent)); - } else { - return false; - } - } - // We need to get the object for the existing file with the same - // name (if there is one) before we do the patch. If oldfile - // exists and is a directory we have to delete it before we - // do the rename too. - $oldfile = $this->getDriveFile($path2); - if ($oldfile && $this->is_dir($path2)) { - $this->rmdir($path2); - $oldfile = false; - } - $result = $this->service->files->patch($file->getId(), $file); - if ($result) { - $this->setDriveFile($path1, false); - $this->setDriveFile($path2, $result); - if ($oldfile && $newFile) { - // only delete if they have a different id (same id can happen for part files) - if ($newFile->getId() !== $oldfile->getId()) { - $this->service->files->delete($oldfile->getId()); - } - } - } - return (bool)$result; - } else { - return false; - } - } - - public function fopen($path, $mode) { - $pos = strrpos($path, '.'); - if ($pos !== false) { - $ext = substr($path, $pos); - } else { - $ext = ''; - } - switch ($mode) { - case 'r': - case 'rb': - $file = $this->getDriveFile($path); - if ($file) { - $exportLinks = $file->getExportLinks(); - $mimetype = $this->getMimeType($path); - $downloadUrl = null; - if ($exportLinks && isset($exportLinks[$mimetype])) { - $downloadUrl = $exportLinks[$mimetype]; - } else { - $downloadUrl = $file->getDownloadUrl(); - } - if (isset($downloadUrl)) { - $request = new \Google_Http_Request($downloadUrl, 'GET', null, null); - $httpRequest = $this->client->getAuth()->sign($request); - // the library's service doesn't support streaming, so we use Guzzle instead - $client = \OC::$server->getHTTPClientService()->newClient(); - try { - $response = $client->get($downloadUrl, [ - 'headers' => $httpRequest->getRequestHeaders(), - 'stream' => true, - 'verify' => __DIR__ . '/../../3rdparty/google-api-php-client/src/Google/IO/cacerts.pem', - ]); - } catch (RequestException $e) { - if(!is_null($e->getResponse())) { - if ($e->getResponse()->getStatusCode() === 404) { - return false; - } else { - throw $e; - } - } else { - throw $e; - } - } - - $handle = $response->getBody(); - return RetryWrapper::wrap($handle); - } - } - 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+': - $tmpFile = \OCP\Files::tmpFile($ext); - \OC\Files\Stream\Close::registerCallback($tmpFile, array($this, 'writeBack')); - if ($this->file_exists($path)) { - $source = $this->fopen($path, 'rb'); - file_put_contents($tmpFile, $source); - } - self::$tempFiles[$tmpFile] = $path; - return fopen('close://'.$tmpFile, $mode); - } - } - - public function writeBack($tmpFile) { - if (isset(self::$tempFiles[$tmpFile])) { - $path = self::$tempFiles[$tmpFile]; - $parentFolder = $this->getDriveFile(dirname($path)); - if ($parentFolder) { - $mimetype = \OC::$server->getMimeTypeDetector()->detect($tmpFile); - $params = array( - 'mimeType' => $mimetype, - 'uploadType' => 'media' - ); - $result = false; - - $chunkSizeBytes = 10 * 1024 * 1024; - - $useChunking = false; - $size = filesize($tmpFile); - if ($size > $chunkSizeBytes) { - $useChunking = true; - } else { - $params['data'] = file_get_contents($tmpFile); - } - - if ($this->file_exists($path)) { - $file = $this->getDriveFile($path); - $this->client->setDefer($useChunking); - $request = $this->service->files->update($file->getId(), $file, $params); - } else { - $file = new \Google_Service_Drive_DriveFile(); - $file->setTitle(basename($path)); - $file->setMimeType($mimetype); - $parent = new \Google_Service_Drive_ParentReference(); - $parent->setId($parentFolder->getId()); - $file->setParents(array($parent)); - $this->client->setDefer($useChunking); - $request = $this->service->files->insert($file, $params); - } - - if ($useChunking) { - // Create a media file upload to represent our upload process. - $media = new \Google_Http_MediaFileUpload( - $this->client, - $request, - 'text/plain', - null, - true, - $chunkSizeBytes - ); - $media->setFileSize($size); - - // Upload the various chunks. $status will be false until the process is - // complete. - $status = false; - $handle = fopen($tmpFile, 'rb'); - while (!$status && !feof($handle)) { - $chunk = fread($handle, $chunkSizeBytes); - $status = $media->nextChunk($chunk); - } - - // The final value of $status will be the data from the API for the object - // that has been uploaded. - $result = false; - if ($status !== false) { - $result = $status; - } - - fclose($handle); - } else { - $result = $request; - } - - // Reset to the client to execute requests immediately in the future. - $this->client->setDefer(false); - - if ($result) { - $this->setDriveFile($path, $result); - } - } - unlink($tmpFile); - } - } - - public function getMimeType($path) { - $file = $this->getDriveFile($path); - if ($file) { - $mimetype = $file->getMimeType(); - // Convert Google Doc mimetypes, choosing Open Document formats for download - if ($mimetype === self::FOLDER) { - return 'httpd/unix-directory'; - } else if ($mimetype === self::DOCUMENT) { - return 'application/vnd.oasis.opendocument.text'; - } else if ($mimetype === self::SPREADSHEET) { - return 'application/x-vnd.oasis.opendocument.spreadsheet'; - } else if ($mimetype === self::DRAWING) { - return 'image/jpeg'; - } else if ($mimetype === self::PRESENTATION) { - // Download as .odp is not available - return 'application/pdf'; - } else { - // use extension-based detection, could be an encrypted file - return parent::getMimeType($path); - } - } else { - return false; - } - } - - public function free_space($path) { - $about = $this->service->about->get(); - return $about->getQuotaBytesTotal() - $about->getQuotaBytesUsed(); - } - - public function touch($path, $mtime = null) { - $file = $this->getDriveFile($path); - $result = false; - if ($file) { - if (isset($mtime)) { - // This is just RFC3339, but frustratingly, GDrive's API *requires* - // the fractions portion be present, while no handy PHP constant - // for RFC3339 or ISO8601 includes it. So we do it ourselves. - $file->setModifiedDate(date('Y-m-d\TH:i:s.uP', $mtime)); - $result = $this->service->files->patch($file->getId(), $file, array( - 'setModifiedDate' => true, - )); - } else { - $result = $this->service->files->touch($file->getId()); - } - } else { - $parentFolder = $this->getDriveFile(dirname($path)); - if ($parentFolder) { - $file = new \Google_Service_Drive_DriveFile(); - $file->setTitle(basename($path)); - $parent = new \Google_Service_Drive_ParentReference(); - $parent->setId($parentFolder->getId()); - $file->setParents(array($parent)); - $result = $this->service->files->insert($file); - } - } - if ($result) { - $this->setDriveFile($path, $result); - } - return (bool)$result; - } - - public function test() { - if ($this->free_space('')) { - return true; - } - return false; - } - - public function hasUpdated($path, $time) { - $appConfig = \OC::$server->getAppConfig(); - if ($this->is_file($path)) { - return parent::hasUpdated($path, $time); - } else { - // Google Drive doesn't change modified times of folders when files inside are updated - // Instead we use the Changes API to see if folders have been updated, and it's a pain - $folder = $this->getDriveFile($path); - if ($folder) { - $result = false; - $folderId = $folder->getId(); - $startChangeId = $appConfig->getValue('files_external', $this->getId().'cId'); - $params = array( - 'includeDeleted' => true, - 'includeSubscribed' => true, - ); - if (isset($startChangeId)) { - $startChangeId = (int)$startChangeId; - $largestChangeId = $startChangeId; - $params['startChangeId'] = $startChangeId + 1; - } else { - $largestChangeId = 0; - } - $pageToken = true; - while ($pageToken) { - if ($pageToken !== true) { - $params['pageToken'] = $pageToken; - } - $changes = $this->service->changes->listChanges($params); - if ($largestChangeId === 0 || $largestChangeId === $startChangeId) { - $largestChangeId = $changes->getLargestChangeId(); - } - if (isset($startChangeId)) { - // Check if a file in this folder has been updated - // There is no way to filter by folder at the API level... - foreach ($changes->getItems() as $change) { - $file = $change->getFile(); - if ($file) { - foreach ($file->getParents() as $parent) { - if ($parent->getId() === $folderId) { - $result = true; - // Check if there are changes in different folders - } else if ($change->getId() <= $largestChangeId) { - // Decrement id so this change is fetched when called again - $largestChangeId = $change->getId(); - $largestChangeId--; - } - } - } - } - $pageToken = $changes->getNextPageToken(); - } else { - // Assuming the initial scan just occurred and changes are negligible - break; - } - } - $appConfig->setValue('files_external', $this->getId().'cId', $largestChangeId); - return $result; - } - } - return false; - } - - /** - * check if curl is installed - */ - public static function checkDependencies() { - return true; - } - -} diff --git a/apps/files_external/lib/storage/owncloud.php b/apps/files_external/lib/storage/owncloud.php deleted file mode 100644 index 22ecb4c806a..00000000000 --- a/apps/files_external/lib/storage/owncloud.php +++ /dev/null @@ -1,74 +0,0 @@ - - * @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 OCA\Files_External\Lib\Storage; - -/** - * ownCloud backend for external storage based on DAV backend. - * - * The ownCloud URL consists of three parts: - * http://%host/%context/remote.php/webdav/%root - * - */ -class OwnCloud extends \OC\Files\Storage\DAV{ - const OC_URL_SUFFIX = 'remote.php/webdav'; - - public function __construct($params) { - // extract context path from host if specified - // (owncloud install path on host) - $host = $params['host']; - // strip protocol - if (substr($host, 0, 8) == "https://") { - $host = substr($host, 8); - $params['secure'] = true; - } else if (substr($host, 0, 7) == "http://") { - $host = substr($host, 7); - $params['secure'] = false; - } - $contextPath = ''; - $hostSlashPos = strpos($host, '/'); - if ($hostSlashPos !== false){ - $contextPath = substr($host, $hostSlashPos); - $host = substr($host, 0, $hostSlashPos); - } - - if (substr($contextPath, -1) !== '/'){ - $contextPath .= '/'; - } - - if (isset($params['root'])){ - $root = $params['root']; - if (substr($root, 0, 1) !== '/'){ - $root = '/' . $root; - } - } - else{ - $root = '/'; - } - - $params['host'] = $host; - $params['root'] = $contextPath . self::OC_URL_SUFFIX . $root; - - parent::__construct($params); - } -} diff --git a/apps/files_external/lib/storage/sftp.php b/apps/files_external/lib/storage/sftp.php deleted file mode 100644 index 2375f84dcda..00000000000 --- a/apps/files_external/lib/storage/sftp.php +++ /dev/null @@ -1,467 +0,0 @@ - - * @author Bart Visscher - * @author hkjolhede - * @author Jörn Friedrich Dreyer - * @author Lennart Rosam - * @author Lukas Reschke - * @author Morris Jobke - * @author Robin Appelman - * @author Robin McCorkell - * @author Ross Nicoll - * @author SA - * @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 OCA\Files_External\Lib\Storage; -use Icewind\Streams\IteratorDirectory; - -use Icewind\Streams\RetryWrapper; -use phpseclib\Net\SFTP\Stream; - -/** -* Uses phpseclib's Net\SFTP class and the Net\SFTP\Stream stream wrapper to -* provide access to SFTP servers. -*/ -class SFTP extends \OC\Files\Storage\Common { - private $host; - private $user; - private $root; - private $port = 22; - - private $auth; - - /** - * @var SFTP - */ - protected $client; - - /** - * @param string $host protocol://server:port - * @return array [$server, $port] - */ - private function splitHost($host) { - $input = $host; - if (strpos($host, '://') === false) { - // add a protocol to fix parse_url behavior with ipv6 - $host = 'http://' . $host; - } - - $parsed = parse_url($host); - if(is_array($parsed) && isset($parsed['port'])) { - return [$parsed['host'], $parsed['port']]; - } else if (is_array($parsed)) { - return [$parsed['host'], 22]; - } else { - return [$input, 22]; - } - } - - /** - * {@inheritdoc} - */ - public function __construct($params) { - // Register sftp:// - Stream::register(); - - $parsedHost = $this->splitHost($params['host']); - - $this->host = $parsedHost[0]; - $this->port = $parsedHost[1]; - - if (!isset($params['user'])) { - throw new \UnexpectedValueException('no authentication parameters specified'); - } - $this->user = $params['user']; - - if (isset($params['public_key_auth'])) { - $this->auth = $params['public_key_auth']; - } elseif (isset($params['password'])) { - $this->auth = $params['password']; - } else { - throw new \UnexpectedValueException('no authentication parameters specified'); - } - - $this->root - = isset($params['root']) ? $this->cleanPath($params['root']) : '/'; - - if ($this->root[0] != '/') { - $this->root = '/' . $this->root; - } - - if (substr($this->root, -1, 1) != '/') { - $this->root .= '/'; - } - } - - /** - * Returns the connection. - * - * @return \phpseclib\Net\SFTP connected client instance - * @throws \Exception when the connection failed - */ - public function getConnection() { - if (!is_null($this->client)) { - return $this->client; - } - - $hostKeys = $this->readHostKeys(); - $this->client = new \phpseclib\Net\SFTP($this->host, $this->port); - - // The SSH Host Key MUST be verified before login(). - $currentHostKey = $this->client->getServerPublicHostKey(); - if (array_key_exists($this->host, $hostKeys)) { - if ($hostKeys[$this->host] != $currentHostKey) { - throw new \Exception('Host public key does not match known key'); - } - } else { - $hostKeys[$this->host] = $currentHostKey; - $this->writeHostKeys($hostKeys); - } - - if (!$this->client->login($this->user, $this->auth)) { - throw new \Exception('Login failed'); - } - return $this->client; - } - - /** - * {@inheritdoc} - */ - public function test() { - if ( - !isset($this->host) - || !isset($this->user) - ) { - return false; - } - return $this->getConnection()->nlist() !== false; - } - - /** - * {@inheritdoc} - */ - public function getId(){ - $id = 'sftp::' . $this->user . '@' . $this->host; - if ($this->port !== 22) { - $id .= ':' . $this->port; - } - // note: this will double the root slash, - // we should not change it to keep compatible with - // old storage ids - $id .= '/' . $this->root; - return $id; - } - - /** - * @return string - */ - public function getHost() { - return $this->host; - } - - /** - * @return string - */ - public function getRoot() { - return $this->root; - } - - /** - * @return mixed - */ - public function getUser() { - return $this->user; - } - - /** - * @param string $path - * @return string - */ - private function absPath($path) { - return $this->root . $this->cleanPath($path); - } - - /** - * @return string|false - */ - private function hostKeysPath() { - try { - $storage_view = \OCP\Files::getStorage('files_external'); - if ($storage_view) { - return \OC::$server->getConfig()->getSystemValue('datadirectory') . - $storage_view->getAbsolutePath('') . - 'ssh_hostKeys'; - } - } catch (\Exception $e) { - } - return false; - } - - /** - * @param $keys - * @return bool - */ - protected function writeHostKeys($keys) { - try { - $keyPath = $this->hostKeysPath(); - if ($keyPath && file_exists($keyPath)) { - $fp = fopen($keyPath, 'w'); - foreach ($keys as $host => $key) { - fwrite($fp, $host . '::' . $key . "\n"); - } - fclose($fp); - return true; - } - } catch (\Exception $e) { - } - return false; - } - - /** - * @return array - */ - protected function readHostKeys() { - try { - $keyPath = $this->hostKeysPath(); - if (file_exists($keyPath)) { - $hosts = array(); - $keys = array(); - $lines = file($keyPath, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES); - if ($lines) { - foreach ($lines as $line) { - $hostKeyArray = explode("::", $line, 2); - if (count($hostKeyArray) == 2) { - $hosts[] = $hostKeyArray[0]; - $keys[] = $hostKeyArray[1]; - } - } - return array_combine($hosts, $keys); - } - } - } catch (\Exception $e) { - } - return array(); - } - - /** - * {@inheritdoc} - */ - public function mkdir($path) { - try { - return $this->getConnection()->mkdir($this->absPath($path)); - } catch (\Exception $e) { - return false; - } - } - - /** - * {@inheritdoc} - */ - public function rmdir($path) { - try { - $result = $this->getConnection()->delete($this->absPath($path), true); - // workaround: stray stat cache entry when deleting empty folders - // see https://github.com/phpseclib/phpseclib/issues/706 - $this->getConnection()->clearStatCache(); - return $result; - } catch (\Exception $e) { - return false; - } - } - - /** - * {@inheritdoc} - */ - public function opendir($path) { - try { - $list = $this->getConnection()->nlist($this->absPath($path)); - if ($list === false) { - return false; - } - - $id = md5('sftp:' . $path); - $dirStream = array(); - foreach($list as $file) { - if ($file != '.' && $file != '..') { - $dirStream[] = $file; - } - } - return IteratorDirectory::wrap($dirStream); - } catch(\Exception $e) { - return false; - } - } - - /** - * {@inheritdoc} - */ - public function filetype($path) { - try { - $stat = $this->getConnection()->stat($this->absPath($path)); - if ($stat['type'] == NET_SFTP_TYPE_REGULAR) { - return 'file'; - } - - if ($stat['type'] == NET_SFTP_TYPE_DIRECTORY) { - return 'dir'; - } - } catch (\Exception $e) { - - } - return false; - } - - /** - * {@inheritdoc} - */ - public function file_exists($path) { - try { - return $this->getConnection()->stat($this->absPath($path)) !== false; - } catch (\Exception $e) { - return false; - } - } - - /** - * {@inheritdoc} - */ - public function unlink($path) { - try { - return $this->getConnection()->delete($this->absPath($path), true); - } catch (\Exception $e) { - return false; - } - } - - /** - * {@inheritdoc} - */ - public function fopen($path, $mode) { - try { - $absPath = $this->absPath($path); - switch($mode) { - case 'r': - case 'rb': - if ( !$this->file_exists($path)) { - 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+': - $context = stream_context_create(array('sftp' => array('session' => $this->getConnection()))); - $handle = fopen($this->constructUrl($path), $mode, false, $context); - return RetryWrapper::wrap($handle); - } - } catch (\Exception $e) { - } - return false; - } - - /** - * {@inheritdoc} - */ - public function touch($path, $mtime=null) { - try { - if (!is_null($mtime)) { - return false; - } - if (!$this->file_exists($path)) { - $this->getConnection()->put($this->absPath($path), ''); - } else { - return false; - } - } catch (\Exception $e) { - return false; - } - return true; - } - - /** - * @param string $path - * @param string $target - * @throws \Exception - */ - public function getFile($path, $target) { - $this->getConnection()->get($path, $target); - } - - /** - * @param string $path - * @param string $target - * @throws \Exception - */ - public function uploadFile($path, $target) { - $this->getConnection()->put($target, $path, NET_SFTP_LOCAL_FILE); - } - - /** - * {@inheritdoc} - */ - public function rename($source, $target) { - try { - if (!$this->is_dir($target) && $this->file_exists($target)) { - $this->unlink($target); - } - return $this->getConnection()->rename( - $this->absPath($source), - $this->absPath($target) - ); - } catch (\Exception $e) { - return false; - } - } - - /** - * {@inheritdoc} - */ - public function stat($path) { - try { - $stat = $this->getConnection()->stat($this->absPath($path)); - - $mtime = $stat ? $stat['mtime'] : -1; - $size = $stat ? $stat['size'] : 0; - - return array('mtime' => $mtime, 'size' => $size, 'ctime' => -1); - } catch (\Exception $e) { - return false; - } - } - - /** - * @param string $path - * @return string - */ - public function constructUrl($path) { - // Do not pass the password here. We want to use the Net_SFTP object - // supplied via stream context or fail. We only supply username and - // hostname because this might show up in logs (they are not used). - $url = 'sftp://' . urlencode($this->user) . '@' . $this->host . ':' . $this->port . $this->root . $path; - return $url; - } -} diff --git a/apps/files_external/lib/storage/smb.php b/apps/files_external/lib/storage/smb.php deleted file mode 100644 index 868c52a63b4..00000000000 --- a/apps/files_external/lib/storage/smb.php +++ /dev/null @@ -1,396 +0,0 @@ - - * @author Jesús Macias - * @author Jörn Friedrich Dreyer - * @author Michael Gapczynski - * @author Morris Jobke - * @author Philipp Kapfer - * @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 OCA\Files_External\Lib\Storage; - -use Icewind\SMB\Exception\ConnectException; -use Icewind\SMB\Exception\Exception; -use Icewind\SMB\Exception\ForbiddenException; -use Icewind\SMB\Exception\NotFoundException; -use Icewind\SMB\NativeServer; -use Icewind\SMB\Server; -use Icewind\Streams\CallbackWrapper; -use Icewind\Streams\IteratorDirectory; -use OC\Cache\CappedMemoryCache; -use OC\Files\Filesystem; -use OCP\Files\StorageNotAvailableException; - -class SMB extends \OC\Files\Storage\Common { - /** - * @var \Icewind\SMB\Server - */ - protected $server; - - /** - * @var \Icewind\SMB\Share - */ - protected $share; - - /** - * @var string - */ - protected $root; - - /** - * @var \Icewind\SMB\FileInfo[] - */ - protected $statCache; - - public function __construct($params) { - if (isset($params['host']) && isset($params['user']) && isset($params['password']) && isset($params['share'])) { - if (Server::NativeAvailable()) { - $this->server = new NativeServer($params['host'], $params['user'], $params['password']); - } else { - $this->server = new Server($params['host'], $params['user'], $params['password']); - } - $this->share = $this->server->getShare(trim($params['share'], '/')); - - $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 configuration'); - } - $this->statCache = new CappedMemoryCache(); - } - - /** - * @return string - */ - public function getId() { - // FIXME: double slash to keep compatible with the old storage ids, - // failure to do so will lead to creation of a new storage id and - // loss of shares from the storage - return 'smb::' . $this->server->getUser() . '@' . $this->server->getHost() . '//' . $this->share->getName() . '/' . $this->root; - } - - /** - * @param string $path - * @return string - */ - protected function buildPath($path) { - return Filesystem::normalizePath($this->root . '/' . $path, true, false, true); - } - - /** - * @param string $path - * @return \Icewind\SMB\IFileInfo - * @throws StorageNotAvailableException - */ - protected function getFileInfo($path) { - try { - $path = $this->buildPath($path); - if (!isset($this->statCache[$path])) { - $this->statCache[$path] = $this->share->stat($path); - } - return $this->statCache[$path]; - } catch (ConnectException $e) { - throw new StorageNotAvailableException($e->getMessage(), $e->getCode(), $e); - } - } - - /** - * @param string $path - * @return \Icewind\SMB\IFileInfo[] - * @throws StorageNotAvailableException - */ - protected function getFolderContents($path) { - try { - $path = $this->buildPath($path); - $files = $this->share->dir($path); - foreach ($files as $file) { - $this->statCache[$path . '/' . $file->getName()] = $file; - } - return $files; - } catch (ConnectException $e) { - throw new StorageNotAvailableException($e->getMessage(), $e->getCode(), $e); - } - } - - /** - * @param \Icewind\SMB\IFileInfo $info - * @return array - */ - protected function formatInfo($info) { - return array( - 'size' => $info->getSize(), - 'mtime' => $info->getMTime() - ); - } - - /** - * @param string $path - * @return array - */ - public function stat($path) { - return $this->formatInfo($this->getFileInfo($path)); - } - - /** - * @param string $path - * @return bool - */ - public function unlink($path) { - try { - if ($this->is_dir($path)) { - return $this->rmdir($path); - } else { - $path = $this->buildPath($path); - unset($this->statCache[$path]); - $this->share->del($path); - return true; - } - } catch (NotFoundException $e) { - return false; - } catch (ForbiddenException $e) { - return false; - } catch (ConnectException $e) { - throw new StorageNotAvailableException($e->getMessage(), $e->getCode(), $e); - } - } - - /** - * 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 (!$path and $this->root == '/') { - // mtime doesn't work for shares, but giving the nature of the backend, - // doing a full update is still just fast enough - return true; - } else { - $actualTime = $this->filemtime($path); - return $actualTime > $time; - } - } - - /** - * @param string $path - * @param string $mode - * @return resource - */ - public function fopen($path, $mode) { - $fullPath = $this->buildPath($path); - try { - switch ($mode) { - case 'r': - case 'rb': - if (!$this->file_exists($path)) { - return false; - } - return $this->share->read($fullPath); - case 'w': - case 'wb': - $source = $this->share->write($fullPath); - return CallBackWrapper::wrap($source, null, null, function () use ($fullPath) { - unset($this->statCache[$fullPath]); - }); - case 'a': - case 'ab': - case 'r+': - case 'w+': - case 'wb+': - case 'a+': - case 'x': - case 'x+': - case 'c': - case 'c+': - //emulate these - if (strrpos($path, '.') !== false) { - $ext = substr($path, strrpos($path, '.')); - } else { - $ext = ''; - } - if ($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($ext); - } - $source = fopen($tmpFile, $mode); - $share = $this->share; - return CallbackWrapper::wrap($source, null, null, function () use ($tmpFile, $fullPath, $share) { - unset($this->statCache[$fullPath]); - $share->put($tmpFile, $fullPath); - unlink($tmpFile); - }); - } - return false; - } catch (NotFoundException $e) { - return false; - } catch (ForbiddenException $e) { - return false; - } catch (ConnectException $e) { - throw new StorageNotAvailableException($e->getMessage(), $e->getCode(), $e); - } - } - - public function rmdir($path) { - try { - $this->statCache = array(); - $content = $this->share->dir($this->buildPath($path)); - foreach ($content as $file) { - if ($file->isDirectory()) { - $this->rmdir($path . '/' . $file->getName()); - } else { - $this->share->del($file->getPath()); - } - } - $this->share->rmdir($this->buildPath($path)); - return true; - } catch (NotFoundException $e) { - return false; - } catch (ForbiddenException $e) { - return false; - } catch (ConnectException $e) { - throw new StorageNotAvailableException($e->getMessage(), $e->getCode(), $e); - } - } - - public function touch($path, $time = null) { - try { - if (!$this->file_exists($path)) { - $fh = $this->share->write($this->buildPath($path)); - fclose($fh); - return true; - } - return false; - } catch (ConnectException $e) { - throw new StorageNotAvailableException($e->getMessage(), $e->getCode(), $e); - } - } - - public function opendir($path) { - try { - $files = $this->getFolderContents($path); - } catch (NotFoundException $e) { - return false; - } catch (ForbiddenException $e) { - return false; - } - $names = array_map(function ($info) { - /** @var \Icewind\SMB\IFileInfo $info */ - return $info->getName(); - }, $files); - return IteratorDirectory::wrap($names); - } - - public function filetype($path) { - try { - return $this->getFileInfo($path)->isDirectory() ? 'dir' : 'file'; - } catch (NotFoundException $e) { - return false; - } catch (ForbiddenException $e) { - return false; - } - } - - public function mkdir($path) { - $path = $this->buildPath($path); - try { - $this->share->mkdir($path); - return true; - } catch (ConnectException $e) { - throw new StorageNotAvailableException($e->getMessage(), $e->getCode(), $e); - } catch (Exception $e) { - return false; - } - } - - public function file_exists($path) { - try { - $this->getFileInfo($path); - return true; - } catch (NotFoundException $e) { - return false; - } catch (ForbiddenException $e) { - return false; - } catch (ConnectException $e) { - throw new StorageNotAvailableException($e->getMessage(), $e->getCode(), $e); - } - } - - public function isReadable($path) { - try { - $info = $this->getFileInfo($path); - return !$info->isHidden(); - } catch (NotFoundException $e) { - return false; - } catch (ForbiddenException $e) { - return false; - } - } - - public function isUpdatable($path) { - try { - $info = $this->getFileInfo($path); - return !$info->isHidden() && !$info->isReadOnly(); - } catch (NotFoundException $e) { - return false; - } catch (ForbiddenException $e) { - return false; - } - } - - /** - * check if smbclient is installed - */ - public static function checkDependencies() { - return ( - (bool)\OC_Helper::findBinaryPath('smbclient') - || Server::NativeAvailable() - ) ? true : ['smbclient']; - } - - /** - * Test a storage for availability - * - * @return bool - */ - public function test() { - try { - return parent::test(); - } catch (Exception $e) { - return false; - } - } -} diff --git a/apps/files_external/lib/storage/streamwrapper.php b/apps/files_external/lib/storage/streamwrapper.php deleted file mode 100644 index 0b4dff78c4f..00000000000 --- a/apps/files_external/lib/storage/streamwrapper.php +++ /dev/null @@ -1,128 +0,0 @@ - - * @author Jörn Friedrich Dreyer - * @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 OCA\Files_External\Lib\Storage; - -abstract class StreamWrapper extends \OC\Files\Storage\Common { - - /** - * @param string $path - * @return string|null - */ - abstract public function constructUrl($path); - - public function mkdir($path) { - return mkdir($this->constructUrl($path)); - } - - public function rmdir($path) { - if ($this->is_dir($path) && $this->isDeletable($path)) { - $dh = $this->opendir($path); - if (!is_resource($dh)) { - return false; - } - while (($file = readdir($dh)) !== false) { - if ($this->is_dir($path . '/' . $file)) { - $this->rmdir($path . '/' . $file); - } else { - $this->unlink($path . '/' . $file); - } - } - $url = $this->constructUrl($path); - $success = rmdir($url); - clearstatcache(false, $url); - return $success; - } else { - return false; - } - } - - public function opendir($path) { - return opendir($this->constructUrl($path)); - } - - public function filetype($path) { - return @filetype($this->constructUrl($path)); - } - - public function file_exists($path) { - return file_exists($this->constructUrl($path)); - } - - public function unlink($path) { - $url = $this->constructUrl($path); - $success = unlink($url); - // normally unlink() is supposed to do this implicitly, - // but doing it anyway just to be sure - clearstatcache(false, $url); - return $success; - } - - public function fopen($path, $mode) { - return fopen($this->constructUrl($path), $mode); - } - - public function touch($path, $mtime = null) { - if ($this->file_exists($path)) { - if (is_null($mtime)) { - $fh = $this->fopen($path, 'a'); - fwrite($fh, ''); - fclose($fh); - - return true; - } else { - return false; //not supported - } - } else { - $this->file_put_contents($path, ''); - return true; - } - } - - /** - * @param string $path - * @param string $target - */ - public function getFile($path, $target) { - return copy($this->constructUrl($path), $target); - } - - /** - * @param string $target - */ - public function uploadFile($path, $target) { - return copy($path, $this->constructUrl($target)); - } - - public function rename($path1, $path2) { - return rename($this->constructUrl($path1), $this->constructUrl($path2)); - } - - public function stat($path) { - return stat($this->constructUrl($path)); - } - -} diff --git a/apps/files_external/lib/storage/swift.php b/apps/files_external/lib/storage/swift.php deleted file mode 100644 index 4578cd9a5c7..00000000000 --- a/apps/files_external/lib/storage/swift.php +++ /dev/null @@ -1,599 +0,0 @@ - - * @author Benjamin Liles - * @author Christian Berendt - * @author Daniel Tosello - * @author Felix Moeller - * @author Jörn Friedrich Dreyer - * @author Martin Mattel - * @author Morris Jobke - * @author Philipp Kapfer - * @author Robin Appelman - * @author Robin McCorkell - * @author Thomas Müller - * @author Tim Dettrick - * @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 OCA\Files_External\Lib\Storage; - -use Guzzle\Http\Url; -use Guzzle\Http\Exception\ClientErrorResponseException; -use Icewind\Streams\IteratorDirectory; -use OpenCloud; -use OpenCloud\Common\Exceptions; -use OpenCloud\OpenStack; -use OpenCloud\Rackspace; -use OpenCloud\ObjectStore\Resource\DataObject; -use OpenCloud\ObjectStore\Exception; - -class Swift extends \OC\Files\Storage\Common { - - /** - * @var \OpenCloud\ObjectStore\Service - */ - private $connection; - /** - * @var \OpenCloud\ObjectStore\Resource\Container - */ - private $container; - /** - * @var \OpenCloud\OpenStack - */ - private $anchor; - /** - * @var string - */ - private $bucket; - /** - * Connection parameters - * - * @var array - */ - private $params; - /** - * @var array - */ - private static $tmpFiles = array(); - - /** - * @param string $path - */ - private function normalizePath($path) { - $path = trim($path, '/'); - - if (!$path) { - $path = '.'; - } - - $path = str_replace('#', '%23', $path); - - return $path; - } - - const SUBCONTAINER_FILE = '.subcontainers'; - - /** - * translate directory path to container name - * - * @param string $path - * @return string - */ - private function getContainerName($path) { - $path = trim(trim($this->root, '/') . "/" . $path, '/.'); - return str_replace('/', '\\', $path); - } - - /** - * @param string $path - */ - private function doesObjectExist($path) { - try { - $this->getContainer()->getPartialObject($path); - return true; - } catch (ClientErrorResponseException $e) { - // Expected response is "404 Not Found", so only log if it isn't - if ($e->getResponse()->getStatusCode() !== 404) { - \OCP\Util::writeLog('files_external', $e->getMessage(), \OCP\Util::ERROR); - } - return false; - } - } - - public function __construct($params) { - if ((empty($params['key']) and empty($params['password'])) - or empty($params['user']) or empty($params['bucket']) - or empty($params['region']) - ) { - throw new \Exception("API Key or password, Username, Bucket and Region have to be configured."); - } - - $this->id = 'swift::' . $params['user'] . md5($params['bucket']); - - $bucketUrl = Url::factory($params['bucket']); - if ($bucketUrl->isAbsolute()) { - $this->bucket = end(($bucketUrl->getPathSegments())); - $params['endpoint_url'] = $bucketUrl->addPath('..')->normalizePath(); - } else { - $this->bucket = $params['bucket']; - } - - if (empty($params['url'])) { - $params['url'] = 'https://identity.api.rackspacecloud.com/v2.0/'; - } - - if (empty($params['service_name'])) { - $params['service_name'] = 'cloudFiles'; - } - - $this->params = $params; - } - - public function mkdir($path) { - $path = $this->normalizePath($path); - - if ($this->is_dir($path)) { - return false; - } - - if ($path !== '.') { - $path .= '/'; - } - - try { - $customHeaders = array('content-type' => 'httpd/unix-directory'); - $metadataHeaders = DataObject::stockHeaders(array()); - $allHeaders = $customHeaders + $metadataHeaders; - $this->getContainer()->uploadObject($path, '', $allHeaders); - } catch (Exceptions\CreateUpdateError $e) { - \OCP\Util::writeLog('files_external', $e->getMessage(), \OCP\Util::ERROR); - return false; - } - - return true; - } - - public function file_exists($path) { - $path = $this->normalizePath($path); - - if ($path !== '.' && $this->is_dir($path)) { - $path .= '/'; - } - - return $this->doesObjectExist($path); - } - - public function rmdir($path) { - $path = $this->normalizePath($path); - - if (!$this->is_dir($path) || !$this->isDeletable($path)) { - return false; - } - - $dh = $this->opendir($path); - while ($file = readdir($dh)) { - if (\OC\Files\Filesystem::isIgnoredDir($file)) { - continue; - } - - if ($this->is_dir($path . '/' . $file)) { - $this->rmdir($path . '/' . $file); - } else { - $this->unlink($path . '/' . $file); - } - } - - try { - $this->getContainer()->dataObject()->setName($path . '/')->delete(); - } catch (Exceptions\DeleteError $e) { - \OCP\Util::writeLog('files_external', $e->getMessage(), \OCP\Util::ERROR); - return false; - } - - return true; - } - - public function opendir($path) { - $path = $this->normalizePath($path); - - if ($path === '.') { - $path = ''; - } else { - $path .= '/'; - } - - $path = str_replace('%23', '#', $path); // the prefix is sent as a query param, so revert the encoding of # - - try { - $files = array(); - /** @var OpenCloud\Common\Collection $objects */ - $objects = $this->getContainer()->objectList(array( - 'prefix' => $path, - 'delimiter' => '/' - )); - - /** @var OpenCloud\ObjectStore\Resource\DataObject $object */ - foreach ($objects as $object) { - $file = basename($object->getName()); - if ($file !== basename($path)) { - $files[] = $file; - } - } - - return IteratorDirectory::wrap($files); - } catch (\Exception $e) { - \OCP\Util::writeLog('files_external', $e->getMessage(), \OCP\Util::ERROR); - return false; - } - - } - - public function stat($path) { - $path = $this->normalizePath($path); - - if ($path === '.') { - $path = ''; - } else if ($this->is_dir($path)) { - $path .= '/'; - } - - try { - /** @var DataObject $object */ - $object = $this->getContainer()->getPartialObject($path); - } catch (ClientErrorResponseException $e) { - \OCP\Util::writeLog('files_external', $e->getMessage(), \OCP\Util::ERROR); - return false; - } - - $dateTime = \DateTime::createFromFormat(\DateTime::RFC1123, $object->getLastModified()); - if ($dateTime !== false) { - $mtime = $dateTime->getTimestamp(); - } else { - $mtime = null; - } - $objectMetadata = $object->getMetadata(); - $metaTimestamp = $objectMetadata->getProperty('timestamp'); - if (isset($metaTimestamp)) { - $mtime = $metaTimestamp; - } - - if (!empty($mtime)) { - $mtime = floor($mtime); - } - - $stat = array(); - $stat['size'] = (int)$object->getContentLength(); - $stat['mtime'] = $mtime; - $stat['atime'] = time(); - return $stat; - } - - public function filetype($path) { - $path = $this->normalizePath($path); - - if ($path !== '.' && $this->doesObjectExist($path)) { - return 'file'; - } - - if ($path !== '.') { - $path .= '/'; - } - - if ($this->doesObjectExist($path)) { - return 'dir'; - } - } - - public function unlink($path) { - $path = $this->normalizePath($path); - - if ($this->is_dir($path)) { - return $this->rmdir($path); - } - - try { - $this->getContainer()->dataObject()->setName($path)->delete(); - } catch (ClientErrorResponseException $e) { - \OCP\Util::writeLog('files_external', $e->getMessage(), \OCP\Util::ERROR); - return false; - } - - return true; - } - - public function fopen($path, $mode) { - $path = $this->normalizePath($path); - - switch ($mode) { - case 'r': - case 'rb': - try { - $c = $this->getContainer(); - $streamFactory = new \Guzzle\Stream\PhpStreamRequestFactory(); - $streamInterface = $streamFactory->fromRequest( - $c->getClient() - ->get($c->getUrl($path))); - $streamInterface->rewind(); - $stream = $streamInterface->getStream(); - stream_context_set_option($stream, 'swift','content', $streamInterface); - if(!strrpos($streamInterface - ->getMetaData('wrapper_data')[0], '404 Not Found')) { - return $stream; - } - return false; - } catch (\Guzzle\Http\Exception\BadResponseException $e) { - \OCP\Util::writeLog('files_external', $e->getMessage(), \OCP\Util::ERROR); - 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 = \OCP\Files::tmpFile($ext); - \OC\Files\Stream\Close::registerCallback($tmpFile, array($this, 'writeBack')); - // Fetch existing file if required - if ($mode[0] !== 'w' && $this->file_exists($path)) { - if ($mode[0] === 'x') { - // File cannot already exist - return false; - } - $source = $this->fopen($path, 'r'); - file_put_contents($tmpFile, $source); - // Seek to end if required - if ($mode[0] === 'a') { - fseek($tmpFile, 0, SEEK_END); - } - } - self::$tmpFiles[$tmpFile] = $path; - - return fopen('close://' . $tmpFile, $mode); - } - } - - public function touch($path, $mtime = null) { - $path = $this->normalizePath($path); - if (is_null($mtime)) { - $mtime = time(); - } - $metadata = array('timestamp' => $mtime); - if ($this->file_exists($path)) { - if ($this->is_dir($path) && $path != '.') { - $path .= '/'; - } - - $object = $this->getContainer()->getPartialObject($path); - $object->saveMetadata($metadata); - return true; - } else { - $mimeType = \OC::$server->getMimeTypeDetector()->detectPath($path); - $customHeaders = array('content-type' => $mimeType); - $metadataHeaders = DataObject::stockHeaders($metadata); - $allHeaders = $customHeaders + $metadataHeaders; - $this->getContainer()->uploadObject($path, '', $allHeaders); - return true; - } - } - - public function copy($path1, $path2) { - $path1 = $this->normalizePath($path1); - $path2 = $this->normalizePath($path2); - - $fileType = $this->filetype($path1); - if ($fileType === 'file') { - - // make way - $this->unlink($path2); - - try { - $source = $this->getContainer()->getPartialObject($path1); - $source->copy($this->bucket . '/' . $path2); - } catch (ClientErrorResponseException $e) { - \OCP\Util::writeLog('files_external', $e->getMessage(), \OCP\Util::ERROR); - return false; - } - - } else if ($fileType === 'dir') { - - // make way - $this->unlink($path2); - - try { - $source = $this->getContainer()->getPartialObject($path1 . '/'); - $source->copy($this->bucket . '/' . $path2 . '/'); - } catch (ClientErrorResponseException $e) { - \OCP\Util::writeLog('files_external', $e->getMessage(), \OCP\Util::ERROR); - return false; - } - - $dh = $this->opendir($path1); - while ($file = readdir($dh)) { - if (\OC\Files\Filesystem::isIgnoredDir($file)) { - continue; - } - - $source = $path1 . '/' . $file; - $target = $path2 . '/' . $file; - $this->copy($source, $target); - } - - } else { - //file does not exist - return false; - } - - return true; - } - - public function rename($path1, $path2) { - $path1 = $this->normalizePath($path1); - $path2 = $this->normalizePath($path2); - - $fileType = $this->filetype($path1); - - if ($fileType === 'dir' || $fileType === 'file') { - - // make way - $this->unlink($path2); - - // copy - if ($this->copy($path1, $path2) === false) { - return false; - } - - // cleanup - if ($this->unlink($path1) === false) { - $this->unlink($path2); - return false; - } - - return true; - } - - return false; - } - - public function getId() { - return $this->id; - } - - /** - * Returns the connection - * - * @return OpenCloud\ObjectStore\Service connected client - * @throws \Exception if connection could not be made - */ - public function getConnection() { - if (!is_null($this->connection)) { - return $this->connection; - } - - $settings = array( - 'username' => $this->params['user'], - ); - - if (!empty($this->params['password'])) { - $settings['password'] = $this->params['password']; - } else if (!empty($this->params['key'])) { - $settings['apiKey'] = $this->params['key']; - } - - if (!empty($this->params['tenant'])) { - $settings['tenantName'] = $this->params['tenant']; - } - - if (!empty($this->params['timeout'])) { - $settings['timeout'] = $this->params['timeout']; - } - - if (isset($settings['apiKey'])) { - $this->anchor = new Rackspace($this->params['url'], $settings); - } else { - $this->anchor = new OpenStack($this->params['url'], $settings); - } - - $connection = $this->anchor->objectStoreService($this->params['service_name'], $this->params['region']); - - if (!empty($this->params['endpoint_url'])) { - $endpoint = $connection->getEndpoint(); - $endpoint->setPublicUrl($this->params['endpoint_url']); - $endpoint->setPrivateUrl($this->params['endpoint_url']); - $connection->setEndpoint($endpoint); - } - - $this->connection = $connection; - - return $this->connection; - } - - /** - * Returns the initialized object store container. - * - * @return OpenCloud\ObjectStore\Resource\Container - */ - public function getContainer() { - if (!is_null($this->container)) { - return $this->container; - } - - try { - $this->container = $this->getConnection()->getContainer($this->bucket); - } catch (ClientErrorResponseException $e) { - $this->container = $this->getConnection()->createContainer($this->bucket); - } - - if (!$this->file_exists('.')) { - $this->mkdir('.'); - } - - return $this->container; - } - - public function writeBack($tmpFile) { - if (!isset(self::$tmpFiles[$tmpFile])) { - return false; - } - $fileData = fopen($tmpFile, 'r'); - $this->getContainer()->uploadObject(self::$tmpFiles[$tmpFile], $fileData); - unlink($tmpFile); - } - - public function hasUpdated($path, $time) { - if ($this->is_file($path)) { - return parent::hasUpdated($path, $time); - } - $path = $this->normalizePath($path); - $dh = $this->opendir($path); - $content = array(); - while (($file = readdir($dh)) !== false) { - $content[] = $file; - } - if ($path === '.') { - $path = ''; - } - $cachedContent = $this->getCache()->getFolderContents($path); - $cachedNames = array_map(function ($content) { - return $content['name']; - }, $cachedContent); - sort($cachedNames); - sort($content); - return $cachedNames != $content; - } - - /** - * check if curl is installed - */ - public static function checkDependencies() { - return true; - } - -} -- cgit v1.2.3