SECURITY: XSS in unclosed internal links
[lhc/web/wiklou.git] / includes / parser / Parser.php
index bd2f131..38eb621 100644 (file)
@@ -251,7 +251,7 @@ class Parser {
        protected $mProfiler;
 
        /**
-        * @var LinkRenderer
+        * @var \MediaWiki\Linker\LinkRenderer
         */
        protected $mLinkRenderer;
 
@@ -501,60 +501,46 @@ class Parser {
                                [ $this->mHighestExpansionDepth, $this->mOptions->getMaxPPExpandDepth() ]
                        );
                        $this->mOutput->setLimitReportData( 'limitreport-expensivefunctioncount',
-                               [ $this->mExpensiveFunctionCount, $this->mOptions->getExpensiveParserFunctionLimit() ]
+                               [ $this->mExpensiveFunctionCount,
+                                       $this->mOptions->getExpensiveParserFunctionLimit() ]
                        );
                        Hooks::run( 'ParserLimitReportPrepare', [ $this, $this->mOutput ] );
 
-                       $limitReport = "NewPP limit report\n";
-                       if ( $wgShowHostnames ) {
-                               $limitReport .= 'Parsed by ' . wfHostname() . "\n";
-                       }
-                       $limitReport .= 'Cached time: ' . $this->mOutput->getCacheTime() . "\n";
-                       $limitReport .= 'Cache expiry: ' . $this->mOutput->getCacheExpiry() . "\n";
-                       $limitReport .= 'Dynamic content: ' .
-                               ( $this->mOutput->hasDynamicContent() ? 'true' : 'false' ) .
-                               "\n";
-
-                       foreach ( $this->mOutput->getLimitReportData() as $key => $value ) {
-                               if ( Hooks::run( 'ParserLimitReportFormat',
-                                       [ $key, &$value, &$limitReport, false, false ]
-                               ) ) {
-                                       $keyMsg = wfMessage( $key )->inLanguage( 'en' )->useDatabase( false );
-                                       $valueMsg = wfMessage( [ "$key-value-text", "$key-value" ] )
-                                               ->inLanguage( 'en' )->useDatabase( false );
-                                       if ( !$valueMsg->exists() ) {
-                                               $valueMsg = new RawMessage( '$1' );
-                                       }
-                                       if ( !$keyMsg->isDisabled() && !$valueMsg->isDisabled() ) {
-                                               $valueMsg->params( $value );
-                                               $limitReport .= "{$keyMsg->text()}: {$valueMsg->text()}\n";
-                                       }
-                               }
-                       }
-                       // Since we're not really outputting HTML, decode the entities and
-                       // then re-encode the things that need hiding inside HTML comments.
-                       $limitReport = htmlspecialchars_decode( $limitReport );
+                       $limitReport = '';
                        Hooks::run( 'ParserLimitReport', [ $this, &$limitReport ] );
+                       if ( $limitReport != '' ) {
+                               // Sanitize for comment. Note '‐' in the replacement is U+2010,
+                               // which looks much like the problematic '-'.
+                               $limitReport = str_replace( [ '-', '&' ], [ '‐', '&' ], $limitReport );
+                               $text .= "\n<!-- \nNewPP limit report\n$limitReport-->\n";
+                       }
 
-                       // Sanitize for comment. Note '‐' in the replacement is U+2010,
-                       // which looks much like the problematic '-'.
-                       $limitReport = str_replace( [ '-', '&' ], [ '‐', '&amp;' ], $limitReport );
-                       $text .= "\n<!-- \n$limitReport-->\n";
-
-                       // Add on template profiling data
+                       // Add on template profiling data in human/machine readable way
                        $dataByFunc = $this->mProfiler->getFunctionStats();
                        uasort( $dataByFunc, function ( $a, $b ) {
                                return $a['real'] < $b['real']; // descending order
                        } );
-                       $profileReport = "Transclusion expansion time report (%,ms,calls,template)\n";
+                       $profileReport = [];
                        foreach ( array_slice( $dataByFunc, 0, 10 ) as $item ) {
-                               $profileReport .= sprintf( "%6.2f%% %8.3f %6d - %s\n",
-                                       $item['%real'], $item['real'], $item['calls'],
-                                       htmlspecialchars( $item['name'] ) );
+                               $profileReport[] = sprintf( "%6.2f%% %8.3f %6d %s",
+                                       $item['%real'], $item['real'], $item['calls'], $item['name'] );
                        }
-                       $text .= "\n<!-- \n$profileReport-->\n";
+                       $this->mOutput->setLimitReportData( 'limitreport-timingprofile', $profileReport );
 
-                       if ( $this->mGeneratedPPNodeCount > $this->mOptions->getMaxGeneratedPPNodeCount() / 10 ) {
+                       // Add other cache related metadata
+                       if ( $wgShowHostnames ) {
+                               $this->mOutput->setLimitReportData( 'cachereport-origin', wfHostname() );
+                       }
+                       $this->mOutput->setLimitReportData( 'cachereport-timestamp',
+                               $this->mOutput->getCacheTime() );
+                       $this->mOutput->setLimitReportData( 'cachereport-ttl',
+                               $this->mOutput->getCacheExpiry() );
+                       $this->mOutput->setLimitReportData( 'cachereport-transientcontent',
+                               $this->mOutput->hasDynamicContent() );
+
+                       if ( $this->mGeneratedPPNodeCount
+                               > $this->mOptions->getMaxGeneratedPPNodeCount() / 10
+                       ) {
                                wfDebugLog( 'generated-pp-node-count', $this->mGeneratedPPNodeCount . ' ' .
                                        $this->mTitle->getPrefixedDBkey() );
                        }
@@ -896,10 +882,10 @@ class Parser {
        }
 
        /**
-        * Get a LinkRenderer instance to make links with
+        * Get a \MediaWiki\Linker\LinkRenderer instance to make links with
         *
         * @since 1.28
-        * @return LinkRenderer
+        * @return \MediaWiki\Linker\LinkRenderer
         */
        public function getLinkRenderer() {
                if ( !$this->mLinkRenderer ) {
@@ -1264,7 +1250,9 @@ class Parser {
                        $text,
                        [ &$this, 'attributeStripCallback' ],
                        false,
-                       array_keys( $this->mTransparentTagHooks )
+                       array_keys( $this->mTransparentTagHooks ),
+                       [],
+                       [ &$this, 'addTrackingCategory' ]
                );
                Hooks::run( 'InternalParseBeforeLinks', [ &$this, &$text, &$this->mStripState ] );
 
@@ -1353,9 +1341,10 @@ class Parser {
 
                $text = Sanitizer::normalizeCharReferences( $text );
 
-               if ( MWTidy::isEnabled() && $this->mOptions->getTidy() ) {
-                       $text = MWTidy::tidy( $text );
-                       $this->mOutput->addModuleStyles( MWTidy::getModuleStyles() );
+               if ( MWTidy::isEnabled() ) {
+                       if ( $this->mOptions->getTidy() ) {
+                               $text = MWTidy::tidy( $text );
+                       }
                } else {
                        # attempt to sanitize at least some nesting problems
                        # (bug #2702 and quite a few others)
@@ -2169,7 +2158,7 @@ class Parser {
                                $might_be_img = true;
                                $text = $m[2];
                                if ( strpos( $m[1], '%' ) !== false ) {
-                                       $m[1] = rawurldecode( $m[1] );
+                                       $m[1] = str_replace( [ '<', '>' ], [ '&lt;', '&gt;' ], rawurldecode( $m[1] ) );
                                }
                                $trail = "";
                        } else { # Invalid form; output directly
@@ -4375,11 +4364,7 @@ class Parser {
                $this->startParse( $title, $options, self::OT_WIKI, $clearState );
                $this->setUser( $user );
 
-               $pairs = [
-                       "\r\n" => "\n",
-                       "\r" => "\n",
-               ];
-               $text = str_replace( array_keys( $pairs ), array_values( $pairs ), $text );
+               $text = str_replace( [ "\r\n", "\r" ], "\n", $text );
                if ( $options->getPreSaveTransform() ) {
                        $text = $this->pstPass2( $text, $user );
                }