Skip to content

Commit 9d7a3c2

Browse files
[HttpFoundation] Add HeaderRequestMatcher
1 parent 7be1c03 commit 9d7a3c2

File tree

3 files changed

+137
-0
lines changed

3 files changed

+137
-0
lines changed

src/Symfony/Component/HttpFoundation/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ CHANGELOG
77
* Make `HeaderBag::getDate()`, `Response::getDate()`, `getExpires()` and `getLastModified()` return a `DateTimeImmutable`
88
* Support root-level `Generator` in `StreamedJsonResponse`
99
* Add `UriSigner` from the HttpKernel component
10+
* Add `HeaderRequestMatcher`
1011

1112
6.3
1213
---
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\HttpFoundation\RequestMatcher;
13+
14+
use Symfony\Component\HttpFoundation\Request;
15+
use Symfony\Component\HttpFoundation\RequestMatcherInterface;
16+
17+
/**
18+
* Checks the presence of HTTP headers in a Request.
19+
*
20+
* @author Alexandre Daubois <alex.daubois@gmail.com>
21+
*/
22+
class HeaderRequestMatcher implements RequestMatcherInterface
23+
{
24+
/**
25+
* @var string[]
26+
*/
27+
private array $headers;
28+
29+
/**
30+
* @param string[]|string $headers A header or a list of headers
31+
* Strings can contain a comma-delimited list of headers
32+
*/
33+
public function __construct(array|string $headers)
34+
{
35+
$this->headers = array_reduce((array) $headers, static fn (array $headers, string $header) => array_merge($headers, preg_split('/\s*,\s*/', $header)), []);
36+
}
37+
38+
public function matches(Request $request): bool
39+
{
40+
if (!$this->headers) {
41+
return true;
42+
}
43+
44+
foreach ($this->headers as $header) {
45+
if (!$request->headers->has($header)) {
46+
return false;
47+
}
48+
}
49+
50+
return true;
51+
}
52+
}
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\HttpFoundation\Tests\RequestMatcher;
13+
14+
use PHPUnit\Framework\TestCase;
15+
use Symfony\Component\HttpFoundation\Request;
16+
use Symfony\Component\HttpFoundation\RequestMatcher\HeaderRequestMatcher;
17+
18+
class HeaderRequestMatcherTest extends TestCase
19+
{
20+
/**
21+
* @dataProvider getDataForArray
22+
*/
23+
public function testArray(array $headers, bool $matches)
24+
{
25+
$matcher = new HeaderRequestMatcher(['x-foo', 'bar']);
26+
27+
$request = Request::create('https://example.com');
28+
foreach ($headers as $k => $v) {
29+
$request->headers->set($k, $v);
30+
}
31+
32+
$this->assertSame($matches, $matcher->matches($request));
33+
}
34+
35+
/**
36+
* @dataProvider getDataForArray
37+
*/
38+
public function testCommaSeparatedString(array $headers, bool $matches)
39+
{
40+
$matcher = new HeaderRequestMatcher('x-foo, bar');
41+
42+
$request = Request::create('https://example.com');
43+
foreach ($headers as $k => $v) {
44+
$request->headers->set($k, $v);
45+
}
46+
47+
$this->assertSame($matches, $matcher->matches($request));
48+
}
49+
50+
/**
51+
* @dataProvider getDataForSingleString
52+
*/
53+
public function testSingleString(array $headers, bool $matches)
54+
{
55+
$matcher = new HeaderRequestMatcher('x-foo');
56+
57+
$request = Request::create('https://example.com');
58+
foreach ($headers as $k => $v) {
59+
$request->headers->set($k, $v);
60+
}
61+
62+
$this->assertSame($matches, $matcher->matches($request));
63+
}
64+
65+
public static function getDataForArray(): \Generator
66+
{
67+
yield 'Superfluous header' => [['X-Foo' => 'foo', 'bar' => 'bar', 'baz' => 'baz'], true];
68+
yield 'Exact match' => [['X-Foo' => 'foo', 'bar' => 'bar'], true];
69+
yield 'Case insensitivity' => [['x-foo' => 'foo', 'BAR' => 'bar'], true];
70+
yield 'Only one header matching' => [['bar' => 'bar', 'baz' => 'baz'], false];
71+
yield 'Only one header' => [['X-foo' => 'foo'], false];
72+
yield 'Header name as a value' => [['X-foo'], false];
73+
yield 'Empty headers' => [[], false];
74+
}
75+
76+
public static function getDataForSingleString(): \Generator
77+
{
78+
yield 'Superfluous header' => [['X-Foo' => 'foo', 'bar' => 'bar'], true];
79+
yield 'Exact match' => [['X-foo' => 'foo'], true];
80+
yield 'Case insensitivity' => [['x-foo' => 'foo'], true];
81+
yield 'Header name as a value' => [['X-foo'], false];
82+
yield 'Empty headers' => [[], false];
83+
}
84+
}

0 commit comments

Comments
 (0)