HeaderCallback.php 2.4 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283
  1. <?php
  2. namespace MediaWiki;
  3. /**
  4. * @since 1.29
  5. */
  6. class HeaderCallback {
  7. private static $headersSentException;
  8. private static $messageSent = false;
  9. /**
  10. * Register a callback to be called when headers are sent. There can only
  11. * be one of these handlers active, so all relevant actions have to be in
  12. * here.
  13. *
  14. * @since 1.29
  15. */
  16. public static function register() {
  17. header_register_callback( [ __CLASS__, 'callback' ] );
  18. }
  19. /**
  20. * The callback, which is called by the transport
  21. *
  22. * @since 1.29
  23. */
  24. public static function callback() {
  25. // Prevent caching of responses with cookies (T127993)
  26. $headers = [];
  27. foreach ( headers_list() as $header ) {
  28. $header = explode( ':', $header, 2 );
  29. // Note: The code below (currently) does not care about value-less headers
  30. if ( isset( $header[1] ) ) {
  31. $headers[ strtolower( trim( $header[0] ) ) ][] = trim( $header[1] );
  32. }
  33. }
  34. if ( isset( $headers['set-cookie'] ) ) {
  35. $cacheControl = isset( $headers['cache-control'] )
  36. ? implode( ', ', $headers['cache-control'] )
  37. : '';
  38. if ( !preg_match( '/(?:^|,)\s*(?:private|no-cache|no-store)\s*(?:$|,)/i',
  39. $cacheControl )
  40. ) {
  41. header( 'Expires: Thu, 01 Jan 1970 00:00:00 GMT' );
  42. header( 'Cache-Control: private, max-age=0, s-maxage=0' );
  43. \MediaWiki\Logger\LoggerFactory::getInstance( 'cache-cookies' )->warning(
  44. 'Cookies set on {url} with Cache-Control "{cache-control}"', [
  45. 'url' => \WebRequest::getGlobalRequestURL(),
  46. 'cookies' => $headers['set-cookie'],
  47. 'cache-control' => $cacheControl ?: '<not set>',
  48. ]
  49. );
  50. }
  51. }
  52. // Save a backtrace for logging in case it turns out that headers were sent prematurely
  53. self::$headersSentException = new \Exception( 'Headers already sent from this point' );
  54. }
  55. /**
  56. * Log a warning message if headers have already been sent. This can be
  57. * called before flushing the output.
  58. *
  59. * @since 1.29
  60. */
  61. public static function warnIfHeadersSent() {
  62. if ( headers_sent() && !self::$messageSent ) {
  63. self::$messageSent = true;
  64. \MWDebug::warning( 'Headers already sent, should send headers earlier than ' .
  65. wfGetCaller( 3 ) );
  66. $logger = \MediaWiki\Logger\LoggerFactory::getInstance( 'headers-sent' );
  67. $logger->error( 'Warning: headers were already sent from the location below', [
  68. 'exception' => self::$headersSentException,
  69. 'detection-trace' => new \Exception( 'Detected here' ),
  70. ] );
  71. }
  72. }
  73. }