* @author Roeland Jago Douma * @author Stefan Weil * @author Thomas Müller * @author Vincent Petry * * @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\Connector\Sabre; use OC\User\User; use OCA\DAV\Connector\Sabre\File; use OCA\DAV\Connector\Sabre\FilesPlugin; use OCA\DAV\Connector\Sabre\Node; use OCP\Files\StorageNotAvailableException; use OCP\IConfig; use OCP\IPreview; use OCP\IRequest; use Sabre\DAV\PropFind; use Sabre\DAV\PropPatch; use Sabre\DAV\Server; use Sabre\DAV\Tree; use Sabre\HTTP\RequestInterface; use Sabre\HTTP\ResponseInterface; use Test\TestCase; use OCA\DAV\Upload\FutureFile; use OCA\DAV\Connector\Sabre\Directory; use OCP\Files\FileInfo; /** * Copyright (c) 2015 Vincent Petry * This file is licensed under the Affero General Public License version 3 or * later. * See the COPYING-README file. */ class FilesPluginTest extends TestCase { const GETETAG_PROPERTYNAME = FilesPlugin::GETETAG_PROPERTYNAME; const FILEID_PROPERTYNAME = FilesPlugin::FILEID_PROPERTYNAME; const INTERNAL_FILEID_PROPERTYNAME = FilesPlugin::INTERNAL_FILEID_PROPERTYNAME; const SIZE_PROPERTYNAME = FilesPlugin::SIZE_PROPERTYNAME; const PERMISSIONS_PROPERTYNAME = FilesPlugin::PERMISSIONS_PROPERTYNAME; const LASTMODIFIED_PROPERTYNAME = FilesPlugin::LASTMODIFIED_PROPERTYNAME; const DOWNLOADURL_PROPERTYNAME = FilesPlugin::DOWNLOADURL_PROPERTYNAME; const OWNER_ID_PROPERTYNAME = FilesPlugin::OWNER_ID_PROPERTYNAME; const OWNER_DISPLAY_NAME_PROPERTYNAME = FilesPlugin::OWNER_DISPLAY_NAME_PROPERTYNAME; const DATA_FINGERPRINT_PROPERTYNAME = FilesPlugin::DATA_FINGERPRINT_PROPERTYNAME; const HAS_PREVIEW_PROPERTYNAME = FilesPlugin::HAS_PREVIEW_PROPERTYNAME; /** * @var \Sabre\DAV\Server | \PHPUnit_Framework_MockObject_MockObject */ private $server; /** * @var \Sabre\DAV\Tree | \PHPUnit_Framework_MockObject_MockObject */ private $tree; /** * @var FilesPlugin */ private $plugin; /** * @var \OCP\IConfig | \PHPUnit_Framework_MockObject_MockObject */ private $config; /** * @var \OCP\IRequest | \PHPUnit_Framework_MockObject_MockObject */ private $request; /** * @var \OCP\IPreview | \PHPUnit_Framework_MockObject_MockObject */ private $previewManager; public function setUp() { parent::setUp(); $this->server = $this->getMockBuilder(Server::class) ->disableOriginalConstructor() ->getMock(); $this->tree = $this->getMockBuilder(Tree::class) ->disableOriginalConstructor() ->getMock(); $this->config = $this->createMock(IConfig::class); $this->config->expects($this->any())->method('getSystemValue') ->with($this->equalTo('data-fingerprint'), $this->equalTo('')) ->willReturn('my_fingerprint'); $this->request = $this->getMockBuilder(IRequest::class) ->disableOriginalConstructor() ->getMock(); $this->previewManager = $this->getMockBuilder(IPreview::class) ->disableOriginalConstructor() ->getMock(); $this->plugin = new FilesPlugin( $this->tree, $this->config, $this->request, $this->previewManager ); $response = $this->getMockBuilder(ResponseInterface::class) ->disableOriginalConstructor() ->getMock(); $this->server->httpResponse = $response; $this->plugin->initialize($this->server); } /** * @param string $class * @return \PHPUnit_Framework_MockObject_MockObject */ private function createTestNode($class, $path = '/dummypath') { $node = $this->getMockBuilder($class) ->disableOriginalConstructor() ->getMock(); $node->expects($this->any()) ->method('getId') ->will($this->returnValue(123)); $this->tree->expects($this->any()) ->method('getNodeForPath') ->with($path) ->will($this->returnValue($node)); $node->expects($this->any()) ->method('getFileId') ->will($this->returnValue('00000123instanceid')); $node->expects($this->any()) ->method('getInternalFileId') ->will($this->returnValue('123')); $node->expects($this->any()) ->method('getEtag') ->will($this->returnValue('"abc"')); $node->expects($this->any()) ->method('getDavPermissions') ->will($this->returnValue('DWCKMSR')); $fileInfo = $this->createMock(FileInfo::class); $fileInfo->expects($this->any()) ->method('isReadable') ->willReturn(true); $node->expects($this->any()) ->method('getFileInfo') ->willReturn($fileInfo); return $node; } public function testGetPropertiesForFile() { /** @var \OCA\DAV\Connector\Sabre\File | \PHPUnit_Framework_MockObject_MockObject $node */ $node = $this->createTestNode('\OCA\DAV\Connector\Sabre\File'); $propFind = new PropFind( '/dummyPath', array( self::GETETAG_PROPERTYNAME, self::FILEID_PROPERTYNAME, self::INTERNAL_FILEID_PROPERTYNAME, self::SIZE_PROPERTYNAME, self::PERMISSIONS_PROPERTYNAME, self::DOWNLOADURL_PROPERTYNAME, self::OWNER_ID_PROPERTYNAME, self::OWNER_DISPLAY_NAME_PROPERTYNAME, self::DATA_FINGERPRINT_PROPERTYNAME, ), 0 ); $user = $this->getMockBuilder(User::class) ->disableOriginalConstructor()->getMock(); $user ->expects($this->once()) ->method('getUID') ->will($this->returnValue('foo')); $user ->expects($this->once()) ->method('getDisplayName') ->will($this->returnValue('M. Foo')); $node->expects($this->once()) ->method('getDirectDownload') ->will($this->returnValue(array('url' => 'http://example.com/'))); $node->expects($this->exactly(2)) ->method('getOwner') ->will($this->returnValue($user)); $this->plugin->handleGetProperties( $propFind, $node ); $this->assertEquals('"abc"', $propFind->get(self::GETETAG_PROPERTYNAME)); $this->assertEquals('00000123instanceid', $propFind->get(self::FILEID_PROPERTYNAME)); $this->assertEquals('123', $propFind->get(self::INTERNAL_FILEID_PROPERTYNAME)); $this->assertEquals(null, $propFind->get(self::SIZE_PROPERTYNAME)); $this->assertEquals('DWCKMSR', $propFind->get(self::PERMISSIONS_PROPERTYNAME)); $this->assertEquals('http://example.com/', $propFind->get(self::DOWNLOADURL_PROPERTYNAME)); $this->assertEquals('foo', $propFind->get(self::OWNER_ID_PROPERTYNAME)); $this->assertEquals('M. Foo', $propFind->get(self::OWNER_DISPLAY_NAME_PROPERTYNAME)); $this->assertEquals('my_fingerprint', $propFind->get(self::DATA_FINGERPRINT_PROPERTYNAME)); $this->assertEquals([self::SIZE_PROPERTYNAME], $propFind->get404Properties()); } public function testGetPropertiesStorageNotAvailable() { /** @var \OCA\DAV\Connector\Sabre\File | \PHPUnit_Framework_MockObject_MockObject $node */ $node = $this->createTestNode('\OCA\DAV\Connector\Sabre\File'); $propFind = new PropFind( '/dummyPath', array( self::DOWNLOADURL_PROPERTYNAME, ), 0 ); $node->expects($this->once()) ->method('getDirectDownload') ->will($this->throwException(new StorageNotAvailableException())); $this->plugin->handleGetProperties( $propFind, $node ); $this->assertEquals(null, $propFind->get(self::DOWNLOADURL_PROPERTYNAME)); } public function testGetPublicPermissions() { $this->plugin = new FilesPlugin( $this->tree, $this->config, $this->getMockBuilder(IRequest::class) ->disableOriginalConstructor() ->getMock(), $this->previewManager, true); $this->plugin->initialize($this->server); $propFind = new PropFind( '/dummyPath', [ self::PERMISSIONS_PROPERTYNAME, ], 0 ); /** @var \OCA\DAV\Connector\Sabre\File | \PHPUnit_Framework_MockObject_MockObject $node */ $node = $this->createTestNode('\OCA\DAV\Connector\Sabre\File'); $node->expects($this->any()) ->method('getDavPermissions') ->will($this->returnValue('DWCKMSR')); $this->plugin->handleGetProperties( $propFind, $node ); $this->assertEquals('DWCKR', $propFind->get(self::PERMISSIONS_PROPERTYNAME)); } public function testGetPropertiesForDirectory() { /** @var \OCA\DAV\Connector\Sabre\Directory | \PHPUnit_Framework_MockObject_MockObject $node */ $node = $this->createTestNode('\OCA\DAV\Connector\Sabre\Directory'); $propFind = new PropFind( '/dummyPath', array( self::GETETAG_PROPERTYNAME, self::FILEID_PROPERTYNAME, self::SIZE_PROPERTYNAME, self::PERMISSIONS_PROPERTYNAME, self::DOWNLOADURL_PROPERTYNAME, self::DATA_FINGERPRINT_PROPERTYNAME, ), 0 ); $node->expects($this->once()) ->method('getSize') ->will($this->returnValue(1025)); $this->plugin->handleGetProperties( $propFind, $node ); $this->assertEquals('"abc"', $propFind->get(self::GETETAG_PROPERTYNAME)); $this->assertEquals('00000123instanceid', $propFind->get(self::FILEID_PROPERTYNAME)); $this->assertEquals(1025, $propFind->get(self::SIZE_PROPERTYNAME)); $this->assertEquals('DWCKMSR', $propFind->get(self::PERMISSIONS_PROPERTYNAME)); $this->assertEquals(null, $propFind->get(self::DOWNLOADURL_PROPERTYNAME)); $this->assertEquals('my_fingerprint', $propFind->get(self::DATA_FINGERPRINT_PROPERTYNAME)); $this->assertEquals([self::DOWNLOADURL_PROPERTYNAME], $propFind->get404Properties()); } public function testGetPropertiesForRootDirectory() { /** @var \OCA\DAV\Connector\Sabre\Directory | \PHPUnit_Framework_MockObject_MockObject $node */ $node = $this->getMockBuilder(Directory::class) ->disableOriginalConstructor() ->getMock(); $node->expects($this->any())->method('getPath')->willReturn('/'); $fileInfo = $this->createMock(FileInfo::class); $fileInfo->expects($this->any()) ->method('isReadable') ->willReturn(true); $node->expects($this->any()) ->method('getFileInfo') ->willReturn($fileInfo); $propFind = new PropFind( '/', [ self::DATA_FINGERPRINT_PROPERTYNAME, ], 0 ); $this->plugin->handleGetProperties( $propFind, $node ); $this->assertEquals('my_fingerprint', $propFind->get(self::DATA_FINGERPRINT_PROPERTYNAME)); } public function testGetPropertiesWhenNoPermission() { // No read permissions can be caused by files access control. // But we still want to load the directory list, so this is okay for us. // $this->expectException(\Sabre\DAV\Exception\NotFound::class); /** @var \OCA\DAV\Connector\Sabre\Directory|\PHPUnit_Framework_MockObject_MockObject $node */ $node = $this->getMockBuilder(Directory::class) ->disableOriginalConstructor() ->getMock(); $node->expects($this->any())->method('getPath')->willReturn('/'); $fileInfo = $this->createMock(FileInfo::class); $fileInfo->expects($this->any()) ->method('isReadable') ->willReturn(false); $node->expects($this->any()) ->method('getFileInfo') ->willReturn($fileInfo); $propFind = new PropFind( '/test', [ self::DATA_FINGERPRINT_PROPERTYNAME, ], 0 ); $this->plugin->handleGetProperties( $propFind, $node ); } public function testUpdateProps() { $node = $this->createTestNode('\OCA\DAV\Connector\Sabre\File'); $testDate = 'Fri, 13 Feb 2015 00:01:02 GMT'; $node->expects($this->once()) ->method('touch') ->with($testDate); $node->expects($this->once()) ->method('setEtag') ->with('newetag') ->will($this->returnValue(true)); // properties to set $propPatch = new PropPatch(array( self::GETETAG_PROPERTYNAME => 'newetag', self::LASTMODIFIED_PROPERTYNAME => $testDate )); $this->plugin->handleUpdateProperties( '/dummypath', $propPatch ); $propPatch->commit(); $this->assertEmpty($propPatch->getRemainingMutations()); $result = $propPatch->getResult(); $this->assertEquals(200, $result[self::LASTMODIFIED_PROPERTYNAME]); $this->assertEquals(200, $result[self::GETETAG_PROPERTYNAME]); } public function testUpdatePropsForbidden() { $propPatch = new PropPatch(array( self::OWNER_ID_PROPERTYNAME => 'user2', self::OWNER_DISPLAY_NAME_PROPERTYNAME => 'User Two', self::FILEID_PROPERTYNAME => 12345, self::PERMISSIONS_PROPERTYNAME => 'C', self::SIZE_PROPERTYNAME => 123, self::DOWNLOADURL_PROPERTYNAME => 'http://example.com/', )); $this->plugin->handleUpdateProperties( '/dummypath', $propPatch ); $propPatch->commit(); $this->assertEmpty($propPatch->getRemainingMutations()); $result = $propPatch->getResult(); $this->assertEquals(403, $result[self::OWNER_ID_PROPERTYNAME]); $this->assertEquals(403, $result[self::OWNER_DISPLAY_NAME_PROPERTYNAME]); $this->assertEquals(403, $result[self::FILEID_PROPERTYNAME]); $this->assertEquals(403, $result[self::PERMISSIONS_PROPERTYNAME]); $this->assertEquals(403, $result[self::SIZE_PROPERTYNAME]); $this->assertEquals(403, $result[self::DOWNLOADURL_PROPERTYNAME]); } /** * Testcase from https://github.com/owncloud/core/issues/5251 * * |-FolderA * |-text.txt * |-test.txt * * FolderA is an incoming shared folder and there are no delete permissions. * Thus moving /FolderA/test.txt to /test.txt should fail already on that check * * @expectedException \Sabre\DAV\Exception\Forbidden * @expectedExceptionMessage FolderA/test.txt cannot be deleted */ public function testMoveSrcNotDeletable() { $fileInfoFolderATestTXT = $this->getMockBuilder(FileInfo::class) ->disableOriginalConstructor() ->getMock(); $fileInfoFolderATestTXT->expects($this->once()) ->method('isDeletable') ->willReturn(false); $node = $this->getMockBuilder(Node::class) ->disableOriginalConstructor() ->getMock(); $node->expects($this->once()) ->method('getFileInfo') ->willReturn($fileInfoFolderATestTXT); $this->tree->expects($this->once())->method('getNodeForPath') ->willReturn($node); $this->plugin->checkMove('FolderA/test.txt', 'test.txt'); } public function testMoveSrcDeletable() { $fileInfoFolderATestTXT = $this->getMockBuilder(FileInfo::class) ->disableOriginalConstructor() ->getMock(); $fileInfoFolderATestTXT->expects($this->once()) ->method('isDeletable') ->willReturn(true); $node = $this->getMockBuilder(Node::class) ->disableOriginalConstructor() ->getMock(); $node->expects($this->once()) ->method('getFileInfo') ->willReturn($fileInfoFolderATestTXT); $this->tree->expects($this->once())->method('getNodeForPath') ->willReturn($node); $this->plugin->checkMove('FolderA/test.txt', 'test.txt'); } /** * @expectedException \Sabre\DAV\Exception\NotFound * @expectedExceptionMessage FolderA/test.txt does not exist */ public function testMoveSrcNotExist() { $node = $this->getMockBuilder(Node::class) ->disableOriginalConstructor() ->getMock(); $node->expects($this->once()) ->method('getFileInfo') ->willReturn(null); $this->tree->expects($this->once())->method('getNodeForPath') ->willReturn($node); $this->plugin->checkMove('FolderA/test.txt', 'test.txt'); } public function downloadHeadersProvider() { return [ [ false, 'attachment; filename*=UTF-8\'\'somefile.xml; filename="somefile.xml"' ], [ true, 'attachment; filename="somefile.xml"' ], ]; } /** * @dataProvider downloadHeadersProvider */ public function testDownloadHeaders($isClumsyAgent, $contentDispositionHeader) { $request = $this->getMockBuilder(RequestInterface::class) ->disableOriginalConstructor() ->getMock(); $response = $this->getMockBuilder(ResponseInterface::class) ->disableOriginalConstructor() ->getMock(); $request ->expects($this->once()) ->method('getPath') ->will($this->returnValue('test/somefile.xml')); $node = $this->getMockBuilder(File::class) ->disableOriginalConstructor() ->getMock(); $node ->expects($this->once()) ->method('getName') ->will($this->returnValue('somefile.xml')); $this->tree ->expects($this->once()) ->method('getNodeForPath') ->with('test/somefile.xml') ->will($this->returnValue($node)); $this->request ->expects($this->once()) ->method('isUserAgent') ->will($this->returnValue($isClumsyAgent)); $response ->expects($this->once()) ->method('addHeader') ->with('Content-Disposition', $contentDispositionHeader); $this->plugin->httpGet($request, $response); } public function testHasPreview() { /** @var \OCA\DAV\Connector\Sabre\Directory | \PHPUnit_Framework_MockObject_MockObject $node */ $node = $this->createTestNode('\OCA\DAV\Connector\Sabre\Directory'); $propFind = new PropFind( '/dummyPath', array( self::HAS_PREVIEW_PROPERTYNAME ), 0 ); $this->previewManager->expects($this->once()) ->method('isAvailable') ->will($this->returnValue(false)); $this->plugin->handleGetProperties( $propFind, $node ); $this->assertEquals("false", $propFind->get(self::HAS_PREVIEW_PROPERTYNAME)); } public function testBeforeMoveFutureFileSkip() { $node = $this->createMock(Directory::class); $this->tree->expects($this->any()) ->method('getNodeForPath') ->with('source') ->will($this->returnValue($node)); $this->server->httpResponse->expects($this->never()) ->method('setStatus'); $this->assertNull($this->plugin->beforeMoveFutureFile('source', 'target')); } public function testBeforeMoveFutureFileSkipNonExisting() { $sourceNode = $this->createMock(FutureFile::class); $this->tree->expects($this->any()) ->method('getNodeForPath') ->with('source') ->will($this->returnValue($sourceNode)); $this->tree->expects($this->any()) ->method('nodeExists') ->with('target') ->will($this->returnValue(false)); $this->server->httpResponse->expects($this->never()) ->method('setStatus'); $this->assertNull($this->plugin->beforeMoveFutureFile('source', 'target')); } public function testBeforeMoveFutureFileMoveIt() { $sourceNode = $this->createMock(FutureFile::class); $this->tree->expects($this->any()) ->method('getNodeForPath') ->with('source') ->will($this->returnValue($sourceNode)); $this->tree->expects($this->any()) ->method('nodeExists') ->with('target') ->will($this->returnValue(true)); $this->tree->expects($this->once()) ->method('move') ->with('source', 'target'); $this->server->httpResponse->expects($this->once()) ->method('setHeader') ->with('Content-Length', '0'); $this->server->httpResponse->expects($this->once()) ->method('setStatus') ->with(204); $this->assertFalse($this->plugin->beforeMoveFutureFile('source', 'target')); } }