Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
129 changes: 62 additions & 67 deletions system/HTTP/ContentSecurityPolicy.php
Original file line number Diff line number Diff line change
Expand Up @@ -66,43 +66,43 @@ class ContentSecurityPolicy
/**
* The `base-uri` directive restricts the URLs that can be used to specify the document base URL.
*
* @var array<string, bool>|string|null
* @var array<string, bool|null>|string|null
*/
protected $baseURI = [];

/**
* The `child-src` directive governs the creation of nested browsing contexts as well
* as Worker execution contexts.
*
* @var array<string, bool>|string
* @var array<string, bool|null>|string
*/
protected $childSrc = [];

/**
* The `connect-src` directive restricts which URLs the protected resource can load using script interfaces.
*
* @var array<string, bool>|string
* @var array<string, bool|null>|string
*/
protected $connectSrc = [];

/**
* The `default-src` directive sets a default source list for a number of directives.
*
* @var array<string, bool>|string|null
* @var array<string, bool|null>|string|null
*/
protected $defaultSrc = [];

/**
* The `font-src` directive restricts from where the protected resource can load fonts.
*
* @var array<string, bool>|string
* @var array<string, bool|null>|string
*/
protected $fontSrc = [];

/**
* The `form-action` directive restricts which URLs can be used as the action of HTML form elements.
*
* @var array<string, bool>|string
* @var array<string, bool|null>|string
*/
protected $formAction = [];

Expand All @@ -111,65 +111,65 @@ class ContentSecurityPolicy
* the resource using a `frame`, `iframe`, `object`, `embed` or `applet` element,
* or equivalent functionality in non-HTML resources.
*
* @var array<string, bool>|string
* @var array<string, bool|null>|string
*/
protected $frameAncestors = [];

/**
* The `frame-src` directive restricts the URLs which may be loaded into child navigables.
*
* @var array<string, bool>|string
* @var array<string, bool|null>|string
*/
protected $frameSrc = [];

/**
* The `img-src` directive restricts from where the protected resource can load images.
*
* @var array<string, bool>|string
* @var array<string, bool|null>|string
*/
protected $imageSrc = [];

/**
* The `media-src` directive restricts from where the protected resource can load video,
* audio, and associated text tracks.
*
* @var array<string, bool>|string
* @var array<string, bool|null>|string
*/
protected $mediaSrc = [];

/**
* The `object-src` directive restricts from where the protected resource can load plugins.
*
* @var array<string, bool>|string
* @var array<string, bool|null>|string
*/
protected $objectSrc = [];

/**
* The `plugin-types` directive restricts the set of plugins that can be invoked by the
* protected resource by limiting the types of resources that can be embedded.
*
* @var array<string, bool>|string
* @var array<string, bool|null>|string
*/
protected $pluginTypes = [];

/**
* The `script-src` directive restricts which scripts the protected resource can execute.
*
* @var array<string, bool>|string
* @var array<string, bool|null>|string
*/
protected $scriptSrc = [];

/**
* The `style-src` directive restricts which styles the user may applies to the protected resource.
*
* @var array<string, bool>|string
* @var array<string, bool|null>|string
*/
protected $styleSrc = [];

/**
* The `sandbox` directive specifies an HTML sandbox policy that the user agent applies to the protected resource.
*
* @var array<string, bool>|string
* @var array<string, bool|null>|string
*/
protected $sandbox = [];

Expand All @@ -193,45 +193,45 @@ class ContentSecurityPolicy
/**
* The `manifest-src` directive restricts the URLs from which application manifests may be loaded.
*
* @var array<string, bool>|string
* @var array<string, bool|null>|string
*/
protected $manifestSrc = [];

/**
* The `script-src-elem` directive applies to all script requests and script blocks.
*
* @var array<string, bool>|string
* @var array<string, bool|null>|string
*/
protected array|string $scriptSrcElem = [];

/**
* The `script-src-attr` directive applies to event handlers and, if present,
* it will override the `script-src` directive for relevant checks.
*
* @var array<string, bool>|string
* @var array<string, bool|null>|string
*/
protected array|string $scriptSrcAttr = [];

/**
* The `style-src-elem` directive governs the behaviour of styles except
* for styles defined in inline attributes.
*
* @var array<string, bool>|string
* @var array<string, bool|null>|string
*/
protected array|string $styleSrcElem = [];

/**
* The `style-src-attr` directive governs the behaviour of style attributes.
*
* @var array<string, bool>|string
* @var array<string, bool|null>|string
*/
protected array|string $styleSrcAttr = [];

