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();
388 $localRepo = RepoGroup::singleton()->getRepo(
'local' );
389 $callback( $localRepo, $pathUrls );
390 RepoGroup::singleton()->forEachForeignRepo( $callback, [ &$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'] )
wfParseUrl( $url)
parse_url() work-alike, but non-broken.
wfAppendQuery( $url, $query)
Append a query string to an existing URL, which may or may not already have query string parameters a...
wfScript( $script='index')
Get the path to a specified script file, respecting file extensions; this is a wrapper around $wgScri...
wfMessage( $key,... $params)
This is the function for getting translated interface messages.
getAdditionalSelfUrls()
Get additional host names for the wiki (e.g.
__construct( $nonce, WebResponse $response, Config $mwConfig)
static isNonceRequired(Config $config)
Should we set nonce attribute.
prepareUrlForCSP( $url)
Given a url, convert to form needed for CSP.
makeCSPDirectives( $policyConfig, $mode)
Determine what CSP policies to set for this page.
static falsePositiveBrowser( $ua)
Does this browser give false positive reports?
getHeaderName( $reportOnly)
Get the name of the HTTP header to use.
static sendHeaders(IContextSource $context)
Send CSP headers based on wiki config.
escapeUrlForCSP( $url)
CSP spec says ',' and ';' are not allowed to appear in urls.
string $nonce
The nonce to use for inline scripts (from OutputPage)
getReportUri( $mode)
Get the default report uri.
getCORSSources()
include domains that are allowed to send us CORS requests.
getAdditionalSelfUrlsScript()
Get additional script sources.
sendCSPHeader( $csp, $reportOnly)
Send a single CSP header based on a given policy config.
Config $mwConfig
The site configuration object.
Allow programs to request this object from WebRequest::response() and handle all outputting (or lack ...
Interface for configuration instances.
get( $name)
Get a configuration variable such as "Sitename" or "UploadMaintenance.".
Interface for objects which can provide a MediaWiki context on request.