jenkins-bot has submitted this change and it was merged. ( 
https://gerrit.wikimedia.org/r/338705 )

Change subject: Log a backtrace from the culprit location if headers were 
already sent
......................................................................


Log a backtrace from the culprit location if headers were already sent

Install the backtrace collector very early, so that we can get the
backtrace even if headers were sent from LocalSettings.php.

Bug: T157392
Change-Id: I9bc732b34481c95afb5362e135a87bd4302498e2
---
M autoload.php
M includes/GlobalFunctions.php
A includes/HeaderCallback.php
M includes/Setup.php
M includes/WebResponse.php
M includes/WebStart.php
M includes/libs/HttpStatus.php
M includes/resourceloader/ResourceLoader.php
8 files changed, 77 insertions(+), 32 deletions(-)

Approvals:
  Krinkle: Looks good to me, but someone else must approve
  Chad: Looks good to me, approved
  jenkins-bot: Verified



diff --git a/autoload.php b/autoload.php
index b21310e..fce7891 100644
--- a/autoload.php
+++ b/autoload.php
@@ -852,6 +852,7 @@
        'MediaWiki\\Auth\\UsernameAuthenticationRequest' => __DIR__ . 
'/includes/auth/UsernameAuthenticationRequest.php',
        'MediaWiki\\Diff\\ComplexityException' => __DIR__ . 
'/includes/diff/ComplexityException.php',
        'MediaWiki\\Diff\\WordAccumulator' => __DIR__ . 
'/includes/diff/WordAccumulator.php',
+       'MediaWiki\\HeaderCallback' => __DIR__ . '/includes/HeaderCallback.php',
        'MediaWiki\\Interwiki\\ClassicInterwikiLookup' => __DIR__ . 
'/includes/interwiki/ClassicInterwikiLookup.php',
        'MediaWiki\\Interwiki\\InterwikiLookup' => __DIR__ . 
'/includes/interwiki/InterwikiLookup.php',
        'MediaWiki\\Interwiki\\InterwikiLookupAdapter' => __DIR__ . 
'/includes/interwiki/InterwikiLookupAdapter.php',
diff --git a/includes/GlobalFunctions.php b/includes/GlobalFunctions.php
index 46def53..7a34c74 100644
--- a/includes/GlobalFunctions.php
+++ b/includes/GlobalFunctions.php
@@ -1788,6 +1788,7 @@
                $wgOut->sendCacheControl();
        }
 
+       MediaWiki\HeaderCallback::warnIfHeadersSent();
        header( 'Content-type: text/html; charset=utf-8' );
        print '<!DOCTYPE html>' .
                '<html><head><title>' .
diff --git a/includes/HeaderCallback.php b/includes/HeaderCallback.php
new file mode 100644
index 0000000..b2ca673
--- /dev/null
+++ b/includes/HeaderCallback.php
@@ -0,0 +1,69 @@
+<?php
+
+namespace MediaWiki;
+
+class HeaderCallback {
+       private static $headersSentException;
+       private static $messageSent = false;
+
+       /**
+        * Register a callback to be called when headers are sent. There can 
only
+        * be one of these handlers active, so all relevant actions have to be 
in
+        * here.
+        */
+       public static function register() {
+               header_register_callback( [ __CLASS__, 'callback' ] );
+       }
+
+       /**
+        * The callback, which is called by the transport
+        */
+       public static function callback() {
+               // Prevent caching of responses with cookies (T127993)
+               $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>',
+                                       ]
+                               );
+                       }
+               }
+
+               // Save a backtrace for logging in case it turns out that 
headers were sent prematurely
+               self::$headersSentException = new \Exception( 'Headers already 
sent from this point' );
+       }
+
+       /**
+        * Log a warning message if headers have already been sent. This can be
+        * called before flushing the output.
+        */
+       public static function warnIfHeadersSent() {
+               if ( headers_sent() && !self::$messageSent ) {
+                       self::$messageSent = true;
+                       \MWDebug::warning( 'Headers already sent, should send 
headers earlier than ' .
+                               wfGetCaller( 3 ) );
+                       $logger = \MediaWiki\Logger\LoggerFactory::getInstance( 
'headers-sent' );
+                       $logger->error( 'Warning: headers were already sent 
from the location below', [
+                               'exception' => self::$headersSentException,
+                               'detection-trace' => new \Exception( 'Detected 
here' ),
+                       ] );
+               }
+       }
+}
diff --git a/includes/Setup.php b/includes/Setup.php
index 01ba1e8..72ed1fd 100644
--- a/includes/Setup.php
+++ b/includes/Setup.php
@@ -521,35 +521,6 @@
 // is complete.
 define( 'MW_SERVICE_BOOTSTRAP_COMPLETE', 1 );
 
