MediaWiki  master
OutputHandler.php
Go to the documentation of this file.
1 <?php
23 namespace MediaWiki;
24 
26 
41  public static function handle( $s, $phase ) {
43 
44  // Don't send headers if output is being discarded (T278579)
45  if ( ( $phase & PHP_OUTPUT_HANDLER_CLEAN ) === PHP_OUTPUT_HANDLER_CLEAN ) {
46  $logger = LoggerFactory::getInstance( 'output' );
47  $logger->debug( __METHOD__ . " entrypoint={entry}; size={size}; phase=$phase", [
48  'entry' => MW_ENTRY_POINT,
49  'size' => strlen( $s ),
50  ] );
51 
52  return $s;
53  }
54 
55  if ( $wgMangleFlashPolicy ) {
56  $s = self::mangleFlashPolicy( $s );
57  }
58 
59  // Check if a compression output buffer is already enabled via php.ini. Such
60  // buffers exists at the start of the request and are reflected by ob_get_level().
61  $phpHandlesCompression = (
62  ini_get( 'output_handler' ) === 'ob_gzhandler' ||
63  ini_get( 'zlib.output_handler' ) === 'ob_gzhandler' ||
64  !in_array(
65  strtolower( ini_get( 'zlib.output_compression' ) ),
66  [ '', 'off', '0' ]
67  )
68  );
69 
70  if (
71  // Compression is not already handled by an internal PHP buffer
72  !$phpHandlesCompression &&
73  // Compression is not disabled by the application entry point
74  !defined( 'MW_NO_OUTPUT_COMPRESSION' ) &&
75  // Compression is not disabled by site configuration
77  ) {
78  $s = self::handleGzip( $s );
79  }
80 
81  if (
82  // Response body length does not depend on internal PHP compression buffer
83  !$phpHandlesCompression &&
84  // Response body length does not depend on mangling by a custom buffer
85  !ini_get( 'output_handler' ) &&
86  !ini_get( 'zlib.output_handler' )
87  ) {
88  self::emitContentLength( strlen( $s ) );
89  }
90 
91  return $s;
92  }
93 
104  private static function findUriExtension() {
105  // @todo FIXME: this sort of dupes some code in WebRequest::getRequestUrl()
106  if ( isset( $_SERVER['REQUEST_URI'] ) ) {
107  // Strip the query string...
108  $path = explode( '?', $_SERVER['REQUEST_URI'], 2 )[0];
109  } elseif ( isset( $_SERVER['SCRIPT_NAME'] ) ) {
110  // Probably IIS. QUERY_STRING appears separately.
111  $path = $_SERVER['SCRIPT_NAME'];
112  } else {
113  // Can't get the path from the server? :(
114  return '';
115  }
116 
117  $period = strrpos( $path, '.' );
118  if ( $period !== false ) {
119  return strtolower( substr( $path, $period ) );
120  }
121  return '';
122  }
123 
134  private static function handleGzip( $s ) {
135  if ( !function_exists( 'gzencode' ) ) {
136  wfDebug( __METHOD__ . "() skipping compression (gzencode unavailable)" );
137  return $s;
138  }
139  if ( headers_sent() ) {
140  wfDebug( __METHOD__ . "() skipping compression (headers already sent)" );
141  return $s;
142  }
143 
144  $ext = self::findUriExtension();
145  if ( $ext == '.gz' || $ext == '.tgz' ) {
146  // Don't do gzip compression if the URL path ends in .gz or .tgz
147  // This confuses Safari and triggers a download of the page,
148  // even though it's pretty clearly labeled as viewable HTML.
149  // Bad Safari! Bad!
150  return $s;
151  }
152 
153  if ( $s === '' ) {
154  // Do not gzip empty HTTP responses since that would not only bloat the body
155  // length, but it would result in invalid HTTP responses when the HTTP status code
156  // is one that must not be accompanied by a body (e.g. "204 No Content").
157  return $s;
158  }
159 
160  if ( wfClientAcceptsGzip() ) {
161  wfDebug( __METHOD__ . "() is compressing output" );
162  header( 'Content-Encoding: gzip' );
163  $s = gzencode( $s, 6 );
164  }
165 
166  // Set vary header if it hasn't been set already
167  $headers = headers_list();
168  $foundVary = false;
169  foreach ( $headers as $header ) {
170  $headerName = strtolower( substr( $header, 0, 5 ) );
171  if ( $headerName == 'vary:' ) {
172  $foundVary = true;
173  break;
174  }
175  }
176  if ( !$foundVary ) {
177  header( 'Vary: Accept-Encoding' );
178  }
179  return $s;
180  }
181 
188  private static function mangleFlashPolicy( $s ) {
189  # Avoid weird excessive memory usage in PCRE on big articles
190  if ( preg_match( '/<\s*cross-domain-policy(?=\s|>)/i', $s ) ) {
191  return preg_replace( '/<(\s*)(cross-domain-policy(?=\s|>))/i', '<$1NOT-$2', $s );
192  } else {
193  return $s;
194  }
195  }
196 
212  private static function emitContentLength( $length ) {
213  if ( headers_sent() ) {
214  wfDebug( __METHOD__ . "() headers already sent" );
215  return;
216  }
217 
218  if (
219  in_array( http_response_code(), [ 200, 404 ], true ) ||
220  ( $_SERVER['SERVER_PROTOCOL'] ?? null ) === 'HTTP/1.0'
221  ) {
222  header( "Content-Length: $length" );
223  }
224  }
225 }
MediaWiki\OutputHandler
Definition: OutputHandler.php:30
MediaWiki\OutputHandler\findUriExtension
static findUriExtension()
Get the "file extension" that some client apps will estimate from the currently-requested URL.
Definition: OutputHandler.php:104
$wgMangleFlashPolicy
$wgMangleFlashPolicy
When OutputHandler is used, mangle any output that contains <cross-domain-policy>.
Definition: DefaultSettings.php:3988
MediaWiki\OutputHandler\handleGzip
static handleGzip( $s)
Handler that compresses data with gzip if allowed by the Accept header.
Definition: OutputHandler.php:134
MediaWiki\OutputHandler\handle
static handle( $s, $phase)
Standard output handler for use with ob_start.
Definition: OutputHandler.php:41
MediaWiki\OutputHandler\emitContentLength
static emitContentLength( $length)
Set the Content-Length header if possible.
Definition: OutputHandler.php:212
MediaWiki\Logger\LoggerFactory
PSR-3 logger instance factory.
Definition: LoggerFactory.php:45
MediaWiki
A helper class for throttling authentication attempts.
wfDebug
wfDebug( $text, $dest='all', array $context=[])
Sends a line to the debug log if enabled or, optionally, to a comment in output.
Definition: GlobalFunctions.php:894
$s
foreach( $mmfl['setupFiles'] as $fileName) if( $queue) if(empty( $mmfl['quiet'])) $s
Definition: mergeMessageFileList.php:206
wfClientAcceptsGzip
wfClientAcceptsGzip( $force=false)
Whether the client accept gzip encoding.
Definition: GlobalFunctions.php:1422
$header
$header
Definition: updateCredits.php:37
$wgDisableOutputCompression
$wgDisableOutputCompression
Disable output compression (enabled by default if zlib is available)
Definition: DefaultSettings.php:3847
$path
$path
Definition: NoLocalSettings.php:25
$ext
if(!is_readable( $file)) $ext
Definition: router.php:48
MediaWiki\OutputHandler\mangleFlashPolicy
static mangleFlashPolicy( $s)
Mangle flash policy tags which open up the site to XSS attacks.
Definition: OutputHandler.php:188
MW_ENTRY_POINT
const MW_ENTRY_POINT
Definition: api.php:41