From f756efe66ebcd338738d33c41fe313bd9af79b2a Mon Sep 17 00:00:00 2001 From: Josh Date: Sat, 28 Feb 2026 10:48:13 -0500 Subject: [PATCH 1/2] refactor(Storage/S3): improve headObject normalization and caching logic - Refactor headObject() to use early-return on cache hit for readability - Reduce code nesting - Normalize S3 object metadata to include 'Key' when caching (once) versus when reading (always) - Add explicit structured return type in docblock No observable change in behavior; improves code clarity and consistently ensures normalized cache entries. Signed-off-by: Josh --- .../lib/Lib/Storage/AmazonS3.php | 41 +++++++++++-------- 1 file changed, 25 insertions(+), 16 deletions(-) diff --git a/apps/files_external/lib/Lib/Storage/AmazonS3.php b/apps/files_external/lib/Lib/Storage/AmazonS3.php index 4da56c9527d9b..11d70b045c4f2 100644 --- a/apps/files_external/lib/Lib/Storage/AmazonS3.php +++ b/apps/files_external/lib/Lib/Storage/AmazonS3.php @@ -110,26 +110,35 @@ private function invalidateCache(string $key): void { unset($this->directoryCache[$key]); } + /** + * @return array{Key:string, LastModified?:string, ContentLength?:int, ETag?:string, Size?:int}|false + * @throws S3Exception For server errors and unexpected client errors. + */ private function headObject(string $key): array|false { - if (!isset($this->objectCache[$key])) { - try { - $this->objectCache[$key] = $this->getConnection()->headObject([ - 'Bucket' => $this->bucket, - 'Key' => $key - ] + $this->getSSECParameters())->toArray(); - } catch (S3Exception $e) { - if ($e->getStatusCode() >= 500) { - throw $e; - } - $this->objectCache[$key] = false; - } + if (isset($this->objectCache[$key])) { + return $this->objectCache[$key]; } - if (is_array($this->objectCache[$key]) && !isset($this->objectCache[$key]['Key'])) { - /** @psalm-suppress InvalidArgument Psalm doesn't understand nested arrays well */ - $this->objectCache[$key]['Key'] = $key; + try { + $result = $this->getConnection()->headObject([ + 'Bucket' => $this->bucket, + 'Key' => $key + ] + $this->getSSECParameters())->toArray(); + + // Defensive shape normalization + if (!isset($result['Key'])) { + $result['Key'] = $key; + } + + $this->objectCache[$key] = $result; + return $result; + } catch (S3Exception $e) { + if ($e->getStatusCode() >= 500) { + throw $e; + } + $this->objectCache[$key] = false; + return false; } - return $this->objectCache[$key]; } /** From cde438a74edca5f0ddc293f3733ac7ca77871020 Mon Sep 17 00:00:00 2001 From: Josh Date: Sat, 28 Feb 2026 11:09:59 -0500 Subject: [PATCH 2/2] fix(Storage/S3): no longer swallow non-notfound 4xx scenarios MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Classify explicit “missing object” conditions only (404, maybe NoSuchKey, NotFound) as negative cache. - No longer swallow non-notfound 4xx (i.e. <500) by caching as false. Instead throw upstream (to expose authentication problems, etc.) Signed-off-by: Josh --- apps/files_external/lib/Lib/Storage/AmazonS3.php | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/apps/files_external/lib/Lib/Storage/AmazonS3.php b/apps/files_external/lib/Lib/Storage/AmazonS3.php index 11d70b045c4f2..a2415eb6ff167 100644 --- a/apps/files_external/lib/Lib/Storage/AmazonS3.php +++ b/apps/files_external/lib/Lib/Storage/AmazonS3.php @@ -133,11 +133,18 @@ private function headObject(string $key): array|false { $this->objectCache[$key] = $result; return $result; } catch (S3Exception $e) { - if ($e->getStatusCode() >= 500) { - throw $e; + $status = (int)$e->getStatusCode(); + $awsCode = (string)$e->getAwsErrorCode(); + + $isNotFound = $status === 404 || $awsCode === 'NoSuchKey' || $awsCode === 'NotFound'; + + if ($isNotFound) { + $this->objectCache[$key] = false; + return false; } - $this->objectCache[$key] = false; - return false; + + // Fallback: i.e. surface an unexpected 4xx/5xx + throw $e; } }