From 5d9808679bd221ee67dc0c8feb81793873127e84 Mon Sep 17 00:00:00 2001 From: Cristian Scheid Date: Fri, 23 Oct 2026 10:30:42 -0300 Subject: [PATCH] fix(files): skip quota check when opening files for reading Signed-off-by: Cristian Scheid --- lib/private/Files/Storage/Wrapper/Quota.php | 8 ++++++-- tests/lib/Files/Storage/Wrapper/QuotaTest.php | 17 +++++++++++++++++ 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/lib/private/Files/Storage/Wrapper/Quota.php b/lib/private/Files/Storage/Wrapper/Quota.php index 35a265f8c8e72..c9e59a754a5dc 100644 --- a/lib/private/Files/Storage/Wrapper/Quota.php +++ b/lib/private/Files/Storage/Wrapper/Quota.php @@ -116,7 +116,9 @@ public function copy(string $source, string $target): bool { } public function fopen(string $path, string $mode) { - if (!$this->hasQuota()) { + // reading doesn't consume quota, so it should not be restricted by it + $isReadMode = $mode === 'r' || $mode === 'rb'; + if ($isReadMode || !$this->hasQuota()) { return $this->storage->fopen($path, $mode); } $source = $this->storage->fopen($path, $mode); @@ -124,7 +126,9 @@ public function fopen(string $path, string $mode) { // don't apply quota for part files if (!$this->isPartFile($path)) { $free = $this->free_space($path); - if ($source && (is_int($free) || is_float($free)) && $free >= 0 && $mode !== 'r' && $mode !== 'rb') { + // writing past the remaining free space fails. Negative values mean the free + // space is unknown or unlimited (FileInfo::SPACE_*), so no limit is applied + if ($source && (is_int($free) || is_float($free)) && $free >= 0) { // only apply quota for files, not metadata, trash or others if ($this->shouldApplyQuota($path)) { return \OC\Files\Stream\Quota::wrap($source, $free); diff --git a/tests/lib/Files/Storage/Wrapper/QuotaTest.php b/tests/lib/Files/Storage/Wrapper/QuotaTest.php index 3d313666a93b0..ccc03affb776a 100644 --- a/tests/lib/Files/Storage/Wrapper/QuotaTest.php +++ b/tests/lib/Files/Storage/Wrapper/QuotaTest.php @@ -166,6 +166,23 @@ public function testReturnRegularStreamOnRead(): void { fclose($stream); } + public function testFopenReadWhenQuotaExceeded(): void { + $instance = $this->getLimitedStorage(3); + + // write foobar (6 bytes) past the quota wrapper and scan it, + // so the cached usage exceeds the 3 bytes quota + $instance->getWrapperStorage()->file_put_contents('files/foo', 'foobar'); + $instance->getWrapperStorage()->getScanner()->scan(''); + $this->assertEquals(0, $instance->free_space('files')); + + foreach (['r', 'rb'] as $mode) { + $stream = $instance->fopen('files/foo', $mode); + $this->assertIsResource($stream); + $this->assertEquals('foobar', stream_get_contents($stream)); + fclose($stream); + } + } + public function testReturnRegularStreamWhenOutsideFiles(): void { $instance = $this->getLimitedStorage(9); $instance->mkdir('files_other');