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