MediaWiki REL1_39
OutputHandler.php
Go to the documentation of this file.
1<?php
23namespace 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.
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