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