diff --git a/src/Header/ContentSecurityPolicy.php b/src/Header/ContentSecurityPolicy.php index 9467146b5d..cc30acd3d2 100644 --- a/src/Header/ContentSecurityPolicy.php +++ b/src/Header/ContentSecurityPolicy.php @@ -12,7 +12,7 @@ * * @link http://www.w3.org/TR/CSP/ */ -class ContentSecurityPolicy implements HeaderInterface +class ContentSecurityPolicy implements MultipleHeaderInterface { /** * Valid directive names @@ -154,4 +154,20 @@ public function toString() { return sprintf('%s: %s', $this->getFieldName(), $this->getFieldValue()); } + + public function toStringMultipleHeaders(array $headers) + { + $strings = [$this->toString()]; + foreach ($headers as $header) { + if (! $header instanceof ContentSecurityPolicy) { + throw new Exception\RuntimeException( + 'The ContentSecurityPolicy multiple header implementation can only' + . ' accept an array of ContentSecurityPolicy headers' + ); + } + $strings[] = $header->toString(); + } + + return implode("\r\n", $strings) . "\r\n"; + } } diff --git a/src/HeaderLoader.php b/src/HeaderLoader.php index 28d9da4c82..735e43878d 100644 --- a/src/HeaderLoader.php +++ b/src/HeaderLoader.php @@ -36,6 +36,7 @@ class HeaderLoader extends PluginClassLoader 'contentlocation' => Header\ContentLocation::class, 'contentmd5' => Header\ContentMD5::class, 'contentrange' => Header\ContentRange::class, + 'contentsecuritypolicy' => Header\ContentSecurityPolicy::class, 'contenttransferencoding' => Header\ContentTransferEncoding::class, 'contenttype' => Header\ContentType::class, 'cookie' => Header\Cookie::class, diff --git a/test/Header/ContentSecurityPolicyTest.php b/test/Header/ContentSecurityPolicyTest.php index a4e6edb21d..837bf2f76c 100644 --- a/test/Header/ContentSecurityPolicyTest.php +++ b/test/Header/ContentSecurityPolicyTest.php @@ -8,9 +8,13 @@ namespace ZendTest\Http\Header; use PHPUnit\Framework\TestCase; +use Zend\Http\Exception\RuntimeException; use Zend\Http\Header\ContentSecurityPolicy; use Zend\Http\Header\Exception\InvalidArgumentException; +use Zend\Http\Header\GenericHeader; use Zend\Http\Header\HeaderInterface; +use Zend\Http\Header\MultipleHeaderInterface; +use Zend\Http\Headers; class ContentSecurityPolicyTest extends TestCase { @@ -25,6 +29,7 @@ public function testContentSecurityPolicyFromStringParsesDirectivesCorrectly() $csp = ContentSecurityPolicy::fromString( "Content-Security-Policy: default-src 'none'; script-src 'self'; img-src 'self'; style-src 'self';" ); + $this->assertInstanceOf(MultipleHeaderInterface::class, $csp); $this->assertInstanceOf(HeaderInterface::class, $csp); $this->assertInstanceOf(ContentSecurityPolicy::class, $csp); $directives = [ @@ -139,4 +144,47 @@ public function testContentSecurityPolicySetDirectiveWithEmptyReportUriRemovesEx $csp->toString() ); } + + public function testToStringMultipleHeaders() + { + $csp = new ContentSecurityPolicy(); + $csp->setDirective('default-src', ["'self'"]); + + $additional = new ContentSecurityPolicy(); + $additional->setDirective('img-src', ['https://*.github.com']); + + self::assertSame( + "Content-Security-Policy: default-src 'self';\r\n" + . "Content-Security-Policy: img-src https://*.github.com;\r\n", + $csp->toStringMultipleHeaders([$additional]) + ); + } + + public function testToStringMultipleHeadersExceptionIfDifferent() + { + $csp = new ContentSecurityPolicy(); + $csp->setDirective('default-src', ["'self'"]); + + $additional = new GenericHeader(); + + $this->expectException(RuntimeException::class); + $this->expectExceptionMessage( + 'The ContentSecurityPolicy multiple header implementation' + . ' can only accept an array of ContentSecurityPolicy headers' + ); + $csp->toStringMultipleHeaders([$additional]); + } + + public function testMultiple() + { + $headers = new Headers(); + $headers->addHeader((new ContentSecurityPolicy())->setDirective('default-src', ["'self'"])); + $headers->addHeader((new ContentSecurityPolicy())->setDirective('img-src', ['https://*.github.com'])); + + self::assertSame( + "Content-Security-Policy: default-src 'self';\r\n" + . "Content-Security-Policy: img-src https://*.github.com;\r\n", + $headers->toString() + ); + } }