Merge "Use {{int:}} on MediaWiki:Blockedtext and MediaWiki:Autoblockedtext"
[lhc/web/wiklou.git] / includes / OutputHandler.php
index 2dc3732..16c3784 100644 (file)
  * @file
  */
 
+namespace MediaWiki;
+
 /**
- * Standard output handler for use with ob_start
- *
- * @param string $s
- *
- * @return string
+ * @since 1.31
  */
-function wfOutputHandler( $s ) {
-       global $wgDisableOutputCompression, $wgValidateAllHtml, $wgMangleFlashPolicy;
-       if ( $wgMangleFlashPolicy ) {
-               $s = wfMangleFlashPolicy( $s );
-       }
-       if ( $wgValidateAllHtml ) {
-               $headers = headers_list();
-               $isHTML = false;
-               foreach ( $headers as $header ) {
-                       $parts = explode( ':', $header, 2 );
-                       if ( count( $parts ) !== 2 ) {
-                               continue;
+class OutputHandler {
+       /**
+        * Standard output handler for use with ob_start.
+        *
+        * @param string $s Web response output
+        * @return string
+        */
+       public static function handle( $s ) {
+               global $wgDisableOutputCompression, $wgMangleFlashPolicy;
+               if ( $wgMangleFlashPolicy ) {
+                       $s = self::mangleFlashPolicy( $s );
+               }
+               if ( !$wgDisableOutputCompression && !ini_get( 'zlib.output_compression' ) ) {
+                       if ( !defined( 'MW_NO_OUTPUT_COMPRESSION' ) ) {
+                               $s = self::handleGzip( $s );
                        }
-                       $name = strtolower( trim( $parts[0] ) );
-                       $value = trim( $parts[1] );
-                       if ( $name == 'content-type' && ( strpos( $value, 'text/html' ) === 0
-                               || strpos( $value, 'application/xhtml+xml' ) === 0 )
-                       ) {
-                               $isHTML = true;
-                               break;
+                       if ( !ini_get( 'output_handler' ) ) {
+                               self::emitContentLength( strlen( $s ) );
                        }
                }
-               if ( $isHTML ) {
-                       $s = wfHtmlValidationHandler( $s );
-               }
-       }
-       if ( !$wgDisableOutputCompression && !ini_get( 'zlib.output_compression' ) ) {
-               if ( !defined( 'MW_NO_OUTPUT_COMPRESSION' ) ) {
-                       $s = wfGzipHandler( $s );
-               }
-               if ( !ini_get( 'output_handler' ) ) {
-                       wfDoContentLength( strlen( $s ) );
-               }
-       }
-       return $s;
-}
-
-/**
- * Get the "file extension" that some client apps will estimate from
- * the currently-requested URL.
- * This isn't on WebRequest because we need it when things aren't initialized
- * @private
- *
- * @return string
- */
-function wfRequestExtension() {
-       /// @todo FIXME: this sort of dupes some code in WebRequest::getRequestUrl()
-       if ( isset( $_SERVER['REQUEST_URI'] ) ) {
-               // Strip the query string...
-               list( $path ) = explode( '?', $_SERVER['REQUEST_URI'], 2 );
-       } elseif ( isset( $_SERVER['SCRIPT_NAME'] ) ) {
-               // Probably IIS. QUERY_STRING appears separately.
-               $path = $_SERVER['SCRIPT_NAME'];
-       } else {
-               // Can't get the path from the server? :(
-               return '';
-       }
-
-       $period = strrpos( $path, '.' );
-       if ( $period !== false ) {
-               return strtolower( substr( $path, $period ) );
-       }
-       return '';
-}
-
-/**
- * Handler that compresses data with gzip if allowed by the Accept header.
- * Unlike ob_gzhandler, it works for HEAD requests too.
- *
- * @param string $s
- *
- * @return string
- */
-function wfGzipHandler( $s ) {
-       if ( !function_exists( 'gzencode' ) ) {
-               wfDebug( __FUNCTION__ . "() skipping compression (gzencode unavailable)\n" );
-               return $s;
-       }
-       if ( headers_sent() ) {
-               wfDebug( __FUNCTION__ . "() skipping compression (headers already sent)\n" );
                return $s;
        }
 
-       $ext = wfRequestExtension();
-       if ( $ext == '.gz' || $ext == '.tgz' ) {
-               // Don't do gzip compression if the URL path ends in .gz or .tgz
-               // This confuses Safari and triggers a download of the page,
-               // even though it's pretty clearly labeled as viewable HTML.
-               // Bad Safari! Bad!
-               return $s;
-       }
+       /**
+        * Get the "file extension" that some client apps will estimate from
+        * the currently-requested URL.
+        *
+        * This isn't a WebRequest method, because we need it before the class loads.
+        * @todo As of 2018, this actually runs after autoloader in Setup.php, so
+        * WebRequest seems like a good place for this.
+        *
+        * @return string
+        */
+       private static function findUriExtension() {
+               /// @todo FIXME: this sort of dupes some code in WebRequest::getRequestUrl()
+               if ( isset( $_SERVER['REQUEST_URI'] ) ) {
+                       // Strip the query string...
+                       list( $path ) = explode( '?', $_SERVER['REQUEST_URI'], 2 );
+               } elseif ( isset( $_SERVER['SCRIPT_NAME'] ) ) {
+                       // Probably IIS. QUERY_STRING appears separately.
+                       $path = $_SERVER['SCRIPT_NAME'];
+               } else {
+                       // Can't get the path from the server? :(
+                       return '';
+               }
 
-       if ( wfClientAcceptsGzip() ) {
-               wfDebug( __FUNCTION__ . "() is compressing output\n" );
-               header( 'Content-Encoding: gzip' );
-               $s = gzencode( $s, 6 );
+               $period = strrpos( $path, '.' );
+               if ( $period !== false ) {
+                       return strtolower( substr( $path, $period ) );
+               }
+               return '';
        }
 
-       // Set vary header if it hasn't been set already
-       $headers = headers_list();
-       $foundVary = false;
-       foreach ( $headers as $header ) {
-               $headerName = strtolower( substr( $header, 0, 5 ) );
-               if ( $headerName == 'vary:' ) {
-                       $foundVary = true;
-                       break;
+       /**
+        * Handler that compresses data with gzip if allowed by the Accept header.
+        *
+        * Unlike ob_gzhandler, it works for HEAD requests too.
+        *
+        * @param string $s Web response output
+        * @return string
+        */
+       private static function handleGzip( $s ) {
+               if ( !function_exists( 'gzencode' ) ) {
+                       wfDebug( __METHOD__ . "() skipping compression (gzencode unavailable)\n" );
+                       return $s;
                }
-       }
-       if ( !$foundVary ) {
-               header( 'Vary: Accept-Encoding' );
-               global $wgUseKeyHeader;
-               if ( $wgUseKeyHeader ) {
-                       header( 'Key: Accept-Encoding;match=gzip' );
+               if ( headers_sent() ) {
+                       wfDebug( __METHOD__ . "() skipping compression (headers already sent)\n" );
+                       return $s;
                }
-       }
-       return $s;
-}
 
-/**
- * Mangle flash policy tags which open up the site to XSS attacks.
- *
- * @param string $s
- *
- * @return string
- */
-function wfMangleFlashPolicy( $s ) {
-       # Avoid weird excessive memory usage in PCRE on big articles
-       if ( preg_match( '/\<\s*cross-domain-policy(?=\s|\>)/i', $s ) ) {
-               return preg_replace( '/\<(\s*)(cross-domain-policy(?=\s|\>))/i', '<$1NOT-$2', $s );
-       } else {
-               return $s;
-       }
-}
+               $ext = self::findUriExtension();
+               if ( $ext == '.gz' || $ext == '.tgz' ) {
+                       // Don't do gzip compression if the URL path ends in .gz or .tgz
+                       // This confuses Safari and triggers a download of the page,
+                       // even though it's pretty clearly labeled as viewable HTML.
+                       // Bad Safari! Bad!
+                       return $s;
+               }
 
-/**
- * Add a Content-Length header if possible. This makes it cooperate with CDN better.
- *
- * @param int $length
- */
-function wfDoContentLength( $length ) {
-       if ( !headers_sent()
-               && isset( $_SERVER['SERVER_PROTOCOL'] )
-               && $_SERVER['SERVER_PROTOCOL'] == 'HTTP/1.0'
-       ) {
-               header( "Content-Length: $length" );
-       }
-}
+               if ( wfClientAcceptsGzip() ) {
+                       wfDebug( __METHOD__ . "() is compressing output\n" );
+                       header( 'Content-Encoding: gzip' );
+                       $s = gzencode( $s, 6 );
+               }
 
-/**
- * Replace the output with an error if the HTML is not valid
- *
- * @param string $s
- *
- * @return string
- */
-function wfHtmlValidationHandler( $s ) {
-       $errors = '';
-       if ( MWTidy::checkErrors( $s, $errors ) ) {
+               // Set vary header if it hasn't been set already
+               $headers = headers_list();
+               $foundVary = false;
+               foreach ( $headers as $header ) {
+                       $headerName = strtolower( substr( $header, 0, 5 ) );
+                       if ( $headerName == 'vary:' ) {
+                               $foundVary = true;
+                               break;
+                       }
+               }
+               if ( !$foundVary ) {
+                       header( 'Vary: Accept-Encoding' );
+                       global $wgUseKeyHeader;
+                       if ( $wgUseKeyHeader ) {
+                               header( 'Key: Accept-Encoding;match=gzip' );
+                       }
+               }
                return $s;
        }
 
-       header( 'Cache-Control: no-cache' );
-
-       $out = Html::element( 'h1', null, 'HTML validation error' );
-       $out .= Html::openElement( 'ul' );
-
-       $error = strtok( $errors, "\n" );
-       $badLines = [];
-       while ( $error !== false ) {
-               if ( preg_match( '/^line (\d+)/', $error, $m ) ) {
-                       $lineNum = intval( $m[1] );
-                       $badLines[$lineNum] = true;
-                       $out .= Html::rawElement( 'li', null,
-                               Html::element( 'a', [ 'href' => "#line-{$lineNum}" ], $error ) ) . "\n";
+       /**
+        * Mangle flash policy tags which open up the site to XSS attacks.
+        *
+        * @param string $s Web response output
+        * @return string
+        */
+       private static function mangleFlashPolicy( $s ) {
+               # Avoid weird excessive memory usage in PCRE on big articles
+               if ( preg_match( '/\<\s*cross-domain-policy(?=\s|\>)/i', $s ) ) {
+                       return preg_replace( '/\<(\s*)(cross-domain-policy(?=\s|\>))/i', '<$1NOT-$2', $s );
+               } else {
+                       return $s;
                }
-               $error = strtok( "\n" );
        }
 
-       $out .= Html::closeElement( 'ul' );
-       $out .= Html::element( 'pre', null, $errors );
-       $out .= Html::openElement( 'ol' ) . "\n";
-       $line = strtok( $s, "\n" );
-       $i = 1;
-       while ( $line !== false ) {
-               $attrs = [];
-               if ( isset( $badLines[$i] ) ) {
-                       $attrs['class'] = 'highlight';
-                       $attrs['id'] = "line-$i";
+       /**
+        * Add a Content-Length header if possible. This makes it cooperate with CDN better.
+        *
+        * @param int $length
+        */
+       private static function emitContentLength( $length ) {
+               if ( !headers_sent()
+                       && isset( $_SERVER['SERVER_PROTOCOL'] )
+                       && $_SERVER['SERVER_PROTOCOL'] == 'HTTP/1.0'
+               ) {
+                       header( "Content-Length: $length" );
                }
-               $out .= Html::element( 'li', $attrs, $line ) . "\n";
-               $line = strtok( "\n" );
-               $i++;
        }
-       $out .= Html::closeElement( 'ol' );
-
-       $style = <<<CSS
-.highlight { background-color: #ffc }
-li { white-space: pre }
-CSS;
-
-       $out = Html::htmlHeader( [ 'lang' => 'en', 'dir' => 'ltr' ] ) .
-               Html::rawElement( 'head', null,
-                       Html::element( 'title', null, 'HTML validation error' ) .
-                       Html::inlineStyle( $style ) ) .
-               Html::rawElement( 'body', null, $out ) .
-               Html::closeElement( 'html' );
-
-       return $out;
 }