ImageStyleDownloadController::deliver() infers source image when generating derivatives with image format conversion

Created on 3 January 2024, 12 months ago

Problem/Motivation

When an image style converts an image to another format, Drupal\image\Entity\ImageStyle::addExtension() adds the converted extension instead of replacing it:

  /**
   * Adds an extension to a path.
   *
   * If this image style changes the extension of the derivative, this method
   * adds the new extension to the given path. This way we avoid filename
   * clashes while still allowing us to find the source image.
   *
   * @param string $path
   *   The path to add the extension to.
   *
   * @return string
   *   The given path if this image style doesn't change its extension, or the
   *   path with the added extension if it does.
   */
  protected function addExtension($path) {
    $original_extension = pathinfo($path, PATHINFO_EXTENSION);
    $extension = $this->getDerivativeExtension($original_extension);
    if ($original_extension !== $extension) {
      $path .= '.' . $extension;
    }
    return $path;
  }

This strategy is designed to avoid namespace conflicts. (See comments #27 and #28 in πŸ“Œ Allow image effects to change the MIME type + extension, add a "convert" image effect Fixed .)

In an instance where an image style converts an image to another format (e.g. png to webp), Drupal\image\Controller\ImageStyleDownloadController::deliver() attempts to infer the source image:

    // 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;
      }
    }

In the end, the ::deliver() method is making a best guess. If image.png.jpeg already exists, ::deliver() will return an incorrect image derivative.

Steps to reproduce

  1. Install a vanilla Drupal site with the 'Standard' profile.
  2. Install Media & Media Library modules.
  3. Navigate to /admin/config/media/media-settings and enable the 'Standalone media URL' setting.
  4. Ensure the 'Default' view mode for the 'Image' media type is configured to render the 'Large' image style (/admin/structure/media/manage/image/display).
  5. Update the 'Large' image style to convert to 'WebP' (/admin/config/media/image-styles/manage/large).
  6. Create two test images:
    • test-image.png (solid red image)
    • test-image.png.webp (solid blue image)
  7. Upload test-image.png to a media entity (/media/1).
  8. Upload test-image.png.web to a media entity (/media/2).
  9. Load /media/1 and observe a blue image.
  10. Load /media/2 and observe a blue image instead of a red image.

When ::deliver() receives an inbound request, all it has is the requested image URL. It has no way to definitively determine the source image, because that isn't tracked anywhere.

Proposed resolution

TBD.

Remaining tasks

TBD.

User interface changes

TBD.

API changes

TBD.

Data model changes

TBD.

Release notes snippet

TBD.

πŸ› Bug report
Status

Active

Version

11.0 πŸ”₯

Component
Image systemΒ  β†’

Last updated 1 day ago

Created by

πŸ‡ΊπŸ‡ΈUnited States Chris Burge

Live updates comments and jobs are added and updated live.
Sign in to follow issues

Comments & Activities

Production build 0.71.5 2024