-// 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";
diff --git a/includes/WebResponse.php b/includes/WebResponse.php
index f5fb47f..0208a72 100644
--- a/includes/WebResponse.php
+++ b/includes/WebResponse.php
@@ -39,9 +39,7 @@
         * @param null|int $http_response_code Forces the HTTP response code to 
the specified value.
         */
        public function header( $string, $replace = true, $http_response_code = 
null ) {
-               if ( headers_sent() ) {
-                       MWDebug::warning( 'Headers already sent, should send 
headers earlier than ' . wfGetCaller() );
-               }
+               \MediaWiki\HeaderCallback::warnIfHeadersSent();
                if ( $http_response_code ) {
                        header( $string, $replace, $http_response_code );
                } else {
diff --git a/includes/WebStart.php b/includes/WebStart.php
index 6e4fb09..861e532 100644
--- a/includes/WebStart.php
+++ b/includes/WebStart.php
@@ -104,6 +104,9 @@
        die( 1 );
 }
 
+# Install a header callback
+MediaWiki\HeaderCallback::register();
+
 if ( defined( 'MW_CONFIG_CALLBACK' ) ) {
        # Use a callback function to configure MediaWiki
        call_user_func( MW_CONFIG_CALLBACK );
diff --git a/includes/libs/HttpStatus.php b/includes/libs/HttpStatus.php
index 72fc333..27f8728 100644
--- a/includes/libs/HttpStatus.php
+++ b/includes/libs/HttpStatus.php
@@ -101,6 +101,7 @@
                        return false;
                }
 
+               MediaWiki\HeaderCallback::warnIfHeadersSent();
                if ( $version === null ) {
                        $version = isset( $_SERVER['SERVER_PROTOCOL'] ) &&
                                $_SERVER['SERVER_PROTOCOL'] === 'HTTP/1.0' ?
diff --git a/includes/resourceloader/ResourceLoader.php 
b/includes/resourceloader/ResourceLoader.php
index a55cbc1..59c60df 100644
--- a/includes/resourceloader/ResourceLoader.php
+++ b/includes/resourceloader/ResourceLoader.php
@@ -818,6 +818,7 @@
         * @return void
         */
        protected function sendResponseHeaders( ResourceLoaderContext $context, 
$etag, $errors ) {
+               \MediaWiki\HeaderCallback::warnIfHeadersSent();
                $rlMaxage = $this->config->get( 'ResourceLoaderMaxage' );
                // Use a short cache expiry so that updates propagate to 
clients quickly, if:
                // - No version specified (shared resources, e.g. stylesheets)

-- 
To view, visit https://gerrit.wikimedia.org/r/338705
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings

Gerrit-MessageType: merged
Gerrit-Change-Id: I9bc732b34481c95afb5362e135a87bd4302498e2
Gerrit-PatchSet: 3
Gerrit-Project: mediawiki/core
Gerrit-Branch: master
Gerrit-Owner: Tim Starling <tstarl...@wikimedia.org>
Gerrit-Reviewer: Chad <ch...@wikimedia.org>
Gerrit-Reviewer: Gergő Tisza <gti...@wikimedia.org>
Gerrit-Reviewer: Jforrester <jforres...@wikimedia.org>
Gerrit-Reviewer: Krinkle <krinklem...@gmail.com>
Gerrit-Reviewer: Legoktm <lego...@member.fsf.org>
Gerrit-Reviewer: Tim Starling <tstarl...@wikimedia.org>
Gerrit-Reviewer: jenkins-bot <>

_______________________________________________
MediaWiki-commits mailing list
MediaWiki-commits@lists.wikimedia.org
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to