Upstream version 7.36.149.0
[platform/framework/web/crosswalk.git] / src / extensions / common / csp_validator.cc
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.
4
5 #include "extensions/common/csp_validator.h"
6
7 #include <vector>
8
9 #include "base/strings/string_split.h"
10 #include "base/strings/string_tokenizer.h"
11 #include "base/strings/string_util.h"
12
13 namespace extensions {
14
15 namespace csp_validator {
16
17 namespace {
18
19 const char kDefaultSrc[] = "default-src";
20 const char kScriptSrc[] = "script-src";
21 const char kObjectSrc[] = "object-src";
22
23 const char kSandboxDirectiveName[] = "sandbox";
24 const char kAllowSameOriginToken[] = "allow-same-origin";
25 const char kAllowTopNavigation[] = "allow-top-navigation";
26
27 struct DirectiveStatus {
28   explicit DirectiveStatus(const char* name)
29     : directive_name(name)
30     , seen_in_policy(false)
31     , is_secure(false) {
32   }
33
34   const char* directive_name;
35   bool seen_in_policy;
36   bool is_secure;
37 };
38
39 bool HasOnlySecureTokens(base::StringTokenizer& tokenizer,
40                          Manifest::Type type) {
41   while (tokenizer.GetNext()) {
42     std::string source = tokenizer.token();
43     StringToLowerASCII(&source);
44
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 ':'
48     int wildcards = 0;
49     size_t length = source.length();
50     for (size_t i = 0; i < length; ++i) {
51       if (source[i] == L'*') {
52         wildcards++;
53         if (wildcards > 2)
54           return false;
55
56         bool isWildcardPort = i > 0 && source[i - 1] == L':';
57         bool isWildcardSubdomain = i + 1 < length && source[i + 1] == L'.';
58         if (!isWildcardPort && !isWildcardSubdomain)
59           return false;
60       }
61     }
62
63     // We might need to relax this whitelist over time.
64     if (source == "'self'" ||
65         source == "'none'" ||
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)) {
76       continue;
77     }
78
79     // crbug.com/146487
80     if (type == Manifest::TYPE_EXTENSION ||
81         type == Manifest::TYPE_LEGACY_PACKAGED_APP) {
82       if (source == "'unsafe-eval'")
83         continue;
84     }
85
86     return false;
87   }
88
89   return true;  // Empty values default to 'none', which is secure.
90 }
91
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)
98     return false;
99   if (directive_name != status->directive_name)
100     return false;
101   status->seen_in_policy = true;
102   status->is_secure = HasOnlySecureTokens(tokenizer, type);
103   return true;
104 }
105
106 }  //  namespace
107
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'};
112
113   return policy.find_first_of(kBadChars, 0, arraysize(kBadChars)) ==
114       std::string::npos;
115 }
116
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);
122
123   DirectiveStatus default_src_status(kDefaultSrc);
124   DirectiveStatus script_src_status(kScriptSrc);
125   DirectiveStatus object_src_status(kObjectSrc);
126
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())
131       continue;
132
133     std::string directive_name = tokenizer.token();
134     StringToLowerASCII(&directive_name);
135
136     if (UpdateStatus(directive_name, tokenizer, &default_src_status, type))
137       continue;
138     if (UpdateStatus(directive_name, tokenizer, &script_src_status, type))
139       continue;
140     if (UpdateStatus(directive_name, tokenizer, &object_src_status, type))
141       continue;
142   }
143
144   if (script_src_status.seen_in_policy && !script_src_status.is_secure)
145     return false;
146
147   if (object_src_status.seen_in_policy && !object_src_status.is_secure)
148     return false;
149
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;
153   }
154
155   return default_src_status.seen_in_policy ||
156       (script_src_status.seen_in_policy && object_src_status.seen_in_policy);
157 }
158
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);
164
165   bool seen_sandbox = false;
166
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())
171       continue;
172
173     std::string directive_name = tokenizer.token();
174     StringToLowerASCII(&directive_name);
175
176     if (directive_name != kSandboxDirectiveName)
177       continue;
178
179     seen_sandbox = true;
180
181     while (tokenizer.GetNext()) {
182       std::string token = tokenizer.token();
183       StringToLowerASCII(&token);
184
185       // The same origin token negates the sandboxing.
186       if (token == kAllowSameOriginToken)
187         return false;
188
189       // Platform apps don't allow navigation.
190       if (type == Manifest::TYPE_PLATFORM_APP) {
191         if (token == kAllowTopNavigation)
192           return false;
193       }
194     }
195   }
196
197   return seen_sandbox;
198 }
199
200 }  // namespace csp_validator
201
202 }  // namespace extensions