Skip to content

Commit 335e5dd

Browse files
committed
Merge branch '6.4' into 7.0
* 6.4: (43 commits) [AssetMapper] Fix entrypoint scripts are not preloaded Fix typo in method resolvePackages Make FormPerformanceTestCase compatible with PHPUnit 10 Avoid calling getInvocationCount() [AssetMapper] Always downloading vendor files [Security] Fix resetting traceable listeners [HttpClient] Fix type error with http_version 1.1 [DependencyInjection] Add tests for `AutowireLocator`/`AutowireIterator` [DependencyInjection] Add `#[AutowireIterator]` attribute and improve `#[AutowireLocator]` Update documentation link Fix typo that causes unit test to fail Fix CS [AssetMapper] Add audit command [Mailer] Use idn encoded address otherwise Brevo throws an error [Messenger] Resend failed retries back to failure transport [FrameworkBundle] Fix call to invalid method in NotificationAssertionsTrait [Validator] Add missing italian translations [Notifier] Fix failing testcase Fix order array sum normalizedData and nestedData Add test for 0 and '0' in PeriodicalTrigger Fix '0' case error and remove duplicate code ...
2 parents c99c4ec + 27a2cfc commit 335e5dd

File tree

4 files changed

+243
-45
lines changed

4 files changed

+243
-45
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ CHANGELOG
1717

1818
* Make `HeaderBag::getDate()`, `Response::getDate()`, `getExpires()` and `getLastModified()` return a `DateTimeImmutable`
1919
* Support root-level `Generator` in `StreamedJsonResponse`
20+
* Add `UriSigner` from the HttpKernel component
2021

2122
6.3
2223
---

Request.php

Lines changed: 45 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -122,31 +122,31 @@ class Request
122122
protected $content;
123123

124124
/**
125-
* @var string[]
125+
* @var string[]|null
126126
*/
127-
protected array $languages;
127+
protected ?array $languages = null;
128128

129129
/**
130-
* @var string[]
130+
* @var string[]|null
131131
*/
132-
protected array $charsets;
132+
protected ?array $charsets = null;
133133

134134
/**
135-
* @var string[]
135+
* @var string[]|null
136136
*/
137-
protected array $encodings;
137+
protected ?array $encodings = null;
138138

139139
/**
140-
* @var string[]
140+
* @var string[]|null
141141
*/
142-
protected array $acceptableContentTypes;
142+
protected ?array $acceptableContentTypes = null;
143143

144-
protected string $pathInfo;
145-
protected string $requestUri;
146-
protected string $baseUrl;
147-
protected string $basePath;
148-
protected string $method;
149-
protected ?string $format;
144+
protected ?string $pathInfo = null;
145+
protected ?string $requestUri = null;
146+
protected ?string $baseUrl = null;
147+
protected ?string $basePath = null;
148+
protected ?string $method = null;
149+
protected ?string $format = null;
150150
protected SessionInterface|\Closure|null $session = null;
151151
protected ?string $locale = null;
152152
protected string $defaultLocale = 'en';
@@ -231,16 +231,16 @@ public function initialize(array $query = [], array $request = [], array $attrib
231231
$this->headers = new HeaderBag($this->server->getHeaders());
232232

233233
$this->content = $content;
234-
unset($this->languages);
235-
unset($this->charsets);
236-
unset($this->encodings);
237-
unset($this->acceptableContentTypes);
238-
unset($this->pathInfo);
239-
unset($this->requestUri);
240-
unset($this->baseUrl);
241-
unset($this->basePath);
242-
unset($this->method);
243-
unset($this->format);
234+
$this->languages = null;
235+
$this->charsets = null;
236+
$this->encodings = null;
237+
$this->acceptableContentTypes = null;
238+
$this->pathInfo = null;
239+
$this->requestUri = null;
240+
$this->baseUrl = null;
241+
$this->basePath = null;
242+
$this->method = null;
243+
$this->format = null;
244244
}
245245

