Make CachedResourceLoader originsMatch check more efficient
[WebKit-https.git] / Source / WebCore / page / SecurityOrigin.cpp
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 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 "SecurityOrigin.h"
31
32 #include "BlobURL.h"
33 #include "FileSystem.h"
34 #include "URL.h"
35 #include "SchemeRegistry.h"
36 #include "SecurityPolicy.h"
37 #include "ThreadableBlobRegistry.h"
38 #include <wtf/MainThread.h>
39 #include <wtf/NeverDestroyed.h>
40 #include <wtf/StdLibExtras.h>
41 #include <wtf/text/StringBuilder.h>
42
43 namespace WebCore {
44
45 const int MaxAllowedPort = std::numeric_limits<uint16_t>::max();
46
47 static bool schemeRequiresHost(const URL& url)
48 {
49     // We expect URLs with these schemes to have authority components. If the
50     // URL lacks an authority component, we get concerned and mark the origin
51     // as unique.
52     return url.protocolIsInHTTPFamily() || url.protocolIs("ftp");
53 }
54
55 bool SecurityOrigin::shouldUseInnerURL(const URL& url)
56 {
57     // 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.
58     if (url.protocolIsBlob())
59         return true;
60     UNUSED_PARAM(url);
61     return false;
62 }
63
64 // In general, extracting the inner URL varies by scheme. It just so happens
65 // that all the URL schemes we currently support that use inner URLs for their
66 // security origin can be parsed using this algorithm.
67 URL SecurityOrigin::extractInnerURL(const URL& url)
68 {
69     // FIXME: Update this callsite to use the innerURL member function when
70     // we finish implementing it.
71     return URL(ParsedURLString, decodeURLEscapeSequences(url.path()));
72 }
73
74 static RefPtr<SecurityOrigin> getCachedOrigin(const URL& url)
75 {
76     if (url.protocolIsBlob())
77         return ThreadableBlobRegistry::getCachedOrigin(url);
78     return nullptr;
79 }
80
81 static bool shouldTreatAsUniqueOrigin(const URL& url)
82 {
83     if (!url.isValid())
84         return true;
85
86     // FIXME: Do we need to unwrap the URL further?
87     URL innerURL = SecurityOrigin::shouldUseInnerURL(url) ? SecurityOrigin::extractInnerURL(url) : url;
88
89     // FIXME: Check whether innerURL is valid.
90
91     // For edge case URLs that were probably misparsed, make sure that the origin is unique.
92     // This is an additional safety net against bugs in URL parsing, and for network back-ends that parse URLs differently,
93     // and could misinterpret another component for hostname.
94     if (schemeRequiresHost(innerURL) && innerURL.host().isEmpty())
95         return true;
96
97     if (SchemeRegistry::shouldTreatURLSchemeAsNoAccess(innerURL.protocol().toStringWithoutCopying()))
98         return true;
99
100     // This is the common case.
101     return false;
102 }
103
104 SecurityOrigin::SecurityOrigin(const URL& url)
105     : m_protocol(url.protocol().isNull() ? emptyString() : url.protocol().toString().convertToASCIILowercase())
106     , m_host(url.host().isNull() ? emptyString() : url.host().convertToASCIILowercase())
107     , m_port(url.port())
108     , m_isUnique(false)
109     , m_universalAccess(false)
110     , m_domainWasSetInDOM(false)
111     , m_storageBlockingPolicy(AllowAllStorage)
112     , m_enforceFilePathSeparation(false)
113     , m_needsDatabaseIdentifierQuirkForFiles(false)
114 {
115     // document.domain starts as m_host, but can be set by the DOM.
116     m_domain = m_host;
117
118     if (m_port && isDefaultPortForProtocol(m_port.value(), m_protocol))
119         m_port = Nullopt;
120
121     // By default, only local SecurityOrigins can load local resources.
122     m_canLoadLocalResources = isLocal();
123
124     if (m_canLoadLocalResources)
125         m_filePath = url.path(); // In case enforceFilePathSeparation() is called.
126 }
127
128 SecurityOrigin::SecurityOrigin()
129     : m_protocol(emptyString())
130     , m_host(emptyString())
131     , m_domain(emptyString())
132     , m_isUnique(true)
133     , m_universalAccess(false)
134     , m_domainWasSetInDOM(false)
135     , m_canLoadLocalResources(false)
136     , m_storageBlockingPolicy(AllowAllStorage)
137     , m_enforceFilePathSeparation(false)
138     , m_needsDatabaseIdentifierQuirkForFiles(false)
139 {
140 }
141
142 SecurityOrigin::SecurityOrigin(const SecurityOrigin* other)
143     : m_protocol(other->m_protocol.isolatedCopy())
144     , m_host(other->m_host.isolatedCopy())
145     , m_domain(other->m_domain.isolatedCopy())
146     , m_filePath(other->m_filePath.isolatedCopy())
147     , m_port(other->m_port)
148     , m_isUnique(other->m_isUnique)
149     , m_universalAccess(other->m_universalAccess)
150     , m_domainWasSetInDOM(other->m_domainWasSetInDOM)
151     , m_canLoadLocalResources(other->m_canLoadLocalResources)
152     , m_storageBlockingPolicy(other->m_storageBlockingPolicy)
153     , m_enforceFilePathSeparation(other->m_enforceFilePathSeparation)
154     , m_needsDatabaseIdentifierQuirkForFiles(other->m_needsDatabaseIdentifierQuirkForFiles)
155 {
156 }
157
158 Ref<SecurityOrigin> SecurityOrigin::create(const URL& url)
159 {
160     if (RefPtr<SecurityOrigin> cachedOrigin = getCachedOrigin(url))
161         return cachedOrigin.releaseNonNull();
162
163     if (shouldTreatAsUniqueOrigin(url)) {
164         Ref<SecurityOrigin> origin(adoptRef(*new SecurityOrigin));
165
166         if (url.protocolIs("file")) {
167             // Unfortunately, we can't represent all unique origins exactly
168             // the same way because we need to produce a quirky database
169             // identifier for file URLs due to persistent storage in some
170             // embedders of WebKit.
171             origin->m_needsDatabaseIdentifierQuirkForFiles = true;
172         }
173
174         return origin;
175     }
176
177     if (shouldUseInnerURL(url))
178         return adoptRef(*new SecurityOrigin(extractInnerURL(url)));
179
180     return adoptRef(*new SecurityOrigin(url));
181 }
182
183 Ref<SecurityOrigin> SecurityOrigin::createUnique()
184 {
185     Ref<SecurityOrigin> origin(adoptRef(*new SecurityOrigin));
186     ASSERT(origin.get().isUnique());
187     return origin;
188 }
189
190 Ref<SecurityOrigin> SecurityOrigin::isolatedCopy() const
191 {
192     return adoptRef(*new SecurityOrigin(this));
193 }
194
195 void SecurityOrigin::setDomainFromDOM(const String& newDomain)
196 {
197     m_domainWasSetInDOM = true;
198     m_domain = newDomain.convertToASCIILowercase();
199 }
200
201 bool SecurityOrigin::isSecure(const URL& url)
202 {
203     // Invalid URLs are secure, as are URLs which have a secure protocol.
204     if (!url.isValid() || SchemeRegistry::shouldTreatURLSchemeAsSecure(url.protocol().toStringWithoutCopying()))
205         return true;
206
207     // URLs that wrap inner URLs are secure if those inner URLs are secure.
208     if (shouldUseInnerURL(url) && SchemeRegistry::shouldTreatURLSchemeAsSecure(extractInnerURL(url).protocol().toStringWithoutCopying()))
209         return true;
210
211     return false;
212 }
213
214 bool SecurityOrigin::canAccess(const SecurityOrigin* other) const
215 {
216     if (m_universalAccess)
217         return true;
218
219     if (this == other)
220         return true;
221
222     if (isUnique() || other->isUnique())
223         return false;
224
225     // Here are two cases where we should permit access:
226     //
227     // 1) Neither document has set document.domain. In this case, we insist
228     //    that the scheme, host, and port of the URLs match.
229     //
230     // 2) Both documents have set document.domain. In this case, we insist
231     //    that the documents have set document.domain to the same value and
232     //    that the scheme of the URLs match.
233     //
234     // This matches the behavior of Firefox 2 and Internet Explorer 6.
235     //
236     // Internet Explorer 7 and Opera 9 are more strict in that they require
237     // the port numbers to match when both pages have document.domain set.
238     //
239     // FIXME: Evaluate whether we can tighten this policy to require matched
240     //        port numbers.
241     //
242     // Opera 9 allows access when only one page has set document.domain, but
243     // this is a security vulnerability.
244
245     bool canAccess = false;
246     if (m_protocol == other->m_protocol) {
247         if (!m_domainWasSetInDOM && !other->m_domainWasSetInDOM) {
248             if (m_host == other->m_host && m_port == other->m_port)
249                 canAccess = true;
250         } else if (m_domainWasSetInDOM && other->m_domainWasSetInDOM) {
251             if (m_domain == other->m_domain)
252                 canAccess = true;
253         }
254     }
255
256     if (canAccess && isLocal())
257        canAccess = passesFileCheck(other);
258
259     return canAccess;
260 }
261
262 bool SecurityOrigin::passesFileCheck(const SecurityOrigin* other) const
263 {
264     ASSERT(isLocal() && other->isLocal());
265
266     if (!m_enforceFilePathSeparation && !other->m_enforceFilePathSeparation)
267         return true;
268
269     return (m_filePath == other->m_filePath);
270 }
271
272 bool SecurityOrigin::canRequest(const URL& url) const
273 {
274     if (m_universalAccess)
275         return true;
276
277     if (getCachedOrigin(url) == this)
278         return true;
279
280     if (isUnique())
281         return false;
282
283     Ref<SecurityOrigin> targetOrigin(SecurityOrigin::create(url));
284
285     if (targetOrigin->isUnique())
286         return false;
287
288     // We call isSameSchemeHostPort here instead of canAccess because we want
289     // to ignore document.domain effects.
290     if (isSameSchemeHostPort(&targetOrigin.get()))
291         return true;
292
293     if (SecurityPolicy::isAccessWhiteListed(this, &targetOrigin.get()))
294         return true;
295
296     return false;
297 }
298
299 bool SecurityOrigin::canReceiveDragData(const SecurityOrigin* dragInitiator) const
300 {
301     if (this == dragInitiator)
302         return true;
303
304     return canAccess(dragInitiator);
305 }
306
307 // This is a hack to allow keep navigation to http/https feeds working. To remove this
308 // we need to introduce new API akin to registerURLSchemeAsLocal, that registers a
309 // protocols navigation policy.
310 // feed(|s|search): is considered a 'nesting' scheme by embedders that support it, so it can be
311 // local or remote depending on what is nested. Currently we just check if we are nesting
312 // http or https, otherwise we ignore the nesting for the purpose of a security check. We need
313 // a facility for registering nesting schemes, and some generalized logic for them.
314 // This function should be removed as an outcome of https://bugs.webkit.org/show_bug.cgi?id=69196
315 static bool isFeedWithNestedProtocolInHTTPFamily(const URL& url)
316 {
317     const String& urlString = url.string();
318     if (!urlString.startsWith("feed", false))
319         return false;
320
321     return urlString.startsWith("feed://", false) 
322         || urlString.startsWith("feed:http:", false) || urlString.startsWith("feed:https:", false)
323         || urlString.startsWith("feeds:http:", false) || urlString.startsWith("feeds:https:", false)
324         || urlString.startsWith("feedsearch:http:", false) || urlString.startsWith("feedsearch:https:", false);
325 }
326
327 bool SecurityOrigin::canDisplay(const URL& url) const
328 {
329     if (m_universalAccess)
330         return true;
331
332     if (isFeedWithNestedProtocolInHTTPFamily(url))
333         return true;
334
335     String protocol = url.protocol().toString();
336
337     if (SchemeRegistry::canDisplayOnlyIfCanRequest(protocol))
338         return canRequest(url);
339
340     if (SchemeRegistry::shouldTreatURLSchemeAsDisplayIsolated(protocol))
341         return equalIgnoringASCIICase(m_protocol, protocol) || SecurityPolicy::isAccessToURLWhiteListed(this, url);
342
343     if (SecurityPolicy::restrictAccessToLocal() && SchemeRegistry::shouldTreatURLSchemeAsLocal(protocol))
344         return canLoadLocalResources() || SecurityPolicy::isAccessToURLWhiteListed(this, url);
345
346     return true;
347 }
348
349 bool SecurityOrigin::canAccessStorage(const SecurityOrigin* topOrigin, ShouldAllowFromThirdParty shouldAllowFromThirdParty) const
350 {
351     if (isUnique())
352         return false;
353
354     if (m_storageBlockingPolicy == BlockAllStorage)
355         return false;
356
357     // FIXME: This check should be replaced with an ASSERT once we can guarantee that topOrigin is not null.
358     if (!topOrigin)
359         return true;
360
361     if (topOrigin->m_storageBlockingPolicy == BlockAllStorage)
362         return false;
363
364     if (shouldAllowFromThirdParty == AlwaysAllowFromThirdParty)
365         return true;
366
367     if ((m_storageBlockingPolicy == BlockThirdPartyStorage || topOrigin->m_storageBlockingPolicy == BlockThirdPartyStorage) && topOrigin->isThirdParty(this))
368         return false;
369
370     return true;
371 }
372
373 SecurityOrigin::Policy SecurityOrigin::canShowNotifications() const
374 {
375     if (m_universalAccess)
376         return AlwaysAllow;
377     if (isUnique())
378         return AlwaysDeny;
379     return Ask;
380 }
381
382 bool SecurityOrigin::isThirdParty(const SecurityOrigin* child) const
383 {
384     if (child->m_universalAccess)
385         return false;
386
387     if (this == child)
388         return false;
389
390     if (isUnique() || child->isUnique())
391         return true;
392
393     return !isSameSchemeHostPort(child);
394 }
395
396 void SecurityOrigin::grantLoadLocalResources()
397 {
398     // Granting privileges to some, but not all, documents in a SecurityOrigin
399     // is a security hazard because the documents without the privilege can
400     // obtain the privilege by injecting script into the documents that have
401     // been granted the privilege.
402     m_canLoadLocalResources = true;
403 }
404
405 void SecurityOrigin::grantUniversalAccess()
406 {
407     m_universalAccess = true;
408 }
409
410 #if ENABLE(CACHE_PARTITIONING)
411 String SecurityOrigin::domainForCachePartition() const
412 {
413     if (m_storageBlockingPolicy != BlockThirdPartyStorage)
414         return String();
415
416     if (isHTTPFamily())
417         return host();
418
419     if (SchemeRegistry::shouldPartitionCacheForURLScheme(m_protocol))
420         return host();
421
422     return String();
423 }
424 #endif
425
426 void SecurityOrigin::enforceFilePathSeparation()
427 {
428     ASSERT(isLocal());
429     m_enforceFilePathSeparation = true;
430 }
431
432 bool SecurityOrigin::isLocal() const
433 {
434     return SchemeRegistry::shouldTreatURLSchemeAsLocal(m_protocol);
435 }
436
437 String SecurityOrigin::toString() const
438 {
439     if (isUnique())
440         return ASCIILiteral("null");
441     if (m_protocol == "file" && m_enforceFilePathSeparation)
442         return ASCIILiteral("null");
443     return toRawString();
444 }
445
446 String SecurityOrigin::toRawString() const
447 {
448     if (m_protocol == "file")
449         return ASCIILiteral("file://");
450
451     StringBuilder result;
452     result.reserveCapacity(m_protocol.length() + m_host.length() + 10);
453     result.append(m_protocol);
454     result.appendLiteral("://");
455     result.append(m_host);
456
457     if (m_port) {
458         result.append(':');
459         result.appendNumber(m_port.value());
460     }
461
462     return result.toString();
463 }
464
465 static inline bool areOriginsMatching(const SecurityOrigin& origin1, const SecurityOrigin& origin2)
466 {
467     if (origin1.isUnique() || origin2.isUnique())
468         return origin1.isUnique() == origin2.isUnique();
469
470     if (origin1.protocol() != origin2.protocol())
471         return false;
472
473     if (origin1.protocol() == "file")
474         return true;
475
476     if (origin1.host() != origin2.host())
477         return false;
478
479     return origin1.port() == origin2.port();
480 }
481
482 // This function mimics the result of string comparison of serialized origins
483 bool originsMatch(const SecurityOrigin& origin1, const SecurityOrigin& origin2)
484 {
485     if (&origin1 == &origin2)
486         return true;
487
488     bool result = areOriginsMatching(origin1, origin2);
489     ASSERT(result == (origin1.toString() == origin2.toString()));
490     return result;
491 }
492
493 bool originsMatch(const SecurityOrigin* origin1, const SecurityOrigin* origin2)
494 {
495     if (!origin1 || !origin2)
496         return origin1 == origin2;
497
498     return originsMatch(*origin1, *origin2);
499 }
500
501 Ref<SecurityOrigin> SecurityOrigin::createFromString(const String& originString)
502 {
503     return SecurityOrigin::create(URL(URL(), originString));
504 }
505
506 static const char separatorCharacter = '_';
507
508 RefPtr<SecurityOrigin> SecurityOrigin::maybeCreateFromDatabaseIdentifier(const String& databaseIdentifier)
509 {
510     // Make sure there's a first separator
511     size_t separator1 = databaseIdentifier.find(separatorCharacter);
512     if (separator1 == notFound)
513         return nullptr;
514
515     // Make sure there's a second separator
516     size_t separator2 = databaseIdentifier.reverseFind(separatorCharacter);
517     if (separator2 == notFound)
518         return nullptr;
519
520     // Ensure there were at least 2 separator characters. Some hostnames on intranets have
521     // underscores in them, so we'll assume that any additional underscores are part of the host.
522     if (separator1 == separator2)
523         return nullptr;
524
525     // Make sure the port section is a valid port number or doesn't exist
526     bool portOkay;
527     int port = databaseIdentifier.right(databaseIdentifier.length() - separator2 - 1).toInt(&portOkay);
528     bool portAbsent = (separator2 == databaseIdentifier.length() - 1);
529     if (!(portOkay || portAbsent))
530         return nullptr;
531
532     if (port < 0 || port > MaxAllowedPort)
533         return nullptr;
534
535     // Split out the 3 sections of data
536     String protocol = databaseIdentifier.substring(0, separator1);
537     String host = databaseIdentifier.substring(separator1 + 1, separator2 - separator1 - 1);
538     
539     host = decodeURLEscapeSequences(host);
540     auto origin = create(URL(URL(), protocol + "://" + host + "/"));
541     origin->m_port = port;
542     return WTFMove(origin);
543 }
544
545 Ref<SecurityOrigin> SecurityOrigin::createFromDatabaseIdentifier(const String& databaseIdentifier)
546 {
547     if (RefPtr<SecurityOrigin> origin = maybeCreateFromDatabaseIdentifier(databaseIdentifier))
548         return origin.releaseNonNull();
549     return create(URL());
550 }
551
552 Ref<SecurityOrigin> SecurityOrigin::create(const String& protocol, const String& host, Optional<uint16_t> port)
553 {
554     String decodedHost = decodeURLEscapeSequences(host);
555     auto origin = create(URL(URL(), protocol + "://" + host + "/"));
556     origin->m_port = port;
557     return origin;
558 }
559
560 String SecurityOrigin::databaseIdentifier() const 
561 {
562     // Historically, we've used the following (somewhat non-sensical) string
563     // for the databaseIdentifier of local files. We used to compute this
564     // string because of a bug in how we handled the scheme for file URLs.
565     // Now that we've fixed that bug, we still need to produce this string
566     // to avoid breaking existing persistent state.
567     if (m_needsDatabaseIdentifierQuirkForFiles)
568         return ASCIILiteral("file__0");
569
570     StringBuilder stringBuilder;
571     stringBuilder.append(m_protocol);
572     stringBuilder.append(separatorCharacter);
573     stringBuilder.append(encodeForFileName(m_host));
574     stringBuilder.append(separatorCharacter);
575     stringBuilder.appendNumber(m_port.valueOr(0));
576
577     return stringBuilder.toString();
578 }
579
580 bool SecurityOrigin::equal(const SecurityOrigin* other) const 
581 {
582     if (other == this)
583         return true;
584     
585     if (!isSameSchemeHostPort(other))
586         return false;
587
588     if (m_domainWasSetInDOM != other->m_domainWasSetInDOM)
589         return false;
590
591     if (m_domainWasSetInDOM && m_domain != other->m_domain)
592         return false;
593
594     return true;
595 }
596
597 bool SecurityOrigin::isSameSchemeHostPort(const SecurityOrigin* other) const 
598 {
599     if (m_host != other->m_host)
600         return false;
601
602     if (m_protocol != other->m_protocol)
603         return false;
604
605     if (m_port != other->m_port)
606         return false;
607
608     if (isLocal() && !passesFileCheck(other))
609         return false;
610
611     return true;
612 }
613
614 URL SecurityOrigin::urlWithUniqueSecurityOrigin()
615 {
616     ASSERT(isMainThread());
617     static NeverDestroyed<URL> uniqueSecurityOriginURL(ParsedURLString, ASCIILiteral("data:,"));
618     return uniqueSecurityOriginURL;
619 }
620
621 } // namespace WebCore