diff --git a/core/lib/Drupal/Core/EventSubscriber/RedirectResponseSubscriber.php b/core/lib/Drupal/Core/EventSubscriber/RedirectResponseSubscriber.php index 5a4265e..802cee6 100644 --- a/core/lib/Drupal/Core/EventSubscriber/RedirectResponseSubscriber.php +++ b/core/lib/Drupal/Core/EventSubscriber/RedirectResponseSubscriber.php @@ -10,6 +10,7 @@ use Drupal\Component\Utility\UrlHelper; use Drupal\Core\Routing\RedirectResponseAllowExternalUrl; use Drupal\Core\Routing\RequestContext; +use Drupal\Core\Routing\TrustedRedirectResponse; use Drupal\Core\Routing\UrlGeneratorInterface; use InvalidArgumentException; use Symfony\Component\HttpFoundation\Response; @@ -100,7 +101,7 @@ public function checkRedirectUrl(FilterResponseEvent $event) { // URL is not external. In case this is wanted, check for a custom flag // on RedirectResponse. $url = $response->getTargetUrl(); - if (!$response instanceof RedirectResponseAllowExternalUrl && UrlHelper::isExternal($url) && !UrlHelper::externalIsLocal($url, $this->requestContext->getCompleteBaseUrl())) { + if (!$response instanceof TrustedRedirectResponse && UrlHelper::isExternal($url) && !UrlHelper::externalIsLocal($url, $this->requestContext->getCompleteBaseUrl())) { $this->setBadRequestException($event, 'Redirects to external URLs are not allowed by default, use \Drupal\Core\Routing\RedirectResponseAllowExternalUrl for it'); } } diff --git a/core/lib/Drupal/Core/Routing/RedirectResponseAllowExternalUrl.php b/core/lib/Drupal/Core/Routing/RedirectResponseAllowExternalUrl.php deleted file mode 100644 index 4bbd94f..0000000 --- a/core/lib/Drupal/Core/Routing/RedirectResponseAllowExternalUrl.php +++ /dev/null @@ -1,22 +0,0 @@ -headers = new ResponseHeaderBag($headers); + $this->setContent(''); + $this->setStatusCode($status); + $this->setProtocolVersion('1.0'); + if (!$this->headers->has('Date')) { + $this->setDate(new \DateTime(null, new \DateTimeZone('UTC'))); + } + + $this->setTargetUrl($url, TRUE); + + if (!$this->isRedirect()) { + throw new \InvalidArgumentException(sprintf('The HTTP status code is not a redirect ("%s" given).', $status)); + } + } + + + /** + * The request context. + * + * @var \Drupal\Core\Routing\RequestContext + */ + protected $requestContext; + + /** + * {@inheritdoc} + */ + public function setTargetUrl($url, $trusted = FALSE) { + // Just allow to set a different internal URL, for setting a different + // external URL we provide setTrustedUrl(). + + if (!$trusted && UrlHelper::isExternal($url) && !UrlHelper::externalIsLocal($url, $this->getRequestContext()->getCompleteBaseUrl())) { + throw new \InvalidArgumentException('It is not possible to set an external URL using ::setTargetUrl, use setTrustedTargetUrl instead.'); + } + + return parent::setTargetUrl($url); + } + + /** + * Allows to set the target URl of a response to an external URL. + * + * @param string $url + * A trusted URL. + * + * @return $this + */ + public function setTrustedTargetUrl($url) { + return $this->setTargetUrl($url, TRUE); + } + + /** + * Returns the request context. + * + * @return \Drupal\Core\Routing\RequestContext + */ + protected function getRequestContext() { + if (!isset($this->requestContext)) { + $this->requestContext = \Drupal::service('router.request_context'); + } + return $this->requestContext; + } + +} diff --git a/core/tests/Drupal/Tests/Core/Routing/TrustedRedirectResponseTest.php b/core/tests/Drupal/Tests/Core/Routing/TrustedRedirectResponseTest.php new file mode 100644 index 0000000..4ac1333 --- /dev/null +++ b/core/tests/Drupal/Tests/Core/Routing/TrustedRedirectResponseTest.php @@ -0,0 +1,57 @@ +setTargetUrl('/example2'); + + $this->assertEquals('/example2', $redirect_response->getTargetUrl()); + } + + /** + * @covers ::setTargetUrl + * @expectedException \InvalidArgumentException + */ + public function testSetTargetUrlWithUntrustedUrl() { + $request_context = new RequestContext(); + $request_context->setCompleteBaseUrl('https://www.drupal.org'); + $container = new ContainerBuilder(); + $container->set('router.request_context', $request_context); + \Drupal::setContainer($container); + + $redirect_response = new TrustedRedirectResponse('/example'); + + $redirect_response->setTargetUrl('http://evil-url.com/example'); + } + + /** + * @covers ::setTargetUrl + */ + public function testSetTargetUrlWithTrustedUrl() { + $redirect_response = new TrustedRedirectResponse('/example'); + + $redirect_response->setTrustedTargetUrl('http://good-external-url.com/example'); + $this->assertEquals('http://good-external-url.com/example', $redirect_response->getTargetUrl()); + } + +}