#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)
{
return (c == '\\' || c == '0' || c < ' ' || c >= 127);
}
static bool isIllegalURICharacter(UChar c)
{
return (c == '\'' || c == '"' || c == '<' || c == '>');
}
String XSSAuditor::CachingURLCanonicalizer::canonicalizeURL(const String& url, const TextEncoding& encoding, bool decodeEntities,
bool decodeURLEscapeSequencesTwice)
{
if (decodeEntities == m_decodeEntities && decodeURLEscapeSequencesTwice == m_decodeURLEscapeSequencesTwice
&& encoding == m_encoding && url == m_inputURL)
return m_cachedCanonicalizedURL;
m_cachedCanonicalizedURL = canonicalize(decodeURL(url, encoding, decodeEntities, decodeURLEscapeSequencesTwice));
m_inputURL = url;
m_encoding = encoding;
m_decodeEntities = decodeEntities;
m_decodeURLEscapeSequencesTwice = decodeURLEscapeSequencesTwice;
return m_cachedCanonicalizedURL;
}
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;
FindTask task;
task.string = code;
task.decodeEntities = false;
task.allowRequestIfNoIllegalURICharacters = true;
if (findInRequest(task)) {
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;
FindTask task;
task.string = code;
task.decodeURLEscapeSequencesTwice = true;
if (findInRequest(task)) {
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;
FindTask task;
task.string = code;
task.allowRequestIfNoIllegalURICharacters = true;
if (findInRequest(task)) {
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 (isSameOriginResource(url))
return true;
FindTask task;
task.context = context;
task.string = url;
if (findInRequest(task)) {
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 (isSameOriginResource(url))
return true;
FindTask task;
task.string = url;
task.allowRequestIfNoIllegalURICharacters = true;
if (findInRequest(task)) {
String consoleMessage = String::format("Refused to load an object. URL found within request: \"%s\".\n", url.utf8().data());
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;
if (isSameOriginResource(url))
return true;
FindTask task;
task.string = url;
task.allowRequestIfNoIllegalURICharacters = true;
if (findInRequest(task)) {
DEFINE_STATIC_LOCAL(String, consoleMessage, ("Refused to load from document base URL. URL found within request.\n"));
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 decodeEntities, bool decodeURLEscapeSequencesTwice)
{
String result;
String url = string;
url.replace('+', ' ');
result = decodeURLEscapeSequences(url);
CString utf8Url = result.utf8();
String decodedResult = encoding.decode(utf8Url.data(), utf8Url.length());
if (!decodedResult.isEmpty())
result = decodedResult;
if (decodeURLEscapeSequencesTwice) {
result = decodeURLEscapeSequences(result);
utf8Url = result.utf8();
decodedResult = encoding.decode(utf8Url.data(), utf8Url.length());
if (!decodedResult.isEmpty())
result = decodedResult;
}
if (decodeEntities)
result = decodeHTMLEntities(result);
return result;
}
String XSSAuditor::decodeHTMLEntities(const String& string, bool leaveUndecodableEntitiesUntouched)
{
SegmentedString source(string);
SegmentedString sourceShadow;
Vector<UChar> result;
while (!source.isEmpty()) {
UChar cc = *source;
source.advance();
if (cc != '&') {
result.append(cc);
continue;
}
if (leaveUndecodableEntitiesUntouched)
sourceShadow = source;
bool notEnoughCharacters = false;
unsigned entity = PreloadScanner::consumeEntity(source, notEnoughCharacters);
if (entity > 0xFFFF) {
result.append(U16_LEAD(entity));
result.append(U16_TRAIL(entity));
} else if (entity && (!leaveUndecodableEntitiesUntouched || entity != 0xFFFD)){
result.append(entity);
} else {
result.append('&');
if (leaveUndecodableEntitiesUntouched)
source = sourceShadow;
}
}
return String::adopt(result);
}
bool XSSAuditor::isSameOriginResource(const String& url) const
{
KURL resourceURL(m_frame->document()->url(), url);
return (m_frame->document()->url().host() == resourceURL.host() && resourceURL.query().isEmpty());
}
bool XSSAuditor::findInRequest(const FindTask& task) const
{
bool result = false;
Frame* parentFrame = m_frame->tree()->parent();
if (parentFrame && m_frame->document()->url() == blankURL())
result = findInRequest(parentFrame, task);
if (!result)
result = findInRequest(m_frame, task);
return result;
}
bool XSSAuditor::findInRequest(Frame* frame, const FindTask& task) const
{
ASSERT(frame->document());
if (!frame->document()->decoder()) {
return false;
}
if (task.string.isEmpty())
return false;
FormData* formDataObj = frame->loader()->documentLoader()->originalRequest().httpBody();
const bool hasFormData = formDataObj && !formDataObj->isEmpty();
String pageURL = frame->document()->url().string();
String canonicalizedString;
if (!hasFormData && task.string.length() > 2 * pageURL.length()) {
canonicalizedString = task.string.substring(0, 2 * pageURL.length());
} else
canonicalizedString = task.string;
if (frame->document()->url().protocolIs("data"))
return false;
canonicalizedString = canonicalize(canonicalizedString);
if (canonicalizedString.isEmpty())
return false;
if (!task.context.isEmpty())
canonicalizedString = task.context + canonicalizedString;
String decodedPageURL = m_cache.canonicalizeURL(pageURL, frame->document()->decoder()->encoding(), task.decodeEntities, task.decodeURLEscapeSequencesTwice);
if (task.allowRequestIfNoIllegalURICharacters && !hasFormData && decodedPageURL.find(&isIllegalURICharacter, 0) == -1)
return false;
if (decodedPageURL.find(canonicalizedString, 0, false) != -1)
return true;
if (hasFormData) {
String decodedFormData = m_cache.canonicalizeURL(formDataObj->flattenToString(), frame->document()->decoder()->encoding(), task.decodeEntities, task.decodeURLEscapeSequencesTwice);
if (decodedFormData.find(canonicalizedString, 0, false) != -1)
return true; }
return false;
}
}