Verified Commit cd54e644 authored by Dave Long's avatar Dave Long
Browse files

fix: #3567544 Alias lookup by path is not case sensitive but AliasPrefixList...

fix: #3567544 Alias lookup by path is not case sensitive but AliasPrefixList and AliasRepository::preloadPathAlias's return value is leading to inconsistencies

By: alexpott
By: catch
By: godotislate
parent a7b81e94
Loading
Loading
Loading
Loading
Loading
+12 −0
Original line number Diff line number Diff line
@@ -26833,6 +26833,12 @@
	'count' => 1,
	'path' => __DIR__ . '/modules/path_alias/src/AliasPrefixList.php',
];
$ignoreErrors[] = [
	'message' => '#^Method Drupal\\\\path_alias\\\\AliasPrefixList\\:\\:has\\(\\) has no return type specified\\.$#',
	'identifier' => 'missingType.return',
	'count' => 1,
	'path' => __DIR__ . '/modules/path_alias/src/AliasPrefixList.php',
];
$ignoreErrors[] = [
	'message' => '#^Method Drupal\\\\path_alias\\\\AliasPrefixList\\:\\:lazyLoadCache\\(\\) has no return type specified\\.$#',
	'identifier' => 'missingType.return',
@@ -26845,6 +26851,12 @@
	'count' => 1,
	'path' => __DIR__ . '/modules/path_alias/src/AliasPrefixList.php',
];
$ignoreErrors[] = [
	'message' => '#^Method Drupal\\\\path_alias\\\\AliasPrefixList\\:\\:set\\(\\) has no return type specified\\.$#',
	'identifier' => 'missingType.return',
	'count' => 1,
	'path' => __DIR__ . '/modules/path_alias/src/AliasPrefixList.php',
];
$ignoreErrors[] = [
	'message' => '#^Method Drupal\\\\path_alias\\\\AliasRepository\\:\\:addLanguageFallback\\(\\) has no return type specified\\.$#',
	'identifier' => 'missingType.return',
+22 −0
Original line number Diff line number Diff line
@@ -71,6 +71,8 @@ protected function lazyLoadCache() {
  protected function loadMenuPathRoots() {
    if ($roots = $this->state->get('router.path_roots')) {
      foreach ($roots as $root) {
        // Paths in Drupal are case-insensitive.
        $root = mb_strtolower($root);
        $this->storage[$root] = NULL;
        $this->persist($root);
      }
@@ -82,6 +84,8 @@ protected function loadMenuPathRoots() {
   */
  public function get($offset) {
    $this->lazyLoadCache();
    // Paths in Drupal are case-insensitive.
    $offset = mb_strtolower($offset);
    // This may be called with paths that are not represented by menu router
    // items such as paths that will be rewritten by hook_url_outbound_alter().
    // Therefore internally TRUE is used to indicate valid paths. FALSE is
@@ -97,10 +101,28 @@ public function get($offset) {
    }
  }

  /**
   * {@inheritdoc}
   */
  public function has($key) {
    // Paths in Drupal are case-insensitive.
    return parent::has(mb_strtolower($key));
  }

  /**
   * {@inheritdoc}
   */
  public function set($key, $value) {
    // Paths in Drupal are case-insensitive.
    parent::set(mb_strtolower($key), $value);
  }

  /**
   * {@inheritdoc}
   */
  public function resolveCacheMiss($root) {
    // Paths in Drupal are case-insensitive.
    $root = mb_strtolower($root);
    $exists = $this->pathAliasRepository->pathHasMatchingAlias('/' . $root);
    $this->storage[$root] = $exists;
    $this->persist($root);
+14 −0
Original line number Diff line number Diff line
@@ -36,9 +36,14 @@ public function preloadPathAlias($preloaded, $langcode) {
    $select = $this->getBaseQuery()
      ->fields('base_table', ['path', 'alias']);

    // Use a map of paths to their lowercase equivalents to look up aliases so
    // the returned array will contain an exact match to the provided path if it
    // differs by case to the stored path.
    $path_map = [];
    if (!empty($preloaded)) {
      $conditions = $this->connection->condition('OR');
      foreach ($preloaded as $preloaded_item) {
        $path_map[$preloaded_item] = mb_strtolower($preloaded_item);
        $conditions->condition('base_table.path', $this->connection->escapeLike($preloaded_item), 'LIKE');
      }
      $select->condition($conditions);
@@ -58,6 +63,15 @@ public function preloadPathAlias($preloaded, $langcode) {
    $aliases = [];
    foreach (array_reverse($results) as $result) {
      $aliases[$result['path']] = $result['alias'];
      // Paths are user input and are looked up via case-insensitive LIKE. If
      // the alias's path does not have an exact match, then look up the path
      // provided by the user in the map.
      if (!isset($path_map[$result['path']])) {
        $other_path = array_search(mb_strtolower($result['path']), $path_map);
        if ($other_path !== FALSE) {
          $aliases[$other_path] = $result['alias'];
        }
      }
    }

    return $aliases;
+3 −1
Original line number Diff line number Diff line
@@ -27,7 +27,9 @@ interface AliasRepositoryInterface {
   *   that language it will search paths without language.
   *
   * @return string[]
   *   System paths (keys) to alias (values) mapping.
   *   System paths (keys) to alias (values) mapping. If match to path in
   *   $preload differs by case, the array will contain an entry for the stored
   *   path and the provided path. The stored path will be returned first.
   */
  public function preloadPathAlias($preloaded, $langcode);

+21 −0
Original line number Diff line number Diff line
@@ -286,6 +286,17 @@ public function testPreloadPathAlias(): void {
        'xx-lolspeak'
      )
    );

    // Test preloading using the same path in different cases.
    $this->createPathAlias('/test-source-Case', '/test-alias');
    $path_alias_repository = $this->container->get('path_alias.repository');
    $this->assertEquals([
      '/test-source-Case' => '/test-alias',
    ], $path_alias_repository->preloadPathAlias(['/test-source-Case'], LanguageInterface::LANGCODE_NOT_SPECIFIED));
    $this->assertEquals([
      '/test-source-Case' => '/test-alias',
      '/test-source-case' => '/test-alias',
    ], $path_alias_repository->preloadPathAlias(['/test-source-case'], LanguageInterface::LANGCODE_NOT_SPECIFIED));
  }

  /**
@@ -324,6 +335,14 @@ public function testLookupPath(): void {
    // Start with a language-neutral alias, which we will override.
    $path_alias = $this->createPathAlias('/user/1', '/foo');
    $this->assertEquals($path_alias->getAlias(), $aliasManager->getAliasByPath($path_alias->getPath()), 'Basic alias lookup works.');
    $this->assertEquals($path_alias->getAlias(), $aliasManager->getAliasByPath(strtoupper($path_alias->getPath())), 'Basic alias lookup is case-insensitive.');
    $this->assertEquals($path_alias->getPath(), $aliasManager->getPathByAlias($path_alias->getAlias()), 'Basic source lookup works.');

    // Ensure that ::getPathByAlias() returns the stored path and not the user
    // provided path if path with a different case was looked up first.
    $aliasManager->cacheClear();
    $this->assertEquals($path_alias->getAlias(), $aliasManager->getAliasByPath(strtoupper($path_alias->getPath())), 'Basic alias lookup is case-insensitive.');
    $this->assertEquals($path_alias->getAlias(), $aliasManager->getAliasByPath($path_alias->getPath()), 'Basic alias lookup works.');
    $this->assertEquals($path_alias->getPath(), $aliasManager->getPathByAlias($path_alias->getAlias()), 'Basic source lookup works.');

    // Ensure that path alias data is used.
@@ -389,7 +408,9 @@ public function testPrefixList(): void {
    $this->createPathAlias('/user/1', '/' . $this->randomMachineName());
    $aliasManager->cacheClear();
    $this->assertTrue($prefix_list->get('user'));
    $this->assertTrue($prefix_list->get('User'), 'Prefix list should be case insensitive.');
    $this->assertNull($prefix_list->get('admin'));
    $this->assertNull($prefix_list->get('Admin'), 'Prefix list should be case insensitive.');
    $this->assertNull($prefix_list->get($this->randomMachineName()));

    // Add an alias for admin, both should get cached now.