*/
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
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() ) );
}
/**
* @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";
}
/**
}
/**
- * Add one or more modules recognized by ResourceLoader. Modules added
- * through this function will be loaded by ResourceLoader when the
- * page loads.
+ * Load one or more ResourceLoader modules on this page.
*
* @param string|array $modules Module name (string) or array of module names
*/
}
/**
- * Get the list of module JS to include on this page
+ * Get the list of script-only modules to load on this page.
*
* @param bool $filter
* @param string|null $position Unused
}
/**
- * Add only JS of one or more modules recognized by ResourceLoader. Module
- * scripts added through this function will be loaded by ResourceLoader when
- * the page loads.
+ * Load the scripts of one or more ResourceLoader modules, on this page.
+ *
+ * This method exists purely to provide the legacy behaviour of loading
+ * a module's scripts in the global scope, and without dependency resolution.
+ * See <https://phabricator.wikimedia.org/T188689>.
*
+ * @deprecated since 1.31 Use addModules() instead.
* @param string|array $modules Module name (string) or array of module names
*/
public function addModuleScripts( $modules ) {
}
/**
- * Get the list of module CSS to include on this page
+ * Get the list of style-only modules to load on this page.
*
* @param bool $filter
* @param string|null $position Unused
}
/**
- * Add only CSS of one or more modules recognized by ResourceLoader.
+ * Load the styles of one or more ResourceLoader modules on this page.
*
- * Module styles added through this function will be added using standard link CSS
- * tags, rather than as a combined Javascript and CSS package. Thus, they will
- * load when JavaScript is disabled (unless CSS also happens to be disabled).
+ * Module styles added through this function will be loaded as a stylesheet,
+ * using a standard `<link rel=stylesheet>` HTML tag, rather than as a combined
+ * Javascript and CSS package. Thus, they will even load when JavaScript is disabled.
*
* @param string|array $modules Module name (string) or array of module names
*/
}
}
+ /**
+ * Transfer styles and JavaScript modules from skin.
+ *
+ * @param Skin $sk to load modules for
+ */
+ public function loadSkinModules( $sk ) {
+ foreach ( $sk->getDefaultModules() as $group => $modules ) {
+ if ( $group === 'styles' ) {
+ foreach ( $modules as $key => $moduleMembers ) {
+ $this->addModuleStyles( $moduleMembers );
+ }
+ } else {
+ $this->addModules( $modules );
+ }
+ }
+ }
+
/**
* Finally, all the text has been munged and accumulated into
* the object, let's actually output it:
$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();
}
$sk = $this->getSkin();
- foreach ( $sk->getDefaultModules() as $group ) {
- $this->addModules( $group );
- }
+ $this->loadSkinModules( $sk );
MWDebug::addModules( $this );
$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 ) );
ResourceLoaderContext::newDummyContext(),
[ 'html5shiv' ],
ResourceLoaderModule::TYPE_SCRIPTS,
- [ 'sync' => true ]
+ [ 'sync' => true ],
+ $this->getCSPNonce()
) .
'<![endif]-->';
$this->getRlClientContext(),
$modules,
$only,
- $extraQuery
+ $extraQuery,
+ $this->getCSPNonce()
);
}
$chunks[] = ResourceLoader::makeInlineScript(
ResourceLoader::makeConfigSetScript(
[ 'wgPageParseReport' => $this->limitReportJSData ]
- )
+ ),
+ $this->getCSPNonce()
);
}
);
}
}
+
+ /**
+ * 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;
+ }
}