Use header_register_callback to avoid caching responses with Set-Cookie headers
authorBrad Jorsch <bjorsch@wikimedia.org>
Tue, 8 Mar 2016 17:46:05 +0000 (12:46 -0500)
committerBrad Jorsch <bjorsch@wikimedia.org>
Tue, 8 Mar 2016 19:38:22 +0000 (14:38 -0500)
This change mirrors logic that has been in use on the Wikimedia
Foundation production cluster's Varnish cache system for over 2 years to
guard against accidentally caching backend responses which include
Set-Cookie headers.

Bug: T127993
Change-Id: Ic79cf6c959dd870d6458874a9bffe9e25aba4919

includes/Setup.php
includes/WebRequest.php

index f92c8c2..f26d789 100644 (file)
@@ -536,6 +536,35 @@ if ( !class_exists( 'AutoLoader' ) ) {
        require_once "$IP/includes/AutoLoader.php";
 }
 
+// Install a header callback to prevent caching of responses with cookies (T127993)
+if ( !$wgCommandLineMode ) {
+       header_register_callback( function () {
+               $headers = [];
+               foreach ( headers_list() as $header ) {
+                       list( $name, $value ) = explode( ':', $header, 2 );
+                       $headers[strtolower( trim( $name ) )][] = trim( $value );
+               }
+
+               if ( isset( $headers['set-cookie'] ) ) {
+                       $cacheControl = isset( $headers['cache-control'] )
+                               ? implode( ', ', $headers['cache-control'] )
+                               : '';
+
+                       if ( !preg_match( '/(?:^|,)\s*(?:private|no-cache|no-store)\s*(?:$|,)/i', $cacheControl ) ) {
+                               header( 'Expires: Thu, 01 Jan 1970 00:00:00 GMT' );
+                               header( 'Cache-Control: private, max-age=0, s-maxage=0' );
+                               MediaWiki\Logger\LoggerFactory::getInstance( 'cache-cookies' )->warning(
+                                       'Cookies set on {url} with Cache-Control "{cache-control}"', [
+                                               'url' => WebRequest::getGlobalRequestURL(),
+                                               'cookies' => $headers['set-cookie'],
+                                               'cache-control' => $cacheControl ?: '<not set>',
+                                       ]
+                               );
+                       }
+               }
+       } );
+}
+
 MWExceptionHandler::installHandler();
 
 require_once "$IP/includes/compat/normal/UtfNormalUtil.php";
index ce5cb96..812a320 100644 (file)
@@ -719,13 +719,13 @@ class WebRequest {
        }
 
        /**
-        * Return the path and query string portion of the request URI.
+        * Return the path and query string portion of the main request URI.
         * This will be suitable for use as a relative link in HTML output.
         *
         * @throws MWException
         * @return string
         */
-       public function getRequestURL() {
+       public static function getGlobalRequestURL() {
                if ( isset( $_SERVER['REQUEST_URI'] ) && strlen( $_SERVER['REQUEST_URI'] ) ) {
                        $base = $_SERVER['REQUEST_URI'];
                } elseif ( isset( $_SERVER['HTTP_X_ORIGINAL_URL'] )
@@ -762,6 +762,17 @@ class WebRequest {
                }
        }
 
+       /**
+        * Return the path and query string portion of the request URI.
+        * This will be suitable for use as a relative link in HTML output.
+        *
+        * @throws MWException
+        * @return string
+        */
+       public function getRequestURL() {
+               return self::getGlobalRequestURL();
+       }
+
        /**
         * Return the request URI with the canonical service and hostname, path,
         * and query string. This will be suitable for use as an absolute link