Skip to content
Draft
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
69 changes: 69 additions & 0 deletions apps/encryption/tests/CopySizeTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
<?php

declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

namespace OCA\encryption\tests;

use OC\Files\Storage\Temporary;
use OC\Files\View;
use OCA\Encryption\KeyManager;
use OCP\Server;
use Test\TestCase;
use Test\Traits\EncryptionTrait;
use Test\Traits\MountProviderTrait;
use Test\Traits\UserTrait;

/**
* The size of an encrypted file is kept in `unencrypted_size`, which every reader
* prefers over `size`. A copy has to carry it over, otherwise the copy is reported
* as empty.
*/
#[\PHPUnit\Framework\Attributes\Group(name: 'DB')]
class CopySizeTest extends TestCase {
use MountProviderTrait;
use EncryptionTrait;
use UserTrait;

private function setUpView(): View {
Server::get(KeyManager::class)->validateMasterKey();
Server::get(KeyManager::class)->validateShareKey();
$this->createUser('test1', 'test2');
$this->setupForUser('test1', 'test2');
$this->registerMount('test1', new Temporary(), '/test1/files/other');
$this->loginWithEncryption('test1');

return new View('/test1/files');
}

public function testCopyKeepsTheSize(): void {
$view = $this->setUpView();

$view->file_put_contents('source.bin', str_repeat('a', 20000));
$this->assertTrue($view->copy('source.bin', 'target.bin'));

$this->assertEquals(20000, $view->getFileInfo('target.bin')->getSize());
}

public function testCopyToAnotherStorageKeepsTheSize(): void {
$view = $this->setUpView();

$view->file_put_contents('source.bin', str_repeat('a', 20000));
$this->assertTrue($view->copy('source.bin', 'other/target.bin'));

$this->assertEquals(20000, $view->getFileInfo('other/target.bin')->getSize());
}

public function testCopyOfAFolderKeepsTheSizes(): void {
$view = $this->setUpView();

$view->mkdir('source');
$view->file_put_contents('source/file.bin', str_repeat('a', 20000));
$this->assertTrue($view->copy('source', 'target'));

$this->assertEquals(20000, $view->getFileInfo('target/file.bin')->getSize());
}
}
1 change: 1 addition & 0 deletions build/integration/encryption_features/encryption.feature
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ Feature: encryption
And User "user0" adds a file of 20000 bytes to "/big.bin"
When User "user0" copies file "/big.bin" to "/copy.bin"
Then the HTTP status code should be "201"
And File "/copy.bin" should have prop "d:getcontentlength" equal to "20000"
When Downloading file "/copy.bin"
Then the HTTP status code should be "200"

Expand Down
10 changes: 8 additions & 2 deletions lib/private/Files/Cache/Cache.php
Original file line number Diff line number Diff line change
Expand Up @@ -1316,8 +1316,14 @@ private function cacheEntryToArray(ICacheEntry $entry): array {
$data['permissions'] = $entry['scan_permissions'];
}

if ($entry->isEncrypted() && isset($entry['encryptedVersion'])) {
$data['encryptedVersion'] = $entry['encryptedVersion'];
if ($entry->isEncrypted()) {
// the size of an encrypted file is stored in its own column, which every
// reader prefers over `size`, so the copy is reported as empty without it
$data['unencrypted_size'] = $entry->getUnencryptedSize();

if (isset($entry['encryptedVersion'])) {
$data['encryptedVersion'] = $entry['encryptedVersion'];
}
}

return $data;
Expand Down
22 changes: 20 additions & 2 deletions tests/lib/Files/Cache/CacheTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -602,10 +602,25 @@ public function testCopyFromCachePreservesEncryptedVersion(): void {
$this->assertSame(3, $targetEntry['encryptedVersion']);
}

public function testCopyFromCachePreservesUnencryptedSize(): void {
$data = [
'size' => 128, 'mtime' => 50, 'mimetype' => 'foo/bar',
'encrypted' => true, 'encryptedVersion' => 3, 'unencrypted_size' => 100,
];
$this->cache->put('source', $data);
$sourceEntry = $this->cache->get('source');
$this->assertEquals(100, $sourceEntry->getUnencryptedSize());

$this->cache->copyFromCache($this->cache, $sourceEntry, 'target');

$targetEntry = $this->cache->get('target');
$this->assertEquals(100, $targetEntry->getUnencryptedSize());
}

public function testCopyFromCacheClearsEncryptedVersionWhenCopyingToNonEncryptedStorage(): void {
$data = [
'size' => 100, 'mtime' => 50, 'mimetype' => 'foo/bar',
'encrypted' => true, 'encryptedVersion' => 3,
'size' => 128, 'mtime' => 50, 'mimetype' => 'foo/bar',
'encrypted' => true, 'encryptedVersion' => 3, 'unencrypted_size' => 100,
];
$this->cache2->put('source', $data);
$sourceEntry = $this->cache2->get('source');
Expand All @@ -627,6 +642,9 @@ public function testCopyFromCacheClearsEncryptedVersionWhenCopyingToNonEncrypted
$targetEntry = $targetCache->get('target');
$this->assertFalse($targetEntry->isEncrypted());
$this->assertSame(0, $targetEntry['encryptedVersion']);
// the target is not marked as encrypted, so its size is read from `size`
$this->assertEquals(0, $targetEntry['unencrypted_size']);
$this->assertEquals(128, $targetEntry->getSize());
}

public function testGetIncomplete(): void {
Expand Down