Verified Commit c2090141 authored by Lee Rowlands's avatar Lee Rowlands
Browse files

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

(cherry picked from commit f47d1d15)
parent 51efb1b0
Loading
Loading
Loading
Loading
Loading
+21 −3
Original line number Diff line number Diff line
@@ -12,7 +12,6 @@
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 @@ protected function setExpiresNoCache(Response $response) {
   *
   * @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;
    }

    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($response->getContent()), TRUE);
    $response->headers->set('Content-Length', strlen($content), TRUE);
  }

  /**
+108 −0
Original line number Diff line number Diff line
<?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;
  }

}