Replace WTF::move with WTFMove
[WebKit-https.git] / Source / WebCore / contentextensions / ContentExtensionParser.cpp
1 /*
2  * Copyright (C) 2014 Apple Inc. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23  * THE POSSIBILITY OF SUCH DAMAGE.
24  */
25
26 #include "config.h"
27 #include "ContentExtensionParser.h"
28
29 #if ENABLE(CONTENT_EXTENSIONS)
30
31 #include "CSSParser.h"
32 #include "CSSParserMode.h"
33 #include "CSSSelectorList.h"
34 #include "ContentExtensionError.h"
35 #include "ContentExtensionRule.h"
36 #include "ContentExtensionsBackend.h"
37 #include "ContentExtensionsDebugging.h"
38 #include <JavaScriptCore/IdentifierInlines.h>
39 #include <JavaScriptCore/JSCJSValueInlines.h>
40 #include <JavaScriptCore/JSGlobalObject.h>
41 #include <JavaScriptCore/JSONObject.h>
42 #include <JavaScriptCore/StructureInlines.h>
43 #include <JavaScriptCore/VM.h>
44 #include <wtf/CurrentTime.h>
45 #include <wtf/text/WTFString.h>
46
47 using namespace JSC;
48
49 namespace WebCore {
50
51 namespace ContentExtensions {
52     
53 static bool containsOnlyASCIIWithNoUppercase(const String& domain)
54 {
55     for (unsigned i = 0; i < domain.length(); ++i) {
56         UChar c = domain.at(i);
57         if (!isASCII(c) || isASCIIUpper(c))
58             return false;
59     }
60     return true;
61 }
62     
63 static std::error_code getDomainList(ExecState& exec, const JSObject* arrayObject, Vector<String>& vector)
64 {
65     ASSERT(vector.isEmpty());
66     if (!arrayObject || !isJSArray(arrayObject))
67         return ContentExtensionError::JSONInvalidDomainList;
68     const JSArray* array = jsCast<const JSArray*>(arrayObject);
69     
70     unsigned length = array->length();
71     for (unsigned i = 0; i < length; ++i) {
72         const JSValue value = array->getIndex(&exec, i);
73         if (exec.hadException() || !value.isString())
74             return ContentExtensionError::JSONInvalidDomainList;
75         
76         // Domains should be punycode encoded lower case.
77         const String& domain = jsCast<JSString*>(value)->value(&exec);
78         if (domain.isEmpty())
79             return ContentExtensionError::JSONInvalidDomainList;
80         if (!containsOnlyASCIIWithNoUppercase(domain))
81             return ContentExtensionError::JSONDomainNotLowerCaseASCII;
82         vector.append(domain);
83     }
84     return { };
85 }
86
87 static std::error_code getTypeFlags(ExecState& exec, const JSValue& typeValue, ResourceFlags& flags, uint16_t (*stringToType)(const String&))
88 {
89     if (!typeValue.isObject())
90         return { };
91
92     const JSObject* object = typeValue.toObject(&exec);
93     if (!isJSArray(object))
94         return ContentExtensionError::JSONInvalidTriggerFlagsArray;
95
96     const JSArray* array = jsCast<const JSArray*>(object);
97     
98     unsigned length = array->length();
99     for (unsigned i = 0; i < length; ++i) {
100         const JSValue value = array->getIndex(&exec, i);
101         if (exec.hadException() || !value)
102             return ContentExtensionError::JSONInvalidObjectInTriggerFlagsArray;
103         
104         String name = value.toWTFString(&exec);
105         uint16_t type = stringToType(name);
106         if (!type)
107             return ContentExtensionError::JSONInvalidStringInTriggerFlagsArray;
108
109         flags |= type;
110     }
111
112     return { };
113 }
114     
115 static std::error_code loadTrigger(ExecState& exec, const JSObject& ruleObject, Trigger& trigger)
116 {
117     const JSValue triggerObject = ruleObject.get(&exec, Identifier::fromString(&exec, "trigger"));
118     if (!triggerObject || exec.hadException() || !triggerObject.isObject())
119         return ContentExtensionError::JSONInvalidTrigger;
120     
121     const JSValue urlFilterObject = triggerObject.get(&exec, Identifier::fromString(&exec, "url-filter"));
122     if (!urlFilterObject || exec.hadException() || !urlFilterObject.isString())
123         return ContentExtensionError::JSONInvalidURLFilterInTrigger;
124
125     String urlFilter = urlFilterObject.toWTFString(&exec);
126     if (urlFilter.isEmpty())
127         return ContentExtensionError::JSONInvalidURLFilterInTrigger;
128
129     trigger.urlFilter = urlFilter;
130
131     const JSValue urlFilterCaseValue = triggerObject.get(&exec, Identifier::fromString(&exec, "url-filter-is-case-sensitive"));
132     if (urlFilterCaseValue && !exec.hadException() && urlFilterCaseValue.isBoolean())
133         trigger.urlFilterIsCaseSensitive = urlFilterCaseValue.toBoolean(&exec);
134
135     const JSValue resourceTypeValue = triggerObject.get(&exec, Identifier::fromString(&exec, "resource-type"));
136     if (!exec.hadException() && resourceTypeValue.isObject()) {
137         auto typeFlagsError = getTypeFlags(exec, resourceTypeValue, trigger.flags, readResourceType);
138         if (typeFlagsError)
139             return typeFlagsError;
140     } else if (!resourceTypeValue.isUndefined())
141         return ContentExtensionError::JSONInvalidTriggerFlagsArray;
142
143     const JSValue loadTypeValue = triggerObject.get(&exec, Identifier::fromString(&exec, "load-type"));
144     if (!exec.hadException() && loadTypeValue.isObject()) {
145         auto typeFlagsError = getTypeFlags(exec, loadTypeValue, trigger.flags, readLoadType);
146         if (typeFlagsError)
147             return typeFlagsError;
148     } else if (!loadTypeValue.isUndefined())
149         return ContentExtensionError::JSONInvalidTriggerFlagsArray;
150
151     const JSValue ifDomain = triggerObject.get(&exec, Identifier::fromString(&exec, "if-domain"));
152     if (!exec.hadException() && ifDomain.isObject()) {
153         auto ifDomainError = getDomainList(exec, asObject(ifDomain), trigger.domains);
154         if (ifDomainError)
155             return ifDomainError;
156         if (trigger.domains.isEmpty())
157             return ContentExtensionError::JSONInvalidDomainList;
158         ASSERT(trigger.domainCondition == Trigger::DomainCondition::None);
159         trigger.domainCondition = Trigger::DomainCondition::IfDomain;
160     } else if (!ifDomain.isUndefined())
161         return ContentExtensionError::JSONInvalidDomainList;
162     
163     const JSValue unlessDomain = triggerObject.get(&exec, Identifier::fromString(&exec, "unless-domain"));
164     if (!exec.hadException() && unlessDomain.isObject()) {
165         if (trigger.domainCondition != Trigger::DomainCondition::None)
166             return ContentExtensionError::JSONUnlessAndIfDomain;
167         auto unlessDomainError = getDomainList(exec, asObject(unlessDomain), trigger.domains);
168         if (unlessDomainError)
169             return unlessDomainError;
170         if (trigger.domains.isEmpty())
171             return ContentExtensionError::JSONInvalidDomainList;
172         trigger.domainCondition = Trigger::DomainCondition::UnlessDomain;
173     } else if (!unlessDomain.isUndefined())
174         return ContentExtensionError::JSONInvalidDomainList;
175
176     return { };
177 }
178
179 static bool isValidSelector(const String& selector)
180 {
181     CSSParserContext context(CSSQuirksMode);
182     CSSParser parser(context);
183     CSSSelectorList selectorList;
184     parser.parseSelector(selector, selectorList);
185     return selectorList.isValid();
186 }
187
188 static std::error_code loadAction(ExecState& exec, const JSObject& ruleObject, Action& action, bool& validSelector)
189 {
190     validSelector = true;
191     const JSValue actionObject = ruleObject.get(&exec, Identifier::fromString(&exec, "action"));
192     if (!actionObject || exec.hadException() || !actionObject.isObject())
193         return ContentExtensionError::JSONInvalidAction;
194
195     const JSValue typeObject = actionObject.get(&exec, Identifier::fromString(&exec, "type"));
196     if (!typeObject || exec.hadException() || !typeObject.isString())
197         return ContentExtensionError::JSONInvalidActionType;
198
199     String actionType = typeObject.toWTFString(&exec);
200
201     if (actionType == "block")
202         action = ActionType::BlockLoad;
203     else if (actionType == "ignore-previous-rules")
204         action = ActionType::IgnorePreviousRules;
205     else if (actionType == "block-cookies")
206         action = ActionType::BlockCookies;
207     else if (actionType == "css-display-none") {
208         JSValue selector = actionObject.get(&exec, Identifier::fromString(&exec, "selector"));
209         if (!selector || exec.hadException() || !selector.isString())
210             return ContentExtensionError::JSONInvalidCSSDisplayNoneActionType;
211
212         String s = selector.toWTFString(&exec);
213         if (!isValidSelector(s)) {
214             // Skip rules with invalid selectors to be backwards-compatible.
215             validSelector = false;
216             return { };
217         }
218         action = Action(ActionType::CSSDisplayNoneSelector, s);
219     } else if (actionType == "make-https") {
220         action = ActionType::MakeHTTPS;
221     } else
222         return ContentExtensionError::JSONInvalidActionType;
223
224     return { };
225 }
226
227 static std::error_code loadRule(ExecState& exec, const JSObject& ruleObject, Vector<ContentExtensionRule>& ruleList)
228 {
229     Trigger trigger;
230     auto triggerError = loadTrigger(exec, ruleObject, trigger);
231     if (triggerError)
232         return triggerError;
233
234     Action action;
235     bool validSelector;
236     auto actionError = loadAction(exec, ruleObject, action, validSelector);
237     if (actionError)
238         return actionError;
239
240     if (validSelector)
241         ruleList.append(ContentExtensionRule(trigger, action));
242     return { };
243 }
244
245 static std::error_code loadEncodedRules(ExecState& exec, const String& rules, Vector<ContentExtensionRule>& ruleList)
246 {
247     // FIXME: JSONParse should require callbacks instead of an ExecState.
248     const JSValue decodedRules = JSONParse(&exec, rules);
249
250     if (exec.hadException() || !decodedRules)
251         return ContentExtensionError::JSONInvalid;
252
253     if (!decodedRules.isObject())
254         return ContentExtensionError::JSONTopLevelStructureNotAnObject;
255
256     const JSObject* topLevelObject = decodedRules.toObject(&exec);
257     if (!topLevelObject || exec.hadException())
258         return ContentExtensionError::JSONTopLevelStructureNotAnObject;
259     
260     if (!isJSArray(topLevelObject))
261         return ContentExtensionError::JSONTopLevelStructureNotAnArray;
262
263     const JSArray* topLevelArray = jsCast<const JSArray*>(topLevelObject);
264
265     Vector<ContentExtensionRule> localRuleList;
266
267     unsigned length = topLevelArray->length();
268     const unsigned maxRuleCount = 50000;
269     if (length > maxRuleCount)
270         return ContentExtensionError::JSONTooManyRules;
271     for (unsigned i = 0; i < length; ++i) {
272         const JSValue value = topLevelArray->getIndex(&exec, i);
273         if (exec.hadException() || !value)
274             return ContentExtensionError::JSONInvalidObjectInTopLevelArray;
275
276         const JSObject* ruleObject = value.toObject(&exec);
277         if (!ruleObject || exec.hadException())
278             return ContentExtensionError::JSONInvalidRule;
279
280         auto error = loadRule(exec, *ruleObject, localRuleList);
281         if (error)
282             return error;
283     }
284
285     ruleList = WTFMove(localRuleList);
286     return { };
287 }
288
289 std::error_code parseRuleList(const String& rules, Vector<ContentExtensionRule>& ruleList)
290 {
291 #if CONTENT_EXTENSIONS_PERFORMANCE_REPORTING
292     double loadExtensionStartTime = monotonicallyIncreasingTime();
293 #endif
294     RefPtr<VM> vm = VM::create();
295
296     JSLockHolder locker(vm.get());
297     JSGlobalObject* globalObject = JSGlobalObject::create(*vm, JSGlobalObject::createStructure(*vm, jsNull()));
298
299     ExecState* exec = globalObject->globalExec();
300     auto error = loadEncodedRules(*exec, rules, ruleList);
301
302     vm = nullptr;
303
304     if (error)
305         return error;
306
307     if (ruleList.isEmpty())
308         return ContentExtensionError::JSONContainsNoRules;
309
310 #if CONTENT_EXTENSIONS_PERFORMANCE_REPORTING
311     double loadExtensionEndTime = monotonicallyIncreasingTime();
312     dataLogF("Time spent loading extension %f\n", (loadExtensionEndTime - loadExtensionStartTime));
313 #endif
314
315     return { };
316 }
317
318 } // namespace ContentExtensions
319 } // namespace WebCore
320
321 #endif // ENABLE(CONTENT_EXTENSIONS)