diff options
author | Konstantin Tokarev <annulen@yandex.ru> | 2016-08-25 19:20:41 +0300 |
---|---|---|
committer | Konstantin Tokarev <annulen@yandex.ru> | 2017-02-02 12:30:55 +0000 |
commit | 6882a04fb36642862b11efe514251d32070c3d65 (patch) | |
tree | b7959826000b061fd5ccc7512035c7478742f7b0 /Source/WebCore/contentextensions/ContentExtensionParser.cpp | |
parent | ab6df191029eeeb0b0f16f127d553265659f739e (diff) | |
download | qtwebkit-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.cpp | 321 |
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) |