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