Skip to content

Commit

Permalink
Add injectCSPHeader() (PSR-7) and getHeaderArray() methods
Browse files Browse the repository at this point in the history
  • Loading branch information
paragonie-scott committed Jan 3, 2016
1 parent 8d8b993 commit 84ad3c5
Show file tree
Hide file tree
Showing 4 changed files with 98 additions and 14 deletions.
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,8 +111,18 @@ $csp->addSource('image', 'https://ytimg.com')
* `addDirective()`
* `disableOldBrowserSupport()`
* `enableOldBrowserSupport()`
* `hash()`
* `setDirective()`

## Inject a CSP header into a PSR-7 message

Instead of invoking `sendCSPHeader()`, you can instead inject the headers into
your PSR-7 message object by calling it like so:

```php
$csp->injectCSPHeader($yourMessageHere);
```

## Save a CSP header for configuring Apache/nginx

Instead of calling `sendCSPHeader()` on every request, you can build the CSP once
Expand Down
3 changes: 3 additions & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,5 +28,8 @@
},
"require-dev": {
"phpunit/phpunit": "4.*|5.*"
},
"suggest": {
"psr/http-message": "For CSPBuilder::injectCSPHeader()"
}
}
77 changes: 64 additions & 13 deletions src/CSPBuilder.php
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
<?php
namespace ParagonIE\CSPBuilder;

use \Psr\Http\Message\MessageInterface;

class CSPBuilder
{
const FORMAT_APACHE = 'apache';
Expand Down Expand Up @@ -215,6 +217,22 @@ public function getCompiledHeader()

/**
*
* @param type $legacy
* @return array

This comment has been minimized.

Copy link
@Ocramius

Ocramius Jan 3, 2016

Contributor

string[]

This comment has been minimized.

Copy link
@paragonie-scott

paragonie-scott Jan 3, 2016

Author Member

People really use that notation?

This comment has been minimized.

Copy link
@Ocramius

Ocramius Jan 3, 2016

Contributor

@paragonie-scott yeap. There is also <string>array, but it is really uncommon

*/
public function getHeaderArray($legacy = true)
{
if ($this->needsCompile) {
$this->compile();
}
$return = [];
foreach ($this->getHeaderKeys($legacy) as $key) {
$return[$key] = $this->compiled;
}
return $return;
}

/**
* Add a new nonce to the existing CSP
*
* @param string $directive
Expand All @@ -231,6 +249,25 @@ public function hash($directive = 'script-src', $script = '', $algo = 'sha256')
$algo => \strtr('+/', '-_', $hash)
];
}
return $this;
}

/**
* PSR-7 header injection
*
* @param \Psr\Http\Message\MessageInterface $message
* @param bool $legacy
* @return \Psr\Http\Message\MessageInterface
*/
function injectCSPHeader(MessageInterface $message, $legacy = false)
{
if ($this->needsCompile) {
$this->compile();
}
foreach ($this->getHeaderKeys($legacy) as $key) {
$message = $message->withAddedHeader($key, $this->compiled);
}
return $message;
}

/**
Expand Down Expand Up @@ -315,20 +352,10 @@ public function sendCSPHeader($legacy = true)
if ($this->needsCompile) {
$this->compile();
}
// Are we doing a report-only header?
$which = $this->reportOnly
? 'Content-Security-Policy-Report-Only'
: 'Content-Security-Policy';

\header($which.': '.$this->compiled);
if ($legacy) {
// Add deprecated headers for compatibility with old clients
\header('X-'.$which.': '.$this->compiled);
$which = $this->reportOnly
? 'X-Webkit-CSP-Report-Only'
: 'X-Webkit-CSP';
\header($which.': '.$this->compiled);
foreach ($this->getHeaderKeys($legacy) as $key) {
\header($key.': '.$this->compiled);
}
return true;
}

/**
Expand Down Expand Up @@ -418,6 +445,30 @@ protected function compileSubgroup($directive, $policies = null)
return \rtrim($ret, ' ').'; ';
}

/**
* Get an array of header keys to return
*
* @param bool $legacy
* @return array
*/
protected function getHeaderKeys($legacy = true)
{
$return = [
$this->reportOnly
? 'Content-Security-Policy-Report-Only'
: 'Content-Security-Policy'
];
if ($legacy) {
$return []= $this->reportOnly
? 'X-Content-Security-Policy-Report-Only'
: 'X-Content-Security-Policy';
$return []= $this->reportOnly
? 'X-Webkit-CSP-Report-Only'
: 'X-Webkit-CSP';
}
return $return;
}

/**
* Is this user currently connected over HTTPS?
*
Expand Down
22 changes: 21 additions & 1 deletion test/BasicTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,33 @@ public function testBasic()
$basic->getCompiledHeader()
);

$noOld = file_get_contents(__DIR__.'/vectors/basic-csp-no-old.out');
// We expect different output for ytimg.com when we disable legacy
// browser support (i.e. Safari):
$this->assertEquals(
file_get_contents(__DIR__.'/vectors/basic-csp-no-old.out'),
$noOld,
$basic
->disableOldBrowserSupport()
->getCompiledHeader()
);

$array = $basic->getHeaderArray();
$this->assertEquals(
$array,
[
'Content-Security-Policy' => $noOld,
'X-Content-Security-Policy' => $noOld,
'X-Webkit-CSP' => $noOld
]
);


$array2 = $basic->getHeaderArray(false);
$this->assertEquals(
$array2,
[
'Content-Security-Policy' => $noOld
]
);
}
}

0 comments on commit 84ad3c5

Please sign in to comment.