Problem/Motivation

In Drupal 7 the output of url() is always urlencoded (except for external URLs like url('http://examplecom')) and a system path (input to url() and used for path alias sources) is never urlencoded

In Drupal 8 there are two bugs

The output of the url generator is *not* urlencoded when there is a path alias

The output of \Drupal\Core\Routing\UrlGenerator::getPathFromRoute() *is* urlencoded (though usually this doesn't have any effect). The expected behavior is not documented or tested but I believe it should not be urlencoded.

To compare the output of a node path alias:

om+"<script>alert(99)</script>g.com

In D7:
<a href="/om%2B%22%3Cscript%3Ealert%2899%29%3C/script%3Eg.com">

in D8:
<a href="/om+&quot;&lt;script&gt;alert(99)&lt;/script&gt;g.com" rel="bookmark">

Proposed resolution

Discussed priority with alexpott and thus setting to major

Discussed possible resolution with Crell and dawehner

Change the internal code to

\Drupal\Core\Routing\UrlGenerator::doGenerate()/code> so that it does not urlencode or append the query string

Return value of UrlGenerator::getPathFromRoute() should not be urlencoded

Urlencode and append the query string in <code>\Drupal\Core\Routing\UrlGenerator::generateFromRoute()

after the path processors have acted

This fix can also fix #2507005: UrlGenerator:processPath() doesn't use altered query parameters

We can simplify the logic in \Drupal\Core\Routing\UrlGenerator::processPath() since it won't have an appended query string

Remaining tasks

none

User interface changes

none

API changes

Since the expected return value of some methods is not clearly specified or tested, this may be a minor behavior change, but is not an API change.

The $options['query'] param for \Drupal\Core\Routing\UrlGenerator::generateFromRoute() is documented as being an array and for url() in Drupal 7 could also only be passed as an array. However, the incorrect use of http_build_query() for the extremely rare case of a route with the option _no_path param (in core only the '' and '' routes) means a string $options['query'] would have worked when generating a URL with no path prior to this fix, but not after.

Data model changes

none

CommentFileSizeAuthor
#40 increment-2809789-40.txt2.39 KBpwolanin
#40 2809789-40.patch24.07 KBpwolanin
#34 2809789--test-only-34.patch5.01 KBpwolanin
#31 increment-2809789-31.txt865 bytespwolanin
#31 2809789-31.patch23.86 KBpwolanin
#31 2809789--test-only-31.patch1.97 KBpwolanin
#27 2809789-27.patch23.85 KBpwolanin
#27 increment-27-test-only.patch4.3 KBpwolanin
#26 increment-2809789-26.txt1.98 KBpwolanin
#26 2809789-26.patch19.55 KBpwolanin
#23 interdiff.txt4.34 KBdawehner
#23 2809789-22.patch19.57 KBdawehner
#20 increment-2809789-20.txt1.58 KBpwolanin
#20 2809789-20.patch18.74 KBpwolanin
#18 increment-2809789-18.txt731 bytespwolanin
#18 2809789-18.patch18.82 KBpwolanin
#17 increment-2809789-17.txt653 bytespwolanin
#17 2809789-17.patch18.11 KBpwolanin
#14 increment-2809789-14.txt3.18 KBpwolanin
#14 2809789-14.patch17.47 KBpwolanin
#12 2809789-11.patch16.87 KBpwolanin
#11 interdiff.txt1014 bytesdawehner
#10 2507005-10-testonly.patch4.08 KBpwolanin
#5 2809789-urlencode-path-5.patch8.42 KBpwolanin
#4 2507005-4.patch4.26 KBdawehner
Support from Acquia helps fund testing for Drupal Acquia logo

Comments

pwolanin created an issue. See original summary.

pwolanin’s picture

pwolanin’s picture

Issue summary: View changes

We can also simplify the logic in \Drupal\Core\Routing\UrlGenerator::processPath() by never giving it a path with a query string

dawehner’s picture

This is some of the tests from

pwolanin’s picture

Here's a quick first pass at a patch - let's see if it breaks tests (might break unit tests)

pwolanin’s picture

Status: Active » Needs review

NR for bot

The last submitted patch, 4: 2507005-4.patch, failed testing.

Status: Needs review » Needs work

The last submitted patch, 5: 2809789-urlencode-path-5.patch, failed testing.

pwolanin’s picture

CI error for test patch is
11:48:45 exception 'Drupal\simpletest\Exception\MissingGroupException' with message 'Missing @group annotation in Drupal\KernelTests\Core\Path\UrlAlterTest' in /var/www/html/core/modules/simpletest/src/TestDiscovery.php:351

pwolanin’s picture

Status: Needs work » Needs review
FileSize
4.08 KB

Fixed kernel test - should run but fail on bot

dawehner’s picture

FileSize
1014 bytes

Just a small interdiff for peter

pwolanin’s picture

This patch combines the new test code from #10 plus my changes from #5 plus some of dawehner's test fixes from #2507005: UrlGenerator:processPath() doesn't use altered query parameters

The last submitted patch, 10: 2507005-10-testonly.patch, failed testing.

pwolanin’s picture

Fix to use UrlHelper::buildQuery() instead of http_build_query() and small fix from dawehner.

Also make query params modified by reference in every methods they need to be

The last submitted patch, 12: 2809789-11.patch, failed testing.

Status: Needs review » Needs work

The last submitted patch, 14: 2809789-14.patch, failed testing.

pwolanin’s picture

So silly - need to fix NullGenerator

pwolanin’s picture

Not sure why that worked before - the query option needs to be any array

The last submitted patch, 17: 2809789-17.patch, failed testing.

pwolanin’s picture

+++ b/core/lib/Drupal/Core/Routing/UrlGenerator.php
@@ -292,7 +270,7 @@ public function generateFromRoute($name, $parameters = array(), $options = array
+      $query = $options['query'] ? '?' . http_build_query($options['query'], '', '&') : '';

This is wrong - need to use the Drupal implementation from UrlHelper. Also we are passing too many params to it.

pwolanin’s picture

Need to add some tests for urlaliases for paths that would get incorrectly encoded before

dawehner’s picture

Made a couple of changes:

  • Added some clarifications regarding encoding/not URL encoded
  • Added a BC layer, its not worth breaking things, IMHO
dawehner’s picture

Status: Needs review » Needs work

The last submitted patch, 23: 2809789-22.patch, failed testing.

pwolanin’s picture

I disagree - the query option was never supported as a string in 8.x. It documented on the method as only being an array and we made a very deliberate choice not to support strings. The bug where a string worked was also limited to only the case of a _no_path option, so not a common use.

Also, this looks like an unintended change?

@@ -514,7 +514,7 @@ public function testGenerateWithPathProcessorChangingQueryParameter() {
     $path_processor = $this->getMock(OutboundPathProcessorInterface::CLASS);
     $path_processor->expects($this->atLeastOnce())
       ->method('processOutbound')
-      ->willReturnCallback(function ($path, &$options = array(), Request $request = NULL, BubbleableMetadata $bubbleable_metadata = NULL) {
+      ->willReturnCallback(function ($path, &$options = [], Request $request = NULL, BubbleableMetadata $bubbleable_metadata = NULL) {
         $options['query'] = ['zoo' => 5];
         return $path;
       });
pwolanin’s picture

Status: Needs work » Needs review
FileSize
19.55 KB
1.98 KB
pwolanin’s picture

Ok, here's a quick start on a test to show part of the bug - creating a path alias to a route path that has characters that are urlencoded will fail to match on outbound path processing and the alias lookup will fail.

Possibly could be writeen better with a data provider, or maybe as a kernel test, though I think there's some value to demonstrating the actual web request works.

The test-only patch is the same as the interdiff and will fail in the 2nd method at least.

The last submitted patch, 27: increment-27-test-only.patch, failed testing.

Status: Needs review » Needs work

The last submitted patch, 27: 2809789-27.patch, failed testing.

pwolanin’s picture

Ah, probably didn't account for the tests being run in a subdirectory - need to account for the fact the alias is the last part of the path, not necessarily the full path

pwolanin’s picture

I think this will fix the test fails with the patch (should still fail as test only)

Status: Needs review » Needs work

The last submitted patch, 31: 2809789-31.patch, failed testing.

The last submitted patch, 31: 2809789--test-only-31.patch, failed testing.

pwolanin’s picture

Status: Needs work » Needs review
FileSize
5.01 KB

oops - forgot to include the test module in the test-only patch

Status: Needs review » Needs work

The last submitted patch, 34: 2809789--test-only-34.patch, failed testing.

pwolanin’s picture

Status: Needs work » Needs review

ok, that shows one of the expected fails. Probably should convert to a kernel test and a data provider to better test a range of outbound path processing matching to find aliases

dawehner’s picture

For me this looks really great. Luckily I'm not a subsystem maintainer of routing to decide about it :)

  1. +++ b/core/modules/language/src/Plugin/LanguageNegotiation/LanguageNegotiationContentEntity.php
    +++ b/core/modules/language/src/Plugin/LanguageNegotiation/LanguageNegotiationContentEntity.php
    @@ -123,15 +123,9 @@ public function processOutbound($path, &$options = [], Request $request = NULL,
    
    @@ -123,15 +123,9 @@ public function processOutbound($path, &$options = [], Request $request = NULL,
             parse_str($options['query'], $query);
             $options['query'] = $query;
           }
    

    I guess we could get rid of this part of the code as well

  2. +++ b/core/modules/system/tests/modules/path_encoded_test/path_encoded_test.routing.yml
    @@ -0,0 +1,23 @@
    +    _access: 'TRUE'
    \ No newline at end of file
    
    +++ b/core/tests/Drupal/FunctionalTests/Routing/PathEncodedTest.php
    @@ -0,0 +1,51 @@
    +  }
    +}
    \ No newline at end of file
    

    nitpick: missing empty line / missing new line

catch’s picture

Just nits, overall looks good.

  1. +++ b/core/lib/Drupal/Core/Routing/UrlGenerator.php
    @@ -274,15 +255,13 @@ public function generate($name, $parameters = array(), $referenceType = self::AB
    +      $options['query'] = array();
    

    Should use short array syntax.

    Also when would $options['query'] not be an array? I'd expect we could add this to the += above.

  2. +++ b/core/lib/Drupal/Core/Routing/UrlGenerator.php
    @@ -302,13 +281,32 @@ public function generateFromRoute($name, $parameters = array(), $options = array
    +    // The contexts base URL is already encoded
    

    What's a contexts base url?

dawehner’s picture

Also when would $options['query'] not be an array? I'd expect we could add this to the += above.

Personally I think we should have a BC layer just to be sure, but well, @pwolanin has a different opinion here.

What's a contexts base url?

This is from code just moved around.

pwolanin’s picture

@catch - the rest of the method is not using short array syntax, so using the long syntax is, I think, the correct code style until we do a mass conversion.

There was a bug fixed in the patch with a caller generating a link without a path passed in a string query option, despite the fact that even Drupal 7 only supports passing it as an array. I am sad and perplexed that the various could slipped in treat is as string again. I strongly oppose supporting a string query param which was not valid in even in Drupal 7.

Patch fixes minor problems noted above.

dawehner’s picture

There was a bug fixed in the patch with a caller generating a link without a path passed in a string query option, despite the fact that even Drupal 7 only supports passing it as an array. I am sad and perplexed that the various could slipped in treat is as string again. I strongly oppose supporting a string query param which was not valid in even in Drupal 7.

What about supporting a string and throwing @trigger_error('', E_USER_DEPRECATED) in this case? This makes it really explicit without breaking potential code.

pwolanin’s picture

Issue summary: View changes
pwolanin’s picture

Issue summary: View changes
dawehner’s picture

Issue summary: View changes
Status: Needs review » Reviewed & tested by the community

Thank you for documenting the BC break. A agree, the _no_path "API break" is really just a bugfix

pwolanin’s picture

Issue summary: View changes

  • catch committed 3a8b4ee on 8.3.x
    Issue #2809789 by pwolanin, dawehner: UrlGenerator fails to urlencode...
catch’s picture

Version: 8.2.x-dev » 8.3.x-dev
Status: Reviewed & tested by the community » Fixed

Committed 3a8b4ee and pushed to 8.3.x. Thanks!

I don't really like this for 8.2.x without the bc layer for string query arguments - yes it's not officially supported, but if a module is relying on it, we don't want a patch release update to start spitting errors. I'm fine if we just leave this in 8.3.x though so leaving fixed there. Re-open if you want to discuss for 8.2.x of course.

pwolanin’s picture

Version: 8.3.x-dev » 8.2.x-dev
Status: Fixed » Reviewed & tested by the community

I think it's important to have this fix in 8.2.x also since the incorrectly encoded aliases can cause a number of problems.

Moving back to 8.2.x per discussion with catch in IRC

catch’s picture

Status: Reviewed & tested by the community » Needs work
Issue tags: +Needs change record, +Needs issue summary update

#41 needs answering a bit more conclusively for me - i.e. can we put it in the issue summary/a change record at least?

tuutti’s picture

This might have caused some issues with Block UI: #2822082: Form redirect broken when saving a new block instance

pwolanin’s picture

@catch - ok, I thought it was already in the issue summary? I can certainly make a change record

catch’s picture

xjm’s picture

I'm also very hesitant to commit this to 8.2.x. I'd prefer not to if we can avoid it.

Edit: We should still add a CR either way. Thanks!

dawehner’s picture

pwolanin’s picture

@Dawhener - I don't think this change is related, since it seems to be a preexisitng bug in 8.2

@xjm - I will add a change record, but we do need this for 8.2.

@catch - what else is needed in the issue summary?

xjm’s picture

I need clarification here; does this issue need revert because of the regression or did reverting #2745911: Block add links should respect destination address the issue? Hopefully the latter?

  • catch committed 3a8b4ee on 8.4.x
    Issue #2809789 by pwolanin, dawehner: UrlGenerator fails to urlencode...

  • catch committed 3a8b4ee on 8.4.x
    Issue #2809789 by pwolanin, dawehner: UrlGenerator fails to urlencode...

Version: 8.2.x-dev » 8.3.x-dev

Drupal 8.2.6 was released on February 1, 2017 and is the final full bugfix release for the Drupal 8.2.x series. Drupal 8.2.x will not receive any further development aside from critical and security fixes. Sites should prepare to update to 8.3.0 on April 5, 2017. (Drupal 8.3.0-alpha1 is available for testing.)

Bug reports should be targeted against the 8.3.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.4.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

pwolanin’s picture

Issue summary: View changes
Status: Needs work » Needs review
Issue tags: -Needs change record, -Needs issue summary update +DrupalCampNJ2017, +Triaged for D8 major current state

Adding draft change record, setting to NR for change record.

mlncn’s picture

Status: Needs review » Fixed

Change record approved!

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.