Unverified Commit ba9eff6b authored by Alex Pott's avatar Alex Pott
Browse files

Issue #2929798 by neclimdul, Sut3kh, alexpott, George Bills, mr.baileys,...

Issue #2929798 by neclimdul, Sut3kh, alexpott, George Bills, mr.baileys, mglaman: ActiveLinkResponseFilter breaks any text/html BinaryFileResponse or StreamedResponse for anonymous users
parent 7f44b458
Loading
Loading
Loading
Loading
+17 −9
Original line number Diff line number Diff line
@@ -76,8 +76,10 @@ public function __construct(AccountInterface $current_user, CurrentPathStack $cu
   *   The response event.
   */
  public function onResponse(FilterResponseEvent $event) {
    $response = $event->getResponse();

    // Only care about HTML responses.
    if (stripos($event->getResponse()->headers->get('Content-Type'), 'text/html') === FALSE) {
    if (stripos($response->headers->get('Content-Type'), 'text/html') === FALSE) {
      return;
    }

@@ -87,15 +89,21 @@ public function onResponse(FilterResponseEvent $event) {
      return;
    }

    $response = $event->getResponse();
    // If content is FALSE, assume the response does not support the
    // setContent() method and skip it, for example,
    // \Symfony\Component\HttpFoundation\BinaryFileResponse.
    $content = $response->getContent();
    if ($content !== FALSE) {
      $response->setContent(static::setLinkActiveClass(
      $response->getContent(),
        $content,
        ltrim($this->currentPath->getPath(), '/'),
        $this->pathMatcher->isFrontPage(),
      $this->languageManager->getCurrentLanguage(LanguageInterface::TYPE_URL)->getId(),
        $this->languageManager->getCurrentLanguage(LanguageInterface::TYPE_URL)
          ->getId(),
        $event->getRequest()->query->all()
      ));
    }
  }

  /**
   * Sets the "is-active" class on relevant links.
+99 −0
Original line number Diff line number Diff line
@@ -4,8 +4,21 @@

use Drupal\Component\Serialization\Json;
use Drupal\Core\EventSubscriber\ActiveLinkResponseFilter;
use Drupal\Core\Language\LanguageDefault;
use Drupal\Core\Language\LanguageManager;
use Drupal\Core\Path\CurrentPathStack;
use Drupal\Core\Path\PathMatcherInterface;
use Drupal\Core\Session\AnonymousUserSession;
use Drupal\Core\Template\Attribute;
use Drupal\Tests\UnitTestCase;
use Symfony\Component\HttpFoundation\BinaryFileResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\StreamedResponse;
use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
use Symfony\Component\HttpKernel\HttpKernelInterface;
use Symfony\Component\HttpKernel\KernelInterface;

/**
 * @coversDefaultClass \Drupal\Core\EventSubscriber\ActiveLinkResponseFilter
@@ -397,4 +410,90 @@ public function testSetLinkActiveClass($html_markup, $current_path, $is_front, $
    $this->assertSame($expected_html_markup, ActiveLinkResponseFilter::setLinkActiveClass($html_markup, $current_path, $is_front, $url_language, $query));
  }

  /**
   * Tests ActiveLinkResponseFilter only affects HTML responses.
   *
   * @covers ::onResponse
   */
  public function testOnlyHtml() {
    $session = new AnonymousUserSession();
    $language_manager = new LanguageManager(new LanguageDefault([]));
    $request_stack = new RequestStack();
    $request_stack->push(new Request());
    $current_path_stack = new CurrentPathStack($request_stack);

    // Make sure path matcher isn't called and we didn't get to the link logic.
    $path_matcher = $this->prophesize(PathMatcherInterface::class);
    $path_matcher->isFrontPage()->shouldNotBeCalled();

    $subscriber = new ActiveLinkResponseFilter(
      $session,
      $current_path_stack,
      $path_matcher->reveal(),
      $language_manager
    );

    // A link that might otherwise be set 'active'.
    $content = '<a data-drupal-link-system-path="otherpage">Other page</a>';

    // Assert response with non-html content type gets ignored.
    $response = new Response();
    $response->setContent($content);
    $response->headers->get('Content-Type', 'application/json');
    $subscriber->onResponse(new FilterResponseEvent(
      $this->prophesize(KernelInterface::class)->reveal(),
      $request_stack->getCurrentRequest(),
      HttpKernelInterface::MASTER_REQUEST,
      $response
    ));
    $this->assertSame($response->getContent(), $content);
  }

  /**
   * Tests certain response types ignored by the ActiveLinkResponseFilter.
   *
   * @covers ::onResponse
   */
  public function testSkipCertainResponseTypes() {
    $session = new AnonymousUserSession();
    $language_manager = new LanguageManager(new LanguageDefault([]));
    $request_stack = new RequestStack();
    $request_stack->push(new Request());
    $current_path_stack = new CurrentPathStack($request_stack);

    // Ensure path matcher is not called. This also tests that the
    // ActiveLinkResponseFilter ignores the response.
    $path_matcher = $this->prophesize(PathMatcherInterface::class);
    $path_matcher->isFrontPage()->shouldNotBeCalled();

    $subscriber = new ActiveLinkResponseFilter(
      $session,
      $current_path_stack,
      $path_matcher->reveal(),
      $language_manager
    );

    // Test BinaryFileResponse is ignored. Calling setContent() would throw a
    // logic exception.
    $response = new BinaryFileResponse(__FILE__, 200, ['Content-Type' => 'text/html']);
    $subscriber->onResponse(new FilterResponseEvent(
      $this->prophesize(KernelInterface::class)->reveal(),
      $request_stack->getCurrentRequest(),
      HttpKernelInterface::MASTER_REQUEST,
      $response
    ));

    // Test StreamedResponse is ignored. Calling setContent() would throw a
    // logic exception.
    $response = new StreamedResponse(function () {
      echo 'Success!';
    }, 200, ['Content-Type' => 'text/html']);
    $subscriber->onResponse(new FilterResponseEvent(
      $this->prophesize(KernelInterface::class)->reveal(),
      $request_stack->getCurrentRequest(),
      HttpKernelInterface::MASTER_REQUEST,
      $response
    ));
  }

}