diff --git a/BinaryFileResponse.php b/BinaryFileResponse.php index a2b160f8a..0f5b3fca5 100644 --- a/BinaryFileResponse.php +++ b/BinaryFileResponse.php @@ -189,7 +189,12 @@ public function prepare(Request $request): static } if (!$this->headers->has('Content-Type')) { - $this->headers->set('Content-Type', $this->file->getMimeType() ?: 'application/octet-stream'); + $mimeType = null; + if (!$this->tempFileObject) { + $mimeType = $this->file->getMimeType(); + } + + $this->headers->set('Content-Type', $mimeType ?: 'application/octet-stream'); } parent::prepare($request); diff --git a/IpUtils.php b/IpUtils.php index ceab620c2..18b1c5faf 100644 --- a/IpUtils.php +++ b/IpUtils.php @@ -182,6 +182,16 @@ public static function checkIp6(string $requestIp, string $ip): bool */ public static function anonymize(string $ip): string { + /** + * If the IP contains a % symbol, then it is a local-link address with scoping according to RFC 4007 + * In that case, we only care about the part before the % symbol, as the following functions, can only work with + * the IP address itself. As the scope can leak information (containing interface name), we do not want to + * include it in our anonymized IP data. + */ + if (str_contains($ip, '%')) { + $ip = substr($ip, 0, strpos($ip, '%')); + } + $wrappedIPv6 = false; if (str_starts_with($ip, '[') && str_ends_with($ip, ']')) { $wrappedIPv6 = true; diff --git a/RequestStack.php b/RequestStack.php index ac8263e91..613c439dd 100644 --- a/RequestStack.php +++ b/RequestStack.php @@ -104,4 +104,11 @@ public function getSession(): SessionInterface throw new SessionNotFoundException(); } + + public function resetRequestFormats(): void + { + static $resetRequestFormats; + $resetRequestFormats ??= \Closure::bind(static fn () => self::$formats = null, null, Request::class); + $resetRequestFormats(); + } } diff --git a/Tests/BinaryFileResponseTest.php b/Tests/BinaryFileResponseTest.php index 77bc32e8c..d4d84b305 100644 --- a/Tests/BinaryFileResponseTest.php +++ b/Tests/BinaryFileResponseTest.php @@ -458,4 +458,15 @@ public function testCreateFromTemporaryFile() $string = ob_get_clean(); $this->assertSame('foo,bar', $string); } + + public function testCreateFromTemporaryFileWithoutMimeType() + { + $file = new \SplTempFileObject(); + $file->fwrite('foo,bar'); + + $response = new BinaryFileResponse($file); + $response->prepare(new Request()); + + $this->assertSame('application/octet-stream', $response->headers->get('Content-Type')); + } } diff --git a/Tests/IpUtilsTest.php b/Tests/IpUtilsTest.php index ce93c69e9..2a86fbc2d 100644 --- a/Tests/IpUtilsTest.php +++ b/Tests/IpUtilsTest.php @@ -147,6 +147,7 @@ public static function anonymizedIpData() ['[2a01:198::3]', '[2a01:198::]'], ['::ffff:123.234.235.236', '::ffff:123.234.235.0'], // IPv4-mapped IPv6 addresses ['::123.234.235.236', '::123.234.235.0'], // deprecated IPv4-compatible IPv6 address + ['fe80::1fc4:15d8:78db:2319%enp4s0', 'fe80::'], // IPv6 link-local with RFC4007 scoping ]; } diff --git a/Tests/RequestStackTest.php b/Tests/RequestStackTest.php index 2b26ce5c6..3b958653f 100644 --- a/Tests/RequestStackTest.php +++ b/Tests/RequestStackTest.php @@ -67,4 +67,18 @@ public function testGetParentRequest() $requestStack->push($secondSubRequest); $this->assertSame($firstSubRequest, $requestStack->getParentRequest()); } + + public function testResetRequestFormats() + { + $requestStack = new RequestStack(); + + $request = Request::create('/foo'); + $request->setFormat('foo', ['application/foo']); + + $this->assertSame(['application/foo'], $request->getMimeTypes('foo')); + + $requestStack->resetRequestFormats(); + + $this->assertSame([], $request->getMimeTypes('foo')); + } }