246246
/**
@@ -414,16 +414,16 @@ public function duplicate(array $query = null, array $request = null, array $att
414414
$dup->server = new ServerBag($server);
415415
$dup->headers = new HeaderBag($dup->server->getHeaders());
416416
}
417-
unset($dup->languages);
418-
unset($dup->charsets);
419-
unset($dup->encodings);
420-
unset($dup->acceptableContentTypes);
421-
unset($dup->pathInfo);
422-
unset($dup->requestUri);
423-
unset($dup->baseUrl);
424-
unset($dup->basePath);
425-
unset($dup->method);
426-
unset($dup->format);
417+
$dup->languages = null;
418+
$dup->charsets = null;
419+
$dup->encodings = null;
420+
$dup->acceptableContentTypes = null;
421+
$dup->pathInfo = null;
422+
$dup->requestUri = null;
423+
$dup->baseUrl = null;
424+
$dup->basePath = null;
425+
$dup->method = null;
426+
$dup->format = null;
427427

428428
if (!$dup->get('_format') && $this->get('_format')) {
429429
$dup->attributes->set('_format', $this->get('_format'));
@@ -1115,7 +1115,7 @@ public function getHost(): string
11151115
*/
11161116
public function setMethod(string $method): void
11171117
{
1118-
unset($this->method);
1118+
$this->method = null;
11191119
$this->server->set('REQUEST_METHOD', $method);
11201120
}
11211121

