function AssetControllerBase::deliver
Same name in other branches
- 10 core/modules/system/src/Controller/AssetControllerBase.php \Drupal\system\Controller\AssetControllerBase::deliver()
Generates an aggregate, given a filename.
Parameters
\Symfony\Component\HttpFoundation\Request $request: The request object.
string $file_name: The file to deliver.
Return value
\Symfony\Component\HttpFoundation\BinaryFileResponse|\Symfony\Component\HttpFoundation\Response The transferred file as response.
Throws
\Symfony\Component\HttpKernel\Exception\BadRequestHttpException Thrown when the filename is invalid or an invalid query argument is supplied.
File
-
core/
modules/ system/ src/ Controller/ AssetControllerBase.php, line 115
Class
- AssetControllerBase
- Defines a controller to serve asset aggregates.
Namespace
Drupal\system\ControllerCode
public function deliver(Request $request, string $file_name) {
$uri = 'assets://' . $this->assetType . '/' . $file_name;
// Check to see whether a file matching the $uri already exists, this can
// happen if it was created while this request was in progress.
if (file_exists($uri)) {
return new BinaryFileResponse($uri, 200, [
'Cache-control' => static::CACHE_CONTROL,
]);
}
// First validate that the request is valid enough to produce an asset group
// aggregate. The theme must be passed as a query parameter, since assets
// always depend on the current theme.
if (!$request->query
->has('theme')) {
throw new BadRequestHttpException('The theme must be passed as a query argument');
}
if (!$request->query
->has('delta') || !is_numeric($request->query
->get('delta'))) {
throw new BadRequestHttpException('The numeric delta must be passed as a query argument');
}
if (!$request->query
->has('language')) {
throw new BadRequestHttpException('The language must be passed as a query argument');
}
if (!$request->query
->has('include')) {
throw new BadRequestHttpException('The libraries to include must be passed as a query argument');
}
$file_parts = explode('_', basename($file_name, '.' . $this->fileExtension), 2);
// Ensure the filename is correctly prefixed.
if ($file_parts[0] !== $this->fileExtension) {
throw new BadRequestHttpException('The filename prefix must match the file extension');
}
// The hash is the second segment of the filename.
if (!isset($file_parts[1])) {
throw new BadRequestHttpException('Invalid filename');
}
$received_hash = $file_parts[1];
// Now build the asset groups based on the libraries. It requires the full
// set of asset groups to extract and build the aggregate for the group we
// want, since libraries may be split across different asset groups.
$theme = $request->query
->get('theme');
$active_theme = $this->themeInitialization
->initTheme($theme);
$this->themeManager
->setActiveTheme($active_theme);
$attached_assets = new AttachedAssets();
$include_libraries = explode(',', UrlHelper::uncompressQueryParameter($request->query
->get('include')));
// Check that library names are in the correct format.
$validate = function ($libraries_to_check) {
foreach ($libraries_to_check as $library) {
if (substr_count($library, '/') === 0) {
throw new BadRequestHttpException(sprintf('The "%s" library name must include at least one slash.', $library));
}
}
};
$validate($include_libraries);
$attached_assets->setLibraries($include_libraries);
if ($request->query
->has('exclude')) {
$exclude_libraries = explode(',', UrlHelper::uncompressQueryParameter($request->query
->get('exclude')));
$validate($exclude_libraries);
$attached_assets->setAlreadyLoadedLibraries($exclude_libraries);
}
$groups = $this->getGroups($attached_assets, $request);
$group = $this->getGroup($groups, $request->query
->get('delta'));
// Generate a hash based on the asset group, this uses the same method as
// the collection optimizer does to create the filename, so it should match.
$generated_hash = $this->generateHash($group);
$data = $this->optimizer
->optimizeGroup($group);
$response = new Response($data, 200, [
'Cache-control' => static::CACHE_CONTROL,
'Content-Type' => $this->contentType,
]);
// However, the hash from the library definitions in code may not match the
// hash from the URL. This can be for three reasons:
// 1. Someone has requested an outdated URL, i.e. from a cached page, which
// matches a different version of the code base.
// 2. Someone has requested an outdated URL during a deployment. This is
// the same case as #1 but a much shorter window.
// 3. Someone is attempting to craft an invalid URL in order to conduct a
// denial of service attack on the site.
// Dump the optimized group into an aggregate file, but only if the
// received hash and generated hash match. This prevents invalid filenames
// from filling the disk, while still serving aggregates that may be
// referenced in cached HTML.
if (hash_equals($generated_hash, $received_hash)) {
$this->dumper
->dumpToUri($data, $this->assetType, $uri);
}
else {
$expected_filename = $this->fileExtension . '_' . $generated_hash . '.' . $this->fileExtension;
$response = new RedirectResponse(str_replace($file_name, $expected_filename, $request->getRequestUri()), 301, [
'Cache-Control' => 'public, max-age=3600, must-revalidate',
]);
}
return $response;
}
Buggy or inaccurate documentation? Please file an issue. Need support? Need help programming? Connect with the Drupal community.