summaryrefslogtreecommitdiff
path: root/Source/WebCore/contentextensions/ContentExtensionParser.cpp
diff options
context:
space:
mode:
authorKonstantin Tokarev <annulen@yandex.ru>2016-08-25 19:20:41 +0300
committerKonstantin Tokarev <annulen@yandex.ru>2017-02-02 12:30:55 +0000
commit6882a04fb36642862b11efe514251d32070c3d65 (patch)
treeb7959826000b061fd5ccc7512035c7478742f7b0 /Source/WebCore/contentextensions/ContentExtensionParser.cpp
parentab6df191029eeeb0b0f16f127d553265659f739e (diff)
downloadqtwebkit-6882a04fb36642862b11efe514251d32070c3d65.tar.gz
Imported QtWebKit TP3 (git b57bc6801f1876c3220d5a4bfea33d620d477443)
Change-Id: I3b1d8a2808782c9f34d50240000e20cb38d3680f Reviewed-by: Konstantin Tokarev <annulen@yandex.ru>
Diffstat (limited to 'Source/WebCore/contentextensions/ContentExtensionParser.cpp')
-rw-r--r--Source/WebCore/contentextensions/ContentExtensionParser.cpp321
1 files changed, 321 insertions, 0 deletions
diff --git a/Source/WebCore/contentextensions/ContentExtensionParser.cpp b/Source/WebCore/contentextensions/ContentExtensionParser.cpp
new file mode 100644
index 000000000..6491b7883
--- /dev/null
+++ b/Source/WebCore/contentextensions/ContentExtensionParser.cpp
@@ -0,0 +1,321 @@
+/*
+ * Copyright (C) 2014 Apple Inc. 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. AND ITS CONTRIBUTORS ``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 INC. OR ITS 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 "ContentExtensionParser.h"
+
+#if ENABLE(CONTENT_EXTENSIONS)
+
+#include "CSSParser.h"
+#include "CSSParserMode.h"
+#include "CSSSelectorList.h"
+#include "ContentExtensionError.h"
+#include "ContentExtensionRule.h"
+#include "ContentExtensionsBackend.h"
+#include "ContentExtensionsDebugging.h"
+#include <JavaScriptCore/IdentifierInlines.h>
+#include <JavaScriptCore/JSCJSValueInlines.h>
+#include <JavaScriptCore/JSGlobalObject.h>
+#include <JavaScriptCore/JSONObject.h>
+#include <JavaScriptCore/StructureInlines.h>
+#include <JavaScriptCore/VM.h>
+#include <wtf/CurrentTime.h>
+#include <wtf/text/WTFString.h>
+
+using namespace JSC;
+
+namespace WebCore {
+
+namespace ContentExtensions {
+
+static bool containsOnlyASCIIWithNoUppercase(const String& domain)
+{
+ for (unsigned i = 0; i < domain.length(); ++i) {
+ UChar c = domain.at(i);
+ if (!isASCII(c) || isASCIIUpper(c))
+ return false;
+ }
+ return true;
+}
+
+static std::error_code getDomainList(ExecState& exec, const JSObject* arrayObject, Vector<String>& vector)
+{
+ ASSERT(vector.isEmpty());
+ if (!arrayObject || !isJSArray(arrayObject))
+ return ContentExtensionError::JSONInvalidDomainList;
+ const JSArray* array = jsCast<const JSArray*>(arrayObject);
+
+ unsigned length = array->length();
+ for (unsigned i = 0; i < length; ++i) {
+ const JSValue value = array->getIndex(&exec, i);
+ if (exec.hadException() || !value.isString())
+ return ContentExtensionError::JSONInvalidDomainList;
+
+ // Domains should be punycode encoded lower case.
+ const String& domain = jsCast<JSString*>(value)->value(&exec);
+ if (domain.isEmpty())
+ return ContentExtensionError::JSONInvalidDomainList;
+ if (!containsOnlyASCIIWithNoUppercase(domain))
+ return ContentExtensionError::JSONDomainNotLowerCaseASCII;
+ vector.append(domain);
+ }
+ return { };
+}
+
+static std::error_code getTypeFlags(ExecState& exec, const JSValue& typeValue, ResourceFlags& flags, uint16_t (*stringToType)(const String&))
+{
+ if (!typeValue.isObject())
+ return { };
+
+ const JSObject* object = typeValue.toObject(&exec);
+ if (!isJSArray(object))
+ return ContentExtensionError::JSONInvalidTriggerFlagsArray;
+
+ const JSArray* array = jsCast<const JSArray*>(object);
+
+ unsigned length = array->length();
+ for (unsigned i = 0; i < length; ++i) {
+ const JSValue value = array->getIndex(&exec, i);
+ if (exec.hadException() || !value)
+ return ContentExtensionError::JSONInvalidObjectInTriggerFlagsArray;
+
+ String name = value.toWTFString(&exec);
+ uint16_t type = stringToType(name);
+ if (!type)
+ return ContentExtensionError::JSONInvalidStringInTriggerFlagsArray;
+
+ flags |= type;
+ }
+
+ return { };
+}
+
+static std::error_code loadTrigger(ExecState& exec, const JSObject& ruleObject, Trigger& trigger)
+{
+ const JSValue triggerObject = ruleObject.get(&exec, Identifier::fromString(&exec, "trigger"));
+ if (!triggerObject || exec.hadException() || !triggerObject.isObject())
+ return ContentExtensionError::JSONInvalidTrigger;
+
+ const JSValue urlFilterObject = triggerObject.get(&exec, Identifier::fromString(&exec, "url-filter"));
+ if (!urlFilterObject || exec.hadException() || !urlFilterObject.isString())
+ return ContentExtensionError::JSONInvalidURLFilterInTrigger;
+
+ String urlFilter = urlFilterObject.toWTFString(&exec);
+ if (urlFilter.isEmpty())
+ return ContentExtensionError::JSONInvalidURLFilterInTrigger;
+
+ trigger.urlFilter = urlFilter;
+
+ const JSValue urlFilterCaseValue = triggerObject.get(&exec, Identifier::fromString(&exec, "url-filter-is-case-sensitive"));
+ if (urlFilterCaseValue && !exec.hadException() && urlFilterCaseValue.isBoolean())
+ trigger.urlFilterIsCaseSensitive = urlFilterCaseValue.toBoolean(&exec);
+
+ const JSValue resourceTypeValue = triggerObject.get(&exec, Identifier::fromString(&exec, "resource-type"));
+ if (!exec.hadException() && resourceTypeValue.isObject()) {
+ auto typeFlagsError = getTypeFlags(exec, resourceTypeValue, trigger.flags, readResourceType);
+ if (typeFlagsError)
+ return typeFlagsError;
+ } else if (!resourceTypeValue.isUndefined())
+ return ContentExtensionError::JSONInvalidTriggerFlagsArray;
+
+ const JSValue loadTypeValue = triggerObject.get(&exec, Identifier::fromString(&exec, "load-type"));
+ if (!exec.hadException() && loadTypeValue.isObject()) {
+ auto typeFlagsError = getTypeFlags(exec, loadTypeValue, trigger.flags, readLoadType);
+ if (typeFlagsError)
+ return typeFlagsError;
+ } else if (!loadTypeValue.isUndefined())
+ return ContentExtensionError::JSONInvalidTriggerFlagsArray;
+
+ const JSValue ifDomain = triggerObject.get(&exec, Identifier::fromString(&exec, "if-domain"));
+ if (!exec.hadException() && ifDomain.isObject()) {
+ auto ifDomainError = getDomainList(exec, asObject(ifDomain), trigger.domains);
+ if (ifDomainError)
+ return ifDomainError;
+ if (trigger.domains.isEmpty())
+ return ContentExtensionError::JSONInvalidDomainList;
+ ASSERT(trigger.domainCondition == Trigger::DomainCondition::None);
+ trigger.domainCondition = Trigger::DomainCondition::IfDomain;
+ } else if (!ifDomain.isUndefined())
+ return ContentExtensionError::JSONInvalidDomainList;
+
+ const JSValue unlessDomain = triggerObject.get(&exec, Identifier::fromString(&exec, "unless-domain"));
+ if (!exec.hadException() && unlessDomain.isObject()) {
+ if (trigger.domainCondition != Trigger::DomainCondition::None)
+ return ContentExtensionError::JSONUnlessAndIfDomain;
+ auto unlessDomainError = getDomainList(exec, asObject(unlessDomain), trigger.domains);
+ if (unlessDomainError)
+ return unlessDomainError;
+ if (trigger.domains.isEmpty())
+ return ContentExtensionError::JSONInvalidDomainList;
+ trigger.domainCondition = Trigger::DomainCondition::UnlessDomain;
+ } else if (!unlessDomain.isUndefined())
+ return ContentExtensionError::JSONInvalidDomainList;
+
+ return { };
+}
+
+static bool isValidSelector(const String& selector)
+{
+ CSSParserContext context(CSSQuirksMode);
+ CSSParser parser(context);
+ CSSSelectorList selectorList;
+ parser.parseSelector(selector, selectorList);
+ return selectorList.isValid();
+}
+
+static std::error_code loadAction(ExecState& exec, const JSObject& ruleObject, Action& action, bool& validSelector)
+{
+ validSelector = true;
+ const JSValue actionObject = ruleObject.get(&exec, Identifier::fromString(&exec, "action"));
+ if (!actionObject || exec.hadException() || !actionObject.isObject())
+ return ContentExtensionError::JSONInvalidAction;
+
+ const JSValue typeObject = actionObject.get(&exec, Identifier::fromString(&exec, "type"));
+ if (!typeObject || exec.hadException() || !typeObject.isString())
+ return ContentExtensionError::JSONInvalidActionType;
+
+ String actionType = typeObject.toWTFString(&exec);
+
+ if (actionType == "block")
+ action = ActionType::BlockLoad;
+ else if (actionType == "ignore-previous-rules")
+ action = ActionType::IgnorePreviousRules;
+ else if (actionType == "block-cookies")
+ action = ActionType::BlockCookies;
+ else if (actionType == "css-display-none") {
+ JSValue selector = actionObject.get(&exec, Identifier::fromString(&exec, "selector"));
+ if (!selector || exec.hadException() || !selector.isString())
+ return ContentExtensionError::JSONInvalidCSSDisplayNoneActionType;
+
+ String s = selector.toWTFString(&exec);
+ if (!isValidSelector(s)) {
+ // Skip rules with invalid selectors to be backwards-compatible.
+ validSelector = false;
+ return { };
+ }
+ action = Action(ActionType::CSSDisplayNoneSelector, s);
+ } else if (actionType == "make-https") {
+ action = ActionType::MakeHTTPS;
+ } else
+ return ContentExtensionError::JSONInvalidActionType;
+
+ return { };
+}
+
+static std::error_code loadRule(ExecState& exec, const JSObject& ruleObject, Vector<ContentExtensionRule>& ruleList)
+{
+ Trigger trigger;
+ auto triggerError = loadTrigger(exec, ruleObject, trigger);
+ if (triggerError)
+ return triggerError;
+
+ Action action;
+ bool validSelector;
+ auto actionError = loadAction(exec, ruleObject, action, validSelector);
+ if (actionError)
+ return actionError;
+
+ if (validSelector)
+ ruleList.append(ContentExtensionRule(trigger, action));
+ return { };
+}
+
+static std::error_code loadEncodedRules(ExecState& exec, const String& rules, Vector<ContentExtensionRule>& ruleList)
+{
+ // FIXME: JSONParse should require callbacks instead of an ExecState.
+ const JSValue decodedRules = JSONParse(&exec, rules);
+
+ if (exec.hadException() || !decodedRules)
+ return ContentExtensionError::JSONInvalid;
+
+ if (!decodedRules.isObject())
+ return ContentExtensionError::JSONTopLevelStructureNotAnObject;
+
+ const JSObject* topLevelObject = decodedRules.toObject(&exec);
+ if (!topLevelObject || exec.hadException())
+ return ContentExtensionError::JSONTopLevelStructureNotAnObject;
+
+ if (!isJSArray(topLevelObject))
+ return ContentExtensionError::JSONTopLevelStructureNotAnArray;
+
+ const JSArray* topLevelArray = jsCast<const JSArray*>(topLevelObject);
+
+ Vector<ContentExtensionRule> localRuleList;
+
+ unsigned length = topLevelArray->length();
+ const unsigned maxRuleCount = 50000;
+ if (length > maxRuleCount)
+ return ContentExtensionError::JSONTooManyRules;
+ for (unsigned i = 0; i < length; ++i) {
+ const JSValue value = topLevelArray->getIndex(&exec, i);
+ if (exec.hadException() || !value)
+ return ContentExtensionError::JSONInvalidObjectInTopLevelArray;
+
+ const JSObject* ruleObject = value.toObject(&exec);
+ if (!ruleObject || exec.hadException())
+ return ContentExtensionError::JSONInvalidRule;
+
+ auto error = loadRule(exec, *ruleObject, localRuleList);
+ if (error)
+ return error;
+ }
+
+ ruleList = WTFMove(localRuleList);
+ return { };
+}
+
+std::error_code parseRuleList(const String& rules, Vector<ContentExtensionRule>& ruleList)
+{
+#if CONTENT_EXTENSIONS_PERFORMANCE_REPORTING
+ double loadExtensionStartTime = monotonicallyIncreasingTime();
+#endif
+ RefPtr<VM> vm = VM::create();
+
+ JSLockHolder locker(vm.get());
+ JSGlobalObject* globalObject = JSGlobalObject::create(*vm, JSGlobalObject::createStructure(*vm, jsNull()));
+
+ ExecState* exec = globalObject->globalExec();
+ auto error = loadEncodedRules(*exec, rules, ruleList);
+
+ vm = nullptr;
+
+ if (error)
+ return error;
+
+ if (ruleList.isEmpty())
+ return ContentExtensionError::JSONContainsNoRules;
+
+#if CONTENT_EXTENSIONS_PERFORMANCE_REPORTING
+ double loadExtensionEndTime = monotonicallyIncreasingTime();
+ dataLogF("Time spent loading extension %f\n", (loadExtensionEndTime - loadExtensionStartTime));
+#endif
+
+ return { };
+}
+
+} // namespace ContentExtensions
+} // namespace WebCore
+
+#endif // ENABLE(CONTENT_EXTENSIONS)