Skip to content

Commit 7ca93ab

Browse files
committed
Merge branch '7.2' into 7.3
* 7.2: reject URLs containing whitespaces Update validators.fa.xlf the "max" option can be zero [TypeInfo] Fix PHPDoc resolving of union with mixed [Security/Csrf] Trust "Referer" at the same level as "Origin" [HttpClient] Fix a typo in NoPrivateNetworkHttpClient
2 parents da15553 + 9899329 commit 7ca93ab

File tree

10 files changed

+93
-26
lines changed

10 files changed

+93
-26
lines changed

src/Symfony/Component/HtmlSanitizer/Tests/TextSanitizer/UrlSanitizerTest.php

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -358,10 +358,10 @@ public static function provideParse(): iterable
358358
'non-special://:@untrusted.com/x' => ['scheme' => 'non-special', 'host' => 'untrusted.com'],
359359
'http:foo.com' => ['scheme' => 'http', 'host' => null],
360360
" :foo.com \n" => null,
361-
' foo.com ' => ['scheme' => null, 'host' => null],
361+
' foo.com ' => null,
362362
'a: foo.com' => null,
363-
'http://f:21/ b ? d # e ' => ['scheme' => 'http', 'host' => 'f'],
364-
'lolscheme:x x#x x' => ['scheme' => 'lolscheme', 'host' => null],
363+
'http://f:21/ b ? d # e ' => null,
364+
'lolscheme:x x#x x' => null,
365365
'http://f:/c' => ['scheme' => 'http', 'host' => 'f'],
366366
'http://f:0/c' => ['scheme' => 'http', 'host' => 'f'],
367367
'http://f:00000000000000/c' => ['scheme' => 'http', 'host' => 'f'],
@@ -434,7 +434,7 @@ public static function provideParse(): iterable
434434
'javascript:example.com/' => ['scheme' => 'javascript', 'host' => null],
435435
'mailto:example.com/' => ['scheme' => 'mailto', 'host' => null],
436436
'/a/b/c' => ['scheme' => null, 'host' => null],
437-
'/a/ /c' => ['scheme' => null, 'host' => null],
437+
'/a/ /c' => null,
438438
'/a%2fc' => ['scheme' => null, 'host' => null],
439439
'/a/%2f/c' => ['scheme' => null, 'host' => null],
440440
'' => ['scheme' => null, 'host' => null],
@@ -495,10 +495,10 @@ public static function provideParse(): iterable
495495
'http://example.com/你好你好' => ['scheme' => 'http', 'host' => 'example.com'],
496496
'http://example.com/‥/foo' => ['scheme' => 'http', 'host' => 'example.com'],
497497
"http://example.com/\u{feff}/foo" => ['scheme' => 'http', 'host' => 'example.com'],
498-
"http://example.com\u{002f}\u{202e}\u{002f}\u{0066}\u{006f}\u{006f}\u{002f}\u{202d}\u{002f}\u{0062}\u{0061}\u{0072}\u{0027}\u{0020}" => ['scheme' => 'http', 'host' => 'example.com'],
498+
"http://example.com\u{002f}\u{202e}\u{002f}\u{0066}\u{006f}\u{006f}\u{002f}\u{202d}\u{002f}\u{0062}\u{0061}\u{0072}\u{0027}\u{0020}" => null,
499499
'http://www.google.com/foo?bar=baz#' => ['scheme' => 'http', 'host' => 'www.google.com'],
500-
'http://www.google.com/foo?bar=baz# »' => ['scheme' => 'http', 'host' => 'www.google.com'],
501-
'data:test# »' => ['scheme' => 'data', 'host' => null],
500+
'http://www.google.com/foo?bar=baz# »' => null,
501+
'data:test# »' => null,
502502
'http://www.google.com' => ['scheme' => 'http', 'host' => 'www.google.com'],
503503
'http://192.0x00A80001' => ['scheme' => 'http', 'host' => '192.0x00A80001'],
504504
'http://www/foo%2Ehtml' => ['scheme' => 'http', 'host' => 'www'],
@@ -706,11 +706,11 @@ public static function provideParse(): iterable
706706
'test-a-colon-slash-slash-b.html' => ['scheme' => null, 'host' => null],
707707
'http://example.org/test?a#bc' => ['scheme' => 'http', 'host' => 'example.org'],
708708
'http:\\/\\/f:b\\/c' => ['scheme' => 'http', 'host' => null],
709-
'http:\\/\\/f: \\/c' => ['scheme' => 'http', 'host' => null],
709+
'http:\\/\\/f: \\/c' => null,
710710
'http:\\/\\/f:fifty-two\\/c' => ['scheme' => 'http', 'host' => null],
711711
'http:\\/\\/f:999999\\/c' => ['scheme' => 'http', 'host' => null],
712712
'non-special:\\/\\/f:999999\\/c' => ['scheme' => 'non-special', 'host' => null],
713-
'http:\\/\\/f: 21 \\/ b ? d # e ' => ['scheme' => 'http', 'host' => null],
713+
'http:\\/\\/f: 21 \\/ b ? d # e ' => null,
714714
'http:\\/\\/[1::2]:3:4' => ['scheme' => 'http', 'host' => null],
715715
'http:\\/\\/2001::1' => ['scheme' => 'http', 'host' => null],
716716
'http:\\/\\/2001::1]' => ['scheme' => 'http', 'host' => null],
@@ -734,8 +734,8 @@ public static function provideParse(): iterable
734734
'http:@:www.example.com' => ['scheme' => 'http', 'host' => null],
735735
'http:\\/@:www.example.com' => ['scheme' => 'http', 'host' => null],
736736
'http:\\/\\/@:www.example.com' => ['scheme' => 'http', 'host' => null],
737-
'http:\\/\\/example example.com' => ['scheme' => 'http', 'host' => null],
738-
'http:\\/\\/Goo%20 goo%7C|.com' => ['scheme' => 'http', 'host' => null],
737+
'http:\\/\\/example example.com' => null,
738+
'http:\\/\\/Goo%20 goo%7C|.com' => null,
739739
'http:\\/\\/[]' => ['scheme' => 'http', 'host' => null],
740740
'http:\\/\\/[:]' => ['scheme' => 'http', 'host' => null],
741741
'http:\\/\\/GOO\\u00a0\\u3000goo.com' => ['scheme' => 'http', 'host' => null],
@@ -752,8 +752,8 @@ public static function provideParse(): iterable
752752
'http:\\/\\/hello%00' => ['scheme' => 'http', 'host' => null],
753753
'http:\\/\\/192.168.0.257' => ['scheme' => 'http', 'host' => null],
754754
'http:\\/\\/%3g%78%63%30%2e%30%32%35%30%2E.01' => ['scheme' => 'http', 'host' => null],
755-
'http:\\/\\/192.168.0.1 hello' => ['scheme' => 'http', 'host' => null],
756-
'https:\\/\\/x x:12' => ['scheme' => 'https', 'host' => null],
755+
'http:\\/\\/192.168.0.1 hello' => null,
756+
'https:\\/\\/x x:12' => null,
757757
'http:\\/\\/[www.google.com]\\/' => ['scheme' => 'http', 'host' => null],
758758
'http:\\/\\/[google.com]' => ['scheme' => 'http', 'host' => null],
759759
'http:\\/\\/[::1.2.3.4x]' => ['scheme' => 'http', 'host' => null],
@@ -763,7 +763,7 @@ public static function provideParse(): iterable
763763
'..\\/i' => ['scheme' => null, 'host' => null],
764764
'\\/i' => ['scheme' => null, 'host' => null],
765765
'sc:\\/\\/\\u0000\\/' => ['scheme' => 'sc', 'host' => null],
766-
'sc:\\/\\/ \\/' => ['scheme' => 'sc', 'host' => null],
766+
'sc:\\/\\/ \\/' => null,
767767
'sc:\\/\\/@\\/' => ['scheme' => 'sc', 'host' => null],
768768
'sc:\\/\\/te@s:t@\\/' => ['scheme' => 'sc', 'host' => null],
769769
'sc:\\/\\/:\\/' => ['scheme' => 'sc', 'host' => null],

