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