Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
91 changes: 75 additions & 16 deletions apps/dav/lib/Connector/Sabre/File.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
namespace OCA\DAV\Connector\Sabre;

use OC\Files\Filesystem;
use OC\Files\Storage\Storage;
use OCA\DAV\Connector\Sabre\Exception\EntityTooLarge;
use OCA\DAV\Connector\Sabre\Exception\FileLocked;
use OCA\DAV\Connector\Sabre\Exception\Forbidden as DAVForbiddenException;
Expand Down Expand Up @@ -133,6 +134,10 @@ public function put($data) {
list($count, $result) = \OC_Helper::streamCopy($data, $target);
fclose($target);

if (!self::isChecksumValid($partStorage, $internalPartPath)) {
throw new BadRequest('The computed checksum does not match the one received from the client.');
}

if ($result === false) {
$expected = -1;
if (isset($_SERVER['CONTENT_LENGTH'])) {
Expand Down Expand Up @@ -222,15 +227,17 @@ public function put($data) {

$this->refreshInfo();

if (isset($request->server['HTTP_OC_CHECKSUM'])) {
$checksum = trim($request->server['HTTP_OC_CHECKSUM']);
$this->fileView->putFileInfo($this->path, ['checksum' => $checksum]);
$this->refreshInfo();
} else if ($this->getChecksum() !== null && $this->getChecksum() !== '') {
$this->fileView->putFileInfo($this->path, ['checksum' => '']);
$this->refreshInfo();
$meta = $partStorage->getCache()->get($internalPartPath);

if (isset($meta['checksum'])) {
$this->fileView->putFileInfo(
$this->path,
['checksum' => $meta['checksum']]
);
}

$this->refreshInfo();

} catch (StorageNotAvailableException $e) {
throw new ServiceUnavailable("Failed to check file size: " . $e->getMessage());
}
Expand Down Expand Up @@ -441,6 +448,10 @@ private function createFileChunked($data) {

$chunk_handler->file_assemble($partStorage, $partInternalPath);

if (!self::isChecksumValid($partStorage, $partInternalPath)) {
throw new BadRequest('The computed checksum does not match the one received from the client.');
}

// here is the final atomic rename
$renameOkay = $targetStorage->moveFromStorage($partStorage, $partInternalPath, $targetInternalPath);
$fileExists = $targetStorage->file_exists($targetInternalPath);
Expand Down Expand Up @@ -479,13 +490,20 @@ private function createFileChunked($data) {
// FIXME: should call refreshInfo but can't because $this->path is not the of the final file
$info = $this->fileView->getFileInfo($targetPath);

if (isset($request->server['HTTP_OC_CHECKSUM'])) {
$checksum = trim($request->server['HTTP_OC_CHECKSUM']);
$this->fileView->putFileInfo($targetPath, ['checksum' => $checksum]);
} else if ($info->getChecksum() !== null && $info->getChecksum() !== '') {
$this->fileView->putFileInfo($this->path, ['checksum' => '']);

if (isset($partStorage) && isset($partInternalPath)) {
$checksums = $partStorage->getCache()->get($partInternalPath)['checksum'];
} else {
$checksums = $targetStorage->getCache()->get($targetInternalPath)['checksum'];
}

$this->fileView->putFileInfo(
$targetPath,
['checksum' => $checksums]
);

$this->refreshInfo();

$this->fileView->unlockFile($targetPath, ILockingProvider::LOCK_SHARED);

return $info->getEtag();
Expand All @@ -500,6 +518,29 @@ private function createFileChunked($data) {
return null;
}

/**
* will return true if checksum was not provided in request
*
* @param Storage $storage
* @param $path
* @return bool
*/
private static function isChecksumValid(Storage $storage, $path) {
$meta = $storage->getMetaData($path);
$request = \OC::$server->getRequest();

if (!isset($request->server['HTTP_OC_CHECKSUM']) || !isset($meta['checksum'])) {
// No comparison possible, skip the check
return true;
}

$expectedChecksum = trim($request->server['HTTP_OC_CHECKSUM']);
$computedChecksums = $meta['checksum'];

return strpos($computedChecksums, $expectedChecksum) !== false;

}

/**
* Returns whether a part file is needed for the given storage
* or whether the file can be assembled/uploaded directly on the
Expand Down Expand Up @@ -562,12 +603,30 @@ private function convertToSabreException(\Exception $e) {
throw new \Sabre\DAV\Exception($e->getMessage(), 0, $e);
}


/**
* Get the checksum for this file
*
* Set $algo to get a specific checksum, leave null to get all checksums
* (space seperated)
* @param null $algo
* @return string
*/
public function getChecksum() {
return $this->info->getChecksum();
public function getChecksum($algo = null) {
$allChecksums = $this->info->getChecksum();

if (!$algo) {
return $allChecksums;
}

$checksums = explode(' ', $allChecksums);
$algoPrefix = strtoupper($algo) . ':';

foreach ($checksums as $checksum) {
// starts with $algoPrefix
if (substr($checksum, 0, strlen($algoPrefix)) === $algoPrefix) {
return $checksum;
}
}

return '';
}
}
4 changes: 2 additions & 2 deletions apps/dav/lib/Connector/Sabre/FilesPlugin.php
Original file line number Diff line number Diff line change
Expand Up @@ -268,7 +268,7 @@ function httpGet(RequestInterface $request, ResponseInterface $response) {
if ($node instanceof \OCA\DAV\Connector\Sabre\File) {
//Add OC-Checksum header
/** @var $node File */
$checksum = $node->getChecksum();
$checksum = $node->getChecksum('sha1');
if ($checksum !== null && $checksum !== '') {
$response->addHeader('OC-Checksum', $checksum);
}
Expand Down Expand Up @@ -363,7 +363,7 @@ public function handleGetProperties(PropFind $propFind, \Sabre\DAV\INode $node)
});

$propFind->handle(self::CHECKSUMS_PROPERTYNAME, function() use ($node) {
$checksum = $node->getChecksum();
$checksum = $node->getChecksum('sha1');
if ($checksum === NULL || $checksum === '') {
return null;
}
Expand Down
4 changes: 4 additions & 0 deletions apps/files/lib/Capabilities.php
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,10 @@ public function __construct(IConfig $config) {
*/
public function getCapabilities() {
return [
'checksums' => [
'supportedTypes' => ['SHA1'],

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe we should also offer something else 😉

'preferredUploadType' => 'SHA1'
],
'files' => [
'bigfilechunking' => true,
'blacklisted_files' => $this->config->getSystemValue('blacklisted_files', ['.htaccess']),
Expand Down
88 changes: 46 additions & 42 deletions apps/files_trashbin/tests/TrashbinTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -147,50 +147,8 @@ protected function tearDown() {
parent::tearDown();
}

/**
* test expiration of files older then the max storage time defined for the trash
*/
public function testExpireOldFiles() {

$currentTime = time();
$expireAt = $currentTime - 2 * 24 * 60 * 60;
$expiredDate = $currentTime - 3 * 24 * 60 * 60;

// create some files
\OC\Files\Filesystem::file_put_contents('file1.txt', 'file1');
\OC\Files\Filesystem::file_put_contents('file2.txt', 'file2');
\OC\Files\Filesystem::file_put_contents('file3.txt', 'file3');

// delete them so that they end up in the trash bin
\OC\Files\Filesystem::unlink('file1.txt');
\OC\Files\Filesystem::unlink('file2.txt');
\OC\Files\Filesystem::unlink('file3.txt');

//make sure that files are in the trash bin
$filesInTrash = OCA\Files_Trashbin\Helper::getTrashFiles('/', self::TEST_TRASHBIN_USER1, 'name');
$this->assertSame(3, count($filesInTrash));

// every second file will get a date in the past so that it will get expired
$manipulatedList = $this->manipulateDeleteTime($filesInTrash, $this->trashRoot1, $expiredDate);

$testClass = new TrashbinForTesting();
list($sizeOfDeletedFiles, $count) = $testClass->dummyDeleteExpiredFiles($manipulatedList, $expireAt);

$this->assertSame(10, $sizeOfDeletedFiles);
$this->assertSame(2, $count);

// only file2.txt should be left
$remainingFiles = array_slice($manipulatedList, $count);
$this->assertSame(1, count($remainingFiles));
$remainingFile = reset($remainingFiles);
$this->assertSame('file2.txt', $remainingFile['name']);

// check that file1.txt and file3.txt was really deleted
$newTrashContent = OCA\Files_Trashbin\Helper::getTrashFiles('/', self::TEST_TRASHBIN_USER1);
$this->assertSame(1, count($newTrashContent));
$element = reset($newTrashContent);
$this->assertSame('file2.txt', $element['name']);
}

/**
* test expiration of files older then the max storage time defined for the trash
Expand Down Expand Up @@ -268,6 +226,52 @@ public function testExpireOldFilesShared() {
$this->verifyArray($filesInTrashUser1AfterDelete, array('user1-2.txt', 'user1-4.txt'));
}

/**
* test expiration of files older then the max storage time defined for the trash
*/
public function testExpireOldFiles() {

$currentTime = time();
$expireAt = $currentTime - 2 * 24 * 60 * 60;
$expiredDate = $currentTime - 3 * 24 * 60 * 60;

// create some files
\OC\Files\Filesystem::file_put_contents('file1.txt', 'file1');
\OC\Files\Filesystem::file_put_contents('file2.txt', 'file2');
\OC\Files\Filesystem::file_put_contents('file3.txt', 'file3');

// delete them so that they end up in the trash bin
\OC\Files\Filesystem::unlink('file1.txt');
\OC\Files\Filesystem::unlink('file2.txt');
\OC\Files\Filesystem::unlink('file3.txt');

//make sure that files are in the trash bin
$filesInTrash = OCA\Files_Trashbin\Helper::getTrashFiles('/', self::TEST_TRASHBIN_USER1, 'name');
$this->assertSame(3, count($filesInTrash));

// every second file will get a date in the past so that it will get expired
$manipulatedList = $this->manipulateDeleteTime($filesInTrash, $this->trashRoot1, $expiredDate);

$testClass = new TrashbinForTesting();
list($sizeOfDeletedFiles, $count) = $testClass->dummyDeleteExpiredFiles($manipulatedList, $expireAt);

$this->assertSame(10, $sizeOfDeletedFiles);
$this->assertSame(2, $count);

// only file2.txt should be left
$remainingFiles = array_slice($manipulatedList, $count);
$this->assertSame(1, count($remainingFiles));
$remainingFile = reset($remainingFiles);
$this->assertSame('file2.txt', $remainingFile['name']);

// check that file1.txt and file3.txt was really deleted
$newTrashContent = OCA\Files_Trashbin\Helper::getTrashFiles('/', self::TEST_TRASHBIN_USER1);
$this->assertSame(1, count($newTrashContent));
$element = reset($newTrashContent);
$this->assertSame('file2.txt', $element['name']);
}


/**
* verify that the array contains the expected results
*
Expand Down
101 changes: 51 additions & 50 deletions apps/files_versions/tests/VersioningTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,57 @@ protected function tearDown() {
parent::tearDown();
}


public function testMoveFileIntoSharedFolderAsRecipient() {

\OC\Files\Filesystem::mkdir('folder1');
$fileInfo = \OC\Files\Filesystem::getFileInfo('folder1');

$node = \OC::$server->getUserFolder(self::TEST_VERSIONS_USER)->get('folder1');
$share = \OC::$server->getShareManager()->newShare();
$share->setNode($node)
->setShareType(\OCP\Share::SHARE_TYPE_USER)
->setSharedBy(self::TEST_VERSIONS_USER)
->setSharedWith(self::TEST_VERSIONS_USER2)
->setPermissions(\OCP\Constants::PERMISSION_ALL);
$share = \OC::$server->getShareManager()->createShare($share);

self::loginHelper(self::TEST_VERSIONS_USER2);
$versionsFolder2 = '/' . self::TEST_VERSIONS_USER2 . '/files_versions';
\OC\Files\Filesystem::file_put_contents('test.txt', 'test file');

$t1 = time();
// second version is two weeks older, this way we make sure that no
// version will be expired
$t2 = $t1 - 60 * 60 * 24 * 14;

$this->rootView->mkdir($versionsFolder2);
// create some versions
$v1 = $versionsFolder2 . '/test.txt.v' . $t1;
$v2 = $versionsFolder2 . '/test.txt.v' . $t2;

$this->rootView->file_put_contents($v1, 'version1');
$this->rootView->file_put_contents($v2, 'version2');

// move file into the shared folder as recipient
\OC\Files\Filesystem::rename('/test.txt', '/folder1/test.txt');

$this->assertFalse($this->rootView->file_exists($v1));
$this->assertFalse($this->rootView->file_exists($v2));

self::loginHelper(self::TEST_VERSIONS_USER);

$versionsFolder1 = '/' . self::TEST_VERSIONS_USER . '/files_versions';

$v1Renamed = $versionsFolder1 . '/folder1/test.txt.v' . $t1;
$v2Renamed = $versionsFolder1 . '/folder1/test.txt.v' . $t2;

$this->assertTrue($this->rootView->file_exists($v1Renamed));
$this->assertTrue($this->rootView->file_exists($v2Renamed));

\OC::$server->getShareManager()->deleteShare($share);
}

/**
* @medium
* test expire logic
Expand Down Expand Up @@ -380,56 +431,6 @@ public function testMoveFolder() {
}


public function testMoveFileIntoSharedFolderAsRecipient() {

\OC\Files\Filesystem::mkdir('folder1');
$fileInfo = \OC\Files\Filesystem::getFileInfo('folder1');

$node = \OC::$server->getUserFolder(self::TEST_VERSIONS_USER)->get('folder1');
$share = \OC::$server->getShareManager()->newShare();
$share->setNode($node)
->setShareType(\OCP\Share::SHARE_TYPE_USER)
->setSharedBy(self::TEST_VERSIONS_USER)
->setSharedWith(self::TEST_VERSIONS_USER2)
->setPermissions(\OCP\Constants::PERMISSION_ALL);
$share = \OC::$server->getShareManager()->createShare($share);

self::loginHelper(self::TEST_VERSIONS_USER2);
$versionsFolder2 = '/' . self::TEST_VERSIONS_USER2 . '/files_versions';
\OC\Files\Filesystem::file_put_contents('test.txt', 'test file');

$t1 = time();
// second version is two weeks older, this way we make sure that no
// version will be expired
$t2 = $t1 - 60 * 60 * 24 * 14;

$this->rootView->mkdir($versionsFolder2);
// create some versions
$v1 = $versionsFolder2 . '/test.txt.v' . $t1;
$v2 = $versionsFolder2 . '/test.txt.v' . $t2;

$this->rootView->file_put_contents($v1, 'version1');
$this->rootView->file_put_contents($v2, 'version2');

// move file into the shared folder as recipient
\OC\Files\Filesystem::rename('/test.txt', '/folder1/test.txt');

$this->assertFalse($this->rootView->file_exists($v1));
$this->assertFalse($this->rootView->file_exists($v2));

self::loginHelper(self::TEST_VERSIONS_USER);

$versionsFolder1 = '/' . self::TEST_VERSIONS_USER . '/files_versions';

$v1Renamed = $versionsFolder1 . '/folder1/test.txt.v' . $t1;
$v2Renamed = $versionsFolder1 . '/folder1/test.txt.v' . $t2;

$this->assertTrue($this->rootView->file_exists($v1Renamed));
$this->assertTrue($this->rootView->file_exists($v2Renamed));

\OC::$server->getShareManager()->deleteShare($share);
}

public function testMoveFolderIntoSharedFolderAsRecipient() {

\OC\Files\Filesystem::mkdir('folder1');
Expand Down
Loading