@@ -1134,7 +1134,7 @@ public function setMethod(string $method): void
11341134
*/
11351135
public function getMethod(): string
11361136
{
1137-
if (isset($this->method)) {
1137+
if (null !== $this->method) {
11381138
return $this->method;
11391139
}
11401140

@@ -1182,7 +1182,7 @@ public function getRealMethod(): string
11821182
*/
11831183
public function getMimeType(string $format): ?string
11841184
{
1185-
if (!isset(static::$formats)) {
1185+
if (null === static::$formats) {
11861186
static::initializeFormats();
11871187
}
11881188

@@ -1196,7 +1196,7 @@ public function getMimeType(string $format): ?string
11961196
*/
11971197
public static function getMimeTypes(string $format): array
11981198
{
1199-
if (!isset(static::$formats)) {
1199+
if (null === static::$formats) {
12001200
static::initializeFormats();
12011201
}
12021202

@@ -1213,7 +1213,7 @@ public function getFormat(?string $mimeType): ?string
12131213
$canonicalMimeType = trim(substr($mimeType, 0, $pos));
12141214
}
12151215

1216-
if (!isset(static::$formats)) {
1216+
if (null === static::$formats) {
12171217
static::initializeFormats();
12181218
}
12191219

@@ -1236,7 +1236,7 @@ public function getFormat(?string $mimeType): ?string
12361236
*/
12371237
public function setFormat(?string $format, string|array $mimeTypes): void
12381238
{
1239-
if (!isset(static::$formats)) {
1239+
if (null === static::$formats) {
12401240
static::initializeFormats();
12411241
}
12421242

@@ -1499,13 +1499,13 @@ public function isNoCache(): bool
14991499
*/
15001500
public function getPreferredFormat(?string $default = 'html'): ?string
15011501
{
1502-
if (isset($this->preferredFormat) || null !== $preferredFormat = $this->getRequestFormat(null)) {
1503-
return $this->preferredFormat ??= $preferredFormat;
1502+
if ($this->preferredFormat ??= $this->getRequestFormat(null)) {
1503+
return $this->preferredFormat;
15041504
}
15051505

15061506
foreach ($this->getAcceptableContentTypes() as $mimeType) {
1507-
if ($preferredFormat = $this->getFormat($mimeType)) {
1508-
return $this->preferredFormat = $preferredFormat;
1507+
if ($this->preferredFormat = $this->getFormat($mimeType)) {
1508+
return $this->preferredFormat;
15091509
}
15101510
}
15111511

@@ -1552,7 +1552,7 @@ public function getPreferredLanguage(array $locales = null): ?string
15521552
*/
15531553
public function getLanguages(): array
15541554
{
1555-
if (isset($this->languages)) {
1555+
if (null !== $this->languages) {
15561556
return $this->languages;
15571557
}
15581558

Tests/UriSignerTest.php

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
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;
13+
14+
use PHPUnit\Framework\TestCase;
15+
use Symfony\Component\HttpFoundation\Request;
16+
use Symfony\Component\HttpFoundation\UriSigner;
17+
18+
class UriSignerTest extends TestCase
19+
{
20+
public function testSign()
21+
{
22+
$signer = new UriSigner('foobar');
23+
24+
$this->assertStringContainsString('?_hash=', $signer->sign('http://example.com/foo'));
25+
$this->assertStringContainsString('?_hash=', $signer->sign('http://example.com/foo?foo=bar'));
26+
$this->assertStringContainsString('&foo=', $signer->sign('http://example.com/foo?foo=bar'));
27+
}
28+
29+
public function testCheck()
30+
{
31+
$signer = new UriSigner('foobar');
32+
33+
$this->assertFalse($signer->check('http://example.com/foo?_hash=foo'));
34+
$this->assertFalse($signer->check('http://example.com/foo?foo=bar&_hash=foo'));
35+
$this->assertFalse($signer->check('http://example.com/foo?foo=bar&_hash=foo&bar=foo'));
36+
37+
$this->assertTrue($signer->check($signer->sign('http://example.com/foo')));
38+
$this->assertTrue($signer->check($signer->sign('http://example.com/foo?foo=bar')));
39+
$this->assertTrue($signer->check($signer->sign('http://example.com/foo?foo=bar&0=integer')));
40+
41+
$this->assertSame($signer->sign('http://example.com/foo?foo=bar&bar=foo'), $signer->sign('http://example.com/foo?bar=foo&foo=bar'));
42+
}
43+
44+
public function testCheckWithDifferentArgSeparator()
45+
{
46+
$this->iniSet('arg_separator.output', '&amp;');
47+
$signer = new UriSigner('foobar');
48+
49+
$this->assertSame(
50+
'http://example.com/foo?_hash=rIOcC%2FF3DoEGo%2FvnESjSp7uU9zA9S%2F%2BOLhxgMexoPUM%3D&baz=bay&foo=bar',
51+
$signer->sign('http://example.com/foo?foo=bar&baz=bay')
52+
);
53+
$this->assertTrue($signer->check($signer->sign('http://example.com/foo?foo=bar&baz=bay')));
54+
}
55+
56+
public function testCheckWithRequest()
57+
{
58+
$signer = new UriSigner('foobar');
59+
60+
$this->assertTrue($signer->checkRequest(Request::create($signer->sign('http://example.com/foo'))));
61+
$this->assertTrue($signer->checkRequest(Request::create($signer->sign('http://example.com/foo?foo=bar'))));
62+
$this->assertTrue($signer->checkRequest(Request::create($signer->sign('http://example.com/foo?foo=bar&0=integer'))));
63+
}
64+
65+
public function testCheckWithDifferentParameter()
66+
{
67+
$signer = new UriSigner('foobar', 'qux');
68+
69+
$this->assertSame(
70+
'http://example.com/foo?baz=bay&foo=bar&qux=rIOcC%2FF3DoEGo%2FvnESjSp7uU9zA9S%2F%2BOLhxgMexoPUM%3D',
71+
$signer->sign('http://example.com/foo?foo=bar&baz=bay')
72+
);
73+
$this->assertTrue($signer->check($signer->sign('http://example.com/foo?foo=bar&baz=bay')));
74+
}
75+
76+
public function testSignerWorksWithFragments()
77+
{
78+
$signer = new UriSigner('foobar');
79+
80+
$this->assertSame(
81+
'http://example.com/foo?_hash=EhpAUyEobiM3QTrKxoLOtQq5IsWyWedoXDPqIjzNj5o%3D&bar=foo&foo=bar#foobar',
82+
$signer->sign('http://example.com/foo?bar=foo&foo=bar#foobar')
83+
);
84+
$this->assertTrue($signer->check($signer->sign('http://example.com/foo?bar=foo&foo=bar#foobar')));
85+
}
86+
}

UriSigner.php

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
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;
13+
14+
/**
15+
* Signs URIs.
16+
*
17+
* @author Fabien Potencier <fabien@symfony.com>
18+
*/
19+
class UriSigner
20+
{
21+
private string $secret;
22+
private string $parameter;
23+
24+
/**
25+
* @param string $secret A secret
26+
* @param string $parameter Query string parameter to use
27+
*/
28+
public function __construct(#[\SensitiveParameter] string $secret, string $parameter = '_hash')
29+
{
30+
$this->secret = $secret;
31+
$this->parameter = $parameter;
32+
}
33+
34+
/**
35+
* Signs a URI.
36+
*
37+
* The given URI is signed by adding the query string parameter
38+
* which value depends on the URI and the secret.
39+
*/
40+
public function sign(string $uri): string
41+
{
42+
$url = parse_url($uri);
43+
$params = [];
44+
45+
if (isset($url['query'])) {
46+
parse_str($url['query'], $params);
47+
}
48+
49+
$uri = $this->buildUrl($url, $params);
50+
$params[$this->parameter] = $this->computeHash($uri);
51+
52+
return $this->buildUrl($url, $params);
53+
}
54+
55+
/**
56+
* Checks that a URI contains the correct hash.
57+
*/
58+
public function check(string $uri): bool
59+
{
60+
$url = parse_url($uri);
61+
$params = [];
62+
63+
if (isset($url['query'])) {
64+
parse_str($url['query'], $params);
65+
}
66+
67+
if (empty($params[$this->parameter])) {
68+
return false;
69+
}
70+
71+
$hash = $params[$this->parameter];
72+
unset($params[$this->parameter]);
73+
74+
return hash_equals($this->computeHash($this->buildUrl($url, $params)), $hash);
75+
}
76+
77+
public function checkRequest(Request $request): bool
78+
{
79+
$qs = ($qs = $request->server->get('QUERY_STRING')) ? '?'.$qs : '';
80+
81+
// we cannot use $request->getUri() here as we want to work with the original URI (no query string reordering)
82+
return $this->check($request->getSchemeAndHttpHost().$request->getBaseUrl().$request->getPathInfo().$qs);
83+
}
84+
85+
private function computeHash(string $uri): string
86+
{
87+
return base64_encode(hash_hmac('sha256', $uri, $this->secret, true));
88+
}
89+
90+
private function buildUrl(array $url, array $params = []): string
91+
{
92+
ksort($params, \SORT_STRING);
93+
$url['query'] = http_build_query($params, '', '&');
94+
95+
$scheme = isset($url['scheme']) ? $url['scheme'].'://' : '';
96+
$host = $url['host'] ?? '';
97+
$port = isset($url['port']) ? ':'.$url['port'] : '';
98+
$user = $url['user'] ?? '';
99+
$pass = isset($url['pass']) ? ':'.$url['pass'] : '';
100+
$pass = ($user || $pass) ? "$pass@" : '';
101+
$path = $url['path'] ?? '';
102+
$query = $url['query'] ? '?'.$url['query'] : '';
103+
$fragment = isset($url['fragment']) ? '#'.$url['fragment'] : '';
104+
105+
return $scheme.$user.$pass.$host.$port.$path.$query.$fragment;
106+
}
107+
}
108+
109+
if (!class_exists(\Symfony\Component\HttpKernel\UriSigner::class, false)) {
110+
class_alias(UriSigner::class, \Symfony\Component\HttpKernel\UriSigner::class);
111+
}

0 commit comments

Comments
 (0)