diff --git a/apps/dav/lib/Capabilities.php b/apps/dav/lib/Capabilities.php
index e4c29cd3b610..71903b20f4d3 100644
--- a/apps/dav/lib/Capabilities.php
+++ b/apps/dav/lib/Capabilities.php
@@ -41,6 +41,7 @@ public function getCapabilities() {
$cap = [
'dav' => [
'chunking' => '1.0',
+ 'trashbin' => '1.0',
'reports' => [
'search-files',
]
diff --git a/apps/dav/lib/Connector/Sabre/CopyEtagHeaderPlugin.php b/apps/dav/lib/Connector/Sabre/CopyEtagHeaderPlugin.php
index 2d9b3553f53a..57a291408eb3 100644
--- a/apps/dav/lib/Connector/Sabre/CopyEtagHeaderPlugin.php
+++ b/apps/dav/lib/Connector/Sabre/CopyEtagHeaderPlugin.php
@@ -22,6 +22,7 @@
namespace OCA\DAV\Connector\Sabre;
+use Sabre\DAV\Exception\NotFound;
use Sabre\HTTP\RequestInterface;
use Sabre\HTTP\ResponseInterface;
@@ -71,11 +72,15 @@ public function afterMethod(RequestInterface $request, ResponseInterface $respon
* @return void
*/
public function afterMove($source, $destination) {
- $node = $this->server->tree->getNodeForPath($destination);
- if ($node instanceof File) {
- $eTag = $node->getETag();
- $this->server->httpResponse->setHeader('OC-ETag', $eTag);
- $this->server->httpResponse->setHeader('ETag', $eTag);
+ try {
+ $node = $this->server->tree->getNodeForPath($destination);
+ if ($node instanceof File) {
+ $eTag = $node->getETag();
+ $this->server->httpResponse->setHeader('OC-ETag', $eTag);
+ $this->server->httpResponse->setHeader('ETag', $eTag);
+ }
+ } catch (NotFound $ex) {
+ // nothing to do then ....
}
}
}
diff --git a/apps/dav/lib/Connector/Sabre/Directory.php b/apps/dav/lib/Connector/Sabre/Directory.php
index 214a5ebe4acf..58fd0dc5c96a 100644
--- a/apps/dav/lib/Connector/Sabre/Directory.php
+++ b/apps/dav/lib/Connector/Sabre/Directory.php
@@ -36,6 +36,7 @@
use OCA\DAV\Connector\Sabre\Exception\FileLocked;
use OCA\DAV\Connector\Sabre\Exception\Forbidden;
use OCA\DAV\Connector\Sabre\Exception\InvalidPath;
+use OCA\DAV\TrashBin\ITrashBinNode;
use OCA\DAV\Upload\FutureFile;
use OCP\Files\FileContentNotAllowedException;
use OCP\Files\ForbiddenException;
@@ -401,6 +402,9 @@ public function getQuotaInfo() {
*/
public function moveInto($targetName, $fullSourcePath, INode $sourceNode) {
if (!$sourceNode instanceof Node) {
+ if ($sourceNode instanceof ITrashBinNode) {
+ return $sourceNode->restore($this->path . '/' . $targetName);
+ }
// it's a file of another kind, like FutureFile
if ($sourceNode instanceof IFile) {
// fallback to default copy+delete handling
diff --git a/apps/dav/lib/RootCollection.php b/apps/dav/lib/RootCollection.php
index 43eb9b178c03..4d10aaad038a 100644
--- a/apps/dav/lib/RootCollection.php
+++ b/apps/dav/lib/RootCollection.php
@@ -58,6 +58,8 @@ public function __construct() {
$systemPrincipals->disableListing = $disableListing;
$filesCollection = new Files\RootCollection($userPrincipalBackend, 'principals/users');
$filesCollection->disableListing = $disableListing;
+ $trashBinCollection = new TrashBin\RootCollection($userPrincipalBackend, 'principals/users');
+ $trashBinCollection->disableListing = $disableListing;
$caldavBackend = new CalDavBackend($db, $userPrincipalBackend, $groupPrincipalBackend, $random);
$calendarRoot = new CalendarRoot($userPrincipalBackend, $caldavBackend, 'principals/users');
$calendarRoot->disableListing = $disableListing;
@@ -100,6 +102,7 @@ public function __construct() {
$groupPrincipals,
$systemPrincipals]),
$filesCollection,
+ $trashBinCollection,
$calendarRoot,
$publicCalendarRoot,
new SimpleCollection('addressbooks', [
diff --git a/apps/dav/lib/Server.php b/apps/dav/lib/Server.php
index be6e4826b818..66446254cfca 100644
--- a/apps/dav/lib/Server.php
+++ b/apps/dav/lib/Server.php
@@ -55,6 +55,7 @@
use OCA\DAV\Files\PreviewPlugin;
use OCA\DAV\JobStatus\Entity\JobStatusMapper;
use OCA\DAV\SystemTag\SystemTagPlugin;
+use OCA\DAV\TrashBin\TrashBinPlugin;
use OCA\DAV\Upload\ChunkingPlugin;
use OCP\IRequest;
use OCP\SabrePluginEvent;
@@ -187,6 +188,7 @@ public function __construct(IRequest $request, $baseUri) {
$this->server->addPlugin(new CopyEtagHeaderPlugin());
$this->server->addPlugin(new ChunkingPlugin());
+ $this->server->addPlugin(new TrashBinPlugin());
// Allow view-only plugin for webdav requests
$this->server->addPlugin(new ViewOnlyPlugin(
@@ -215,7 +217,7 @@ public function __construct(IRequest $request, $baseUri) {
)
);
- if ($this->isRequestForSubtree(['files', 'uploads'])) {
+ if ($this->isRequestForSubtree(['files', 'uploads', 'trash-bin'])) {
//For files only
$filePropertiesPlugin = new FileCustomPropertiesPlugin(
new FileCustomPropertiesBackend(
diff --git a/apps/dav/lib/TrashBin/AbstractTrashBinNode.php b/apps/dav/lib/TrashBin/AbstractTrashBinNode.php
new file mode 100644
index 000000000000..4c12dc7f3451
--- /dev/null
+++ b/apps/dav/lib/TrashBin/AbstractTrashBinNode.php
@@ -0,0 +1,153 @@
+
+ *
+ * @copyright Copyright (c) 2019, ownCloud GmbH
+ * @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\DAV\TrashBin;
+
+use OCA\Files_Trashbin\Trashbin;
+use OCP\Files\FileInfo;
+use Sabre\DAV\Exception\Forbidden;
+
+abstract class AbstractTrashBinNode implements ITrashBinNode {
+
+ /**
+ * @var FileInfo
+ */
+ protected $fileInfo;
+ /**
+ * @var TrashBinManager
+ */
+ protected $trashBinManager;
+ /**
+ * @var string
+ */
+ protected $user;
+
+ public function __construct(string $user, FileInfo $fileInfo, TrashBinManager $trashBinManager) {
+ $this->fileInfo = $fileInfo;
+ $this->trashBinManager = $trashBinManager;
+ $this->user = $user;
+ }
+
+ /**
+ * Returns the name of the node.
+ *
+ * This is used to generate the url.
+ *
+ * @return string
+ */
+ public function getName() {
+ return (string)$this->fileInfo->getId();
+ }
+
+ /**
+ * Returns the mime-type for a file
+ *
+ * If null is returned, we'll assume application/octet-stream
+ *
+ * @return string|null
+ */
+ public function getContentType() {
+ return $this->fileInfo->getMimetype();
+ }
+
+ public function getETag() {
+ return $this->fileInfo->getEtag();
+ }
+
+ public function getLastModified() {
+ return $this->fileInfo->getMtime();
+ }
+ public function getSize() {
+ return $this->fileInfo->getSize();
+ }
+
+ public function getOriginalFileName() : string {
+ $path = $this->getPathInTrash();
+ if (\count($path) === 1) {
+ $path = \end($path);
+ $pathInfo = \pathinfo($path);
+ return $pathInfo['filename'];
+ }
+ return \end($path);
+ }
+
+ public function getOriginalLocation() : string {
+ $pathElements = $this->getPathInTrash();
+ $path = $pathElements[0];
+ $pathParts = \pathinfo($path);
+ $timestamp = (int)\substr($pathParts['extension'], 1);
+
+ $pathElements[0] = $pathParts['filename'];
+ $originalPath = \implode('/', $pathElements);
+
+ $location = $this->trashBinManager->getLocation($this->user, $pathParts['filename'], $timestamp);
+ if ($location !== '.') {
+ $originalPath = $location . '/' . $originalPath;
+ }
+
+ return $originalPath;
+ }
+
+ public function getDeleteTimestamp() : int {
+ $path = $this->getPathInTrash();
+ $path = $path[0];
+ $pathParts = \pathinfo($path);
+ return (int)\substr($pathParts['extension'], 1);
+ }
+
+ /**
+ * @codeCoverageIgnore
+ * @param string $targetLocation
+ * @return bool
+ */
+ public function restore(string $targetLocation): bool {
+ return $this->trashBinManager->restore($this->user, $this, $targetLocation);
+ }
+
+ /**
+ * @codeCoverageIgnore
+ */
+ public function delete() : void {
+ $path = $this->fileInfo->getPath();
+ $path = \explode('/', $path);
+ $user = $path[1];
+ $elements = \array_splice($path, 4);
+ $path = \implode('/', $elements);
+
+ $delimiter = \strrpos($path, '.d');
+ $path = \substr($path, 0, $delimiter);
+
+ Trashbin::delete($path, $user, $this->getDeleteTimestamp());
+ }
+
+ /**
+ * @return array
+ */
+ public function getPathInTrash() {
+ $path = $this->fileInfo->getPath();
+ $path = \explode('/', $path);
+ return \array_splice($path, 4);
+ }
+
+ public function setName($name) {
+ throw new Forbidden('Permission denied to rename this resource');
+ }
+}
diff --git a/apps/dav/lib/TrashBin/ITrashBinNode.php b/apps/dav/lib/TrashBin/ITrashBinNode.php
new file mode 100644
index 000000000000..30a674e85b99
--- /dev/null
+++ b/apps/dav/lib/TrashBin/ITrashBinNode.php
@@ -0,0 +1,51 @@
+
+ *
+ * @copyright Copyright (c) 2019, ownCloud GmbH
+ * @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\DAV\TrashBin;
+
+use Sabre\DAV\INode;
+
+/**
+ * Interface ITrashBinNode
+ *
+ * A common interface for all trash bin items
+ *
+ * @package OCA\DAV\TrashBin
+ */
+interface ITrashBinNode extends INode {
+ /**
+ * @return string
+ */
+ public function getOriginalFileName() : string;
+ /**
+ * @return string
+ */
+ public function getOriginalLocation() : string;
+ /**
+ * @return int
+ */
+ public function getDeleteTimestamp() : int;
+
+ /**
+ * @param string $targetLocation
+ * @return bool
+ */
+ public function restore(string $targetLocation) : bool;
+}
diff --git a/apps/dav/lib/TrashBin/RootCollection.php b/apps/dav/lib/TrashBin/RootCollection.php
new file mode 100644
index 000000000000..32e68a4f66bd
--- /dev/null
+++ b/apps/dav/lib/TrashBin/RootCollection.php
@@ -0,0 +1,45 @@
+
+ *
+ * @copyright Copyright (c) 2019, ownCloud GmbH
+ * @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\DAV\TrashBin;
+
+use Sabre\DAVACL\AbstractPrincipalCollection;
+
+class RootCollection extends AbstractPrincipalCollection {
+
+ /**
+ * This method returns a node for a principal.
+ *
+ * The passed array contains principal information, and is guaranteed to
+ * at least contain a uri item. Other properties may or may not be
+ * supplied by the authentication backend.
+ *
+ * @param array $principalInfo
+ * @return TrashBinHome
+ */
+ public function getChildForPrincipal(array $principalInfo) {
+ return new TrashBinHome($principalInfo, new TrashBinManager());
+ }
+
+ public function getName() {
+ return 'trash-bin';
+ }
+}
diff --git a/apps/dav/lib/TrashBin/TrashBinFile.php b/apps/dav/lib/TrashBin/TrashBinFile.php
new file mode 100644
index 000000000000..58b61db37cb2
--- /dev/null
+++ b/apps/dav/lib/TrashBin/TrashBinFile.php
@@ -0,0 +1,35 @@
+
+ *
+ * @copyright Copyright (c) 2019, ownCloud GmbH
+ * @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\DAV\TrashBin;
+
+use Sabre\DAV\Exception\Forbidden;
+use Sabre\DAV\IFile;
+
+class TrashBinFile extends AbstractTrashBinNode implements IFile {
+ public function put($data) {
+ throw new Forbidden('Permission denied to write this file');
+ }
+
+ public function get() {
+ throw new Forbidden('Permission denied to read this file');
+ }
+}
diff --git a/apps/dav/lib/TrashBin/TrashBinFolder.php b/apps/dav/lib/TrashBin/TrashBinFolder.php
new file mode 100644
index 000000000000..44adb64d5c25
--- /dev/null
+++ b/apps/dav/lib/TrashBin/TrashBinFolder.php
@@ -0,0 +1,56 @@
+
+ *
+ * @copyright Copyright (c) 2019, ownCloud GmbH
+ * @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\DAV\TrashBin;
+
+use Sabre\DAV\Exception\Forbidden;
+use Sabre\DAV\Exception\MethodNotAllowed;
+use Sabre\DAV\Exception\NotFound;
+use Sabre\DAV\ICollection;
+
+class TrashBinFolder extends AbstractTrashBinNode implements ICollection {
+ public function getChild($name) {
+ return $this->trashBinManager->getChild($this->user, $name);
+ }
+
+ public function getChildren() {
+ return $this->trashBinManager->getChildren($this->user, $this->getName());
+ }
+
+ public function createFile($name, $data = null) {
+ throw new Forbidden('Permission denied to create a file');
+ }
+
+ public function createDirectory($name) {
+ throw new Forbidden('Permission denied to create a folder');
+ }
+
+ public function childExists($name) {
+ try {
+ $ret = $this->getChild($name);
+ return $ret !== null;
+ } catch (NotFound $ex) {
+ return false;
+ } catch (MethodNotAllowed $ex) {
+ return false;
+ }
+ }
+}
diff --git a/apps/dav/lib/TrashBin/TrashBinHome.php b/apps/dav/lib/TrashBin/TrashBinHome.php
new file mode 100644
index 000000000000..7899337e6c5c
--- /dev/null
+++ b/apps/dav/lib/TrashBin/TrashBinHome.php
@@ -0,0 +1,69 @@
+
+ *
+ * @copyright Copyright (c) 2019, ownCloud GmbH
+ * @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\DAV\TrashBin;
+
+use Sabre\DAV\Collection;
+use Sabre\DAV\Exception\Forbidden;
+
+class TrashBinHome extends Collection {
+
+ /** @var TrashBinManager */
+ private $trashBinManager;
+ /** @var string */
+ private $user;
+
+ /**
+ * TrashBinHome constructor.
+ *
+ * @param array $principalInfo
+ * @param TrashBinManager $trashBinManager
+ */
+ public function __construct(array $principalInfo, TrashBinManager $trashBinManager) {
+ $this->trashBinManager = $trashBinManager;
+ [, $name] = \Sabre\Uri\split($principalInfo['uri']);
+ $this->user = $name;
+ }
+
+ public function getChild($name) {
+ return $this->trashBinManager->getChild($this->user, $name);
+ }
+
+ public function getChildren() {
+ return $this->trashBinManager->getChildren($this->user);
+ }
+
+ public function delete() {
+ $this->trashBinManager->deleteAll();
+ }
+
+ public function getName() {
+ return $this->user;
+ }
+
+ public function setName($name) {
+ throw new Forbidden('Permission denied to rename this folder');
+ }
+
+ public function getLastModified() {
+ return null;
+ }
+}
diff --git a/apps/dav/lib/TrashBin/TrashBinManager.php b/apps/dav/lib/TrashBin/TrashBinManager.php
new file mode 100644
index 000000000000..a32e85cd4680
--- /dev/null
+++ b/apps/dav/lib/TrashBin/TrashBinManager.php
@@ -0,0 +1,93 @@
+
+ *
+ * @copyright Copyright (c) 2019, ownCloud GmbH
+ * @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\DAV\TrashBin;
+
+use OC\Files\FileInfo;
+use OC\Files\View;
+use OCA\Files_Trashbin\Trashbin;
+use OCP\Files\NotFoundException;
+use Sabre\DAV\Exception\InvalidResourceType;
+use Sabre\DAV\Exception\NotFound;
+
+/**
+ * Class TrashBinManager
+ *
+ * @package OCA\DAV\TrashBin
+ * @codeCoverageIgnore
+ */
+class TrashBinManager {
+ public function getChild(string $user, string $id) {
+ try {
+ $view = new View('/' . $user . '/files_trashbin/files');
+ $path = $view->getPath($id);
+ $fileInfo = $view->getFileInfo($path);
+ return $this->nodeFactory($user, $fileInfo);
+ } catch (NotFoundException $ex) {
+ throw new NotFound();
+ }
+ }
+
+ public function getChildren(string $user, string $fileId = null) {
+ try {
+ $view = new View('/' . $user . '/files_trashbin/files');
+ $path = '/';
+ if ($fileId) {
+ $path = $view->getPath($fileId);
+ }
+ $fileInfo = $view->getFileInfo($path);
+ if ($fileInfo === false) {
+ throw new NotFound();
+ }
+ if ($fileInfo->getMimetype() !== 'httpd/unix-directory') {
+ throw new InvalidResourceType();
+ }
+ $files = $view->getDirectoryContent($path);
+ return \array_map(function ($fileInfo) use ($user) {
+ return $this->nodeFactory($user, $fileInfo);
+ }, $files);
+ } catch (\Exception $exception) {
+ return [];
+ }
+ }
+
+ private function nodeFactory(string $user, FileInfo $fileInfo) {
+ if ($fileInfo->getMimetype() === 'httpd/unix-directory') {
+ return new TrashBinFolder($user, $fileInfo, $this);
+ }
+ return new TrashBinFile($user, $fileInfo, $this);
+ }
+
+ public function restore(string $user, AbstractTrashBinNode $trashItem, $targetLocation) {
+ $path = $trashItem->getPathInTrash();
+ $path = \implode('/', $path);
+ return Trashbin::restore($path,
+ $trashItem->getOriginalFileName(), $trashItem->getDeleteTimestamp(), $targetLocation);
+ }
+
+ public function deleteAll() {
+ return Trashbin::deleteAll();
+ }
+
+ public function getLocation(string $user, $filename, int $timestamp) {
+ return Trashbin::getLocation($user, $filename, $timestamp);
+ }
+}
diff --git a/apps/dav/lib/TrashBin/TrashBinPlugin.php b/apps/dav/lib/TrashBin/TrashBinPlugin.php
new file mode 100644
index 000000000000..aa39de03d67a
--- /dev/null
+++ b/apps/dav/lib/TrashBin/TrashBinPlugin.php
@@ -0,0 +1,66 @@
+
+ *
+ * @copyright Copyright (c) 2019, ownCloud GmbH
+ * @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\DAV\TrashBin;
+
+use Sabre\DAV\INode;
+use Sabre\DAV\PropFind;
+use Sabre\DAV\Server;
+use Sabre\DAV\ServerPlugin;
+use Sabre\DAV\Xml\Property\GetLastModified;
+
+class TrashBinPlugin extends ServerPlugin {
+ public const TRASHBIN_ORIGINAL_FILENAME = '{http://owncloud.org/ns}trashbin-original-filename';
+ public const TRASHBIN_ORIGINAL_LOCATION = '{http://owncloud.org/ns}trashbin-original-location';
+ public const TRASHBIN_DELETE_TIMESTAMP = '{http://owncloud.org/ns}trashbin-delete-timestamp';
+ public const TRASHBIN_DELETE_DATETIME = '{http://owncloud.org/ns}trashbin-delete-datetime';
+
+ /** @var Server */
+ private $server;
+
+ public function initialize(Server $server) {
+ $this->server = $server;
+
+ $this->server->on('propFind', [$this, 'propFind']);
+ }
+
+ public function propFind(PropFind $propFind, INode $node) {
+ if (!($node instanceof ITrashBinNode)) {
+ return;
+ }
+
+ $propFind->handle(self::TRASHBIN_ORIGINAL_FILENAME, static function () use ($node) {
+ return $node->getOriginalFileName();
+ });
+
+ $propFind->handle(self::TRASHBIN_ORIGINAL_LOCATION, static function () use ($node) {
+ return $node->getOriginalLocation();
+ });
+
+ $propFind->handle(self::TRASHBIN_DELETE_TIMESTAMP, static function () use ($node) {
+ return $node->getDeleteTimestamp();
+ });
+
+ $propFind->handle(self::TRASHBIN_DELETE_DATETIME, static function () use ($node) {
+ return new GetLastModified($node->getDeleteTimestamp());
+ });
+ }
+}
diff --git a/apps/dav/tests/unit/TrashBin/RootCollectionTest.php b/apps/dav/tests/unit/TrashBin/RootCollectionTest.php
new file mode 100644
index 000000000000..b99c5e9203fd
--- /dev/null
+++ b/apps/dav/tests/unit/TrashBin/RootCollectionTest.php
@@ -0,0 +1,44 @@
+
+ *
+ * @copyright Copyright (c) 2019, ownCloud GmbH
+ * @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\DAV\Tests\Unit\TrashBin;
+
+use OCA\DAV\TrashBin\RootCollection;
+use OCA\DAV\TrashBin\TrashBinHome;
+use Sabre\DAVACL\PrincipalBackend\BackendInterface;
+use Test\TestCase;
+
+class RootCollectionTest extends TestCase {
+ public function testGetName() {
+ $backEnd = $this->createMock(BackendInterface::class);
+ $collection = new RootCollection($backEnd);
+ self::assertEquals('trash-bin', $collection->getName());
+ }
+
+ public function testGetChildForPrincipal() {
+ $backEnd = $this->createMock(BackendInterface::class);
+ $collection = new RootCollection($backEnd);
+ $child = $collection->getChildForPrincipal([
+ 'uri' => 'principals/alice'
+ ]);
+ self::assertInstanceOf(TrashBinHome::class, $child);
+ }
+}
diff --git a/apps/dav/tests/unit/TrashBin/TrashBinFileTest.php b/apps/dav/tests/unit/TrashBin/TrashBinFileTest.php
new file mode 100644
index 000000000000..6221c8afa690
--- /dev/null
+++ b/apps/dav/tests/unit/TrashBin/TrashBinFileTest.php
@@ -0,0 +1,65 @@
+
+ *
+ * @copyright Copyright (c) 2019, ownCloud GmbH
+ * @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\DAV\Tests\Unit\TrashBin;
+
+use OCA\DAV\TrashBin\TrashBinFile;
+use OCA\DAV\TrashBin\TrashBinManager;
+use OCP\Files\FileInfo;
+use Test\TestCase;
+
+class TrashBinFileTest extends TestCase {
+ /**
+ * @var TrashBinFile
+ */
+ private $trashBinFile;
+
+ protected function setUp() {
+ parent::setUp();
+ $fileInfo = $this->createMock(FileInfo::class);
+ $trashBinManager = $this->createMock(TrashBinManager::class);
+ $this->trashBinFile = new TrashBinFile('alice', $fileInfo, $trashBinManager);
+ }
+
+ /**
+ * @expectedException \Sabre\DAV\Exception\Forbidden
+ * @expectedExceptionMessage Permission denied to write this file
+ */
+ public function testPut() {
+ $this->trashBinFile->put('');
+ }
+
+ /**
+ * @expectedException \Sabre\DAV\Exception\Forbidden
+ * @expectedExceptionMessage Permission denied to read this file
+ */
+ public function testGet() {
+ $this->trashBinFile->get();
+ }
+
+ /**
+ * @expectedException \Sabre\DAV\Exception\Forbidden
+ * @expectedExceptionMessage Permission denied to rename this resource
+ */
+ public function testSetName() {
+ $this->trashBinFile->setName('');
+ }
+}
diff --git a/apps/dav/tests/unit/TrashBin/TrashBinFolderTest.php b/apps/dav/tests/unit/TrashBin/TrashBinFolderTest.php
new file mode 100644
index 000000000000..0b37040617ff
--- /dev/null
+++ b/apps/dav/tests/unit/TrashBin/TrashBinFolderTest.php
@@ -0,0 +1,133 @@
+
+ *
+ * @copyright Copyright (c) 2019, ownCloud GmbH
+ * @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\DAV\Tests\Unit\TrashBin;
+
+use OCA\DAV\TrashBin\TrashBinFolder;
+use OCA\DAV\TrashBin\TrashBinManager;
+use OCP\Files\FileInfo;
+use Sabre\DAV\Exception\NotFound;
+use Test\TestCase;
+
+class TrashBinFolderTest extends TestCase {
+ /**
+ * @var TrashBinFolder
+ */
+ private $trashBinFolder;
+ /**
+ * @var TrashBinManager | \PHPUnit\Framework\MockObject\MockObject
+ */
+ private $trashBinManager;
+ /**
+ * @var FileInfo | \PHPUnit\Framework\MockObject\MockObject
+ */
+ private $fileInfo;
+
+ protected function setUp() {
+ parent::setUp();
+ $this->fileInfo = $this->createMock(FileInfo::class);
+ $this->trashBinManager = $this->createMock(TrashBinManager::class);
+ $this->trashBinFolder = new TrashBinFolder('alice', $this->fileInfo, $this->trashBinManager);
+
+ $this->fileInfo->method('getId')->willReturn(666);
+ $this->fileInfo->method('getMimeType')->willReturn('foo');
+ $this->fileInfo->method('getEtag')->willReturn('abcdefgh');
+ $this->fileInfo->method('getMtime')->willReturn(789123456);
+ $this->fileInfo->method('getSize')->willReturn(12345678);
+ $this->fileInfo->method('getPath')->willReturn('/alice/files_trashbin/files/folder.d1561467869/foo');
+
+ $this->trashBinManager->method('getLocation')->willReturn('.');
+ }
+
+ /**
+ * @expectedException \Sabre\DAV\Exception\Forbidden
+ * @expectedExceptionMessage Permission denied to create a file
+ */
+ public function testCreateFile() {
+ $this->trashBinFolder->createFile('');
+ }
+
+ /**
+ * @expectedException \Sabre\DAV\Exception\Forbidden
+ * @expectedExceptionMessage Permission denied to create a folder
+ */
+ public function testCreateFolder() {
+ $this->trashBinFolder->createDirectory('');
+ }
+
+ /**
+ * @expectedException \Sabre\DAV\Exception\Forbidden
+ * @expectedExceptionMessage Permission denied to rename this resource
+ */
+ public function testSetName() {
+ $this->trashBinFolder->setName('');
+ }
+
+ public function testGetChildren() {
+ $this->trashBinManager->method('getChildren')
+ ->with('alice', '666')->willReturn([
+ 'dummy string'
+ ]);
+ $children = $this->trashBinFolder->getChildren();
+ self::assertEquals(['dummy string'], $children);
+ }
+
+ public function testGetChild() {
+ $this->trashBinManager->method('getChild')
+ ->with('alice', '777')->willReturn('dummy string');
+ $child = $this->trashBinFolder->getChild(777);
+ self::assertEquals('dummy string', $child);
+ }
+
+ public function testChildExists() {
+ $this->trashBinManager->method('getChild')
+ ->with('alice', '777')->willReturn('dummy string');
+ $exists = $this->trashBinFolder->childExists(777);
+ self::assertTrue($exists);
+ }
+
+ public function testChildDoesNotExists() {
+ $this->trashBinManager->method('getChild')
+ ->with('alice', '777')->willThrowException(new NotFound());
+ $exists = $this->trashBinFolder->childExists(777);
+ self::assertFalse($exists);
+ }
+
+ /**
+ * @dataProvider providesMethods
+ */
+ public function testGetter($expectedValue, $method) {
+ self::assertEquals($expectedValue, $this->trashBinFolder->$method());
+ }
+
+ public function providesMethods() {
+ return [
+ ['666', 'getName'],
+ ['foo', 'getContentType'],
+ ['abcdefgh', 'getEtag'],
+ [789123456, 'getLastModified'],
+ [12345678, 'getSize'],
+ ['foo', 'getOriginalFileName'],
+ ['folder/foo', 'getOriginalLocation'],
+ [1561467869, 'getDeleteTimestamp'],
+ ];
+ }
+}
diff --git a/apps/dav/tests/unit/TrashBin/TrashBinHomeTest.php b/apps/dav/tests/unit/TrashBin/TrashBinHomeTest.php
new file mode 100644
index 000000000000..adf90bdc3eda
--- /dev/null
+++ b/apps/dav/tests/unit/TrashBin/TrashBinHomeTest.php
@@ -0,0 +1,100 @@
+
+ *
+ * @copyright Copyright (c) 2019, ownCloud GmbH
+ * @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\DAV\Tests\Unit\TrashBin;
+
+use OCA\DAV\TrashBin\TrashBinHome;
+use OCA\DAV\TrashBin\TrashBinManager;
+use Sabre\DAV\Exception\NotFound;
+use Test\TestCase;
+
+class TrashBinHomeTest extends TestCase {
+ /**
+ * @var TrashBinHome
+ */
+ private $trashBinHome;
+ /**
+ * @var TrashBinManager | \PHPUnit\Framework\MockObject\MockObject
+ */
+ private $trashBinManager;
+
+ protected function setUp() {
+ parent::setUp();
+ $this->trashBinManager = $this->createMock(TrashBinManager::class);
+ $this->trashBinHome = new TrashBinHome([
+ 'uri' => 'principals/alice'
+ ], $this->trashBinManager);
+ }
+
+ /**
+ * @expectedException \Sabre\DAV\Exception\Forbidden
+ * @expectedExceptionMessage Permission denied to rename this folder
+ */
+ public function testSetName() {
+ $this->trashBinHome->setName('');
+ }
+
+ public function testGetName() {
+ self::assertEquals('alice', $this->trashBinHome->getName());
+ }
+
+ public function testGetLastModified() {
+ self::assertNull($this->trashBinHome->getLastModified());
+ }
+
+ public function testGetChildren() {
+ $this->trashBinManager->method('getChildren')
+ ->with('alice')->willReturn([
+ 'dummy string'
+ ]);
+ $children = $this->trashBinHome->getChildren();
+ self::assertEquals(['dummy string'], $children);
+ }
+
+ public function testGetChild() {
+ $this->trashBinManager->method('getChild')
+ ->with('alice', '777')->willReturn('dummy string');
+ $child = $this->trashBinHome->getChild(777);
+ self::assertEquals('dummy string', $child);
+ }
+
+ public function testChildExists() {
+ $this->trashBinManager->method('getChild')
+ ->with('alice', '777')->willReturn('dummy string');
+ $exists = $this->trashBinHome->childExists(777);
+ self::assertTrue($exists);
+ }
+
+ public function testChildDoesNotExists() {
+ $this->trashBinManager->method('getChild')
+ ->with('alice', '777')->willThrowException(new NotFound());
+ $exists = $this->trashBinHome->childExists(777);
+ self::assertFalse($exists);
+ }
+
+ public function testDelete() {
+ $this->trashBinManager
+ ->expects($this->once())
+ ->method('deleteAll')
+ ->willReturn(true);
+ $this->trashBinHome->delete();
+ }
+}
diff --git a/apps/dav/tests/unit/TrashBin/TrashBinPluginTest.php b/apps/dav/tests/unit/TrashBin/TrashBinPluginTest.php
new file mode 100644
index 000000000000..7d2460a9c51a
--- /dev/null
+++ b/apps/dav/tests/unit/TrashBin/TrashBinPluginTest.php
@@ -0,0 +1,68 @@
+
+ *
+ * @copyright Copyright (c) 2019, ownCloud GmbH
+ * @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\DAV\Tests\Unit\TrashBin;
+
+use OCA\DAV\TrashBin\ITrashBinNode;
+use OCA\DAV\TrashBin\RootCollection;
+use OCA\DAV\TrashBin\TrashBinHome;
+use OCA\DAV\TrashBin\TrashBinPlugin;
+use phpDocumentor\Reflection\Types\This;
+use Sabre\DAV\PropFind;
+use Sabre\DAV\Server;
+use Sabre\DAV\Xml\Property\GetLastModified;
+use Sabre\DAVACL\PrincipalBackend\BackendInterface;
+use Test\TestCase;
+
+class TrashBinPluginTest extends TestCase {
+ public function testInit() {
+ $server = $this->createMock(Server::class);
+ $server->expects($this->once())->method('on')->with('propFind');
+
+ $plugin = new TrashBinPlugin();
+ $plugin->initialize($server);
+ }
+
+ /**
+ * @dataProvider providesMethods
+ */
+ public function testPropFind($expectedMethod, $expectedMethodReturn, $prop, $methodReturnValue = null) {
+ if ($methodReturnValue === null) {
+ $methodReturnValue = $expectedMethodReturn;
+ }
+ $node = $this->createMock(ITrashBinNode::class);
+ $node->expects(self::once())->method($expectedMethod)->willReturn($methodReturnValue);
+ $propFind = new PropFind('', [$prop]);
+ $plugin = new TrashBinPlugin();
+ $plugin->propFind($propFind, $node);
+
+ self::assertEquals($expectedMethodReturn, $propFind->get($prop));
+ }
+
+ public function providesMethods() {
+ return [
+ ['getOriginalFileName', 'bar.txt', TrashBinPlugin::TRASHBIN_ORIGINAL_FILENAME],
+ ['getOriginalLocation', 'foo/bar.txt', TrashBinPlugin::TRASHBIN_ORIGINAL_LOCATION],
+ ['getDeleteTimestamp', 123456, TrashBinPlugin::TRASHBIN_DELETE_TIMESTAMP],
+ ['getDeleteTimestamp', new GetLastModified(123456), TrashBinPlugin::TRASHBIN_DELETE_DATETIME, 123456]
+ ];
+ }
+}
diff --git a/apps/files_trashbin/lib/Trashbin.php b/apps/files_trashbin/lib/Trashbin.php
index 93e8fc5475c2..e8407eb22c9b 100644
--- a/apps/files_trashbin/lib/Trashbin.php
+++ b/apps/files_trashbin/lib/Trashbin.php
@@ -474,32 +474,35 @@ private static function copy(View $view, $source, $target) {
*
* @return bool true on success, false otherwise
*/
- public static function restore($file, $filename, $timestamp) {
+ public static function restore($file, $filename, $timestamp, $targetLocation = null) {
$user = User::getUser();
$view = new View('/' . $user);
- $location = '';
- if ($timestamp) {
- $location = self::getLocation($user, $filename, $timestamp);
- if ($location === false) {
- \OCP\Util::writeLog('files_trashbin', 'Original location of file ' . $filename .
- ' not found in database, hence restoring into user\'s root instead', \OCP\Util::DEBUG);
- } else {
- // if location no longer exists, restore file in the root directory
- if ($location !== '/' &&
- (!$view->is_dir('files/' . $location) ||
- !$view->isCreatable('files/' . $location))
- ) {
- $location = '';
+ if ($targetLocation === null) {
+ $location = '';
+ if ($timestamp) {
+ $location = self::getLocation($user, $filename, $timestamp);
+ if ($location === false) {
+ \OCP\Util::writeLog('files_trashbin', 'Original location of file ' . $filename .
+ ' not found in database, hence restoring into user\'s root instead', \OCP\Util::DEBUG);
+ } else {
+ // if location no longer exists, restore file in the root directory
+ if ($location !== '/' &&
+ (!$view->is_dir('files/' . $location) ||
+ !$view->isCreatable('files/' . $location))
+ ) {
+ $location = '';
+ }
}
}
- }
- // we need a extension in case a file/dir with the same name already exists
- $uniqueFilename = self::getUniqueFilename($location, $filename, $view);
+ // we need a extension in case a file/dir with the same name already exists
+ $uniqueFilename = self::getUniqueFilename($location, $filename, $view);
+ $targetLocation = $location . '/' . $uniqueFilename;
+ }
$source = Filesystem::normalizePath('files_trashbin/files/' . $file);
- $target = Filesystem::normalizePath('files/' . $location . '/' . $uniqueFilename);
+ $target = Filesystem::normalizePath('files/' . $targetLocation);
if (!$view->file_exists($source)) {
return false;
}
@@ -512,12 +515,12 @@ public static function restore($file, $filename, $timestamp) {
if ($restoreResult) {
$fakeRoot = $view->getRoot();
$view->chroot('/' . $user . '/files');
- $view->touch('/' . $location . '/' . $uniqueFilename, $mtime);
+ $view->touch('/' . $targetLocation, $mtime);
$view->chroot($fakeRoot);
- \OCP\Util::emitHook('\OCA\Files_Trashbin\Trashbin', 'post_restore', ['filePath' => Filesystem::normalizePath('/' . $location . '/' . $uniqueFilename),
+ \OCP\Util::emitHook('\OCA\Files_Trashbin\Trashbin', 'post_restore', ['filePath' => Filesystem::normalizePath('/' . $targetLocation),
'trashPath' => Filesystem::normalizePath($file)]);
- self::restoreVersions($view, $file, $filename, $uniqueFilename, $location, $timestamp);
+ self::restoreVersions($view, $file, $filename, $targetLocation, $timestamp);
if ($timestamp) {
$query = \OC_DB::prepare('DELETE FROM `*PREFIX*files_trash` WHERE `user`=? AND `id`=? AND `timestamp`=?');
@@ -541,12 +544,12 @@ public static function restore($file, $filename, $timestamp) {
* @param int $timestamp deletion time
* @return false|null
*/
- private static function restoreVersions(View $view, $file, $filename, $uniqueFilename, $location, $timestamp) {
+ private static function restoreVersions(View $view, $file, $filename, $targetLocation, $timestamp) {
if (\OCP\App::isEnabled('files_versions')) {
$user = User::getUser();
$rootView = new View('/');
- $target = Filesystem::normalizePath('/' . $location . '/' . $uniqueFilename);
+ $target = Filesystem::normalizePath('/' . $targetLocation);
list($owner, $ownerPath) = self::getUidAndFilename($target);
diff --git a/tests/acceptance/features/apiTrashbin/trashbinRestore.feature b/tests/acceptance/features/apiTrashbin/trashbinRestore.feature
index 6e3bcb8cada6..63cb89db1e96 100644
--- a/tests/acceptance/features/apiTrashbin/trashbinRestore.feature
+++ b/tests/acceptance/features/apiTrashbin/trashbinRestore.feature
@@ -19,7 +19,6 @@ Feature: Restore deleted files/folders
And user "user0" has shared folder "/shared" with user "user1"
And user "user1" has moved file "/shared" to "/renamed_shared"
And user "user1" has deleted file "/renamed_shared/shared_file.txt"
- And user "user1" has logged in to a web-style session
When user "user1" restores the file with original path "/renamed_shared/shared_file.txt" using the trashbin API
Then as "user1" the file with original path "/renamed_shared/shared_file.txt" should not exist in trash
And user "user1" should see the following elements
@@ -36,7 +35,6 @@ Feature: Restore deleted files/folders
And user "user0" has been created with default attributes and skeleton files
And user "user0" has deleted file "/textfile0.txt"
And as "user0" file "/textfile0.txt" should exist in trash
- And user "user0" has logged in to a web-style session
When user "user0" restores the folder with original path "/textfile0.txt" using the trashbin API
Then as "user0" the folder with original path "/textfile0.txt" should not exist in trash
And user "user0" should see the following elements
@@ -59,7 +57,6 @@ Feature: Restore deleted files/folders
And user "user0" has created folder "/new-folder"
And user "user0" has moved file "/textfile0.txt" to "/new-folder/new-file.txt"
And user "user0" has deleted file "/new-folder/new-file.txt"
- And user "user0" has logged in to a web-style session
When user "user0" restores the file with original path "/new-folder/new-file.txt" using the trashbin API
Then as "user0" the file with original path "/new-folder/new-file.txt" should not exist in trash
And as "user0" file "/new-folder/new-file.txt" should exist
@@ -68,22 +65,6 @@ Feature: Restore deleted files/folders
| old |
| new |
- Scenario Outline: A file deleted from a folder is restored to root if the original folder does not exist
- Given using DAV path
- And user "user0" has been created with default attributes and skeleton files
- And user "user0" has created folder "/new-folder"
- And user "user0" has moved file "/textfile0.txt" to "/new-folder/new-file.txt"
- And user "user0" has deleted file "/new-folder/new-file.txt"
- And user "user0" has deleted folder "/new-folder"
- And user "user0" has logged in to a web-style session
- When user "user0" restores the file with original path "/new-folder/new-file.txt" using the trashbin API
- Then as "user0" the file with original path "/new-folder/new-file.txt" should not exist in trash
- And as "user0" file "/new-file.txt" should exist
- Examples:
- | dav-path |
- | old |
- | new |
-
Scenario Outline: A file deleted from a folder is restored to the original folder if the original folder was deleted and restored
Given using DAV path
And user "user0" has been created with default attributes and skeleton files
@@ -91,7 +72,6 @@ Feature: Restore deleted files/folders
And user "user0" has moved file "/textfile0.txt" to "/new-folder/new-file.txt"
And user "user0" has deleted file "/new-folder/new-file.txt"
And user "user0" has deleted folder "/new-folder"
- And user "user0" has logged in to a web-style session
When user "user0" restores the folder with original path "/new-folder" using the trashbin API
And user "user0" restores the file with original path "/new-folder/new-file.txt" using the trashbin API
Then as "user0" the file with original path "/new-folder/new-file.txt" should not exist in trash
@@ -108,7 +88,6 @@ Feature: Restore deleted files/folders
And user "user0" has moved file "/textfile0.txt" to "/new-folder/new-file.txt"
And user "user0" has deleted file "/new-folder/new-file.txt"
And user "user0" has deleted folder "/new-folder"
- And user "user0" has logged in to a web-style session
When user "user0" creates folder "/new-folder" using the WebDAV API
And user "user0" restores the file with original path "/new-folder/new-file.txt" using the trashbin API
Then as "user0" the file with original path "/new-folder/new-file.txt" should not exist in trash
@@ -129,7 +108,6 @@ Feature: Restore deleted files/folders
And user "user0" has moved file "/textfile0.txt" to "/local_storage/tmp/textfile0.txt"
And user "user0" has deleted file "/local_storage/tmp/textfile0.txt"
And as "user0" the folder with original path "/local_storage/tmp/textfile0.txt" should exist in trash
- And user "user0" has logged in to a web-style session
When user "user0" restores the folder with original path "/local_storage/tmp/textfile0.txt" using the trashbin API
Then as "user0" the folder with original path "/local_storage/tmp/textfile0.txt" should not exist in trash
And user "user0" should see the following elements
@@ -153,7 +131,6 @@ Feature: Restore deleted files/folders
And user "user0" has uploaded chunk file "1" of "1" with "AA" to "/local_storage/tmp/textfile0.txt"
And user "user0" has deleted file "/local_storage/tmp/textfile0.txt"
And as "user0" the folder with original path "/local_storage/tmp/textfile0.txt" should exist in trash
- And user "user0" has logged in to a web-style session
When user "user0" restores the folder with original path "/local_storage/tmp/textfile0.txt" using the trashbin API
Then as "user0" the folder with original path "/local_storage/tmp/textfile0.txt" should not exist in trash
And the downloaded content when downloading file "/local_storage/tmp/textfile0.txt" for user "user0" with range "bytes=0-1" should be "AA"
@@ -171,8 +148,6 @@ Feature: Restore deleted files/folders
| 1 | AA |
And user "user0" has deleted file "/local_storage/tmp/textfile0.txt"
And as "user0" the folder with original path "/local_storage/tmp/textfile0.txt" should exist in trash
- And user "user0" has logged in to a web-style session
When user "user0" restores the folder with original path "/local_storage/tmp/textfile0.txt" using the trashbin API
Then as "user0" the folder with original path "/local_storage/tmp/textfile0.txt" should not exist in trash
And the downloaded content when downloading file "/local_storage/tmp/textfile0.txt" for user "user0" with range "bytes=0-1" should be "AA"
-
\ No newline at end of file
diff --git a/tests/acceptance/features/bootstrap/TrashbinContext.php b/tests/acceptance/features/bootstrap/TrashbinContext.php
index ecc130326cf6..4dc4645fbb56 100644
--- a/tests/acceptance/features/bootstrap/TrashbinContext.php
+++ b/tests/acceptance/features/bootstrap/TrashbinContext.php
@@ -21,6 +21,7 @@
use Behat\Behat\Context\Context;
use Behat\Behat\Hook\Scope\BeforeScenarioScope;
+use TestHelpers\WebDavHelper;
require_once 'bootstrap.php';
@@ -44,21 +45,22 @@ class TrashbinContext implements Context {
* @return void
*/
public function emptyTrashbin($user) {
- $body = new \Behat\Gherkin\Node\TableNode(
- [['allfiles', 'true'], ['dir', '/']]
- );
- $this->featureContext->sendingToWithDirectUrl(
- $user, 'POST', "/index.php/apps/files_trashbin/ajax/delete.php", $body
+ $response = WebDavHelper::makeDavRequest(
+ $this->featureContext->getBaseUrl(),
+ $user,
+ $this->featureContext->getPasswordForUser($user),
+ 'DELETE',
+ "/trash-bin/$user/",
+ [],
+ null,
+ null,
+ 2,
+ 'trash-bin'
);
- $this->featureContext->theHTTPStatusCodeShouldBe('200');
- $decodedResponse = \json_decode(
- $this->featureContext->getResponse()->getBody(), true
+
+ PHPUnit\Framework\Assert::assertEquals(
+ 204, $response->getStatusCode()
);
- if (isset($decodedResponse['status'])) {
- PHPUnit\Framework\Assert::assertNotEquals(
- 'error', $decodedResponse['status']
- );
- }
}
/**
@@ -70,20 +72,66 @@ public function emptyTrashbin($user) {
* @return array response
*/
public function listTrashbinFolder($user, $path) {
- $params = '?dir=' . \rawurlencode('/' . \trim($path, '/'));
- $this->featureContext->sendingToWithDirectUrl(
+ $path = $path ?? '/';
+ $responseXml = WebDavHelper::listFolder(
+ $this->featureContext->getBaseUrl(),
$user,
- 'GET',
- "/index.php/apps/files_trashbin/ajax/list.php$params",
- null
+ $this->featureContext->getPasswordForUser($user),
+ "/trash-bin/$user/$path",
+ 1,
+ [
+ 'oc:trashbin-original-filename',
+ 'oc:trashbin-original-location',
+ 'oc:trashbin-delete-timestamp',
+ 'd:getlastmodified'
+ ],
+ 'trash-bin'
);
- $this->featureContext->theHTTPStatusCodeShouldBe('200');
- $decodedResponse = \json_decode(
- $this->featureContext->getResponse()->getBody(), true
+ $xmlElements = $responseXml->xpath('//d:response');
+ $files = \array_map(
+ static function (SimpleXMLElement $element) {
+ $href = $element->xpath('./d:href')[0];
+
+ $propStats = $element->xpath('./d:propstat');
+ $successPropStat = \array_filter(
+ $propStats, static function (SimpleXMLElement $propStat) {
+ $status = $propStat->xpath('./d:status');
+ return (string)$status[0] === 'HTTP/1.1 200 OK';
+ }
+ );
+ if (isset($successPropStat[0])) {
+ $successPropStat = $successPropStat[0];
+
+ $name = $successPropStat->xpath('./d:prop/oc:trashbin-original-filename');
+ $mtime = $successPropStat->xpath('./d:prop/oc:trashbin-delete-timestamp');
+ $originalLocation = $successPropStat->xpath('./d:prop/oc:trashbin-original-location');
+ } else {
+ $name = [];
+ $mtime = [];
+ $originalLocation = [];
+ }
+
+ return [
+ 'href' => (string)$href,
+ 'name' => isset($name[0]) ? (string)$name[0] : null,
+ 'mtime' => isset($mtime[0]) ? (string)$mtime[0] : null,
+ 'original-location' => isset($originalLocation[0]) ? (string)$originalLocation[0] : null
+ ];
+ }, $xmlElements
);
- return $decodedResponse['data']['files'];
+ // filter root element
+ $files = \array_filter(
+ $files, static function ($element) use ($user, $path) {
+ $path = \ltrim($path, '/');
+ if ($path !== '') {
+ $path .= '/';
+ }
+ return ($element['href'] !== "/remote.php/dav/trash-bin/$user/$path");
+ }
+ );
+ return $files;
}
/**
@@ -108,14 +156,8 @@ public function asFileOrFolderExistsInTrash($user, $path) {
return;
}
- $subdir = \trim(\dirname($sections[1]), '/');
- if ($subdir !== '' && $subdir !== '.') {
- $subdir = "$firstEntry/$subdir";
- } else {
- $subdir = $firstEntry;
- }
-
- $listing = $this->listTrashbinFolder($user, $subdir);
+ // TODO: handle deeper structures
+ $listing = $this->listTrashbinFolder($user, \basename(\rtrim($firstEntry['href'], '/')));
$checkedName = \basename($path);
$found = false;
@@ -141,41 +183,32 @@ private function isInTrash($user, $originalPath) {
$listing = $this->listTrashbinFolder($user, null);
$originalPath = \trim($originalPath, '/');
- $found = false;
foreach ($listing as $entry) {
- if (\substr($entry['extraData'], 0, 2) === "./") {
- $entry['extraData'] = \substr($entry['extraData'], 2);
- }
- if ($entry['extraData'] === $originalPath) {
- $found = true;
- break;
+ if ($entry['original-location'] === $originalPath) {
+ return true;
}
}
- return $found;
+ return false;
}
/**
* @param string $user
- * @param string $elementTrashID
+ * @param string $trashItemHRef
+ * @param string $originalLocation
*
* @return void
*/
- private function sendUndeleteRequest($user, $elementTrashID) {
- $body = new \Behat\Gherkin\Node\TableNode(
- [['files', "[\"$elementTrashID\"]"], ['dir', '/']]
- );
- $this->featureContext->sendingToWithDirectUrl(
- $user, 'POST', "/index.php/apps/files_trashbin/ajax/undelete.php", $body
+ private function sendUndeleteRequest($user, $trashItemHRef, $originalLocation) {
+ $destinationValue = $this->featureContext->getBaseUrl() . "/remote.php/dav/files/$user/$originalLocation";
+
+ $trashItemHRef = \substr($trashItemHRef, 15);
+ $headers['Destination'] = $destinationValue;
+ $response = $this->featureContext->makeDavRequest(
+ $user, 'MOVE', $trashItemHRef, $headers, null, 'trash-bin', null, 2
);
- $this->featureContext->theHTTPStatusCodeShouldBe('200');
- $decodedResponse = \json_decode(
- $this->featureContext->getResponse()->getBody(), true
+ PHPUnit\Framework\Assert::assertEquals(
+ 201, $response->getStatusCode()
);
- if (isset($decodedResponse['status'])) {
- PHPUnit\Framework\Assert::assertNotEquals(
- 'error', $decodedResponse['status']
- );
- }
}
/**
@@ -189,13 +222,11 @@ private function restoreElement($user, $originalPath) {
$originalPath = \trim($originalPath, '/');
foreach ($listing as $entry) {
- if (\substr($entry['extraData'], 0, 2) === "./") {
- $entry['extraData'] = \substr($entry['extraData'], 2);
- }
- if ($entry['extraData'] === $originalPath) {
+ if ($entry['original-location'] === $originalPath) {
$this->sendUndeleteRequest(
$user,
- $entry['name'] . '.d' . \floor((integer)$entry['mtime'] / 1000)
+ $entry['href'],
+ $entry['original-location']
);
break;
}
@@ -266,7 +297,7 @@ private function findFirstTrashedEntry($user, $name) {
foreach ($listing as $entry) {
if ($entry['name'] === $name) {
- return $entry['name'] . '.d' . ((int)$entry['mtime'] / 1000);
+ return $entry;
}
}