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 ) {
42  $config = MediaWikiServices::getInstance()->getMainConfig();
43  $disableOutputCompression = $config->get( MainConfigNames::DisableOutputCompression );
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  // Check if a compression output buffer is already enabled via php.ini. Such
56  // buffers exists at the start of the request and are reflected by ob_get_level().
57  $phpHandlesCompression = (
58  ini_get( 'output_handler' ) === 'ob_gzhandler' ||
59  ini_get( 'zlib.output_handler' ) === 'ob_gzhandler' ||
60  !in_array(
61  strtolower( ini_get( 'zlib.output_compression' ) ),
62  [ '', 'off', '0' ]
63  )
64  );
65 
66  if (
67  // Compression is not already handled by an internal PHP buffer
68  !$phpHandlesCompression &&
69  // Compression is not disabled by the application entry point
70  !defined( 'MW_NO_OUTPUT_COMPRESSION' ) &&
71  // Compression is not disabled by site configuration
72  !$disableOutputCompression
73  ) {
74  $s = self::handleGzip( $s );
75  }
76 
77  if (
78  // Response body length does not depend on internal PHP compression buffer
79  !$phpHandlesCompression &&
80  // Response body length does not depend on mangling by a custom buffer
81  !ini_get( 'output_handler' ) &&
82  !ini_get( 'zlib.output_handler' )
83  ) {
84  self::emitContentLength( strlen( $s ) );
85  }
86 
87  return $s;
88  }
89 
100  private static function findUriExtension() {
101  // @todo FIXME: this sort of dupes some code in WebRequest::getRequestUrl()
102  if ( isset( $_SERVER['REQUEST_URI'] ) ) {
103  // Strip the query string...
104  $path = explode( '?', $_SERVER['REQUEST_URI'], 2 )[0];
105  } elseif ( isset( $_SERVER['SCRIPT_NAME'] ) ) {
106  // Probably IIS. QUERY_STRING appears separately.
107  $path = $_SERVER['SCRIPT_NAME'];
108  } else {
109  // Can't get the path from the server? :(
110  return '';
111  }
112 
113  $period = strrpos( $path, '.' );
114  if ( $period !== false ) {
115  return strtolower( substr( $path, $period ) );
116  }
117  return '';
118  }
119 
130  private static function handleGzip( $s ) {
131  if ( !function_exists( 'gzencode' ) ) {
132  wfDebug( __METHOD__ . "() skipping compression (gzencode unavailable)" );
133  return $s;
134  }
135  if ( headers_sent() ) {
136  wfDebug( __METHOD__ . "() skipping compression (headers already sent)" );
137  return $s;
138  }
139 
140  $ext = self::findUriExtension();
141  if ( $ext == '.gz' || $ext == '.tgz' ) {
142  // Don't do gzip compression if the URL path ends in .gz or .tgz
143  // This confuses Safari and triggers a download of the page,
144  // even though it's pretty clearly labeled as viewable HTML.
145  // Bad Safari! Bad!
146  return $s;
147  }
148 
149  if ( $s === '' ) {
150  // Do not gzip empty HTTP responses since that would not only bloat the body
151  // length, but it would result in invalid HTTP responses when the HTTP status code
152  // is one that must not be accompanied by a body (e.g. "204 No Content").
153  return $s;
154  }
155 
156  if ( wfClientAcceptsGzip() ) {
157  wfDebug( __METHOD__ . "() is compressing output" );
158  header( 'Content-Encoding: gzip' );
159  $s = gzencode( $s, 6 );
160  }
161 
162  // Set vary header if it hasn't been set already
163  $headers = headers_list();
164  $foundVary = false;
165  foreach ( $headers as $header ) {
166  $headerName = strtolower( substr( $header, 0, 5 ) );
167  if ( $headerName == 'vary:' ) {
168  $foundVary = true;
169  break;
170  }
171  }
172  if ( !$foundVary ) {
173  header( 'Vary: Accept-Encoding' );
174  }
175  return $s;
176  }
177 
193  private static function emitContentLength( $length ) {
194  if ( headers_sent() ) {
195  wfDebug( __METHOD__ . "() headers already sent" );
196  return;
197  }
198 
199  if (
200  in_array( http_response_code(), [ 200, 404 ], true ) ||
201  ( $_SERVER['SERVER_PROTOCOL'] ?? null ) === 'HTTP/1.0'
202  ) {
203  header( "Content-Length: $length" );
204  }
205  }
206 }
wfDebug( $text, $dest='all', array $context=[])
Sends a line to the debug log if enabled or, optionally, to a comment in output.
wfClientAcceptsGzip( $force=false)
Whether the client accept gzip encoding.
const MW_ENTRY_POINT
Definition: api.php:41
PSR-3 logger instance factory.
static handle( $s, $phase)
Standard output handler for use with ob_start.
The MediaWiki class is the helper class for the index.php entry point.
Definition: MediaWiki.php:39
foreach( $mmfl['setupFiles'] as $fileName) if( $queue) if(empty( $mmfl['quiet'])) $s
if(!is_readable( $file)) $ext
Definition: router.php:48
$header