summaryrefslogtreecommitdiffstats
path: root/WebCore/page/XSSAuditor.cpp
diff options
context:
space:
mode:
authorBen Murdoch <benm@google.com>2009-08-11 17:01:47 +0100
committerBen Murdoch <benm@google.com>2009-08-11 18:21:02 +0100
commit0bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5 (patch)
tree2943df35f62d885c89d01063cc528dd73b480fea /WebCore/page/XSSAuditor.cpp
parent7e7a70bfa49a1122b2597a1e6367d89eb4035eca (diff)
downloadexternal_webkit-0bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5.zip
external_webkit-0bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5.tar.gz
external_webkit-0bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5.tar.bz2
Merge in WebKit r47029.
Diffstat (limited to 'WebCore/page/XSSAuditor.cpp')
-rw-r--r--WebCore/page/XSSAuditor.cpp267
1 files changed, 267 insertions, 0 deletions
diff --git a/WebCore/page/XSSAuditor.cpp b/WebCore/page/XSSAuditor.cpp
new file mode 100644
index 0000000..70b691b
--- /dev/null
+++ b/WebCore/page/XSSAuditor.cpp
@@ -0,0 +1,267 @@
+/*
+ * Copyright (C) 2008, 2009 Daniel Bates (dbates@intudata.com)
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "config.h"
+#include "XSSAuditor.h"
+
+#include <wtf/StdLibExtras.h>
+#include <wtf/Vector.h>
+
+#include "Console.h"
+#include "CString.h"
+#include "DocumentLoader.h"
+#include "DOMWindow.h"
+#include "Frame.h"
+#include "KURL.h"
+#include "PreloadScanner.h"
+#include "ResourceResponseBase.h"
+#include "ScriptSourceCode.h"
+#include "Settings.h"
+#include "TextResourceDecoder.h"
+
+using namespace WTF;
+
+namespace WebCore {
+
+static bool isNonCanonicalCharacter(UChar c)
+{
+ // Note, we don't remove backslashes like PHP stripslashes(), which among other things converts "\\0" to the \0 character.
+ // Instead, we remove backslashes and zeros (since the string "\\0" =(remove backslashes)=> "0"). However, this has the
+ // adverse effect that we remove any legitimate zeros from a string.
+ //
+ // For instance: new String("http://localhost:8000") => new String("http://localhost:8").
+ return (c == '\\' || c == '0' || c < ' ' || c == 127);
+}
+
+XSSAuditor::XSSAuditor(Frame* frame)
+ : m_frame(frame)
+{
+}
+
+XSSAuditor::~XSSAuditor()
+{
+}
+
+bool XSSAuditor::isEnabled() const
+{
+ Settings* settings = m_frame->settings();
+ return (settings && settings->xssAuditorEnabled());
+}
+
+bool XSSAuditor::canEvaluate(const String& code) const
+{
+ if (!isEnabled())
+ return true;
+
+ if (findInRequest(code, false)) {
+ DEFINE_STATIC_LOCAL(String, consoleMessage, ("Refused to execute a JavaScript script. Source code of script found within request.\n"));
+ m_frame->domWindow()->console()->addMessage(JSMessageSource, LogMessageType, ErrorMessageLevel, consoleMessage, 1, String());
+ return false;
+ }
+ return true;
+}
+
+bool XSSAuditor::canEvaluateJavaScriptURL(const String& code) const
+{
+ if (!isEnabled())
+ return true;
+
+ if (findInRequest(code)) {
+ DEFINE_STATIC_LOCAL(String, consoleMessage, ("Refused to execute a JavaScript script. Source code of script found within request.\n"));
+ m_frame->domWindow()->console()->addMessage(JSMessageSource, LogMessageType, ErrorMessageLevel, consoleMessage, 1, String());
+ return false;
+ }
+ return true;
+}
+
+bool XSSAuditor::canCreateInlineEventListener(const String&, const String& code) const
+{
+ if (!isEnabled())
+ return true;
+
+ if (findInRequest(code)) {
+ DEFINE_STATIC_LOCAL(String, consoleMessage, ("Refused to execute a JavaScript script. Source code of script found within request.\n"));
+ m_frame->domWindow()->console()->addMessage(JSMessageSource, LogMessageType, ErrorMessageLevel, consoleMessage, 1, String());
+ return false;
+ }
+ return true;
+}
+
+bool XSSAuditor::canLoadExternalScriptFromSrc(const String& context, const String& url) const
+{
+ if (!isEnabled())
+ return true;
+
+ if (findInRequest(context + url)) {
+ DEFINE_STATIC_LOCAL(String, consoleMessage, ("Refused to execute a JavaScript script. Source code of script found within request.\n"));
+ m_frame->domWindow()->console()->addMessage(JSMessageSource, LogMessageType, ErrorMessageLevel, consoleMessage, 1, String());
+ return false;
+ }
+ return true;
+}
+
+bool XSSAuditor::canLoadObject(const String& url) const
+{
+ if (!isEnabled())
+ return true;
+
+ if (findInRequest(url)) {
+ DEFINE_STATIC_LOCAL(String, consoleMessage, ("Refused to execute a JavaScript script. Source code of script found within request"));
+ m_frame->domWindow()->console()->addMessage(JSMessageSource, LogMessageType, ErrorMessageLevel, consoleMessage, 1, String());
+ return false;
+ }
+ return true;
+}
+
+bool XSSAuditor::canSetBaseElementURL(const String& url) const
+{
+ if (!isEnabled())
+ return true;
+
+ KURL baseElementURL(m_frame->document()->url(), url);
+ if (m_frame->document()->url().host() != baseElementURL.host() && findInRequest(url)) {
+ DEFINE_STATIC_LOCAL(String, consoleMessage, ("Refused to execute a JavaScript script. Source code of script found within request"));
+ m_frame->domWindow()->console()->addMessage(JSMessageSource, LogMessageType, ErrorMessageLevel, consoleMessage, 1, String());
+ return false;
+ }
+ return true;
+}
+
+String XSSAuditor::canonicalize(const String& string)
+{
+ String result = decodeHTMLEntities(string);
+ return result.removeCharacters(&isNonCanonicalCharacter);
+}
+
+String XSSAuditor::decodeURL(const String& string, const TextEncoding& encoding, bool decodeHTMLentities)
+{
+ String result;
+ String url = string;
+
+ url.replace('+', ' ');
+ result = decodeURLEscapeSequences(url);
+ String decodedResult = encoding.decode(result.utf8().data(), result.length());
+ if (!decodedResult.isEmpty())
+ result = decodedResult;
+ if (decodeHTMLentities)
+ result = decodeHTMLEntities(result);
+ return result;
+}
+
+String XSSAuditor::decodeHTMLEntities(const String& string, bool leaveUndecodableHTMLEntitiesUntouched)
+{
+ SegmentedString source(string);
+ SegmentedString sourceShadow;
+ Vector<UChar> result;
+
+ while (!source.isEmpty()) {
+ UChar cc = *source;
+ source.advance();
+
+ if (cc != '&') {
+ result.append(cc);
+ continue;
+ }
+
+ if (leaveUndecodableHTMLEntitiesUntouched)
+ sourceShadow = source;
+ bool notEnoughCharacters = false;
+ unsigned entity = PreloadScanner::consumeEntity(source, notEnoughCharacters);
+ // We ignore notEnoughCharacters because we might as well use this loop
+ // to copy the remaining characters into |result|.
+
+ if (entity > 0xFFFF) {
+ result.append(U16_LEAD(entity));
+ result.append(U16_TRAIL(entity));
+ } else if (entity && (!leaveUndecodableHTMLEntitiesUntouched || entity != 0xFFFD)){
+ result.append(entity);
+ } else {
+ result.append('&');
+ if (leaveUndecodableHTMLEntitiesUntouched)
+ source = sourceShadow;
+ }
+ }
+
+ return String::adopt(result);
+}
+
+bool XSSAuditor::findInRequest(const String& string, bool decodeHTMLentities) const
+{
+ bool result = false;
+ Frame* parentFrame = m_frame->tree()->parent();
+ if (parentFrame && m_frame->document()->url() == blankURL())
+ result = findInRequest(parentFrame, string, decodeHTMLentities);
+ if (!result)
+ result = findInRequest(m_frame, string, decodeHTMLentities);
+ return result;
+}
+
+bool XSSAuditor::findInRequest(Frame* frame, const String& string, bool decodeHTMLentities) const
+{
+ ASSERT(frame->document());
+ String pageURL = frame->document()->url().string();
+
+ if (!frame->document()->decoder()) {
+ // Note, JavaScript URLs do not have a charset.
+ return false;
+ }
+
+ if (protocolIs(pageURL, "data"))
+ return false;
+
+ if (string.isEmpty())
+ return false;
+
+ String canonicalizedString = canonicalize(string);
+ if (canonicalizedString.isEmpty())
+ return false;
+
+ if (string.length() < pageURL.length()) {
+ // The string can actually fit inside the pageURL.
+ String decodedPageURL = canonicalize(decodeURL(pageURL, frame->document()->decoder()->encoding(), decodeHTMLentities));
+ if (decodedPageURL.find(canonicalizedString, 0, false) != -1)
+ return true; // We've found the smoking gun.
+ }
+
+ FormData* formDataObj = frame->loader()->documentLoader()->originalRequest().httpBody();
+ if (formDataObj && !formDataObj->isEmpty()) {
+ String formData = formDataObj->flattenToString();
+ if (string.length() < formData.length()) {
+ // Notice it is sufficient to compare the length of the string to
+ // the url-encoded POST data because the length of the url-decoded
+ // code is less than or equal to the length of the url-encoded
+ // string.
+ String decodedFormData = canonicalize(decodeURL(formData, frame->document()->decoder()->encoding(), decodeHTMLentities));
+ if (decodedFormData.find(canonicalizedString, 0, false) != -1)
+ return true; // We found the string in the POST data.
+ }
+ }
+
+ return false;
+}
+
+} // namespace WebCore
+