Mercurial > hg > isophonics-drupal-site
comparison core/lib/Drupal/Core/EventSubscriber/FinishResponseSubscriber.php @ 0:4c8ae668cc8c
Initial import (non-working)
author | Chris Cannam |
---|---|
date | Wed, 29 Nov 2017 16:09:58 +0000 |
parents | |
children |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:4c8ae668cc8c |
---|---|
1 <?php | |
2 | |
3 namespace Drupal\Core\EventSubscriber; | |
4 | |
5 use Drupal\Component\Datetime\DateTimePlus; | |
6 use Drupal\Core\Cache\CacheableResponseInterface; | |
7 use Drupal\Core\Cache\Context\CacheContextsManager; | |
8 use Drupal\Core\Config\ConfigFactoryInterface; | |
9 use Drupal\Core\Language\LanguageManagerInterface; | |
10 use Drupal\Core\PageCache\RequestPolicyInterface; | |
11 use Drupal\Core\PageCache\ResponsePolicyInterface; | |
12 use Drupal\Core\Site\Settings; | |
13 use Symfony\Component\HttpFoundation\Request; | |
14 use Symfony\Component\HttpFoundation\Response; | |
15 use Symfony\Component\HttpKernel\Event\FilterResponseEvent; | |
16 use Symfony\Component\HttpKernel\KernelEvents; | |
17 use Symfony\Component\EventDispatcher\EventSubscriberInterface; | |
18 | |
19 /** | |
20 * Response subscriber to handle finished responses. | |
21 */ | |
22 class FinishResponseSubscriber implements EventSubscriberInterface { | |
23 | |
24 /** | |
25 * The language manager object for retrieving the correct language code. | |
26 * | |
27 * @var \Drupal\Core\Language\LanguageManagerInterface | |
28 */ | |
29 protected $languageManager; | |
30 | |
31 /** | |
32 * A config object for the system performance configuration. | |
33 * | |
34 * @var \Drupal\Core\Config\Config | |
35 */ | |
36 protected $config; | |
37 | |
38 /** | |
39 * A policy rule determining the cacheability of a request. | |
40 * | |
41 * @var \Drupal\Core\PageCache\RequestPolicyInterface | |
42 */ | |
43 protected $requestPolicy; | |
44 | |
45 /** | |
46 * A policy rule determining the cacheability of the response. | |
47 * | |
48 * @var \Drupal\Core\PageCache\ResponsePolicyInterface | |
49 */ | |
50 protected $responsePolicy; | |
51 | |
52 /** | |
53 * The cache contexts manager service. | |
54 * | |
55 * @var \Drupal\Core\Cache\Context\CacheContextsManager | |
56 */ | |
57 protected $cacheContexts; | |
58 | |
59 /** | |
60 * Whether to send cacheability headers for debugging purposes. | |
61 * | |
62 * @var bool | |
63 */ | |
64 protected $debugCacheabilityHeaders = FALSE; | |
65 | |
66 /** | |
67 * Constructs a FinishResponseSubscriber object. | |
68 * | |
69 * @param \Drupal\Core\Language\LanguageManagerInterface $language_manager | |
70 * The language manager object for retrieving the correct language code. | |
71 * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory | |
72 * A config factory for retrieving required config objects. | |
73 * @param \Drupal\Core\PageCache\RequestPolicyInterface $request_policy | |
74 * A policy rule determining the cacheability of a request. | |
75 * @param \Drupal\Core\PageCache\ResponsePolicyInterface $response_policy | |
76 * A policy rule determining the cacheability of a response. | |
77 * @param \Drupal\Core\Cache\Context\CacheContextsManager $cache_contexts_manager | |
78 * The cache contexts manager service. | |
79 * @param bool $http_response_debug_cacheability_headers | |
80 * (optional) Whether to send cacheability headers for debugging purposes. | |
81 */ | |
82 public function __construct(LanguageManagerInterface $language_manager, ConfigFactoryInterface $config_factory, RequestPolicyInterface $request_policy, ResponsePolicyInterface $response_policy, CacheContextsManager $cache_contexts_manager, $http_response_debug_cacheability_headers = FALSE) { | |
83 $this->languageManager = $language_manager; | |
84 $this->config = $config_factory->get('system.performance'); | |
85 $this->requestPolicy = $request_policy; | |
86 $this->responsePolicy = $response_policy; | |
87 $this->cacheContextsManager = $cache_contexts_manager; | |
88 $this->debugCacheabilityHeaders = $http_response_debug_cacheability_headers; | |
89 } | |
90 | |
91 /** | |
92 * Sets extra headers on any responses, also subrequest ones. | |
93 * | |
94 * @param \Symfony\Component\HttpKernel\Event\FilterResponseEvent $event | |
95 * The event to process. | |
96 */ | |
97 public function onAllResponds(FilterResponseEvent $event) { | |
98 $response = $event->getResponse(); | |
99 // Always add the 'http_response' cache tag to be able to invalidate every | |
100 // response, for example after rebuilding routes. | |
101 if ($response instanceof CacheableResponseInterface) { | |
102 $response->getCacheableMetadata()->addCacheTags(['http_response']); | |
103 } | |
104 } | |
105 | |
106 /** | |
107 * Sets extra headers on successful responses. | |
108 * | |
109 * @param \Symfony\Component\HttpKernel\Event\FilterResponseEvent $event | |
110 * The event to process. | |
111 */ | |
112 public function onRespond(FilterResponseEvent $event) { | |
113 if (!$event->isMasterRequest()) { | |
114 return; | |
115 } | |
116 | |
117 $request = $event->getRequest(); | |
118 $response = $event->getResponse(); | |
119 | |
120 // Set the X-UA-Compatible HTTP header to force IE to use the most recent | |
121 // rendering engine. | |
122 $response->headers->set('X-UA-Compatible', 'IE=edge', FALSE); | |
123 | |
124 // Set the Content-language header. | |
125 $response->headers->set('Content-language', $this->languageManager->getCurrentLanguage()->getId()); | |
126 | |
127 // Prevent browsers from sniffing a response and picking a MIME type | |
128 // different from the declared content-type, since that can lead to | |
129 // XSS and other vulnerabilities. | |
130 // https://www.owasp.org/index.php/List_of_useful_HTTP_headers | |
131 $response->headers->set('X-Content-Type-Options', 'nosniff', FALSE); | |
132 $response->headers->set('X-Frame-Options', 'SAMEORIGIN', FALSE); | |
133 | |
134 // If the current response isn't an implementation of the | |
135 // CacheableResponseInterface, we assume that a Response is either | |
136 // explicitly not cacheable or that caching headers are already set in | |
137 // another place. | |
138 if (!$response instanceof CacheableResponseInterface) { | |
139 if (!$this->isCacheControlCustomized($response)) { | |
140 $this->setResponseNotCacheable($response, $request); | |
141 } | |
142 | |
143 // HTTP/1.0 proxies do not support the Vary header, so prevent any caching | |
144 // by sending an Expires date in the past. HTTP/1.1 clients ignore the | |
145 // Expires header if a Cache-Control: max-age directive is specified (see | |
146 // RFC 2616, section 14.9.3). | |
147 if (!$response->headers->has('Expires')) { | |
148 $this->setExpiresNoCache($response); | |
149 } | |
150 return; | |
151 } | |
152 | |
153 if ($this->debugCacheabilityHeaders) { | |
154 // Expose the cache contexts and cache tags associated with this page in a | |
155 // X-Drupal-Cache-Contexts and X-Drupal-Cache-Tags header respectively. | |
156 $response_cacheability = $response->getCacheableMetadata(); | |
157 $response->headers->set('X-Drupal-Cache-Tags', implode(' ', $response_cacheability->getCacheTags())); | |
158 $response->headers->set('X-Drupal-Cache-Contexts', implode(' ', $this->cacheContextsManager->optimizeTokens($response_cacheability->getCacheContexts()))); | |
159 } | |
160 | |
161 $is_cacheable = ($this->requestPolicy->check($request) === RequestPolicyInterface::ALLOW) && ($this->responsePolicy->check($response, $request) !== ResponsePolicyInterface::DENY); | |
162 | |
163 // Add headers necessary to specify whether the response should be cached by | |
164 // proxies and/or the browser. | |
165 if ($is_cacheable && $this->config->get('cache.page.max_age') > 0) { | |
166 if (!$this->isCacheControlCustomized($response)) { | |
167 // Only add the default Cache-Control header if the controller did not | |
168 // specify one on the response. | |
169 $this->setResponseCacheable($response, $request); | |
170 } | |
171 } | |
172 else { | |
173 // If either the policy forbids caching or the sites configuration does | |
174 // not allow to add a max-age directive, then enforce a Cache-Control | |
175 // header declaring the response as not cacheable. | |
176 $this->setResponseNotCacheable($response, $request); | |
177 } | |
178 } | |
179 | |
180 /** | |
181 * Determine whether the given response has a custom Cache-Control header. | |
182 * | |
183 * Upon construction, the ResponseHeaderBag is initialized with an empty | |
184 * Cache-Control header. Consequently it is not possible to check whether the | |
185 * header was set explicitly by simply checking its presence. Instead, it is | |
186 * necessary to examine the computed Cache-Control header and compare with | |
187 * values known to be present only when Cache-Control was never set | |
188 * explicitly. | |
189 * | |
190 * When neither Cache-Control nor any of the ETag, Last-Modified, Expires | |
191 * headers are set on the response, ::get('Cache-Control') returns the value | |
192 * 'no-cache, private'. If any of ETag, Last-Modified or Expires are set but | |
193 * not Cache-Control, then 'private, must-revalidate' (in exactly this order) | |
194 * is returned. | |
195 * | |
196 * @see \Symfony\Component\HttpFoundation\ResponseHeaderBag::computeCacheControlValue() | |
197 * | |
198 * @param \Symfony\Component\HttpFoundation\Response $response | |
199 * | |
200 * @return bool | |
201 * TRUE when Cache-Control header was set explicitly on the given response. | |
202 */ | |
203 protected function isCacheControlCustomized(Response $response) { | |
204 $cache_control = $response->headers->get('Cache-Control'); | |
205 return $cache_control != 'no-cache, private' && $cache_control != 'private, must-revalidate'; | |
206 } | |
207 | |
208 /** | |
209 * Add Cache-Control and Expires headers to a response which is not cacheable. | |
210 * | |
211 * @param \Symfony\Component\HttpFoundation\Response $response | |
212 * A response object. | |
213 * @param \Symfony\Component\HttpFoundation\Request $request | |
214 * A request object. | |
215 */ | |
216 protected function setResponseNotCacheable(Response $response, Request $request) { | |
217 $this->setCacheControlNoCache($response); | |
218 $this->setExpiresNoCache($response); | |
219 | |
220 // There is no point in sending along headers necessary for cache | |
221 // revalidation, if caching by proxies and browsers is denied in the first | |
222 // place. Therefore remove ETag, Last-Modified and Vary in that case. | |
223 $response->setEtag(NULL); | |
224 $response->setLastModified(NULL); | |
225 $response->setVary(NULL); | |
226 } | |
227 | |
228 /** | |
229 * Add Cache-Control and Expires headers to a cacheable response. | |
230 * | |
231 * @param \Symfony\Component\HttpFoundation\Response $response | |
232 * A response object. | |
233 * @param \Symfony\Component\HttpFoundation\Request $request | |
234 * A request object. | |
235 */ | |
236 protected function setResponseCacheable(Response $response, Request $request) { | |
237 // HTTP/1.0 proxies do not support the Vary header, so prevent any caching | |
238 // by sending an Expires date in the past. HTTP/1.1 clients ignore the | |
239 // Expires header if a Cache-Control: max-age directive is specified (see | |
240 // RFC 2616, section 14.9.3). | |
241 if (!$response->headers->has('Expires')) { | |
242 $this->setExpiresNoCache($response); | |
243 } | |
244 | |
245 $max_age = $this->config->get('cache.page.max_age'); | |
246 $response->headers->set('Cache-Control', 'public, max-age=' . $max_age); | |
247 | |
248 // In order to support HTTP cache-revalidation, ensure that there is a | |
249 // Last-Modified and an ETag header on the response. | |
250 if (!$response->headers->has('Last-Modified')) { | |
251 $timestamp = REQUEST_TIME; | |
252 $response->setLastModified(new \DateTime(gmdate(DateTimePlus::RFC7231, REQUEST_TIME))); | |
253 } | |
254 else { | |
255 $timestamp = $response->getLastModified()->getTimestamp(); | |
256 } | |
257 $response->setEtag($timestamp); | |
258 | |
259 // Allow HTTP proxies to cache pages for anonymous users without a session | |
260 // cookie. The Vary header is used to indicates the set of request-header | |
261 // fields that fully determines whether a cache is permitted to use the | |
262 // response to reply to a subsequent request for a given URL without | |
263 // revalidation. | |
264 if (!$response->hasVary() && !Settings::get('omit_vary_cookie')) { | |
265 $response->setVary('Cookie', FALSE); | |
266 } | |
267 } | |
268 | |
269 /** | |
270 * Disable caching in the browser and for HTTP/1.1 proxies and clients. | |
271 * | |
272 * @param \Symfony\Component\HttpFoundation\Response $response | |
273 * A response object. | |
274 */ | |
275 protected function setCacheControlNoCache(Response $response) { | |
276 $response->headers->set('Cache-Control', 'no-cache, must-revalidate'); | |
277 } | |
278 | |
279 /** | |
280 * Disable caching in ancient browsers and for HTTP/1.0 proxies and clients. | |
281 * | |
282 * HTTP/1.0 proxies do not support the Vary header, so prevent any caching by | |
283 * sending an Expires date in the past. HTTP/1.1 clients ignore the Expires | |
284 * header if a Cache-Control: max-age= directive is specified (see RFC 2616, | |
285 * section 14.9.3). | |
286 * | |
287 * @param \Symfony\Component\HttpFoundation\Response $response | |
288 * A response object. | |
289 */ | |
290 protected function setExpiresNoCache(Response $response) { | |
291 $response->setExpires(\DateTime::createFromFormat('j-M-Y H:i:s T', '19-Nov-1978 05:00:00 UTC')); | |
292 } | |
293 | |
294 /** | |
295 * Registers the methods in this class that should be listeners. | |
296 * | |
297 * @return array | |
298 * An array of event listener definitions. | |
299 */ | |
300 public static function getSubscribedEvents() { | |
301 $events[KernelEvents::RESPONSE][] = ['onRespond']; | |
302 // There is no specific reason for choosing 16 beside it should be executed | |
303 // before ::onRespond(). | |
304 $events[KernelEvents::RESPONSE][] = ['onAllResponds', 16]; | |
305 return $events; | |
306 } | |
307 | |
308 } |