src/Symfony/Component/HtmlSanitizer/TextSanitizer/UrlSanitizer.php

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,13 @@ public static function parse(string $url): ?array
9494
}
9595

9696
try {
97-
return UriString::parse($url);
97+
$parsedUrl = UriString::parse($url);
98+
99+
if (preg_match('/\s/', $url)) {
100+
return null;
101+
}
102+
103+
return $parsedUrl;
98104
} catch (SyntaxError) {
99105
return null;
100106
}

src/Symfony/Component/HttpClient/NoPrivateNetworkHttpClient.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@ public function request(string $method, string $url, array $options = []): Respo
138138
$filterContentHeaders = static function ($h) {
139139
return 0 !== stripos($h, 'Content-Length:') && 0 !== stripos($h, 'Content-Type:') && 0 !== stripos($h, 'Transfer-Encoding:');
140140
};
141-
$options['header'] = array_filter($options['header'], $filterContentHeaders);
141+
$options['headers'] = array_filter($options['headers'], $filterContentHeaders);
142142
$redirectHeaders['no_auth'] = array_filter($redirectHeaders['no_auth'], $filterContentHeaders);
143143
$redirectHeaders['with_auth'] = array_filter($redirectHeaders['with_auth'], $filterContentHeaders);
144144
}

src/Symfony/Component/HttpClient/Tests/NoPrivateNetworkHttpClientTest.php

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,27 @@ public function testNonCallableOnProgressCallback()
175175
$client->request('GET', $url, ['on_progress' => $customCallback]);
176176
}
177177

