Merge "mediawiki.debug: Move internal footHovzer to the same module"
[lhc/web/wiklou.git] / includes / OutputPage.php
index fbc7b60..c7028db 100644 (file)
@@ -304,6 +304,11 @@ class OutputPage extends ContextSource {
         */
        private $mLinkHeader = [];
 
+       /**
+        * @var string The nonce for Content-Security-Policy
+        */
+       private $CSPNonce;
+
        /**
         * Constructor for OutputPage. This should not be called directly.
         * Instead a new RequestContext should be created and it will implicitly create
@@ -475,7 +480,7 @@ class OutputPage extends ContextSource {
                if ( is_null( $version ) ) {
                        $version = $this->getConfig()->get( 'StyleVersion' );
                }
-               $this->addScript( Html::linkedScript( wfAppendQuery( $path, $version ) ) );
+               $this->addScript( Html::linkedScript( wfAppendQuery( $path, $version ), $this->getCSPNonce() ) );
        }
 
        /**
@@ -485,7 +490,7 @@ class OutputPage extends ContextSource {
         * @param string $script JavaScript text, no script tags
         */
        public function addInlineScript( $script ) {
-               $this->mScripts .= Html::inlineScript( $script );
+               $this->mScripts .= Html::inlineScript( "\n$script\n", $this->getCSPNonce() ) . "\n";
        }
 
        /**
@@ -2433,10 +2438,12 @@ class OutputPage extends ContextSource {
                        $response->header( "X-Frame-Options: $frameOptions" );
                }
 
+               ContentSecurityPolicy::sendHeaders( $this );
+
                if ( $this->mArticleBodyOnly ) {
                        echo $this->mBodytext;
                } else {
-                       // Enable safe mode if requested
+                       // Enable safe mode if requested (T152169)
                        if ( $this->getRequest()->getBool( 'safemode' ) ) {
                                $this->disallowUserJs();
                        }
@@ -2854,6 +2861,17 @@ class OutputPage extends ContextSource {
 
                        $rlClient = new ResourceLoaderClientHtml( $context, [
                                'target' => $this->getTarget(),
+                               'nonce' => $this->getCSPNonce(),
+                               // When 'safemode', disallowUserJs(), or reduceAllowedModules() is used
+                               // to only restrict modules to ORIGIN_CORE (ie. disallow ORIGIN_USER), the list of
+                               // modules enqueud for loading on this page is filtered to just those.
+                               // However, to make sure we also apply the restriction to dynamic dependencies and
+                               // lazy-loaded modules at run-time on the client-side, pass 'safemode' down to the
+                               // StartupModule so that the client-side registry will not contain any restricted
+                               // modules either. (T152169, T185303)
+                               'safemode' => ( $this->getAllowedModules( ResourceLoaderModule::TYPE_COMBINED )
+                                       <= ResourceLoaderModule::ORIGIN_CORE_INDIVIDUAL
+                               ) ? '1' : null,
                        ] );
                        $rlClient->setConfig( $this->getJSVars() );
                        $rlClient->setModules( $this->getModules( /*filter*/ true ) );
@@ -2911,7 +2929,8 @@ class OutputPage extends ContextSource {
                                ResourceLoaderContext::newDummyContext(),
                                [ 'html5shiv' ],
                                ResourceLoaderModule::TYPE_SCRIPTS,
-                               [ 'sync' => true ]
+                               [ 'sync' => true ],
+                               $this->getCSPNonce()
                        ) .
                        '<![endif]-->';
 
@@ -2992,7 +3011,8 @@ class OutputPage extends ContextSource {
                        $this->getRlClientContext(),
                        $modules,
                        $only,
-                       $extraQuery
+                       $extraQuery,
+                       $this->getCSPNonce()
                );
        }
 
@@ -3025,7 +3045,8 @@ class OutputPage extends ContextSource {
                        $chunks[] = ResourceLoader::makeInlineScript(
                                ResourceLoader::makeConfigSetScript(
                                        [ 'wgPageParseReport' => $this->limitReportJSData ]
-                               )
+                               ),
+                               $this->getCSPNonce()
                        );
                }
 
@@ -3992,4 +4013,26 @@ class OutputPage extends ContextSource {
                        );
                }
        }
+
+       /**
+        * Get (and set if not yet set) the CSP nonce.
+        *
+        * This value needs to be included in any <script> tags on the
+        * page.
+        *
+        * @return string|bool Nonce or false to mean don't output nonce
+        * @since 1.32
+        */
+       public function getCSPNonce() {
+               if ( !ContentSecurityPolicy::isEnabled( $this->getConfig() ) ) {
+                       return false;
+               }
+               if ( $this->CSPNonce === null ) {
+                       // XXX It might be expensive to generate randomness
+                       // on every request, on windows.
+                       $rand = MWCryptRand::generate( 15 );
+                       $this->CSPNonce = base64_encode( $rand );
+               }
+               return $this->CSPNonce;
+       }
 }