diff --git a/apps/files_external/lib/Lib/Storage/AmazonS3.php b/apps/files_external/lib/Lib/Storage/AmazonS3.php index 4da56c9527d9b..a2415eb6ff167 100644 --- a/apps/files_external/lib/Lib/Storage/AmazonS3.php +++ b/apps/files_external/lib/Lib/Storage/AmazonS3.php @@ -110,26 +110,42 @@ 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; - } + if (isset($this->objectCache[$key])) { + return $this->objectCache[$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) { + $status = (int)$e->getStatusCode(); + $awsCode = (string)$e->getAwsErrorCode(); + + $isNotFound = $status === 404 || $awsCode === 'NoSuchKey' || $awsCode === 'NotFound'; + + if ($isNotFound) { $this->objectCache[$key] = false; + return false; } - } - 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; + // Fallback: i.e. surface an unexpected 4xx/5xx + throw $e; } - return $this->objectCache[$key]; } /**