When adding an external link to a menu, dots in query parameter names are converted to underscores, even though dots are allowed in query parameter names in general. This is a confusing experience for site owners trying to create links to external sites, the link is accepted, but its rendered differently.

This seems to be because Symfony uses parse_str() to convert parameters to PHP safe variable names. See https://github.com/symfony/symfony/issues/25541 It would be ideal for this to be fixed by PHP or Symfony instead of Drupal. However, it seems there's not much movement in that direction. Therefore, it could make sense for Symfony, or Drupal, to have a function used to parse_str for the scenario of rendering links to other sites.

To reproduce:

  1. From any installation of Drupal
  2. Add an external link to the main menu (https://www.drupal.org?first.name=Aaron&last.name=Wolfe)
  3. Expected behavior: The url in the menu link should be https://www.drupal.org?first.name=Aaron&last.name=Wolfe
  4. Actual behavior: Instead, the menu item links to https://www.drupal.org?first_name=Aaron&last_name=Wolfe

This also happens with redirects, and I imagine any other place that prepares URLs with query strings.

CommentFileSizeAuthor
#67 2984272-67.patch11.29 KBShubham Chandra
#66 fix-mangled-query-parameter-names-2984272-66.patch11.29 KBAnkit.Gupta
#59 fix-mangled-query-parameter-names-2984272-59.patch11.91 KBao5357
#54 fix-mangled-query-parameter-names-2984272-54.patch11.3 KBjberube
#53 fix-mangled-query-parameter-names-2984272-53.patch11.01 KBjberube
#52 fix-mangled-query-parameter-names-2984272-52.patch10.34 KBjberube
#50 fix-mangled-query-parameter-names-2984272-50-9.2.x.patch8.58 KBanmolgoyal74
#49 fix-mangled-query-parameter-names-2984272-49-8.9.x.patch8.58 KBjberube
#49 fix-mangled-query-parameter-names-2984272-49-9.2.x.patch8.83 KBjberube
#46 fix-mangled-query-parameter-names-2984272-46.patch5.62 KBaaronpinero
#45 fix-mangled-query-parameters-2984272-45.patch5.62 KBaaronpinero
#44 fix-mangled-query-parameters.patch5.62 KBaaronpinero
#43 interdiff-36-43.txt549 bytesjberube
#43 fix-mangled-query-parameter-names-2984272-43.patch5.55 KBjberube
#36 interdiff-35-36.txt597 bytesrob_pr
#36 fix-mangled-query-parameter-names-2984272-36.patch5.52 KBrob_pr
#35 interdiff_31-35.txt1.7 KBom_kha
#35 fix-mangled-query-parameter-names-2984272-35.patch5.51 KBom_kha
#31 fix-mangled-query-parameter-names-2984272-31.patch6.02 KBDarren Oh
#31 fix-mangled-query-parameter-names-2984272-31-test-only.patch2.45 KBDarren Oh
#31 interdiff-28-31.txt4.95 KBDarren Oh
#28 fix-mangled-query-parameter-names-2984272-28.patch5.25 KBom_kha
#28 interdiff.txt1.54 KBom_kha
#24 interdiff.txt2.81 KBDarren Oh
#24 fix-mangled-query-parameter-names-2984272-24.patch5.5 KBDarren Oh
#5 dots-in-query-parameter-names-in-externa-urls-converted-to-underscores-2984272-5.patch4.21 KBWilfred Waltman
#4 dots-in-query-parameter-names-in-externa-urls-converted-to-underscores-2984272-4.patch4.2 KBWilfred Waltman

Issue fork drupal-2984272

Command icon Show commands

Start within a Git clone of the project using the version control instructions.

Or, if you do not have SSH keys set up on git.drupalcode.org:

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

awolfey created an issue. See original summary.

awolfey’s picture

Issue summary: View changes
awolfey’s picture

UrlHelper.php seems to be the problem at line 164

      // If there is a query string, transform it into keyed query parameters.
      if (isset($parts[1])) {
        parse_str($parts[1], $options['query']);
      }

I'm not sure why the the query string of an external url should have parameter names parsed into php variables.

Wilfred Waltman’s picture

I ran into the same problem and created a new method to work around this default PHP behaviour. Created a patch to implement that method in the parse method of UrlHelper.php and in the fromUri method of Url.php.

There are more places in core where parse_str is used and where it might be replaced with this new method.

Wilfred Waltman’s picture

Created new patch. Basically the same as #4 but compliant to coding standards. I know, should have been paying more attention the first time.

Wilfred Waltman’s picture

seanB’s picture

Version: 8.5.5 » 8.7.x-dev
Status: Active » Needs review

Triggering the testbot. Besides that, there are lots of other occurrences of parse_str in core. Not sure if we want to replace those as well?

seanB’s picture

Looks good to me. This probably needs more reviews from the menu/link maintainers.

awolfey’s picture

This solves my issue with the menu and redirects (redirect module). Thank you!

borisson_’s picture

The testcoverage of this looks good, but I would love to also see unit/kernel testcoverage for this. Right now it looks like this is only tested by a webtest.

In any case, I like the approach here.

Anas_maw’s picture

This solves my issue too.
Thanks

seanB’s picture

The testcoverage of this looks good, but I would love to also see unit/kernel testcoverage for this. Right now it looks like this is only tested by a webtest.

Drupal\Tests\Core\UrlTest is a unit test?

borisson_’s picture

Status: Needs review » Reviewed & tested by the community

/headdesk - yes it is. I missed that, sorry. Setting to RTBC to move this up the chain to a subsystem/core maintainer.

alexpott’s picture

Status: Reviewed & tested by the community » Needs review
Issue tags: +Needs security review

Are we sure we want to do this. Symfony has chosen to not support it and neither has Drupal. Reimpleming parse_str() in Drupal introduces its own problems and surface area to attack since we are definitely handling user input here.

Personally I'm -1 on doing this for the reasons outlined in the https://github.com/symfony/symfony/issues/25541 - I'd prefer to try to change the behaviour of parse_str() in PHP.

catch’s picture

Agreed with #14. Is there a PHP bug report for this already?

awolfey’s picture

I think that if dots are allowed in query parameter names, which they are from what I can tell, then we should allow them.

Our clients are linking to third party services that have dots in parameter names.

alexpott’s picture

The problem is here is that we're changing very low level functionality. UrlHelper::parse() is used in all sorts of places. One of which is in external URL parsing. So its very very hard to predict the outcome of making that change. PHP has been altering query parameters like this forever. For example, if you make a request to PHP with the query param ?foo.bar=bar and dump $_GET the output will be

Array
(
    [foo_bar] => bar
)

One thing I think I support though is not mangling user inputted external URLs in this way since that is just frustrating. The user knows the correct URL but the system is breaking it for them. That's not good. In fact this is the second report we've had of this type of bug in 8.6.x and I don't think it is happening in 8.5.x. The other report is #2987114: Regression in external URLs in menu links with valueless query parameters and all of this appears to have been caused by #2955685: Unrouted URLs cannot have have overridden query or fragments

alexpott’s picture

charly71’s picture

Any backporting on 8.6.x versions?

Thanks

charly71’s picture

#16 +1

Version: 8.7.x-dev » 8.8.x-dev

Drupal 8.7.0-alpha1 will be released the week of March 11, 2019, which means new developments and disruptive changes should now be targeted against the 8.8.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

idebr’s picture

Darren Oh’s picture

Assigned: Unassigned » Darren Oh
Issue tags: +Seattle2019
Darren Oh’s picture

Title: Dots in query parameter names in external menu links converted to underscores » Dots in query parameter names converted to underscores
Assigned: Darren Oh » Unassigned
Issue tags: +Novice
FileSize
5.5 KB
2.81 KB

This issue also breaks front end libraries that require specific query parameter names. It is the result of a deprecated PHP behavior that can generate variables directly from a query string. For some reason, this name change is also applied to the top-level keys when the query string is being parsed to an array. I have updated the patch to apply to internal URLs and made clear that our wrapper method only restores original parameter names to the parsed query.

Darren Oh’s picture

Status: Needs review » Needs work

The last submitted patch, 24: fix-mangled-query-parameter-names-2984272-24.patch, failed testing. View results

Version: 8.8.x-dev » 8.9.x-dev

Drupal 8.8.0-alpha1 will be released the week of October 14th, 2019, which means new developments and disruptive changes should now be targeted against the 8.9.x-dev branch. (Any changes to 8.9.x will also be committed to 9.0.x in preparation for Drupal 9’s release, but some changes like significant feature additions will be deferred to 9.1.x.). For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

om_kha’s picture

Patch in #24 does not resolve nested query parameters e.g ?param[nested][0]=value&param[nested][1]=2ndvalue.

Rithesh BK’s picture

Assigned: Unassigned » Rithesh BK

currently checking the code for 8.9.x-dev ..... . i am working on it

mradcliffe’s picture

Assigned: Rithesh BK » Unassigned
Issue tags: +Needs issue summary update

I added the Needs issue summary update tag to the issue. The issue is still tagged as Needs subsystem maintainer review and needs security review, and I think it would help if the issue summary could be updated with what is requested to be reviewed. If after the issue summary is updated, it isn't clear, please also remove the Novice tag when removing the Needs issue summary update tag.

I removed the Assigned attribute. Generally we shouldn't assign issues to ourselves per Assigning Ownership.

Darren Oh’s picture

Status: Needs review » Needs work

The last submitted patch, 31: fix-mangled-query-parameter-names-2984272-31.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

Version: 8.9.x-dev » 9.1.x-dev

Drupal 8.9.0-beta1 was released on March 20, 2020. 8.9.x is the final, long-term support (LTS) minor release of Drupal 8, which means new developments and disruptive changes should now be targeted against the 9.1.x-dev branch. For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

Pol’s picture

Hello,

I also encountered the issue in a Symfony application. In order to fix this, I created a bundle that might be helpful for the Drupal community as well.

Find it here: https://github.com/loophp/unaltered-psr-http-message-bridge-bundle

I see that Drupal is already using symfony/psr-http-message. This package is a drop-in replacement, so adding my package should be just enough to fix the issue.

om_kha’s picture

Adds urldecode for name parameter and merge parsed parameters recursivly.

rob_pr’s picture

Preserve integer keys when recursively merging nested parameters.

aaronpinero’s picture

I can confirm that the patch for 8.9 in #36 resolves the issue in my case.

I was also able to cleanly apply the patch for Drupal 8.7.14 running in a local dev environment with PHP v7.0.32

Darren Oh’s picture

Status: Needs work » Reviewed & tested by the community

Don't forget to update the issue status.

Renrhaf’s picture

Confirming that #36 resolves the issue on a Drupal 8.8.6 and PHP 7.4.7. Thanks!

alexpott’s picture

Status: Reviewed & tested by the community » Needs work

So one thing that's good is that no tests are failing with this change. BUT that only means it's likely that core is not relying on PHP's behaviour. Unfortunately that doesn't mean anything for existing contrib and custom code that might very well be relying on it. It's almost 100% for sure that someone somewhere is relying on the way that PHP mangles the query parameter names. See discussion on https://externals.io/message/106213, https://github.com/symfony/symfony/issues/29664, https://github.com/symfony/symfony/issues/25541 for examples of the BC concerns.

One of the probable reasons nothing is broken is that we're only changing UrlHelper - which is mainly used for processing external Urls - but Drupal's underlying request handling infrastructure is still using parse_str() and so will continue to mangle query parameter keys. Also we have no idea what UrlHelper might be used for in the wild.

This is a super tricky issue.

A possible way forward would be to a flag to UrlHelper::parse() to allows the current behaviour to be maintained but the new behaviour to be opted into. Also we could target the problem outlined in the issue summary - when linking to an external url don't mangle it at all - which would a smaller (in terms of underlying impact) change than this.

Renrhaf’s picture

As mentioned by javiereguiluz in https://github.com/symfony/symfony/issues/29664#issuecomment-512147641 :

Note that the automatic conversion of dots into underscores is a PHP behavior ... which could finally be changed/removed in PHP 8: https://externals.io/message/106213

Code from #36 issues PHP warning when $parsed[$name] is not an array at the following line :

$parsed[$name] = NestedArray::mergeDeepArray([$parsed[$name], reset($param)], TRUE);
aaronpinero’s picture

I have encountered an issue when using this patch with a URL where the query string begins with a number. If the query string begins with a number instead of an alphabetic character, the query string that appears in the processed link is 0. So if my query string begins "?1335453" the end result I get from Drupal after the URL is processed is "?0=". I have tried running through the php commands in the public static function parseQueryString() from the patch, but have not been able to figure out where the issue is. It seems like it should be fine, but maybe the problem is introduced in a different step.

jberube’s picture

I used the #36 patch and I also encountered the error mentioned in #41. In my case it was because the same parameter was used twice in the supplied URL:
http://example.com/?a=1&b=2&a=3

The parseQueryString function chokes on the second occurrence of "a" and rewrites the URL like this:
http://example.com/?&b=2

You can make the warning go away if you change the "isset($parsed[$name])" to "isset($parsed[$name]) && is_array($parsed[$name])". This will make it preserve the "a" variable, allowing the second occurrence to overwrite the first:
http://example.com/?a=3&b=2

I think this is a more graceful way to deal with a malformed URL. This does however assume that the URL is malformed and that it's OK for this function to rewrite it.

aaronpinero’s picture

I also ran into the issue in #41. It seems like the behavior being attempted here is to allow multiple values for a single parameter name, thus the invocation of the mergeDeepArray function. Therefore, if the name already exists but only has one value, the value should be converted into an array so the next value could be added.

if (!is_array($parsed[$name])) {
  $parsed[$name] = [$parsed[$name]];
}

I made this change on my Drupal 8.9.5 site (in development) and it appears to have cleared up the errors I was receiving without also creating new problems.

I am attempting to upload a revised version of the patch in #36, but I have to admit I'm not very good at this.

aaronpinero’s picture

FileSize
5.62 KB

Adding patch again with correct naming convention

aaronpinero’s picture

FileSize
5.62 KB

Patch in #45 was broken, trying again

aaronpinero’s picture

Okay, sorry for making a mess of this issue. The patch I provided in #46 doesn't seem to fix the error; for some reason It worked initially in dev but is now not working as a patch. I am passing what should be an array of arrays to mergeDeepArray, but am still getting the invalid argument error.

Version: 9.1.x-dev » 9.2.x-dev

Drupal 9.1.0-alpha1 will be released the week of October 19, 2020, which means new developments and disruptive changes should now be targeted for the 9.2.x-dev branch. For more information see the Drupal 9 minor version schedule and the Allowed changes during the Drupal 9 release cycle.

jberube’s picture

In addition to preserving the dots in the parameter names, I needed to allow duplicate parameters, like "?a=1&b2&a=2". There is no way to do this using the parsed query array because the array can't have duplicate keys. However, I believe that for external links, if you're not altering the query by adding parameters to it, you should preserve the original string without parsing it at all. These patches do that.

anmolgoyal74’s picture

Fixed the failed CS issues.
Not able to generate interdiff. getting error.

Status: Needs review » Needs work
jberube’s picture

The #49 patch had an error that was causing tests to fail. Here's a redo.

jberube’s picture

Version: 9.2.x-dev » 9.3.x-dev

Drupal 9.2.0-alpha1 will be released the week of May 3, 2021, which means new developments and disruptive changes should now be targeted for the 9.3.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

cbanman’s picture

Status: Needs work » Needs review

I've just run into this same issue and tested the latest patch and works for me. Though I'm not sure about any security implications it might have if any.

ao5357 made their first commit to this issue’s fork.

ao5357’s picture

Here's an after-the-fact `git format-patch` file with the same changeset as the issue fork and merge request, for composer-based patching.

jeetmail72’s picture

Quick solution:

function my_module_preprocess_node__articles(array &$variables) {
}

function HOOK_preprocess_node_HOOK(array &$variables) {
if (isset($variables['content']['field_link'])) {
if (!empty($variables['content']['field_link'][0]['#url'])) {
$variables['content']['field_link'][0]['#url'] = [
'#markup' => $variables['content']['field_link'][0]['#url']->getUri(),
'#type' => 'markup',
];
}
}
}

Version: 9.3.x-dev » 9.4.x-dev

Drupal 9.3.0-rc1 was released on November 26, 2021, which means new developments and disruptive changes should now be targeted for the 9.4.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 9.4.x-dev » 9.5.x-dev

Drupal 9.4.0-alpha1 was released on May 6, 2022, which means new developments and disruptive changes should now be targeted for the 9.5.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 9.5.x-dev » 10.1.x-dev

Drupal 9.5.0-beta2 and Drupal 10.0.0-beta2 were released on September 29, 2022, which means new developments and disruptive changes should now be targeted for the 10.1.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

tlo405’s picture

Version: 10.1.x-dev » 9.4.x-dev
Status: Needs review » Reviewed & tested by the community

The patch in #59 fixes the issue for me (using Drupal 9.4.8).

So I have a field on a node that uses the 'link' field type. I have some content where the URL contains a query string that has semi-colons in it. When that URL renders, the semi-colons are getting encoded to %3B, and the link breaks. After applying this patch, the semi-colon in the query string is no longer encoded and the link works as expected.

catch’s picture

Status: Reviewed & tested by the community » Needs work
Issue tags: +Needs reroll

The merge request is against 9.3, it needs to be against 9.5 or 10.1 with a clean test run before it can be RTBC. I haven't reviewed the issue otherwise.

Ankit.Gupta’s picture

Version: 9.4.x-dev » 9.5.x-dev
Status: Needs work » Needs review
Issue tags: -Needs reroll
FileSize
11.29 KB

Reroll the patch #59 with Drupal 9.5.x

Shubham Chandra’s picture

Re-rolled patch #66 with Drupal 10.1.x

The last submitted patch, 66: fix-mangled-query-parameter-names-2984272-66.patch, failed testing. View results

Status: Needs review » Needs work

The last submitted patch, 67: 2984272-67.patch, failed testing. View results

NicholasS made their first commit to this issue’s fork.

Darren Oh’s picture

Status: Needs work » Needs review
smustgrave’s picture

Version: 9.5.x-dev » 11.x-dev
Status: Needs review » Needs work

seems to have a test failure

Did not test or review.

greggles’s picture

Issue summary: View changes

Thanks to those who have worked on this. The test fail is in a test introduced in this MR and appears to be worth researching and fixing.

I updated to issue summary based on my review of this issue and the related symfony issue.