Skip to content
This repository was archived by the owner on Jan 30, 2020. It is now read-only.

Commit 9570850

Browse files
committed
4 parents e1f29e3 + 5c10b75 + 9f15230 + 36d537b commit 9570850

File tree

2 files changed

+242
-0
lines changed

2 files changed

+242
-0
lines changed

src/Header/ContentSecurityPolicy.php

Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
<?php
2+
/**
3+
* Zend Framework (http://framework.zend.com/)
4+
*
5+
* @link http://github.com/zendframework/zf2 for the canonical source repository
6+
* @copyright Copyright (c) 2005-2013 Zend Technologies USA Inc. (http://www.zend.com)
7+
* @license http://framework.zend.com/license/new-bsd New BSD License
8+
*/
9+
10+
namespace Zend\Http\Header;
11+
12+
/**
13+
* Content Security Policy 1.0 Header
14+
*
15+
* @link http://www.w3.org/TR/CSP/
16+
*/
17+
class ContentSecurityPolicy implements HeaderInterface
18+
{
19+
/**
20+
* Valid directive names
21+
*
22+
* @var array
23+
*/
24+
protected $validDirectiveNames = array(
25+
// As per http://www.w3.org/TR/CSP/#directives
26+
'default-src',
27+
'script-src',
28+
'object-src',
29+
'style-src',
30+
'img-src',
31+
'media-src',
32+
'frame-src',
33+
'font-src',
34+
'connect-src',
35+
'sandbox',
36+
'report-uri',
37+
);
38+
39+
/**
40+
* The directives defined for this policy
41+
*
42+
* @var array
43+
*/
44+
protected $directives = array();
45+
46+
/**
47+
* Get the list of defined directives
48+
*
49+
* @return array
50+
*/
51+
public function getDirectives()
52+
{
53+
return $this->directives;
54+
}
55+
56+
/**
57+
* Sets the directive to consist of the source list
58+
*
59+
* Reverses http://www.w3.org/TR/CSP/#parsing-1
60+
*
61+
* @param string $name The directive name.
62+
* @param array $sources The source list.
63+
* @return self
64+
* @throws Exception\InvalidArgumentException If the name is not a valid directive name.
65+
*/
66+
public function setDirective($name, array $sources)
67+
{
68+
if (!in_array($name, $this->validDirectiveNames, true)) {
69+
throw new Exception\InvalidArgumentException(sprintf(
70+
'%s expects a valid directive name; received "%s"',
71+
__METHOD__,
72+
(string) $name
73+
));
74+
}
75+
if (empty($sources)) {
76+
$this->directives[$name] = "'none'";
77+
} else {
78+
$this->directives[$name] = implode(' ', $sources);
79+
}
80+
return $this;
81+
}
82+
83+
/**
84+
* Create Content Security Policy header from a given header line
85+
*
86+
* @param string $headerLine The header line to parse.
87+
* @return self
88+
* @throws Exception\InvalidArgumentException If the name field in the given header line does not match.
89+
*/
90+
public static function fromString($headerLine)
91+
{
92+
$header = new static();
93+
$headerName = $header->getFieldName();
94+
list($name, $value) = GenericHeader::splitHeaderLine($headerLine);
95+
// Ensure the proper header name
96+
if (strcasecmp($name, $headerName) != 0) {
97+
throw new Exception\InvalidArgumentException(sprintf(
98+
'Invalid header line for %s string: "%s"',
99+
$headerName,
100+
$name
101+
));
102+
}
103+
// As per http://www.w3.org/TR/CSP/#parsing
104+
$tokens = explode(';', $value);
105+
foreach ($tokens as $token) {
106+
$token = trim($token);
107+
if ($token) {
108+
list($directiveName, $directiveValue) = explode(' ', $token, 2);
109+
if (!isset($header->directives[$directiveName])) {
110+
$header->directives[$directiveName] = $directiveValue;
111+
}
112+
}
113+
}
114+
return $header;
115+
}
116+
117+
/**
118+
* Get the header name
119+
*
120+
* @return string
121+
*/
122+
public function getFieldName()
123+
{
124+
return 'Content-Security-Policy';
125+
}
126+
127+
/**
128+
* Get the header value
129+
*
130+
* @return string
131+
*/
132+
public function getFieldValue()
133+
{
134+
$directives = array();
135+
foreach ($this->directives as $name => $value) {
136+
$directives[] = sprintf('%s %s;', $name, $value);
137+
}
138+
return implode(' ', $directives);
139+
}
140+
141+
/**
142+
* Return the header as a string
143+
*
144+
* @return string
145+
*/
146+
public function toString()
147+
{
148+
return sprintf('%s: %s', $this->getFieldName(), $this->getFieldValue());
149+
}
150+
}
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
<?php
2+
/**
3+
* Zend Framework (http://framework.zend.com/)
4+
*
5+
* @link http://github.com/zendframework/zf2 for the canonical source repository
6+
* @copyright Copyright (c) 2005-2013 Zend Technologies USA Inc. (http://www.zend.com)
7+
* @license http://framework.zend.com/license/new-bsd New BSD License
8+
*/
9+
10+
namespace ZendTest\Http\Header;
11+
12+
use Zend\Http\Header\ContentSecurityPolicy;
13+
14+
class ContentSecurityPolicyTest extends \PHPUnit_Framework_TestCase
15+
{
16+
public function testContentSecurityPolicyFromStringThrowsExceptionIfImproperHeaderNameUsed()
17+
{
18+
$this->setExpectedException('Zend\Http\Header\Exception\InvalidArgumentException');
19+
ContentSecurityPolicy::fromString('X-Content-Security-Policy: default-src *;');
20+
}
21+
22+
public function testContentSecurityPolicyFromStringParsesDirectivesCorrectly()
23+
{
24+
$csp = ContentSecurityPolicy::fromString(
25+
"Content-Security-Policy: default-src 'none'; script-src 'self'; img-src 'self'; style-src 'self';"
26+
);
27+
$this->assertInstanceOf('Zend\Http\Header\HeaderInterface', $csp);
28+
$this->assertInstanceOf('Zend\Http\Header\ContentSecurityPolicy', $csp);
29+
$directives = array('default-src' => "'none'",
30+
'script-src' => "'self'",
31+
'img-src' => "'self'",
32+
'style-src' => "'self'");
33+
$this->assertEquals($directives, $csp->getDirectives());
34+
}
35+
36+
public function testContentSecurityPolicyGetFieldNameReturnsHeaderName()
37+
{
38+
$csp = new ContentSecurityPolicy();
39+
$this->assertEquals('Content-Security-Policy', $csp->getFieldName());
40+
}
41+
42+
public function testContentSecurityPolicyToStringReturnsHeaderFormattedString()
43+
{
44+
$csp = ContentSecurityPolicy::fromString(
45+
"Content-Security-Policy: default-src 'none'; img-src 'self' https://*.gravatar.com;"
46+
);
47+
$this->assertInstanceOf('Zend\Http\Header\HeaderInterface', $csp);
48+
$this->assertInstanceOf('Zend\Http\Header\ContentSecurityPolicy', $csp);
49+
$this->assertEquals(
50+
"Content-Security-Policy: default-src 'none'; img-src 'self' https://*.gravatar.com;",
51+
$csp->toString()
52+
);
53+
}
54+
55+
public function testContentSecurityPolicySetDirective()
56+
{
57+
$csp = new ContentSecurityPolicy();
58+
$csp->setDirective('default-src', array('https://*.google.com', 'http://foo.com'))
59+
->setDirective('img-src', array("'self'"))
60+
->setDirective('script-src', array('https://*.googleapis.com', 'https://*.bar.com'));
61+
$header = "Content-Security-Policy: default-src https://*.google.com http://foo.com; "
62+
. "img-src 'self'; script-src https://*.googleapis.com https://*.bar.com;";
63+
$this->assertEquals($header, $csp->toString());
64+
}
65+
66+
public function testContentSecurityPolicySetDirectiveWithEmptySourcesDefaultsToNone()
67+
{
68+
$csp = new ContentSecurityPolicy();
69+
$csp->setDirective('default-src', array("'self'"))
70+
->setDirective('img-src', array('*'))
71+
->setDirective('script-src', array());
72+
$this->assertEquals(
73+
"Content-Security-Policy: default-src 'self'; img-src *; script-src 'none';",
74+
$csp->toString()
75+
);
76+
}
77+
78+
public function testContentSecurityPolicySetDirectiveThrowsExceptionIfInvalidDirectiveNameGiven()
79+
{
80+
$this->setExpectedException('Zend\Http\Header\Exception\InvalidArgumentException');
81+
$csp = new ContentSecurityPolicy();
82+
$csp->setDirective('foo', array());
83+
}
84+
85+
public function testContentSecurityPolicyGetFieldValueReturnsProperValue()
86+
{
87+
$csp = new ContentSecurityPolicy();
88+
$csp->setDirective('default-src', array("'self'"))
89+
->setDirective('img-src', array('https://*.github.com'));
90+
$this->assertEquals("default-src 'self'; img-src https://*.github.com;", $csp->getFieldValue());
91+
}
92+
}

0 commit comments

Comments
 (0)