summaryrefslogtreecommitdiffstats
path: root/Source/WebCore/page/ContentSecurityPolicy.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'Source/WebCore/page/ContentSecurityPolicy.cpp')
-rw-r--r--Source/WebCore/page/ContentSecurityPolicy.cpp488
1 files changed, 429 insertions, 59 deletions
diff --git a/Source/WebCore/page/ContentSecurityPolicy.cpp b/Source/WebCore/page/ContentSecurityPolicy.cpp
index 97cd447..6bcf99c 100644
--- a/Source/WebCore/page/ContentSecurityPolicy.cpp
+++ b/Source/WebCore/page/ContentSecurityPolicy.cpp
@@ -25,28 +25,390 @@
#include "config.h"
#include "ContentSecurityPolicy.h"
+
#include "Document.h"
+#include "NotImplemented.h"
+#include "SecurityOrigin.h"
namespace WebCore {
-class CSPDirective {
+// Normally WebKit uses "static" for internal linkage, but using "static" for
+// these functions causes a compile error because these functions are used as
+// template parameters.
+namespace {
+
+bool isDirectiveNameCharacter(UChar c)
+{
+ return isASCIIAlphanumeric(c) || c == '-';
+}
+
+bool isDirectiveValueCharacter(UChar c)
+{
+ return isASCIISpace(c) || (c >= 0x21 && c <= 0x7e); // Whitespace + VCHAR
+}
+
+bool isSourceCharacter(UChar c)
+{
+ return !isASCIISpace(c);
+}
+
+bool isHostCharacter(UChar c)
+{
+ return isASCIIAlphanumeric(c) || c == '-';
+}
+
+bool isSchemeContinuationCharacter(UChar c)
+{
+ return isASCIIAlphanumeric(c) || c == '+' || c == '-' || c == '.';
+}
+
+} // namespace
+
+static bool skipExactly(const UChar*& position, const UChar* end, UChar delimiter)
+{
+ if (position < end && *position == delimiter) {
+ ++position;
+ return true;
+ }
+ return false;
+}
+
+template<bool characterPredicate(UChar)>
+static bool skipExactly(const UChar*& position, const UChar* end)
+{
+ if (position < end && characterPredicate(*position)) {
+ ++position;
+ return true;
+ }
+ return false;
+}
+
+static void skipUtil(const UChar*& position, const UChar* end, UChar delimiter)
+{
+ while (position < end && *position != delimiter)
+ ++position;
+}
+
+template<bool characterPredicate(UChar)>
+static void skipWhile(const UChar*& position, const UChar* end)
+{
+ while (position < end && characterPredicate(*position))
+ ++position;
+}
+
+class CSPSource {
public:
- explicit CSPDirective(const String& value)
- : m_value(value)
+ CSPSource(const String& scheme, const String& host, int port, bool hostHasWildcard, bool portHasWildcard)
+ : m_scheme(scheme)
+ , m_host(host)
+ , m_port(port)
+ , m_hostHasWildcard(hostHasWildcard)
+ , m_portHasWildcard(portHasWildcard)
+ {
+ }
+
+ bool matches(const KURL& url) const
+ {
+ if (!schemeMatches(url))
+ return false;
+ if (isSchemeOnly())
+ return true;
+ return hostMatches(url) && portMatches(url);
+ }
+
+private:
+ bool schemeMatches(const KURL& url) const
+ {
+ return equalIgnoringCase(url.protocol(), m_scheme);
+ }
+
+ bool hostMatches(const KURL& url) const
{
+ if (m_hostHasWildcard)
+ notImplemented();
+
+ return equalIgnoringCase(url.host(), m_host);
}
- bool allows(const KURL&)
+ bool portMatches(const KURL& url) const
{
+ if (m_portHasWildcard)
+ return true;
+
+ // FIXME: Handle explicit default ports correctly.
+ return url.port() == m_port;
+ }
+
+ bool isSchemeOnly() const { return m_host.isEmpty(); }
+
+ String m_scheme;
+ String m_host;
+ int m_port;
+
+ bool m_hostHasWildcard;
+ bool m_portHasWildcard;
+};
+
+class CSPSourceList {
+public:
+ explicit CSPSourceList(SecurityOrigin*);
+
+ void parse(const String&);
+ bool matches(const KURL&);
+
+private:
+ void parse(const UChar* begin, const UChar* end);
+
+ bool parseSource(const UChar* begin, const UChar* end, String& scheme, String& host, int& port, bool& hostHasWildcard, bool& portHasWildcard);
+ bool parseScheme(const UChar* begin, const UChar* end, String& scheme);
+ bool parseHost(const UChar* begin, const UChar* end, String& host, bool& hostHasWildcard);
+ bool parsePort(const UChar* begin, const UChar* end, int& port, bool& portHasWildcard);
+
+ void addSourceSelf();
+
+ SecurityOrigin* m_origin;
+ Vector<CSPSource> m_list;
+};
+
+CSPSourceList::CSPSourceList(SecurityOrigin* origin)
+ : m_origin(origin)
+{
+}
+
+void CSPSourceList::parse(const String& value)
+{
+ parse(value.characters(), value.characters() + value.length());
+}
+
+bool CSPSourceList::matches(const KURL& url)
+{
+ for (size_t i = 0; i < m_list.size(); ++i) {
+ if (m_list[i].matches(url))
+ return true;
+ }
+ return false;
+}
+
+// source-list = *WSP [ source *( 1*WSP source ) *WSP ]
+// / *WSP "'none'" *WSP
+//
+void CSPSourceList::parse(const UChar* begin, const UChar* end)
+{
+ const UChar* position = begin;
+
+ bool isFirstSourceInList = true;
+ while (position < end) {
+ skipWhile<isASCIISpace>(position, end);
+ const UChar* beginSource = position;
+ skipWhile<isSourceCharacter>(position, end);
+
+ if (isFirstSourceInList && equalIgnoringCase("'none'", beginSource, position - beginSource))
+ return; // We represent 'none' as an empty m_list.
+ isFirstSourceInList = false;
+
+ String scheme, host;
+ int port = 0;
+ bool hostHasWildcard = false;
+ bool portHasWildcard = false;
+
+ if (parseSource(beginSource, position, scheme, host, port, hostHasWildcard, portHasWildcard)) {
+ if (scheme.isEmpty())
+ scheme = m_origin->protocol();
+ m_list.append(CSPSource(scheme, host, port, hostHasWildcard, portHasWildcard));
+ }
+
+ ASSERT(position == end || isASCIISpace(*position));
+ }
+}
+
+// source = scheme ":"
+// / ( [ scheme "://" ] host [ port ] )
+// / "'self'"
+//
+bool CSPSourceList::parseSource(const UChar* begin, const UChar* end,
+ String& scheme, String& host, int& port,
+ bool& hostHasWildcard, bool& portHasWildcard)
+{
+ if (begin == end)
+ return false;
+
+ if (equalIgnoringCase("'self'", begin, end - begin)) {
+ addSourceSelf();
return false;
}
+ const UChar* position = begin;
+
+ const UChar* beginHost = begin;
+ skipUtil(position, end, ':');
+
+ if (position == end) {
+ // This must be a host-only source.
+ if (!parseHost(beginHost, position, host, hostHasWildcard))
+ return false;
+ return true;
+ }
+
+ if (end - position == 1) {
+ ASSERT(*position == ':');
+ // This must be a scheme-only source.
+ if (!parseScheme(begin, position, scheme))
+ return false;
+ return true;
+ }
+
+ ASSERT(end - position >= 2);
+ if (position[1] == '/') {
+ if (!parseScheme(begin, position, scheme)
+ || !skipExactly(position, end, ':')
+ || !skipExactly(position, end, '/')
+ || !skipExactly(position, end, '/'))
+ return false;
+ beginHost = position;
+ skipUtil(position, end, ':');
+ }
+
+ if (position == beginHost)
+ return false;
+
+ if (!parseHost(beginHost, position, host, hostHasWildcard))
+ return false;
+
+ if (position == end) {
+ port = 0;
+ return true;
+ }
+
+ if (!skipExactly(position, end, ':'))
+ ASSERT_NOT_REACHED();
+
+ if (!parsePort(position, end, port, portHasWildcard))
+ return false;
+
+ return true;
+}
+
+// ; <scheme> production from RFC 3986
+// scheme = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." )
+//
+bool CSPSourceList::parseScheme(const UChar* begin, const UChar* end, String& scheme)
+{
+ ASSERT(begin <= end);
+ ASSERT(scheme.isEmpty());
+
+ if (begin == end)
+ return false;
+
+ const UChar* position = begin;
+
+ if (!skipExactly<isASCIIAlpha>(position, end))
+ return false;
+
+ skipWhile<isSchemeContinuationCharacter>(position, end);
+
+ if (position != end)
+ return false;
+
+ scheme = String(begin, end - begin);
+ return true;
+}
+
+// host = [ "*." ] 1*host-char *( "." 1*host-char )
+// / "*"
+// host-char = ALPHA / DIGIT / "-"
+//
+bool CSPSourceList::parseHost(const UChar* begin, const UChar* end, String& host, bool& hostHasWildcard)
+{
+ ASSERT(begin <= end);
+ ASSERT(host.isEmpty());
+ ASSERT(!hostHasWildcard);
+
+ if (begin == end)
+ return false;
+
+ const UChar* position = begin;
+
+ if (skipExactly(position, end, '*')) {
+ hostHasWildcard = true;
+
+ if (position == end)
+ return true;
+
+ if (!skipExactly(position, end, '.'))
+ return false;
+ }
+
+ const UChar* hostBegin = position;
+
+ while (position < end) {
+ if (!skipExactly<isHostCharacter>(position, end))
+ return false;
+
+ skipWhile<isHostCharacter>(position, end);
+
+ if (position < end && !skipExactly(position, end, '.'))
+ return false;
+ }
+
+ ASSERT(position == end);
+ host = String(hostBegin, end - hostBegin);
+ return true;
+}
+
+// port = ":" ( 1*DIGIT / "*" )
+//
+bool CSPSourceList::parsePort(const UChar* begin, const UChar* end, int& port, bool& portHasWildcard)
+{
+ ASSERT(begin <= end);
+ ASSERT(!port);
+ ASSERT(!portHasWildcard);
+
+ if (begin == end)
+ return false;
+
+ if (end - begin == 1 && *begin == '*') {
+ port = 0;
+ portHasWildcard = true;
+ return true;
+ }
+
+ const UChar* position = begin;
+ skipWhile<isASCIIDigit>(position, end);
+
+ if (position != end)
+ return false;
+
+ bool ok;
+ port = charactersToIntStrict(begin, end - begin, &ok);
+ return ok;
+}
+
+void CSPSourceList::addSourceSelf()
+{
+ // FIXME: Inherit the scheme, host, and port from the current URL.
+ notImplemented();
+}
+
+class CSPDirective {
+public:
+ CSPDirective(const String& value, SecurityOrigin* origin)
+ : m_sourceList(origin)
+ {
+ m_sourceList.parse(value);
+ }
+
+ bool allows(const KURL& url)
+ {
+ return m_sourceList.matches(url);
+ }
+
private:
- String m_value;
+ CSPSourceList m_sourceList;
};
-ContentSecurityPolicy::ContentSecurityPolicy()
+ContentSecurityPolicy::ContentSecurityPolicy(SecurityOrigin* origin)
: m_havePolicy(false)
+ , m_origin(origin)
{
}
@@ -68,11 +430,19 @@ bool ContentSecurityPolicy::allowJavaScriptURLs() const
return !m_scriptSrc;
}
-bool ContentSecurityPolicy::canLoadExternalScriptFromSrc(const String& url) const
+bool ContentSecurityPolicy::allowInlineEventHandlers() const
+{
+ return !m_scriptSrc;
+}
+
+bool ContentSecurityPolicy::allowScriptFromSource(const KURL& url) const
{
- return !m_scriptSrc || m_scriptSrc->allows(KURL(ParsedURLString, url));
+ return !m_scriptSrc || m_scriptSrc->allows(url);
}
+// policy = directive-list
+// directive-list = [ directive *( ";" [ directive ] ) ]
+//
void ContentSecurityPolicy::parse(const String& policy)
{
ASSERT(!m_havePolicy);
@@ -80,75 +450,75 @@ void ContentSecurityPolicy::parse(const String& policy)
if (policy.isEmpty())
return;
- const UChar* pos = policy.characters();
- const UChar* end = pos + policy.length();
+ const UChar* position = policy.characters();
+ const UChar* end = position + policy.length();
- while (pos < end) {
- Vector<UChar, 32> name;
- Vector<UChar, 64> value;
+ while (position < end) {
+ const UChar* directiveBegin = position;
+ skipUtil(position, end, ';');
- parseDirective(pos, end, name, value);
- if (name.isEmpty())
- continue;
+ String name, value;
+ if (parseDirective(directiveBegin, position, name, value)) {
+ ASSERT(!name.isEmpty());
+ addDirective(name, value);
+ }
- // We use a copy here instead of String::adopt because we expect
- // the name and the value to be relatively short, so the copy will
- // be cheaper than the extra malloc.
- emitDirective(String(name), String(value));
+ ASSERT(position == end || *position == ';');
+ skipExactly(position, end, ';');
}
}
-void ContentSecurityPolicy::parseDirective(const UChar*& pos, const UChar* end, Vector<UChar, 32>& name, Vector<UChar, 64>& value)
+// directive = *WSP [ directive-name [ WSP directive-value ] ]
+// directive-name = 1*( ALPHA / DIGIT / "-" )
+// directive-value = *( WSP / <VCHAR except ";"> )
+//
+bool ContentSecurityPolicy::parseDirective(const UChar* begin, const UChar* end, String& name, String& value)
{
- ASSERT(pos < end);
ASSERT(name.isEmpty());
ASSERT(value.isEmpty());
- enum {
- BeforeDirectiveName,
- DirectiveName,
- AfterDirectiveName,
- DirectiveValue,
- } state = BeforeDirectiveName;
-
- while (pos < end) {
- UChar currentCharacter = *pos++;
- switch (state) {
- case BeforeDirectiveName:
- if (isASCIISpace(currentCharacter))
- continue;
- state = DirectiveName;
- // Fall through.
- case DirectiveName:
- if (!isASCIISpace(currentCharacter)) {
- name.append(currentCharacter);
- continue;
- }
- state = AfterDirectiveName;
- // Fall through.
- case AfterDirectiveName:
- if (isASCIISpace(currentCharacter))
- continue;
- state = DirectiveValue;
- // Fall through.
- case DirectiveValue:
- if (currentCharacter != ';') {
- value.append(currentCharacter);
- continue;
- }
- return;
- }
- }
+ const UChar* position = begin;
+ skipWhile<isASCIISpace>(position, end);
+
+ const UChar* nameBegin = position;
+ skipWhile<isDirectiveNameCharacter>(position, end);
+
+ // The directive-name must be non-empty.
+ if (nameBegin == position)
+ return false;
+
+ name = String(nameBegin, position - nameBegin);
+
+ if (position == end)
+ return true;
+
+ if (!skipExactly<isASCIISpace>(position, end))
+ return false;
+
+ skipWhile<isASCIISpace>(position, end);
+
+ const UChar* valueBegin = position;
+ skipWhile<isDirectiveValueCharacter>(position, end);
+
+ if (position != end)
+ return false;
+
+ // The directive-value may be empty.
+ if (valueBegin == position)
+ return true;
+
+ value = String(valueBegin, position - valueBegin);
+ return true;
}
-void ContentSecurityPolicy::emitDirective(const String& name, const String& value)
+void ContentSecurityPolicy::addDirective(const String& name, const String& value)
{
DEFINE_STATIC_LOCAL(String, scriptSrc, ("script-src"));
ASSERT(!name.isEmpty());
if (!m_scriptSrc && equalIgnoringCase(name, scriptSrc))
- m_scriptSrc = adoptPtr(new CSPDirective(value));
+ m_scriptSrc = adoptPtr(new CSPDirective(value, m_origin.get()));
}
}