aboutsummaryrefslogtreecommitdiffstats
path: root/includes/HeaderCallback.php
diff options
context:
space:
mode:
authorTim Starling <tstarling@wikimedia.org>2017-02-20 16:29:54 +1100
committerTim Starling <tstarling@wikimedia.org>2017-02-23 14:10:12 +1100
commitf193271cffbca25d01df6af3b33418b0721b1d5f (patch)
tree0d465a23a1fe8ed18335ec78f8fcb0b39014f6d0 /includes/HeaderCallback.php
parenta0b8d8e1f5cb56e2ee5bd8abc3703ea0f084deb6 (diff)
downloadmediawikicore-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.php69
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' ),
+ ] );
+ }
+ }
+}