Browse Source

Use WHATWG percent-encoding rules

master
J. King 4 years ago
parent
commit
5147c7e919
  1. 33
      lib/Url.php
  2. 4
      tests/cases/Util/Url/Psr7TestCase.php
  3. 3
      tests/cases/Util/Url/UrlTest.php

33
lib/Url.php

@ -55,6 +55,13 @@ PCRE;
protected const PORT_PATTERN = '/^\d*$/'; protected const PORT_PATTERN = '/^\d*$/';
protected const FORBIDDEN_HOST_PATTERN = '/[\x{00}\t\n\r #%\/:\?@\[\]\\\]/'; protected const FORBIDDEN_HOST_PATTERN = '/[\x{00}\t\n\r #%\/:\?@\[\]\\\]/';
protected const WHITESPACE_CHARS = "\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1A\x1B\x1C\x1D\x1E\x1F\x20"; protected const WHITESPACE_CHARS = "\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1A\x1B\x1C\x1D\x1E\x1F\x20";
protected const PERCENT_ENCODE_SETS = [
'C0' => "",
'fragment' => " \"<>`",
'path' => " \"<>`?#{}",
'userinfo' => " \"<>`?#{}/:;=@[\]^|",
'query' => " \"<>#", // single-quote as well if scheme is special
];
protected const ESCAPE_CHARS = [ protected const ESCAPE_CHARS = [
'user' => [":", "@", "/", "?", "#"], 'user' => [":", "@", "/", "?", "#"],
'pass' => [":", "@", "/", "?", "#"], 'pass' => [":", "@", "/", "?", "#"],
@ -300,11 +307,11 @@ PCRE;
} }
protected function setUser(string $value): void { protected function setUser(string $value): void {
$this->user = $this->normalizeEncoding($value, "user"); $this->user = $this->percentEncode($value, "userinfo");
} }
protected function setPass(string $value): void { protected function setPass(string $value): void {
$this->pass = $this->normalizeEncoding($value, "pass"); $this->pass = $this->percentEncode($value, "userinfo");
} }
protected function setHost(?string $value): void { protected function setHost(?string $value): void {
@ -330,14 +337,14 @@ PCRE;
if ($this->specialScheme) { if ($this->specialScheme) {
$value = str_replace("\\", "/", $value); $value = str_replace("\\", "/", $value);
} }
$this->path = $this->normalizeEncoding($value, "path"); $this->path = $this->percentEncode($value, $this->isUrn() ? "C0" : "path");
} }
protected function setQuery(?string $value): void { protected function setQuery(?string $value): void {
if (is_null($value)) { if (is_null($value)) {
$this->query = $value; $this->query = $value;
} else { } else {
$this->query = $this->normalizeEncoding($value, "query"); $this->query = $this->percentEncode($value, "query");
} }
} }
@ -345,9 +352,25 @@ PCRE;
if (is_null($value)) { if (is_null($value)) {
$this->fragment = $value; $this->fragment = $value;
} else { } else {
$this->fragment = $this->normalizeEncoding($value, "fragment"); $this->fragment = $this->percentEncode($value, "fragment");
}
}
protected function percentEncode(string $data, string $type): string {
assert(array_key_exists($type, self::PERCENT_ENCODE_SETS), "Invalid percent-encoding set");
$out = "";
$end = strlen($data);
for ($p = 0; $p < $end; $p++) {
$c = $data[$p];
$o = ord($c);
if ($o > 0x1F && $o < 0x7F && !strspn($c, self::PERCENT_ENCODE_SETS[$type]) && !($this->specialScheme && $type === "query" && $c === "'")) {
$out .= $c;
} else {
$out .= strtoupper("%".str_pad(dechex($o), 2, "0", \STR_PAD_LEFT));
} }
} }
return $out;
}
protected function resolve(self $base): void { protected function resolve(self $base): void {
if ($base->isUrn()) { if ($base->isUrn()) {

4
tests/cases/Util/Url/Psr7TestCase.php

@ -215,7 +215,7 @@ abstract class Psr7TestCase extends TestCase {
public function queryProvider() { public function queryProvider() {
return [ return [
'normalized query' => ['foo.bar=%7evalue', 'foo.bar=~value'], 'normalized query' => ['foo.bar=%7evalue', 'foo.bar=%7evalue'],
'empty query' => ['', ''], 'empty query' => ['', ''],
'same param query' => ['foo.bar=1&foo.bar=1', 'foo.bar=1&foo.bar=1'], 'same param query' => ['foo.bar=1&foo.bar=1', 'foo.bar=1&foo.bar=1'],
'same param query' => ['?foo=1', '?foo=1'], 'same param query' => ['?foo=1', '?foo=1'],
@ -286,7 +286,7 @@ abstract class Psr7TestCase extends TestCase {
'path' => '/%7ejohndoe/%a1/index.php', 'path' => '/%7ejohndoe/%a1/index.php',
'query' => 'foo.bar=%7evalue', 'query' => 'foo.bar=%7evalue',
'fragment' => 'fragment', 'fragment' => 'fragment',
'uri' => 'https://iGoR:rAsMuZeN@master.example.com/~johndoe/%A1/index.php?foo.bar=~value#fragment', 'uri' => 'https://iGoR:rAsMuZeN@master.example.com/%7ejohndoe/%a1/index.php?foo.bar=%7evalue#fragment',
], ],
'URL without scheme' => [ 'URL without scheme' => [
'scheme' => '', 'scheme' => '',

3
tests/cases/Util/Url/UrlTest.php

@ -11,9 +11,6 @@ use MensBeam\Lax\Url;
/** @covers MensBeam\Lax\Url<extended> */ /** @covers MensBeam\Lax\Url<extended> */
class UrlTest extends Psr7TestCase { class UrlTest extends Psr7TestCase {
private const INCOMPLETE_STD_INPUT = [ private const INCOMPLETE_STD_INPUT = [
"a:\t foo.com",
"lolscheme:x x#x x",
"http://&a:foo(b]c@d:2/",
]; ];
protected function createUri($uri = '') { protected function createUri($uri = '') {

Loading…
Cancel
Save