*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace CodeIgniter\HTTP;
use Config\App;
use Config\ContentSecurityPolicy as ContentSecurityPolicyConfig;
/**
* Provides tools for working with the Content-Security-Policy header
* to help defeat XSS attacks.
*
* @see http://www.w3.org/TR/CSP/
* @see http://www.html5rocks.com/en/tutorials/security/content-security-policy/
* @see http://content-security-policy.com/
* @see https://www.owasp.org/index.php/Content_Security_Policy
* @see \CodeIgniter\HTTP\ContentSecurityPolicyTest
*/
class ContentSecurityPolicy
{
/**
* Used for security enforcement
*
* @var array|string
*/
protected $baseURI = [];
/**
* Used for security enforcement
*
* @var array|string
*/
protected $childSrc = [];
/**
* Used for security enforcement
*
* @var array
*/
protected $connectSrc = [];
/**
* Used for security enforcement
*
* @var array|string
*/
protected $defaultSrc = [];
/**
* Used for security enforcement
*
* @var array|string
*/
protected $fontSrc = [];
/**
* Used for security enforcement
*
* @var array|string
*/
protected $formAction = [];
/**
* Used for security enforcement
*
* @var array|string
*/
protected $frameAncestors = [];
/**
* Used for security enforcement
*
* @var array|string
*/
protected $frameSrc = [];
/**
* Used for security enforcement
*
* @var array|string
*/
protected $imageSrc = [];
/**
* Used for security enforcement
*
* @var array|string
*/
protected $mediaSrc = [];
/**
* Used for security enforcement
*
* @var array|string
*/
protected $objectSrc = [];
/**
* Used for security enforcement
*
* @var array|string
*/
protected $pluginTypes = [];
/**
* Used for security enforcement
*
* @var string
*/
protected $reportURI;
/**
* Used for security enforcement
*
* @var array|string
*/
protected $sandbox = [];
/**
* Used for security enforcement
*
* @var array|string
*/
protected $scriptSrc = [];
/**
* Used for security enforcement
*
* @var array|string
*/
protected $styleSrc = [];
/**
* Used for security enforcement
*
* @var array|string
*/
protected $manifestSrc = [];
/**
* Used for security enforcement
*
* @var bool
*/
protected $upgradeInsecureRequests = false;
/**
* Used for security enforcement
*
* @var bool
*/
protected $reportOnly = false;
/**
* Used for security enforcement
*
* @var array
*/
protected $validSources = [
'self',
'none',
'unsafe-inline',
'unsafe-eval',
];
/**
* Used for security enforcement
*
* @var array
*/
protected $nonces = [];
/**
* Nonce for style
*
* @var string
*/
protected $styleNonce;
/**
* Nonce for script
*
* @var string
*/
protected $scriptNonce;
/**
* Nonce tag for style
*
* @var string
*/
protected $styleNonceTag = '{csp-style-nonce}';
/**
* Nonce tag for script
*
* @var string
*/
protected $scriptNonceTag = '{csp-script-nonce}';
/**
* Replace nonce tag automatically
*
* @var bool
*/
protected $autoNonce = true;
/**
* An array of header info since we have
* to build ourself before passing to Response.
*
* @var array
*/
protected $tempHeaders = [];
/**
* An array of header info to build
* that should only be reported.
*
* @var array
*/
protected $reportOnlyHeaders = [];
/**
* Whether Content Security Policy is being enforced.
*
* @var bool
*/
protected $CSPEnabled = false;
/**
* Constructor.
*
* Stores our default values from the Config file.
*/
public function __construct(ContentSecurityPolicyConfig $config)
{
$appConfig = config(App::class);
$this->CSPEnabled = $appConfig->CSPEnabled;
foreach (get_object_vars($config) as $setting => $value) {
if (property_exists($this, $setting)) {
$this->{$setting} = $value;
}
}
if (! is_array($this->styleSrc)) {
$this->styleSrc = [$this->styleSrc];
}
if (! is_array($this->scriptSrc)) {
$this->scriptSrc = [$this->scriptSrc];
}
}
/**
* Whether Content Security Policy is being enforced.
*/
public function enabled(): bool
{
return $this->CSPEnabled;
}
/**
* Get the nonce for the style tag.
*/
public function getStyleNonce(): string
{
if ($this->styleNonce === null) {
$this->styleNonce = bin2hex(random_bytes(12));
$this->styleSrc[] = 'nonce-' . $this->styleNonce;
}
return $this->styleNonce;
}
/**
* Get the nonce for the script tag.
*/
public function getScriptNonce(): string
{
if ($this->scriptNonce === null) {
$this->scriptNonce = bin2hex(random_bytes(12));
$this->scriptSrc[] = 'nonce-' . $this->scriptNonce;
}
return $this->scriptNonce;
}
/**
* Compiles and sets the appropriate headers in the request.
*
* Should be called just prior to sending the response to the user agent.
*
* @return void
*/
public function finalize(ResponseInterface $response)
{
if ($this->autoNonce) {
$this->generateNonces($response);
}
$this->buildHeaders($response);
}
/**
* If TRUE, nothing will be restricted. Instead all violations will
* be reported to the reportURI for monitoring. This is useful when
* you are just starting to implement the policy, and will help
* determine what errors need to be addressed before you turn on
* all filtering.
*
* @return $this
*/
public function reportOnly(bool $value = true)
{
$this->reportOnly = $value;
return $this;
}
/**
* Adds a new base_uri value. Can be either a URI class or a simple string.
*
* base_uri restricts the URLs that can appear in a page's element.
*
* @see http://www.w3.org/TR/CSP/#directive-base-uri
*
* @param array|string $uri
*
* @return $this
*/
public function addBaseURI($uri, ?bool $explicitReporting = null)
{
$this->addOption($uri, 'baseURI', $explicitReporting ?? $this->reportOnly);
return $this;
}
/**
* Adds a new valid endpoint for a form's action. Can be either
* a URI class or a simple string.
*
* child-src lists the URLs for workers and embedded frame contents.
* For example: child-src https://youtube.com would enable embedding
* videos from YouTube but not from other origins.
*
* @see http://www.w3.org/TR/CSP/#directive-child-src
*
* @param array|string $uri
*
* @return $this
*/
public function addChildSrc($uri, ?bool $explicitReporting = null)
{
$this->addOption($uri, 'childSrc', $explicitReporting ?? $this->reportOnly);
return $this;
}
/**
* Adds a new valid endpoint for a form's action. Can be either
* a URI class or a simple string.
*
* connect-src limits the origins to which you can connect
* (via XHR, WebSockets, and EventSource).
*
* @see http://www.w3.org/TR/CSP/#directive-connect-src
*
* @param array|string $uri
*
* @return $this
*/
public function addConnectSrc($uri, ?bool $explicitReporting = null)
{
$this->addOption($uri, 'connectSrc', $explicitReporting ?? $this->reportOnly);
return $this;
}
/**
* Adds a new valid endpoint for a form's action. Can be either
* a URI class or a simple string.
*
* default_src is the URI that is used for many of the settings when
* no other source has been set.
*
* @see http://www.w3.org/TR/CSP/#directive-default-src
*
* @param array|string $uri
*
* @return $this
*/
public function setDefaultSrc($uri, ?bool $explicitReporting = null)
{
$this->defaultSrc = [(string) $uri => $explicitReporting ?? $this->reportOnly];
return $this;
}
/**
* Adds a new valid endpoint for a form's action. Can be either
* a URI class or a simple string.
*
* font-src specifies the origins that can serve web fonts.
*
* @see http://www.w3.org/TR/CSP/#directive-font-src
*
* @param array|string $uri
*
* @return $this
*/
public function addFontSrc($uri, ?bool $explicitReporting = null)
{
$this->addOption($uri, 'fontSrc', $explicitReporting ?? $this->reportOnly);
return $this;
}
/**
* Adds a new valid endpoint for a form's action. Can be either
* a URI class or a simple string.
*
* @see http://www.w3.org/TR/CSP/#directive-form-action
*
* @param array|string $uri
*
* @return $this
*/
public function addFormAction($uri, ?bool $explicitReporting = null)
{
$this->addOption($uri, 'formAction', $explicitReporting ?? $this->reportOnly);
return $this;
}
/**
* Adds a new resource that should allow embedding the resource using
* ,