MediaWiki REL1_37
OutputHandler.php
Go to the documentation of this file.
1<?php
23namespace 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
56 $s = self::mangleFlashPolicy( $s );
57 }
58
59 // Sanity 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}
$wgDisableOutputCompression
Disable output compression (enabled by default if zlib is available)
$wgMangleFlashPolicy
When OutputHandler is used, mangle any output that contains <cross-domain-policy>.
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.
static handleGzip( $s)
Handler that compresses data with gzip if allowed by the Accept header.
static emitContentLength( $length)
Set the Content-Length header if possible.
static mangleFlashPolicy( $s)
Mangle flash policy tags which open up the site to XSS attacks.
static findUriExtension()
Get the "file extension" that some client apps will estimate from the currently-requested URL.
foreach( $mmfl['setupFiles'] as $fileName) if($queue) if(empty( $mmfl['quiet'])) $s
A helper class for throttling authentication attempts.
if(!is_readable( $file)) $ext
Definition router.php:48
$header