diff options
author | Tim Starling <tstarling@wikimedia.org> | 2017-02-20 16:29:54 +1100 |
---|---|---|
committer | Tim Starling <tstarling@wikimedia.org> | 2017-02-23 14:10:12 +1100 |
commit | f193271cffbca25d01df6af3b33418b0721b1d5f (patch) | |
tree | 0d465a23a1fe8ed18335ec78f8fcb0b39014f6d0 /includes/HeaderCallback.php | |
parent | a0b8d8e1f5cb56e2ee5bd8abc3703ea0f084deb6 (diff) | |
download | mediawikicore-f193271cffbca25d01df6af3b33418b0721b1d5f.tar.gz mediawikicore-f193271cffbca25d01df6af3b33418b0721b1d5f.zip |
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
Diffstat (limited to 'includes/HeaderCallback.php')
-rw-r--r-- | includes/HeaderCallback.php | 69 |
1 files changed, 69 insertions, 0 deletions
diff --git a/includes/HeaderCallback.php b/includes/HeaderCallback.php new file mode 100644 index 000000000000..b2ca6733f6cd --- /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' ), + ] ); + } + } +} |