1 /*
2 * Copyright (C) 2010 Apple Inc. All Rights Reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 * 1. Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
12 *
13 * THIS SOFTWARE IS PROVIDED BY APPLE, INC. ``AS IS'' AND ANY
14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR
17 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 *
25 */
26
27 #include "third_party/blink/renderer/platform/weborigin/scheme_registry.h"
28
29 #include "third_party/blink/renderer/platform/wtf/allocator/allocator.h"
30 #include "third_party/blink/renderer/platform/wtf/text/string_builder.h"
31 #include "third_party/blink/renderer/platform/wtf/thread_specific.h"
32 #include "third_party/blink/renderer/platform/wtf/threading.h"
33 #include "third_party/blink/renderer/platform/wtf/threading_primitives.h"
34 #include "url/url_util.h"
35 #include "url/url_util_qt.h"
36
37 namespace blink {
38
39 namespace {
40
41 struct PolicyAreasHashTraits : HashTraits<SchemeRegistry::PolicyAreas> {
42 static const bool kEmptyValueIsZero = true;
EmptyValueblink::__anonc6f9c0480111::PolicyAreasHashTraits43 static SchemeRegistry::PolicyAreas EmptyValue() {
44 return SchemeRegistry::kPolicyAreaNone;
45 }
46 };
47
48 class URLSchemesRegistry final {
49 USING_FAST_MALLOC(URLSchemesRegistry);
50
51 public:
URLSchemesRegistry()52 URLSchemesRegistry()
53 : // For ServiceWorker schemes: HTTP is required because http://localhost
54 // is considered secure. Additional checks are performed to ensure that
55 // other http pages are filtered out.
56 service_worker_schemes({"http", "https"}),
57 fetch_api_schemes({"http", "https"}),
58 allowed_in_referrer_schemes({"http", "https"}) {
59 for (auto& scheme : url::GetLocalSchemes())
60 local_schemes.insert(scheme.c_str());
61 for (auto& scheme : url::GetSecureSchemes())
62 secure_schemes.insert(scheme.c_str());
63 for (auto& scheme : url::GetNoAccessSchemes())
64 schemes_with_unique_origins.insert(scheme.c_str());
65 for (auto& scheme : url::GetCorsEnabledSchemes())
66 cors_enabled_schemes.insert(scheme.c_str());
67 for (auto& scheme : url::GetCSPBypassingSchemes()) {
68 content_security_policy_bypassing_schemes.insert(
69 scheme.c_str(), SchemeRegistry::kPolicyAreaAll);
70 }
71 for (auto& scheme : url::GetEmptyDocumentSchemes())
72 empty_document_schemes.insert(scheme.c_str());
73
74 // NOTE(juvaldma)(Chromium 67.0.3396.47)
75 //
76 // Non-blink Chromium has it's own version of this list (see
77 // content::RegisterContentSchemes).
78 for (auto& cs : url::CustomScheme::GetSchemes()) {
79 if (cs.flags & url::CustomScheme::ServiceWorkersAllowed)
80 service_worker_schemes.insert(String(cs.name.c_str()));
81 }
82 }
83 ~URLSchemesRegistry() = default;
84
85 URLSchemesSet local_schemes;
86 URLSchemesSet display_isolated_url_schemes;
87 URLSchemesSet secure_schemes;
88 URLSchemesSet schemes_with_unique_origins;
89 URLSchemesSet empty_document_schemes;
90 URLSchemesSet schemes_forbidden_from_domain_relaxation;
91 URLSchemesSet not_allowing_javascript_urls_schemes;
92 URLSchemesSet cors_enabled_schemes;
93 URLSchemesSet service_worker_schemes;
94 URLSchemesSet fetch_api_schemes;
95 URLSchemesSet first_party_when_top_level_schemes;
96 URLSchemesMap<SchemeRegistry::PolicyAreas, PolicyAreasHashTraits>
97 content_security_policy_bypassing_schemes;
98 URLSchemesSet secure_context_bypassing_schemes;
99 URLSchemesSet allowed_in_referrer_schemes;
100 URLSchemesSet wasm_eval_csp_schemes;
101
102 private:
103 friend const URLSchemesRegistry& GetURLSchemesRegistry();
104 friend URLSchemesRegistry& GetMutableURLSchemesRegistry();
105
GetInstance()106 static URLSchemesRegistry& GetInstance() {
107 DEFINE_STATIC_LOCAL(URLSchemesRegistry, schemes, ());
108 return schemes;
109 }
110 };
111
GetURLSchemesRegistry()112 const URLSchemesRegistry& GetURLSchemesRegistry() {
113 return URLSchemesRegistry::GetInstance();
114 }
115
GetMutableURLSchemesRegistry()116 URLSchemesRegistry& GetMutableURLSchemesRegistry() {
117 #if DCHECK_IS_ON()
118 DCHECK(WTF::IsBeforeThreadCreated());
119 #endif
120 return URLSchemesRegistry::GetInstance();
121 }
122
123 } // namespace
124
RegisterURLSchemeAsLocal(const String & scheme)125 void SchemeRegistry::RegisterURLSchemeAsLocal(const String& scheme) {
126 DCHECK_EQ(scheme, scheme.LowerASCII());
127 GetMutableURLSchemesRegistry().local_schemes.insert(scheme);
128 }
129
ShouldTreatURLSchemeAsLocal(const String & scheme)130 bool SchemeRegistry::ShouldTreatURLSchemeAsLocal(const String& scheme) {
131 DCHECK_EQ(scheme, scheme.LowerASCII());
132 if (scheme.IsEmpty())
133 return false;
134 return GetURLSchemesRegistry().local_schemes.Contains(scheme);
135 }
136
ShouldTreatURLSchemeAsNoAccess(const String & scheme)137 bool SchemeRegistry::ShouldTreatURLSchemeAsNoAccess(const String& scheme) {
138 DCHECK_EQ(scheme, scheme.LowerASCII());
139 if (scheme.IsEmpty())
140 return false;
141 return GetURLSchemesRegistry().schemes_with_unique_origins.Contains(scheme);
142 }
143
RegisterURLSchemeAsDisplayIsolated(const String & scheme)144 void SchemeRegistry::RegisterURLSchemeAsDisplayIsolated(const String& scheme) {
145 DCHECK_EQ(scheme, scheme.LowerASCII());
146 GetMutableURLSchemesRegistry().display_isolated_url_schemes.insert(scheme);
147 }
148
ShouldTreatURLSchemeAsDisplayIsolated(const String & scheme)149 bool SchemeRegistry::ShouldTreatURLSchemeAsDisplayIsolated(
150 const String& scheme) {
151 DCHECK_EQ(scheme, scheme.LowerASCII());
152 if (scheme.IsEmpty())
153 return false;
154 return GetURLSchemesRegistry().display_isolated_url_schemes.Contains(scheme);
155 }
156
ShouldTreatURLSchemeAsRestrictingMixedContent(const String & scheme)157 bool SchemeRegistry::ShouldTreatURLSchemeAsRestrictingMixedContent(
158 const String& scheme) {
159 DCHECK_EQ(scheme, scheme.LowerASCII());
160 return scheme == "https";
161 }
162
RegisterURLSchemeAsSecure(const String & scheme)163 void SchemeRegistry::RegisterURLSchemeAsSecure(const String& scheme) {
164 DCHECK_EQ(scheme, scheme.LowerASCII());
165 GetMutableURLSchemesRegistry().secure_schemes.insert(scheme);
166 }
167
ShouldTreatURLSchemeAsSecure(const String & scheme)168 bool SchemeRegistry::ShouldTreatURLSchemeAsSecure(const String& scheme) {
169 DCHECK_EQ(scheme, scheme.LowerASCII());
170 if (scheme.IsEmpty())
171 return false;
172 return GetURLSchemesRegistry().secure_schemes.Contains(scheme);
173 }
174
ShouldLoadURLSchemeAsEmptyDocument(const String & scheme)175 bool SchemeRegistry::ShouldLoadURLSchemeAsEmptyDocument(const String& scheme) {
176 DCHECK_EQ(scheme, scheme.LowerASCII());
177 if (scheme.IsEmpty())
178 return false;
179 return GetURLSchemesRegistry().empty_document_schemes.Contains(scheme);
180 }
181
SetDomainRelaxationForbiddenForURLScheme(bool forbidden,const String & scheme)182 void SchemeRegistry::SetDomainRelaxationForbiddenForURLScheme(
183 bool forbidden,
184 const String& scheme) {
185 DCHECK_EQ(scheme, scheme.LowerASCII());
186 if (scheme.IsEmpty())
187 return;
188
189 if (forbidden) {
190 GetMutableURLSchemesRegistry()
191 .schemes_forbidden_from_domain_relaxation.insert(scheme);
192 } else {
193 GetMutableURLSchemesRegistry()
194 .schemes_forbidden_from_domain_relaxation.erase(scheme);
195 }
196 }
197
IsDomainRelaxationForbiddenForURLScheme(const String & scheme)198 bool SchemeRegistry::IsDomainRelaxationForbiddenForURLScheme(
199 const String& scheme) {
200 DCHECK_EQ(scheme, scheme.LowerASCII());
201 if (scheme.IsEmpty())
202 return false;
203 return GetURLSchemesRegistry()
204 .schemes_forbidden_from_domain_relaxation.Contains(scheme);
205 }
206
CanDisplayOnlyIfCanRequest(const String & scheme)207 bool SchemeRegistry::CanDisplayOnlyIfCanRequest(const String& scheme) {
208 DCHECK_EQ(scheme, scheme.LowerASCII());
209 return scheme == "blob" || scheme == "filesystem";
210 }
211
RegisterURLSchemeAsNotAllowingJavascriptURLs(const String & scheme)212 void SchemeRegistry::RegisterURLSchemeAsNotAllowingJavascriptURLs(
213 const String& scheme) {
214 DCHECK_EQ(scheme, scheme.LowerASCII());
215 GetMutableURLSchemesRegistry().not_allowing_javascript_urls_schemes.insert(
216 scheme);
217 }
218
RemoveURLSchemeAsNotAllowingJavascriptURLs(const String & scheme)219 void SchemeRegistry::RemoveURLSchemeAsNotAllowingJavascriptURLs(
220 const String& scheme) {
221 GetMutableURLSchemesRegistry().not_allowing_javascript_urls_schemes.erase(
222 scheme);
223 }
224
ShouldTreatURLSchemeAsNotAllowingJavascriptURLs(const String & scheme)225 bool SchemeRegistry::ShouldTreatURLSchemeAsNotAllowingJavascriptURLs(
226 const String& scheme) {
227 DCHECK_EQ(scheme, scheme.LowerASCII());
228 if (scheme.IsEmpty())
229 return false;
230 return GetURLSchemesRegistry().not_allowing_javascript_urls_schemes.Contains(
231 scheme);
232 }
233
ShouldTreatURLSchemeAsCorsEnabled(const String & scheme)234 bool SchemeRegistry::ShouldTreatURLSchemeAsCorsEnabled(const String& scheme) {
235 DCHECK_EQ(scheme, scheme.LowerASCII());
236 if (scheme.IsEmpty())
237 return false;
238 return GetURLSchemesRegistry().cors_enabled_schemes.Contains(scheme);
239 }
240
ListOfCorsEnabledURLSchemes()241 String SchemeRegistry::ListOfCorsEnabledURLSchemes() {
242 StringBuilder builder;
243 bool add_separator = false;
244 for (const auto& scheme : GetURLSchemesRegistry().cors_enabled_schemes) {
245 if (add_separator)
246 builder.Append(", ");
247 else
248 add_separator = true;
249
250 builder.Append(scheme);
251 }
252 return builder.ToString();
253 }
254
ShouldTreatURLSchemeAsLegacy(const String & scheme)255 bool SchemeRegistry::ShouldTreatURLSchemeAsLegacy(const String& scheme) {
256 return scheme == "ftp";
257 }
258
ShouldTrackUsageMetricsForScheme(const String & scheme)259 bool SchemeRegistry::ShouldTrackUsageMetricsForScheme(const String& scheme) {
260 // This SchemeRegistry is primarily used by Blink UseCounter, which aims to
261 // match the tracking policy of page_load_metrics (see
262 // pageTrackDecider::ShouldTrack() for more details).
263 // The scheme represents content which likely cannot be easily updated.
264 // Specifically this includes internal pages such as about, devtools,
265 // etc.
266 // "chrome-extension" is not included because they have a single deployment
267 // point (the webstore) and are designed specifically for Chrome.
268 // "data" is not included because real sites shouldn't be using it for
269 // top-level pages and Chrome does use it internally (eg. PluginPlaceholder).
270 // "file" is not included because file:// navigations have different loading
271 // behaviors.
272 return scheme == "http" || scheme == "https";
273 }
274
RegisterURLSchemeAsAllowingServiceWorkers(const String & scheme)275 void SchemeRegistry::RegisterURLSchemeAsAllowingServiceWorkers(
276 const String& scheme) {
277 DCHECK_EQ(scheme, scheme.LowerASCII());
278 GetMutableURLSchemesRegistry().service_worker_schemes.insert(scheme);
279 }
280
ShouldTreatURLSchemeAsAllowingServiceWorkers(const String & scheme)281 bool SchemeRegistry::ShouldTreatURLSchemeAsAllowingServiceWorkers(
282 const String& scheme) {
283 DCHECK_EQ(scheme, scheme.LowerASCII());
284 if (scheme.IsEmpty())
285 return false;
286 return GetURLSchemesRegistry().service_worker_schemes.Contains(scheme);
287 }
288
RegisterURLSchemeAsSupportingFetchAPI(const String & scheme)289 void SchemeRegistry::RegisterURLSchemeAsSupportingFetchAPI(
290 const String& scheme) {
291 DCHECK_EQ(scheme, scheme.LowerASCII());
292 GetMutableURLSchemesRegistry().fetch_api_schemes.insert(scheme);
293 }
294
ShouldTreatURLSchemeAsSupportingFetchAPI(const String & scheme)295 bool SchemeRegistry::ShouldTreatURLSchemeAsSupportingFetchAPI(
296 const String& scheme) {
297 DCHECK_EQ(scheme, scheme.LowerASCII());
298 if (scheme.IsEmpty())
299 return false;
300 return GetURLSchemesRegistry().fetch_api_schemes.Contains(scheme);
301 }
302
303 // https://fetch.spec.whatwg.org/#fetch-scheme
IsFetchScheme(const String & scheme)304 bool SchemeRegistry::IsFetchScheme(const String& scheme) {
305 DCHECK_EQ(scheme, scheme.LowerASCII());
306 // "A fetch scheme is a scheme that is "about", "blob", "data", "file",
307 // "filesystem", or a network scheme." [spec text]
308 return scheme == "about" || scheme == "blob" || scheme == "data" ||
309 scheme == "file" || scheme == "filesystem" || scheme == "ftp" ||
310 scheme == "http" || scheme == "https";
311 }
312
RegisterURLSchemeAsFirstPartyWhenTopLevel(const String & scheme)313 void SchemeRegistry::RegisterURLSchemeAsFirstPartyWhenTopLevel(
314 const String& scheme) {
315 DCHECK_EQ(scheme, scheme.LowerASCII());
316 GetMutableURLSchemesRegistry().first_party_when_top_level_schemes.insert(
317 scheme);
318 }
319
RemoveURLSchemeAsFirstPartyWhenTopLevel(const String & scheme)320 void SchemeRegistry::RemoveURLSchemeAsFirstPartyWhenTopLevel(
321 const String& scheme) {
322 DCHECK_EQ(scheme, scheme.LowerASCII());
323 GetMutableURLSchemesRegistry().first_party_when_top_level_schemes.erase(
324 scheme);
325 }
326
ShouldTreatURLSchemeAsFirstPartyWhenTopLevel(const String & scheme)327 bool SchemeRegistry::ShouldTreatURLSchemeAsFirstPartyWhenTopLevel(
328 const String& scheme) {
329 DCHECK_EQ(scheme, scheme.LowerASCII());
330 if (scheme.IsEmpty())
331 return false;
332 return GetURLSchemesRegistry().first_party_when_top_level_schemes.Contains(
333 scheme);
334 }
335
RegisterURLSchemeAsAllowedForReferrer(const String & scheme)336 void SchemeRegistry::RegisterURLSchemeAsAllowedForReferrer(
337 const String& scheme) {
338 DCHECK_EQ(scheme, scheme.LowerASCII());
339 GetMutableURLSchemesRegistry().allowed_in_referrer_schemes.insert(scheme);
340 }
341
RemoveURLSchemeAsAllowedForReferrer(const String & scheme)342 void SchemeRegistry::RemoveURLSchemeAsAllowedForReferrer(const String& scheme) {
343 GetMutableURLSchemesRegistry().allowed_in_referrer_schemes.erase(scheme);
344 }
345
ShouldTreatURLSchemeAsAllowedForReferrer(const String & scheme)346 bool SchemeRegistry::ShouldTreatURLSchemeAsAllowedForReferrer(
347 const String& scheme) {
348 DCHECK_EQ(scheme, scheme.LowerASCII());
349 if (scheme.IsEmpty())
350 return false;
351 return GetURLSchemesRegistry().allowed_in_referrer_schemes.Contains(scheme);
352 }
353
RegisterURLSchemeAsBypassingContentSecurityPolicy(const String & scheme,PolicyAreas policy_areas)354 void SchemeRegistry::RegisterURLSchemeAsBypassingContentSecurityPolicy(
355 const String& scheme,
356 PolicyAreas policy_areas) {
357 DCHECK_EQ(scheme, scheme.LowerASCII());
358 GetMutableURLSchemesRegistry()
359 .content_security_policy_bypassing_schemes.insert(scheme, policy_areas);
360 }
361
RemoveURLSchemeRegisteredAsBypassingContentSecurityPolicy(const String & scheme)362 void SchemeRegistry::RemoveURLSchemeRegisteredAsBypassingContentSecurityPolicy(
363 const String& scheme) {
364 DCHECK_EQ(scheme, scheme.LowerASCII());
365 GetMutableURLSchemesRegistry()
366 .content_security_policy_bypassing_schemes.erase(scheme);
367 }
368
SchemeShouldBypassContentSecurityPolicy(const String & scheme,PolicyAreas policy_areas)369 bool SchemeRegistry::SchemeShouldBypassContentSecurityPolicy(
370 const String& scheme,
371 PolicyAreas policy_areas) {
372 DCHECK_NE(policy_areas, kPolicyAreaNone);
373 if (scheme.IsEmpty() || policy_areas == kPolicyAreaNone)
374 return false;
375
376 // get() returns 0 (PolicyAreaNone) if there is no entry in the map.
377 // Thus by default, schemes do not bypass CSP.
378 return (GetURLSchemesRegistry().content_security_policy_bypassing_schemes.at(
379 scheme) &
380 policy_areas) == policy_areas;
381 }
382
RegisterURLSchemeBypassingSecureContextCheck(const String & scheme)383 void SchemeRegistry::RegisterURLSchemeBypassingSecureContextCheck(
384 const String& scheme) {
385 DCHECK_EQ(scheme, scheme.LowerASCII());
386 GetMutableURLSchemesRegistry().secure_context_bypassing_schemes.insert(
387 scheme);
388 }
389
SchemeShouldBypassSecureContextCheck(const String & scheme)390 bool SchemeRegistry::SchemeShouldBypassSecureContextCheck(
391 const String& scheme) {
392 if (scheme.IsEmpty())
393 return false;
394 DCHECK_EQ(scheme, scheme.LowerASCII());
395 return GetURLSchemesRegistry().secure_context_bypassing_schemes.Contains(
396 scheme);
397 }
398
RegisterURLSchemeAsAllowingWasmEvalCSP(const String & scheme)399 void SchemeRegistry::RegisterURLSchemeAsAllowingWasmEvalCSP(
400 const String& scheme) {
401 DCHECK_EQ(scheme, scheme.LowerASCII());
402 GetMutableURLSchemesRegistry().wasm_eval_csp_schemes.insert(scheme);
403 }
404
SchemeSupportsWasmEvalCSP(const String & scheme)405 bool SchemeRegistry::SchemeSupportsWasmEvalCSP(const String& scheme) {
406 if (scheme.IsEmpty())
407 return false;
408 DCHECK_EQ(scheme, scheme.LowerASCII());
409 return GetURLSchemesRegistry().wasm_eval_csp_schemes.Contains(scheme);
410 }
411
412 } // namespace blink
413