178+
public function testHeadersArePassedOnRedirect()
179+
{
180+
$ipAddr = '104.26.14.6';
181+
$url = sprintf('http://%s/', $ipAddr);
182+
$content = 'foo';
183+
184+
$callback = function ($method, $url, $options) use ($content): MockResponse {
185+
$this->assertArrayHasKey('headers', $options);
186+
$this->assertNotContains('content-type: application/json', $options['headers']);
187+
$this->assertContains('foo: bar', $options['headers']);
188+
return new MockResponse($content);
189+
};
190+
$responses = [
191+
new MockResponse('', ['http_code' => 302, 'redirect_url' => 'http://104.26.14.7']),
192+
$callback,
193+
];
194+
$client = new NoPrivateNetworkHttpClient(new MockHttpClient($responses));
195+
$response = $client->request('POST', $url, ['headers' => ['foo' => 'bar', 'content-type' => 'application/json']]);
196+
$this->assertEquals($content, $response->getContent());
197+
}
198+
178199
private function getMockHttpClient(string $ipAddr, string $content)
179200
{
180201
return new MockHttpClient(new MockResponse($content, ['primary_ip' => $ipAddr]));

src/Symfony/Component/Security/Csrf/SameOriginCsrfTokenManager.php

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -227,9 +227,21 @@ public function onKernelResponse(ResponseEvent $event): void
227227
*/
228228
private function isValidOrigin(Request $request): ?bool
229229
{
230-
$source = $request->headers->get('Origin') ?? $request->headers->get('Referer') ?? 'null';
230+
$target = $request->getSchemeAndHttpHost().'/';
231+
$source = 'null';
231232

232-
return 'null' === $source ? null : str_starts_with($source.'/', $request->getSchemeAndHttpHost().'/');
233+
foreach (['Origin', 'Referer'] as $header) {
234+
if (!$request->headers->has($header)) {
235+
continue;
236+
}
237+
$source = $request->headers->get($header);
238+
239+
if (str_starts_with($source.'/', $target)) {
240+
return true;
241+
}
242+
}
243+
244+
return 'null' === $source ? null : false;
233245
}
234246

235247
/**

src/Symfony/Component/Security/Csrf/Tests/SameOriginCsrfTokenManagerTest.php

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,20 @@ public function testValidOrigin()
100100
$this->assertSame(1 << 8, $request->attributes->get('csrf-token'));
101101
}
102102

103+
public function testValidRefererInvalidOrigin()
104+
{
105+
$request = new Request();
106+
$request->headers->set('Origin', 'http://localhost:1234');
107+
$request->headers->set('Referer', $request->getSchemeAndHttpHost());
108+
$this->requestStack->push($request);
109+
110+
$token = new CsrfToken('test_token', str_repeat('a', 24));
111+
112+
$this->logger->expects($this->once())->method('debug')->with('CSRF validation accepted using origin info.');
113+
$this->assertTrue($this->csrfTokenManager->isTokenValid($token));
114+
$this->assertSame(1 << 8, $request->attributes->get('csrf-token'));
115+
}
116+
103117
public function testValidOriginAfterDoubleSubmit()
104118
{
105119
$session = $this->createMock(Session::class);

src/Symfony/Component/TypeInfo/Tests/TypeResolver/StringTypeResolverTest.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,8 @@ public static function resolveDataProvider(): iterable
155155

156156
// union
157157
yield [Type::union(Type::int(), Type::string()), 'int|string'];
158+
yield [Type::mixed(), 'int|mixed'];
159+
yield [Type::mixed(), 'mixed|int'];
158160

159161
// intersection
160162
yield [Type::intersection(Type::object(\DateTime::class), Type::object(\Stringable::class)), \DateTime::class.'&'.\Stringable::class];

src/Symfony/Component/TypeInfo/TypeResolver/StringTypeResolver.php

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -223,7 +223,19 @@ private function getTypeFromNode(TypeNode $node, ?TypeContext $typeContext): Typ
223223
}
224224

225225
if ($node instanceof UnionTypeNode) {
226-
return Type::union(...array_map(fn (TypeNode $t): Type => $this->getTypeFromNode($t, $typeContext), $node->types));
226+
$types = [];
227+
228+
foreach ($node->types as $nodeType) {
229+
$type = $this->getTypeFromNode($nodeType, $typeContext);
230+
231+
if ($type instanceof BuiltinType && TypeIdentifier::MIXED === $type->getTypeIdentifier()) {
232+
return Type::mixed();
233+
}
234+
235+
$types[] = $type;
236+
}
237+
238+
return Type::union(...$types);
227239
}
228240

229241
if ($node instanceof IntersectionTypeNode) {

src/Symfony/Component/Validator/Constraints/Count.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ class Count extends Constraint
4545
/**
4646
* @param int<0, max>|array<string,mixed>|null $exactly The exact expected number of elements
4747
* @param int<0, max>|null $min Minimum expected number of elements
48-
* @param positive-int|null $max Maximum expected number of elements
48+
* @param int<0, max>|null $max Maximum expected number of elements
4949
* @param positive-int|null $divisibleBy The number the collection count should be divisible by
5050
* @param string[]|null $groups
5151
* @param array<mixed,string> $options

src/Symfony/Component/Validator/Resources/translations/validators.fa.xlf

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -444,27 +444,27 @@
444444
</trans-unit>
445445
<trans-unit id="114">
446446
<source>This value is too short. It should contain at least one word.|This value is too short. It should contain at least {{ min }} words.</source>
447-
<target state="needs-translation">This value is too short. It should contain at least one word.|This value is too short. It should contain at least {{ min }} words.</target>
447+
<target>این مقدار بسیار کوتاه است. باید حداقل یک کلمه داشته باشد.|این مقدار بسیار کوتاه است. باید حداقل {{ min }} کلمه داشته باشد.</target>
448448
</trans-unit>
449449
<trans-unit id="115">
450450
<source>This value is too long. It should contain one word.|This value is too long. It should contain {{ max }} words or less.</source>
451-
<target state="needs-translation">This value is too long. It should contain one word.|This value is too long. It should contain {{ max }} words or less.</target>
451+
<target>این مقدار بیش از حد طولانی است. باید فقط یک کلمه باشد.|این مقدار بیش از حد طولانی است. باید حداکثر {{ max }} کلمه داشته باشد.</target>
452452
</trans-unit>
453453
<trans-unit id="116">
454454
<source>This value does not represent a valid week in the ISO 8601 format.</source>
455-
<target state="needs-translation">This value does not represent a valid week in the ISO 8601 format.</target>
455+
<target>این مقدار یک هفته معتبر در قالب ISO 8601 را نشان نمی‌دهد.</target>
456456
</trans-unit>
457457
<trans-unit id="117">
458458
<source>This value is not a valid week.</source>
459-
<target state="needs-translation">This value is not a valid week.</target>
459+
<target>این مقدار یک هفته معتبر نیست.</target>
460460
</trans-unit>
461461
<trans-unit id="118">
462462
<source>This value should not be before week "{{ min }}".</source>
463-
<target state="needs-translation">This value should not be before week "{{ min }}".</target>
463+
<target>این مقدار نباید قبل از هفته "{{ min }}" باشد.</target>
464464
</trans-unit>
465465
<trans-unit id="119">
466466
<source>This value should not be after week "{{ max }}".</source>
467-
<target state="needs-translation">This value should not be after week "{{ max }}".</target>
467+
<target>این مقدار نباید بعد از هفته "{{ max }}" باشد.</target>
468468
</trans-unit>
469469
</body>
470470
</file>

0 commit comments

Comments
 (0)