Recently I was adding a photo gallery page to a Drupal 9 site. The markup for each photo looked something like this1:
<a href="/sites/default/files/styles/max_1280x1280/public/2021-01/butterfly.jpg?itok=EUoBeFr6"> <img src="/sites/default/files/styles/photo_thumbnail/public/2021-01/butterfly.jpg?itok=PhOG4ZTh"> </a>
When a photo was clicked/tapped, the link was supposed to take you to a larger version of the image. Instead, to my surprise, it gave a “page not found” error.
How do image style URLs work?
First I spent some time trying to understand image style URLs better.
When I upload an image to this Drupal site, the original image is placed in a folder in the document root — e.g. sites/default/files/2021-01/
. (Depending on how your site’s file system is configured, uploaded images could be placed in a different location or on a different server.)
The first part of the URL — e.g. /sites/default/files/styles/photo_thumbnail/public/2021-01/butterfly.jpg
— refers to an image that Drupal generates automatically from the original image (by scaling, cropping, etc.). This image is called an image derivative.
The last part of the URL — e.g. ?itok=PhOG4ZTh
– avoids a denial of service vulnerability.
When are the image derivatives generated?
The answer in the Drupal documentation is: “the first time a particular image is requested in that style”.
Indeed, the first time I visited my photo gallery page, all of the image derivatives referenced from <img>
tags were created in my sites/default/files/styles/photo_thumbnail/public/2021-01
folder. Thus all of the images appeared on the page.
But when I clicked on an image to follow the link, the image derivative referenced from the <a>
tag did not get created.
I had incorrectly assumed that clicking the link would count as the image being “requested in that style”. On further testing, I learned that if the request is coming from an <a>
tag, the URL has to be constructed in a particular format in order to prompt the image derivative to be generated.
What is the format of a URL to generate an image derivative?
At first, I hadn’t realized that I could request the image derivative via a URL. Thinking it would require custom code, I started working on that and came across Drupal core’s ImageStyleDownloadController class. From there, I discovered that the custom functionality I’d started implementing already existed in Drupal core.
core/modules/image/image.routing.yml
defines the following route2:
image.style_private: path: '/system/files/styles/{image_style}/{scheme}' defaults: _controller: '\Drupal\image\Controller\ImageStyleDownloadController::deliver' requirements: _access: 'TRUE'
When a request is made to this route, ImageStyleDownloadController::deliver
is called. In the first lines, this function constructs a URI for the image — e.g. public://2021-01/butterfly.jpg
— by grabbing the first part from the scheme
route parameter and the second part from the request’s file
query parameter.
public function deliver(Request $request, $scheme, ImageStyleInterface $image_style) { $target = $request->query->get('file'); $image_uri = $scheme . '://' . $target;
A little later, the function looks up the expected image token using the image_style
route parameter, then compares it to the itok
query parameter. (If they don’t match, it throws an exception.)
$valid &= hash_equals($image_style->getPathToken($image_uri), $request->query->get(IMAGE_DERIVATIVE_TOKEN, ''));
The function uses the image_style
route parameter again to build a URI for the image derivative.
$derivative_uri = $image_style->buildUri($image_uri);
Then, basically, the function generates the image derivative if it doesn’t already exist.
Based on that code, here are the pieces of the URL we need to build:
- Start with
/system/files/styles
(from the route path). - Add the machine name for the image style as a path component (first routing parameter).
- Add the scheme, either
public
orprivate
, as a path component (second routing parameter). - Add the URL-encoded relative path to the original image file as a query parameter called
file
. - Add the image token as a query parameter called
itok
.
So instead of this:
<a href="/sites/default/files/styles/max_1280x1280/public/2021-01/butterfly.jpg?itok=EUoBeFr6">
We have this:
<a href="/system/files/styles/max_1280x1280/public?file=2021-01%2Fbutterfly.jpg&itok=EUoBeFr6">
Clicking on the link now takes you to the image derivative in the max_1280x1280
image style, first creating the image file if it doesn’t already exist.
Jaymie Strecker is a software developer at Kosada.