diff --git a/core/lib/Drupal/Core/EventSubscriber/FinishResponseSubscriber.php b/core/lib/Drupal/Core/EventSubscriber/FinishResponseSubscriber.php index 6fc54a38c00..b29ed6f4128 100644 --- a/core/lib/Drupal/Core/EventSubscriber/FinishResponseSubscriber.php +++ b/core/lib/Drupal/Core/EventSubscriber/FinishResponseSubscriber.php @@ -12,7 +12,6 @@ use Drupal\Core\PageCache\ResponsePolicyInterface; use Drupal\Core\Site\Settings; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; -use Symfony\Component\HttpFoundation\StreamedResponse; use Symfony\Component\HttpKernel\Event\ResponseEvent; use Symfony\Component\HttpKernel\KernelEvents; use Symfony\Component\EventDispatcher\EventSubscriberInterface; @@ -306,14 +305,33 @@ class FinishResponseSubscriber implements EventSubscriberInterface { * * @param \Symfony\Component\HttpKernel\Event\ResponseEvent $event * The event to process. + * + * @see \Symfony\Component\HttpFoundation\Response::prepare() + * @see https://www.rfc-editor.org/rfc/rfc9110.html#name-content-length */ public function setContentLengthHeader(ResponseEvent $event): void { $response = $event->getResponse(); - if ($response instanceof StreamedResponse) { + + if ($response->isInformational() || $response->isEmpty()) { return; } - $response->headers->set('Content-Length', strlen($response->getContent()), TRUE); + if ($response->headers->has('Transfer-Encoding')) { + return; + } + + // Drupal cannot set the correct content length header when there is a + // server error. + if ($response->isServerError()) { + return; + } + + $content = $response->getContent(); + if ($content === FALSE) { + return; + } + + $response->headers->set('Content-Length', strlen($content), TRUE); } /** diff --git a/core/tests/Drupal/Tests/Core/EventSubscriber/FinishResponserSubscriberTest.php b/core/tests/Drupal/Tests/Core/EventSubscriber/FinishResponserSubscriberTest.php new file mode 100644 index 00000000000..aefe20609c6 --- /dev/null +++ b/core/tests/Drupal/Tests/Core/EventSubscriber/FinishResponserSubscriberTest.php @@ -0,0 +1,108 @@ +prophesize(LanguageManagerInterface::class)->reveal(), + $this->getConfigFactoryStub(), + $this->prophesize(RequestPolicyInterface::class)->reveal(), + $this->prophesize(ResponsePolicyInterface::class)->reveal(), + $this->prophesize(CacheContextsManager::class)->reveal() + ); + + $event = new ResponseEvent( + $this->prophesize(HttpKernelInterface::class)->reveal(), + $this->prophesize(Request::class)->reveal(), + HttpKernelInterface::MAIN_REQUEST, + $response + ); + + $event_subscriber->setContentLengthHeader($event); + if ($expected_header === FALSE) { + $this->assertFalse($event->getResponse()->headers->has('Content-Length')); + } + else { + $this->assertSame((string) $expected_header, $event->getResponse()->headers->get('Content-Length')); + } + } + + public function providerTestSetContentLengthHeader() { + return [ + 'Informational' => [ + FALSE, + new Response('', 101), + ], + '200 ok' => [ + 12, + new Response('Test content', 200), + ], + '204' => [ + FALSE, + new Response('Test content', 204), + ], + '304' => [ + FALSE, + new Response('Test content', 304), + ], + 'Client error' => [ + 13, + new Response('Access denied', 403), + ], + 'Server error' => [ + FALSE, + new Response('Test content', 500), + ], + '200 with transfer-encoding' => [ + FALSE, + new Response('Test content', 200, ['Transfer-Encoding' => 'Chunked']), + ], + '200 with FalseContentResponse' => [ + FALSE, + new FalseContentResponse('Test content', 200), + ], + '200 with StreamedResponse' => [ + FALSE, + new StreamedResponse(status: 200), + ], + + ]; + } + +} + +/** + * Response that returns FALSE from ::getContent(). + */ +class FalseContentResponse extends Response { + + /** + * {@inheritdoc} + */ + public function getContent(): string|false { + return FALSE; + } + +}