123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894 |
- <?php
- namespace OC\Files\Storage;
- use Exception;
- use Icewind\Streams\CallbackWrapper;
- use Icewind\Streams\IteratorDirectory;
- use OC\Files\Filesystem;
- use OC\MemCache\ArrayCache;
- use OCP\AppFramework\Http;
- use OCP\Constants;
- use OCP\Diagnostics\IEventLogger;
- use OCP\Files\FileInfo;
- use OCP\Files\ForbiddenException;
- use OCP\Files\StorageInvalidException;
- use OCP\Files\StorageNotAvailableException;
- use OCP\Http\Client\IClientService;
- use OCP\ICertificateManager;
- use OCP\Util;
- use Psr\Http\Message\ResponseInterface;
- use Sabre\DAV\Client;
- use Sabre\DAV\Xml\Property\ResourceType;
- use Sabre\HTTP\ClientException;
- use Sabre\HTTP\ClientHttpException;
- use Psr\Log\LoggerInterface;
- use Sabre\HTTP\RequestInterface;
- class DAV extends Common {
-
- protected $password;
-
- protected $user;
-
- protected $authType;
-
- protected $host;
-
- protected $secure;
-
- protected $root;
-
- protected $certPath;
-
- protected $ready;
-
- protected $client;
-
- protected $statCache;
-
- protected $httpClientService;
-
- protected $certManager;
- protected LoggerInterface $logger;
- protected IEventLogger $eventLogger;
-
- public function __construct($params) {
- $this->statCache = new ArrayCache();
- $this->httpClientService = \OC::$server->getHTTPClientService();
- if (isset($params['host']) && isset($params['user']) && isset($params['password'])) {
- $host = $params['host'];
-
- if (substr($host, 0, 8) == "https://") {
- $host = substr($host, 8);
- } elseif (substr($host, 0, 7) == "http://") {
- $host = substr($host, 7);
- }
- $this->host = $host;
- $this->user = $params['user'];
- $this->password = $params['password'];
- if (isset($params['authType'])) {
- $this->authType = $params['authType'];
- }
- if (isset($params['secure'])) {
- if (is_string($params['secure'])) {
- $this->secure = ($params['secure'] === 'true');
- } else {
- $this->secure = (bool)$params['secure'];
- }
- } else {
- $this->secure = false;
- }
- if ($this->secure === true) {
-
- $this->certManager = \OC::$server->getCertificateManager();
- }
- $this->root = $params['root'] ?? '/';
- $this->root = '/' . ltrim($this->root, '/');
- $this->root = rtrim($this->root, '/') . '/';
- } else {
- throw new \Exception('Invalid webdav storage configuration');
- }
- $this->logger = \OC::$server->get(LoggerInterface::class);
- $this->eventLogger = \OC::$server->get(IEventLogger::class);
- }
- protected function init() {
- if ($this->ready) {
- return;
- }
- $this->ready = true;
- $settings = [
- 'baseUri' => $this->createBaseUri(),
- 'userName' => $this->user,
- 'password' => $this->password,
- ];
- if ($this->authType !== null) {
- $settings['authType'] = $this->authType;
- }
- $proxy = \OC::$server->getConfig()->getSystemValueString('proxy', '');
- if ($proxy !== '') {
- $settings['proxy'] = $proxy;
- }
- $this->client = new Client($settings);
- $this->client->setThrowExceptions(true);
- if ($this->secure === true) {
- $certPath = $this->certManager->getAbsoluteBundlePath();
- if (file_exists($certPath)) {
- $this->certPath = $certPath;
- }
- if ($this->certPath) {
- $this->client->addCurlSetting(CURLOPT_CAINFO, $this->certPath);
- }
- }
- $lastRequestStart = 0;
- $this->client->on('beforeRequest', function (RequestInterface $request) use (&$lastRequestStart) {
- $this->logger->debug("sending dav " . $request->getMethod() . " request to external storage: " . $request->getAbsoluteUrl(), ['app' => 'dav']);
- $lastRequestStart = microtime(true);
- $this->eventLogger->start('fs:storage:dav:request', "Sending dav request to external storage");
- });
- $this->client->on('afterRequest', function (RequestInterface $request) use (&$lastRequestStart) {
- $elapsed = microtime(true) - $lastRequestStart;
- $this->logger->debug("dav " . $request->getMethod() . " request to external storage: " . $request->getAbsoluteUrl() . " took " . round($elapsed * 1000, 1) . "ms", ['app' => 'dav']);
- $this->eventLogger->end('fs:storage:dav:request');
- });
- }
-
- public function clearStatCache() {
- $this->statCache->clear();
- }
-
- public function getId() {
- return 'webdav::' . $this->user . '@' . $this->host . '/' . $this->root;
- }
-
- public function createBaseUri() {
- $baseUri = 'http';
- if ($this->secure) {
- $baseUri .= 's';
- }
- $baseUri .= '://' . $this->host . $this->root;
- return $baseUri;
- }
-
- public function mkdir($path) {
- $this->init();
- $path = $this->cleanPath($path);
- $result = $this->simpleResponse('MKCOL', $path, null, 201);
- if ($result) {
- $this->statCache->set($path, true);
- }
- return $result;
- }
-
- public function rmdir($path) {
- $this->init();
- $path = $this->cleanPath($path);
-
-
- $result = $this->simpleResponse('DELETE', $path . '/', null, 204);
- $this->statCache->clear($path . '/');
- $this->statCache->remove($path);
- return $result;
- }
-
- public function opendir($path) {
- $this->init();
- $path = $this->cleanPath($path);
- try {
- $response = $this->client->propFind(
- $this->encodePath($path),
- ['{DAV:}getetag'],
- 1
- );
- if ($response === false) {
- return false;
- }
- $content = [];
- $files = array_keys($response);
- array_shift($files);
- if (!$this->statCache->hasKey($path)) {
- $this->statCache->set($path, true);
- }
- foreach ($files as $file) {
- $file = urldecode($file);
-
- if (!$this->statCache->hasKey($path)) {
- $this->statCache->set($file, true);
- }
- $file = basename($file);
- $content[] = $file;
- }
- return IteratorDirectory::wrap($content);
- } catch (\Exception $e) {
- $this->convertException($e, $path);
- }
- return false;
- }
-
- protected function propfind($path) {
- $path = $this->cleanPath($path);
- $cachedResponse = $this->statCache->get($path);
-
- if (is_null($cachedResponse) || $cachedResponse === true) {
- $this->init();
- try {
- $response = $this->client->propFind(
- $this->encodePath($path),
- [
- '{DAV:}getlastmodified',
- '{DAV:}getcontentlength',
- '{DAV:}getcontenttype',
- '{http://owncloud.org/ns}permissions',
- '{http://open-collaboration-services.org/ns}share-permissions',
- '{DAV:}resourcetype',
- '{DAV:}getetag',
- '{DAV:}quota-available-bytes',
- ]
- );
- $this->statCache->set($path, $response);
- } catch (ClientHttpException $e) {
- if ($e->getHttpStatus() === 404 || $e->getHttpStatus() === 405) {
- $this->statCache->clear($path . '/');
- $this->statCache->set($path, false);
- return false;
- }
- $this->convertException($e, $path);
- } catch (\Exception $e) {
- $this->convertException($e, $path);
- }
- } else {
- $response = $cachedResponse;
- }
- return $response;
- }
-
- public function filetype($path) {
- try {
- $response = $this->propfind($path);
- if ($response === false) {
- return false;
- }
- $responseType = [];
- if (isset($response["{DAV:}resourcetype"])) {
-
- $responseType = $response["{DAV:}resourcetype"]->getValue();
- }
- return (count($responseType) > 0 and $responseType[0] == "{DAV:}collection") ? 'dir' : 'file';
- } catch (\Exception $e) {
- $this->convertException($e, $path);
- }
- return false;
- }
-
- public function file_exists($path) {
- try {
- $path = $this->cleanPath($path);
- $cachedState = $this->statCache->get($path);
- if ($cachedState === false) {
-
- return false;
- } elseif (!is_null($cachedState)) {
- return true;
- }
-
- return ($this->propfind($path) !== false);
- } catch (\Exception $e) {
- $this->convertException($e, $path);
- }
- return false;
- }
-
- public function unlink($path) {
- $this->init();
- $path = $this->cleanPath($path);
- $result = $this->simpleResponse('DELETE', $path, null, 204);
- $this->statCache->clear($path . '/');
- $this->statCache->remove($path);
- return $result;
- }
-
- public function fopen($path, $mode) {
- $this->init();
- $path = $this->cleanPath($path);
- switch ($mode) {
- case 'r':
- case 'rb':
- try {
- $response = $this->httpClientService
- ->newClient()
- ->get($this->createBaseUri() . $this->encodePath($path), [
- 'auth' => [$this->user, $this->password],
- 'stream' => true
- ]);
- } catch (\GuzzleHttp\Exception\ClientException $e) {
- if ($e->getResponse() instanceof ResponseInterface
- && $e->getResponse()->getStatusCode() === 404) {
- return false;
- } else {
- throw $e;
- }
- }
- if ($response->getStatusCode() !== Http::STATUS_OK) {
- if ($response->getStatusCode() === Http::STATUS_LOCKED) {
- throw new \OCP\Lock\LockedException($path);
- } else {
- \OC::$server->get(LoggerInterface::class)->error('Guzzle get returned status code ' . $response->getStatusCode(), ['app' => 'webdav client']);
- }
- }
- return $response->getBody();
- case 'w':
- case 'wb':
- case 'a':
- case 'ab':
- case 'r+':
- case 'w+':
- case 'wb+':
- case 'a+':
- case 'x':
- case 'x+':
- case 'c':
- case 'c+':
-
- $tempManager = \OC::$server->getTempManager();
- if (strrpos($path, '.') !== false) {
- $ext = substr($path, strrpos($path, '.'));
- } else {
- $ext = '';
- }
- if ($this->file_exists($path)) {
- if (!$this->isUpdatable($path)) {
- return false;
- }
- if ($mode === 'w' or $mode === 'w+') {
- $tmpFile = $tempManager->getTemporaryFile($ext);
- } else {
- $tmpFile = $this->getCachedFile($path);
- }
- } else {
- if (!$this->isCreatable(dirname($path))) {
- return false;
- }
- $tmpFile = $tempManager->getTemporaryFile($ext);
- }
- $handle = fopen($tmpFile, $mode);
- return CallbackWrapper::wrap($handle, null, null, function () use ($path, $tmpFile) {
- $this->writeBack($tmpFile, $path);
- });
- }
- }
-
- public function writeBack($tmpFile, $path) {
- $this->uploadFile($tmpFile, $path);
- unlink($tmpFile);
- }
-
- public function free_space($path) {
- $this->init();
- $path = $this->cleanPath($path);
- try {
- $response = $this->propfind($path);
- if ($response === false) {
- return FileInfo::SPACE_UNKNOWN;
- }
- if (isset($response['{DAV:}quota-available-bytes'])) {
- return Util::numericToNumber($response['{DAV:}quota-available-bytes']);
- } else {
- return FileInfo::SPACE_UNKNOWN;
- }
- } catch (\Exception $e) {
- return FileInfo::SPACE_UNKNOWN;
- }
- }
-
- public function touch($path, $mtime = null) {
- $this->init();
- if (is_null($mtime)) {
- $mtime = time();
- }
- $path = $this->cleanPath($path);
-
- if ($this->file_exists($path)) {
- try {
- $this->statCache->remove($path);
- $this->client->proppatch($this->encodePath($path), ['{DAV:}lastmodified' => $mtime]);
-
- $response = $this->client->propfind($this->encodePath($path), ['{DAV:}getlastmodified'], 0);
- if ($response === false) {
- return false;
- }
- if (isset($response['{DAV:}getlastmodified'])) {
- $remoteMtime = strtotime($response['{DAV:}getlastmodified']);
- if ($remoteMtime !== $mtime) {
-
- return false;
- }
- }
- } catch (ClientHttpException $e) {
- if ($e->getHttpStatus() === 501) {
- return false;
- }
- $this->convertException($e, $path);
- return false;
- } catch (\Exception $e) {
- $this->convertException($e, $path);
- return false;
- }
- } else {
- $this->file_put_contents($path, '');
- }
- return true;
- }
-
- public function file_put_contents($path, $data) {
- $path = $this->cleanPath($path);
- $result = parent::file_put_contents($path, $data);
- $this->statCache->remove($path);
- return $result;
- }
-
- protected function uploadFile($path, $target) {
- $this->init();
-
- $target = $this->cleanPath($target);
- $this->statCache->remove($target);
- $source = fopen($path, 'r');
- $this->httpClientService
- ->newClient()
- ->put($this->createBaseUri() . $this->encodePath($target), [
- 'body' => $source,
- 'auth' => [$this->user, $this->password]
- ]);
- $this->removeCachedFile($target);
- }
-
- public function rename($source, $target) {
- $this->init();
- $source = $this->cleanPath($source);
- $target = $this->cleanPath($target);
- try {
-
- if ($this->is_dir($target)) {
-
- $target = rtrim($target, '/') . '/';
- }
- $this->client->request(
- 'MOVE',
- $this->encodePath($source),
- null,
- [
- 'Destination' => $this->createBaseUri() . $this->encodePath($target),
- ]
- );
- $this->statCache->clear($source . '/');
- $this->statCache->clear($target . '/');
- $this->statCache->set($source, false);
- $this->statCache->set($target, true);
- $this->removeCachedFile($source);
- $this->removeCachedFile($target);
- return true;
- } catch (\Exception $e) {
- $this->convertException($e);
- }
- return false;
- }
-
- public function copy($source, $target) {
- $this->init();
- $source = $this->cleanPath($source);
- $target = $this->cleanPath($target);
- try {
-
- if ($this->is_dir($target)) {
-
- $target = rtrim($target, '/') . '/';
- }
- $this->client->request(
- 'COPY',
- $this->encodePath($source),
- null,
- [
- 'Destination' => $this->createBaseUri() . $this->encodePath($target),
- ]
- );
- $this->statCache->clear($target . '/');
- $this->statCache->set($target, true);
- $this->removeCachedFile($target);
- return true;
- } catch (\Exception $e) {
- $this->convertException($e);
- }
- return false;
- }
-
- public function stat($path) {
- try {
- $response = $this->propfind($path);
- if (!$response) {
- return false;
- }
- return [
- 'mtime' => isset($response['{DAV:}getlastmodified']) ? strtotime($response['{DAV:}getlastmodified']) : null,
- 'size' => Util::numericToNumber($response['{DAV:}getcontentlength'] ?? 0),
- ];
- } catch (\Exception $e) {
- $this->convertException($e, $path);
- }
- return [];
- }
-
- public function getMimeType($path) {
- $remoteMimetype = $this->getMimeTypeFromRemote($path);
- if ($remoteMimetype === 'application/octet-stream') {
- return \OC::$server->getMimeTypeDetector()->detectPath($path);
- } else {
- return $remoteMimetype;
- }
- }
- public function getMimeTypeFromRemote($path) {
- try {
- $response = $this->propfind($path);
- if ($response === false) {
- return false;
- }
- $responseType = [];
- if (isset($response["{DAV:}resourcetype"])) {
-
- $responseType = $response["{DAV:}resourcetype"]->getValue();
- }
- $type = (count($responseType) > 0 and $responseType[0] == "{DAV:}collection") ? 'dir' : 'file';
- if ($type == 'dir') {
- return 'httpd/unix-directory';
- } elseif (isset($response['{DAV:}getcontenttype'])) {
- return $response['{DAV:}getcontenttype'];
- } else {
- return 'application/octet-stream';
- }
- } catch (\Exception $e) {
- return false;
- }
- }
-
- public function cleanPath($path) {
- if ($path === '') {
- return $path;
- }
- $path = Filesystem::normalizePath($path);
-
- return substr($path, 1);
- }
-
- protected function encodePath($path) {
-
- return str_replace('%2F', '/', rawurlencode($path));
- }
-
- protected function simpleResponse($method, $path, $body, $expected) {
- $path = $this->cleanPath($path);
- try {
- $response = $this->client->request($method, $this->encodePath($path), $body);
- return $response['statusCode'] == $expected;
- } catch (ClientHttpException $e) {
- if ($e->getHttpStatus() === 404 && $method === 'DELETE') {
- $this->statCache->clear($path . '/');
- $this->statCache->set($path, false);
- return false;
- }
- $this->convertException($e, $path);
- } catch (\Exception $e) {
- $this->convertException($e, $path);
- }
- return false;
- }
-
- public static function checkDependencies() {
- return true;
- }
-
- public function isUpdatable($path) {
- return (bool)($this->getPermissions($path) & Constants::PERMISSION_UPDATE);
- }
-
- public function isCreatable($path) {
- return (bool)($this->getPermissions($path) & Constants::PERMISSION_CREATE);
- }
-
- public function isSharable($path) {
- return (bool)($this->getPermissions($path) & Constants::PERMISSION_SHARE);
- }
-
- public function isDeletable($path) {
- return (bool)($this->getPermissions($path) & Constants::PERMISSION_DELETE);
- }
-
- public function getPermissions($path) {
- $this->init();
- $path = $this->cleanPath($path);
- $response = $this->propfind($path);
- if ($response === false) {
- return 0;
- }
- if (isset($response['{http://owncloud.org/ns}permissions'])) {
- return $this->parsePermissions($response['{http://owncloud.org/ns}permissions']);
- } elseif ($this->is_dir($path)) {
- return Constants::PERMISSION_ALL;
- } elseif ($this->file_exists($path)) {
- return Constants::PERMISSION_ALL - Constants::PERMISSION_CREATE;
- } else {
- return 0;
- }
- }
-
- public function getETag($path) {
- $this->init();
- $path = $this->cleanPath($path);
- $response = $this->propfind($path);
- if ($response === false) {
- return null;
- }
- if (isset($response['{DAV:}getetag'])) {
- $etag = trim($response['{DAV:}getetag'], '"');
- if (strlen($etag) > 40) {
- $etag = md5($etag);
- }
- return $etag;
- }
- return parent::getEtag($path);
- }
-
- protected function parsePermissions($permissionsString) {
- $permissions = Constants::PERMISSION_READ;
- if (strpos($permissionsString, 'R') !== false) {
- $permissions |= Constants::PERMISSION_SHARE;
- }
- if (strpos($permissionsString, 'D') !== false) {
- $permissions |= Constants::PERMISSION_DELETE;
- }
- if (strpos($permissionsString, 'W') !== false) {
- $permissions |= Constants::PERMISSION_UPDATE;
- }
- if (strpos($permissionsString, 'CK') !== false) {
- $permissions |= Constants::PERMISSION_CREATE;
- $permissions |= Constants::PERMISSION_UPDATE;
- }
- return $permissions;
- }
-
- public function hasUpdated($path, $time) {
- $this->init();
- $path = $this->cleanPath($path);
- try {
-
- $this->statCache->remove($path);
- $response = $this->propfind($path);
- if ($response === false) {
- if ($path === '') {
-
- throw new StorageNotAvailableException('root is gone');
- }
- return false;
- }
- if (isset($response['{DAV:}getetag'])) {
- $cachedData = $this->getCache()->get($path);
- $etag = trim($response['{DAV:}getetag'], '"');
- if (($cachedData === false) || (!empty($etag) && ($cachedData['etag'] !== $etag))) {
- return true;
- } elseif (isset($response['{http://open-collaboration-services.org/ns}share-permissions'])) {
- $sharePermissions = (int)$response['{http://open-collaboration-services.org/ns}share-permissions'];
- return $sharePermissions !== $cachedData['permissions'];
- } elseif (isset($response['{http://owncloud.org/ns}permissions'])) {
- $permissions = $this->parsePermissions($response['{http://owncloud.org/ns}permissions']);
- return $permissions !== $cachedData['permissions'];
- } else {
- return false;
- }
- } elseif (isset($response['{DAV:}getlastmodified'])) {
- $remoteMtime = strtotime($response['{DAV:}getlastmodified']);
- return $remoteMtime > $time;
- } else {
-
- return false;
- }
- } catch (ClientHttpException $e) {
- if ($e->getHttpStatus() === 405) {
- if ($path === '') {
-
- throw new StorageNotAvailableException(get_class($e) . ': ' . $e->getMessage());
- }
- return false;
- }
- $this->convertException($e, $path);
- return false;
- } catch (\Exception $e) {
- $this->convertException($e, $path);
- return false;
- }
- }
-
- protected function convertException(Exception $e, $path = '') {
- \OC::$server->get(LoggerInterface::class)->debug($e->getMessage(), ['app' => 'files_external', 'exception' => $e]);
- if ($e instanceof ClientHttpException) {
- if ($e->getHttpStatus() === Http::STATUS_LOCKED) {
- throw new \OCP\Lock\LockedException($path);
- }
- if ($e->getHttpStatus() === Http::STATUS_UNAUTHORIZED) {
-
- throw new StorageInvalidException(get_class($e) . ': ' . $e->getMessage());
- } elseif ($e->getHttpStatus() === Http::STATUS_METHOD_NOT_ALLOWED) {
-
- return;
- } elseif ($e->getHttpStatus() === Http::STATUS_FORBIDDEN) {
-
- throw new ForbiddenException(get_class($e) . ':' . $e->getMessage(), false);
- }
- throw new StorageNotAvailableException(get_class($e) . ': ' . $e->getMessage());
- } elseif ($e instanceof ClientException) {
-
- throw new StorageNotAvailableException(get_class($e) . ': ' . $e->getMessage());
- } elseif ($e instanceof \InvalidArgumentException) {
-
-
- throw new StorageNotAvailableException(get_class($e) . ': ' . $e->getMessage());
- } elseif (($e instanceof StorageNotAvailableException) || ($e instanceof StorageInvalidException)) {
-
- throw $e;
- }
-
- }
- }
|