diff --git a/core/lib/Drupal/Core/DrupalKernel.php b/core/lib/Drupal/Core/DrupalKernel.php index f994cf0f0e8675b22f68ce4da680b196358d378f..c0bdf11cb7a72dc24c3b7e6c89839c0ab33f1c67 100644 --- a/core/lib/Drupal/Core/DrupalKernel.php +++ b/core/lib/Drupal/Core/DrupalKernel.php @@ -17,6 +17,7 @@ use Drupal\Core\DependencyInjection\YamlFileLoader; use Drupal\Core\Extension\Extension; use Drupal\Core\Extension\ExtensionDiscovery; +use Drupal\Core\File\MimeType\MimeTypeGuesser; use Drupal\Core\Http\TrustedHostsRequestFactory; use Drupal\Core\Installer\InstallerKernel; use Drupal\Core\Installer\InstallerRedirectTrait; @@ -596,6 +597,9 @@ public function preHandle(Request $request) { // Set the allowed protocols. UrlHelper::setAllowedProtocols($this->container->getParameter('filter_protocols')); + // Override of Symfony's MIME type guesser singleton. + MimeTypeGuesser::registerWithSymfonyGuesser($this->container); + $this->prepared = TRUE; } diff --git a/core/lib/Drupal/Core/File/MimeType/ExtensionMimeTypeGuesser.php b/core/lib/Drupal/Core/File/MimeType/ExtensionMimeTypeGuesser.php index 56467361b564f4c004d97d83e9de6819e1c72afc..1fb5c4c6d5291c68c6f90941175f178ebd36c99c 100644 --- a/core/lib/Drupal/Core/File/MimeType/ExtensionMimeTypeGuesser.php +++ b/core/lib/Drupal/Core/File/MimeType/ExtensionMimeTypeGuesser.php @@ -153,6 +153,8 @@ class ExtensionMimeTypeGuesser implements MimeTypeGuesserInterface { 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', diff --git a/core/lib/Drupal/Core/File/MimeType/MimeTypeGuesser.php b/core/lib/Drupal/Core/File/MimeType/MimeTypeGuesser.php index e3bc1211543b0a9c663491a5f55f822e41d4470e..8cd65463cbc912c450f339ed37d138096af79e81 100644 --- a/core/lib/Drupal/Core/File/MimeType/MimeTypeGuesser.php +++ b/core/lib/Drupal/Core/File/MimeType/MimeTypeGuesser.php @@ -3,7 +3,9 @@ namespace Drupal\Core\File\MimeType; use Drupal\Core\StreamWrapper\StreamWrapperManagerInterface; +use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\Mime\MimeTypeGuesserInterface; +use Symfony\Component\Mime\MimeTypes; /** * Defines a MIME type guesser that also supports stream wrapper paths. @@ -111,4 +113,19 @@ protected function sortGuessers() { return array_merge(...$this->guessers); } + /** + * A helper function to register with Symfony's singleton MIME type guesser. + * + * Symfony's default mimetype guessers have dependencies on PHP's fileinfo + * extension or being able to run the system command file. Drupal's guesser + * does not have these dependencies. + * + * @see \Symfony\Component\Mime\MimeTypes + */ + public static function registerWithSymfonyGuesser(ContainerInterface $container) { + $guesser = new MimeTypes(); + $guesser->registerGuesser($container->get('file.mime_type.guesser')); + MimeTypes::setDefault($guesser); + } + } diff --git a/core/modules/image/tests/src/Kernel/SettingsConfigValidationTest.php b/core/modules/image/tests/src/Kernel/SettingsConfigValidationTest.php index 577b5cb5d984b8fb7d53108281e7d2a433cfb248..461a33bfb4148c3bf477cc1dc1ad737f4dead991 100644 --- a/core/modules/image/tests/src/Kernel/SettingsConfigValidationTest.php +++ b/core/modules/image/tests/src/Kernel/SettingsConfigValidationTest.php @@ -23,7 +23,11 @@ class SettingsConfigValidationTest extends KernelTestBase { public function testPreviewImagePathIsValidated(): void { $this->installConfig('image'); - $file = sys_get_temp_dir() . '/fake_image.png'; + // Drupal does not have a hard dependency on the fileinfo extension and + // implements an extension-based mimetype guesser. Therefore, we must use + // an incorrect extension here instead of writing text to a supposed PNG + // file and depending on a check of the file contents. + $file = sys_get_temp_dir() . '/fake_image.png.txt'; file_put_contents($file, 'Not an image!'); $this->expectException(SchemaIncompleteException::class); diff --git a/core/modules/system/src/Controller/AssetControllerBase.php b/core/modules/system/src/Controller/AssetControllerBase.php index 197af589cb6d454a5ec4fe701e6870f4a42b5a61..6d4b74c3a46ee85c78c503439c676e31d74cc1af 100644 --- a/core/modules/system/src/Controller/AssetControllerBase.php +++ b/core/modules/system/src/Controller/AssetControllerBase.php @@ -119,9 +119,6 @@ public function deliver(Request $request, string $file_name) { if (file_exists($uri)) { return new BinaryFileResponse($uri, 200, [ 'Cache-control' => static::CACHE_CONTROL, - // @todo: remove the explicit setting of Content-Type once this is - // fixed in https://www.drupal.org/project/drupal/issues/3172550. - 'Content-Type' => $this->contentType, ]); } diff --git a/core/modules/system/tests/modules/binary_file_response_test/binary_file_response_test.info.yml b/core/modules/system/tests/modules/binary_file_response_test/binary_file_response_test.info.yml new file mode 100644 index 0000000000000000000000000000000000000000..e6c50ace4d9d88aaf9f31e70fd92df8dfb17c7c4 --- /dev/null +++ b/core/modules/system/tests/modules/binary_file_response_test/binary_file_response_test.info.yml @@ -0,0 +1,5 @@ +name: 'Binary File Response test' +type: module +description: 'Support module for Batch Binary File Response tests.' +package: Testing +version: VERSION diff --git a/core/modules/system/tests/modules/binary_file_response_test/binary_file_response_test.routing.yml b/core/modules/system/tests/modules/binary_file_response_test/binary_file_response_test.routing.yml new file mode 100644 index 0000000000000000000000000000000000000000..5372588bd23ae9f6f2f0aa89261f7ebad406ea46 --- /dev/null +++ b/core/modules/system/tests/modules/binary_file_response_test/binary_file_response_test.routing.yml @@ -0,0 +1,7 @@ +binary_file_response_test.download: + path: '/binary_file_response_test/download' + defaults: + _controller: '\Drupal\binary_file_response_test\Controller\BinaryFileResponseTestController::download' + _title: 'Redirect' + requirements: + _access: 'TRUE' diff --git a/core/modules/system/tests/modules/binary_file_response_test/src/Controller/BinaryFileResponseTestController.php b/core/modules/system/tests/modules/binary_file_response_test/src/Controller/BinaryFileResponseTestController.php new file mode 100644 index 0000000000000000000000000000000000000000..c27d0db083be22cdd20fd5d5e37b60e8416c9ab2 --- /dev/null +++ b/core/modules/system/tests/modules/binary_file_response_test/src/Controller/BinaryFileResponseTestController.php @@ -0,0 +1,38 @@ +<?php + +namespace Drupal\binary_file_response_test\Controller; + +use Symfony\Component\HttpFoundation\BinaryFileResponse; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; + +/** + * Controller routines for binary file response tests. + */ +class BinaryFileResponseTestController { + + /** + * Download the file set in the relative_file_url query parameter. + * + * @return \Symfony\Component\HttpFoundation\BinaryFileResponse + * The response wrapping the file content. + */ + public function download(Request $request) { + if (!$request->query->has('relative_file_url')) { + throw new BadRequestHttpException(); + } + + $relative_file_url = $request->query->get('relative_file_url'); + + // A relative URL for a file contains '%20' instead of spaces. A relative + // file path contains spaces. + $relative_file_path = rawurldecode($relative_file_url); + + // Ensure the file path does not start with a slash to prevent exploring + // the file system root. + $relative_file_path = ltrim($relative_file_path, '/'); + + return new BinaryFileResponse($relative_file_path); + } + +} diff --git a/core/tests/Drupal/KernelTests/Core/Http/BinaryFileResponseTest.php b/core/tests/Drupal/KernelTests/Core/Http/BinaryFileResponseTest.php new file mode 100644 index 0000000000000000000000000000000000000000..ddf54f3bb71575eb96f07f6532aebcaeb4c6d94e --- /dev/null +++ b/core/tests/Drupal/KernelTests/Core/Http/BinaryFileResponseTest.php @@ -0,0 +1,48 @@ +<?php + +declare(strict_types=1); + +namespace Drupal\KernelTests\Core\Http; + +use Drupal\KernelTests\KernelTestBase; +use Symfony\Component\HttpFoundation\Request; + +/** + * Tests the headers added by BinaryFileResponse. + * + * @group Http + */ +class BinaryFileResponseTest extends KernelTestBase { + + /** + * {@inheritdoc} + */ + protected static $modules = ['binary_file_response_test']; + + /** + * Test the content type generated by Drupal is correct. + * + * @dataProvider providerTestCalculatedContentType + */ + public function testCalculatedContentType($path, $content_type): void { + $query = ['relative_file_url' => $path]; + $request = Request::create('/binary_file_response_test/download', 'GET', $query); + + $response = \Drupal::service('http_kernel')->handle($request); + $response->prepare($request); + + $this->assertSame($content_type, current(explode(';', $response->headers->get('Content-Type')))); + } + + /** + * Data provider of file names and expected content-type values. + */ + public static function providerTestCalculatedContentType(): array { + return [ + ['core/misc/print.css', 'text/css'], + ['core/misc/checkbox.js', 'text/javascript'], + ['core/misc/tree.png', 'image/png'], + ]; + } + +}