Compare commits

...

5 Commits

Author SHA1 Message Date
Josh 570deb4130 chore(Storage/S3): final tidying
Signed-off-by: Josh <josh.t.richards@gmail.com>
2026-02-27 23:36:17 -05:00
Josh 407ce54e10 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 <josh.t.richards@gmail.com>
2026-02-27 23:32:38 -05:00
Josh 4cbf8dab5e fix(Storage/S3): empty prefix guard for invalidCache()
Signed-off-by: Josh <josh.t.richards@gmail.com>
2026-02-27 23:20:38 -05:00
Josh 474097d013 refactor(Storage/S3): move needsPartFile()
Signed-off-by: Josh <josh.t.richards@gmail.com>
2026-02-27 23:11:51 -05:00
Josh 80f192bb05 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 <josh.t.richards@gmail.com>
2026-02-27 23:09:57 -05:00
@@ -1,7 +1,7 @@
<?php
/**
* SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
* SPDX-FileCopyrightText: 2016-2026 Nextcloud GmbH and Nextcloud contributors
* SPDX-FileCopyrightText: 2016 ownCloud, Inc.
* SPDX-License-Identifier: AGPL-3.0-only
*/
@@ -32,10 +32,6 @@ class AmazonS3 extends Common {
private LoggerInterface $logger;
public function needsPartFile(): bool {
return false;
}
/** @var CappedMemoryCache<array|false> */
private CappedMemoryCache $objectCache;
@@ -77,6 +73,10 @@ class AmazonS3 extends Common {
return $path === '.';
}
public function needsPartFile(): bool {
return false;
}
private function cleanKey(string $path): string {
if ($this->isRoot($path)) {
return '/';
@@ -90,24 +90,28 @@ class AmazonS3 extends Common {
$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 {
if ($prefix === '') {
return; // or throw InvalidArgumentException?
}
$this->invalidateByPrefix($this->objectCache, $prefix);
$this->invalidateByPrefix($this->directoryCache, $prefix);
// FILES: exact match keys only (not hierarchical)
unset($this->filesCache[$prefix]);
}
private function invalidateByPrefix(CappedMemoryCache $cache, string $prefix): void {
$keys = array_keys($cache->getData());
$descendantPrefix = rtrim($prefix, '/') . '/';
foreach ($keys as $existingKey) {
if (substr($existingKey, 0, $keyLength) === $key) {
unset($this->objectCache[$existingKey]);
// exact + normalized prefix matched keys
if ($existingKey === $prefix || str_starts_with($existingKey, $descendantPrefix)) {
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 {