/** * Sets a BigPipe no-JS cookie, redirects back to the original location. * * @param \Symfony\Component\HttpFoundation\Request $request * The current request. * * @return \Drupal\Core\Routing\LocalRedirectResponse * A response that sets the no-JS cookie and redirects back to the original * location. * * @throws \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException * Thrown when the no-JS cookie is already set or when there is no session. * @throws \Symfony\Component\HttpKernel\Exception\HttpException * Thrown when the original location is missing, i.e. when no 'destination' * query argument is set. * * @see \Drupal\big_pipe\Render\Placeholder\BigPipeStrategy */ public function setNoJsCookie(Request $request) { // This controller may only be accessed when the browser does not support // JavaScript. It is accessed automatically when that's the case thanks to // big_pipe_page_attachments(). When this controller is executed, deny // access when either: // - the no-JS cookie is already set: this indicates a redirect loop, since // the cookie was already set, yet the user is executing this controller; // - there is no session, in which case BigPipe is not enabled anyway, so it // is pointless to set this cookie. if ($request->cookies->has(BigPipeStrategy::NOJS_COOKIE) || $request->getSession() === NULL) { throw new AccessDeniedHttpException(); } if (!$request->query->has('destination')) { throw new HttpException(500, 'The original location is missing.'); } $response = new LocalRedirectResponse($request->query->get('destination')); $response->headers->setCookie(new Cookie(BigPipeStrategy::NOJS_COOKIE, TRUE)); $response->addCacheableDependency((new CacheableMetadata())->addCacheContexts(['cookies:' . BigPipeStrategy::NOJS_COOKIE, 'session.exists'])); return $response; }
/** * Allows manipulation of the response object when performing a redirect. * * @param \Symfony\Component\HttpKernel\Event\FilterResponseEvent $event * The Event to process. */ public function checkRedirectUrl(FilterResponseEvent $event) { $response = $event->getResponse(); if ($response instanceof RedirectResponse) { $request = $event->getRequest(); // Let the 'destination' query parameter override the redirect target. // If $response is already a SecuredRedirectResponse, it might reject the // new target as invalid, in which case proceed with the old target. $destination = $request->query->get('destination'); if ($destination) { // The 'Location' HTTP header must always be absolute. $destination = $this->getDestinationAsAbsoluteUrl($destination, $request->getSchemeAndHttpHost()); try { $response->setTargetUrl($destination); } catch (\InvalidArgumentException $e) { } } // Regardless of whether the target is the original one or the overridden // destination, ensure that all redirects are safe. if (!$response instanceof SecuredRedirectResponse) { try { // SecuredRedirectResponse is an abstract class that requires a // concrete implementation. Default to LocalRedirectResponse, which // considers only redirects to within the same site as safe. $safe_response = LocalRedirectResponse::createFromRedirectResponse($response); $safe_response->setRequestContext($this->requestContext); } catch (\InvalidArgumentException $e) { // If the above failed, it's because the redirect target wasn't // local. Do not follow that redirect. Display an error message // instead. We're already catching one exception, so trigger_error() // rather than throw another one. // We don't throw an exception, because this is a client error rather than a // server error. $message = 'Redirects to external URLs are not allowed by default, use \\Drupal\\Core\\Routing\\TrustedRedirectResponse for it.'; trigger_error($message, E_USER_ERROR); $safe_response = new Response($message, 400); } $event->setResponse($safe_response); } } }