From 256ebb26da19cd92981fd4e70fc04dde8f14b663 Mon Sep 17 00:00:00 2001 From: Kent Delante Date: Thu, 16 Jul 2026 16:38:24 +0800 Subject: [PATCH 1/2] fix(files): preserve encryptedVersion when copying cache entries Cache::copyFromCache() rebuilt the target's cache row via cacheEntryToArray(), which only carried over a boolean `encrypted` flag and dropped the real `encryptedVersion` count. Since View::copy() unconditionally calls copyFromCache() after the storage copy completes, this silently overwrote the correct encryptedVersion that Encryption::updateEncryptedVersion() had just set, collapsing it back to 0/1 on every copy of a file whose encryptedVersion was above 1. cacheEntryToArray() now includes the source entry's encryptedVersion when it is encrypted. The existing encrypted-to-non-encrypted-storage override in copyFromCache() also clears encryptedVersion alongside `encrypted`, since normalizeData() prefers encryptedVersion over `encrypted` when both are present in the update data. Signed-off-by: Kent Delante Assisted-by: ClaudeCode:claude-sonnet-5 --- lib/private/Files/Cache/Cache.php | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/lib/private/Files/Cache/Cache.php b/lib/private/Files/Cache/Cache.php index 7a811631a9a77..3d6442ea473fa 100644 --- a/lib/private/Files/Cache/Cache.php +++ b/lib/private/Files/Cache/Cache.php @@ -1210,6 +1210,9 @@ public function copyFromCache(ICache $sourceCache, ICacheEntry $sourceEntry, str && $sourceCache->hasEncryptionWrapper() && !$this->shouldEncrypt($targetPath)) { $data['encrypted'] = 0; + // normalizeData() prefers 'encryptedVersion' over 'encrypted' when both are + // set, so it has to be cleared too or the mark above gets ignored + unset($data['encryptedVersion']); } $fileId = $this->put($targetPath, $data); @@ -1243,6 +1246,11 @@ private function cacheEntryToArray(ICacheEntry $entry): array { if ($entry instanceof CacheEntry && isset($entry['scan_permissions'])) { $data['permissions'] = $entry['scan_permissions']; } + + if ($entry->isEncrypted() && isset($entry['encryptedVersion'])) { + $data['encryptedVersion'] = $entry['encryptedVersion']; + } + return $data; } From bc07e02e6ac7b50656f1480bf38878ae4032d37c Mon Sep 17 00:00:00 2001 From: Kent Delante Date: Thu, 16 Jul 2026 16:54:47 +0800 Subject: [PATCH 2/2] test: add tests for encryptedVersion cache entry preservation Signed-off-by: Kent Delante Assisted-by: ClaudeCode:claude-sonnet-5 --- tests/lib/Files/Cache/CacheTest.php | 43 +++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/tests/lib/Files/Cache/CacheTest.php b/tests/lib/Files/Cache/CacheTest.php index e07bca0c5c669..a240e0cfb00b8 100644 --- a/tests/lib/Files/Cache/CacheTest.php +++ b/tests/lib/Files/Cache/CacheTest.php @@ -540,6 +540,49 @@ public function testMoveFromCacheJail(): void { $this->assertEquals($this->cache->getId(''), $this->cache->get('targetsub')->getParentId()); } + public function testCopyFromCachePreservesEncryptedVersion(): void { + $data = [ + 'size' => 100, 'mtime' => 50, 'mimetype' => 'foo/bar', + 'encrypted' => true, 'encryptedVersion' => 3, + ]; + $this->cache->put('source', $data); + $sourceEntry = $this->cache->get('source'); + $this->assertSame(3, $sourceEntry['encryptedVersion']); + + $this->cache->copyFromCache($this->cache, $sourceEntry, 'target'); + + $targetEntry = $this->cache->get('target'); + $this->assertTrue($targetEntry->isEncrypted()); + $this->assertSame(3, $targetEntry['encryptedVersion']); + } + + public function testCopyFromCacheClearsEncryptedVersionWhenCopyingToNonEncryptedStorage(): void { + $data = [ + 'size' => 100, 'mtime' => 50, 'mimetype' => 'foo/bar', + 'encrypted' => true, 'encryptedVersion' => 3, + ]; + $this->cache2->put('source', $data); + $sourceEntry = $this->cache2->get('source'); + + $sourceCache = $this->getMockBuilder(Cache::class) + ->setConstructorArgs([$this->storage2]) + ->onlyMethods(['hasEncryptionWrapper']) + ->getMock(); + $sourceCache->method('hasEncryptionWrapper')->willReturn(true); + + $targetCache = $this->getMockBuilder(Cache::class) + ->setConstructorArgs([$this->storage]) + ->onlyMethods(['shouldEncrypt']) + ->getMock(); + $targetCache->method('shouldEncrypt')->willReturn(false); + + $targetCache->copyFromCache($sourceCache, $sourceEntry, 'target'); + + $targetEntry = $targetCache->get('target'); + $this->assertFalse($targetEntry->isEncrypted()); + $this->assertSame(0, $targetEntry['encryptedVersion']); + } + public function testGetIncomplete(): void { $file1 = 'folder1'; $file2 = 'folder2';