diff --git a/_build/test/Tests/Model/Sources/MediaSourceGetObjectContentsTest.php b/_build/test/Tests/Model/Sources/MediaSourceGetObjectContentsTest.php new file mode 100644 index 00000000000..82dff2cc932 --- /dev/null +++ b/_build/test/Tests/Model/Sources/MediaSourceGetObjectContentsTest.php @@ -0,0 +1,64 @@ +modx->newObject(modFileMediaSource::class); + $source->fromArray([ + 'name' => 'UnitTestGetObjectContents', + 'class_key' => modFileMediaSource::class, + 'properties' => [], + ], '', true); + $this->assertTrue((bool)$source->save()); + + $filesystem = $this->createMock(Filesystem::class); + $filesystem->method('fileExists')->willReturn(true); + $filesystem->method('read')->willReturn('payload-from-s3'); + $filesystem->method('fileSize')->willReturn(15); + $filesystem->method('lastModified')->willReturn(1700000000); + $filesystem->method('mimeType')->willThrowException( + UnableToRetrieveMetadata::mimeType('demo.jpg') + ); + + $prop = new ReflectionProperty(modFileMediaSource::class, 'filesystem'); + $prop->setAccessible(true); + $prop->setValue($source, $filesystem); + + try { + $result = $source->getObjectContents('demo.jpg'); + $this->assertNotEmpty($result); + $this->assertSame('payload-from-s3', $result['content']); + $this->assertSame('application/octet-stream', $result['mime']); + } finally { + $source->remove(); + } + } +} diff --git a/core/src/Revolution/Sources/modMediaSource.php b/core/src/Revolution/Sources/modMediaSource.php index 2d43cf04911..204bb35a8a9 100644 --- a/core/src/Revolution/Sources/modMediaSource.php +++ b/core/src/Revolution/Sources/modMediaSource.php @@ -592,23 +592,42 @@ public function getObjectContents($path) 'trim', explode(',', $this->getOption('imageExtensions', $properties, 'jpg,jpeg,png,gif,svg,webp')) ); + + /* + * Read content first; never discard it if metadata calls fail. + * S3 (and some other adapters) can throw UnableToRetrieveMetadata for + * mimeType/fileSize/lastModified even when the object body is readable (#16497). + */ + $size = false; + $lastModified = false; + $mime = 'application/octet-stream'; try { - $fa = [ - 'name' => rtrim($path, DIRECTORY_SEPARATOR), - 'basename' => basename($path), - 'path' => $path, - 'size' => $this->filesystem->fileSize($path), - 'last_accessed' => $this->filesystem->lastModified($path), // We only have lastModified() here. - 'last_modified' => $this->filesystem->lastModified($path), - 'content' => $content, - 'mime' => $this->filesystem->mimeType($path), - 'image' => $this->isFileImage($path, $imageExtensions), - ]; + $size = $this->filesystem->fileSize($path); } catch (FilesystemException | UnableToRetrieveMetadata $e) { - $this->addError('file', $this->xpdo->lexicon('file_err_nf')); - $this->xpdo->log(modX::LOG_LEVEL_ERROR, $e->getMessage()); - return []; + $this->xpdo->log(modX::LOG_LEVEL_INFO, $e->getMessage()); + } + try { + $lastModified = $this->filesystem->lastModified($path); + } catch (FilesystemException | UnableToRetrieveMetadata $e) { + $this->xpdo->log(modX::LOG_LEVEL_INFO, $e->getMessage()); } + try { + $mime = $this->filesystem->mimeType($path); + } catch (FilesystemException | UnableToRetrieveMetadata $e) { + $this->xpdo->log(modX::LOG_LEVEL_INFO, $e->getMessage()); + } + + $fa = [ + 'name' => rtrim($path, DIRECTORY_SEPARATOR), + 'basename' => basename($path), + 'path' => $path, + 'size' => $size, + 'last_accessed' => $lastModified, + 'last_modified' => $lastModified, + 'content' => $content, + 'mime' => $mime, + 'image' => $this->isFileImage($path, $imageExtensions), + ]; $visibility = $this->visibility_files ? $this->getVisibility($path) : false; if ($visibility) { $fa['visibility'] = $visibility;