Chris@0: languageManager = $language_manager; Chris@0: $this->config = $config_factory->get('system.performance'); Chris@0: $this->requestPolicy = $request_policy; Chris@0: $this->responsePolicy = $response_policy; Chris@0: $this->cacheContextsManager = $cache_contexts_manager; Chris@0: $this->debugCacheabilityHeaders = $http_response_debug_cacheability_headers; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Sets extra headers on any responses, also subrequest ones. Chris@0: * Chris@0: * @param \Symfony\Component\HttpKernel\Event\FilterResponseEvent $event Chris@0: * The event to process. Chris@0: */ Chris@0: public function onAllResponds(FilterResponseEvent $event) { Chris@0: $response = $event->getResponse(); Chris@0: // Always add the 'http_response' cache tag to be able to invalidate every Chris@0: // response, for example after rebuilding routes. Chris@0: if ($response instanceof CacheableResponseInterface) { Chris@0: $response->getCacheableMetadata()->addCacheTags(['http_response']); Chris@0: } Chris@0: } Chris@0: Chris@0: /** Chris@0: * Sets extra headers on successful responses. Chris@0: * Chris@0: * @param \Symfony\Component\HttpKernel\Event\FilterResponseEvent $event Chris@0: * The event to process. Chris@0: */ Chris@0: public function onRespond(FilterResponseEvent $event) { Chris@0: if (!$event->isMasterRequest()) { Chris@0: return; Chris@0: } Chris@0: Chris@0: $request = $event->getRequest(); Chris@0: $response = $event->getResponse(); Chris@0: Chris@0: // Set the X-UA-Compatible HTTP header to force IE to use the most recent Chris@0: // rendering engine. Chris@0: $response->headers->set('X-UA-Compatible', 'IE=edge', FALSE); Chris@0: Chris@0: // Set the Content-language header. Chris@0: $response->headers->set('Content-language', $this->languageManager->getCurrentLanguage()->getId()); Chris@0: Chris@0: // Prevent browsers from sniffing a response and picking a MIME type Chris@0: // different from the declared content-type, since that can lead to Chris@0: // XSS and other vulnerabilities. Chris@0: // https://www.owasp.org/index.php/List_of_useful_HTTP_headers Chris@0: $response->headers->set('X-Content-Type-Options', 'nosniff', FALSE); Chris@0: $response->headers->set('X-Frame-Options', 'SAMEORIGIN', FALSE); Chris@0: Chris@0: // If the current response isn't an implementation of the Chris@0: // CacheableResponseInterface, we assume that a Response is either Chris@0: // explicitly not cacheable or that caching headers are already set in Chris@0: // another place. Chris@0: if (!$response instanceof CacheableResponseInterface) { Chris@0: if (!$this->isCacheControlCustomized($response)) { Chris@0: $this->setResponseNotCacheable($response, $request); Chris@0: } Chris@0: Chris@0: // HTTP/1.0 proxies do not support the Vary header, so prevent any caching Chris@0: // by sending an Expires date in the past. HTTP/1.1 clients ignore the Chris@0: // Expires header if a Cache-Control: max-age directive is specified (see Chris@0: // RFC 2616, section 14.9.3). Chris@0: if (!$response->headers->has('Expires')) { Chris@0: $this->setExpiresNoCache($response); Chris@0: } Chris@0: return; Chris@0: } Chris@0: Chris@0: if ($this->debugCacheabilityHeaders) { Chris@0: // Expose the cache contexts and cache tags associated with this page in a Chris@0: // X-Drupal-Cache-Contexts and X-Drupal-Cache-Tags header respectively. Chris@0: $response_cacheability = $response->getCacheableMetadata(); Chris@0: $response->headers->set('X-Drupal-Cache-Tags', implode(' ', $response_cacheability->getCacheTags())); Chris@0: $response->headers->set('X-Drupal-Cache-Contexts', implode(' ', $this->cacheContextsManager->optimizeTokens($response_cacheability->getCacheContexts()))); Chris@0: } Chris@0: Chris@0: $is_cacheable = ($this->requestPolicy->check($request) === RequestPolicyInterface::ALLOW) && ($this->responsePolicy->check($response, $request) !== ResponsePolicyInterface::DENY); Chris@0: Chris@0: // Add headers necessary to specify whether the response should be cached by Chris@0: // proxies and/or the browser. Chris@0: if ($is_cacheable && $this->config->get('cache.page.max_age') > 0) { Chris@0: if (!$this->isCacheControlCustomized($response)) { Chris@0: // Only add the default Cache-Control header if the controller did not Chris@0: // specify one on the response. Chris@0: $this->setResponseCacheable($response, $request); Chris@0: } Chris@0: } Chris@0: else { Chris@0: // If either the policy forbids caching or the sites configuration does Chris@0: // not allow to add a max-age directive, then enforce a Cache-Control Chris@0: // header declaring the response as not cacheable. Chris@0: $this->setResponseNotCacheable($response, $request); Chris@0: } Chris@0: } Chris@0: Chris@0: /** Chris@0: * Determine whether the given response has a custom Cache-Control header. Chris@0: * Chris@0: * Upon construction, the ResponseHeaderBag is initialized with an empty Chris@0: * Cache-Control header. Consequently it is not possible to check whether the Chris@0: * header was set explicitly by simply checking its presence. Instead, it is Chris@0: * necessary to examine the computed Cache-Control header and compare with Chris@0: * values known to be present only when Cache-Control was never set Chris@0: * explicitly. Chris@0: * Chris@0: * When neither Cache-Control nor any of the ETag, Last-Modified, Expires Chris@0: * headers are set on the response, ::get('Cache-Control') returns the value Chris@0: * 'no-cache, private'. If any of ETag, Last-Modified or Expires are set but Chris@0: * not Cache-Control, then 'private, must-revalidate' (in exactly this order) Chris@0: * is returned. Chris@0: * Chris@0: * @see \Symfony\Component\HttpFoundation\ResponseHeaderBag::computeCacheControlValue() Chris@0: * Chris@0: * @param \Symfony\Component\HttpFoundation\Response $response Chris@0: * Chris@0: * @return bool Chris@0: * TRUE when Cache-Control header was set explicitly on the given response. Chris@0: */ Chris@0: protected function isCacheControlCustomized(Response $response) { Chris@0: $cache_control = $response->headers->get('Cache-Control'); Chris@0: return $cache_control != 'no-cache, private' && $cache_control != 'private, must-revalidate'; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Add Cache-Control and Expires headers to a response which is not cacheable. Chris@0: * Chris@0: * @param \Symfony\Component\HttpFoundation\Response $response Chris@0: * A response object. Chris@0: * @param \Symfony\Component\HttpFoundation\Request $request Chris@0: * A request object. Chris@0: */ Chris@0: protected function setResponseNotCacheable(Response $response, Request $request) { Chris@0: $this->setCacheControlNoCache($response); Chris@0: $this->setExpiresNoCache($response); Chris@0: Chris@0: // There is no point in sending along headers necessary for cache Chris@0: // revalidation, if caching by proxies and browsers is denied in the first Chris@0: // place. Therefore remove ETag, Last-Modified and Vary in that case. Chris@0: $response->setEtag(NULL); Chris@0: $response->setLastModified(NULL); Chris@0: $response->setVary(NULL); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Add Cache-Control and Expires headers to a cacheable response. Chris@0: * Chris@0: * @param \Symfony\Component\HttpFoundation\Response $response Chris@0: * A response object. Chris@0: * @param \Symfony\Component\HttpFoundation\Request $request Chris@0: * A request object. Chris@0: */ Chris@0: protected function setResponseCacheable(Response $response, Request $request) { Chris@0: // HTTP/1.0 proxies do not support the Vary header, so prevent any caching Chris@0: // by sending an Expires date in the past. HTTP/1.1 clients ignore the Chris@0: // Expires header if a Cache-Control: max-age directive is specified (see Chris@0: // RFC 2616, section 14.9.3). Chris@0: if (!$response->headers->has('Expires')) { Chris@0: $this->setExpiresNoCache($response); Chris@0: } Chris@0: Chris@0: $max_age = $this->config->get('cache.page.max_age'); Chris@0: $response->headers->set('Cache-Control', 'public, max-age=' . $max_age); Chris@0: Chris@0: // In order to support HTTP cache-revalidation, ensure that there is a Chris@0: // Last-Modified and an ETag header on the response. Chris@0: if (!$response->headers->has('Last-Modified')) { Chris@0: $timestamp = REQUEST_TIME; Chris@0: $response->setLastModified(new \DateTime(gmdate(DateTimePlus::RFC7231, REQUEST_TIME))); Chris@0: } Chris@0: else { Chris@0: $timestamp = $response->getLastModified()->getTimestamp(); Chris@0: } Chris@0: $response->setEtag($timestamp); Chris@0: Chris@0: // Allow HTTP proxies to cache pages for anonymous users without a session Chris@0: // cookie. The Vary header is used to indicates the set of request-header Chris@0: // fields that fully determines whether a cache is permitted to use the Chris@0: // response to reply to a subsequent request for a given URL without Chris@0: // revalidation. Chris@0: if (!$response->hasVary() && !Settings::get('omit_vary_cookie')) { Chris@0: $response->setVary('Cookie', FALSE); Chris@0: } Chris@0: } Chris@0: Chris@0: /** Chris@0: * Disable caching in the browser and for HTTP/1.1 proxies and clients. Chris@0: * Chris@0: * @param \Symfony\Component\HttpFoundation\Response $response Chris@0: * A response object. Chris@0: */ Chris@0: protected function setCacheControlNoCache(Response $response) { Chris@0: $response->headers->set('Cache-Control', 'no-cache, must-revalidate'); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Disable caching in ancient browsers and for HTTP/1.0 proxies and clients. Chris@0: * Chris@0: * HTTP/1.0 proxies do not support the Vary header, so prevent any caching by Chris@0: * sending an Expires date in the past. HTTP/1.1 clients ignore the Expires Chris@0: * header if a Cache-Control: max-age= directive is specified (see RFC 2616, Chris@0: * section 14.9.3). Chris@0: * Chris@0: * @param \Symfony\Component\HttpFoundation\Response $response Chris@0: * A response object. Chris@0: */ Chris@0: protected function setExpiresNoCache(Response $response) { Chris@0: $response->setExpires(\DateTime::createFromFormat('j-M-Y H:i:s T', '19-Nov-1978 05:00:00 UTC')); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Registers the methods in this class that should be listeners. Chris@0: * Chris@0: * @return array Chris@0: * An array of event listener definitions. Chris@0: */ Chris@0: public static function getSubscribedEvents() { Chris@0: $events[KernelEvents::RESPONSE][] = ['onRespond']; Chris@0: // There is no specific reason for choosing 16 beside it should be executed Chris@0: // before ::onRespond(). Chris@0: $events[KernelEvents::RESPONSE][] = ['onAllResponds', 16]; Chris@0: return $events; Chris@0: } Chris@0: Chris@0: }