-
-
Notifications
You must be signed in to change notification settings - Fork 35
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add a
ForceUriScheme
filter to ease removal of UriNormalize
filter
Signed-off-by: George Steel <[email protected]>
- Loading branch information
Showing
5 changed files
with
199 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Laminas\Filter; | ||
|
||
use Laminas\Filter\Exception\InvalidArgumentException; | ||
|
||
use function is_string; | ||
use function ltrim; | ||
use function parse_url; | ||
use function preg_match; | ||
use function preg_quote; | ||
use function preg_replace; | ||
use function sprintf; | ||
|
||
/** | ||
* @psalm-type Options = array{scheme: non-empty-string} | ||
*/ | ||
final class ForceUriScheme implements FilterInterface | ||
{ | ||
private const DEFAULT_SCHEME = 'https'; | ||
|
||
/** @var non-empty-string */ | ||
private string $scheme; | ||
|
||
/** @param Options $options */ | ||
public function __construct(array $options = ['scheme' => self::DEFAULT_SCHEME]) | ||
{ | ||
if (! preg_match('/^[a-z0-9]+$/i', $options['scheme'])) { | ||
throw new InvalidArgumentException(sprintf( | ||
'The `scheme` option should be a string consisting only of letters and numbers. Please omit the :// ' | ||
. ' Received "%s"', | ||
$options['scheme'], | ||
)); | ||
} | ||
|
||
$this->scheme = $options['scheme']; | ||
} | ||
|
||
public function __invoke(mixed $value): mixed | ||
{ | ||
return $this->filter($value); | ||
} | ||
|
||
public function filter(mixed $value): mixed | ||
{ | ||
if (! is_string($value) || $value === '') { | ||
return $value; | ||
} | ||
|
||
$url = parse_url($value); | ||
|
||
if (! isset($url['host']) || $url['host'] === '') { | ||
return $value; | ||
} | ||
|
||
if (! isset($url['scheme']) || $url['scheme'] === '') { | ||
return sprintf( | ||
'%s://%s', | ||
$this->scheme, | ||
ltrim($value, ':/'), | ||
); | ||
} | ||
|
||
$search = sprintf( | ||
'/^(%s)(.+)/', | ||
preg_quote($url['scheme'], '/'), | ||
); | ||
$replace = sprintf( | ||
'%s$2', | ||
$this->scheme, | ||
); | ||
|
||
return preg_replace($search, $replace, $value); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,96 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace LaminasTest\Filter; | ||
|
||
use Laminas\Filter\Exception\InvalidArgumentException; | ||
use Laminas\Filter\FilterPluginManager; | ||
use Laminas\Filter\ForceUriScheme; | ||
use PHPUnit\Framework\Attributes\CoversClass; | ||
use PHPUnit\Framework\Attributes\DataProvider; | ||
use PHPUnit\Framework\TestCase; | ||
use Psr\Container\ContainerInterface; | ||
|
||
#[CoversClass(ForceUriScheme::class)] | ||
class ForceUriSchemeTest extends TestCase | ||
{ | ||
/** @return list<array{0: non-empty-string, 1: mixed, 2: mixed}> */ | ||
public static function filterDataProvider(): array | ||
{ | ||
return [ | ||
['https', 'www.example.com/foo', 'www.example.com/foo'], | ||
['https', 'www.example.com', 'www.example.com'], | ||
['https', 'example.com', 'example.com'], | ||
['https', 'http://www.example.com', 'https://www.example.com'], | ||
['ftp', 'https://www.example.com', 'ftp://www.example.com'], | ||
['foobar5', 'https://www.example.com', 'foobar5://www.example.com'], | ||
['https', '//www.example.com', 'https://www.example.com'], | ||
['https', 'http://http.example.com', 'https://http.example.com'], | ||
['https', '42', '42'], | ||
['https', 42, 42], | ||
['https', false, false], | ||
['https', null, null], | ||
['https', (object) [], (object) []], | ||
]; | ||
} | ||
|
||
/** | ||
* @param non-empty-string $scheme | ||
*/ | ||
#[DataProvider('filterDataProvider')] | ||
public function testBasicFiltering(string $scheme, mixed $input, mixed $expect): void | ||
{ | ||
$filter = new ForceUriScheme(['scheme' => $scheme]); | ||
self::assertEquals($expect, $filter->filter($input)); | ||
} | ||
|
||
/** | ||
* @param non-empty-string $scheme | ||
*/ | ||
#[DataProvider('filterDataProvider')] | ||
public function testFilterCanBeInvoked(string $scheme, mixed $input, mixed $expect): void | ||
{ | ||
$filter = new ForceUriScheme(['scheme' => $scheme]); | ||
self::assertEquals($expect, $filter->__invoke($input)); | ||
} | ||
|
||
/** @return list<array{0: string}> */ | ||
public static function badSchemeProvider(): array | ||
{ | ||
return [ | ||
[''], | ||
['foo://'], | ||
['mailto:'], | ||
['...'], | ||
]; | ||
} | ||
|
||
#[DataProvider('badSchemeProvider')] | ||
public function testInvalidScheme(string $scheme): void | ||
{ | ||
$this->expectException(InvalidArgumentException::class); | ||
$this->expectExceptionMessage('The `scheme` option should be a string consisting only of letters and numbers'); | ||
|
||
/** @psalm-suppress ArgumentTypeCoercion */ | ||
new ForceUriScheme(['scheme' => $scheme]); | ||
} | ||
|
||
public function testThatThePluginManagerWillReturnAnInstance(): void | ||
{ | ||
$manager = new FilterPluginManager($this->createMock(ContainerInterface::class)); | ||
$filter = $manager->get(ForceUriScheme::class); | ||
self::assertInstanceOf(ForceUriScheme::class, $filter); | ||
|
||
self::assertSame('https://example.com', $filter->filter('ftp://example.com')); | ||
} | ||
|
||
public function testThatThePluginManagerCanBuildWithOptions(): void | ||
{ | ||
$manager = new FilterPluginManager($this->createMock(ContainerInterface::class)); | ||
$filter = $manager->build(ForceUriScheme::class, ['scheme' => 'muppets']); | ||
self::assertInstanceOf(ForceUriScheme::class, $filter); | ||
|
||
self::assertSame('muppets://example.com', $filter->filter('ftp://example.com')); | ||
} | ||
} |