Skip to content

Commit

Permalink
Merge pull request #74 from fritzmg/csp-header-parser
Browse files Browse the repository at this point in the history
Add a CSP header parser (`CSPBuilder::fromHeader`)
  • Loading branch information
paragonie-security authored Jul 18, 2023
2 parents 0c9592c + 2839a51 commit 34b2d80
Show file tree
Hide file tree
Showing 2 changed files with 117 additions and 0 deletions.
64 changes: 64 additions & 0 deletions src/CSPBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -347,6 +347,70 @@ public static function fromFile(string $filename = ''): self
return self::fromData($contents);
}

/**
* Factory method - create a new CSPBuilder object from an existing CSP header
*
* @param string $header
* @return self
* @throws Exception
*/
public static function fromHeader(string $header = ''): self
{
$csp = new CSPBuilder();

$directives = explode(';', $header);

foreach ($directives as $directive) {
[$name, $values] = explode(' ', trim($directive), 2) + [null, null];

if (null === $name) {
continue;
}

if ('upgrade-insecure-requests' === $name) {
$csp->addDirective('upgrade-insecure-requests');

continue;
}

if (null === $values) {
continue;
}

foreach (explode(' ', $values) as $value) {
if ('report-to' === $name) {
$csp->setReportTo($value);
} elseif ('report-uri' === $name) {
$csp->setReportUri($value);
} elseif ('require-sri-for' === $name) {
$csp->requireSRIFor($value);
} elseif ('plugin-types' === $name) {
$csp->allowPluginType($value);
} else {
switch ($value) {
case "'none'": $csp->addDirective($name, false); break;
case "'self'": $csp->setSelfAllowed($name, true); break;
case 'blob:': $csp->setBlobAllowed($name, true); break;
case 'data:': $csp->setDataAllowed($name, true); break;
case 'filesystem:': $csp->setFileSystemAllowed($name, true); break;
case 'https:': $csp->setHttpsAllowed($name, true); break;
case 'mediastream:': $csp->setMediaStreamAllowed($name, true); break;
case "'report-sample'": $csp->setReportSample($name, true); break;
case "'strict-dynamic'": $csp->setStrictDynamic($name, true); break;
case "'unsafe-eval'": $csp->setAllowUnsafeEval($name, true); break;
case "'unsafe-hashes'": $csp->setAllowUnsafeHashes($name, true); break;
case "'unsafe-inline'": $csp->setAllowUnsafeInline($name, true); break;
case "'unsafe-hashed-attributes'": $csp->setAllowUnsafeHashedAttributes('script-src', true); break;

default: $csp->addSource($name, $value);
}
}
}
}

return $csp;
}

/**
* Get the formatted CSP header
*
Expand Down
53 changes: 53 additions & 0 deletions test/ParserTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
<?php

namespace ParagonIE\CSPBuilderTest;

use PHPUnit\Framework\TestCase;
use ParagonIE\CSPBuilder\CSPBuilder;

/**
* Class ParserTest
* @package ParagonIE\CSPBuilderTest
*/
class ParserTest extends TestCase
{
/**
* @covers CSPBuilder::fromHeader()
* @dataProvider cspDirectivesProvider
*/
public function testParsesCspHeader(string $header): void
{
$csp = CSPBuilder::fromHeader($header)
->disableHttpsTransformOnHttpsConnections()
->disableOldBrowserSupport()
;

$result = $csp->compile();

$this->assertSame($header, $result);
}

public static function cspDirectivesProvider(): \Generator
{
yield ["default-src 'self'"];
yield ["script-src 'none'"];
yield ["script-src 'unsafe-eval'"];
yield ["script-src 'unsafe-inline'"];
yield ["style-src 'none'"];
yield ["style-src 'self'"];
yield ["style-src 'unsafe-inline'"];
yield ["script-src 'self' example.com"];
yield ["script-src 'self' example.com; style-src 'self'"];
yield ["script-src 'self' example.com; style-src 'self' 'unsafe-inline'"];
yield ["script-src 'self' example.com; style-src 'self' 'unsafe-inline'; upgrade-insecure-requests"];
yield ["frame-ancestors 'none'; script-src 'self' example.com"];
yield ["img-src 'self' data:; script-src 'self' example.com"];
yield ["frame-ancestors 'self' https://example.org https://example.com https://store.example.com"];
yield ["default-src 'self'; script-src https://example.com"];
yield ["base-uri 'self'; report-uri https://endpoint.com; report-to csp-endpoint"];
yield ["font-src https://example.com/"];
yield ["script-src 'unsafe-hashed-attributes'"];
yield ["plugin-types application/x-java-applet"];
yield ["form-action 'none'; sandbox allow-scripts; style-src-attr 'none'; worker-src https://example.com/"];
}
}

0 comments on commit 34b2d80

Please sign in to comment.