From ca5db794096927a15837b9750972c33078e5502c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dunglas?= Date: Fri, 2 Feb 2024 16:32:09 +0100 Subject: [PATCH] [HttpFoundation] Prevent duplicated headers when using Early Hints --- .github/workflows/integration-tests.yml | 10 ++++++ .../Component/HttpFoundation/Response.php | 24 +++++++------- .../response-functional/early_hints.php | 31 +++++++++++++++++++ .../Tests/ResponseFunctionalTest.php | 28 ++++++++++++++++- 4 files changed, 79 insertions(+), 14 deletions(-) create mode 100644 src/Symfony/Component/HttpFoundation/Tests/Fixtures/response-functional/early_hints.php diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml index b9ba20ba9926a..6464a2f1f31e5 100644 --- a/.github/workflows/integration-tests.yml +++ b/.github/workflows/integration-tests.yml @@ -110,6 +110,16 @@ jobs: KAFKA_ADVERTISED_HOST_NAME: 127.0.0.1 KAFKA_ZOOKEEPER_CONNECT: 'zookeeper:2181' KAFKA_ADVERTISED_PORT: 9092 + frankenphp: + image: dunglas/frankenphp:1.1.0 + ports: + - 80:80 + volumes: + - ${{ github.workspace }}:/symfony + env: + SERVER_NAME: 'http://localhost' + CADDY_SERVER_EXTRA_DIRECTIVES: | + root * /symfony/src/Symfony/Component/HttpFoundation/Tests/Fixtures/response-functional/ steps: - name: Checkout diff --git a/src/Symfony/Component/HttpFoundation/Response.php b/src/Symfony/Component/HttpFoundation/Response.php index d67c8f7264432..a43e7a9ac21e3 100644 --- a/src/Symfony/Component/HttpFoundation/Response.php +++ b/src/Symfony/Component/HttpFoundation/Response.php @@ -355,23 +355,21 @@ public function sendHeaders(/* int $statusCode = null */): static $replace = false; // As recommended by RFC 8297, PHP automatically copies headers from previous 103 responses, we need to deal with that if headers changed - if (103 === $statusCode) { - $previousValues = $this->sentHeaders[$name] ?? null; - if ($previousValues === $values) { - // Header already sent in a previous response, it will be automatically copied in this response by PHP - continue; - } + $previousValues = $this->sentHeaders[$name] ?? null; + if ($previousValues === $values) { + // Header already sent in a previous response, it will be automatically copied in this response by PHP + continue; + } - $replace = 0 === strcasecmp($name, 'Content-Type'); + $replace = 0 === strcasecmp($name, 'Content-Type'); - if (null !== $previousValues && array_diff($previousValues, $values)) { - header_remove($name); - $previousValues = null; - } - - $newValues = null === $previousValues ? $values : array_diff($values, $previousValues); + if (null !== $previousValues && array_diff($previousValues, $values)) { + header_remove($name); + $previousValues = null; } + $newValues = null === $previousValues ? $values : array_diff($values, $previousValues); + foreach ($newValues as $value) { header($name.': '.$value, $replace, $this->statusCode); } diff --git a/src/Symfony/Component/HttpFoundation/Tests/Fixtures/response-functional/early_hints.php b/src/Symfony/Component/HttpFoundation/Tests/Fixtures/response-functional/early_hints.php new file mode 100644 index 0000000000000..90294d9ae2d0e --- /dev/null +++ b/src/Symfony/Component/HttpFoundation/Tests/Fixtures/response-functional/early_hints.php @@ -0,0 +1,31 @@ +headers->set('Link', '; rel="preload"; as="style"'); +$r->sendHeaders(103); + +$r->headers->set('Link', '; rel="preload"; as="script"', false); +$r->sendHeaders(103); + +$r->setContent('Hello, Early Hints'); +$r->send(); diff --git a/src/Symfony/Component/HttpFoundation/Tests/ResponseFunctionalTest.php b/src/Symfony/Component/HttpFoundation/Tests/ResponseFunctionalTest.php index ccda147df6a57..1b3566a2c4860 100644 --- a/src/Symfony/Component/HttpFoundation/Tests/ResponseFunctionalTest.php +++ b/src/Symfony/Component/HttpFoundation/Tests/ResponseFunctionalTest.php @@ -12,6 +12,8 @@ namespace Symfony\Component\HttpFoundation\Tests; use PHPUnit\Framework\TestCase; +use Symfony\Component\Process\ExecutableFinder; +use Symfony\Component\Process\Process; class ResponseFunctionalTest extends TestCase { @@ -51,7 +53,31 @@ public function testCookie($fixture) public static function provideCookie() { foreach (glob(__DIR__.'/Fixtures/response-functional/*.php') as $file) { - yield [pathinfo($file, \PATHINFO_FILENAME)]; + if (str_contains($file, 'cookie')) { + yield [pathinfo($file, \PATHINFO_FILENAME)]; + } } } + + /** + * @group integration + */ + public function testInformationalResponse() + { + if (!(new ExecutableFinder())->find('curl')) { + $this->markTestSkipped('curl is not installed'); + } + + if (!($fp = @fsockopen('localhost', 80, $errorCode, $errorMessage, 2))) { + $this->markTestSkipped('FrankenPHP is not running'); + } + fclose($fp); + + $p = new Process(['curl', '-v', 'http://localhost/early_hints.php']); + $p->run(); + $output = $p->getErrorOutput(); + + $this->assertSame(3, preg_match_all('#Link: ; rel="preload"; as="style"#', $output)); + $this->assertSame(2, preg_match_all('#Link: ; rel="preload"; as="script"#', $output)); + } } pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy