Redirects from aliased paths aren't triggered

Created on 19 May 2017, about 7 years ago
Updated 5 June 2024, 23 days ago

Problem/Motivation

When I create a new redirect from /node/1 to /node/2 it works fine but when I use path alias e.g. /footo /node/2 it does not redirect. This happens because the hash that gets generated while saving a new redirect is based on the user input i.e it can be alias or node path but when we try to match a path we use system path and hence does not match.

Steps to reproduce

  1. Create a redirect from node/1 to /node/2 and confirm it works
  2. Create a redirect from foo to /node/2 and confirm it works
  3. Create a node with alias /bar
  4. Add a redirect from bar to /node/2 and confirm it does not work

Proposed resolution

The following approaches have been discussed:

  1. Get consistency in the hash generation function by always using system path
  2. Find redirects that match either the alias or system path
  3. Convert aliases to system paths before they're saved

Remaining tasks

  • Decide on the preferred approach

User interface changes

TBD

API changes

TBD

Data model changes

TBD

🐛 Bug report
Status

Needs work

Version

1.0

Component

Code

Created by

🇮🇳India ameymudras

Live updates comments and jobs are added and updated live.
  • Needs tests

    The change is currently missing an automated test that fails when run with the original code, and succeeds when the bug has been fixed.

Sign in to follow issues

Comments & Activities

Not all content is available!

