Merge "Convert OutputHandler functions to a class"
authorjenkins-bot <jenkins-bot@gerrit.wikimedia.org>
Wed, 21 Mar 2018 20:56:22 +0000 (20:56 +0000)
committerGerrit Code Review <gerrit@wikimedia.org>
Wed, 21 Mar 2018 20:56:22 +0000 (20:56 +0000)
RELEASE-NOTES-1.31
autoload.php
includes/GlobalFunctions.php
includes/OutputHandler.php
includes/WebStart.php
includes/api/ApiFormatBase.php
includes/api/ApiFormatJson.php
includes/api/ApiFormatPhp.php
tests/phpunit/includes/api/format/ApiFormatPhpTest.php

index 899f8b3..48b0500 100644 (file)
@@ -287,7 +287,7 @@ changes to languages because of Phabricator reports.
   * Parser::isValidHalfParsedText()
   * StripState::getSubState()
   * StripState::merge()
-* The "free" class is now only applied to unbracketed URLs in wikitext. Links
+* The "free" CSS class is now only applied to unbracketed URLs in wikitext. Links
   written using square brackets will get the class "text" not "free".
 * OpenSearch::getOpenSearchTemplate(), deprecated in 1.25, has been removed. You
   can use ApiOpenSearch::getOpenSearchTemplate() instead.
@@ -302,6 +302,9 @@ changes to languages because of Phabricator reports.
   transaction also results in an exception. Previously these were logged as errors.
   The startAtomic() and endAtomic() methods, or AtomicSectionUpdate should be used
   instead.
+* The global function wfOutputHandler() was removed, use the its replacement
+  MediaWiki\OutputHandler::handle() instead. The global function was only sometimes defined.
+  Its replacement is always available via the autoloader.
 
 == Compatibility ==
 MediaWiki 1.31 requires PHP 5.5.9 or later. Although HHVM 3.18.5 or later is supported,
index 6386a5e..eb3d776 100644 (file)
@@ -911,6 +911,7 @@ $wgAutoloadLocalClasses = [
        'MediaWiki\\Logger\\NullSpi' => __DIR__ . '/includes/debug/logger/NullSpi.php',
        'MediaWiki\\Logger\\Spi' => __DIR__ . '/includes/debug/logger/Spi.php',
        'MediaWiki\\MediaWikiServices' => __DIR__ . '/includes/MediaWikiServices.php',
+       'MediaWiki\\OutputHandler' => __DIR__ . '/includes/OutputHandler.php',
        'MediaWiki\\Preferences\\DefaultPreferencesFactory' => __DIR__ . '/includes/preferences/DefaultPreferencesFactory.php',
        'MediaWiki\\Preferences\\PreferencesFactory' => __DIR__ . '/includes/preferences/PreferencesFactory.php',
        'MediaWiki\\ProcOpenError' => __DIR__ . '/includes/exception/ProcOpenError.php',
index 7a5a5d8..42a6573 100644 (file)
@@ -1845,7 +1845,7 @@ function wfHttpError( $code, $label, $desc ) {
 function wfResetOutputBuffers( $resetGzipEncoding = true ) {
        if ( $resetGzipEncoding ) {
                // Suppress Content-Encoding and Content-Length
-               // headers from 1.10+s wfOutputHandler
+               // headers from OutputHandler::handle.
                global $wgDisableOutputCompression;
                $wgDisableOutputCompression = true;
        }
index 2dc3732..842922d 100644 (file)
  * @file
  */
 
+namespace MediaWiki;
+
+use MWTidy;
+use Html;
+
 /**
- * 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, $wgValidateAllHtml, $wgMangleFlashPolicy;
+               if ( $wgMangleFlashPolicy ) {
+                       $s = self::mangleFlashPolicy( $s );
+               }
+               if ( $wgValidateAllHtml ) {
+                       $headers = headers_list();
+                       $isHTML = false;
+                       foreach ( $headers as $header ) {
+                               $parts = explode( ':', $header, 2 );
+                               if ( count( $parts ) !== 2 ) {
+                                       continue;
+                               }
+                               $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;
+                               }
                        }
-                       $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 ( $isHTML ) {
+                               $s = self::validateAllHtml( $s );
                        }
                }
-               if ( $isHTML ) {
-                       $s = wfHtmlValidationHandler( $s );
+               if ( !$wgDisableOutputCompression && !ini_get( 'zlib.output_compression' ) ) {
+                       if ( !defined( 'MW_NO_OUTPUT_COMPRESSION' ) ) {
+                               $s = self::handleGzip( $s );
+                       }
+                       if ( !ini_get( 'output_handler' ) ) {
+                               self::emitContentLength( strlen( $s ) );
+                       }
                }
+               return $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 ) );
+
+       /**
+        * 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 '';
                }
-       }
-       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? :(
+               $period = strrpos( $path, '.' );
+               if ( $period !== false ) {
+                       return strtolower( substr( $path, $period ) );
+               }
                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;
-       }
+       /**
+        * 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 ( headers_sent() ) {
+                       wfDebug( __METHOD__ . "() 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;
-       }
+               $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;
+               }
 
-       if ( wfClientAcceptsGzip() ) {
-               wfDebug( __FUNCTION__ . "() is compressing output\n" );
-               header( 'Content-Encoding: gzip' );
-               $s = gzencode( $s, 6 );
-       }
+               if ( wfClientAcceptsGzip() ) {
+                       wfDebug( __METHOD__ . "() is compressing output\n" );
+                       header( 'Content-Encoding: gzip' );
+                       $s = gzencode( $s, 6 );
+               }
 
-       // 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;
+               // 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' );
+               if ( !$foundVary ) {
+                       header( 'Vary: Accept-Encoding' );
+                       global $wgUseKeyHeader;
+                       if ( $wgUseKeyHeader ) {
+                               header( 'Key: Accept-Encoding;match=gzip' );
+                       }
                }
-       }
-       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;
        }
-}
 
-/**
- * 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" );
+       /**
+        * 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;
+               }
        }
-}
 
-/**
- * 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 ) ) {
-               return $s;
+       /**
+        * 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" );
+               }
        }
 
-       header( 'Cache-Control: no-cache' );
+       /**
+        * Replace the output with an error if the HTML is not valid.
+        *
+        * @param string $s
+        * @return string
+        */
+       private static function validateAllHtml( $s ) {
+               $errors = '';
+               if ( MWTidy::checkErrors( $s, $errors ) ) {
+                       return $s;
+               }
+
+               header( 'Cache-Control: no-cache' );
 
-       $out = Html::element( 'h1', null, 'HTML validation error' );
-       $out .= Html::openElement( 'ul' );
+               $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";
+               $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";
+                       }
+                       $error = strtok( "\n" );
                }
-               $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";
-               }
-               $out .= Html::element( 'li', $attrs, $line ) . "\n";
-               $line = strtok( "\n" );
-               $i++;
-       }
-       $out .= Html::closeElement( 'ol' );
+               $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";
+                       }
+                       $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 }
+               $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' );
+               $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;
+               return $out;
+       }
 }
