[TASK] Separate sending from gathering HTTP headers 58/57758/4
authorBenni Mack <benni@typo3.org>
Wed, 1 Aug 2018 15:41:50 +0000 (17:41 +0200)
committerChristian Kuhn <lolli@schwarzbu.ch>
Wed, 1 Aug 2018 19:13:09 +0000 (21:13 +0200)
In order to streamline HTTP response output, the list compiling of
headers sent to the browser is separated from the actual sending
(PHP call "header()").

This is a pre-patch to allow further refactorings for
better PSR-7 support.

Resolves: #85709
Releases: master
Change-Id: Id1f7750fcdf6a40ed23d3529ae8552d0a85abd93
Reviewed-on: https://review.typo3.org/57758
Reviewed-by: Markus Klein <markus.klein@typo3.org>
Tested-by: Markus Klein <markus.klein@typo3.org>
Tested-by: TYPO3com <no-reply@typo3.com>
Reviewed-by: Andreas Fernandez <a.fernandez@scripting-base.de>
Reviewed-by: Nicole Cordes <typo3@cordes.co>
Reviewed-by: Christian Kuhn <lolli@schwarzbu.ch>
Tested-by: Christian Kuhn <lolli@schwarzbu.ch>
typo3/sysext/core/Classes/Authentication/AbstractUserAuthentication.php
typo3/sysext/frontend/Classes/Controller/TypoScriptFrontendController.php

index 70ad670..55db936 100644 (file)
@@ -405,24 +405,7 @@ abstract class AbstractUserAuthentication implements LoggerAwareInterface
         // Make certain that NO user is set initially
         $this->user = null;
         // Set all possible headers that could ensure that the script is not cached on the client-side
-        if ($this->sendNoCacheHeaders && !Environment::isCli()) {
-            header('Expires: 0');
-            header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT');
-            $cacheControlHeader = 'no-cache, must-revalidate';
-            $pragmaHeader = 'no-cache';
-            // Prevent error message in IE when using a https connection
-            // see http://forge.typo3.org/issues/24125
-            $clientInfo = GeneralUtility::clientInfo();
-            if ($clientInfo['BROWSER'] === 'msie' && GeneralUtility::getIndpEnv('TYPO3_SSL')) {
-                // Some IEs can not handle no-cache
-                // see http://support.microsoft.com/kb/323308/en-us
-                $cacheControlHeader = 'must-revalidate';
-                // IE needs "Pragma: private" if SSL connection
-                $pragmaHeader = 'private';
-            }
-            header('Cache-Control: ' . $cacheControlHeader);
-            header('Pragma: ' . $pragmaHeader);
-        }
+        $this->sendHttpHeaders();
         // Load user session, check to see if anyone has submitted login-information and if so authenticate
         // the user with the session. $this->user[uid] may be used to write log...
         $this->checkAuthentication();
