OLD | NEW |
| (Empty) |
1 /* | |
2 * Copyright (C) 2007 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 * | |
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 Apple Computer, Inc. ("Apple") 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 APPLE 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 "weborigin/SecurityOrigin.h" | |
31 | |
32 #include "weborigin/KURL.h" | |
33 #include "weborigin/KnownPorts.h" | |
34 #include "weborigin/SchemeRegistry.h" | |
35 #include "weborigin/SecurityOriginCache.h" | |
36 #include "weborigin/SecurityPolicy.h" | |
37 #include "wtf/HexNumber.h" | |
38 #include "wtf/MainThread.h" | |
39 #include "wtf/StdLibExtras.h" | |
40 #include "wtf/text/StringBuilder.h" | |
41 | |
42 namespace WebCore { | |
43 | |
44 const int InvalidPort = 0; | |
45 const int MaxAllowedPort = 65535; | |
46 | |
47 static SecurityOriginCache* s_originCache = 0; | |
48 | |
49 static bool schemeRequiresAuthority(const KURL& url) | |
50 { | |
51 // We expect URLs with these schemes to have authority components. If the | |
52 // URL lacks an authority component, we get concerned and mark the origin | |
53 // as unique. | |
54 return url.protocolIsInHTTPFamily() || url.protocolIs("ftp"); | |
55 } | |
56 | |
57 static SecurityOrigin* cachedOrigin(const KURL& url) | |
58 { | |
59 if (s_originCache) | |
60 return s_originCache->cachedOrigin(url); | |
61 return 0; | |
62 } | |
63 | |
64 bool SecurityOrigin::shouldUseInnerURL(const KURL& url) | |
65 { | |
66 // FIXME: Blob URLs don't have inner URLs. Their form is "blob:<inner-origin
>/<UUID>", so treating the part after "blob:" as a URL is incorrect. | |
67 if (url.protocolIs("blob")) | |
68 return true; | |
69 if (url.protocolIs("filesystem")) | |
70 return true; | |
71 return false; | |
72 } | |
73 | |
74 // In general, extracting the inner URL varies by scheme. It just so happens | |
75 // that all the URL schemes we currently support that use inner URLs for their | |
76 // security origin can be parsed using this algorithm. | |
77 KURL SecurityOrigin::extractInnerURL(const KURL& url) | |
78 { | |
79 if (url.innerURL()) | |
80 return *url.innerURL(); | |
81 // FIXME: Update this callsite to use the innerURL member function when | |
82 // we finish implementing it. | |
83 return KURL(ParsedURLString, decodeURLEscapeSequences(url.path())); | |
84 } | |
85 | |
86 void SecurityOrigin::setCache(SecurityOriginCache* originCache) | |
87 { | |
88 s_originCache = originCache; | |
89 } | |
90 | |
91 static bool shouldTreatAsUniqueOrigin(const KURL& url) | |
92 { | |
93 if (!url.isValid()) | |
94 return true; | |
95 | |
96 // FIXME: Do we need to unwrap the URL further? | |
97 KURL innerURL = SecurityOrigin::shouldUseInnerURL(url) ? SecurityOrigin::ext
ractInnerURL(url) : url; | |
98 | |
99 // FIXME: Check whether innerURL is valid. | |
100 | |
101 // For edge case URLs that were probably misparsed, make sure that the origi
n is unique. | |
102 // FIXME: Do we really need to do this? This looks to be a hack around a | |
103 // security bug in CFNetwork that might have been fixed. | |
104 if (schemeRequiresAuthority(innerURL) && innerURL.host().isEmpty()) | |
105 return true; | |
106 | |
107 // SchemeRegistry needs a lower case protocol because it uses HashMaps | |
108 // that assume the scheme has already been canonicalized. | |
109 String protocol = innerURL.protocol().lower(); | |
110 | |
111 if (SchemeRegistry::shouldTreatURLSchemeAsNoAccess(protocol)) | |
112 return true; | |
113 | |
114 // This is the common case. | |
115 return false; | |
116 } | |
117 | |
118 SecurityOrigin::SecurityOrigin(const KURL& url) | |
119 : m_protocol(url.protocol().isNull() ? "" : url.protocol().lower()) | |
120 , m_host(url.host().isNull() ? "" : url.host().lower()) | |
121 , m_port(url.port()) | |
122 , m_isUnique(false) | |
123 , m_universalAccess(false) | |
124 , m_domainWasSetInDOM(false) | |
125 , m_enforceFilePathSeparation(false) | |
126 , m_needsDatabaseIdentifierQuirkForFiles(false) | |
127 { | |
128 // document.domain starts as m_host, but can be set by the DOM. | |
129 m_domain = m_host; | |
130 | |
131 if (isDefaultPortForProtocol(m_port, m_protocol)) | |
132 m_port = InvalidPort; | |
133 | |
134 // By default, only local SecurityOrigins can load local resources. | |
135 m_canLoadLocalResources = isLocal(); | |
136 | |
137 if (m_canLoadLocalResources) | |
138 m_filePath = url.path(); // In case enforceFilePathSeparation() is calle
d. | |
139 } | |
140 | |
141 SecurityOrigin::SecurityOrigin() | |
142 : m_protocol("") | |
143 , m_host("") | |
144 , m_domain("") | |
145 , m_port(InvalidPort) | |
146 , m_isUnique(true) | |
147 , m_universalAccess(false) | |
148 , m_domainWasSetInDOM(false) | |
149 , m_canLoadLocalResources(false) | |
150 , m_enforceFilePathSeparation(false) | |
151 , m_needsDatabaseIdentifierQuirkForFiles(false) | |
152 { | |
153 } | |
154 | |
155 SecurityOrigin::SecurityOrigin(const SecurityOrigin* other) | |
156 : m_protocol(other->m_protocol.isolatedCopy()) | |
157 , m_host(other->m_host.isolatedCopy()) | |
158 , m_domain(other->m_domain.isolatedCopy()) | |
159 , m_filePath(other->m_filePath.isolatedCopy()) | |
160 , m_port(other->m_port) | |
161 , m_isUnique(other->m_isUnique) | |
162 , m_universalAccess(other->m_universalAccess) | |
163 , m_domainWasSetInDOM(other->m_domainWasSetInDOM) | |
164 , m_canLoadLocalResources(other->m_canLoadLocalResources) | |
165 , m_enforceFilePathSeparation(other->m_enforceFilePathSeparation) | |
166 , m_needsDatabaseIdentifierQuirkForFiles(other->m_needsDatabaseIdentifierQui
rkForFiles) | |
167 { | |
168 } | |
169 | |
170 PassRefPtr<SecurityOrigin> SecurityOrigin::create(const KURL& url) | |
171 { | |
172 if (RefPtr<SecurityOrigin> origin = cachedOrigin(url)) | |
173 return origin.release(); | |
174 | |
175 if (shouldTreatAsUniqueOrigin(url)) { | |
176 RefPtr<SecurityOrigin> origin = adoptRef(new SecurityOrigin()); | |
177 | |
178 if (url.protocolIs("file")) { | |
179 // Unfortunately, we can't represent all unique origins exactly | |
180 // the same way because we need to produce a quirky database | |
181 // identifier for file URLs due to persistent storage in some | |
182 // embedders of WebKit. | |
183 origin->m_needsDatabaseIdentifierQuirkForFiles = true; | |
184 } | |
185 | |
186 return origin.release(); | |
187 } | |
188 | |
189 if (shouldUseInnerURL(url)) | |
190 return adoptRef(new SecurityOrigin(extractInnerURL(url))); | |
191 | |
192 return adoptRef(new SecurityOrigin(url)); | |
193 } | |
194 | |
195 PassRefPtr<SecurityOrigin> SecurityOrigin::createUnique() | |
196 { | |
197 RefPtr<SecurityOrigin> origin = adoptRef(new SecurityOrigin()); | |
198 ASSERT(origin->isUnique()); | |
199 return origin.release(); | |
200 } | |
201 | |
202 PassRefPtr<SecurityOrigin> SecurityOrigin::isolatedCopy() const | |
203 { | |
204 return adoptRef(new SecurityOrigin(this)); | |
205 } | |
206 | |
207 void SecurityOrigin::setDomainFromDOM(const String& newDomain) | |
208 { | |
209 m_domainWasSetInDOM = true; | |
210 m_domain = newDomain.lower(); | |
211 } | |
212 | |
213 bool SecurityOrigin::isSecure(const KURL& url) | |
214 { | |
215 // Invalid URLs are secure, as are URLs which have a secure protocol. | |
216 if (!url.isValid() || SchemeRegistry::shouldTreatURLSchemeAsSecure(url.proto
col())) | |
217 return true; | |
218 | |
219 // URLs that wrap inner URLs are secure if those inner URLs are secure. | |
220 if (shouldUseInnerURL(url) && SchemeRegistry::shouldTreatURLSchemeAsSecure(e
xtractInnerURL(url).protocol())) | |
221 return true; | |
222 | |
223 return false; | |
224 } | |
225 | |
226 bool SecurityOrigin::canAccess(const SecurityOrigin* other) const | |
227 { | |
228 if (m_universalAccess) | |
229 return true; | |
230 | |
231 if (this == other) | |
232 return true; | |
233 | |
234 if (isUnique() || other->isUnique()) | |
235 return false; | |
236 | |
237 // Here are two cases where we should permit access: | |
238 // | |
239 // 1) Neither document has set document.domain. In this case, we insist | |
240 // that the scheme, host, and port of the URLs match. | |
241 // | |
242 // 2) Both documents have set document.domain. In this case, we insist | |
243 // that the documents have set document.domain to the same value and | |
244 // that the scheme of the URLs match. | |
245 // | |
246 // This matches the behavior of Firefox 2 and Internet Explorer 6. | |
247 // | |
248 // Internet Explorer 7 and Opera 9 are more strict in that they require | |
249 // the port numbers to match when both pages have document.domain set. | |
250 // | |
251 // FIXME: Evaluate whether we can tighten this policy to require matched | |
252 // port numbers. | |
253 // | |
254 // Opera 9 allows access when only one page has set document.domain, but | |
255 // this is a security vulnerability. | |
256 | |
257 bool canAccess = false; | |
258 if (m_protocol == other->m_protocol) { | |
259 if (!m_domainWasSetInDOM && !other->m_domainWasSetInDOM) { | |
260 if (m_host == other->m_host && m_port == other->m_port) | |
261 canAccess = true; | |
262 } else if (m_domainWasSetInDOM && other->m_domainWasSetInDOM) { | |
263 if (m_domain == other->m_domain) | |
264 canAccess = true; | |
265 } | |
266 } | |
267 | |
268 if (canAccess && isLocal()) | |
269 canAccess = passesFileCheck(other); | |
270 | |
271 return canAccess; | |
272 } | |
273 | |
274 bool SecurityOrigin::passesFileCheck(const SecurityOrigin* other) const | |
275 { | |
276 ASSERT(isLocal() && other->isLocal()); | |
277 | |
278 if (!m_enforceFilePathSeparation && !other->m_enforceFilePathSeparation) | |
279 return true; | |
280 | |
281 return (m_filePath == other->m_filePath); | |
282 } | |
283 | |
284 bool SecurityOrigin::canRequest(const KURL& url) const | |
285 { | |
286 if (m_universalAccess) | |
287 return true; | |
288 | |
289 if (cachedOrigin(url) == this) | |
290 return true; | |
291 | |
292 if (isUnique()) | |
293 return false; | |
294 | |
295 RefPtr<SecurityOrigin> targetOrigin = SecurityOrigin::create(url); | |
296 | |
297 if (targetOrigin->isUnique()) | |
298 return false; | |
299 | |
300 // We call isSameSchemeHostPort here instead of canAccess because we want | |
301 // to ignore document.domain effects. | |
302 if (isSameSchemeHostPort(targetOrigin.get())) | |
303 return true; | |
304 | |
305 if (SecurityPolicy::isAccessWhiteListed(this, targetOrigin.get())) | |
306 return true; | |
307 | |
308 return false; | |
309 } | |
310 | |
311 bool SecurityOrigin::taintsCanvas(const KURL& url) const | |
312 { | |
313 if (canRequest(url)) | |
314 return false; | |
315 | |
316 // This function exists because we treat data URLs as having a unique origin
, | |
317 // contrary to the current (9/19/2009) draft of the HTML5 specification. | |
318 // We still want to let folks paint data URLs onto untainted canvases, so | |
319 // we special case data URLs below. If we change to match HTML5 w.r.t. | |
320 // data URL security, then we can remove this function in favor of | |
321 // !canRequest. | |
322 if (url.protocolIsData()) | |
323 return false; | |
324 | |
325 return true; | |
326 } | |
327 | |
328 bool SecurityOrigin::canReceiveDragData(const SecurityOrigin* dragInitiator) con
st | |
329 { | |
330 if (this == dragInitiator) | |
331 return true; | |
332 | |
333 return canAccess(dragInitiator); | |
334 } | |
335 | |
336 // This is a hack to allow keep navigation to http/https feeds working. To remov
e this | |
337 // we need to introduce new API akin to registerURLSchemeAsLocal, that registers
a | |
338 // protocols navigation policy. | |
339 // feed(|s|search): is considered a 'nesting' scheme by embedders that support i
t, so it can be | |
340 // local or remote depending on what is nested. Currently we just check if we ar
e nesting | |
341 // http or https, otherwise we ignore the nesting for the purpose of a security
check. We need | |
342 // a facility for registering nesting schemes, and some generalized logic for th
em. | |
343 // This function should be removed as an outcome of https://bugs.webkit.org/show
_bug.cgi?id=69196 | |
344 static bool isFeedWithNestedProtocolInHTTPFamily(const KURL& url) | |
345 { | |
346 const String& urlString = url.string(); | |
347 if (!urlString.startsWith("feed", false)) | |
348 return false; | |
349 | |
350 return urlString.startsWith("feed://", false) | |
351 || urlString.startsWith("feed:http:", false) || urlString.startsWith("fe
ed:https:", false) | |
352 || urlString.startsWith("feeds:http:", false) || urlString.startsWith("f
eeds:https:", false) | |
353 || urlString.startsWith("feedsearch:http:", false) || urlString.startsWi
th("feedsearch:https:", false); | |
354 } | |
355 | |
356 bool SecurityOrigin::canDisplay(const KURL& url) const | |
357 { | |
358 if (m_universalAccess) | |
359 return true; | |
360 | |
361 String protocol = url.protocol().lower(); | |
362 | |
363 if (isFeedWithNestedProtocolInHTTPFamily(url)) | |
364 return true; | |
365 | |
366 if (SchemeRegistry::canDisplayOnlyIfCanRequest(protocol)) | |
367 return canRequest(url); | |
368 | |
369 if (SchemeRegistry::shouldTreatURLSchemeAsDisplayIsolated(protocol)) | |
370 return m_protocol == protocol || SecurityPolicy::isAccessToURLWhiteListe
d(this, url); | |
371 | |
372 if (SchemeRegistry::shouldTreatURLSchemeAsLocal(protocol)) | |
373 return canLoadLocalResources() || SecurityPolicy::isAccessToURLWhiteList
ed(this, url); | |
374 | |
375 return true; | |
376 } | |
377 | |
378 SecurityOrigin::Policy SecurityOrigin::canShowNotifications() const | |
379 { | |
380 if (m_universalAccess) | |
381 return AlwaysAllow; | |
382 if (isUnique()) | |
383 return AlwaysDeny; | |
384 return Ask; | |
385 } | |
386 | |
387 void SecurityOrigin::grantLoadLocalResources() | |
388 { | |
389 // Granting privileges to some, but not all, documents in a SecurityOrigin | |
390 // is a security hazard because the documents without the privilege can | |
391 // obtain the privilege by injecting script into the documents that have | |
392 // been granted the privilege. | |
393 m_canLoadLocalResources = true; | |
394 } | |
395 | |
396 void SecurityOrigin::grantUniversalAccess() | |
397 { | |
398 m_universalAccess = true; | |
399 } | |
400 | |
401 void SecurityOrigin::enforceFilePathSeparation() | |
402 { | |
403 ASSERT(isLocal()); | |
404 m_enforceFilePathSeparation = true; | |
405 } | |
406 | |
407 bool SecurityOrigin::isLocal() const | |
408 { | |
409 return SchemeRegistry::shouldTreatURLSchemeAsLocal(m_protocol); | |
410 } | |
411 | |
412 String SecurityOrigin::toString() const | |
413 { | |
414 if (isUnique()) | |
415 return "null"; | |
416 if (m_protocol == "file" && m_enforceFilePathSeparation) | |
417 return "null"; | |
418 return toRawString(); | |
419 } | |
420 | |
421 String SecurityOrigin::toRawString() const | |
422 { | |
423 if (m_protocol == "file") | |
424 return "file://"; | |
425 | |
426 StringBuilder result; | |
427 result.reserveCapacity(m_protocol.length() + m_host.length() + 10); | |
428 result.append(m_protocol); | |
429 result.append("://"); | |
430 result.append(m_host); | |
431 | |
432 if (m_port) { | |
433 result.append(':'); | |
434 result.appendNumber(m_port); | |
435 } | |
436 | |
437 return result.toString(); | |
438 } | |
439 | |
440 PassRefPtr<SecurityOrigin> SecurityOrigin::createFromString(const String& origin
String) | |
441 { | |
442 return SecurityOrigin::create(KURL(KURL(), originString)); | |
443 } | |
444 | |
445 PassRefPtr<SecurityOrigin> SecurityOrigin::create(const String& protocol, const
String& host, int port) | |
446 { | |
447 if (port < 0 || port > MaxAllowedPort) | |
448 return createUnique(); | |
449 String decodedHost = decodeURLEscapeSequences(host); | |
450 return create(KURL(KURL(), protocol + "://" + host + ":" + String::number(po
rt) + "/")); | |
451 } | |
452 | |
453 bool SecurityOrigin::equal(const SecurityOrigin* other) const | |
454 { | |
455 if (other == this) | |
456 return true; | |
457 | |
458 if (!isSameSchemeHostPort(other)) | |
459 return false; | |
460 | |
461 if (m_domainWasSetInDOM != other->m_domainWasSetInDOM) | |
462 return false; | |
463 | |
464 if (m_domainWasSetInDOM && m_domain != other->m_domain) | |
465 return false; | |
466 | |
467 return true; | |
468 } | |
469 | |
470 bool SecurityOrigin::isSameSchemeHostPort(const SecurityOrigin* other) const | |
471 { | |
472 if (m_host != other->m_host) | |
473 return false; | |
474 | |
475 if (m_protocol != other->m_protocol) | |
476 return false; | |
477 | |
478 if (m_port != other->m_port) | |
479 return false; | |
480 | |
481 if (isLocal() && !passesFileCheck(other)) | |
482 return false; | |
483 | |
484 return true; | |
485 } | |
486 | |
487 const String& SecurityOrigin::urlWithUniqueSecurityOrigin() | |
488 { | |
489 ASSERT(isMainThread()); | |
490 DEFINE_STATIC_LOCAL(const String, uniqueSecurityOriginURL, ("data:,")); | |
491 return uniqueSecurityOriginURL; | |
492 } | |
493 | |
494 } // namespace WebCore | |
OLD | NEW |