index be95779..c9aecce 100644 (file)
@@ -78,14 +78,10 @@ if ( !defined( 'MW_CONFIG_CALLBACK' ) ) {
 // Custom setup for WebStart entry point
 if ( !defined( 'MW_SETUP_CALLBACK' ) ) {
        function wfWebStartSetup() {
-               # Initialise output buffering
-               # Check that there is no previous output or previously set up buffers, because
-               # that would cause us to potentially mix gzip and non-gzip output, creating a
-               # big mess.
-               global $IP;
+               // Initialise output buffering
+               // Check for previously set up buffers, to avoid a mix of gzip and non-gzip output.
                if ( ob_get_level() == 0 ) {
-                       require_once "$IP/includes/OutputHandler.php";
-                       ob_start( 'wfOutputHandler' );
+                       ob_start( 'MediaWiki\\OutputHandler::handle' );
                }
        }
        define( 'MW_SETUP_CALLBACK', 'wfWebStartSetup' );
index 4b93b31..234fcfd 100644 (file)
@@ -315,7 +315,7 @@ abstract class ApiFormatBase extends ApiBase {
                                        false, FormatJson::ALL_OK
                                );
 
-                               // T68776: wfMangleFlashPolicy() is needed to avoid a nasty bug in
+                               // T68776: OutputHandler::mangleFlashPolicy() avoids a nasty bug in
                                // Flash, but what it does isn't friendly for the API, so we need to
                                // work around it.
                                if ( preg_match( '/\<\s*cross-domain-policy\s*\>/i', $json ) ) {
index 6d8e743..1aa9e15 100644 (file)
@@ -87,7 +87,7 @@ class ApiFormatJson extends ApiFormatBase {
                $data = $this->getResult()->getResultData( null, $transform );
                $json = FormatJson::encode( $data, $this->getIsHtml(), $opt );
 
-               // T68776: wfMangleFlashPolicy() is needed to avoid a nasty bug in
+               // T68776: OutputHandler::mangleFlashPolicy() avoids a nasty bug in
                // Flash, but what it does isn't friendly for the API, so we need to
                // work around it.
                if ( preg_match( '/\<\s*cross-domain-policy(?=\s|\>)/i', $json ) ) {
index b05097a..cc0f159 100644 (file)
@@ -56,12 +56,12 @@ class ApiFormatPhp extends ApiFormatBase {
                }
                $text = serialize( $this->getResult()->getResultData( null, $transforms ) );
 
-               // T68776: wfMangleFlashPolicy() is needed to avoid a nasty bug in
+               // T68776: OutputHandler::mangleFlashPolicy() avoids a nasty bug in
                // Flash, but what it does isn't friendly for the API. There's nothing
                // we can do here that isn't actively broken in some manner, so let's
                // just be broken in a useful manner.
                if ( $this->getConfig()->get( 'MangleFlashPolicy' ) &&
-                       in_array( 'wfOutputHandler', ob_list_handlers(), true ) &&
+                       in_array( 'MediaWiki\\OutputHandler::handle', ob_list_handlers(), true ) &&
                        preg_match( '/\<\s*cross-domain-policy(?=\s|\>)/i', $text )
                ) {
                        $this->dieWithError( 'apierror-formatphp', 'internalerror' );
index 2d2e29b..66e620e 100644 (file)
@@ -110,14 +110,8 @@ class ApiFormatPhpTest extends ApiFormatTestBase {
                $main = new ApiMain( $context );
                $main->getResult()->addValue( null, null, '< Cross-Domain-Policy >' );
 
-               if ( !function_exists( 'wfOutputHandler' ) ) {
-                       function wfOutputHandler( $s ) {
-                               return $s;
-                       }
-               }
-
                $printer = $main->createPrinterByName( 'php' );
-               ob_start( 'wfOutputHandler' );
+               ob_start( 'MediaWiki\\OutputHandler::handle' );
                $printer->initPrinter();
                $printer->execute();
                $printer->closePrinter();
@@ -126,7 +120,7 @@ class ApiFormatPhpTest extends ApiFormatTestBase {
 
                $config->set( 'MangleFlashPolicy', true );
                $printer = $main->createPrinterByName( 'php' );
-               ob_start( 'wfOutputHandler' );
+               ob_start( 'MediaWiki\\OutputHandler::handle' );
                try {
                        $printer->initPrinter();
                        $printer->execute();