@@ -449,6 +432,53 @@ abstract class AbstractUserAuthentication implements LoggerAwareInterface
     }
 
     /**
+     * Set all possible headers that could ensure that the script
+     * is not cached on the client-side.
+     *
+     * Only do this if $this->sendNoCacheHeaders is set.
+     */
+    protected function sendHttpHeaders()
+    {
+        // skip sending the "no-cache" headers if it's a CLI request or the no-cache headers should not be sent.
+        if (!$this->sendNoCacheHeaders || Environment::isCli()) {
+            return;
+        }
+        $httpHeaders = $this->getHttpHeaders();
+        foreach ($httpHeaders as $httpHeaderName => $value) {
+            header($httpHeaderName . ': ' . $value);
+        }
+    }
+
+    /**
+     * Get the http headers to be sent if an authenticated user is available, in order to disallow
+     * browsers to store the response on the client side.
+     *
+     * @return array
+     */
+    protected function getHttpHeaders(): array
+    {
+        $headers = [
+            'Expires' => 0,
+            'Last-Modified' => gmdate('D, d M Y H:i:s') . ' GMT'
+        ];
+        $cacheControlHeader = 'no-cache, must-revalidate';
+        $pragmaHeader = 'no-cache';
+        // Prevent error message in IE when using a https connection
+        // see http://forge.typo3.org/issues/24125
+        $clientInfo = GeneralUtility::clientInfo();
+        if ($clientInfo['BROWSER'] === 'msie' && GeneralUtility::getIndpEnv('TYPO3_SSL')) {
+            // Some IEs can not handle no-cache
+            // see http://support.microsoft.com/kb/323308/en-us
+            $cacheControlHeader = 'must-revalidate';
+            // IE needs "Pragma: private" if SSL connection
+            $pragmaHeader = 'private';
+        }
+        $headers['Cache-Control'] = $cacheControlHeader;
+        $headers['Pragma'] = $pragmaHeader;
+        return $headers;
+    }
+
+    /**
      * Sets the session cookie for the current disposal.
      *
      * @throws Exception
index de9a44e..10de128 100644 (file)
@@ -3861,54 +3861,66 @@ class TypoScriptFrontendController implements LoggerAwareInterface
     }
 
     /**
-     * Send cache headers good for client/reverse proxy caching
-     * This function should not be called if the page content is
-     * temporary (like for "Page is being generated..." message,
-     * but in that case it is ok because the config-variables
-     * are not yet available and so will not allow to send
-     * cache headers)
+     * Send cache headers good for client/reverse proxy caching.
+     * @see getCacheHeaders() for more details
      */
     public function sendCacheHeaders()
     {
+        $headers = $this->getCacheHeaders();
+        foreach ($headers as $header => $value) {
+            header($header . ': ' . $value);
+        }
+    }
+
+    /**
+     * Get cache headers good for client/reverse proxy caching.
+     * This function should not be called if the page content is temporary (like for "Page is being generated..."
+     * message, but in that case it is ok because the config-variables are not yet available and so will not allow to
+     * send cache headers).
+     *
+     * @return array
+     */
+    protected function getCacheHeaders(): array
+    {
         // Getting status whether we can send cache control headers for proxy caching:
         $doCache = $this->isStaticCacheble();
         // This variable will be TRUE unless cache headers are configured to be sent ONLY if a branch does not allow logins and logins turns out to be allowed anyway...
         $loginsDeniedCfg = empty($this->config['config']['sendCacheHeaders_onlyWhenLoginDeniedInBranch']) || empty($this->loginAllowedInBranch);
         // Finally, when backend users are logged in, do not send cache headers at all (Admin Panel might be displayed for instance).
-        if ($doCache && !$this->isBackendUserLoggedIn() && !$this->doWorkspacePreview() && $loginsDeniedCfg) {
-            // Build headers:
+        $this->isClientCachable = $doCache && !$this->isBackendUserLoggedIn() && !$this->doWorkspacePreview() && $loginsDeniedCfg;
+        if ($this->isClientCachable) {
             $headers = [
-                'Expires: ' . gmdate('D, d M Y H:i:s T', $this->cacheExpires),
-                'ETag"' . md5($this->content) . '"',
-                'Cache-Controlmax-age=' . ($this->cacheExpires - $GLOBALS['EXEC_TIME']),
+                'Expires' => gmdate('D, d M Y H:i:s T', $this->cacheExpires),
+                'ETag' => '"' . md5($this->content) . '"',
+                'Cache-Control' => 'max-age=' . ($this->cacheExpires - $GLOBALS['EXEC_TIME']),
                 // no-cache
-                'Pragmapublic'
+                'Pragma' => 'public'
             ];
-            $this->isClientCachable = true;
         } else {
-            // Build headers
             // "no-store" is used to ensure that the client HAS to ask the server every time, and is not allowed to store anything at all
             $headers = [
-                'Cache-Controlprivate, no-store'
+                'Cache-Control' => 'private, no-store'
             ];
-            $this->isClientCachable = false;
             // Now, if a backend user is logged in, tell him in the Admin Panel log what the caching status would have been:
             if ($this->isBackendUserLoggedIn()) {
                 if ($doCache) {
                     $this->getTimeTracker()->setTSlogMessage('Cache-headers with max-age "' . ($this->cacheExpires - $GLOBALS['EXEC_TIME']) . '" would have been sent');
                 } else {
-                    $reasonMsg = '';
-                    $reasonMsg .= !$this->no_cache ? '' : 'Caching disabled (no_cache). ';
-                    $reasonMsg .= !$this->isINTincScript() ? '' : '*_INT object(s) on page. ';
-                    $reasonMsg .= !is_array($this->fe_user->user) ? '' : 'Frontend user logged in. ';
-                    $this->getTimeTracker()->setTSlogMessage('Cache-headers would disable proxy caching! Reason(s): "' . $reasonMsg . '"', 1);
+                    $reasonMsg = [];
+                    if ($this->no_cache) {
+                        $reasonMsg[] = 'Caching disabled (no_cache).';
+                    }
+                    if ($this->isINTincScript()) {
+                        $reasonMsg[] = '*_INT object(s) on page.';
+                    }
+                    if (is_array($this->fe_user->user)) {
+                        $reasonMsg[] = 'Frontend user logged in.';
+                    }
+                    $this->getTimeTracker()->setTSlogMessage('Cache-headers would disable proxy caching! Reason(s): "' . implode(' ', $reasonMsg) . '"', 1);
                 }
             }
         }
-        // Send headers:
-        foreach ($headers as $hL) {
-            header($hL);
-        }
+        return $headers;
     }
 
     /**
@@ -4005,12 +4017,26 @@ class TypoScriptFrontendController implements LoggerAwareInterface
     public function addTempContentHttpHeaders()
     {
         header('HTTP/1.0 503 Service unavailable');
-        header('Retry-after: 3600');
-        header('Pragma: no-cache');
-        header('Cache-control: no-cache');
-        header('Expire: 0');
+        $headers = $this->getHttpHeadersForTemporaryContent();
+        foreach ($headers as $header => $value) {
+            header($header . ': ' . $value);
+        }
     }
 
+    /**
+     * Returns HTTP headers for temporary content.
+     * These headers prevent search engines from caching temporary content and asks them to revisit this page again.
+     * Please ensure to also send a 503 HTTP Status code with these headers.
+     */
+    protected function getHttpHeadersForTemporaryContent(): array
+    {
+        return [
+            'Retry-after' => '3600',
+            'Pragma' => 'no-cache',
+            'Cache-control' => 'no-cache',
+            'Expire' => 0,
+        ];
+    }
     /********************************************
      *
      * Various internal API functions