Skip to content

Commit

Permalink
Merge pull request #92 from Tobion/uri-with-query-value-encoding
Browse files Browse the repository at this point in the history
Fix handling of encoding in Uri::with(out)QueryValue
  • Loading branch information
mtdowling authored Jun 20, 2016
2 parents 5882861 + 0d33e80 commit d591cba
Show file tree
Hide file tree
Showing 4 changed files with 78 additions and 35 deletions.
18 changes: 7 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ echo $stream; // 0123456789

Compose stream implementations based on a hash of functions.

Allows for easy testing and extension of a provided stream without needing
Allows for easy testing and extension of a provided stream without needing
to create a concrete class for a simple extension point.

```php
Expand Down Expand Up @@ -500,7 +500,7 @@ an associative array (e.g., `foo[a]=1&foo[b]=2` will be parsed into

Build a query string from an array of key value pairs.

This function can use the return value of parseQuery() to build a query string.
This function can use the return value of parse_query() to build a query string.
This function does not modify the provided keys when an array is encountered
(like http_build_query would).

Expand All @@ -526,7 +526,7 @@ The `GuzzleHttp\Psr7\Uri` class has several static methods to manipulate URIs.

## `GuzzleHttp\Psr7\Uri::removeDotSegments`

`public static function removeDotSegments($path) -> UriInterface`
`public static function removeDotSegments(string $path): string`

Removes dot segments from a path and returns the new path.

Expand All @@ -535,7 +535,7 @@ See http://tools.ietf.org/html/rfc3986#section-5.2.4

## `GuzzleHttp\Psr7\Uri::resolve`

`public static function resolve(UriInterface $base, $rel) -> UriInterface`
`public static function resolve(UriInterface $base, $rel): UriInterface`

Resolve a base URI with a relative URI and return a new URI.

Expand All @@ -544,30 +544,26 @@ See http://tools.ietf.org/html/rfc3986#section-5

## `GuzzleHttp\Psr7\Uri::withQueryValue`

`public static function withQueryValue(UriInterface $uri, $key, $value) -> UriInterface`
`public static function withQueryValue(UriInterface $uri, $key, $value): UriInterface`

Create a new URI with a specific query string value.

Any existing query string values that exactly match the provided key are
removed and replaced with the given key value pair.

Note: this function will convert "=" to "%3D" and "&" to "%26".


## `GuzzleHttp\Psr7\Uri::withoutQueryValue`

`public static function withoutQueryValue(UriInterface $uri, $key, $value) -> UriInterface`
`public static function withoutQueryValue(UriInterface $uri, $key): UriInterface`

Create a new URI with a specific query string value removed.

Any existing query string values that exactly match the provided key are
removed.

Note: this function will convert "=" to "%3D" and "&" to "%26".


## `GuzzleHttp\Psr7\Uri::fromParts`

`public static function fromParts(array $parts) -> UriInterface`
`public static function fromParts(array $parts): UriInterface`

Create a `GuzzleHttp\Psr7\Uri` object from a hash of `parse_url` parts.
39 changes: 19 additions & 20 deletions src/Uri.php
Original file line number Diff line number Diff line change
Expand Up @@ -178,10 +178,8 @@ public static function resolve(UriInterface $base, $rel)
* Any existing query string values that exactly match the provided key are
* removed.
*
* Note: this function will convert "=" to "%3D" and "&" to "%26".
*
* @param UriInterface $uri URI to use as a base.
* @param string $key Query string key value pair to remove.
* @param string $key Query string key to remove.
*
* @return UriInterface
*/
Expand All @@ -192,12 +190,10 @@ public static function withoutQueryValue(UriInterface $uri, $key)
return $uri;
}

$result = [];
foreach (explode('&', $current) as $part) {
if (explode('=', $part)[0] !== $key) {
$result[] = $part;
};
}
$decodedKey = rawurldecode($key);
$result = array_filter(explode('&', $current), function ($part) use ($decodedKey) {
return rawurldecode(explode('=', $part)[0]) !== $decodedKey;
});

return $uri->withQuery(implode('&', $result));
}
Expand All @@ -208,30 +204,33 @@ public static function withoutQueryValue(UriInterface $uri, $key)
* Any existing query string values that exactly match the provided key are
* removed and replaced with the given key value pair.
*
* Note: this function will convert "=" to "%3D" and "&" to "%26".
* A value of null will set the query string key without a value, e.g. "key"
* instead of "key=value".
*
* @param UriInterface $uri URI to use as a base.
* @param string $key Key to set.
* @param string $value Value to set.
* @param UriInterface $uri URI to use as a base.
* @param string $key Key to set.
* @param string|null $value Value to set
*
* @return UriInterface
*/
public static function withQueryValue(UriInterface $uri, $key, $value)
{
$current = $uri->getQuery();
$key = strtr($key, self::$replaceQuery);

if ($current == '') {
$result = [];
} else {
$result = [];
foreach (explode('&', $current) as $part) {
if (explode('=', $part)[0] !== $key) {
$result[] = $part;
};
}
$decodedKey = rawurldecode($key);
$result = array_filter(explode('&', $current), function ($part) use ($decodedKey) {
return rawurldecode(explode('=', $part)[0]) !== $decodedKey;
});
}

// Query string separators ("=", "&") within the key or value need to be encoded
// (while preventing double-encoding) before setting the query string. All other
// chars that need percent-encoding will be encoded by withQuery().
$key = strtr($key, self::$replaceQuery);

if ($value !== null) {
$result[] = $key . '=' . strtr($value, self::$replaceQuery);
} else {
Expand Down
6 changes: 3 additions & 3 deletions src/functions.php
Original file line number Diff line number Diff line change
Expand Up @@ -559,7 +559,7 @@ function parse_query($str, $urlEncoding = true)
/**
* Build a query string from an array of key value pairs.
*
* This function can use the return value of parseQuery() to build a query
* This function can use the return value of parse_query() to build a query
* string. This function does not modify the provided keys when an array is
* encountered (like http_build_query would).
*
Expand All @@ -577,9 +577,9 @@ function build_query(array $params, $encoding = PHP_QUERY_RFC3986)

if ($encoding === false) {
$encoder = function ($str) { return $str; };
} elseif ($encoding == PHP_QUERY_RFC3986) {
} elseif ($encoding === PHP_QUERY_RFC3986) {
$encoder = 'rawurlencode';
} elseif ($encoding == PHP_QUERY_RFC1738) {
} elseif ($encoding === PHP_QUERY_RFC1738) {
$encoder = 'urlencode';
} else {
throw new \InvalidArgumentException('Invalid type');
Expand Down
50 changes: 49 additions & 1 deletion tests/UriTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -298,13 +298,61 @@ public function testAddAndRemoveQueryValues()
$this->assertSame('a=b&c=d&e', $uri->getQuery());

$uri = Uri::withoutQueryValue($uri, 'c');
$this->assertSame('a=b&e', $uri->getQuery());
$uri = Uri::withoutQueryValue($uri, 'e');
$this->assertSame('a=b', $uri->getQuery());
$uri = Uri::withoutQueryValue($uri, 'a');
$uri = Uri::withoutQueryValue($uri, 'a');
$this->assertSame('', $uri->getQuery());
}

public function testWithQueryValueReplacesSameKeys()
{
$uri = new Uri();
$uri = Uri::withQueryValue($uri, 'a', 'b');
$uri = Uri::withQueryValue($uri, 'c', 'd');
$uri = Uri::withQueryValue($uri, 'a', 'e');
$this->assertSame('c=d&a=e', $uri->getQuery());
}

public function testWithoutQueryValueRemovesAllSameKeys()
{
$uri = (new Uri())->withQuery('a=b&c=d&a=e');
$uri = Uri::withoutQueryValue($uri, 'a');
$this->assertSame('c=d', $uri->getQuery());
}

public function testRemoveNonExistingQueryValue()
{
$uri = new Uri();
$uri = Uri::withQueryValue($uri, 'a', 'b');
$uri = Uri::withoutQueryValue($uri, 'c');
$this->assertSame('a=b', $uri->getQuery());
}

public function testWithQueryValueHandlesEncoding()
{
$uri = new Uri();
$uri = Uri::withQueryValue($uri, 'E=mc^2', 'ein&stein');
$this->assertSame('E%3Dmc%5E2=ein%26stein', $uri->getQuery(), 'Decoded key/value get encoded');

$uri = new Uri();
$uri = Uri::withQueryValue($uri, 'E%3Dmc%5e2', 'ein%26stein');
$this->assertSame('E%3Dmc%5e2=ein%26stein', $uri->getQuery(), 'Encoded key/value do not get double-encoded');
}

public function testWithoutQueryValueHandlesEncoding()
{
// It also tests that the case of the percent-encoding does not matter,
// i.e. both lowercase "%3d" and uppercase "%5E" can be removed.
$uri = (new Uri())->withQuery('E%3dmc%5E2=einstein&foo=bar');
$uri = Uri::withoutQueryValue($uri, 'E=mc^2');
$this->assertSame('foo=bar', $uri->getQuery(), 'Handles key in decoded form');

$uri = (new Uri())->withQuery('E%3dmc%5E2=einstein&foo=bar');
$uri = Uri::withoutQueryValue($uri, 'E%3Dmc%5e2');
$this->assertSame('foo=bar', $uri->getQuery(), 'Handles key in encoded form');
}

public function testSchemeIsNormalizedToLowercase()
{
$uri = new Uri('HTTP://example.com');
Expand Down

0 comments on commit d591cba

Please sign in to comment.