Issue #3396559 by alexpott, catch: Only set content-length header in specific situations

merge-requests/4109/merge
Lee Rowlands 2024-01-02 06:45:26 +10:00
parent d6b6ad8fd6
commit f47d1d159e
No known key found for this signature in database
GPG Key ID: 2B829A3DF9204DC4
2 changed files with 129 additions and 3 deletions

View File

@ -12,7 +12,6 @@ use Drupal\Core\PageCache\ResponsePolicyInterface;
use Drupal\Core\Site\Settings; use Drupal\Core\Site\Settings;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\StreamedResponse;
use Symfony\Component\HttpKernel\Event\ResponseEvent; use Symfony\Component\HttpKernel\Event\ResponseEvent;
use Symfony\Component\HttpKernel\KernelEvents; use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\EventDispatcher\EventSubscriberInterface;
@ -306,14 +305,33 @@ class FinishResponseSubscriber implements EventSubscriberInterface {
* *
* @param \Symfony\Component\HttpKernel\Event\ResponseEvent $event * @param \Symfony\Component\HttpKernel\Event\ResponseEvent $event
* The event to process. * 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 { public function setContentLengthHeader(ResponseEvent $event): void {
$response = $event->getResponse(); $response = $event->getResponse();
if ($response instanceof StreamedResponse) {
if ($response->isInformational() || $response->isEmpty()) {
return; 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);
} }
/** /**

View File

@ -0,0 +1,108 @@
<?php
namespace Drupal\Tests\Core\EventSubscriber;
use Drupal\Core\Cache\Context\CacheContextsManager;
use Drupal\Core\EventSubscriber\FinishResponseSubscriber;
use Drupal\Core\Language\LanguageManagerInterface;
use Drupal\Core\PageCache\RequestPolicyInterface;
use Drupal\Core\PageCache\ResponsePolicyInterface;
use Drupal\Tests\UnitTestCase;
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\HttpKernelInterface;
/**
* @coversDefaultClass \Drupal\Core\EventSubscriber\FinishResponseSubscriber
* @group EventSubscriber
*/
class FinishResponserSubscriberTest extends UnitTestCase {
/**
* @covers ::setContentLengthHeader
* @dataProvider providerTestSetContentLengthHeader
*/
public function testSetContentLengthHeader(false|int $expected_header, Response $response) {
$event_subscriber = new FinishResponseSubscriber(
$this->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;
}
}