summaryrefslogtreecommitdiffstatshomepage
diff options
context:
space:
mode:
authorAlex Pott <alex.a.pott@googlemail.com>2025-04-16 09:58:40 +0200
committerAlex Pott <alex.a.pott@googlemail.com>2025-04-16 09:58:40 +0200
commit37737af85cf02359a55bb3346936d5e78b8d901b (patch)
treea94ae0182c05a48e103dfb2a74fd8b182e399003
parente3894f6a6aa6cbc6778322878f68de8066a26516 (diff)
downloaddrupal-37737af85cf02359a55bb3346936d5e78b8d901b.tar.gz
drupal-37737af85cf02359a55bb3346936d5e78b8d901b.zip
Issue #2311679 by kim.pepper, mondrake, bhanu951, arla, alexpott, ParisLiakos, yogeshmpawar, pcambra, godotislate, mfb, berdir: Separate MIME type mapping from ExtensionMimeTypeGuesser
-rw-r--r--core/.phpstan-baseline.php18
-rw-r--r--core/core.services.yml9
-rw-r--r--core/lib/Drupal/Core/File/Event/MimeTypeMapLoadedEvent.php19
-rw-r--r--core/lib/Drupal/Core/File/EventSubscriber/LegacyMimeTypeMapLoadedSubscriber.php56
-rw-r--r--core/lib/Drupal/Core/File/MimeType/ExtensionMimeTypeGuesser.php113
-rw-r--r--core/lib/Drupal/Core/File/MimeType/MimeTypeGuesser.php4
-rw-r--r--core/lib/Drupal/Core/File/MimeType/MimeTypeMap.php1018
-rw-r--r--core/lib/Drupal/Core/File/MimeType/MimeTypeMapFactory.php35
-rw-r--r--core/lib/Drupal/Core/File/MimeType/MimeTypeMapInterface.php108
-rw-r--r--core/lib/Drupal/Core/File/file.api.php13
-rw-r--r--core/lib/Drupal/Core/ProxyClass/File/MimeType/ExtensionMimeTypeGuesser.php4
-rw-r--r--core/lib/Drupal/Core/ProxyClass/File/MimeType/MimeTypeGuesser.php8
-rw-r--r--core/modules/file/src/Plugin/Field/FieldFormatter/FileMediaFormatterBase.php7
-rw-r--r--core/modules/file/tests/file_deprecated_test/file_deprecated_test.info.yml4
-rw-r--r--core/modules/file/tests/file_deprecated_test/file_deprecated_test.module35
-rw-r--r--core/modules/file/tests/file_test/file_test.services.yml3
-rw-r--r--core/modules/file/tests/file_test/src/EventSubscriber/DummyMimeTypeMapLoadedSubscriber.php41
-rw-r--r--core/modules/file/tests/file_test/src/Hook/FileTestHooks.php24
-rw-r--r--core/modules/responsive_image/responsive_image.module32
-rw-r--r--core/tests/Drupal/KernelTests/Core/File/ExtensionMimeTypeGuesserDeprecationTest.php42
-rw-r--r--core/tests/Drupal/KernelTests/Core/File/MimeType/ExtensionMimeTypeGuesserTest.php133
-rw-r--r--core/tests/Drupal/KernelTests/Core/File/MimeType/LegacyMimeTypeTest.php38
-rw-r--r--core/tests/Drupal/KernelTests/Core/File/MimeTypeTest.php106
-rw-r--r--core/tests/Drupal/Tests/Core/File/MimeType/MimeTypeMapTest.php116
24 files changed, 1796 insertions, 190 deletions
diff --git a/core/.phpstan-baseline.php b/core/.phpstan-baseline.php
index 0abeb6f41cb..6f4a52139f1 100644
--- a/core/.phpstan-baseline.php
+++ b/core/.phpstan-baseline.php
@@ -7430,18 +7430,6 @@ $ignoreErrors[] = [
'path' => __DIR__ . '/lib/Drupal/Core/File/HtaccessWriterInterface.php',
];
$ignoreErrors[] = [
- 'message' => '#^Method Drupal\\\\Core\\\\File\\\\MimeType\\\\ExtensionMimeTypeGuesser\\:\\:setMapping\\(\\) has no return type specified\\.$#',
- 'identifier' => 'missingType.return',
- 'count' => 1,
- 'path' => __DIR__ . '/lib/Drupal/Core/File/MimeType/ExtensionMimeTypeGuesser.php',
-];
-$ignoreErrors[] = [
- 'message' => '#^Method Drupal\\\\Core\\\\File\\\\MimeType\\\\MimeTypeGuesser\\:\\:registerWithSymfonyGuesser\\(\\) has no return type specified\\.$#',
- 'identifier' => 'missingType.return',
- 'count' => 1,
- 'path' => __DIR__ . '/lib/Drupal/Core/File/MimeType/MimeTypeGuesser.php',
-];
-$ignoreErrors[] = [
'message' => '#^Method Drupal\\\\Core\\\\FileTransfer\\\\ChmodInterface\\:\\:chmodJailed\\(\\) has no return type specified\\.$#',
'identifier' => 'missingType.return',
'count' => 1,
@@ -9400,12 +9388,6 @@ $ignoreErrors[] = [
'path' => __DIR__ . '/lib/Drupal/Core/ProxyClass/Extension/ModuleInstaller.php',
];
$ignoreErrors[] = [
- 'message' => '#^Method Drupal\\\\Core\\\\ProxyClass\\\\File\\\\MimeType\\\\ExtensionMimeTypeGuesser\\:\\:setMapping\\(\\) has no return type specified\\.$#',
- 'identifier' => 'missingType.return',
- 'count' => 1,
- 'path' => __DIR__ . '/lib/Drupal/Core/ProxyClass/File/MimeType/ExtensionMimeTypeGuesser.php',
-];
-$ignoreErrors[] = [
'message' => '#^Method Drupal\\\\Core\\\\ProxyClass\\\\File\\\\MimeType\\\\MimeTypeGuesser\\:\\:addMimeTypeGuesser\\(\\) has no return type specified\\.$#',
'identifier' => 'missingType.return',
'count' => 1,
diff --git a/core/core.services.yml b/core/core.services.yml
index bec73aa5ccf..4372e6512c3 100644
--- a/core/core.services.yml
+++ b/core/core.services.yml
@@ -1805,10 +1805,17 @@ services:
lazy: true
file.mime_type.guesser.extension:
class: Drupal\Core\File\MimeType\ExtensionMimeTypeGuesser
- arguments: ['@module_handler']
+ arguments: ['@Drupal\Core\File\MimeType\MimeTypeMapInterface', '@file_system']
tags:
- { name: mime_type_guesser }
lazy: true
+ Drupal\Core\File\MimeType\MimeTypeMapFactory:
+ autowire: true
+ public: false
+ Drupal\Core\File\MimeType\MimeTypeMapInterface:
+ factory: ['@Drupal\Core\File\MimeType\MimeTypeMapFactory', create]
+ Drupal\Core\File\EventSubscriber\LegacyMimeTypeMapLoadedSubscriber:
+ autowire: true
# Currently needs to be public as it is called by
# \Drupal\Core\Render\Element\StatusMessages.
# @todo Consider making this service private again after
diff --git a/core/lib/Drupal/Core/File/Event/MimeTypeMapLoadedEvent.php b/core/lib/Drupal/Core/File/Event/MimeTypeMapLoadedEvent.php
new file mode 100644
index 00000000000..8d7ea48630d
--- /dev/null
+++ b/core/lib/Drupal/Core/File/Event/MimeTypeMapLoadedEvent.php
@@ -0,0 +1,19 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\Core\File\Event;
+
+use Drupal\Core\File\MimeType\MimeTypeMapInterface;
+use Symfony\Contracts\EventDispatcher\Event;
+
+/**
+ * Event that is fired when the MIME type map is loaded.
+ */
+final class MimeTypeMapLoadedEvent extends Event {
+
+ public function __construct(
+ public readonly MimeTypeMapInterface $map,
+ ) {}
+
+}
diff --git a/core/lib/Drupal/Core/File/EventSubscriber/LegacyMimeTypeMapLoadedSubscriber.php b/core/lib/Drupal/Core/File/EventSubscriber/LegacyMimeTypeMapLoadedSubscriber.php
new file mode 100644
index 00000000000..942cc65f709
--- /dev/null
+++ b/core/lib/Drupal/Core/File/EventSubscriber/LegacyMimeTypeMapLoadedSubscriber.php
@@ -0,0 +1,56 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\Core\File\EventSubscriber;
+
+use Drupal\Core\Extension\ModuleHandlerInterface;
+use Drupal\Core\File\Event\MimeTypeMapLoadedEvent;
+use Drupal\Core\File\MimeType\MimeTypeMap;
+use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+
+/**
+ * Modifies the MIME type map by calling hook_file_mimetype_mapping_alter().
+ *
+ * This event subscriber provides BC support for the deprecated
+ * hook_file_mimetype_mapping_alter() and will be removed in drupal:12.0.0.
+ *
+ * @internal
+ *
+ * @see https://www.drupal.org/node/3494040
+ */
+final class LegacyMimeTypeMapLoadedSubscriber implements
+ EventSubscriberInterface {
+
+ public function __construct(
+ protected readonly ModuleHandlerInterface $moduleHandler,
+ ) {}
+
+ /**
+ * Handle the event by calling deprecated hook_file_mimetype_mapping_alter().
+ */
+ public function onMimeTypeMapLoaded(MimeTypeMapLoadedEvent $event): void {
+ if (!$event->map instanceof MimeTypeMap) {
+ return;
+ }
+ // @phpstan-ignore-next-line method.deprecated
+ $mapping = $event->map->getMapping();
+ $this->moduleHandler->alterDeprecated(
+ 'This hook is deprecated in drupal:11.2.0 and is removed from drupal:12.0.0. Implement a \Drupal\Core\File\Event\MimeTypeMapLoadedEvent listener instead. See https://www.drupal.org/node/3494040',
+ 'file_mimetype_mapping',
+ $mapping,
+ );
+ // @phpstan-ignore-next-line method.deprecated
+ $event->map->setMapping($mapping);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public static function getSubscribedEvents(): array {
+ return [
+ MimeTypeMapLoadedEvent::class => 'onMimeTypeMapLoaded',
+ ];
+ }
+
+}
diff --git a/core/lib/Drupal/Core/File/MimeType/ExtensionMimeTypeGuesser.php b/core/lib/Drupal/Core/File/MimeType/ExtensionMimeTypeGuesser.php
index 4ed1bb82d7d..066bee922b4 100644
--- a/core/lib/Drupal/Core/File/MimeType/ExtensionMimeTypeGuesser.php
+++ b/core/lib/Drupal/Core/File/MimeType/ExtensionMimeTypeGuesser.php
@@ -2,19 +2,28 @@
namespace Drupal\Core\File\MimeType;
+use Drupal\Core\DependencyInjection\DeprecatedServicePropertyTrait;
use Drupal\Core\Extension\ModuleHandlerInterface;
+use Drupal\Core\File\Event\MimeTypeMapLoadedEvent;
+use Drupal\Core\File\FileSystemInterface;
use Symfony\Component\Mime\MimeTypeGuesserInterface;
/**
* Makes possible to guess the MIME type of a file using its extension.
*/
class ExtensionMimeTypeGuesser implements MimeTypeGuesserInterface {
+ use DeprecatedServicePropertyTrait;
/**
* Default MIME extension mapping.
*
* @var array
* Array of mimetypes correlated to the extensions that relate to them.
+ *
+ * @deprecated in drupal:11.2.0 and is removed from drupal:12.0.0. Pass a
+ * MimeTypeMapInterface $map to the constructor instead.
+ *
+ * @see https://www.drupal.org/node/3494040
*/
protected $defaultMapping = [
// cspell:disable
@@ -877,39 +886,86 @@ class ExtensionMimeTypeGuesser implements MimeTypeGuesserInterface {
* The MIME types mapping array after going through the module handler.
*
* @var array
+ *
+ * @deprecated in drupal:11.2.0 and is removed from drupal:12.0.0. Pass a
+ * MimeTypeMapInterface $map to the constructor instead.
+ *
+ * @see https://www.drupal.org/node/3494040
*/
protected $mapping;
/**
- * The module handler.
+ * Deprecated service properties.
+ *
+ * @var string[]
*
- * @var \Drupal\Core\Extension\ModuleHandlerInterface
+ * @see https://www.drupal.org/node/3494040
*/
- protected $moduleHandler;
+ protected $deprecatedProperties = [
+ 'moduleHandler' => 'module_handler',
+ ];
+
+ /**
+ * The MIME type map.
+ */
+ protected MimeTypeMapInterface $map;
+
+ /**
+ * The file system.
+ */
+ protected FileSystemInterface $fileSystem;
/**
* Constructs a new ExtensionMimeTypeGuesser.
*
- * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
- * The module handler.
+ * @param \Drupal\Core\Extension\ModuleHandlerInterface|\Drupal\Core\File\MimeType\MimeTypeMapInterface $map
+ * The MIME type map.
+ * @param \Drupal\Core\File\FileSystemInterface|null $fileSystem
+ * The file system.
*/
- public function __construct(ModuleHandlerInterface $module_handler) {
- $this->moduleHandler = $module_handler;
+ public function __construct(
+ MimeTypeMapInterface|ModuleHandlerInterface $map,
+ ?FileSystemInterface $fileSystem = NULL,
+ ) {
+ if ($map instanceof ModuleHandlerInterface) {
+ @trigger_error(
+ 'Calling ' . __METHOD__ . '() with the $map argument as an instance of \Drupal\Core\Extension\ModuleHandlerInterface is deprecated in drupal:11.2.0 and an instance of \Drupal\Core\File\MimeType\MimeTypeMapInterface is required in drupal:12.0.0. See https://www.drupal.org/node/3494040',
+ E_USER_DEPRECATED
+ );
+ $map = \Drupal::service(MimeTypeMapInterface::class);
+ }
+ $this->map = $map;
+ if (!$fileSystem) {
+ @trigger_error(
+ 'Calling ' . __METHOD__ . '() without the $fileSystem argument is deprecated in drupal:11.2.0 and is required in drupal:12.0.0. See https://www.drupal.org/node/3494040',
+ E_USER_DEPRECATED
+ );
+ $fileSystem = \Drupal::service(FileSystemInterface::class);
+ }
+ $this->fileSystem = $fileSystem;
}
/**
* {@inheritdoc}
*/
public function guessMimeType($path): ?string {
- if ($this->mapping === NULL) {
- $mapping = $this->defaultMapping;
- // Allow modules to alter the default mapping.
- $this->moduleHandler->alter('file_mimetype_mapping', $mapping);
- $this->mapping = $mapping;
+ if (!isset($this->fileSystem)) {
+ @trigger_error(
+ 'Calling ' . __METHOD__ . '() without the file_system service already injected is deprecated in drupal:11.2.0 and throws an exception in drupal:12.0.0. See https://www.drupal.org/node/3494040',
+ E_USER_DEPRECATED
+ );
+ $this->fileSystem = \Drupal::service(FileSystemInterface::class);
+ }
+ if (!isset($this->map)) {
+ @trigger_error(
+ 'Calling ' . __METHOD__ . '() without the MimeTypeMapInterface service already injected is deprecated in drupal:11.2.0 and throws an exception in drupal:12.0.0. See https://www.drupal.org/node/3494040',
+ E_USER_DEPRECATED
+ );
+ $this->map = \Drupal::service(MimeTypeMapInterface::class);
}
$extension = '';
- $file_parts = explode('.', \Drupal::service('file_system')->basename($path));
+ $file_parts = explode('.', $this->fileSystem->basename($path));
// Remove the first part: a full filename should not match an extension,
// then iterate over the file parts, trying to find a match.
@@ -919,8 +975,8 @@ class ExtensionMimeTypeGuesser implements MimeTypeGuesserInterface {
// empty.
while (array_shift($file_parts) !== NULL) {
$extension = strtolower(implode('.', $file_parts));
- if (isset($this->mapping['extensions'][$extension])) {
- return $this->mapping['mimetypes'][$this->mapping['extensions'][$extension]];
+ if ($mimeType = $this->map->getMimeTypeForExtension($extension)) {
+ return $mimeType;
}
}
@@ -932,9 +988,32 @@ class ExtensionMimeTypeGuesser implements MimeTypeGuesserInterface {
*
* @param array|null $mapping
* Passing a NULL mapping will cause guess() to use self::$defaultMapping.
+ *
+ * @deprecated in drupal:11.2.0 and is removed from drupal:12.0.0. Use
+ * \Drupal\Core\File\MimeType\MimeTypeMapInterface::addMapping() instead.
+ *
+ * @see https://www.drupal.org/node/3494040
*/
- public function setMapping(?array $mapping = NULL) {
- $this->mapping = $mapping;
+ public function setMapping(?array $mapping = NULL): void {
+ @trigger_error(
+ __METHOD__ . '() is deprecated in drupal:11.2.0 and is removed from drupal:12.0.0. Use \Drupal\Core\File\MimeType\MimeTypeMapInterface::addMapping() instead or define your own MimeTypeMapInterface implementation. See https://www.drupal.org/node/3494040',
+ E_USER_DEPRECATED
+ );
+ // Convert the mapping to be keyed by type.
+ $typeMapping = [];
+ foreach ($mapping['mimetypes'] as $index => $mimetype) {
+ $typeMapping[$mimetype] = array_keys($mapping['extensions'], $index);
+ }
+
+ $this->map = new MimeTypeMap();
+ foreach ($typeMapping as $type => $extensions) {
+ foreach ($extensions as $extension) {
+ $this->map->addMapping($type, $extension);
+ }
+ }
+ \Drupal::service('event_dispatcher')->dispatch(
+ new MimeTypeMapLoadedEvent($this->map)
+ );
}
/**
diff --git a/core/lib/Drupal/Core/File/MimeType/MimeTypeGuesser.php b/core/lib/Drupal/Core/File/MimeType/MimeTypeGuesser.php
index d1ec980d68b..3a121c1146c 100644
--- a/core/lib/Drupal/Core/File/MimeType/MimeTypeGuesser.php
+++ b/core/lib/Drupal/Core/File/MimeType/MimeTypeGuesser.php
@@ -122,7 +122,9 @@ class MimeTypeGuesser implements MimeTypeGuesserInterface {
*
* @see \Symfony\Component\Mime\MimeTypes
*/
- public static function registerWithSymfonyGuesser(ContainerInterface $container) {
+ public static function registerWithSymfonyGuesser(
+ ContainerInterface $container,
+ ): void {
$guesser = new MimeTypes();
$guesser->registerGuesser($container->get('file.mime_type.guesser'));
MimeTypes::setDefault($guesser);
diff --git a/core/lib/Drupal/Core/File/MimeType/MimeTypeMap.php b/core/lib/Drupal/Core/File/MimeType/MimeTypeMap.php
new file mode 100644
index 00000000000..1a1203241fa
--- /dev/null
+++ b/core/lib/Drupal/Core/File/MimeType/MimeTypeMap.php
@@ -0,0 +1,1018 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\Core\File\MimeType;
+
+/**
+ * Provides a sensible mapping between filename extensions and MIME types.
+ */
+class MimeTypeMap implements MimeTypeMapInterface {
+
+ /**
+ * The MIME extension mapping.
+ *
+ * @var array{'mimetypes': array{int,string}, 'extensions': array{string,int}}
+ * An array consisting of two arrays:
+ * - mimetypes: MIME types, keyed by a unique number.
+ * - extensions: an associative array with the MIME type key numbers as
+ * values. The keys are file extensions, in lower case and without any
+ * preceding dot.
+ *
+ * cspell:disable.
+ */
+ protected array $mapping = [
+ 'mimetypes' => [
+ 0 => 'application/andrew-inset',
+ 1 => 'application/atom',
+ 2 => 'application/atomcat+xml',
+ 3 => 'application/atomserv+xml',
+ 4 => 'application/cap',
+ 5 => 'application/cu-seeme',
+ 6 => 'application/dsptype',
+ 350 => 'application/epub+zip',
+ 359 => 'application/gzip',
+ 7 => 'application/hta',
+ 8 => 'application/java-archive',
+ 9 => 'application/java-serialized-object',
+ 10 => 'application/java-vm',
+ 11 => 'application/mac-binhex40',
+ 12 => 'application/mathematica',
+ 13 => 'application/msaccess',
+ 14 => 'application/msword',
+ 15 => 'application/octet-stream',
+ 16 => 'application/oda',
+ 17 => 'application/ogg',
+ 18 => 'application/pdf',
+ 19 => 'application/pgp-keys',
+ 20 => 'application/pgp-signature',
+ 21 => 'application/pics-rules',
+ 22 => 'application/postscript',
+ 23 => 'application/rar',
+ 24 => 'application/rdf+xml',
+ 25 => 'application/rss+xml',
+ 26 => 'application/rtf',
+ 27 => 'application/smil',
+ 349 => 'application/vnd.amazon.ebook',
+ 28 => 'application/vnd.cinderella',
+ 29 => 'application/vnd.google-earth.kml+xml',
+ 30 => 'application/vnd.google-earth.kmz',
+ 31 => 'application/vnd.mozilla.xul+xml',
+ 32 => 'application/vnd.ms-excel',
+ 33 => 'application/vnd.ms-excel.addin.macroEnabled.12',
+ 34 => 'application/vnd.ms-excel.sheet.binary.macroEnabled.12',
+ 35 => 'application/vnd.ms-excel.sheet.macroEnabled.12',
+ 36 => 'application/vnd.ms-excel.template.macroEnabled.12',
+ 37 => 'application/vnd.ms-pki.seccat',
+ 38 => 'application/vnd.ms-pki.stl',
+ 39 => 'application/vnd.ms-powerpoint',
+ 40 => 'application/vnd.ms-powerpoint.addin.macroEnabled.12',
+ 41 => 'application/vnd.ms-powerpoint.presentation.macroEnabled.12',
+ 42 => 'application/vnd.ms-powerpoint.slideshow.macroEnabled.12',
+ 43 => 'application/vnd.ms-powerpoint.template.macroEnabled.12',
+ 44 => 'application/vnd.ms-word.document.macroEnabled.12',
+ 45 => 'application/vnd.ms-word.template.macroEnabled.12',
+ 46 => 'application/vnd.ms-xpsdocument',
+ 47 => 'application/vnd.oasis.opendocument.chart',
+ 48 => 'application/vnd.oasis.opendocument.database',
+ 49 => 'application/vnd.oasis.opendocument.formula',
+ 50 => 'application/vnd.oasis.opendocument.graphics',
+ 51 => 'application/vnd.oasis.opendocument.graphics-template',
+ 52 => 'application/vnd.oasis.opendocument.image',
+ 53 => 'application/vnd.oasis.opendocument.presentation',
+ 54 => 'application/vnd.oasis.opendocument.presentation-template',
+ 55 => 'application/vnd.oasis.opendocument.spreadsheet',
+ 56 => 'application/vnd.oasis.opendocument.spreadsheet-template',
+ 57 => 'application/vnd.oasis.opendocument.text',
+ 58 => 'application/vnd.oasis.opendocument.text-master',
+ 59 => 'application/vnd.oasis.opendocument.text-template',
+ 60 => 'application/vnd.oasis.opendocument.text-web',
+ 61 => 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
+ 62 => 'application/vnd.openxmlformats-officedocument.presentationml.slideshow',
+ 63 => 'application/vnd.openxmlformats-officedocument.presentationml.template',
+ 64 => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
+ 65 => 'application/vnd.openxmlformats-officedocument.spreadsheetml.template',
+ 66 => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
+ 67 => 'application/vnd.openxmlformats-officedocument.wordprocessingml.template',
+ 68 => 'application/vnd.rim.cod',
+ 69 => 'application/vnd.smaf',
+ 70 => 'application/vnd.stardivision.calc',
+ 71 => 'application/vnd.stardivision.chart',
+ 72 => 'application/vnd.stardivision.draw',
+ 73 => 'application/vnd.stardivision.impress',
+ 74 => 'application/vnd.stardivision.math',
+ 75 => 'application/vnd.stardivision.writer',
+ 76 => 'application/vnd.stardivision.writer-global',
+ 77 => 'application/vnd.sun.xml.calc',
+ 78 => 'application/vnd.sun.xml.calc.template',
+ 79 => 'application/vnd.sun.xml.draw',
+ 80 => 'application/vnd.sun.xml.draw.template',
+ 81 => 'application/vnd.sun.xml.impress',
+ 82 => 'application/vnd.sun.xml.impress.template',
+ 83 => 'application/vnd.sun.xml.math',
+ 84 => 'application/vnd.sun.xml.writer',
+ 85 => 'application/vnd.sun.xml.writer.global',
+ 86 => 'application/vnd.sun.xml.writer.template',
+ 87 => 'application/vnd.symbian.install',
+ 88 => 'application/vnd.visio',
+ 89 => 'application/vnd.wap.wbxml',
+ 90 => 'application/vnd.wap.wmlc',
+ 91 => 'application/vnd.wap.wmlscriptc',
+ 92 => 'application/wordperfect',
+ 93 => 'application/wordperfect5.1',
+ 94 => 'application/x-123',
+ 95 => 'application/x-7z-compressed',
+ 96 => 'application/x-abiword',
+ 97 => 'application/x-apple-diskimage',
+ 98 => 'application/x-bcpio',
+ 99 => 'application/x-bittorrent',
+ 100 => 'application/x-cab',
+ 101 => 'application/x-cbr',
+ 102 => 'application/x-cbz',
+ 103 => 'application/x-cdf',
+ 104 => 'application/x-cdlink',
+ 105 => 'application/x-chess-pgn',
+ 106 => 'application/x-cpio',
+ 107 => 'application/x-debian-package',
+ 108 => 'application/x-director',
+ 109 => 'application/x-dms',
+ 110 => 'application/x-doom',
+ 111 => 'application/x-dvi',
+ 113 => 'application/x-font',
+ 114 => 'application/x-freemind',
+ 115 => 'application/x-futuresplash',
+ 116 => 'application/x-gnumeric',
+ 117 => 'application/x-go-sgf',
+ 118 => 'application/x-graphing-calculator',
+ 119 => 'application/x-gtar',
+ 120 => 'application/x-hdf',
+ 121 => 'application/x-httpd-eruby',
+ 122 => 'application/x-httpd-php',
+ 123 => 'application/x-httpd-php-source',
+ 124 => 'application/x-httpd-php3',
+ 125 => 'application/x-httpd-php3-preprocessed',
+ 126 => 'application/x-httpd-php4',
+ 127 => 'application/x-ica',
+ 128 => 'application/x-internet-signup',
+ 129 => 'application/x-iphone',
+ 130 => 'application/x-iso9660-image',
+ 131 => 'application/x-java-jnlp-file',
+ // Per RFC 9239, text/javascript is preferred over application/javascript.
+ // @see https://www.rfc-editor.org/rfc/rfc9239
+ 132 => 'text/javascript',
+ 133 => 'application/x-jmol',
+ 134 => 'application/x-kchart',
+ 135 => 'application/x-killustrator',
+ 136 => 'application/x-koan',
+ 137 => 'application/x-kpresenter',
+ 138 => 'application/x-kspread',
+ 139 => 'application/x-kword',
+ 140 => 'application/x-latex',
+ 141 => 'application/x-lha',
+ 142 => 'application/x-lyx',
+ 143 => 'application/x-lzh',
+ 144 => 'application/x-lzx',
+ 145 => 'application/x-maker',
+ 146 => 'application/x-mif',
+ 351 => 'application/x-mobipocket-ebook',
+ 352 => 'application/x-mobipocket-ebook',
+ 147 => 'application/x-ms-wmd',
+ 148 => 'application/x-ms-wmz',
+ 149 => 'application/x-msdos-program',
+ 150 => 'application/x-msi',
+ 151 => 'application/x-netcdf',
+ 152 => 'application/x-ns-proxy-autoconfig',
+ 153 => 'application/x-nwc',
+ 154 => 'application/x-object',
+ 155 => 'application/x-oz-application',
+ 156 => 'application/x-pkcs7-certreqresp',
+ 157 => 'application/x-pkcs7-crl',
+ 158 => 'application/x-python-code',
+ 159 => 'application/x-quicktimeplayer',
+ 160 => 'application/x-redhat-package-manager',
+ 161 => 'application/x-shar',
+ 162 => 'application/x-shockwave-flash',
+ 163 => 'application/x-stuffit',
+ 164 => 'application/x-sv4cpio',
+ 165 => 'application/x-sv4crc',
+ 166 => 'application/x-tar',
+ 167 => 'application/x-tcl',
+ 168 => 'application/x-tex-gf',
+ 169 => 'application/x-tex-pk',
+ 170 => 'application/x-texinfo',
+ 171 => 'application/x-trash',
+ 172 => 'application/x-troff',
+ 173 => 'application/x-troff-man',
+ 174 => 'application/x-troff-me',
+ 175 => 'application/x-troff-ms',
+ 176 => 'application/x-ustar',
+ 177 => 'application/x-wais-source',
+ 178 => 'application/x-wingz',
+ 179 => 'application/x-x509-ca-cert',
+ 180 => 'application/x-xcf',
+ 181 => 'application/x-xfig',
+ 182 => 'application/x-xpinstall',
+ 183 => 'application/xhtml+xml',
+ 184 => 'application/xml',
+ 185 => 'application/zip',
+ 360 => 'audio/aac',
+ 186 => 'audio/basic',
+ 112 => 'audio/flac',
+ 187 => 'audio/midi',
+ 346 => 'audio/mp4',
+ 188 => 'audio/mpeg',
+ 189 => 'audio/ogg',
+ 190 => 'audio/prs.sid',
+ 356 => 'audio/webm',
+ 191 => 'audio/x-aiff',
+ 192 => 'audio/x-gsm',
+ 354 => 'audio/x-matroska',
+ 193 => 'audio/x-mpegurl',
+ 194 => 'audio/x-ms-wax',
+ 195 => 'audio/x-ms-wma',
+ 196 => 'audio/x-pn-realaudio',
+ 197 => 'audio/x-realaudio',
+ 198 => 'audio/x-scpls',
+ 199 => 'audio/x-sd2',
+ 200 => 'audio/x-wav',
+ 201 => 'chemical/x-alchemy',
+ 202 => 'chemical/x-cache',
+ 203 => 'chemical/x-cache-csf',
+ 204 => 'chemical/x-cactvs-binary',
+ 205 => 'chemical/x-cdx',
+ 206 => 'chemical/x-cerius',
+ 207 => 'chemical/x-chem3d',
+ 208 => 'chemical/x-chemdraw',
+ 209 => 'chemical/x-cif',
+ 210 => 'chemical/x-cmdf',
+ 211 => 'chemical/x-cml',
+ 212 => 'chemical/x-compass',
+ 213 => 'chemical/x-crossfire',
+ 214 => 'chemical/x-csml',
+ 215 => 'chemical/x-ctx',
+ 216 => 'chemical/x-cxf',
+ 217 => 'chemical/x-embl-dl-nucleotide',
+ 218 => 'chemical/x-galactic-spc',
+ 219 => 'chemical/x-gamess-input',
+ 220 => 'chemical/x-gaussian-checkpoint',
+ 221 => 'chemical/x-gaussian-cube',
+ 222 => 'chemical/x-gaussian-input',
+ 223 => 'chemical/x-gaussian-log',
+ 224 => 'chemical/x-gcg8-sequence',
+ 225 => 'chemical/x-genbank',
+ 226 => 'chemical/x-hin',
+ 227 => 'chemical/x-isostar',
+ 228 => 'chemical/x-jcamp-dx',
+ 229 => 'chemical/x-kinemage',
+ 230 => 'chemical/x-macmolecule',
+ 231 => 'chemical/x-macromodel-input',
+ 232 => 'chemical/x-mdl-molfile',
+ 233 => 'chemical/x-mdl-rdfile',
+ 234 => 'chemical/x-mdl-rxnfile',
+ 235 => 'chemical/x-mdl-sdfile',
+ 236 => 'chemical/x-mdl-tgf',
+ 237 => 'chemical/x-mmcif',
+ 238 => 'chemical/x-mol2',
+ 239 => 'chemical/x-molconn-Z',
+ 240 => 'chemical/x-mopac-graph',
+ 241 => 'chemical/x-mopac-input',
+ 242 => 'chemical/x-mopac-out',
+ 243 => 'chemical/x-mopac-vib',
+ 244 => 'chemical/x-ncbi-asn1-ascii',
+ 245 => 'chemical/x-ncbi-asn1-binary',
+ 246 => 'chemical/x-ncbi-asn1-spec',
+ 247 => 'chemical/x-pdb',
+ 248 => 'chemical/x-rosdal',
+ 249 => 'chemical/x-swissprot',
+ 250 => 'chemical/x-vamas-iso14976',
+ 251 => 'chemical/x-vmd',
+ 252 => 'chemical/x-xtel',
+ 253 => 'chemical/x-xyz',
+ 362 => 'image/avif',
+ 254 => 'image/gif',
+ 255 => 'image/ief',
+ 256 => 'image/jpeg',
+ 257 => 'image/pcx',
+ 258 => 'image/png',
+ 259 => 'image/svg+xml',
+ 260 => 'image/tiff',
+ 261 => 'image/vnd.djvu',
+ 262 => 'image/vnd.microsoft.icon',
+ 263 => 'image/vnd.wap.wbmp',
+ 355 => 'image/webp',
+ 264 => 'image/x-cmu-raster',
+ 265 => 'image/x-coreldraw',
+ 266 => 'image/x-coreldrawpattern',
+ 267 => 'image/x-coreldrawtemplate',
+ 268 => 'image/x-corelphotopaint',
+ 269 => 'image/x-jg',
+ 270 => 'image/x-jng',
+ 271 => 'image/x-ms-bmp',
+ 272 => 'image/x-photoshop',
+ 273 => 'image/x-portable-anymap',
+ 274 => 'image/x-portable-bitmap',
+ 275 => 'image/x-portable-graymap',
+ 276 => 'image/x-portable-pixmap',
+ 277 => 'image/x-rgb',
+ 278 => 'image/x-xbitmap',
+ 279 => 'image/x-xpixmap',
+ 280 => 'image/x-xwindowdump',
+ 281 => 'message/rfc822',
+ 282 => 'model/iges',
+ 283 => 'model/mesh',
+ 284 => 'model/vrml',
+ 285 => 'text/calendar',
+ 286 => 'text/css',
+ 287 => 'text/csv',
+ 288 => 'text/h323',
+ 289 => 'text/html',
+ 290 => 'text/iuls',
+ 291 => 'text/mathml',
+ 292 => 'text/plain',
+ 293 => 'text/richtext',
+ 294 => 'text/scriptlet',
+ 295 => 'text/tab-separated-values',
+ 296 => 'text/texmacs',
+ 297 => 'text/vnd.sun.j2me.app-descriptor',
+ 298 => 'text/vnd.wap.wml',
+ 299 => 'text/vnd.wap.wmlscript',
+ 358 => 'text/vtt',
+ 300 => 'text/x-bibtex',
+ 301 => 'text/x-boo',
+ 302 => 'text/x-c++hdr',
+ 303 => 'text/x-c++src',
+ 304 => 'text/x-chdr',
+ 305 => 'text/x-component',
+ 306 => 'text/x-csh',
+ 307 => 'text/x-csrc',
+ 308 => 'text/x-diff',
+ 309 => 'text/x-dsrc',
+ 310 => 'text/x-haskell',
+ 311 => 'text/x-java',
+ 312 => 'text/x-literate-haskell',
+ 313 => 'text/x-moc',
+ 314 => 'text/x-pascal',
+ 315 => 'text/x-pcs-gcd',
+ 316 => 'text/x-perl',
+ 317 => 'text/x-python',
+ 318 => 'text/x-setext',
+ 319 => 'text/x-sh',
+ 320 => 'text/x-tcl',
+ 321 => 'text/x-tex',
+ 322 => 'text/x-vcalendar',
+ 323 => 'text/x-vcard',
+ 324 => 'video/3gpp',
+ 325 => 'video/dl',
+ 326 => 'video/dv',
+ 327 => 'video/fli',
+ 328 => 'video/gl',
+ 329 => 'video/mp4',
+ 330 => 'video/mpeg',
+ 331 => 'video/ogg',
+ 332 => 'video/quicktime',
+ 333 => 'video/vnd.mpegurl',
+ 357 => 'video/webm',
+ 347 => 'video/x-flv',
+ 334 => 'video/x-la-asf',
+ 348 => 'video/x-m4v',
+ 353 => 'video/x-matroska',
+ 335 => 'video/x-mng',
+ 336 => 'video/x-ms-asf',
+ 337 => 'video/x-ms-wm',
+ 338 => 'video/x-ms-wmv',
+ 339 => 'video/x-ms-wmx',
+ 340 => 'video/x-ms-wvx',
+ 341 => 'video/x-msvideo',
+ 342 => 'video/x-sgi-movie',
+ 343 => 'x-conference/x-cooltalk',
+ 344 => 'x-epoc/x-sisx-app',
+ 345 => 'x-world/x-vrml',
+ 361 => 'application/json',
+ ],
+
+ // Extensions added to this list MUST be lower-case.
+ 'extensions' => [
+ 'ez' => 0,
+ 'atom' => 1,
+ 'atomcat' => 2,
+ 'atomsrv' => 3,
+ 'cap' => 4,
+ 'pcap' => 4,
+ 'cu' => 5,
+ 'tsp' => 6,
+ 'hta' => 7,
+ 'jar' => 8,
+ 'ser' => 9,
+ 'class' => 10,
+ 'hqx' => 11,
+ 'nb' => 12,
+ 'mdb' => 13,
+ 'dot' => 14,
+ 'doc' => 14,
+ 'bin' => 15,
+ 'oda' => 16,
+ 'ogx' => 17,
+ 'pdf' => 18,
+ 'key' => 19,
+ 'pgp' => 20,
+ 'prf' => 21,
+ 'eps' => 22,
+ 'ai' => 22,
+ 'ps' => 22,
+ 'rar' => 23,
+ 'rdf' => 24,
+ 'rss' => 25,
+ 'rtf' => 26,
+ 'smi' => 27,
+ 'smil' => 27,
+ 'cdy' => 28,
+ 'kml' => 29,
+ 'kmz' => 30,
+ 'xul' => 31,
+ 'xlb' => 32,
+ 'xlt' => 32,
+ 'xls' => 32,
+ 'xlam' => 33,
+ 'xlsb' => 34,
+ 'xlsm' => 35,
+ 'xltm' => 36,
+ 'cat' => 37,
+ 'stl' => 38,
+ 'pps' => 39,
+ 'ppt' => 39,
+ 'ppam' => 40,
+ 'pptm' => 41,
+ 'ppsm' => 42,
+ 'potm' => 43,
+ 'docm' => 44,
+ 'dotm' => 45,
+ 'xps' => 46,
+ 'odc' => 47,
+ 'odb' => 48,
+ 'odf' => 49,
+ 'odg' => 50,
+ 'otg' => 51,
+ 'odi' => 52,
+ 'odp' => 53,
+ 'otp' => 54,
+ 'ods' => 55,
+ 'ots' => 56,
+ 'odt' => 57,
+ 'odm' => 58,
+ 'ott' => 59,
+ 'oth' => 60,
+ 'pptx' => 61,
+ 'ppsx' => 62,
+ 'potx' => 63,
+ 'xlsx' => 64,
+ 'xltx' => 65,
+ 'docx' => 66,
+ 'dotx' => 67,
+ 'cod' => 68,
+ 'mmf' => 69,
+ 'sdc' => 70,
+ 'sds' => 71,
+ 'sda' => 72,
+ 'sdd' => 73,
+ 'sdw' => 75,
+ 'sgl' => 76,
+ 'sxc' => 77,
+ 'stc' => 78,
+ 'sxd' => 79,
+ 'std' => 80,
+ 'sxi' => 81,
+ 'sti' => 82,
+ 'sxm' => 83,
+ 'sxw' => 84,
+ 'sxg' => 85,
+ 'stw' => 86,
+ 'sis' => 87,
+ 'vsd' => 88,
+ 'wbxml' => 89,
+ 'wmlc' => 90,
+ 'wmlsc' => 91,
+ 'wpd' => 92,
+ 'wp5' => 93,
+ 'wk' => 94,
+ '7z' => 95,
+ 'abw' => 96,
+ 'dmg' => 97,
+ 'bcpio' => 98,
+ 'torrent' => 99,
+ 'cab' => 100,
+ 'cbr' => 101,
+ 'cbz' => 102,
+ 'cdf' => 103,
+ 'vcd' => 104,
+ 'pgn' => 105,
+ 'cpio' => 106,
+ 'udeb' => 107,
+ 'deb' => 107,
+ 'dir' => 108,
+ 'dxr' => 108,
+ 'dcr' => 108,
+ 'dms' => 109,
+ 'wad' => 110,
+ 'dvi' => 111,
+ 'flac' => 112,
+ 'pfa' => 113,
+ 'pfb' => 113,
+ 'pcf' => 113,
+ 'gsf' => 113,
+ 'pcf.z' => 113,
+ 'mm' => 114,
+ 'spl' => 115,
+ 'gnumeric' => 116,
+ 'sgf' => 117,
+ 'gcf' => 118,
+ 'taz' => 119,
+ 'gtar' => 119,
+ 'tgz' => 119,
+ 'hdf' => 120,
+ 'rhtml' => 121,
+ 'phtml' => 122,
+ 'pht' => 122,
+ 'php' => 122,
+ 'phps' => 123,
+ 'php3' => 124,
+ 'php3p' => 125,
+ 'php4' => 126,
+ 'ica' => 127,
+ 'ins' => 128,
+ 'isp' => 128,
+ 'iii' => 129,
+ 'iso' => 130,
+ 'jnlp' => 131,
+ 'js' => 132,
+ 'jmz' => 133,
+ 'chrt' => 134,
+ 'kil' => 135,
+ 'skp' => 136,
+ 'skd' => 136,
+ 'skm' => 136,
+ 'skt' => 136,
+ 'kpr' => 137,
+ 'kpt' => 137,
+ 'ksp' => 138,
+ 'kwd' => 139,
+ 'kwt' => 139,
+ 'latex' => 140,
+ 'lha' => 141,
+ 'lyx' => 142,
+ 'lzh' => 143,
+ 'lzx' => 144,
+ 'maker' => 145,
+ 'frm' => 145,
+ 'frame' => 145,
+ 'fm' => 145,
+ 'book' => 145,
+ 'fb' => 145,
+ 'fbdoc' => 145,
+ 'mif' => 146,
+ 'wmd' => 147,
+ 'wmz' => 148,
+ 'dll' => 149,
+ 'bat' => 149,
+ 'exe' => 149,
+ 'com' => 149,
+ 'msi' => 150,
+ 'nc' => 151,
+ 'pac' => 152,
+ 'nwc' => 153,
+ 'o' => 154,
+ 'oza' => 155,
+ 'p7r' => 156,
+ 'crl' => 157,
+ 'pyo' => 158,
+ 'pyc' => 158,
+ 'qtl' => 159,
+ 'rpm' => 160,
+ 'shar' => 161,
+ 'swf' => 162,
+ 'swfl' => 162,
+ 'sitx' => 163,
+ 'sit' => 163,
+ 'sv4cpio' => 164,
+ 'sv4crc' => 165,
+ 'tar' => 166,
+ 'gf' => 168,
+ 'pk' => 169,
+ 'texi' => 170,
+ 'texinfo' => 170,
+ 'sik' => 171,
+ '~' => 171,
+ 'bak' => 171,
+ '%' => 171,
+ 'old' => 171,
+ 't' => 172,
+ 'roff' => 172,
+ 'tr' => 172,
+ 'man' => 173,
+ 'me' => 174,
+ 'ms' => 175,
+ 'ustar' => 176,
+ 'src' => 177,
+ 'wz' => 178,
+ 'crt' => 179,
+ 'xcf' => 180,
+ 'fig' => 181,
+ 'xpi' => 182,
+ 'xht' => 183,
+ 'xhtml' => 183,
+ 'xml' => 184,
+ 'xsl' => 184,
+ 'zip' => 185,
+ 'au' => 186,
+ 'snd' => 186,
+ 'mid' => 187,
+ 'midi' => 187,
+ 'kar' => 187,
+ 'mpega' => 188,
+ 'mpga' => 188,
+ 'mp3' => 188,
+ 'mp2' => 188,
+ 'ogg' => 189,
+ 'oga' => 189,
+ 'opus' => 189,
+ 'spx' => 189,
+ 'sid' => 190,
+ 'aif' => 191,
+ 'aiff' => 191,
+ 'aifc' => 191,
+ 'gsm' => 192,
+ 'm3u' => 193,
+ 'wax' => 194,
+ 'wma' => 195,
+ 'rm' => 196,
+ 'ram' => 196,
+ 'ra' => 197,
+ 'pls' => 198,
+ 'sd2' => 199,
+ 'wav' => 200,
+ 'alc' => 201,
+ 'cac' => 202,
+ 'cache' => 202,
+ 'csf' => 203,
+ 'cascii' => 204,
+ 'cbin' => 204,
+ 'ctab' => 204,
+ 'cdx' => 205,
+ 'cer' => 206,
+ 'c3d' => 207,
+ 'chm' => 208,
+ 'cif' => 209,
+ 'cmdf' => 210,
+ 'cml' => 211,
+ 'cpa' => 212,
+ 'bsd' => 213,
+ 'csml' => 214,
+ 'csm' => 214,
+ 'ctx' => 215,
+ 'cxf' => 216,
+ 'cef' => 216,
+ 'emb' => 217,
+ 'embl' => 217,
+ 'spc' => 218,
+ 'gam' => 219,
+ 'inp' => 219,
+ 'gamin' => 219,
+ 'fchk' => 220,
+ 'fch' => 220,
+ 'cub' => 221,
+ 'gau' => 222,
+ 'gjf' => 222,
+ 'gjc' => 222,
+ 'gal' => 223,
+ 'gcg' => 224,
+ 'gen' => 225,
+ 'hin' => 226,
+ 'istr' => 227,
+ 'ist' => 227,
+ 'dx' => 228,
+ 'jdx' => 228,
+ 'kin' => 229,
+ 'mcm' => 230,
+ 'mmd' => 231,
+ 'mmod' => 231,
+ 'mol' => 232,
+ 'rd' => 233,
+ 'rxn' => 234,
+ 'sdf' => 235,
+ 'sd' => 235,
+ 'tgf' => 236,
+ 'mcif' => 237,
+ 'mol2' => 238,
+ 'b' => 239,
+ 'gpt' => 240,
+ 'mopcrt' => 241,
+ 'zmt' => 241,
+ 'mpc' => 241,
+ 'dat' => 241,
+ 'mop' => 241,
+ 'moo' => 242,
+ 'mvb' => 243,
+ 'prt' => 244,
+ 'aso' => 245,
+ 'val' => 245,
+ 'asn' => 246,
+ 'ent' => 247,
+ 'pdb' => 247,
+ 'ros' => 248,
+ 'sw' => 249,
+ 'vms' => 250,
+ 'vmd' => 251,
+ 'xtel' => 252,
+ 'xyz' => 253,
+ 'gif' => 254,
+ 'ief' => 255,
+ 'jpeg' => 256,
+ 'jpe' => 256,
+ 'jpg' => 256,
+ 'pcx' => 257,
+ 'png' => 258,
+ 'svgz' => 259,
+ 'svg' => 259,
+ 'tif' => 260,
+ 'tiff' => 260,
+ 'djvu' => 261,
+ 'djv' => 261,
+ 'ico' => 262,
+ 'wbmp' => 263,
+ 'ras' => 264,
+ 'cdr' => 265,
+ 'pat' => 266,
+ 'cdt' => 267,
+ 'cpt' => 268,
+ 'art' => 269,
+ 'jng' => 270,
+ 'bmp' => 271,
+ 'psd' => 272,
+ 'pnm' => 273,
+ 'pbm' => 274,
+ 'pgm' => 275,
+ 'ppm' => 276,
+ 'rgb' => 277,
+ 'xbm' => 278,
+ 'xpm' => 279,
+ 'xwd' => 280,
+ 'eml' => 281,
+ 'igs' => 282,
+ 'iges' => 282,
+ 'silo' => 283,
+ 'msh' => 283,
+ 'mesh' => 283,
+ 'icz' => 285,
+ 'ics' => 285,
+ 'css' => 286,
+ 'csv' => 287,
+ '323' => 288,
+ 'html' => 289,
+ 'htm' => 289,
+ 'shtml' => 289,
+ 'uls' => 290,
+ 'mml' => 291,
+ 'txt' => 292,
+ 'pot' => 292,
+ 'text' => 292,
+ 'asc' => 292,
+ 'rtx' => 293,
+ 'wsc' => 294,
+ 'sct' => 294,
+ 'tsv' => 295,
+ 'ts' => 296,
+ 'tm' => 296,
+ 'jad' => 297,
+ 'wml' => 298,
+ 'wmls' => 299,
+ 'bib' => 300,
+ 'boo' => 301,
+ 'hpp' => 302,
+ 'hh' => 302,
+ 'h++' => 302,
+ 'hxx' => 302,
+ 'cxx' => 303,
+ 'cc' => 303,
+ 'cpp' => 303,
+ 'c++' => 303,
+ 'h' => 304,
+ 'htc' => 305,
+ 'csh' => 306,
+ 'c' => 307,
+ 'patch' => 308,
+ 'diff' => 308,
+ 'd' => 309,
+ 'hs' => 310,
+ 'java' => 311,
+ 'lhs' => 312,
+ 'moc' => 313,
+ 'pas' => 314,
+ 'p' => 314,
+ 'gcd' => 315,
+ 'pm' => 316,
+ 'pl' => 316,
+ 'py' => 317,
+ 'etx' => 318,
+ 'sh' => 319,
+ 'tk' => 320,
+ 'tcl' => 320,
+ 'cls' => 321,
+ 'ltx' => 321,
+ 'sty' => 321,
+ 'tex' => 321,
+ 'vcs' => 322,
+ 'vcf' => 323,
+ '3gp' => 324,
+ 'dl' => 325,
+ 'dif' => 326,
+ 'dv' => 326,
+ 'fli' => 327,
+ 'gl' => 328,
+ 'mp4' => 329,
+ 'f4v' => 329,
+ 'f4p' => 329,
+ 'mpe' => 330,
+ 'mpeg' => 330,
+ 'mpg' => 330,
+ 'ogv' => 331,
+ 'qt' => 332,
+ 'mov' => 332,
+ 'mxu' => 333,
+ 'lsf' => 334,
+ 'lsx' => 334,
+ 'mng' => 335,
+ 'asx' => 336,
+ 'asf' => 336,
+ 'wm' => 337,
+ 'wmv' => 338,
+ 'wmx' => 339,
+ 'wvx' => 340,
+ 'avi' => 341,
+ 'movie' => 342,
+ 'ice' => 343,
+ 'sisx' => 344,
+ 'wrl' => 345,
+ 'vrm' => 345,
+ 'vrml' => 345,
+ 'f4a' => 346,
+ 'f4b' => 346,
+ 'm4a' => 346,
+ 'flv' => 347,
+ 'm4v' => 348,
+ 'azw' => 349,
+ 'epub' => 350,
+ 'mobi' => 351,
+ 'prc' => 352,
+ 'mkv' => 353,
+ 'mka' => 354,
+ 'webp' => 355,
+ 'weba' => 356,
+ 'webm' => 357,
+ 'vtt' => 358,
+ 'gz' => 359,
+ 'mjs' => 132,
+ 'aac' => 360,
+ 'json' => 361,
+ 'avif' => 362,
+ ],
+ ];
+
+ // cspell:enable
+
+ /**
+ * {@inheritdoc}
+ */
+ public function addMapping(string $mimetype, string $extension): static {
+ $mimetype = strtolower($mimetype);
+ $extension = strtolower($extension);
+ $this->mapping['mimetypes'] ??= [];
+ $key = array_search($mimetype, $this->mapping['mimetypes'], TRUE);
+ if ($key === FALSE) {
+ $key = count($this->mapping['mimetypes']);
+ $this->mapping['mimetypes'][] = $mimetype;
+ }
+ $this->mapping['extensions'][$extension] = $key;
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function removeMapping(string $mimetype, string $extension): bool {
+ $extension = strtolower($extension);
+ if (isset($this->mapping['extensions'][$extension])) {
+ unset($this->mapping['extensions'][$extension]);
+ return TRUE;
+ }
+ return FALSE;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function removeMimeType(string $mimetype): bool {
+ $mimetype = strtolower($mimetype);
+ $key = array_search($mimetype, $this->mapping['mimetypes'], TRUE);
+ if ($key === FALSE) {
+ return FALSE;
+ }
+ foreach ($this->getExtensionsForMimeType($mimetype) as $extension) {
+ $this->removeMapping($mimetype, $extension);
+ }
+ unset($this->mapping['mimetypes'][$key]);
+ return TRUE;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function listMimeTypes(): array {
+ $mimeTypes = array_values($this->mapping['mimetypes']);
+ sort($mimeTypes);
+
+ return $mimeTypes;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function listExtensions(): array {
+ $extensions = array_keys($this->mapping['extensions']);
+ sort($extensions);
+
+ return $extensions;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function hasMimeType(string $mimetype): bool {
+ return in_array(
+ strtolower($mimetype),
+ $this->mapping['mimetypes'],
+ TRUE
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function hasExtension(string $extension): bool {
+ return isset($this->mapping['extensions'][strtolower($extension)]);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getMimeTypeForExtension($extension): ?string {
+ $extension = strtolower($extension);
+ $extensions = $this->mapping['extensions'] ?? [];
+
+ return isset($extensions[$extension]) ? $this->mapping['mimetypes'][$extensions[$extension]] : NULL;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getExtensionsForMimeType($mimetype): array {
+ $mimetype = strtolower($mimetype);
+ $key = array_search($mimetype, $this->mapping['mimetypes'], TRUE);
+ if ($key === FALSE) {
+ return [];
+ }
+ $extensions = array_keys($this->mapping['extensions'], $key, TRUE);
+ sort($extensions);
+ return $extensions;
+ }
+
+ /**
+ * Gets the underlying mapping array.
+ *
+ * @deprecated in drupal:11.2.0 and is removed from drupal:12.0.0. There is no
+ * replacement for this method.
+ *
+ * @see https://www.drupal.org/node/3494040
+ * @see \Drupal\Core\File\EventSubscriber\LegacyMimeTypeMapLoadedSubscriber
+ */
+ public function getMapping(): array {
+ // We do not trigger a deprecation error as this method is needed for
+ // calling the deprecated hook_file_mimetype_mapping_alter() in
+ // LegacyMimeTypeMapLoadedSubscriber::onMimeTypeMapLoaded().
+ return $this->mapping;
+ }
+
+ /**
+ * Sets the underlying mapping array.
+ *
+ * @deprecated in drupal:11.2.0 and is removed from drupal:12.0.0. There is no
+ * replacement for this method.
+ *
+ * @see https://www.drupal.org/node/3494040
+ */
+ public function setMapping(array $mapping): void {
+ // We do not trigger a deprecation error as this method is needed for
+ // calling the deprecated hook_file_mimetype_mapping_alter() in
+ // LegacyMimeTypeMapLoadedSubscriber::onMimeTypeMapLoaded().
+ $this->mapping = $mapping;
+ }
+
+}
diff --git a/core/lib/Drupal/Core/File/MimeType/MimeTypeMapFactory.php b/core/lib/Drupal/Core/File/MimeType/MimeTypeMapFactory.php
new file mode 100644
index 00000000000..573a33fec5c
--- /dev/null
+++ b/core/lib/Drupal/Core/File/MimeType/MimeTypeMapFactory.php
@@ -0,0 +1,35 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\Core\File\MimeType;
+
+use Drupal\Core\File\Event\MimeTypeMapLoadedEvent;
+use Psr\EventDispatcher\EventDispatcherInterface;
+
+/**
+ * Factory for creating the MIME type map.
+ */
+class MimeTypeMapFactory {
+
+ public function __construct(
+ protected readonly EventDispatcherInterface $eventDispatcher,
+ ) {}
+
+ /**
+ * Creates an instance of the MIME type map.
+ *
+ * @return \Drupal\Core\File\MimeType\MimeTypeMapInterface
+ * The MIME type map.
+ */
+ public function create(): MimeTypeMapInterface {
+ $map = $this->doCreateMap();
+ $this->eventDispatcher->dispatch(new MimeTypeMapLoadedEvent($map));
+ return $map;
+ }
+
+ protected function doCreateMap(): MimeTypeMapInterface {
+ return new MimeTypeMap();
+ }
+
+}
diff --git a/core/lib/Drupal/Core/File/MimeType/MimeTypeMapInterface.php b/core/lib/Drupal/Core/File/MimeType/MimeTypeMapInterface.php
new file mode 100644
index 00000000000..5483c67d8a1
--- /dev/null
+++ b/core/lib/Drupal/Core/File/MimeType/MimeTypeMapInterface.php
@@ -0,0 +1,108 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\Core\File\MimeType;
+
+/**
+ * Provides an interface for MIME type to file extension mapping.
+ */
+interface MimeTypeMapInterface {
+
+ /**
+ * Adds a mapping between a MIME type and an extension.
+ *
+ * @param string $mimetype
+ * The MIME type the passed extension should map.
+ * @param string $extension
+ * The extension(s) that should map to the passed MIME type.
+ *
+ * @return $this
+ */
+ public function addMapping(string $mimetype, string $extension): self;
+
+ /**
+ * Removes the mapping between a MIME type and an extension.
+ *
+ * @param string $mimetype
+ * The MIME type to be removed from the mapping.
+ * @param string $extension
+ * The extension to be removed from the mapping.
+ *
+ * @return bool
+ * TRUE if the extension was present, FALSE otherwise.
+ */
+ public function removeMapping(string $mimetype, string $extension): bool;
+
+ /**
+ * Removes a MIME type and all its mapped extensions from the mapping.
+ *
+ * @param string $mimetype
+ * The MIME type to be removed from the mapping.
+ *
+ * @return bool
+ * TRUE if the MIME type was present, FALSE otherwise.
+ */
+ public function removeMimeType(string $mimetype): bool;
+
+ /**
+ * Returns known MIME types.
+ *
+ * @return string[]
+ * An array of MIME types.
+ */
+ public function listMimeTypes(): array;
+
+ /**
+ * Returns known file extensions.
+ *
+ * @return string[]
+ * An array of file extensions.
+ */
+ public function listExtensions(): array;
+
+ /**
+ * Determines if a MIME type exists.
+ *
+ * @param string $mimetype
+ * The mime type.
+ *
+ * @return bool
+ * TRUE if the MIME type exists, FALSE otherwise.
+ */
+ public function hasMimeType(string $mimetype): bool;
+
+ /**
+ * Determines if a file extension exists.
+ *
+ * @param string $extension
+ * The file extension.
+ *
+ * @return bool
+ * TRUE if the file extension exists, FALSE otherwise.
+ */
+ public function hasExtension(string $extension): bool;
+
+ /**
+ * Returns the appropriate MIME type for a given file extension.
+ *
+ * @param string $extension
+ * A file extension, without leading dot.
+ *
+ * @return string|null
+ * A matching MIME type, or NULL if no MIME type matches the extension.
+ */
+ public function getMimeTypeForExtension(string $extension): ?string;
+
+ /**
+ * Returns the appropriate extensions for a given MIME type.
+ *
+ * @param string $mimetype
+ * A MIME type.
+ *
+ * @return string[]
+ * An array of file extensions matching the MIME type, without leading dot.
+ */
+ public function getExtensionsForMimeType(string $mimetype): array;
+
+}
diff --git a/core/lib/Drupal/Core/File/file.api.php b/core/lib/Drupal/Core/File/file.api.php
index 04e6e8a43df..0c18aece9cd 100644
--- a/core/lib/Drupal/Core/File/file.api.php
+++ b/core/lib/Drupal/Core/File/file.api.php
@@ -109,16 +109,19 @@ function hook_file_url_alter(&$uri) {
/**
* Alter MIME type mappings used to determine MIME type from a file extension.
*
- * Invoked by
- * \Drupal\Core\File\MimeType\ExtensionMimeTypeGuesser::guessMimeType(). It is
- * used to allow modules to add to or modify the default mapping from
- * \Drupal\Core\File\MimeType\ExtensionMimeTypeGuesser::$defaultMapping.
- *
* @param array $mapping
* An array of mimetypes correlated to the extensions that relate to them.
* The array has 'mimetypes' and 'extensions' elements, each of which is an
* array.
*
+ * @deprecated in drupal:11.2.0 and is removed from drupal:12.0.0. Create a
+ * \Drupal\Core\File\Event\MimeTypeMapLoadedEvent subscriber instead.
+ *
+ * It is used to allow modules to add to or modify the default mapping of
+ * MIME type to file extensions.
+ *
+ * @see https://www.drupal.org/node/3494040
+ * @see \Drupal\Core\File\EventSubscriber\LegacyMimeTypeMapLoadedSubscriber
* @see \Drupal\Core\File\MimeType\ExtensionMimeTypeGuesser::guessMimeType()
* @see \Drupal\Core\File\MimeType\ExtensionMimeTypeGuesser::$defaultMapping
*/
diff --git a/core/lib/Drupal/Core/ProxyClass/File/MimeType/ExtensionMimeTypeGuesser.php b/core/lib/Drupal/Core/ProxyClass/File/MimeType/ExtensionMimeTypeGuesser.php
index 2cb59856a95..a9e4bd55f15 100644
--- a/core/lib/Drupal/Core/ProxyClass/File/MimeType/ExtensionMimeTypeGuesser.php
+++ b/core/lib/Drupal/Core/ProxyClass/File/MimeType/ExtensionMimeTypeGuesser.php
@@ -78,9 +78,9 @@ namespace Drupal\Core\ProxyClass\File\MimeType {
/**
* {@inheritdoc}
*/
- public function setMapping(?array $mapping = NULL)
+ public function setMapping(?array $mapping = NULL): void
{
- return $this->lazyLoadItself()->setMapping($mapping);
+ $this->lazyLoadItself()->setMapping($mapping);
}
/**
diff --git a/core/lib/Drupal/Core/ProxyClass/File/MimeType/MimeTypeGuesser.php b/core/lib/Drupal/Core/ProxyClass/File/MimeType/MimeTypeGuesser.php
index 0a259a396c0..5480214fc1e 100644
--- a/core/lib/Drupal/Core/ProxyClass/File/MimeType/MimeTypeGuesser.php
+++ b/core/lib/Drupal/Core/ProxyClass/File/MimeType/MimeTypeGuesser.php
@@ -91,6 +91,14 @@ namespace Drupal\Core\ProxyClass\File\MimeType {
return $this->lazyLoadItself()->isGuesserSupported();
}
+ /**
+ * {@inheritdoc}
+ */
+ public static function registerWithSymfonyGuesser(\Symfony\Component\DependencyInjection\ContainerInterface $container): void
+ {
+ \Drupal\Core\File\MimeType\MimeTypeGuesser::registerWithSymfonyGuesser($container);
+ }
+
}
}
diff --git a/core/modules/file/src/Plugin/Field/FieldFormatter/FileMediaFormatterBase.php b/core/modules/file/src/Plugin/Field/FieldFormatter/FileMediaFormatterBase.php
index 7b749cfbce4..c00f4aeb397 100644
--- a/core/modules/file/src/Plugin/Field/FieldFormatter/FileMediaFormatterBase.php
+++ b/core/modules/file/src/Plugin/Field/FieldFormatter/FileMediaFormatterBase.php
@@ -6,6 +6,7 @@ use Drupal\Core\Cache\Cache;
use Drupal\Core\Field\EntityReferenceFieldItemListInterface;
use Drupal\Core\Field\FieldDefinitionInterface;
use Drupal\Core\Field\FieldItemListInterface;
+use Drupal\Core\File\MimeType\MimeTypeMapInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Template\Attribute;
@@ -75,12 +76,12 @@ abstract class FileMediaFormatterBase extends FileFormatterBase implements FileM
if (!parent::isApplicable($field_definition)) {
return FALSE;
}
- /** @var \Symfony\Component\Mime\MimeTypeGuesserInterface $extension_mime_type_guesser */
- $extension_mime_type_guesser = \Drupal::service('file.mime_type.guesser.extension');
+ /** @var \Drupal\Core\File\MimeType\MimeTypeMapInterface $mime_type_map */
+ $mime_type_map = \Drupal::service(MimeTypeMapInterface::class);
$extension_list = array_filter(preg_split('/\s+/', $field_definition->getSetting('file_extensions')));
foreach ($extension_list as $extension) {
- $mime_type = $extension_mime_type_guesser->guessMimeType('fakedFile.' . $extension);
+ $mime_type = $mime_type_map->getMimeTypeForExtension($extension);
if ($mime_type !== NULL && static::mimeTypeApplies($mime_type)) {
return TRUE;
}
diff --git a/core/modules/file/tests/file_deprecated_test/file_deprecated_test.info.yml b/core/modules/file/tests/file_deprecated_test/file_deprecated_test.info.yml
new file mode 100644
index 00000000000..50907a4f62e
--- /dev/null
+++ b/core/modules/file/tests/file_deprecated_test/file_deprecated_test.info.yml
@@ -0,0 +1,4 @@
+name: 'File deprecation test'
+type: module
+description: 'Support module for testing deprecated file features.'
+package: Testing
diff --git a/core/modules/file/tests/file_deprecated_test/file_deprecated_test.module b/core/modules/file/tests/file_deprecated_test/file_deprecated_test.module
new file mode 100644
index 00000000000..625f5ee9dbb
--- /dev/null
+++ b/core/modules/file/tests/file_deprecated_test/file_deprecated_test.module
@@ -0,0 +1,35 @@
+<?php
+
+/**
+ * @file
+ * Support module for testing deprecated file features.
+ */
+
+declare(strict_types=1);
+
+// cspell:ignore garply tarz
+
+/**
+ * Implements hook_file_mimetype_mapping_alter().
+ *
+ * @deprecated in drupal:11.2.0 and is removed from drupal:12.0.0. Kept only for BC test coverage, see \Drupal\KernelTests\Core\File\MimeTypeLegacyTest.
+ *
+ * @see https://www.drupal.org/node/3494040
+ */
+function file_deprecated_test_file_mimetype_mapping_alter(&$mapping): void {
+ // Add new mappings.
+ $mapping['mimetypes']['file_test_mimetype_1'] = 'made_up/file_test_1';
+ $mapping['mimetypes']['file_test_mimetype_2'] = 'made_up/file_test_2';
+ $mapping['mimetypes']['file_test_mimetype_3'] = 'made_up/doc';
+ $mapping['mimetypes']['application-x-compress'] = 'application/x-compress';
+ $mapping['mimetypes']['application-x-tarz'] = 'application/x-tarz';
+ $mapping['mimetypes']['application-x-garply-waldo'] = 'application/x-garply-waldo';
+ $mapping['extensions']['file_test_1'] = 'file_test_mimetype_1';
+ $mapping['extensions']['file_test_2'] = 'file_test_mimetype_2';
+ $mapping['extensions']['file_test_3'] = 'file_test_mimetype_2';
+ $mapping['extensions']['z'] = 'application-x-compress';
+ $mapping['extensions']['tar.z'] = 'application-x-tarz';
+ $mapping['extensions']['garply.waldo'] = 'application-x-garply-waldo';
+ // Override existing mapping.
+ $mapping['extensions']['doc'] = 'file_test_mimetype_3';
+}
diff --git a/core/modules/file/tests/file_test/file_test.services.yml b/core/modules/file/tests/file_test/file_test.services.yml
index e70db1ebd65..095f9e42c0c 100644
--- a/core/modules/file/tests/file_test/file_test.services.yml
+++ b/core/modules/file/tests/file_test/file_test.services.yml
@@ -20,3 +20,6 @@ services:
tags:
- { name: stream_wrapper, scheme: dummy1 }
- { name: stream_wrapper, scheme: dummy2 }
+ Drupal\file_test\EventSubscriber\DummyMimeTypeMapLoadedSubscriber:
+ autowire: true
+ public: false
diff --git a/core/modules/file/tests/file_test/src/EventSubscriber/DummyMimeTypeMapLoadedSubscriber.php b/core/modules/file/tests/file_test/src/EventSubscriber/DummyMimeTypeMapLoadedSubscriber.php
new file mode 100644
index 00000000000..9f9e5e98438
--- /dev/null
+++ b/core/modules/file/tests/file_test/src/EventSubscriber/DummyMimeTypeMapLoadedSubscriber.php
@@ -0,0 +1,41 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\file_test\EventSubscriber;
+
+use Drupal\Core\File\Event\MimeTypeMapLoadedEvent;
+use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+
+// cspell:ignore garply tarz
+
+/**
+ * Modifies the MIME type map by adding dummy mappings.
+ */
+class DummyMimeTypeMapLoadedSubscriber implements EventSubscriberInterface {
+
+ /**
+ * {@inheritdoc}
+ */
+ public function onMimeTypeMapLoaded(MimeTypeMapLoadedEvent $event): void {
+ // Add new mappings.
+ $event->map->addMapping('made_up/file_test_1', 'file_test_1');
+ $event->map->addMapping('made_up/file_test_2', 'file_test_2');
+ $event->map->addMapping('made_up/file_test_2', 'file_test_3');
+ $event->map->addMapping('application/x-compress', 'z');
+ $event->map->addMapping('application/x-tarz', 'tar.z');
+ $event->map->addMapping('application/x-garply-waldo', 'garply.waldo');
+ // Override existing mapping.
+ $event->map->addMapping('made_up/doc', 'doc');
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public static function getSubscribedEvents(): array {
+ return [
+ MimeTypeMapLoadedEvent::class => 'onMimeTypeMapLoaded',
+ ];
+ }
+
+}
diff --git a/core/modules/file/tests/file_test/src/Hook/FileTestHooks.php b/core/modules/file/tests/file_test/src/Hook/FileTestHooks.php
index c58c2eba4fd..f23949b6b95 100644
--- a/core/modules/file/tests/file_test/src/Hook/FileTestHooks.php
+++ b/core/modules/file/tests/file_test/src/Hook/FileTestHooks.php
@@ -4,10 +4,10 @@ declare(strict_types=1);
namespace Drupal\file_test\Hook;
+use Drupal\Core\Hook\Attribute\Hook;
use Drupal\file\Entity\File;
use Drupal\file_test\FileTestCdn;
use Drupal\file_test\FileTestHelper;
-use Drupal\Core\Hook\Attribute\Hook;
// cspell:ignore tarz
// cspell:ignore garply
@@ -167,28 +167,6 @@ class FileTestHooks {
}
/**
- * Implements hook_file_mimetype_mapping_alter().
- */
- #[Hook('file_mimetype_mapping_alter')]
- public function fileMimetypeMappingAlter(&$mapping): void {
- // Add new mappings.
- $mapping['mimetypes']['file_test_mimetype_1'] = 'made_up/file_test_1';
- $mapping['mimetypes']['file_test_mimetype_2'] = 'made_up/file_test_2';
- $mapping['mimetypes']['file_test_mimetype_3'] = 'made_up/doc';
- $mapping['mimetypes']['application-x-compress'] = 'application/x-compress';
- $mapping['mimetypes']['application-x-tarz'] = 'application/x-tarz';
- $mapping['mimetypes']['application-x-garply-waldo'] = 'application/x-garply-waldo';
- $mapping['extensions']['file_test_1'] = 'file_test_mimetype_1';
- $mapping['extensions']['file_test_2'] = 'file_test_mimetype_2';
- $mapping['extensions']['file_test_3'] = 'file_test_mimetype_2';
- $mapping['extensions']['z'] = 'application-x-compress';
- $mapping['extensions']['tar.z'] = 'application-x-tarz';
- $mapping['extensions']['garply.waldo'] = 'application-x-garply-waldo';
- // Override existing mapping.
- $mapping['extensions']['doc'] = 'file_test_mimetype_3';
- }
-
- /**
* Implements hook_entity_type_alter().
*/
#[Hook('entity_type_alter')]
diff --git a/core/modules/responsive_image/responsive_image.module b/core/modules/responsive_image/responsive_image.module
index 90b1fb26b01..9fe3dc72b50 100644
--- a/core/modules/responsive_image/responsive_image.module
+++ b/core/modules/responsive_image/responsive_image.module
@@ -4,6 +4,7 @@
* @file
*/
+use Drupal\Core\File\MimeType\MimeTypeMapInterface;
use Drupal\Core\Template\Attribute;
use Drupal\Core\Logger\RfcLogLevel;
use Drupal\image\Entity\ImageStyle;
@@ -435,22 +436,25 @@ function responsive_image_get_image_dimensions($image_style_name, array $dimensi
* @param string $extension
* The original extension of the image (without the leading dot).
*
- * @return string
- * The MIME type of the image after the image style is applied.
+ * @return string|null
+ * The MIME type of the image after the image style is applied, or NULL if not
+ * found.
*/
function responsive_image_get_mime_type($image_style_name, $extension) {
- if ($image_style_name == ResponsiveImageStyleInterface::EMPTY_IMAGE) {
- return 'image/gif';
- }
- // The MIME type guesser needs a full path, not just an extension, but the
- // file doesn't have to exist.
- if ($image_style_name === ResponsiveImageStyleInterface::ORIGINAL_IMAGE) {
- $fake_path = 'responsive_image.' . $extension;
- }
- else {
- $fake_path = 'responsive_image.' . ImageStyle::load($image_style_name)->getDerivativeExtension($extension);
- }
- return \Drupal::service('file.mime_type.guesser.extension')->guessMimeType($fake_path);
+ $extension = match ($image_style_name) {
+ // The file extension for the empty image is 'gif'.
+ ResponsiveImageStyleInterface::EMPTY_IMAGE => 'gif',
+ // If using the original image, the file extension is just passed on to the
+ // MIME type mapper.
+ ResponsiveImageStyleInterface::ORIGINAL_IMAGE => $extension,
+ // In all other cases, get the file extension of the derivative image from
+ // the image style configuration.
+ default => ImageStyle::load($image_style_name)->getDerivativeExtension($extension),
+ };
+
+ return \Drupal::service(MimeTypeMapInterface::class)->getMimeTypeForExtension(
+ $extension
+ );
}
/**
diff --git a/core/tests/Drupal/KernelTests/Core/File/ExtensionMimeTypeGuesserDeprecationTest.php b/core/tests/Drupal/KernelTests/Core/File/ExtensionMimeTypeGuesserDeprecationTest.php
new file mode 100644
index 00000000000..e00864d2e45
--- /dev/null
+++ b/core/tests/Drupal/KernelTests/Core/File/ExtensionMimeTypeGuesserDeprecationTest.php
@@ -0,0 +1,42 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\KernelTests\Core\File;
+
+use Drupal\Core\File\MimeType\ExtensionMimeTypeGuesser;
+use Drupal\KernelTests\KernelTestBase;
+
+/**
+ * Tests that deprecation messages are raised for deprecations.
+ *
+ * @covers \Drupal\Core\File\MimeType\ExtensionMimeTypeGuesser
+ * @group file
+ * @group legacy
+ *
+ * @todo Remove this class once deprecations are removed.
+ */
+class ExtensionMimeTypeGuesserDeprecationTest extends KernelTestBase {
+
+ /**
+ * {@inheritdoc}
+ */
+ protected static $modules = ['system', 'file_deprecated_test'];
+
+ /**
+ * Tests that deprecations are raised for missing constructor arguments.
+ *
+ * @covers \Drupal\Core\File\MimeType\ExtensionMimeTypeGuesser::__construct
+ * @group legacy
+ */
+ public function testConstructorDeprecation(): void {
+ $this->expectDeprecation(
+ 'Calling Drupal\Core\File\MimeType\ExtensionMimeTypeGuesser::__construct() with the $map argument as an instance of \Drupal\Core\Extension\ModuleHandlerInterface is deprecated in drupal:11.2.0 and an instance of \Drupal\Core\File\MimeType\MimeTypeMapInterface is required in drupal:12.0.0. See https://www.drupal.org/node/3494040'
+ );
+
+ new ExtensionMimeTypeGuesser(
+ \Drupal::service('module_handler'),
+ );
+ }
+
+}
diff --git a/core/tests/Drupal/KernelTests/Core/File/MimeType/ExtensionMimeTypeGuesserTest.php b/core/tests/Drupal/KernelTests/Core/File/MimeType/ExtensionMimeTypeGuesserTest.php
new file mode 100644
index 00000000000..9dbd3ef8e9a
--- /dev/null
+++ b/core/tests/Drupal/KernelTests/Core/File/MimeType/ExtensionMimeTypeGuesserTest.php
@@ -0,0 +1,133 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\KernelTests\Core\File\MimeType;
+
+use Drupal\KernelTests\KernelTestBase;
+
+// cspell:ignore garply tarz
+
+/**
+ * Tests filename mimetype detection.
+ *
+ * @group File
+ * @coversDefaultClass \Drupal\Core\File\MimeType\ExtensionMimeTypeGuesser
+ */
+class ExtensionMimeTypeGuesserTest extends KernelTestBase {
+
+ /**
+ * {@inheritdoc}
+ */
+ protected static $modules = ['file_deprecated_test', 'file_test'];
+
+ /**
+ * Tests mapping of mimetypes from filenames.
+ *
+ * @covers ::guessMimeType
+ * @group legacy
+ */
+ public function testGuessMimeType(): void {
+ $prefixes = ['public://', 'private://', 'temporary://', 'dummy-remote://'];
+
+ $test_case = [
+ 'test.jar' => 'application/java-archive',
+ 'test.jpeg' => 'image/jpeg',
+ 'test.JPEG' => 'image/jpeg',
+ 'test.jpg' => 'image/jpeg',
+ 'test.jar.jpg' => 'image/jpeg',
+ 'test.jpg.jar' => 'application/java-archive',
+ 'test.pcf.Z' => 'application/x-font',
+ 'test.garply.waldo' => 'application/x-garply-waldo',
+ 'pcf.z' => 'application/x-compress',
+ 'jar' => NULL,
+ 'garply.waldo' => NULL,
+ 'some.junk' => NULL,
+ // Mime type added by file_test_mimetype_alter()
+ 'foo.file_test_1' => 'made_up/file_test_1',
+ 'foo.file_test_2' => 'made_up/file_test_2',
+ 'foo.doc' => 'made_up/doc',
+ 'test.ogg' => 'audio/ogg',
+ 'foobar.z' => 'application/x-compress',
+ 'foobar.tar' => 'application/x-tar',
+ 'foobar.tar.z' => 'application/x-tarz',
+ 'foobar.0.zip' => 'application/zip',
+ 'foobar..zip' => 'application/zip',
+ ];
+
+ $this->expectDeprecation(
+ 'The deprecated alter hook hook_file_mimetype_mapping_alter() is implemented in these locations: file_deprecated_test_file_mimetype_mapping_alter. This hook is deprecated in drupal:11.2.0 and is removed from drupal:12.0.0. Implement a \Drupal\Core\File\Event\MimeTypeMapLoadedEvent listener instead. See https://www.drupal.org/node/3494040'
+ );
+
+ /** @var \Drupal\Core\File\MimeType\ExtensionMimeTypeGuesser $guesser */
+ $guesser = \Drupal::service('file.mime_type.guesser.extension');
+ // Test using default mappings.
+ foreach ($test_case as $input => $expected) {
+ // Test stream [URI].
+ foreach ($prefixes as $prefix) {
+ $output = $guesser->guessMimeType($prefix . $input);
+ $this->assertSame($expected, $output);
+ }
+
+ // Test normal path equivalent.
+ $output = $guesser->guessMimeType($input);
+ $this->assertSame($expected, $output);
+ }
+ }
+
+ /**
+ * Tests mapping of mimetypes from filenames.
+ *
+ * @group legacy
+ * @covers ::guessMimeType
+ * @covers ::setMapping
+ */
+ public function testFileMimeTypeDetectionCustomMapping(): void {
+ /** @var \Drupal\Core\File\MimeType\ExtensionMimeTypeGuesser $extension_guesser */
+ $extension_guesser = \Drupal::service('file.mime_type.guesser.extension');
+
+ // Pass in a custom mapping.
+ $mapping = [
+ 'mimetypes' => [
+ 0 => 'application/java-archive',
+ 1 => 'image/jpeg',
+ ],
+ 'extensions' => [
+ 'jar' => 0,
+ 'jpg' => 1,
+ ],
+ ];
+
+ $this->expectDeprecation(
+ 'Drupal\Core\File\MimeType\ExtensionMimeTypeGuesser::setMapping() is deprecated in drupal:11.2.0 and is removed from drupal:12.0.0. Use \Drupal\Core\File\MimeType\MimeTypeMapInterface::addMapping() instead or define your own MimeTypeMapInterface implementation. See https://www.drupal.org/node/3494040'
+ );
+ $extension_guesser->setMapping($mapping);
+
+ $test_case = [
+ 'test.jar' => 'application/java-archive',
+ 'test.jpeg' => 'image/jpeg',
+ 'test.jpg' => 'image/jpeg',
+ 'test.jar.jpg' => 'image/jpeg',
+ 'test.jpg.jar' => 'application/java-archive',
+ 'test.pcf.z' => 'application/x-font',
+ 'test.garply.waldo' => 'application/x-garply-waldo',
+ 'pcf.z' => 'application/x-compress',
+ 'jar' => NULL,
+ 'garply.waldo' => NULL,
+ 'some.junk' => NULL,
+ 'foo.file_test_1' => 'made_up/file_test_1',
+ 'foo.file_test_2' => 'made_up/file_test_2',
+ 'foo.doc' => 'made_up/doc',
+ 'test.ogg' => 'audio/ogg',
+ 'foobar.z' => 'application/x-compress',
+ 'foobar.tar' => 'application/x-tar',
+ 'foobar.tar.z' => 'application/x-tarz',
+ ];
+
+ foreach ($test_case as $input => $expected) {
+ $output = $extension_guesser->guessMimeType($input);
+ $this->assertSame($expected, $output, 'Failed for extension ' . $input);
+ }
+ }
+
+}
diff --git a/core/tests/Drupal/KernelTests/Core/File/MimeType/LegacyMimeTypeTest.php b/core/tests/Drupal/KernelTests/Core/File/MimeType/LegacyMimeTypeTest.php
new file mode 100644
index 00000000000..ae713fc27ae
--- /dev/null
+++ b/core/tests/Drupal/KernelTests/Core/File/MimeType/LegacyMimeTypeTest.php
@@ -0,0 +1,38 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\KernelTests\Core\File\MimeType;
+
+use Drupal\Core\File\MimeType\MimeTypeMapInterface;
+use Drupal\KernelTests\KernelTestBase;
+
+/**
+ * Tests deprecated file features.
+ *
+ * @group legacy
+ * @group File
+ */
+class LegacyMimeTypeTest extends KernelTestBase {
+
+ /**
+ * {@inheritdoc}
+ */
+ protected static $modules = ['system', 'file_deprecated_test'];
+
+ /**
+ * Tests deprecation of hook_file_mimetype_mapping_alter.
+ *
+ * @group legacy
+ */
+ public function testHookFileMimetypeMappingAlter(): void {
+ $this->expectDeprecation(
+ 'The deprecated alter hook hook_file_mimetype_mapping_alter() is implemented in these locations: file_deprecated_test_file_mimetype_mapping_alter. This hook is deprecated in drupal:11.2.0 and is removed from drupal:12.0.0. Implement a \Drupal\Core\File\Event\MimeTypeMapLoadedEvent listener instead. See https://www.drupal.org/node/3494040'
+ );
+
+ $map = \Drupal::service(MimeTypeMapInterface::class);
+ $this->assertEquals(['file_test_2', 'file_test_3'],
+ $map->getExtensionsForMimeType('made_up/file_test_2'));
+ }
+
+}
diff --git a/core/tests/Drupal/KernelTests/Core/File/MimeTypeTest.php b/core/tests/Drupal/KernelTests/Core/File/MimeTypeTest.php
deleted file mode 100644
index 653902e9f58..00000000000
--- a/core/tests/Drupal/KernelTests/Core/File/MimeTypeTest.php
+++ /dev/null
@@ -1,106 +0,0 @@
-<?php
-
-declare(strict_types=1);
-
-namespace Drupal\KernelTests\Core\File;
-
-// cspell:ignore garply tarz
-
-/**
- * Tests filename mimetype detection.
- *
- * @group File
- */
-class MimeTypeTest extends FileTestBase {
-
- /**
- * {@inheritdoc}
- */
- protected static $modules = ['file_test'];
-
- /**
- * Tests mapping of mimetypes from filenames.
- */
- public function testFileMimeTypeDetection(): void {
- $prefixes = ['public://', 'private://', 'temporary://', 'dummy-remote://'];
-
- $test_case = [
- 'test.jar' => 'application/java-archive',
- 'test.jpeg' => 'image/jpeg',
- 'test.JPEG' => 'image/jpeg',
- 'test.jpg' => 'image/jpeg',
- 'test.jar.jpg' => 'image/jpeg',
- 'test.jpg.jar' => 'application/java-archive',
- 'test.pcf.Z' => 'application/x-font',
- 'test.garply.waldo' => 'application/x-garply-waldo',
- 'pcf.z' => 'application/x-compress',
- 'jar' => 'application/octet-stream',
- 'garply.waldo' => 'application/octet-stream',
- 'some.junk' => 'application/octet-stream',
- 'foo.file_test_1' => 'made_up/file_test_1',
- 'foo.file_test_2' => 'made_up/file_test_2',
- 'foo.doc' => 'made_up/doc',
- 'test.ogg' => 'audio/ogg',
- 'foobar.z' => 'application/x-compress',
- 'foobar.tar' => 'application/x-tar',
- 'foobar.tar.z' => 'application/x-tarz',
- 'foobar.0.zip' => 'application/zip',
- 'foobar..zip' => 'application/zip',
- ];
-
- $guesser = $this->container->get('file.mime_type.guesser');
- // Test using default mappings.
- foreach ($test_case as $input => $expected) {
- // Test stream [URI].
- foreach ($prefixes as $prefix) {
- $output = $guesser->guessMimeType($prefix . $input);
- $this->assertSame($expected, $output);
- }
-
- // Test normal path equivalent
- $output = $guesser->guessMimeType($input);
- $this->assertSame($expected, $output);
- }
-
- // Now test the extension guesser by passing in a custom mapping.
- $mapping = [
- 'mimetypes' => [
- 0 => 'application/java-archive',
- 1 => 'image/jpeg',
- ],
- 'extensions' => [
- 'jar' => 0,
- 'jpg' => 1,
- ],
- ];
-
- $test_case = [
- 'test.jar' => 'application/java-archive',
- 'test.jpeg' => NULL,
- 'test.jpg' => 'image/jpeg',
- 'test.jar.jpg' => 'image/jpeg',
- 'test.jpg.jar' => 'application/java-archive',
- 'test.pcf.z' => NULL,
- 'test.garply.waldo' => NULL,
- 'pcf.z' => NULL,
- 'jar' => NULL,
- 'garply.waldo' => NULL,
- 'some.junk' => NULL,
- 'foo.file_test_1' => NULL,
- 'foo.file_test_2' => NULL,
- 'foo.doc' => NULL,
- 'test.ogg' => NULL,
- 'foobar.z' => NULL,
- 'foobar.tar' => NULL,
- 'foobar.tar.z' => NULL,
- ];
- $extension_guesser = $this->container->get('file.mime_type.guesser.extension');
- $extension_guesser->setMapping($mapping);
-
- foreach ($test_case as $input => $expected) {
- $output = $extension_guesser->guessMimeType($input);
- $this->assertSame($expected, $output);
- }
- }
-
-}
diff --git a/core/tests/Drupal/Tests/Core/File/MimeType/MimeTypeMapTest.php b/core/tests/Drupal/Tests/Core/File/MimeType/MimeTypeMapTest.php
new file mode 100644
index 00000000000..c0a7995b15c
--- /dev/null
+++ b/core/tests/Drupal/Tests/Core/File/MimeType/MimeTypeMapTest.php
@@ -0,0 +1,116 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\Tests\Core\File\MimeType;
+
+use Drupal\Core\File\MimeType\MimeTypeMap;
+use Drupal\Tests\UnitTestCase;
+
+/**
+ * Tests the MIME type mapper to extension.
+ *
+ * @coversDefaultClass \Drupal\Core\File\MimeType\MimeTypeMap
+ *
+ * @group File
+ */
+class MimeTypeMapTest extends UnitTestCase {
+
+ /**
+ * The default MIME type map under test.
+ */
+ protected MimeTypeMap $map;
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function setUp(): void {
+ parent::setUp();
+ $this->map = new MimeTypeMap();
+ }
+
+ /**
+ * @covers ::addMapping
+ */
+ public function testAddMapping(): void {
+ $this->map->addMapping('image/gif', 'gif');
+ $this->assertEquals(
+ 'image/gif',
+ $this->map->getMimeTypeForExtension('gif')
+ );
+
+ $this->map->addMapping('image/jpeg', 'jpeg');
+ $this->assertEquals(
+ 'image/jpeg',
+ $this->map->getMimeTypeForExtension('jpeg')
+ );
+ }
+
+ /**
+ * @covers ::removeMapping
+ */
+ public function testRemoveMapping(): void {
+ $this->assertTrue($this->map->removeMapping('image/jpeg', 'jpg'));
+ $this->assertNull($this->map->getMimeTypeForExtension('jpg'));
+ $this->assertFalse($this->map->removeMapping('bar', 'foo'));
+ }
+
+ /**
+ * @covers ::removeMimeType
+ */
+ public function testRemoveMimeType(): void {
+ $this->assertTrue($this->map->removeMimeType('image/jpeg'));
+ $this->assertNull($this->map->getMimeTypeForExtension('jpg'));
+ $this->assertFalse($this->map->removeMimeType('foo/bar'));
+ }
+
+ /**
+ * @covers ::listMimeTypes
+ */
+ public function testListMimeTypes(): void {
+ $mimeTypes = $this->map->listMimeTypes();
+ $this->assertContains('application/java-archive', $mimeTypes);
+ $this->assertContains('image/jpeg', $mimeTypes);
+ }
+
+ /**
+ * @covers ::hasMimeType
+ */
+ public function testHasMimeType(): void {
+ $this->assertTrue($this->map->hasMimeType('image/jpeg'));
+ $this->assertFalse($this->map->hasMimeType('foo/bar'));
+ }
+
+ /**
+ * @covers ::getMimeTypeForExtension
+ */
+ public function testGetMimeTypeForExtension(): void {
+ $this->assertSame('image/jpeg', $this->map->getMimeTypeForExtension('jpe'));
+ }
+
+ /**
+ * @covers ::getExtensionsForMimeType
+ */
+ public function testGetExtensionsForMimeType(): void {
+ $this->assertEquals(['jpe', 'jpeg', 'jpg'],
+ $this->map->getExtensionsForMimeType('image/jpeg'));
+ }
+
+ /**
+ * @covers ::listExtensions
+ */
+ public function testListExtension(): void {
+ $extensions = $this->map->listExtensions();
+ $this->assertContains('jar', $extensions);
+ $this->assertContains('jpg', $extensions);
+ }
+
+ /**
+ * @covers ::hasExtension
+ */
+ public function testHasExtension(): void {
+ $this->assertTrue($this->map->hasExtension('jpg'));
+ $this->assertFalse($this->map->hasExtension('foo'));
+ }
+
+}