1 | /* |
2 | * Copyright (C) 2011 Google 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 | * |
8 | * 1. Redistributions of source code must retain the above copyright |
9 | * notice, this list of conditions and the following disclaimer. |
10 | * 2. Redistributions in binary form must reproduce the above copyright |
11 | * notice, this list of conditions and the following disclaimer in the |
12 | * documentation and/or other materials provided with the distribution. |
13 | * 3. Neither the name of Google, Inc. ("Google") nor the names of |
14 | * its contributors may be used to endorse or promote products derived |
15 | * from this software without specific prior written permission. |
16 | * |
17 | * THIS SOFTWARE IS PROVIDED BY GOOGLE AND ITS CONTRIBUTORS "AS IS" AND ANY |
18 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
19 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
20 | * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY |
21 | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES |
22 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
23 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND |
24 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
25 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF |
26 | * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
27 | */ |
28 | |
29 | #include "config.h" |
30 | #include "SecurityPolicy.h" |
31 | |
32 | #include "OriginAccessEntry.h" |
33 | #include "SecurityOrigin.h" |
34 | #include <memory> |
35 | #include <wtf/HashMap.h> |
36 | #include <wtf/MainThread.h> |
37 | #include <wtf/NeverDestroyed.h> |
38 | #include <wtf/URL.h> |
39 | #include <wtf/text/StringHash.h> |
40 | |
41 | namespace WebCore { |
42 | |
43 | static SecurityPolicy::LocalLoadPolicy localLoadPolicy = SecurityPolicy::AllowLocalLoadsForLocalOnly; |
44 | |
45 | typedef Vector<OriginAccessEntry> OriginAccessWhiteList; |
46 | typedef HashMap<String, std::unique_ptr<OriginAccessWhiteList>> OriginAccessMap; |
47 | |
48 | static Lock originAccessMapLock; |
49 | static OriginAccessMap& originAccessMap() |
50 | { |
51 | ASSERT(originAccessMapLock.isHeld()); |
52 | static NeverDestroyed<OriginAccessMap> originAccessMap; |
53 | return originAccessMap; |
54 | } |
55 | |
56 | bool SecurityPolicy::shouldHideReferrer(const URL& url, const String& referrer) |
57 | { |
58 | bool referrerIsSecureURL = protocolIs(referrer, "https" ); |
59 | bool referrerIsWebURL = referrerIsSecureURL || protocolIs(referrer, "http" ); |
60 | |
61 | if (!referrerIsWebURL) |
62 | return true; |
63 | |
64 | if (!referrerIsSecureURL) |
65 | return false; |
66 | |
67 | bool URLIsSecureURL = url.protocolIs("https" ); |
68 | |
69 | return !URLIsSecureURL; |
70 | } |
71 | |
72 | String SecurityPolicy::referrerToOriginString(const String& referrer) |
73 | { |
74 | String originString = SecurityOrigin::createFromString(referrer)->toString(); |
75 | if (originString == "null" ) |
76 | return String(); |
77 | // A security origin is not a canonical URL as it lacks a path. Add / |
78 | // to turn it into a canonical URL we can use as referrer. |
79 | return originString + "/" ; |
80 | } |
81 | |
82 | String SecurityPolicy::(ReferrerPolicy referrerPolicy, const URL& url, const String& referrer) |
83 | { |
84 | ASSERT(referrer == URL(URL(), referrer).strippedForUseAsReferrer()); |
85 | |
86 | if (referrer.isEmpty()) |
87 | return String(); |
88 | |
89 | if (!protocolIsInHTTPFamily(referrer)) |
90 | return String(); |
91 | |
92 | switch (referrerPolicy) { |
93 | case ReferrerPolicy::EmptyString: |
94 | ASSERT_NOT_REACHED(); |
95 | break; |
96 | case ReferrerPolicy::NoReferrer: |
97 | return String(); |
98 | case ReferrerPolicy::NoReferrerWhenDowngrade: |
99 | break; |
100 | case ReferrerPolicy::SameOrigin: { |
101 | auto origin = SecurityOrigin::createFromString(referrer); |
102 | if (!origin->canRequest(url)) |
103 | return String(); |
104 | break; |
105 | } |
106 | case ReferrerPolicy::Origin: |
107 | return referrerToOriginString(referrer); |
108 | case ReferrerPolicy::StrictOrigin: |
109 | if (shouldHideReferrer(url, referrer)) |
110 | return String(); |
111 | return referrerToOriginString(referrer); |
112 | case ReferrerPolicy::OriginWhenCrossOrigin: { |
113 | auto origin = SecurityOrigin::createFromString(referrer); |
114 | if (!origin->canRequest(url)) |
115 | return referrerToOriginString(referrer); |
116 | break; |
117 | } |
118 | case ReferrerPolicy::StrictOriginWhenCrossOrigin: { |
119 | auto origin = SecurityOrigin::createFromString(referrer); |
120 | if (!origin->canRequest(url)) { |
121 | if (shouldHideReferrer(url, referrer)) |
122 | return String(); |
123 | return referrerToOriginString(referrer); |
124 | } |
125 | break; |
126 | } |
127 | case ReferrerPolicy::UnsafeUrl: |
128 | return referrer; |
129 | } |
130 | |
131 | return shouldHideReferrer(url, referrer) ? String() : referrer; |
132 | } |
133 | |
134 | bool SecurityPolicy::shouldInheritSecurityOriginFromOwner(const URL& url) |
135 | { |
136 | // Paraphrased from <https://html.spec.whatwg.org/multipage/browsers.html#origin> (8 July 2016) |
137 | // |
138 | // If a Document has the address "about:blank" |
139 | // The origin of the document is the origin it was assigned when its browsing context was created. |
140 | // If a Document has the address "about:srcdoc" |
141 | // The origin of the document is the origin of its parent document. |
142 | // |
143 | // Note: We generalize this to invalid URLs because we treat such URLs as about:blank. |
144 | // |
145 | return url.isEmpty() || equalIgnoringASCIICase(url.string(), WTF::blankURL()) || equalLettersIgnoringASCIICase(url.string(), "about:srcdoc" ); |
146 | } |
147 | |
148 | void SecurityPolicy::setLocalLoadPolicy(LocalLoadPolicy policy) |
149 | { |
150 | localLoadPolicy = policy; |
151 | } |
152 | |
153 | bool SecurityPolicy::restrictAccessToLocal() |
154 | { |
155 | return localLoadPolicy != SecurityPolicy::AllowLocalLoadsForAll; |
156 | } |
157 | |
158 | bool SecurityPolicy::allowSubstituteDataAccessToLocal() |
159 | { |
160 | return localLoadPolicy != SecurityPolicy::AllowLocalLoadsForLocalOnly; |
161 | } |
162 | |
163 | bool SecurityPolicy::isAccessWhiteListed(const SecurityOrigin* activeOrigin, const SecurityOrigin* targetOrigin) |
164 | { |
165 | Locker<Lock> locker(originAccessMapLock); |
166 | if (OriginAccessWhiteList* list = originAccessMap().get(activeOrigin->toString())) { |
167 | for (auto& entry : *list) { |
168 | if (entry.matchesOrigin(*targetOrigin)) |
169 | return true; |
170 | } |
171 | } |
172 | return false; |
173 | } |
174 | |
175 | bool SecurityPolicy::isAccessToURLWhiteListed(const SecurityOrigin* activeOrigin, const URL& url) |
176 | { |
177 | Ref<SecurityOrigin> targetOrigin(SecurityOrigin::create(url)); |
178 | return isAccessWhiteListed(activeOrigin, &targetOrigin.get()); |
179 | } |
180 | |
181 | void SecurityPolicy::addOriginAccessWhitelistEntry(const SecurityOrigin& sourceOrigin, const String& destinationProtocol, const String& destinationDomain, bool allowDestinationSubdomains) |
182 | { |
183 | ASSERT(!sourceOrigin.isUnique()); |
184 | if (sourceOrigin.isUnique()) |
185 | return; |
186 | |
187 | String sourceString = sourceOrigin.toString(); |
188 | |
189 | Locker<Lock> locker(originAccessMapLock); |
190 | OriginAccessMap::AddResult result = originAccessMap().add(sourceString, nullptr); |
191 | if (result.isNewEntry) |
192 | result.iterator->value = std::make_unique<OriginAccessWhiteList>(); |
193 | |
194 | OriginAccessWhiteList* list = result.iterator->value.get(); |
195 | list->append(OriginAccessEntry(destinationProtocol, destinationDomain, allowDestinationSubdomains ? OriginAccessEntry::AllowSubdomains : OriginAccessEntry::DisallowSubdomains, OriginAccessEntry::TreatIPAddressAsIPAddress)); |
196 | } |
197 | |
198 | void SecurityPolicy::removeOriginAccessWhitelistEntry(const SecurityOrigin& sourceOrigin, const String& destinationProtocol, const String& destinationDomain, bool allowDestinationSubdomains) |
199 | { |
200 | ASSERT(!sourceOrigin.isUnique()); |
201 | if (sourceOrigin.isUnique()) |
202 | return; |
203 | |
204 | String sourceString = sourceOrigin.toString(); |
205 | |
206 | Locker<Lock> locker(originAccessMapLock); |
207 | OriginAccessMap& map = originAccessMap(); |
208 | OriginAccessMap::iterator it = map.find(sourceString); |
209 | if (it == map.end()) |
210 | return; |
211 | |
212 | OriginAccessWhiteList& list = *it->value; |
213 | OriginAccessEntry originAccessEntry(destinationProtocol, destinationDomain, allowDestinationSubdomains ? OriginAccessEntry::AllowSubdomains : OriginAccessEntry::DisallowSubdomains, OriginAccessEntry::TreatIPAddressAsIPAddress); |
214 | if (!list.removeFirst(originAccessEntry)) |
215 | return; |
216 | |
217 | if (list.isEmpty()) |
218 | map.remove(it); |
219 | } |
220 | |
221 | void SecurityPolicy::resetOriginAccessWhitelists() |
222 | { |
223 | Locker<Lock> locker(originAccessMapLock); |
224 | originAccessMap().clear(); |
225 | } |
226 | |
227 | } // namespace WebCore |
228 | |