diff --git a/application/Espo/Core/Utils/File/MimeType.php b/application/Espo/Core/Utils/File/MimeType.php index 8f07276a8f..7f7b382fd4 100644 --- a/application/Espo/Core/Utils/File/MimeType.php +++ b/application/Espo/Core/Utils/File/MimeType.php @@ -54,4 +54,17 @@ class MimeType return $typeList[0] ?? null; } + + public static function matchMimeTypeToAcceptToken(string $mimeType, string $token): bool + { + if ($mimeType === $token) { + return true; + } + + if (in_array($token, ['audio/*', 'video/*', 'image/*'])) { + return strpos($mimeType, substr($token, 0, -2)) === 0; + } + + return false; + } } diff --git a/application/Espo/Services/Attachment.php b/application/Espo/Services/Attachment.php index b9ea795c98..f4b0f55144 100644 --- a/application/Espo/Services/Attachment.php +++ b/application/Espo/Services/Attachment.php @@ -283,6 +283,9 @@ class Attachment extends Record } } + /** + * @throws Forbidden + */ private function checkAttachmentType(AttachmentEntity $attachment): void { $field = $attachment->getTargetField(); @@ -302,6 +305,42 @@ class Attachment extends Record return; } + + $name = $attachment->getName() ?? ''; + + $extension = strtolower( + array_slice(explode('.', $name), -1)[0] ?? '' + ); + + /** @var string[] */ + $accept = $this->metadata->get(['entityDefs', $entityType, 'fields', $field, 'accept']) ?? []; + + if ($accept === []) { + return; + } + + $mimeType = $this->getMimeTypeUtil()->getMimeTypeByExtension($extension) ?? + $attachment->getType(); + + $found = false; + + foreach ($accept as $token) { + if (strtolower($token) === '.' . $extension) { + $found = true; + + break; + } + + if ($mimeType && MimeType::matchMimeTypeToAcceptToken($mimeType, $token)) { + $found = true; + + break; + } + } + + if (!$found) { + throw new ForbiddenSilent("Not allowed file type."); + } } /** diff --git a/tests/unit/Espo/Core/Utils/File/MimeTypeTest.php b/tests/unit/Espo/Core/Utils/File/MimeTypeTest.php index 5db1f9c27e..0fd8faa72f 100644 --- a/tests/unit/Espo/Core/Utils/File/MimeTypeTest.php +++ b/tests/unit/Espo/Core/Utils/File/MimeTypeTest.php @@ -41,7 +41,7 @@ class MimeTypeTest extends \PHPUnit\Framework\TestCase $this->metadata = $this->createMock(Metadata::class); } - public function testGetMimeTypeByExtension1(): void + public function testGetMimeTypeByExtension(): void { $this->metadata ->expects($this->any()) @@ -54,4 +54,12 @@ class MimeTypeTest extends \PHPUnit\Framework\TestCase $this->assertEquals('text/csv', $util->getMimeTypeByExtension('csv')); $this->assertEquals('text/csv', $util->getMimeTypeByExtension('CSV')); } + + public function testMatchMimeTypeToAcceptToken(): void + { + $this->assertTrue(MimeType::matchMimeTypeToAcceptToken('text/csv', 'text/csv')); + $this->assertFalse(MimeType::matchMimeTypeToAcceptToken('text/csv', 'text/plain')); + $this->assertTrue(MimeType::matchMimeTypeToAcceptToken('video/mpeg', 'video/*')); + $this->assertFalse(MimeType::matchMimeTypeToAcceptToken('video/mpeg', 'image/*')); + } }