diff --git a/src/Validator/URL.php b/src/Validator/URL.php index bb26568..a3a4f63 100644 --- a/src/Validator/URL.php +++ b/src/Validator/URL.php @@ -17,14 +17,18 @@ class URL extends Validator protected bool $allowEmpty; + protected bool $allowFragments; + /** * @param array $allowedSchemes * @param bool $allowEmpty + * @param bool $allowFragments */ - public function __construct(array $allowedSchemes = [], bool $allowEmpty = false) + public function __construct(array $allowedSchemes = [], bool $allowEmpty = false, bool $allowFragments = true) { $this->allowedSchemes = $allowedSchemes; $this->allowEmpty = $allowEmpty; + $this->allowFragments = $allowFragments; } /** @@ -37,7 +41,17 @@ public function __construct(array $allowedSchemes = [], bool $allowEmpty = false public function getDescription(): string { if (!empty($this->allowedSchemes)) { - return 'Value must be a valid URL with following schemes (' . \implode(', ', $this->allowedSchemes) . ')'; + $description = 'Value must be a valid URL with following schemes (' . \implode(', ', $this->allowedSchemes) . ')'; + + if (!$this->allowFragments) { + $description .= ' and without a fragment component'; + } + + return $description; + } + + if (!$this->allowFragments) { + return 'Value must be a valid URL without a fragment component'; } return 'Value must be a valid URL'; @@ -65,6 +79,10 @@ public function isValid($value): bool return false; } + if (!$this->allowFragments && \parse_url($value, PHP_URL_FRAGMENT) !== null) { + return false; + } + return true; } diff --git a/tests/Validator/URLTest.php b/tests/Validator/URLTest.php index d7d010e..bfa876d 100644 --- a/tests/Validator/URLTest.php +++ b/tests/Validator/URLTest.php @@ -42,6 +42,7 @@ public function testIsValid(): void $this->assertSame(false, $this->url->isValid('htt@s://example.com')); $this->assertSame(true, $this->url->isValid('http://www.example.com/foo%2\u00c2\u00a9zbar')); $this->assertSame(true, $this->url->isValid('http://www.example.com/?q=%3Casdf%3E')); + $this->assertSame(true, $this->url->isValid('https://example.com/callback#fragment')); } public function testIsValidAllowedSchemes(): void @@ -64,4 +65,24 @@ public function testAllowEmpty(): void $this->assertSame(false, $this->url->isValid('')); $this->assertSame(false, $this->url->isValid(null)); } + + public function testDisallowFragments(): void + { + $urlWithoutFragments = new URL(allowFragments: false); + + $this->assertSame('Value must be a valid URL without a fragment component', $urlWithoutFragments->getDescription()); + $this->assertSame(true, $urlWithoutFragments->isValid('https://example.com/callback')); + $this->assertSame(false, $urlWithoutFragments->isValid('https://example.com/callback#fragment')); + $this->assertSame(false, $urlWithoutFragments->isValid('https://example.com/callback#')); + } + + public function testDisallowFragmentsAllowedSchemes(): void + { + $urlWithoutFragments = new URL(['http', 'https'], allowFragments: false); + + $this->assertSame('Value must be a valid URL with following schemes (http, https) and without a fragment component', $urlWithoutFragments->getDescription()); + $this->assertSame(true, $urlWithoutFragments->isValid('https://example.com/callback')); + $this->assertSame(false, $urlWithoutFragments->isValid('https://example.com/callback#fragment')); + $this->assertSame(false, $urlWithoutFragments->isValid('gopher://www.example.com')); + } }