/**
* The `worker-src` directive restricts the URLs which may be loaded as a `Worker`,
* `SharedWorker`, or `ServiceWorker`.
*
* @var array<string, bool>|string
* @var array<string, bool|null>|string
*/
protected array|string $workerSrc = [];

Expand Down Expand Up @@ -365,8 +365,8 @@ public function __construct(ContentSecurityPolicyConfig $config)
&& array_is_list($value)
) {
// Config sets these directives as `list<string>|string`
// but we need them as `array<string, bool>` internally.
$this->{$setting} = array_combine($value, array_fill(0, count($value), $this->reportOnly));
// but we need them as `array<string, bool|null>` internally.
$this->{$setting} = array_fill_keys($value, null);

continue;
}
Expand All @@ -375,11 +375,11 @@ public function __construct(ContentSecurityPolicyConfig $config)
}

if (! is_array($this->styleSrc)) {
$this->styleSrc = [$this->styleSrc => $this->reportOnly];
$this->styleSrc = [$this->styleSrc => null];
}

if (! is_array($this->scriptSrc)) {
$this->scriptSrc = [$this->scriptSrc => $this->reportOnly];
$this->scriptSrc = [$this->scriptSrc => null];
}
}

Expand Down Expand Up @@ -885,6 +885,28 @@ public function addReportingEndpoints(array $endpoint): static
return $this;
}

public function clearDirective(string $directive): void
{
if (! array_key_exists($directive, $this->directives)) {
return;
}

if ($directive === 'report-uri') {
$this->reportURI = null;

return;
}

if ($directive === 'report-to') {
$this->reportURI = null;
$this->reportTo = null;

return;
}

$this->{$this->directives[$directive]} = [];
}

/**
* DRY method to add an string or array to a class property.
*
Expand All @@ -896,7 +918,7 @@ protected function addOption($options, string $target, ?bool $explicitReporting
{
// Ensure we have an array to work with...
if (is_string($this->{$target})) {
$this->{$target} = $this->stringToOptions($this->{$target});
$this->{$target} = $this->parseSourceList($this->{$target});
} elseif (! is_array($this->{$target})) {
$this->{$target} = [];
}
Expand All @@ -908,28 +930,6 @@ protected function addOption($options, string $target, ?bool $explicitReporting
}
}

/**
* Converts a string directive value into an array of options.
*
* @return array<string, bool>
*/
private function stringToOptions(string $values): array
{
$valueStr = trim($values);

if ($valueStr === '') {
return ['' => $this->reportOnly];
}

$options = [];

foreach (preg_split('/\s+/', $valueStr) as $val) {
$options[$val] = $this->reportOnly;
}

return $options;
}

/**
* Scans the body of the request message and replaces any nonce
* placeholders with actual nonces, that we'll then add to our
Expand Down Expand Up @@ -1047,8 +1047,9 @@ protected function buildHeaders(ResponseInterface $response)
* Adds a directive and its options to the appropriate header. The $values
* array might have options that are geared toward either the regular or the
* reportOnly header, since it's viable to have both simultaneously.
* An option flagged `null` follows the current `reportOnly()` setting.
*
* @param array<string, bool>|string $values
* @param array<string, bool|null>|string $values
*
* @return void
*/
Expand All @@ -1059,7 +1060,7 @@ protected function addToHeader(string $name, $values = null)
}

if (is_string($values)) {
$values = $this->stringToOptions($values);
$values = $this->parseSourceList($values);
}

$sources = [];
Expand All @@ -1078,7 +1079,7 @@ protected function addToHeader(string $name, $values = null)
$value = "'{$value}'";
}

if ($reportOnly) {
if ($reportOnly ?? $this->reportOnly) {
$reportSources[] = $value;
} else {
$sources[] = $value;
Expand All @@ -1102,25 +1103,19 @@ protected function addToHeader(string $name, $values = null)
}
}

public function clearDirective(string $directive): void
/**
* Converts a string directive value into an array of options.
*
* @return array<string, bool|null>
*/
private function parseSourceList(string $values): array
{
if (! array_key_exists($directive, $this->directives)) {
return;
}

if ($directive === 'report-uri') {
$this->reportURI = null;

return;
}

if ($directive === 'report-to') {
$this->reportURI = null;
$this->reportTo = null;
$valueStr = trim($values);

return;
if ($valueStr === '') {
return ['' => null];
}

$this->{$this->directives[$directive]} = [];
return array_fill_keys(preg_split('/\s+/', $valueStr), null);
}
}
Loading
Loading