It's likely this issue predates Contrib.social: some issue and comment data are missing.

  • 🇦🇺Australia acbramley

    Patch #142 can trigger the following error if $paths ends up as an empty array.

    Drupal\Core\Database\DatabaseExceptionWrapper: SQLSTATE[42000]: Syntax error or access violation: 1064 You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near ') ORDER BY LENGTH(redirect_source__query) DESC' at line 1: SELECT rid FROM "redirect" WHERE hash IN () ORDER BY LENGTH(redirect_source__query) DESC; Array ( ) in Drupal\redirect\RedirectRepository->findRedirectByHashes() (line 140 of modules/contrib/redirect/src/RedirectRepository.php). 
    

    This doesn't happen without this patch because $path is just an empty string, which generates hashes and everything works.

    I can trigger this locally by hitting a URL like 127.0.0.1/%2f

    This popped up in our logs from malicious users trying to do weird stuff on our site. The fix is easy (check for empty $paths in the event subscriber) but writing a test is proving to be more difficult than expected.

  • Status changed to Needs review over 1 year ago
  • 🇦🇺Australia acbramley

    Yeah it doesn't seem possible to test this in a functional test because of UnroutedUrlAssembler::buildLocalUrl which ends up doing a rawurlencode

    Also tidied up the other new tests, replacing the deprecated functions.

  • Status changed to Needs work over 1 year ago
  • 🇨🇭Switzerland Berdir Switzerland

    1+ year after comments #131-135, I partially changed my opinion, but only partially.

    As said back then, some of the use cases people have seem to overlap with rabbit_hole, which is a better solution for having content that shouldn't be accessible on its own path.

    But there are valid use cases for this. We get support requests quite frequently about this, when people archive old content for example and want to keep it but still redirect it to another page. There are basically two options to handle that right now, without this patch.

    a) Remove the alias of the archived content, then create the redirect.
    b) Set up the redirect on the system path of the archived content. Problem is that it's then tied to that, if you delete that in the future, the redirect is broken.

    Neither option is perfect. For most cases that *I* see, a) is better, but its hard to understand and requires multiple steps.

    First, some relevant feedback on this patch:

    +++ b/redirect.module
    @@ -99,9 +99,10 @@ function redirect_path_alias_update(PathAliasInterface $path_alias) {
    -  // Create redirect from the old path alias to the new one.
       if ($original_path_alias->getAlias() != $path_alias->getAlias()) {
    -    if (!redirect_repository()->findMatchingRedirect($original_path_alias->getAlias(), [], $original_path_alias->language()->getId())) {
    +    $existing_redirect = redirect_repository()->findMatchingRedirect($original_path_alias->getAlias(), [], $original_path_alias->language()->getId());
    +    if (!$existing_redirect) {
    +      // Create redirect from the old path alias to the new one.
           $redirect = Redirect::create();
           $redirect->setSource($original_path_alias->getAlias());
           $redirect->setRedirect($path_alias->getPath());
    @@ -109,6 +110,13 @@ function redirect_path_alias_update(PathAliasInterface $path_alias) {
    
    @@ -109,6 +110,13 @@ function redirect_path_alias_update(PathAliasInterface $path_alias) {
           $redirect->setStatusCode($config->get('default_status_code'));
           $redirect->save();
         }
    +    else {
    +      // Duplicate the existing redirect with:
    +      // source = [new alias], dest = [existing redirect].
    +      $redirect = $existing_redirect->createDuplicate();
    +      $redirect->setSource($path_alias->getAlias());
    +      $redirect->save();
    +    }
       }
     }
    
    +++ b/tests/src/Functional/RedirectUITest.php
    @@ -275,4 +274,63 @@ class RedirectUITest extends BrowserTestBase {
    +
    +  /**
    +   * Test adding a node alias when a redirect already exists.
    +   */
    +  public function testNodeAliasOnExistingRedirect(): void {
    +    $this->drupalLogin($this->adminUser);
    +
    +    $this->drupalGet('admin/config/search/redirect/add');
    +    $this->submitForm([
    +      'redirect_source[0][path]' => 'some-url',
    +      'redirect_redirect[0][uri]' => '/node',
    +    ], 'Save');
    +
    +    $this->assertRedirect('some-url', '/node', 301);
    +
    +    $this->drupalCreateNode([
    +      'type' => 'article',
    +      'title' => 'Test node redirect',
    +      'path' => ['alias' => '/some-url'],
    +    ]);
    +
    +    $this->assertRedirect('some-url', '/node', 301);
    +  }
    

    I don't think this behavior is desired and it's inconsistent. Above, we still have the line to delete redirects and that is important, when changing an alias through pathauto or manually, you do _not_ expect that an existing redirect will override it and it will mean your content is inaccessible. Redirects get set up automatically, and if there's real content again on a certain path, that has to show up again.

    If anything, we should restrict this logic here to not remove redirects if updating an alias and the alias does not change and only then.
    The use case that this is trying to solve also already "just works" technically if you set the request path to the system path and not the alias, which is very similar to how link fields on a path/alias vs. selecting an entity via autocomplete (aka an entity:TYPE/ID URI). But unlike the autocomplete behavior, it's hard to understand for users.

    The test coverage here needs to be adjusted to cover that scenario then.

    To summarize, there are 3 possible options on how to deal with a redirect on an existing alias, a and b above, plus what this patch is adding, to actually support that aka c. I still have serious concerns about the current patch and its effect on existing sites. Site might have a considerable amount of redirects on aliases that they do not know about and that are "invisible" right now. By adding this they would suddenly start to work and content might no longer be accessible. I'm not sure how to deal with that in a backwards-compatible way. A setting maybe?

    I still think I would prefer to have a patch that improves the usability of those two existing options a and b, for example by giving users a choice to do either directly from the add redirect form. Additionally to the warning message we have right now, we would display a radios element where users can choose between automatically deleting that existing alias on save, and a description that re-creating said alias will delete the redirect again. Or they let the system change the from path to the system path, tying it to the entity behind it then, no matter which alias it currently has. That could be done in a separate issue, and this could then tie into that with a third option, if enabled, that allows them to save the redirect as-is for the alias. But in my opinion, with the first two existing options (and existing modules like rabbit_hole), we can already cover 90% of the use cases. But I'm interested in hearing what use case still needs explicit support of redirecting on aliases.

    I know that this is awkward for people who rely on this patch and have data that builds on it. That is in the end a risk you always have when relying on patches that change data structures or affect how things work on stored data. But if we introduce this with an optional setting, you can just enable that, or migrate to option a or b, someone could write an update script/batch for that.

  • 🇦🇺Australia acbramley

    Reroll of #146

  • Open in Jenkins → Open on Drupal.org →
    Core: 10.1.x + Environment: PHP 8.1 & MySQL 5.7
    last update 10 months ago
    58 pass, 2 fail
  • Open in Jenkins → Open on Drupal.org →
    Core: 9.5.x + Environment: PHP 8.1 & MySQL 5.7
    last update 10 months ago
    68 pass
  • #149 isnt working for me

    TypeError: Drupal\redirect\EventSubscriber\RedirectRequestSubscriber::__construct(): Argument #9 ($redirect_path_processor) must be of type Drupal\redirect\PathProcessor\RedirectPathProcessorManagerInterface, Drupal\Core\PathProcessor\PathProcessorManager given, called in /app/web/core/lib/Drupal/Component/DependencyInjection/Container.php on line 259 in Drupal\redirect\EventSubscriber\RedirectRequestSubscriber->__construct() (line 95 of modules/contrib/redirect/src/EventSubscriber/RedirectRequestSubscriber.php).

    D9.5.11, php 8.1

  • 🇺🇸United States DamienMcKenna NH, USA

    FWIW #149 resolved some problems I had with path aliases on D10.0 not being created when user entities were modified. While I appreciate Berdir wants a more solid solution for the module's long term stability, #149 seems like a reasonable temporary solution for sites running into this problem.

  • 🇺🇸United States DamienMcKenna NH, USA

    FYI I should note that patch #149 did hit a minor issue:

    • Have a user with an alias.
    • Run this in terminal:
      • drush php
      • use \Drupal\user\Entity\User;
      • $u = User::load(USERID);
      • $u->set('field_stuff', 'something different');
      • $u->save();
    • Load this admin page: admin/config/search/redirect
    • Confirm that a redirect was created for the user entity.
    • In the same terminal, still logged into "drush php", run the following:
      • $u->set('field_stuff', 'something else');
      • $u->save();
    • Reload the redirect admin page.
    • The new redirect will no be visible.
    • Clear the caches.
    • The new redirect will be visible.

    The redirect admin page uses core's tag-baed caching. This suggests that the tags are not being invalidated correctly somewhere.

  • 🇬🇧United Kingdom Alina Basarabeanu

    We came across this issue using Drupal 10.1.5 and Redirect 1.9.0.
    I created a new redirect from /old-page to /new-page but when navigating to /old-page I still can view the page.
    The only change on the page is the breadcrumb pointing to the new page.
    I did clear the cache and even ran cron but the redirect is not triggered.
    Applying the patch from #149 fixed the redirect but the URL redirect is not added to either node's edit form.

  • 🇵🇹Portugal guilherme-lima-almeida Lisbon

    Hi everyone,

    With:

    • Drupal: 10.0.11
    • Redirect: 8.x-1.9

    The patch #149 works for me and the URL redirect is added to either node's edit form.

  • 🇬🇧United Kingdom altcom_nh

    Hi all,

    Sorry if this is cross pollinating with another issue but we had exactly this issue after migrating to D9 and updating to the new version of the Redirects module. It caused a lot of confusion with clients trying to manage their content as in D7 the alias redirects worked but not in D9 after the upgrade.

    One way we solved this was to integrate not only showing the redirects to a node but the redirect away as well. This makes it easier for a user when editing the node to see it is being redirected to another URL. And this simplifies the creation of a redirect from an old node to a new one.

    There is a patch here that some people may find useful https://www.drupal.org/project/redirect/issues/3386107 Simplify how to redirect from one node to another node or URL Active

  • Was going through this same problem, the module wasn't working with path aliases for the redirection. The patch #142 works for me.
    Thanks @Rajab Natshah

  • 🇪🇸Spain paucala

    The patch #142 works for me also. Thanks

Production build 0.69.0 2024