67a4aae4220ff236716353dfc4fe771cde391121
[yaffs-website] / web / core / lib / Drupal / Core / EventSubscriber / RedirectResponseSubscriber.php
1 <?php
2
3 namespace Drupal\Core\EventSubscriber;
4
5 use Drupal\Component\HttpFoundation\SecuredRedirectResponse;
6 use Drupal\Component\Utility\UrlHelper;
7 use Drupal\Core\Routing\LocalRedirectResponse;
8 use Drupal\Core\Routing\RequestContext;
9 use Drupal\Core\Utility\UnroutedUrlAssemblerInterface;
10 use Symfony\Component\HttpFoundation\Response;
11 use Symfony\Component\HttpKernel\KernelEvents;
12 use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
13 use Symfony\Component\HttpFoundation\RedirectResponse;
14 use Symfony\Component\EventDispatcher\EventSubscriberInterface;
15
16 /**
17  * Allows manipulation of the response object when performing a redirect.
18  */
19 class RedirectResponseSubscriber implements EventSubscriberInterface {
20
21   /**
22    * The unrouted URL assembler service.
23    *
24    * @var \Drupal\Core\Utility\UnroutedUrlAssemblerInterface
25    */
26   protected $unroutedUrlAssembler;
27
28   /**
29    * Constructs a RedirectResponseSubscriber object.
30    *
31    * @param \Drupal\Core\Utility\UnroutedUrlAssemblerInterface $url_assembler
32    *   The unrouted URL assembler service.
33    * @param \Drupal\Core\Routing\RequestContext $request_context
34    *   The request context.
35    */
36   public function __construct(UnroutedUrlAssemblerInterface $url_assembler, RequestContext $request_context) {
37     $this->unroutedUrlAssembler = $url_assembler;
38     $this->requestContext = $request_context;
39   }
40
41   /**
42    * Allows manipulation of the response object when performing a redirect.
43    *
44    * @param \Symfony\Component\HttpKernel\Event\FilterResponseEvent $event
45    *   The Event to process.
46    */
47   public function checkRedirectUrl(FilterResponseEvent $event) {
48     $response = $event->getResponse();
49     if ($response instanceof RedirectResponse) {
50       $request = $event->getRequest();
51
52       // Let the 'destination' query parameter override the redirect target.
53       // If $response is already a SecuredRedirectResponse, it might reject the
54       // new target as invalid, in which case proceed with the old target.
55       $destination = $request->query->get('destination');
56       if ($destination) {
57         // The 'Location' HTTP header must always be absolute.
58         $destination = $this->getDestinationAsAbsoluteUrl($destination, $request->getSchemeAndHttpHost());
59         try {
60           $response->setTargetUrl($destination);
61         }
62         catch (\InvalidArgumentException $e) {
63         }
64       }
65
66       // Regardless of whether the target is the original one or the overridden
67       // destination, ensure that all redirects are safe.
68       if (!($response instanceof SecuredRedirectResponse)) {
69         try {
70           // SecuredRedirectResponse is an abstract class that requires a
71           // concrete implementation. Default to LocalRedirectResponse, which
72           // considers only redirects to within the same site as safe.
73           $safe_response = LocalRedirectResponse::createFromRedirectResponse($response);
74           $safe_response->setRequestContext($this->requestContext);
75         }
76         catch (\InvalidArgumentException $e) {
77           // If the above failed, it's because the redirect target wasn't
78           // local. Do not follow that redirect. Display an error message
79           // instead. We're already catching one exception, so trigger_error()
80           // rather than throw another one.
81           // We don't throw an exception, because this is a client error rather than a
82           // server error.
83           $message = 'Redirects to external URLs are not allowed by default, use \Drupal\Core\Routing\TrustedRedirectResponse for it.';
84           trigger_error($message, E_USER_ERROR);
85           $safe_response = new Response($message, 400);
86         }
87         $event->setResponse($safe_response);
88       }
89     }
90   }
91
92   /**
93    * Converts the passed in destination into an absolute URL.
94    *
95    * @param string $destination
96    *   The path for the destination. In case it starts with a slash it should
97    *   have the base path included already.
98    * @param string $scheme_and_host
99    *   The scheme and host string of the current request.
100    *
101    * @return string
102    *   The destination as absolute URL.
103    */
104   protected function getDestinationAsAbsoluteUrl($destination, $scheme_and_host) {
105     if (!UrlHelper::isExternal($destination)) {
106       // The destination query parameter can be a relative URL in the sense of
107       // not including the scheme and host, but its path is expected to be
108       // absolute (start with a '/'). For such a case, prepend the scheme and
109       // host, because the 'Location' header must be absolute.
110       if (strpos($destination, '/') === 0) {
111         $destination = $scheme_and_host . $destination;
112       }
113       else {
114         // Legacy destination query parameters can be internal paths that have
115         // not yet been converted to URLs.
116         $destination = UrlHelper::parse($destination);
117         $uri = 'base:' . $destination['path'];
118         $options = [
119           'query' => $destination['query'],
120           'fragment' => $destination['fragment'],
121           'absolute' => TRUE,
122         ];
123         // Treat this as if it's user input of a path relative to the site's
124         // base URL.
125         $destination = $this->unroutedUrlAssembler->assemble($uri, $options);
126       }
127     }
128     return $destination;
129   }
130
131   /**
132    * Registers the methods in this class that should be listeners.
133    *
134    * @return array
135    *   An array of event listener definitions.
136    */
137   public static function getSubscribedEvents() {
138     $events[KernelEvents::RESPONSE][] = ['checkRedirectUrl'];
139     return $events;
140   }
141
142 }