Skip to content
Open
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
4 changes: 2 additions & 2 deletions lib/private/Files/Cache/Cache.php
Original file line number Diff line number Diff line change
Expand Up @@ -1042,7 +1042,7 @@ protected function calculateFolderSizeInner(string $path, $entry = null, bool $i
$id = $entry['fileid'];

$query = $this->getQueryBuilder();
$query->select('size', 'unencrypted_size')
$query->select('size', 'unencrypted_size', 'encrypted')
->from('filecache')
->whereStorageId($this->getNumericStorageId())
->whereParent($id);
Expand All @@ -1062,7 +1062,7 @@ protected function calculateFolderSizeInner(string $path, $entry = null, bool $i
return Util::numericToNumber($row['unencrypted_size']);
}, $rows);
$unencryptedSizes = array_map(function (array $row) {
return Util::numericToNumber(($row['unencrypted_size'] > 0) ? $row['unencrypted_size'] : $row['size']);
return Util::numericToNumber($row['encrypted'] ? $row['unencrypted_size'] : $row['size']);
}, $rows);

$sum = array_sum($sizes);
Expand Down
2 changes: 1 addition & 1 deletion lib/private/Files/Cache/CacheEntry.php
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ public function __clone() {

#[\Override]
public function getUnencryptedSize(): int {
if ($this->data['encrypted'] && isset($this->data['unencrypted_size']) && $this->data['unencrypted_size'] > 0) {
if ($this->data['encrypted'] && isset($this->data['unencrypted_size'])) {
return $this->data['unencrypted_size'];
} else {
return $this->data['size'] ?? 0;
Expand Down
11 changes: 8 additions & 3 deletions lib/private/Files/Cache/Scanner.php
Original file line number Diff line number Diff line change
Expand Up @@ -184,8 +184,10 @@ public function scanFile($file, $reuseExisting = 0, $parentId = -1, $cacheData =
}
}

// we only updated unencrypted_size if it's already set
if (isset($cacheData['unencrypted_size']) && $cacheData['unencrypted_size'] === 0) {
// Skip updating unencrypted_size only when both cached and new values are 0
if (isset($cacheData['unencrypted_size'])
&& $cacheData['unencrypted_size'] === 0
&& isset($data['unencrypted_size']) && $data['unencrypted_size'] === 0) {
unset($data['unencrypted_size']);
}

Expand All @@ -203,7 +205,10 @@ public function scanFile($file, $reuseExisting = 0, $parentId = -1, $cacheData =
$data['etag_changed'] = true;
}
} else {
unset($data['unencrypted_size']);
// For new files, only preserve unencrypted_size when the file is encrypted
if (!isset($data['encrypted']) || !$data['encrypted']) {
unset($data['unencrypted_size']);
}
$newData = $data;
$fileId = -1;
}
Expand Down
6 changes: 3 additions & 3 deletions lib/private/Files/FileInfo.php
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ public function __construct(
private ?IUser $owner = null,
) {
$this->mount = $mount;
if (isset($this->data['unencrypted_size']) && $this->data['unencrypted_size'] !== 0) {
if (($this->data['encrypted'] ?? false) && isset($this->data['unencrypted_size'])) {
$this->rawSize = $this->data['unencrypted_size'];
} else {
$this->rawSize = $this->data['size'] ?? 0;
Expand Down Expand Up @@ -174,7 +174,7 @@ public function getSize($includeMounts = true) {
if ($includeMounts) {
$this->updateEntryFromSubMounts();

if ($this->isEncrypted() && isset($this->data['unencrypted_size']) && $this->data['unencrypted_size'] > 0) {
if ($this->isEncrypted() && isset($this->data['unencrypted_size'])) {
return $this->data['unencrypted_size'];
} else {
return isset($this->data['size']) ? 0 + $this->data['size'] : 0;
Expand Down Expand Up @@ -356,7 +356,7 @@ public function addSubEntry($data, $entryPath) {
if (!$data) {
return;
}
$hasUnencryptedSize = isset($data['unencrypted_size']) && $data['unencrypted_size'] > 0;
$hasUnencryptedSize = !empty($data['encrypted']) && isset($data['unencrypted_size']);
if ($hasUnencryptedSize) {
$subSize = $data['unencrypted_size'];
} else {
Expand Down
1 change: 1 addition & 0 deletions lib/private/Files/Storage/Wrapper/Encryption.php
Original file line number Diff line number Diff line change
Expand Up @@ -399,6 +399,7 @@ protected function verifyUnencryptedSize(string $path, int $unencryptedSize): in
if ($unencryptedSize < 0
|| ($size > 0 && $unencryptedSize === $size)
|| $unencryptedSize > $size
|| ($unencryptedSize === 0 && $size > $this->util->getHeaderSize())
) {
// check if we already calculate the unencrypted size for the
// given path to avoid recursions
Expand Down
2 changes: 1 addition & 1 deletion lib/private/Files/Stream/Encryption.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ class Encryption extends Wrapper {
protected string $cache;
protected ?int $size = null;
protected int $position;
protected ?int $unencryptedSize = null;
protected int|float|null $unencryptedSize = null;
protected int $headerSize;
protected int $unencryptedBlockSize;
protected array $header;
Expand Down
43 changes: 43 additions & 0 deletions tests/lib/Files/Cache/CacheTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,16 @@ public function testCacheEntryGetters(): void {
$this->assertEquals($entry->getUnencryptedSize(), 100);
}

public function testGetUnencryptedSizeEncryptedZeroByte(): void {
$file1 = 'encrypted_zero';
$this->cache->put($file1, ['size' => 8192, 'mtime' => 50, 'mimetype' => 'application/octet-stream', 'encrypted' => 1, 'unencrypted_size' => 0]);
$entry = $this->cache->get($file1);

// getUnencryptedSize() must return 0 (the true plaintext size), not 8192 (the encrypted on-disk size)
$this->assertEquals(0, $entry->getUnencryptedSize());
$this->assertTrue($entry->isEncrypted());
}

public function testPartial(): void {
$file1 = 'foo';

Expand Down Expand Up @@ -292,6 +302,39 @@ public function testEncryptedFolder(): void {
$this->assertFalse($this->cache->inCache('folder/bar'));
}

public function testCalculateFolderSizeWithEncryptedZeroByte(): void {
$folder = 'enc_folder';
$this->cache->put($folder, ['size' => -1, 'mtime' => 20, 'mimetype' => ICacheEntry::DIRECTORY_MIMETYPE]);

// Child 1: zero-byte encrypted file — on-disk 8192 (header only), plaintext 0
$child1 = $folder . '/empty.enc';
$this->cache->put($child1, [
'size' => 8192,
'mtime' => 20,
'mimetype' => 'application/octet-stream',
'encrypted' => 1,
'unencrypted_size' => 0,
]);

// Child 2: non-zero encrypted file — opens the write-back gate ($unencryptedMax > 0)
$child2 = $folder . '/small.enc';
$this->cache->put($child2, [
'size' => 8292,
'mtime' => 20,
'mimetype' => 'application/octet-stream',
'encrypted' => 1,
'unencrypted_size' => 100,
]);

$this->cache->calculateFolderSize($folder);

$entry = $this->cache->get($folder);
// Must sum plaintext sizes (0 + 100 = 100), not fall back to on-disk size for
// the zero-byte child (8192 + 100 = 8292 with the old buggy code)
$this->assertEquals(100, $entry['unencrypted_size'], 'Folder unencrypted_size should sum plaintext sizes');
$this->assertEquals(16484, $entry['size'], 'Folder size should sum on-disk sizes (8192 + 8292)');
}

public function testRootFolderSizeForNonHomeStorage(): void {
$dir1 = 'knownsize';
$dir2 = 'unknownsize';
Expand Down
24 changes: 24 additions & 0 deletions tests/lib/Files/FileInfoTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,30 @@ protected function setUp(): void {
$this->config = $this->getMockBuilder(IConfig::class)->getMock();
}

private function makeFileInfo(array $data): FileInfo {
$storage = new Temporary();
return new FileInfo('', $storage, '', $data, new MountPoint($storage, '/foo/files'));
}

public function testGetSizeEncryptedZeroByte(): void {
$info = $this->makeFileInfo(['encrypted' => true, 'size' => 8192, 'unencrypted_size' => 0]);
// Both paths must report the true plaintext size (0), not the on-disk encrypted size (8192)
$this->assertSame(0, $info->getSize(true));
$this->assertSame(0, $info->getSize(false));
}

public function testGetSizeEncryptedNonZero(): void {
$info = $this->makeFileInfo(['encrypted' => true, 'size' => 16384, 'unencrypted_size' => 5000]);
$this->assertSame(5000, $info->getSize(true));
$this->assertSame(5000, $info->getSize(false));
}

public function testGetSizeNonEncrypted(): void {
$info = $this->makeFileInfo(['encrypted' => false, 'size' => 100]);
$this->assertSame(100, $info->getSize(true));
$this->assertSame(100, $info->getSize(false));
}

public function testIsMountedHomeStorage(): void {
$user = $this->createMock(IUser::class);
$user->method('getUID')
Expand Down
6 changes: 5 additions & 1 deletion tests/lib/Files/Storage/Wrapper/EncryptionTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -386,7 +386,11 @@ public static function dataTestVerifyUnencryptedSize(): array {
[120, 80, false, 80],
[120, 120, false, 80],
[120, -1, false, 80],
[120, -1, true, -1]
[120, -1, true, -1],
// Zero-byte encrypted file: on-disk size equals header only (8192) — should NOT recalculate
[8192, 0, false, 0],
// Encrypted file with content beyond header but unencrypted_size=0 — SHOULD recalculate
[16384, 0, false, 80],
];
}

Expand Down
Loading