MediaWiki master
OutputHandler.php
Go to the documentation of this file.
1<?php
9namespace MediaWiki\Output;
10
14
29 public static function handle( $s, $phase ) {
30 $config = MediaWikiServices::getInstance()->getMainConfig();
31 $disableOutputCompression = $config->get( MainConfigNames::DisableOutputCompression );
32 // Don't send headers if output is being discarded (T278579)
33 if ( ( $phase & PHP_OUTPUT_HANDLER_CLEAN ) === PHP_OUTPUT_HANDLER_CLEAN ) {
34 $logger = LoggerFactory::getInstance( 'output' );
35 $logger->debug( __METHOD__ . " entrypoint={entry}; size={size}; phase=$phase", [
36 'entry' => MW_ENTRY_POINT,
37 'size' => strlen( $s ),
38 ] );
39
40 return $s;
41 }
42
43 // Check if a compression output buffer is already enabled via php.ini. Such
44 // buffers exists at the start of the request and are reflected by ob_get_level().
45 $phpHandlesCompression = (
46 ini_get( 'output_handler' ) === 'ob_gzhandler' ||
47 ini_get( 'zlib.output_handler' ) === 'ob_gzhandler' ||
48 !in_array(
49 strtolower( ini_get( 'zlib.output_compression' ) ),
50 [ '', 'off', '0' ]
51 )
52 );
53
54 if (
55 // Compression is not already handled by an internal PHP buffer
56 !$phpHandlesCompression &&
57 // Compression is not disabled by the application entry point
58 !defined( 'MW_NO_OUTPUT_COMPRESSION' ) &&
59 // Compression is not disabled by site configuration
60 !$disableOutputCompression
61 ) {
62 $s = self::handleGzip( $s );
63 }
64
65 if (
66 // Response body length does not depend on internal PHP compression buffer
67 !$phpHandlesCompression &&
68 // Response body length does not depend on mangling by a custom buffer
69 !ini_get( 'output_handler' ) &&
70 !ini_get( 'zlib.output_handler' )
71 ) {
72 self::emitContentLength( strlen( $s ) );
73 }
74
75 return $s;
76 }
77
88 private static function findUriExtension() {
89 // @todo FIXME: this sort of dupes some code in WebRequest::getRequestUrl()
90 if ( isset( $_SERVER['REQUEST_URI'] ) ) {
91 // Strip the query string...
92 $path = explode( '?', $_SERVER['REQUEST_URI'], 2 )[0];
93 } elseif ( isset( $_SERVER['SCRIPT_NAME'] ) ) {
94 // Probably IIS. QUERY_STRING appears separately.
95 $path = $_SERVER['SCRIPT_NAME'];
96 } else {
97 // Can't get the path from the server? :(
98 return '';
99 }
100
101 $period = strrpos( $path, '.' );
102 if ( $period !== false ) {
103 return strtolower( substr( $path, $period ) );
104 }
105 return '';
106 }
107
118 private static function handleGzip( $s ) {
119 if ( !function_exists( 'gzencode' ) ) {
120 wfDebug( __METHOD__ . "() skipping compression (gzencode unavailable)" );
121 return $s;
122 }
123 if ( headers_sent() ) {
124 wfDebug( __METHOD__ . "() skipping compression (headers already sent)" );
125 return $s;
126 }
127
128 $ext = self::findUriExtension();
129 if ( $ext == '.gz' || $ext == '.tgz' ) {
130 // Don't do gzip compression if the URL path ends in .gz or .tgz
131 // This confuses Safari and triggers a download of the page,
132 // even though it's pretty clearly labeled as viewable HTML.
133 // Bad Safari! Bad!
134 return $s;
135 }
136
137 if ( $s === '' ) {
138 // Do not gzip empty HTTP responses since that would not only bloat the body
139 // length, but it would result in invalid HTTP responses when the HTTP status code
140 // is one that must not be accompanied by a body (e.g. "204 No Content").
141 return $s;
142 }
143
144 if ( wfClientAcceptsGzip() ) {
145 wfDebug( __METHOD__ . "() is compressing output" );
146 header( 'Content-Encoding: gzip' );
147 $s = gzencode( $s, 6 );
148 }
149
150 // Set vary header if it hasn't been set already
151 if ( !preg_grep( '/^Vary:/i', headers_list() ) ) {
152 header( 'Vary: Accept-Encoding' );
153 }
154 return $s;
155 }
156
172 private static function emitContentLength( $length ) {
173 if ( headers_sent() ) {
174 wfDebug( __METHOD__ . "() headers already sent" );
175 return;
176 }
177
178 if (
179 in_array( http_response_code(), [ 200, 404 ], true ) ||
180 ( $_SERVER['SERVER_PROTOCOL'] ?? null ) === 'HTTP/1.0'
181 ) {
182 header( "Content-Length: $length" );
183 }
184 }
185}
186
188class_alias( OutputHandler::class, 'MediaWiki\\OutputHandler' );
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:21
Create PSR-3 logger objects.
A class containing constants representing the names of configuration variables.
const DisableOutputCompression
Name constant for the DisableOutputCompression setting, for use with Config::get()
Service locator for MediaWiki core services.
static getInstance()
Returns the global default instance of the top level service locator.
static handle( $s, $phase)
Standard output handler for use with ob_start.