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