5063bfbc06c4bd6c600198654c93012ab3b24540
[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 "ContentExtensionError.h"
32 #include "ContentExtensionRule.h"
33 #include "ContentExtensionsBackend.h"
34 #include "ContentExtensionsDebugging.h"
35 #include <JavaScriptCore/IdentifierInlines.h>
36 #include <JavaScriptCore/JSCJSValueInlines.h>
37 #include <JavaScriptCore/JSGlobalObject.h>
38 #include <JavaScriptCore/JSONObject.h>
39 #include <JavaScriptCore/StructureInlines.h>
40 #include <JavaScriptCore/VM.h>
41 #include <wtf/CurrentTime.h>
42 #include <wtf/text/WTFString.h>
43
44 using namespace JSC;
45
46 namespace WebCore {
47
48 namespace ContentExtensions {
49     
50 static bool containsOnlyASCIIWithNoUppercase(const String& domain)
51 {
52     for (unsigned i = 0; i < domain.length(); ++i) {
53         UChar c = domain.at(i);
54         if (!isASCII(c) || isASCIIUpper(c))
55             return false;
56     }
57     return true;
58 }
59     
60 static std::error_code getDomainList(ExecState& exec, const JSObject* arrayObject, Vector<String>& vector)
61 {
62     ASSERT(vector.isEmpty());
63     if (!arrayObject || !isJSArray(arrayObject))
64         return ContentExtensionError::JSONInvalidDomainList;
65     const JSArray* array = jsCast<const JSArray*>(arrayObject);
66     
67     unsigned length = array->length();
68     for (unsigned i = 0; i < length; ++i) {
69         const JSValue value = array->getIndex(&exec, i);
70         if (exec.hadException() || !value.isString())
71             return ContentExtensionError::JSONInvalidDomainList;
72         
73         // Domains should be punycode encoded lower case.
74         const String& domain = jsCast<JSString*>(value)->value(&exec);
75         if (domain.isEmpty())
76             return ContentExtensionError::JSONInvalidDomainList;
77         if (!containsOnlyASCIIWithNoUppercase(domain))
78             return ContentExtensionError::JSONDomainNotLowerCaseASCII;
79         vector.append(domain);
80     }
81     return { };
82 }
83
84 static std::error_code getTypeFlags(ExecState& exec, const JSValue& typeValue, ResourceFlags& flags, uint16_t (*stringToType)(const String&))
85 {
86     if (!typeValue.isObject())
87         return { };
88
89     const JSObject* object = typeValue.toObject(&exec);
90     if (!isJSArray(object))
91         return ContentExtensionError::JSONInvalidTriggerFlagsArray;
92
93     const JSArray* array = jsCast<const JSArray*>(object);
94     
95     unsigned length = array->length();
96     for (unsigned i = 0; i < length; ++i) {
97         const JSValue value = array->getIndex(&exec, i);
98         if (exec.hadException() || !value)
99             return ContentExtensionError::JSONInvalidObjectInTriggerFlagsArray;
100         
101         String name = value.toWTFString(&exec);
102         uint16_t type = stringToType(name);
103         if (!type)
104             return ContentExtensionError::JSONInvalidStringInTriggerFlagsArray;
105
106         flags |= type;
107     }
108
109     return { };
110 }
111     
112 static std::error_code loadTrigger(ExecState& exec, const JSObject& ruleObject, Trigger& trigger)
113 {
114     const JSValue triggerObject = ruleObject.get(&exec, Identifier::fromString(&exec, "trigger"));
115     if (!triggerObject || exec.hadException() || !triggerObject.isObject())
116         return ContentExtensionError::JSONInvalidTrigger;
117     
118     const JSValue urlFilterObject = triggerObject.get(&exec, Identifier::fromString(&exec, "url-filter"));
119     if (!urlFilterObject || exec.hadException() || !urlFilterObject.isString())
120         return ContentExtensionError::JSONInvalidURLFilterInTrigger;
121
122     String urlFilter = urlFilterObject.toWTFString(&exec);
123     if (urlFilter.isEmpty())
124         return ContentExtensionError::JSONInvalidURLFilterInTrigger;
125
126     trigger.urlFilter = urlFilter;
127
128     const JSValue urlFilterCaseValue = triggerObject.get(&exec, Identifier::fromString(&exec, "url-filter-is-case-sensitive"));
129     if (urlFilterCaseValue && !exec.hadException() && urlFilterCaseValue.isBoolean())
130         trigger.urlFilterIsCaseSensitive = urlFilterCaseValue.toBoolean(&exec);
131
132     const JSValue resourceTypeValue = triggerObject.get(&exec, Identifier::fromString(&exec, "resource-type"));
133     if (!exec.hadException() && resourceTypeValue.isObject()) {
134         auto typeFlagsError = getTypeFlags(exec, resourceTypeValue, trigger.flags, readResourceType);
135         if (typeFlagsError)
136             return typeFlagsError;
137     } else if (!resourceTypeValue.isUndefined())
138         return ContentExtensionError::JSONInvalidTriggerFlagsArray;
139
140     const JSValue loadTypeValue = triggerObject.get(&exec, Identifier::fromString(&exec, "load-type"));
141     if (!exec.hadException() && loadTypeValue.isObject()) {
142         auto typeFlagsError = getTypeFlags(exec, loadTypeValue, trigger.flags, readLoadType);
143         if (typeFlagsError)
144             return typeFlagsError;
145     } else if (!loadTypeValue.isUndefined())
146         return ContentExtensionError::JSONInvalidTriggerFlagsArray;
147
148     const JSValue ifDomain = triggerObject.get(&exec, Identifier::fromString(&exec, "if-domain"));
149     if (!exec.hadException() && ifDomain.isObject()) {
150         auto ifDomainError = getDomainList(exec, asObject(ifDomain), trigger.domains);
151         if (ifDomainError)
152             return ifDomainError;
153         if (trigger.domains.isEmpty())
154             return ContentExtensionError::JSONInvalidDomainList;
155         ASSERT(trigger.domainCondition == Trigger::DomainCondition::None);
156         trigger.domainCondition = Trigger::DomainCondition::IfDomain;
157     } else if (!ifDomain.isUndefined())
158         return ContentExtensionError::JSONInvalidDomainList;
159     
160     const JSValue unlessDomain = triggerObject.get(&exec, Identifier::fromString(&exec, "unless-domain"));
161     if (!exec.hadException() && unlessDomain.isObject()) {
162         if (trigger.domainCondition != Trigger::DomainCondition::None)
163             return ContentExtensionError::JSONUnlessAndIfDomain;
164         auto unlessDomainError = getDomainList(exec, asObject(unlessDomain), trigger.domains);
165         if (unlessDomainError)
166             return unlessDomainError;
167         if (trigger.domains.isEmpty())
168             return ContentExtensionError::JSONInvalidDomainList;
169         trigger.domainCondition = Trigger::DomainCondition::UnlessDomain;
170     } else if (!unlessDomain.isUndefined())
171         return ContentExtensionError::JSONInvalidDomainList;
172
173     return { };
174 }
175
176 static std::error_code loadAction(ExecState& exec, const JSObject& ruleObject, Action& action)
177 {
178     const JSValue actionObject = ruleObject.get(&exec, Identifier::fromString(&exec, "action"));
179     if (!actionObject || exec.hadException() || !actionObject.isObject())
180         return ContentExtensionError::JSONInvalidAction;
181
182     const JSValue typeObject = actionObject.get(&exec, Identifier::fromString(&exec, "type"));
183     if (!typeObject || exec.hadException() || !typeObject.isString())
184         return ContentExtensionError::JSONInvalidActionType;
185
186     String actionType = typeObject.toWTFString(&exec);
187
188     if (actionType == "block")
189         action = ActionType::BlockLoad;
190     else if (actionType == "ignore-previous-rules")
191         action = ActionType::IgnorePreviousRules;
192     else if (actionType == "block-cookies")
193         action = ActionType::BlockCookies;
194     else if (actionType == "css-display-none") {
195         JSValue selector = actionObject.get(&exec, Identifier::fromString(&exec, "selector"));
196         if (!selector || exec.hadException() || !selector.isString())
197             return ContentExtensionError::JSONInvalidCSSDisplayNoneActionType;
198
199         action = Action(ActionType::CSSDisplayNoneSelector, selector.toWTFString(&exec));
200     } else
201         return ContentExtensionError::JSONInvalidActionType;
202
203     return { };
204 }
205
206 static std::error_code loadRule(ExecState& exec, const JSObject& ruleObject, Vector<ContentExtensionRule>& ruleList)
207 {
208     Trigger trigger;
209     auto triggerError = loadTrigger(exec, ruleObject, trigger);
210     if (triggerError)
211         return triggerError;
212
213     Action action;
214     auto actionError = loadAction(exec, ruleObject, action);
215     if (actionError)
216         return actionError;
217
218     ruleList.append(ContentExtensionRule(trigger, action));
219     return { };
220 }
221
222 static std::error_code loadEncodedRules(ExecState& exec, const String& rules, Vector<ContentExtensionRule>& ruleList)
223 {
224     // FIXME: JSONParse should require callbacks instead of an ExecState.
225     const JSValue decodedRules = JSONParse(&exec, rules);
226
227     if (exec.hadException() || !decodedRules)
228         return ContentExtensionError::JSONInvalid;
229
230     if (!decodedRules.isObject())
231         return ContentExtensionError::JSONTopLevelStructureNotAnObject;
232
233     const JSObject* topLevelObject = decodedRules.toObject(&exec);
234     if (!topLevelObject || exec.hadException())
235         return ContentExtensionError::JSONTopLevelStructureNotAnObject;
236     
237     if (!isJSArray(topLevelObject))
238         return ContentExtensionError::JSONTopLevelStructureNotAnArray;
239
240     const JSArray* topLevelArray = jsCast<const JSArray*>(topLevelObject);
241
242     Vector<ContentExtensionRule> localRuleList;
243
244     unsigned length = topLevelArray->length();
245     const unsigned maxRuleCount = 50000;
246     if (length > maxRuleCount)
247         return ContentExtensionError::JSONTooManyRules;
248     for (unsigned i = 0; i < length; ++i) {
249         const JSValue value = topLevelArray->getIndex(&exec, i);
250         if (exec.hadException() || !value)
251             return ContentExtensionError::JSONInvalidObjectInTopLevelArray;
252
253         const JSObject* ruleObject = value.toObject(&exec);
254         if (!ruleObject || exec.hadException())
255             return ContentExtensionError::JSONInvalidRule;
256
257         auto error = loadRule(exec, *ruleObject, localRuleList);
258         if (error)
259             return error;
260     }
261
262     ruleList = WTF::move(localRuleList);
263     return { };
264 }
265
266 std::error_code parseRuleList(const String& rules, Vector<ContentExtensionRule>& ruleList)
267 {
268 #if CONTENT_EXTENSIONS_PERFORMANCE_REPORTING
269     double loadExtensionStartTime = monotonicallyIncreasingTime();
270 #endif
271     RefPtr<VM> vm = VM::create();
272
273     JSLockHolder locker(vm.get());
274     JSGlobalObject* globalObject = JSGlobalObject::create(*vm, JSGlobalObject::createStructure(*vm, jsNull()));
275
276     ExecState* exec = globalObject->globalExec();
277     auto error = loadEncodedRules(*exec, rules, ruleList);
278
279     vm = nullptr;
280
281     if (error)
282         return error;
283
284     if (ruleList.isEmpty())
285         return ContentExtensionError::JSONContainsNoRules;
286
287 #if CONTENT_EXTENSIONS_PERFORMANCE_REPORTING
288     double loadExtensionEndTime = monotonicallyIncreasingTime();
289     dataLogF("Time spent loading extension %f\n", (loadExtensionEndTime - loadExtensionStartTime));
290 #endif
291
292     return { };
293 }
294
295 } // namespace ContentExtensions
296 } // namespace WebCore
297
298 #endif // ENABLE(CONTENT_EXTENSIONS)