1 // Copyright 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "extensions/common/csp_validator.h"
9 #include "base/strings/string_split.h"
10 #include "base/strings/string_tokenizer.h"
11 #include "base/strings/string_util.h"
13 namespace extensions
{
15 namespace csp_validator
{
19 const char kDefaultSrc
[] = "default-src";
20 const char kScriptSrc
[] = "script-src";
21 const char kObjectSrc
[] = "object-src";
23 const char kSandboxDirectiveName
[] = "sandbox";
24 const char kAllowSameOriginToken
[] = "allow-same-origin";
25 const char kAllowTopNavigation
[] = "allow-top-navigation";
27 struct DirectiveStatus
{
28 explicit DirectiveStatus(const char* name
)
29 : directive_name(name
)
30 , seen_in_policy(false)
34 const char* directive_name
;
39 bool HasOnlySecureTokens(base::StringTokenizer
& tokenizer
,
40 Manifest::Type type
) {
41 while (tokenizer
.GetNext()) {
42 std::string source
= tokenizer
.token();
43 StringToLowerASCII(&source
);
45 // Don't alow whitelisting of all hosts. This boils down to:
46 // 1. Maximum of 2 '*' characters.
47 // 2. Each '*' is either followed by a '.' or preceded by a ':'
49 size_t length
= source
.length();
50 for (size_t i
= 0; i
< length
; ++i
) {
51 if (source
[i
] == L
'*') {
56 bool isWildcardPort
= i
> 0 && source
[i
- 1] == L
':';
57 bool isWildcardSubdomain
= i
+ 1 < length
&& source
[i
+ 1] == L
'.';
58 if (!isWildcardPort
&& !isWildcardSubdomain
)
63 // We might need to relax this whitelist over time.
64 if (source
== "'self'" ||
66 source
== "http://127.0.0.1" ||
67 LowerCaseEqualsASCII(source
, "blob:") ||
68 LowerCaseEqualsASCII(source
, "filesystem:") ||
69 LowerCaseEqualsASCII(source
, "http://localhost") ||
70 StartsWithASCII(source
, "http://127.0.0.1:", false) ||
71 StartsWithASCII(source
, "http://localhost:", false) ||
72 StartsWithASCII(source
, "https://", true) ||
73 StartsWithASCII(source
, "chrome://", true) ||
74 StartsWithASCII(source
, "chrome-extension://", true) ||
75 StartsWithASCII(source
, "chrome-extension-resource:", true)) {
80 if (type
== Manifest::TYPE_EXTENSION
||
81 type
== Manifest::TYPE_LEGACY_PACKAGED_APP
) {
82 if (source
== "'unsafe-eval'")
89 return true; // Empty values default to 'none', which is secure.
92 // Returns true if |directive_name| matches |status.directive_name|.
93 bool UpdateStatus(const std::string
& directive_name
,
94 base::StringTokenizer
& tokenizer
,
95 DirectiveStatus
* status
,
96 Manifest::Type type
) {
97 if (status
->seen_in_policy
)
99 if (directive_name
!= status
->directive_name
)
101 status
->seen_in_policy
= true;
102 status
->is_secure
= HasOnlySecureTokens(tokenizer
, type
);
108 bool ContentSecurityPolicyIsLegal(const std::string
& policy
) {
109 // We block these characters to prevent HTTP header injection when
110 // representing the content security policy as an HTTP header.
111 const char kBadChars
[] = {',', '\r', '\n', '\0'};
113 return policy
.find_first_of(kBadChars
, 0, arraysize(kBadChars
)) ==
117 bool ContentSecurityPolicyIsSecure(const std::string
& policy
,
118 Manifest::Type type
) {
119 // See http://www.w3.org/TR/CSP/#parse-a-csp-policy for parsing algorithm.
120 std::vector
<std::string
> directives
;
121 base::SplitString(policy
, ';', &directives
);
123 DirectiveStatus
default_src_status(kDefaultSrc
);
124 DirectiveStatus
script_src_status(kScriptSrc
);
125 DirectiveStatus
object_src_status(kObjectSrc
);
127 for (size_t i
= 0; i
< directives
.size(); ++i
) {
128 std::string
& input
= directives
[i
];
129 base::StringTokenizer
tokenizer(input
, " \t\r\n");
130 if (!tokenizer
.GetNext())
133 std::string directive_name
= tokenizer
.token();
134 StringToLowerASCII(&directive_name
);
136 if (UpdateStatus(directive_name
, tokenizer
, &default_src_status
, type
))
138 if (UpdateStatus(directive_name
, tokenizer
, &script_src_status
, type
))
140 if (UpdateStatus(directive_name
, tokenizer
, &object_src_status
, type
))
144 if (script_src_status
.seen_in_policy
&& !script_src_status
.is_secure
)
147 if (object_src_status
.seen_in_policy
&& !object_src_status
.is_secure
)
150 if (default_src_status
.seen_in_policy
&& !default_src_status
.is_secure
) {
151 return script_src_status
.seen_in_policy
&&
152 object_src_status
.seen_in_policy
;
155 return default_src_status
.seen_in_policy
||
156 (script_src_status
.seen_in_policy
&& object_src_status
.seen_in_policy
);
159 bool ContentSecurityPolicyIsSandboxed(
160 const std::string
& policy
, Manifest::Type type
) {
161 // See http://www.w3.org/TR/CSP/#parse-a-csp-policy for parsing algorithm.
162 std::vector
<std::string
> directives
;
163 base::SplitString(policy
, ';', &directives
);
165 bool seen_sandbox
= false;
167 for (size_t i
= 0; i
< directives
.size(); ++i
) {
168 std::string
& input
= directives
[i
];
169 base::StringTokenizer
tokenizer(input
, " \t\r\n");
170 if (!tokenizer
.GetNext())
173 std::string directive_name
= tokenizer
.token();
174 StringToLowerASCII(&directive_name
);
176 if (directive_name
!= kSandboxDirectiveName
)
181 while (tokenizer
.GetNext()) {
182 std::string token
= tokenizer
.token();
183 StringToLowerASCII(&token
);
185 // The same origin token negates the sandboxing.
186 if (token
== kAllowSameOriginToken
)
189 // Platform apps don't allow navigation.
190 if (type
== Manifest::TYPE_PLATFORM_APP
) {
191 if (token
== kAllowTopNavigation
)
200 } // namespace csp_validator
202 } // namespace extensions