60 $this->response->header(
61 "$headerName: $policy"
81 $cspConfig =
$context->getConfig()->get(
'CSPHeader' );
82 $cspConfigReportOnly =
$context->getConfig()->get(
'CSPReportOnlyHeader' );
84 $csp->sendCSPHeader( $cspConfig, self::FULL_MODE );
85 $csp->sendCSPHeader( $cspConfigReportOnly, self::REPORT_ONLY_MODE );
104 if ( $reportOnly === self::REPORT_ONLY_MODE ) {
105 return 'Content-Security-Policy-Report-Only';
108 if ( $reportOnly === self::FULL_MODE ) {
109 return 'Content-Security-Policy';
111 throw new UnexpectedValueException( $reportOnly );
123 if ( $policyConfig ===
false ) {
127 if ( $policyConfig ===
true ) {
141 $defaultSrc = [
'*',
'data:',
'blob:' ];
145 $scriptSrc = [
"'unsafe-eval'",
"'self'" ];
146 if ( !isset( $policyConfig[
'useNonces'] ) || $policyConfig[
'useNonces'] ) {
147 $scriptSrc[] =
"'nonce-" . $this->nonce .
"'";
150 $scriptSrc = array_merge( $scriptSrc, $additionalSelfUrlsScript );
151 if ( isset( $policyConfig[
'script-src'] )
152 && is_array( $policyConfig[
'script-src'] )
154 foreach ( $policyConfig[
'script-src'] as $src ) {
159 if ( !isset( $policyConfig[
'unsafeFallback'] )
160 || $policyConfig[
'unsafeFallback']
167 $scriptSrc[] =
"'unsafe-inline'";
173 if ( isset( $policyConfig[
'default-src'] )
174 && $policyConfig[
'default-src'] !==
false
176 $defaultSrc = array_merge(
177 [
"'self'",
'data:',
'blob:' ],
180 if ( is_array( $policyConfig[
'default-src'] ) ) {
181 foreach ( $policyConfig[
'default-src'] as $src ) {
187 if ( !isset( $policyConfig[
'includeCORS'] ) || $policyConfig[
'includeCORS'] ) {
189 if ( !in_array(
'*', $defaultSrc ) ) {
190 $defaultSrc = array_merge( $defaultSrc, $CORSUrls );
194 if ( !in_array(
'*', $scriptSrc ) ) {
195 $scriptSrc = array_merge( $scriptSrc, $CORSUrls );
199 Hooks::run(
'ContentSecurityPolicyDefaultSource', [ &$defaultSrc, $policyConfig, $mode ] );
200 Hooks::run(
'ContentSecurityPolicyScriptSource', [ &$scriptSrc, $policyConfig, $mode ] );
203 if ( is_array( $defaultSrc ) ) {
204 $cssSrc = array_merge( $defaultSrc, [
"'unsafe-inline'" ] );
207 if ( isset( $policyConfig[
'report-uri'] ) && $policyConfig[
'report-uri'] !==
true ) {
208 if ( $policyConfig[
'report-uri'] ===
false ) {
218 if ( !is_array( $defaultSrc )
219 || !in_array(
'*', $defaultSrc )
220 || !in_array(
'data:', $defaultSrc )
221 || !in_array(
'blob:', $defaultSrc )
232 $imgSrc = [
'*',
'data:',
'blob:' ];
234 $whitelist =
wfMessage(
'external_image_whitelist' )
235 ->inContentLanguage()
237 if ( preg_match(
'/^\s*[^\s#]/m', $whitelist ) ) {
238 $imgSrc = [
'*',
'data:',
'blob:' ];
245 $directives[] =
'script-src ' . implode(
' ', $scriptSrc );
248 $directives[] =
'default-src ' . implode(
' ', $defaultSrc );
251 $directives[] =
'style-src ' . implode(
' ', $cssSrc );
254 $directives[] =
'img-src ' . implode(
' ', $imgSrc );
257 $directives[] =
'report-uri ' . $reportUri;
260 Hooks::run(
'ContentSecurityPolicyDirectives', [ &$directives, $policyConfig, $mode ] );
262 return implode(
'; ', $directives );
274 'action' =>
'cspreport',
277 if ( $mode === self::REPORT_ONLY_MODE ) {
278 $apiArguments[
'reportonly'] =
'1';
307 if ( preg_match(
'/^[a-z][a-z0-9+.-]*:$/i', $url ) ) {
312 if ( !$bits && strpos( $url,
'/' ) ===
false ) {
318 if ( $bits && isset( $bits[
'host'] )
319 && $bits[
'host'] !== $this->mwConfig->get(
'ServerName' )
321 $result = $bits[
'host'];
322 if ( $bits[
'scheme'] !==
'' ) {
323 $result = $bits[
'scheme'] . $bits[
'delimiter'] . $result;
325 if ( isset( $bits[
'port'] ) ) {
326 $result .=
':' . $bits[
'port'];
339 $additionalUrls = [];
341 $pathVars = [
'LoadScript',
'ExtensionAssetsPath',
'ResourceBasePath' ];
343 foreach ( $pathVars as
$path ) {
344 $url = $this->mwConfig->get(
$path );
346 if ( $preparedUrl ) {
347 $additionalUrls[] = $preparedUrl;
350 $RLSources = $this->mwConfig->get(
'ResourceLoaderSources' );
351 foreach ( $RLSources as $wiki => $sources ) {
352 foreach ( $sources as $id => $value ) {
355 $additionalUrls[] = $url;
360 return array_unique( $additionalUrls );
375 $additionalSelfUrls = [];
382 $callback =
function ( $repo, &$urls ) {
383 $urls[] = $repo->getZoneUrl(
'public' );
384 $urls[] = $repo->getZoneUrl(
'transcoded' );
385 $urls[] = $repo->getZoneUrl(
'thumb' );
386 $urls[] = $repo->getDescriptionStylesheetUrl();
389 $callback( $localRepo, $pathUrls );
393 $pathGlobals = [
'LoadScript',
'ExtensionAssetsPath',
'StylePath',
'ResourceBasePath' ];
394 foreach ( $pathGlobals as
$path ) {
395 $pathUrls[] = $this->mwConfig->get(
$path );
397 foreach ( $pathUrls as
$path ) {
399 if ( $preparedUrl !==
false ) {
400 $additionalSelfUrls[] = $preparedUrl;
403 $RLSources = $this->mwConfig->get(
'ResourceLoaderSources' );
405 foreach ( $RLSources as $wiki => $sources ) {
406 foreach ( $sources as $id => $value ) {
409 $additionalSelfUrls[] = $url;
414 return array_unique( $additionalSelfUrls );
430 $additionalUrls = [];
431 $CORSSources = $this->mwConfig->get(
'CrossSiteAJAXdomains' );
432 foreach ( $CORSSources as
$source ) {
433 if ( strpos(
$source,
'?' ) !==
false ) {
439 $additionalUrls[] = $url;
442 return $additionalUrls;
471 return (
bool)preg_match(
'!Firefox/4[0-2]\.!', $ua );
482 $config->
get(
'CSPHeader' ),
483 $config->
get(
'CSPReportOnlyHeader' )
485 foreach ( $configs as $headerConfig ) {
487 $headerConfig ===
true ||
488 ( is_array( $headerConfig ) &&
489 !isset( $headerConfig[
'useNonces'] ) ) ||
490 ( is_array( $headerConfig ) &&
491 isset( $headerConfig[
'useNonces'] ) &&
492 $headerConfig[
'useNonces'] )