From 62b5a34a69f624e668fe1b46fe6dbdf253f3f235 Mon Sep 17 00:00:00 2001 From: Alexandre Daubois Date: Thu, 10 Aug 2023 15:04:28 +0200 Subject: [PATCH] [HttpFoundation] Add `HeaderRequestMatcher` --- .../Component/HttpFoundation/CHANGELOG.md | 1 + .../RequestMatcher/HeaderRequestMatcher.php | 52 ++++++++++++ .../HeaderRequestMatcherTest.php | 84 +++++++++++++++++++ 3 files changed, 137 insertions(+) create mode 100644 src/Symfony/Component/HttpFoundation/RequestMatcher/HeaderRequestMatcher.php create mode 100644 src/Symfony/Component/HttpFoundation/Tests/RequestMatcher/HeaderRequestMatcherTest.php diff --git a/src/Symfony/Component/HttpFoundation/CHANGELOG.md b/src/Symfony/Component/HttpFoundation/CHANGELOG.md index c3f62a9267f35..a26edc4626a77 100644 --- a/src/Symfony/Component/HttpFoundation/CHANGELOG.md +++ b/src/Symfony/Component/HttpFoundation/CHANGELOG.md @@ -6,6 +6,7 @@ CHANGELOG * Add `UploadedFile::getClientOriginalPath()` * Add `QueryParameterRequestMatcher` + * Add `HeaderRequestMatcher` 7.0 --- diff --git a/src/Symfony/Component/HttpFoundation/RequestMatcher/HeaderRequestMatcher.php b/src/Symfony/Component/HttpFoundation/RequestMatcher/HeaderRequestMatcher.php new file mode 100644 index 0000000000000..8617a8aca40b9 --- /dev/null +++ b/src/Symfony/Component/HttpFoundation/RequestMatcher/HeaderRequestMatcher.php @@ -0,0 +1,52 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\RequestMatcher; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\RequestMatcherInterface; + +/** + * Checks the presence of HTTP headers in a Request. + * + * @author Alexandre Daubois + */ +class HeaderRequestMatcher implements RequestMatcherInterface +{ + /** + * @var string[] + */ + private array $headers; + + /** + * @param string[]|string $headers A header or a list of headers + * Strings can contain a comma-delimited list of headers + */ + public function __construct(array|string $headers) + { + $this->headers = array_reduce((array) $headers, static fn (array $headers, string $header) => array_merge($headers, preg_split('/\s*,\s*/', $header)), []); + } + + public function matches(Request $request): bool + { + if (!$this->headers) { + return true; + } + + foreach ($this->headers as $header) { + if (!$request->headers->has($header)) { + return false; + } + } + + return true; + } +} diff --git a/src/Symfony/Component/HttpFoundation/Tests/RequestMatcher/HeaderRequestMatcherTest.php b/src/Symfony/Component/HttpFoundation/Tests/RequestMatcher/HeaderRequestMatcherTest.php new file mode 100644 index 0000000000000..47a5c7ee83ae4 --- /dev/null +++ b/src/Symfony/Component/HttpFoundation/Tests/RequestMatcher/HeaderRequestMatcherTest.php @@ -0,0 +1,84 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Tests\RequestMatcher; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\RequestMatcher\HeaderRequestMatcher; + +class HeaderRequestMatcherTest extends TestCase +{ + /** + * @dataProvider getDataForArray + */ + public function testArray(array $headers, bool $matches) + { + $matcher = new HeaderRequestMatcher(['x-foo', 'bar']); + + $request = Request::create('https://example.com'); + foreach ($headers as $k => $v) { + $request->headers->set($k, $v); + } + + $this->assertSame($matches, $matcher->matches($request)); + } + + /** + * @dataProvider getDataForArray + */ + public function testCommaSeparatedString(array $headers, bool $matches) + { + $matcher = new HeaderRequestMatcher('x-foo, bar'); + + $request = Request::create('https://example.com'); + foreach ($headers as $k => $v) { + $request->headers->set($k, $v); + } + + $this->assertSame($matches, $matcher->matches($request)); + } + + /** + * @dataProvider getDataForSingleString + */ + public function testSingleString(array $headers, bool $matches) + { + $matcher = new HeaderRequestMatcher('x-foo'); + + $request = Request::create('https://example.com'); + foreach ($headers as $k => $v) { + $request->headers->set($k, $v); + } + + $this->assertSame($matches, $matcher->matches($request)); + } + + public static function getDataForArray(): \Generator + { + yield 'Superfluous header' => [['X-Foo' => 'foo', 'bar' => 'bar', 'baz' => 'baz'], true]; + yield 'Exact match' => [['X-Foo' => 'foo', 'bar' => 'bar'], true]; + yield 'Case insensitivity' => [['x-foo' => 'foo', 'BAR' => 'bar'], true]; + yield 'Only one header matching' => [['bar' => 'bar', 'baz' => 'baz'], false]; + yield 'Only one header' => [['X-foo' => 'foo'], false]; + yield 'Header name as a value' => [['X-foo'], false]; + yield 'Empty headers' => [[], false]; + } + + public static function getDataForSingleString(): \Generator + { + yield 'Superfluous header' => [['X-Foo' => 'foo', 'bar' => 'bar'], true]; + yield 'Exact match' => [['X-foo' => 'foo'], true]; + yield 'Case insensitivity' => [['x-foo' => 'foo'], true]; + yield 'Header name as a value' => [['X-foo'], false]; + yield 'Empty headers' => [[], false]; + } +}