From 80f192bb05f6a8d25b7b154a6375c338d2e98611 Mon Sep 17 00:00:00 2001 From: Josh Date: Fri, 27 Feb 2026 23:09:57 -0500 Subject: [PATCH 1/5] refactor(Storage/S3): reduce duplicate code in invalidateCache() - reuse common logic via new helper - remove unnecessary duplicate exact key removal (covered by prefix matching logic) - use str_starts_with over substr - improve overall clarity (variable naming, consistent ordering of logic) Signed-off-by: Josh --- .../lib/Lib/Storage/AmazonS3.php | 33 ++++++++++--------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/apps/files_external/lib/Lib/Storage/AmazonS3.php b/apps/files_external/lib/Lib/Storage/AmazonS3.php index 4da56c9527d9b..cc2b88a4d2214 100644 --- a/apps/files_external/lib/Lib/Storage/AmazonS3.php +++ b/apps/files_external/lib/Lib/Storage/AmazonS3.php @@ -90,24 +90,27 @@ private function clearCache(): void { $this->filesCache = new CappedMemoryCache(); } - private function invalidateCache(string $key): void { - unset($this->objectCache[$key]); - $keys = array_keys($this->objectCache->getData()); - $keyLength = strlen($key); + private function invalidateCache(string $prefix): void { + // OBJECTS: exact + prefix matched keys + $this->invalidateByPrefix($this->objectCache, $prefix); + // DIRECTORIES: exact + prefix matched keys + $this->invalidateByPrefix($this->directoryCache, $prefix); + // FILES: exact match keys only (not hierarchically) + unset($this->filesCache[$prefix]); + + // Concerns: + // - lack of prefix / keying convention normalization + // - add guard if empty ('') + } + + private function invalidateByPrefix($cache, string $prefix): void { + $keys = array_keys($cache->getData); + // drops any exact or prefix matched keys found foreach ($keys as $existingKey) { - if (substr($existingKey, 0, $keyLength) === $key) { - unset($this->objectCache[$existingKey]); + if (str_starts_with($existingKey, $prefix)) { + unset($cache[$existingKey]); } } - unset($this->filesCache[$key]); - $keys = array_keys($this->directoryCache->getData()); - $keyLength = strlen($key); - foreach ($keys as $existingKey) { - if (substr($existingKey, 0, $keyLength) === $key) { - unset($this->directoryCache[$existingKey]); - } - } - unset($this->directoryCache[$key]); } private function headObject(string $key): array|false { From 474097d013bc1e703999f508de8f15225e93e308 Mon Sep 17 00:00:00 2001 From: Josh Date: Fri, 27 Feb 2026 23:11:51 -0500 Subject: [PATCH 2/5] refactor(Storage/S3): move needsPartFile() Signed-off-by: Josh --- apps/files_external/lib/Lib/Storage/AmazonS3.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/apps/files_external/lib/Lib/Storage/AmazonS3.php b/apps/files_external/lib/Lib/Storage/AmazonS3.php index cc2b88a4d2214..1fc6a77612179 100644 --- a/apps/files_external/lib/Lib/Storage/AmazonS3.php +++ b/apps/files_external/lib/Lib/Storage/AmazonS3.php @@ -1,7 +1,7 @@ */ private CappedMemoryCache $objectCache; @@ -77,6 +73,10 @@ private function isRoot(string $path): bool { return $path === '.'; } + public function needsPartFile(): bool { + return false; + } + private function cleanKey(string $path): string { if ($this->isRoot($path)) { return '/'; From 4cbf8dab5e0b5e4af498432c1ce4174926e64468 Mon Sep 17 00:00:00 2001 From: Josh Date: Fri, 27 Feb 2026 23:20:38 -0500 Subject: [PATCH 3/5] fix(Storage/S3): empty prefix guard for invalidCache() Signed-off-by: Josh --- apps/files_external/lib/Lib/Storage/AmazonS3.php | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/apps/files_external/lib/Lib/Storage/AmazonS3.php b/apps/files_external/lib/Lib/Storage/AmazonS3.php index 1fc6a77612179..68eaa6d5ccaf1 100644 --- a/apps/files_external/lib/Lib/Storage/AmazonS3.php +++ b/apps/files_external/lib/Lib/Storage/AmazonS3.php @@ -91,10 +91,13 @@ private function clearCache(): void { } private function invalidateCache(string $prefix): void { - // OBJECTS: exact + prefix matched keys + if ($prefix === '') { + return; // or throw InvalidArgumentException? + } + $this->invalidateByPrefix($this->objectCache, $prefix); - // DIRECTORIES: exact + prefix matched keys $this->invalidateByPrefix($this->directoryCache, $prefix); + // FILES: exact match keys only (not hierarchically) unset($this->filesCache[$prefix]); From 407ce54e10f5cac3fb8c67b07832ac86021ecafc Mon Sep 17 00:00:00 2001 From: Josh Date: Fri, 27 Feb 2026 23:32:38 -0500 Subject: [PATCH 4/5] fix(Storage/S3): path normalization + boundary-aware matching Always checks keys for both exact match and a normalized prefix match: - fixes over-broad prefix matching (e.g. `foo` would match `foobar`) - normalizes hierarchical keys consistently with rest of class Signed-off-by: Josh --- apps/files_external/lib/Lib/Storage/AmazonS3.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/apps/files_external/lib/Lib/Storage/AmazonS3.php b/apps/files_external/lib/Lib/Storage/AmazonS3.php index 68eaa6d5ccaf1..ada5686ea7962 100644 --- a/apps/files_external/lib/Lib/Storage/AmazonS3.php +++ b/apps/files_external/lib/Lib/Storage/AmazonS3.php @@ -108,9 +108,11 @@ private function invalidateCache(string $prefix): void { private function invalidateByPrefix($cache, string $prefix): void { $keys = array_keys($cache->getData); - // drops any exact or prefix matched keys found + $descendantPrefix = rtrim($prefix, '/') . '/'; + foreach ($keys as $existingKey) { - if (str_starts_with($existingKey, $prefix)) { + // drop any exact + prefix matched keys + if ($existingKey === $prefix || str_starts_with($existingKey, $descendantPrefix)) { unset($cache[$existingKey]); } } From 570deb41300c5beb031ee70287487d39b3b495eb Mon Sep 17 00:00:00 2001 From: Josh Date: Fri, 27 Feb 2026 23:36:17 -0500 Subject: [PATCH 5/5] chore(Storage/S3): final tidying Signed-off-by: Josh --- apps/files_external/lib/Lib/Storage/AmazonS3.php | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/apps/files_external/lib/Lib/Storage/AmazonS3.php b/apps/files_external/lib/Lib/Storage/AmazonS3.php index ada5686ea7962..22a9b28717f4d 100644 --- a/apps/files_external/lib/Lib/Storage/AmazonS3.php +++ b/apps/files_external/lib/Lib/Storage/AmazonS3.php @@ -98,20 +98,16 @@ private function invalidateCache(string $prefix): void { $this->invalidateByPrefix($this->objectCache, $prefix); $this->invalidateByPrefix($this->directoryCache, $prefix); - // FILES: exact match keys only (not hierarchically) + // FILES: exact match keys only (not hierarchical) unset($this->filesCache[$prefix]); - - // Concerns: - // - lack of prefix / keying convention normalization - // - add guard if empty ('') } - private function invalidateByPrefix($cache, string $prefix): void { - $keys = array_keys($cache->getData); + private function invalidateByPrefix(CappedMemoryCache $cache, string $prefix): void { + $keys = array_keys($cache->getData()); $descendantPrefix = rtrim($prefix, '/') . '/'; foreach ($keys as $existingKey) { - // drop any exact + prefix matched keys + // exact + normalized prefix matched keys if ($existingKey === $prefix || str_starts_with($existingKey, $descendantPrefix)) { unset($cache[$existingKey]); }