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