0

This function is pretty cool and works well in all except one situation of mine.

/**
 * 
 * @param string $url
 * @param array $params
 * @return string
 */
public static function getUrlWithUpdatedParams($url, $params) {
    $uri = \League\Uri\Http::createFromString($url);
    $query = \League\Uri\Components\Query::createFromParams($params);
    return \League\Uri\merge_query($uri, $query)->__toString();
}

I'm using "league/uri": "^5.3" (docs).

Here are my tests, where all assertions pass except the last one.

public function testGetUrlWithUpdatedParams() {
    $this->assertEquals(url('/yo') . '?a=7', ST::getUrlWithUpdatedParams(url('/yo'), ['a' => 7]));
    $this->assertEquals(url('/demo') . '?a=6&email=a@b.com', ST::getUrlWithUpdatedParams(url('/demo'), ['a' => 6, 'email' => 'a@b.com']));
    $this->assertEquals(url('/sample') . '?ctrl=1&a=8', ST::getUrlWithUpdatedParams(url('/sample?ctrl=1'), ['a' => 8]));
    $this->assertEquals(url('/sample') . '?ctrl=0&c=5', ST::getUrlWithUpdatedParams(url('/sample?ctrl=1'), ['c' => 5, 'ctrl' => 0]));
    $this->assertEquals(url('/sample') . '?ctrl=1', ST::getUrlWithUpdatedParams(url('/sample?ctrl=1'), []));
    $this->assertEquals(url('/sample') . '?ctrl=1', ST::getUrlWithUpdatedParams(url('/sample?ctrl=1'), ['ctrl' => null]));
    $this->assertEquals(url('/sample') . '?ctrl=', ST::getUrlWithUpdatedParams(url('/sample?ctrl=1'), ['ctrl' => '']));
    $encodedUrlAsParam = rawurlencode(url('/test'));
    $this->assertEquals(url('/demo') . '?email_=a@b.com&url=' . $encodedUrlAsParam, ST::getUrlWithUpdatedParams(url('/demo'), ['email_' => 'a@b.com', 'url' => $encodedUrlAsParam])); //This one FAILS! Why in the world does getUrlWithUpdatedParams rawurlencode a second time?? 
}

I figured out that I could get the final assertion to pass by double rawurlencoding the param as follows:

$this->assertEquals(url('/demo') . '?email_=a@b.com&url=' . rawurlencode($encodedUrlAsParam), ST::getUrlWithUpdatedParams(url('/demo'), ['email_' => 'a@b.com', 'url' => $encodedUrlAsParam]));

But it feels weird to me to then be double decoding later when I want to retrieve the URL param from the main URL.

What's happening, and how can I avoid this?

Ryan
  • 22,332
  • 31
  • 176
  • 357

1 Answers1

0

Just don't encode the URL before passing it to getUrlWithUpdatedParams. I think, this one should work:

$qs = [
    'email_' => 'a@b.com',
    'url' => url('/test'),
];

$url = url('/demo');
$this->assertEquals($url.'?'.http_build_query($qs), ST::getUrlWithUpdatedParams($url, $qs));
Victor
  • 5,493
  • 1
  • 27
  • 28
  • Thanks, but I don't think it's safe to pass a URL as a param within another URL unless it's encoded. Plus, your answer doesn't describe why/where it's getting double encoded. – Ryan Dec 09 '18 at 20:23
  • @Ryan The `getUrlWithUpdatedParams()` function already encodes all values of the `$params` variable. Your mistake is that you pass to it an encoded value and this is why it is double encoded. Check carefully my example and guess what does the `http_build_query()` function ;) – Victor Dec 09 '18 at 21:18
  • that's incorrect. Nothing in my getUrlWithUpdatedParams function encodes in this case (a test that fails but would pass if you were correct): `$redirectUrl = 'https://redirect.com?a=1'; $this->assertEquals('http://sample.com/demo?b=2&email=c@d.com&url=' . rawurlencode($redirectUrl), ST::getUrlWithUpdatedParams('http://sample.com/demo', ['b' => 2, 'email' => 'c@d.com', 'url' => $redirectUrl]));` It outputs `http://sample.com/demo?b=2&email=c@d.com&url=https://redirect.com?a=1` instead of `http://sample.com/demo?b=2&email=c@d.com&url=https%3A%2F%2Fredirect.com%3Fa%3D1` – Ryan Dec 09 '18 at 21:28
  • Did you test my snippet? Anyway, in the `getUrlWithUpdatedParams()` function all values of the `$params` variable are encoded to RFC3986 using the `\League\Uri\Components\Query::createFromParams()`. Check the [source](https://github.com/thephpleague/uri-components/blob/master/src/Component/Query.php#L95) for proofs. – Victor Dec 09 '18 at 22:45
  • Yes, I tested it. Yours outputs `https://sample.com/demo?email_=a@b.com&url=https://sample.com/test`, and you can see that the `url` param is not encoded. So that's not what I want. – Ryan Dec 09 '18 at 22:52
  • @Ryan Ok then. What prints the: `echo ST::getUrlWithUpdatedParams('http://sample.com/demo', ['b' => 2, 'email' => 'c@d.com', 'url' => 'https://redirect.com?a=1'])`? – Victor Dec 09 '18 at 22:52