diff --git a/application/Espo/EntryPoints/Image.php b/application/Espo/EntryPoints/Image.php index 136785aeaa..60b0f752f1 100644 --- a/application/Espo/EntryPoints/Image.php +++ b/application/Espo/EntryPoints/Image.php @@ -45,9 +45,11 @@ use Espo\Core\ORM\EntityManager; use Espo\Core\Utils\Config; use Espo\Core\Utils\File\Manager as FileManager; use Espo\Core\Utils\Metadata; - use Espo\Entities\Attachment; +use GdImage; +use Throwable; + class Image implements EntryPoint { /** @var ?string[] */ @@ -87,7 +89,7 @@ class Image implements EntryPoint throw new BadRequest(); } - $this->show($response, $id, $size, false); + $this->show($response, $id, $size); } /** @@ -112,7 +114,7 @@ class Image implements EntryPoint $fileType = $attachment->getType(); if (!in_array($fileType, $this->getAllowedFileTypeList())) { - throw new ForbiddenSilent("Not allowed file type '{$fileType}'."); + throw new ForbiddenSilent("Not allowed file type '$fileType'."); } if ($this->allowedRelatedTypeList) { @@ -127,22 +129,26 @@ class Image implements EntryPoint } } + $fileSize = 0; + $fileName = $attachment->getName(); + $toResize = $size && in_array($fileType, $this->getResizableFileTypeList()); if ($toResize) { - $fileName = $size . '-' . $attachment->getName(); - $contents = $this->getThumbContents($attachment, $size); - $fileSize = strlen($contents); + if ($contents) { + $fileName = $size . '-' . $attachment->getName(); + $fileSize = strlen($contents); - $response->writeBody($contents); + $response->writeBody($contents); + } else { + $toResize = false; + } } - else { - $fileName = $attachment->getName(); + if (!$toResize) { $stream = $this->fileStorageManager->getStream($attachment); - $fileSize = $stream->getSize() ?? $this->fileStorageManager->getSize($attachment); $response->setBody($stream); @@ -164,7 +170,7 @@ class Image implements EntryPoint * @throws Error * @throws NotFound */ - private function getThumbContents(Attachment $attachment, string $size): string + private function getThumbContents(Attachment $attachment, string $size): ?string { if (!array_key_exists($size, $this->getSizes())) { throw new Error("Bad size."); @@ -174,7 +180,7 @@ class Image implements EntryPoint $sourceId = $attachment->getSourceId(); - $cacheFilePath = "data/upload/thumbs/{$sourceId}_{$size}"; + $cacheFilePath = "data/upload/thumbs/{$sourceId}_$size"; if ($useCache && $this->fileManager->isFile($cacheFilePath)) { return $this->fileManager->getContents($cacheFilePath); @@ -190,6 +196,10 @@ class Image implements EntryPoint $targetImage = $this->createThumbImage($filePath, $fileType, $size); + if (!$targetImage) { + return null; + } + ob_start(); switch ($fileType) { @@ -228,11 +238,9 @@ class Image implements EntryPoint } /** - * @return \GdImage - * @phpstan-ignore-next-line * @throws Error */ - private function createThumbImage(string $filePath, string $fileType, string $size) + private function createThumbImage(string $filePath, string $fileType, string $size): ?GdImage { if (!is_array(getimagesize($filePath))) { throw new Error(); @@ -268,30 +276,52 @@ class Image implements EntryPoint $targetImage = imagecreatetruecolor($targetWidth, $targetHeight); + if ($targetImage === false) { + return null; + } + switch ($fileType) { case 'image/jpeg': $sourceImage = imagecreatefromjpeg($filePath); - imagecopyresampled( - $targetImage, $sourceImage, 0, 0, 0, 0, /** @phpstan-ignore-line */ - $targetWidth, $targetHeight, $originalWidth, $originalHeight + if ($sourceImage === false) { + return null; + } + + $this->resample( + $targetImage, + $sourceImage, + $targetWidth, + $targetHeight, + $originalWidth, + $originalHeight ); + break; case 'image/png': $sourceImage = imagecreatefrompng($filePath); - imagealphablending($targetImage, false); /** @phpstan-ignore-line */ - imagesavealpha($targetImage, true); /** @phpstan-ignore-line */ + if ($sourceImage === false) { + return null; + } - $transparent = imagecolorallocatealpha($targetImage, 255, 255, 255, 127); /** @phpstan-ignore-line */ + imagealphablending($targetImage, false); + imagesavealpha($targetImage, true); - /** @phpstan-ignore-next-line */ - imagefilledrectangle($targetImage, 0, 0, $targetWidth, $targetHeight, $transparent); + $transparent = imagecolorallocatealpha($targetImage, 255, 255, 255, 127); - imagecopyresampled( - $targetImage, $sourceImage, 0, 0, 0, 0, /** @phpstan-ignore-line */ - $targetWidth, $targetHeight, $originalWidth, $originalHeight + if ($transparent !== false) { + imagefilledrectangle($targetImage, 0, 0, $targetWidth, $targetHeight, $transparent); + } + + $this->resample( + $targetImage, + $sourceImage, + $targetWidth, + $targetHeight, + $originalWidth, + $originalHeight ); break; @@ -299,29 +329,50 @@ class Image implements EntryPoint case 'image/gif': $sourceImage = imagecreatefromgif($filePath); - imagecopyresampled( - $targetImage, $sourceImage, 0, 0, 0, 0, /** @phpstan-ignore-line */ - $targetWidth, $targetHeight, $originalWidth, $originalHeight + if ($sourceImage === false) { + return null; + } + + $this->resample( + $targetImage, + $sourceImage, + $targetWidth, + $targetHeight, + $originalWidth, + $originalHeight ); break; case 'image/webp': - $sourceImage = imagecreatefromwebp($filePath); + try { + $sourceImage = imagecreatefromwebp($filePath); + } + catch (Throwable) { + return null; + } - imagecopyresampled( - $targetImage, $sourceImage, 0, 0, 0, 0, /** @phpstan-ignore-line */ - $targetWidth, $targetHeight, $originalWidth, $originalHeight + if ($sourceImage === false) { + return null; + } + + $this->resample( + $targetImage, + $sourceImage, + $targetWidth, + $targetHeight, + $originalWidth, + $originalHeight ); break; } if (in_array($fileType, $this->getFixOrientationFileTypeList())) { - $targetImage = $this->fixOrientation($targetImage, $filePath); /** @phpstan-ignore-line */ + $targetImage = $this->fixOrientation($targetImage, $filePath); } - return $targetImage; /** @phpstan-ignore-line */ + return $targetImage; } /** @@ -340,8 +391,8 @@ class Image implements EntryPoint } /** - * @param \GdImage $targetImage - * @return \GdImage + * @param GdImage $targetImage + * @return GdImage * @phpstan-ignore-next-line */ private function fixOrientation($targetImage, string $filePath) @@ -395,4 +446,21 @@ class Image implements EntryPoint /** @var AttachmentRepository */ return $this->entityManager->getRepository(Attachment::ENTITY_TYPE); } + + private function resample( + GdImage $targetImage, + GdImage $sourceImage, + int $targetWidth, + int $targetHeight, + int $originalWidth, + int $originalHeight + ): void { + + imagecopyresampled( + $targetImage, + $sourceImage, + 0, 0, 0, 0, + $targetWidth, $targetHeight, $originalWidth, $originalHeight + ); + } }