Validation of library names fails in AssetControllerBase

Created on 15 August 2025, about 2 months ago

Problem/Motivation

In core/modules/system/src/Controller/AssetControllerBase.php Drupal collects and delivers assets necessary to display the current page based on some parameters in the request. Parameter include contains a hash with names of all libraries that should be loaded, while parameter exclude contains names of libraries already loaded.
If for whatever reasons uncompressing the hash results in an array containing empty strings, validating the library names fails. This is because the validation function simply checks if the given library names contains slashes which is always FALSE for an empty string.

Steps to reproduce

That's somehow hard. On a clients site with heavy use of javascript we run into this error on some (not all pages). I still haven't found why the exclude parameter has the value eJwDAAAAAAE.
Running this through the code in AssetControllerBase that extracts the library names results in said array with an empty value:

<?php
$exclude_libraries = explode(',', UrlHelper::uncompressQueryParameter($request->query->get('exclude')));
?>

The easiest way to reproduce this is to take the URL of an aggregated asset (.css or .js) and set the "exclude" parameter in the URL to eJwDAAAAAAE. Trying to open this URL (uncached) in the browser results in the message The "" library name must include at least one slash..

Proposed resolution

Make validating the library names more robust by filter empty strings.

🐛 Bug report
Status

Active

Version

11.0 🔥

Component

asset library system

Created by

🇩🇪Germany stborchert

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

Merge Requests

Comments & Activities

  • Issue created by @stborchert
  • Pipeline finished with Success
    about 2 months ago
    Total: 4896s
    #573789
  • 🇬🇧United Kingdom catch

    Even if we can never generate a URL parameter like this someone could theoretically send one so it's worth accounting for it.

    Could use some test coverage - there's existing coverage of invalid asset URLs so should be straightforward to add to that.

  • 🇩🇪Germany stborchert

    Do you happen to know which test this is in? I just took a quick look (not on my laptop right now), but unfortunately couldn't find the right test.

  • Pipeline finished with Failed
    about 2 months ago
    Total: 162s
    #574756
  • Pipeline finished with Failed
    about 2 months ago
    Total: 556s
    #574761
  • 🇮🇳India AkashKumar07

    +1 to the overall change and test coverage. LGTM!

    1. In the return statement, instead of

    an arbitrary string

    can we please update this to

    empty
    /**
     * Replaces the 'include' query parameter with an empty value.
     *
     * @param string $url
     *   The source URL.
     *
     * @return string
     *   The URL with 'include' set to empty.
     */
    

    2. I was just thinking if we can refactor the code duplication in test file.
    something like..

    /**
     * Creates a URL with an empty query parameter validation.
     *
     * @param string $url
     *   The source URL.
     * @param string $parameter
     *   The query parameter to set as empty ('include' or 'exclude').
     *
     * @return string
     *   The URL with the specified parameter set to empty.
     */
    protected function createUrlWithEmptyParameter(string $url, string $parameter): string {
      $url = $this->replaceGroupHash($url);
      $parts = UrlHelper::parse($url);
      $parts['query'][$parameter] = UrlHelper::compressQueryParameter('');
      
      $query = UrlHelper::buildQuery($parts['query']);
      return $this->getAbsoluteUrl($parts['path'] . '?' . $query . $fragment);
    }
    

    In terms of readability, the current code is clear and straightforward. So we can ignore this.

    Thanks!

  • 🇺🇸United States smustgrave

    Can we clean up the steps some to be more concrete? Will help with the review.

  • 🇩🇪Germany stborchert

    While debugging the test to see why it fails, I noticed that it is impossible for the include parameter to ever be empty (or eJwDAAAAAAE). Even if it passes the validation changed by the MR it would fail shortly with message "Invalid filename" after because the (empty) library definition could not be grouped.
    Therefore I removed the test for an empty include parameter.

  • Pipeline finished with Failed
    about 2 months ago
    Total: 576s
    #575275
  • 🇮🇳India AkashKumar07

    Thank you @stborchert for providing the steps.
    I verified the changes and looks good to me.

    Before changes:
    https://drupal11-ddev.ddev.site/sites/default/files/css/css_kRHLMdVsfEc-...

    After changes:
    https://drupal11-ddev.ddev.site/sites/default/files/css/css_kRHLMdVsfEc-...

    Thanks!

  • 🇬🇧United Kingdom catch

    One comment on the MR - I would have expected the validation to mean there's no need to change any code outside AssetControllerBase.

    Therefore I removed the test for an empty include parameter.

    I think this coverage is probably worth having - if we change the validation logic, it would prevent a possible regression.

  • 🇩🇪Germany stborchert

    One comment on the MR - I would have expected the validation to mean there's no need to change any code outside AssetControllerBase.

    I thought so, too. But unfortunately I ran into an error without the changes:
    Exception: Warning: Undefined array key 1 Drupal\Core\Asset\LibraryDependencyResolver->doGetDependencies()() (Line: 67)

    The other changes do not alter the test results so I reverted them.

    Adding the test for empty includes again leads to other errors. You'll get BadRequestHttpException('Invalid filename.') from AssetControllerBase::getGroup() because the "loaded" library groups are empty.

    Trying to fix this directly in AssetControllerBase by changing <?php $include_libraries = explode(',', UrlHelper::uncompressQueryParameter($request->query->get('include')));?> to <?php $include_libraries = array_filter(explode(',', UrlHelper::uncompressQueryParameter($request->query->get('include'))));?> or by changing the code in LibraryDependencyResover does not work since later Drupal tries to load the assets for "group with delta 1" (<?php $group = $this->getGroup($groups, $request->query->get('delta')); ?> (AssetControllerBase:181) but since there are no asset groups, this fails the "Invalid filename" exception.

    I'm unsure where to fix this. AssetControllerBase::getGroups() correctly does not create groups for empty libraries but if this happens we always run into the "Invalid filename" exception because a non-existing asset group is requested.

  • Pipeline finished with Success
    about 2 months ago
    Total: 676s
    #581866
  • 🇬🇧United Kingdom catch

    I thought so, too. But unfortunately I ran into an error without the changes:
    Exception: Warning: Undefined array key 1 Drupal\Core\Asset\LibraryDependencyResolver->doGetDependencies()() (Line: 67)

    Right but this can only happen based on how it's called from AssetControllerBase, so this suggests the validation in AssetControllerBase still isn't enough?

  • Pipeline finished with Failed
    19 days ago
    Total: 250s
    #605305
Production build 0.71.5 2024