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 );
103 if ( $reportOnly === self::REPORT_ONLY_MODE ) {
104 return 'Content-Security-Policy-Report-Only';
105 } elseif ( $reportOnly === self::FULL_MODE ) {
106 return 'Content-Security-Policy';
108 throw new UnexpectedValueException( $reportOnly );
119 if ( $policyConfig ===
false ) {
123 if ( $policyConfig ===
true ) {
137 $defaultSrc = [
'*',
'data:',
'blob:' ];
141 $scriptSrc = [
"'unsafe-eval'",
"'self'" ];
142 if ( !isset( $policyConfig[
'useNonces'] ) || $policyConfig[
'useNonces'] ) {
143 $scriptSrc[] =
"'nonce-" . $this->nonce .
"'";
146 $scriptSrc = array_merge( $scriptSrc, $additionalSelfUrlsScript );
147 if ( isset( $policyConfig[
'script-src'] )
148 && is_array( $policyConfig[
'script-src'] )
150 foreach ( $policyConfig[
'script-src']
as $src ) {
155 if ( ( !isset( $policyConfig[
'unsafeFallback'] )
156 || $policyConfig[
'unsafeFallback'] )
163 $scriptSrc[] =
"'unsafe-inline'";
169 if ( isset( $policyConfig[
'default-src'] )
170 && $policyConfig[
'default-src'] !==
false
172 $defaultSrc = array_merge(
173 [
"'self'",
'data:',
'blob:' ],
176 if ( is_array( $policyConfig[
'default-src'] ) ) {
177 foreach ( $policyConfig[
'default-src']
as $src ) {
183 if ( !isset( $policyConfig[
'includeCORS'] ) || $policyConfig[
'includeCORS'] ) {
185 if ( !in_array(
'*', $defaultSrc ) ) {
186 $defaultSrc = array_merge( $defaultSrc, $CORSUrls );
190 if ( !in_array(
'*', $scriptSrc ) ) {
191 $scriptSrc = array_merge( $scriptSrc, $CORSUrls );
195 Hooks::run(
'ContentSecurityPolicyDefaultSource', [ &$defaultSrc, $policyConfig, $mode ] );
196 Hooks::run(
'ContentSecurityPolicyScriptSource', [ &$scriptSrc, $policyConfig, $mode ] );
199 if ( is_array( $defaultSrc ) ) {
200 $cssSrc = array_merge( $defaultSrc, [
"'unsafe-inline'" ] );
203 if ( isset( $policyConfig[
'report-uri'] ) && $policyConfig[
'report-uri'] !==
true ) {
204 if ( $policyConfig[
'report-uri'] ===
false ) {
214 if ( !is_array( $defaultSrc )
215 || !in_array(
'*', $defaultSrc )
216 || !in_array(
'data:', $defaultSrc )
217 || !in_array(
'blob:', $defaultSrc )
228 $imgSrc = [
'*',
'data:',
'blob:' ];
230 $whitelist =
wfMessage(
'external_image_whitelist' )
231 ->inContentLanguage()
233 if ( preg_match(
'/^\s*[^\s#]/m', $whitelist ) ) {
234 $imgSrc = [
'*',
'data:',
'blob:' ];
241 $directives[] =
'script-src ' . implode(
' ', $scriptSrc );
244 $directives[] =
'default-src ' . implode(
' ', $defaultSrc );
247 $directives[] =
'style-src ' . implode(
' ', $cssSrc );
250 $directives[] =
'img-src ' . implode(
' ', $imgSrc );
253 $directives[] =
'report-uri ' . $reportUri;
256 Hooks::run(
'ContentSecurityPolicyDirectives', [ &$directives, $policyConfig, $mode ] );
258 return implode(
'; ', $directives );
270 'action' =>
'cspreport',
273 if ( $mode === self::REPORT_ONLY_MODE ) {
274 $apiArguments[
'reportonly'] =
'1';
303 if ( preg_match(
'/^[a-z][a-z0-9+.-]*:$/i', $url ) ) {
308 if ( !$bits && strpos( $url,
'/' ) ===
false ) {
314 if ( $bits && isset( $bits[
'host'] )
315 && $bits[
'host'] !== $this->mwConfig->get(
'ServerName' )
318 if ( $bits[
'scheme'] !==
'' ) {
321 if ( isset( $bits[
'port'] ) ) {
322 $result .=
':' . $bits[
'port'];
335 $additionalUrls = [];
337 $pathVars = [
'LoadScript',
'ExtensionAssetsPath',
'ResourceBasePath' ];
339 foreach ( $pathVars
as $path ) {
340 $url = $this->mwConfig->get(
$path );
342 if ( $preparedUrl ) {
343 $additionalUrls[] = $preparedUrl;
346 $RLSources = $this->mwConfig->get(
'ResourceLoaderSources' );
347 foreach ( $RLSources
as $wiki => $sources ) {
348 foreach ( $sources
as $id =>
$value ) {
351 $additionalUrls[] = $url;
356 return array_unique( $additionalUrls );
371 $additionalSelfUrls = [];
378 $callback =
function ( $repo, &$urls ) {
379 $urls[] = $repo->getZoneUrl(
'public' );
380 $urls[] = $repo->getZoneUrl(
'transcoded' );
381 $urls[] = $repo->getZoneUrl(
'thumb' );
382 $urls[] = $repo->getDescriptionStylesheetUrl();
385 $callback( $localRepo, $pathUrls );
389 $pathGlobals = [
'LoadScript',
'ExtensionAssetsPath',
'StylePath',
'ResourceBasePath' ];
390 foreach ( $pathGlobals
as $path ) {
391 $pathUrls[] = $this->mwConfig->get(
$path );
393 foreach ( $pathUrls
as $path ) {
395 if ( $preparedUrl !==
false ) {
396 $additionalSelfUrls[] = $preparedUrl;
399 $RLSources = $this->mwConfig->get(
'ResourceLoaderSources' );
401 foreach ( $RLSources
as $wiki => $sources ) {
402 foreach ( $sources
as $id =>
$value ) {
405 $additionalSelfUrls[] = $url;
410 return array_unique( $additionalSelfUrls );
426 $additionalUrls = [];
427 $CORSSources = $this->mwConfig->get(
'CrossSiteAJAXdomains' );
429 if ( strpos(
$source,
'?' ) !==
false ) {
435 $additionalUrls[] = $url;
438 return $additionalUrls;
467 return (
bool)preg_match(
'!Firefox/4[0-2]\.!', $ua );
478 $config->
get(
'CSPHeader' ),
479 $config->
get(
'CSPReportOnlyHeader' )
481 foreach ( $configs
as $headerConfig ) {
483 $headerConfig ===
true ||
484 ( is_array( $headerConfig ) &&
485 !isset( $headerConfig[
'useNonces'] ) ) ||
486 ( is_array( $headerConfig ) &&
487 isset( $headerConfig[
'useNonces'] ) &&
488 $headerConfig[
'useNonces'] )