From 3362f6b455e591d238cd9a05c691b81619bdbf46 Mon Sep 17 00:00:00 2001 From: Arman <407448+armanist@users.noreply.github.com> Date: Fri, 1 May 2026 20:14:31 +0400 Subject: [PATCH 1/2] [#492] Prevent Request bootstrap circular dependency via UploadedFile lazy init --- src/Storage/UploadedFile.php | 80 +++++++++++++++++++++++++----------- 1 file changed, 56 insertions(+), 24 deletions(-) diff --git a/src/Storage/UploadedFile.php b/src/Storage/UploadedFile.php index c56497af..869d3381 100644 --- a/src/Storage/UploadedFile.php +++ b/src/Storage/UploadedFile.php @@ -44,7 +44,7 @@ class UploadedFile extends SplFileInfo /** * Local File System */ - protected LocalFilesystemAdapterInterface $localFileSystem; + protected ?LocalFilesystemAdapterInterface $localFileSystem = null; /** * Remove File System @@ -113,28 +113,19 @@ class UploadedFile extends SplFileInfo */ protected int $errorCode; + /** + * Whether mime types were loaded from config + */ + protected bool $mimeTypesLoaded = false; + /** * @param array $meta - * @throws ConfigException|DiException|BaseException|ReflectionException */ public function __construct(array $meta) { - $adapter = fs()->getAdapter(); - - if (!$adapter instanceof LocalFilesystemAdapterInterface) { - throw FileSystemException::notInstanceOf( - get_class($adapter), - LocalFilesystemAdapterInterface::class - ); - } - - $this->localFileSystem = $adapter; - $this->originalName = $meta['name']; $this->errorCode = $meta['error']; - $this->loadAllowedMimeTypesFromConfig(); - parent::__construct($meta['tmp_name']); } @@ -154,7 +145,7 @@ public function setAllowedMimeTypes(array $allowedMimeTypes, bool $merge = true) public function getName(): string { if (!$this->name) { - $this->name = $this->localFileSystem->fileName($this->originalName ?? ''); + $this->name = $this->getLocalFileSystem()->fileName($this->originalName ?? ''); } return $this->name; @@ -192,7 +183,7 @@ public function getRemoteFileSystem(): ?FilesystemAdapterInterface public function getExtension(): string { if (!$this->extension) { - $this->extension = strtolower($this->localFileSystem->extension($this->originalName ?? '')); + $this->extension = strtolower($this->getLocalFileSystem()->extension($this->originalName ?? '')); } return $this->extension; @@ -268,15 +259,18 @@ public function getDimensions(): array * @param string $dest * @param bool $overwrite * @return bool - * @throws FileUploadException|FileSystemException|ImageResizeException|BaseException + * @throws FileUploadException|FileSystemException|ImageResizeException|BaseException|ReflectionException */ public function save(string $dest, bool $overwrite = false): bool { + $this->ensureAllowedMimeTypesLoaded(); + $localFileSystem = $this->getLocalFileSystem(); + if ($this->errorCode !== UPLOAD_ERR_OK) { throw new FileUploadException($this->getErrorMessage()); } - if (!$this->localFileSystem->isFile($this->getPathname())) { + if (!$localFileSystem->isFile($this->getPathname())) { throw FileUploadException::fileNotFound($this->getPathname()); } @@ -287,15 +281,15 @@ public function save(string $dest, bool $overwrite = false): bool $filePath = $dest . DS . $this->getNameWithExtension(); if (!$this->remoteFileSystem) { - if (!$this->localFileSystem->isDirectory($dest)) { + if (!$localFileSystem->isDirectory($dest)) { throw FileSystemException::directoryNotExists($dest); } - if (!$this->localFileSystem->isWritable($dest)) { + if (!$localFileSystem->isWritable($dest)) { throw FileSystemException::directoryNotWritable($dest); } - if ($overwrite === false && $this->localFileSystem->exists($filePath)) { + if ($overwrite === false && $localFileSystem->exists($filePath)) { throw FileSystemException::fileAlreadyExists($filePath); } } @@ -369,15 +363,53 @@ public function isImage(string $filePath): bool */ protected function moveUploadedFile(string $filePath): bool { + $localFileSystem = $this->getLocalFileSystem(); + if ($this->remoteFileSystem) { - return (bool) $this->remoteFileSystem->put($filePath, $this->localFileSystem->get($this->getPathname())); + return (bool) $this->remoteFileSystem->put($filePath, $localFileSystem->get($this->getPathname())); } elseif ($this->isUploaded()) { return move_uploaded_file($this->getPathname(), $filePath); } else { - return $this->localFileSystem->copy($this->getPathname(), $filePath); + return $localFileSystem->copy($this->getPathname(), $filePath); } } + /** + * @throws ConfigException|DiException|BaseException|ReflectionException + */ + private function getLocalFileSystem(): LocalFilesystemAdapterInterface + { + if ($this->localFileSystem) { + return $this->localFileSystem; + } + + $adapter = fs()->getAdapter(); + + if (!$adapter instanceof LocalFilesystemAdapterInterface) { + throw FileSystemException::notInstanceOf( + get_class($adapter), + LocalFilesystemAdapterInterface::class + ); + } + + $this->localFileSystem = $adapter; + + return $this->localFileSystem; + } + + /** + * @throws FileUploadException|LoaderException|ConfigException|DiException|ReflectionException + */ + private function ensureAllowedMimeTypesLoaded(): void + { + if ($this->mimeTypesLoaded) { + return; + } + + $this->loadAllowedMimeTypesFromConfig(); + $this->mimeTypesLoaded = true; + } + /** * Validates upload against allowed mime types => extensions map */ From e512b45cf100795466cbc9eae7274de7677a2227 Mon Sep 17 00:00:00 2001 From: Arman <407448+armanist@users.noreply.github.com> Date: Fri, 1 May 2026 20:24:34 +0400 Subject: [PATCH 2/2] [#492] Preserve strict MIME overrides when setAllowedMimeTypes is used --- src/Storage/UploadedFile.php | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/Storage/UploadedFile.php b/src/Storage/UploadedFile.php index 869d3381..91251f9d 100644 --- a/src/Storage/UploadedFile.php +++ b/src/Storage/UploadedFile.php @@ -118,6 +118,11 @@ class UploadedFile extends SplFileInfo */ protected bool $mimeTypesLoaded = false; + /** + * Whether mime types were explicitly set by caller + */ + protected bool $mimeTypesOverridden = false; + /** * @param array $meta */ @@ -136,6 +141,8 @@ public function __construct(array $meta) public function setAllowedMimeTypes(array $allowedMimeTypes, bool $merge = true): UploadedFile { $this->setAllowedMimeTypesMap($allowedMimeTypes, $merge); + $this->mimeTypesOverridden = true; + $this->mimeTypesLoaded = true; return $this; } @@ -402,7 +409,7 @@ private function getLocalFileSystem(): LocalFilesystemAdapterInterface */ private function ensureAllowedMimeTypesLoaded(): void { - if ($this->mimeTypesLoaded) { + if ($this->mimeTypesLoaded || $this->mimeTypesOverridden) { return; } @@ -419,7 +426,7 @@ protected function allowed(string $extension, string $mimeType): bool $mimeType = strtolower($mimeType); return isset($this->allowedMimeTypes[$mimeType]) && - in_array($extension, (array) $this->allowedMimeTypes[$mimeType], true); + in_array($extension, $this->allowedMimeTypes[$mimeType], true); } /**