diff --git a/.travis.yml b/.travis.yml index ed91078..7081cc5 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,6 +5,9 @@ php: - "7.0" - "7.1" - "7.2" + - "7.3" + - "7.4" + - "nightly" matrix: fast_finish: true diff --git a/composer.json b/composer.json index 04503ec..2a6c43f 100644 --- a/composer.json +++ b/composer.json @@ -30,15 +30,18 @@ } }, "require": { - "php": "^7", + "php": "^7|^8", "paragonie/constant_time_encoding": "^2" }, "require-dev": { "psr/http-message": "^1", - "phpunit/phpunit": "4.*|5.*", + "phpunit/phpunit": "^7|^8|^9", "squizlabs/php_codesniffer": "^3", "vimeo/psalm": "^3" }, + "scripts": { + "test": "phpunit && psalm" + }, "suggest": { "psr/http-message": "For CSPBuilder::injectCSPHeader()" } diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 6060fcd..fc96fdf 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -10,16 +10,10 @@ processIsolation="false" stopOnError="false" stopOnFailure="false" - syntaxCheck="true" > ./test - - - ./src - - \ No newline at end of file diff --git a/src/CSPBuilder.php b/src/CSPBuilder.php index db9f5fb..5647121 100644 --- a/src/CSPBuilder.php +++ b/src/CSPBuilder.php @@ -15,7 +15,7 @@ class CSPBuilder const FORMAT_NGINX = 'nginx'; /** - * @var array + * @var array */ private $policies = []; @@ -142,6 +142,7 @@ public function compile(): string */ public function addSource(string $directive, string $path): self { + $this->needsCompile = true; switch ($directive) { case 'child': case 'frame': @@ -212,6 +213,7 @@ public function addSource(string $directive, string $path): self */ public function addDirective(string $key, $value = null): self { + $this->needsCompile = true; if ($value === null) { if (!isset($this->policies[$key])) { $this->policies[$key] = true; @@ -777,6 +779,8 @@ protected function compileSubgroup(string $directive, $policies = []): string } return $directive." 'none'; "; } + /** @var array $policies */ + $ret = $directive.' '; if ($directive === 'plugin-types') { // Expects MIME types, not URLs @@ -787,9 +791,12 @@ protected function compileSubgroup(string $directive, $policies = []): string } if (!empty($policies['allow'])) { - foreach ($policies['allow'] as $url) { + /** @var array $allowedPolicies */ + $allowedPolicies = $policies['allow']; + foreach ($allowedPolicies as $url) { + /** @var string|bool $url */ $url = \filter_var($url, FILTER_SANITIZE_URL); - if ($url !== false) { + if (\is_string($url)) { if ($this->supportOldBrowsers && $directive !== 'sandbox') { if (\strpos($url, '://') === false) { if (($this->isHTTPSConnection() && $this->httpsTransformOnHttpsConnections) @@ -812,7 +819,14 @@ protected function compileSubgroup(string $directive, $policies = []): string } if (!empty($policies['hashes'])) { - foreach ($policies['hashes'] as $hash) { + /** @var array> $hashes */ + $hashes = $policies['hashes']; + /** @var array $hash */ + foreach ($hashes as $hash) { + /** + * @var string $algo + * @var string $hashval + */ foreach ($hash as $algo => $hashval) { $ret .= \implode('', [ "'", @@ -826,7 +840,10 @@ protected function compileSubgroup(string $directive, $policies = []): string } if (!empty($policies['nonces'])) { - foreach ($policies['nonces'] as $nonce) { + /** @var array $nonces */ + $nonces = $policies['nonces']; + /** @var string $nonce */ + foreach ($nonces as $nonce) { $ret .= \implode('', [ "'nonce-", \preg_replace('/[^A-Za-z0-9\+\/=]/', '', $nonce), @@ -836,8 +853,11 @@ protected function compileSubgroup(string $directive, $policies = []): string } if (!empty($policies['types'])) { - foreach ($policies['types'] as $type) { - $ret .= $type.' '; + /** @var array $types */ + $types = $policies['types']; + /** @var string $type */ + foreach ($types as $type) { + $ret .= $type . ' '; } } diff --git a/test/BasicTest.php b/test/BasicTest.php index 4f0a101..9e1e0fe 100644 --- a/test/BasicTest.php +++ b/test/BasicTest.php @@ -4,6 +4,7 @@ use ParagonIE\CSPBuilder\CSPBuilder; use PHPUnit\Framework\TestCase; +use PHPUnit\Framework\MockObject\MockObject; /** * Class BasicTest @@ -103,7 +104,7 @@ public function testPreHash() */ public function testSourceHttpsConversion() { - /** @var CSPBuilder|\PHPUnit_Framework_MockObject_MockObject $cspHttp */ + /** @var CSPBuilder|MockObject $cspHttp */ $cspHttp = $this->getMockBuilder(CSPBuilder::class)->setMethods(['isHTTPSConnection']) ->disableOriginalConstructor()->getMock(); $cspHttp->method('isHTTPSConnection')->willReturn(false); @@ -111,11 +112,12 @@ public function testSourceHttpsConversion() $cspHttp->addSource('form', 'http://example.com'); $cspHttp->addSource('form', 'another.com'); $cspHttp->enableHttpsTransformOnHttpsConnections(); // enabled by default + /** @var string $compiledCspHttp */ $compiledCspHttp = $cspHttp->compile(); - $this->assertContains('http://example.com', $compiledCspHttp); - $this->assertContains('http://another.com', $compiledCspHttp); + $this->assertStringContainsString('http://example.com', $compiledCspHttp); + $this->assertStringContainsString('http://another.com', $compiledCspHttp); - /** @var CSPBuilder|\PHPUnit_Framework_MockObject_MockObject $cspHttps */ + /** @var CSPBuilder|MockObject $cspHttps */ $cspHttps = $this->getMockBuilder(CSPBuilder::class)->setMethods(['isHTTPSConnection']) ->disableOriginalConstructor()->getMock(); $cspHttps->method('isHTTPSConnection')->willReturn(true); @@ -123,16 +125,17 @@ public function testSourceHttpsConversion() $cspHttps->addSource('form', 'http://example.com'); $cspHttps->addSource('form', 'another.com'); + /** @var string $compiledCspHttpsWithConvertEnabled */ $compiledCspHttpsWithConvertEnabled = $cspHttps->compile(); - $this->assertContains('https://example.com', $compiledCspHttpsWithConvertEnabled); - $this->assertContains('https://another.com', $compiledCspHttpsWithConvertEnabled); - $this->assertNotContains('http://example.com', $compiledCspHttpsWithConvertEnabled); - $this->assertNotContains('http://another.com', $compiledCspHttpsWithConvertEnabled); + $this->assertStringContainsString('https://example.com', $compiledCspHttpsWithConvertEnabled); + $this->assertStringContainsString('https://another.com', $compiledCspHttpsWithConvertEnabled); + $this->assertStringNotContainsString('http://example.com', $compiledCspHttpsWithConvertEnabled); + $this->assertStringNotContainsString('http://another.com', $compiledCspHttpsWithConvertEnabled); $cspHttps->disableHttpsTransformOnHttpsConnections(); $compiledCspHttpsWithConvertDisabled = $cspHttps->compile(); - $this->assertContains('http://example.com', $compiledCspHttpsWithConvertDisabled); - $this->assertContains('http://another.com', $compiledCspHttpsWithConvertDisabled); + $this->assertStringContainsString('http://example.com', $compiledCspHttpsWithConvertDisabled); + $this->assertStringContainsString('http://another.com', $compiledCspHttpsWithConvertDisabled); } /** @@ -145,8 +148,8 @@ public function testUpgradeInsecureBeatsDisableHttpsConversionFlag() $csp->disableHttpsTransformOnHttpsConnections(); $csp->addDirective('upgrade-insecure-requests'); $compiled = $csp->compile(); - $this->assertContains('https://example.com', $compiled); - $this->assertNotContains('http://example.com', $compiled); + $this->assertStringContainsString('https://example.com', $compiled); + $this->assertStringNotContainsString('http://example.com', $compiled); } /** @@ -159,7 +162,7 @@ public function testAllowDataUris() $csp->setDataAllowed('img-src', true); $compiled = $csp->compile(); - $this->assertContains("data:", $compiled); + $this->assertStringContainsString("data:", $compiled); } /** * @covers CSPBuilder::setSelfAllowed() @@ -188,7 +191,7 @@ public function testAllowSelfUris() $csp->setSelfAllowed('img-src', true); $compiled = $csp->compile(); - $this->assertContains("'self'", $compiled); + $this->assertStringContainsString("'self'", $compiled); } /** @@ -201,7 +204,7 @@ public function testAllowUnsafeEval() $csp->setAllowUnsafeEval('script-src', true); $compiled = $csp->compile(); - $this->assertContains("'unsafe-eval'", $compiled); + $this->assertStringContainsString("'unsafe-eval'", $compiled); } /** @@ -214,7 +217,7 @@ public function testAllowUnsafeInline() $csp->setAllowUnsafeInline('script-src', true); $compiled = $csp->compile(); - $this->assertContains("'unsafe-inline'", $compiled); + $this->assertStringContainsString("'unsafe-inline'", $compiled); } /** @@ -251,13 +254,13 @@ public function testRemovingDirectives() $csp->addSource('style-src', 'https://example.com'); $compiled = $csp->compile(); - $this->assertContains('frame-ancestors https://example.com', $compiled); - $this->assertContains('style-src https://example.com', $compiled); + $this->assertStringContainsString('frame-ancestors https://example.com', $compiled); + $this->assertStringContainsString('style-src https://example.com', $compiled); $csp->removeDirective('style-src'); $compiled = $csp->compile(); - $this->assertContains('frame-ancestors https://example.com', $compiled); - $this->assertNotContains('style-src https://example.com', $compiled); + $this->assertStringContainsString('frame-ancestors https://example.com', $compiled); + $this->assertStringNotContainsString('style-src https://example.com', $compiled); } }