diff --git a/core/modules/image/image.module b/core/modules/image/image.module index 4203045b5952b4b02e8a3c9aa79e935f3a791acb..0d8bd4b1f265a84c3e2e170787c2343e8aef8f1d 100644 --- a/core/modules/image/image.module +++ b/core/modules/image/image.module @@ -13,6 +13,7 @@ use Drupal\field\FieldConfigInterface; use Drupal\field\FieldStorageConfigInterface; use Drupal\file\FileInterface; +use Drupal\image\Controller\ImageStyleDownloadController; use Drupal\image\Entity\ImageStyle; /** @@ -151,6 +152,18 @@ function image_file_download($uri) { // Check that the file exists and is an image. $image = \Drupal::service('image.factory')->get($uri); if ($image->isValid()) { + // If the image style converted the extension, it has been added to the + // original file, resulting in filenames like image.png.jpeg. So to find + // the actual source image, we remove the extension and check if that + // image exists. + if (!file_exists($original_uri)) { + $converted_original_uri = ImageStyleDownloadController::getUriWithoutConvertedExtension($original_uri); + if ($converted_original_uri !== $original_uri && file_exists($converted_original_uri)) { + // The converted file does exist, use it as the source. + $original_uri = $converted_original_uri; + } + } + // Check the permissions of the original to grant access to this image. $headers = \Drupal::moduleHandler()->invokeAll('file_download', [$original_uri]); // Confirm there's at least one module granting access and none denying access. diff --git a/core/modules/image/src/Controller/ImageStyleDownloadController.php b/core/modules/image/src/Controller/ImageStyleDownloadController.php index 543700115ee00de5c60329108ca064766e3a1c9a..bfc5c985d8dcb3ee1dd004c324f4c39b0af4d9b1 100644 --- a/core/modules/image/src/Controller/ImageStyleDownloadController.php +++ b/core/modules/image/src/Controller/ImageStyleDownloadController.php @@ -173,6 +173,24 @@ public function deliver(Request $request, $scheme, ImageStyleInterface $image_st $headers = []; + // Don't try to generate file if source is missing. + if ($image_uri !== $sample_image_uri && !$this->sourceImageExists($image_uri, $token_is_valid)) { + // If the image style converted the extension, it has been added to the + // original file, resulting in filenames like image.png.jpeg. So to find + // the actual source image, we remove the extension and check if that + // image exists. + $converted_image_uri = static::getUriWithoutConvertedExtension($image_uri); + if ($converted_image_uri !== $image_uri && + $this->sourceImageExists($converted_image_uri, $token_is_valid)) { + // The converted file does exist, use it as the source. + $image_uri = $converted_image_uri; + } + else { + $this->logger->notice('Source image at %source_image_path not found while trying to generate derivative image at %derivative_path.', ['%source_image_path' => $image_uri, '%derivative_path' => $derivative_uri]); + return new Response($this->t('Error generating image, missing source file.'), 404); + } + } + // If not using a public scheme, let other modules provide headers and // control access to the file. if (!$is_public) { @@ -183,28 +201,12 @@ public function deliver(Request $request, $scheme, ImageStyleInterface $image_st } // If it is default sample.png, ignore scheme. + // This value swap must be done after hook_file_download is called since + // the hooks are expecting a URI, not a file path. if ($image_uri === $sample_image_uri) { $image_uri = $target; } - // Don't try to generate file if source is missing. - if (!$this->sourceImageExists($image_uri, $token_is_valid)) { - // If the image style converted the extension, it has been added to the - // original file, resulting in filenames like image.png.jpeg. So to find - // the actual source image, we remove the extension and check if that - // image exists. - $path_info = pathinfo(StreamWrapperManager::getTarget($image_uri)); - $converted_image_uri = sprintf('%s://%s%s%s', $this->streamWrapperManager->getScheme($derivative_uri), $path_info['dirname'], DIRECTORY_SEPARATOR, $path_info['filename']); - if (!$this->sourceImageExists($converted_image_uri, $token_is_valid)) { - $this->logger->notice('Source image at %source_image_path not found while trying to generate derivative image at %derivative_path.', ['%source_image_path' => $image_uri, '%derivative_path' => $derivative_uri]); - return new Response($this->t('Error generating image, missing source file.'), 404); - } - else { - // The converted file does exist, use it as the source. - $image_uri = $converted_image_uri; - } - } - // Don't start generating the image if the derivative already exists or if // generation is in progress in another thread. if (!file_exists($derivative_uri)) { @@ -283,4 +285,31 @@ private function sourceImageExists(string $image_uri, bool $token_is_valid): boo return TRUE; } + /** + * Get the file URI without the extension from any conversion image style. + * + * If the image style converted the image, then an extension has been added + * to the original file, resulting in filenames like image.png.jpeg. + * + * @param string $uri + * The file URI. + * + * @return string + * The file URI without the extension from any conversion image style. + */ + public static function getUriWithoutConvertedExtension(string $uri): string { + $original_uri = $uri; + $path_info = pathinfo(StreamWrapperManager::getTarget($uri)); + // Only convert the URI when the filename still has an extension. + if (!empty($path_info['filename']) && pathinfo($path_info['filename'], PATHINFO_EXTENSION)) { + $original_uri = StreamWrapperManager::getScheme($uri) . '://'; + if (!empty($path_info['dirname']) && $path_info['dirname'] !== '.') { + $original_uri .= $path_info['dirname'] . DIRECTORY_SEPARATOR; + } + $original_uri .= $path_info['filename']; + } + + return $original_uri; + } + } diff --git a/core/modules/image/tests/src/Functional/ImageStylesPathAndUrlTest.php b/core/modules/image/tests/src/Functional/ImageStylesPathAndUrlTest.php index 306684fbe68415838dd1677a8c7a7ae2442d7554..f82e52e39608ba1ea9754a1220d00f6f7d0d901b 100644 --- a/core/modules/image/tests/src/Functional/ImageStylesPathAndUrlTest.php +++ b/core/modules/image/tests/src/Functional/ImageStylesPathAndUrlTest.php @@ -122,6 +122,22 @@ public function testImageStyleUrlExtraSlash() { $this->doImageStyleUrlAndPathTests('public', TRUE, TRUE); } + /** + * Test an image style URL with a private file that also gets converted. + */ + public function testImageStylePrivateWithConversion(): void { + // Add the "convert" image style effect to our style. + $this->style->addImageEffect([ + 'uuid' => '', + 'id' => 'image_convert', + 'weight' => 1, + 'data' => [ + 'extension' => 'jpeg', + ], + ]); + $this->doImageStyleUrlAndPathTests('private'); + } + /** * Tests that an invalid source image returns a 404. */