Prepare to use CSS selectors in content extensions.
[WebKit-https.git] / Source / WebCore / contentextensions / ContentExtensionsManager.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 "ContentExtensionsManager.h"
28
29 #if ENABLE(CONTENT_EXTENSIONS)
30
31 #include "ContentExtensionRule.h"
32 #include "ContentExtensionsBackend.h"
33 #include "ContentExtensionsDebugging.h"
34 #include <JavaScriptCore/IdentifierInlines.h>
35 #include <JavaScriptCore/JSCJSValueInlines.h>
36 #include <JavaScriptCore/JSGlobalObject.h>
37 #include <JavaScriptCore/JSONObject.h>
38 #include <JavaScriptCore/StructureInlines.h>
39 #include <JavaScriptCore/VM.h>
40 #include <wtf/CurrentTime.h>
41 #include <wtf/text/WTFString.h>
42
43 using namespace JSC;
44
45 namespace WebCore {
46
47 namespace ContentExtensions {
48
49 namespace ExtensionsManager {
50
51 static bool loadTrigger(ExecState& exec, JSObject& ruleObject, Trigger& trigger)
52 {
53     JSValue triggerObject = ruleObject.get(&exec, Identifier(&exec, "trigger"));
54     if (!triggerObject || exec.hadException() || !triggerObject.isObject()) {
55         WTFLogAlways("Invalid trigger object.");
56         return false;
57     }
58
59     JSValue urlFilterObject = triggerObject.get(&exec, Identifier(&exec, "url-filter"));
60     if (!urlFilterObject || exec.hadException() || !urlFilterObject.isString()) {
61         WTFLogAlways("Invalid url-filter object.");
62         return false;
63     }
64
65     String urlFilter = urlFilterObject.toWTFString(&exec);
66     if (urlFilter.isEmpty()) {
67         WTFLogAlways("Invalid url-filter object. The url is empty.");
68         return false;
69     }
70     trigger.urlFilter = urlFilter;
71
72     JSValue urlFilterCaseObject = triggerObject.get(&exec, Identifier(&exec, "url-filter-is-case-sensitive"));
73     if (urlFilterCaseObject && !exec.hadException() && urlFilterCaseObject.isBoolean())
74         trigger.urlFilterIsCaseSensitive = urlFilterCaseObject.toBoolean(&exec);
75
76     return true;
77 }
78
79 static bool loadAction(ExecState& exec, JSObject& ruleObject, Action& action)
80 {
81     JSValue actionObject = ruleObject.get(&exec, Identifier(&exec, "action"));
82     if (!actionObject || exec.hadException() || !actionObject.isObject()) {
83         WTFLogAlways("Invalid action object.");
84         return false;
85     }
86
87     JSValue typeObject = actionObject.get(&exec, Identifier(&exec, "type"));
88     if (!typeObject || exec.hadException() || !typeObject.isString()) {
89         WTFLogAlways("Invalid url-filter object.");
90         return false;
91     }
92
93     String actionType = typeObject.toWTFString(&exec);
94
95     if (actionType == "block")
96         action = ActionType::BlockLoad;
97     else if (actionType == "ignore-previous-rules")
98         action = ActionType::IgnorePreviousRules;
99     else if (actionType == "block-cookies")
100         action = ActionType::BlockCookies;
101     else if (actionType == "css-display-none") {
102         JSValue selector = actionObject.get(&exec, Identifier(&exec, "selector"));
103         if (!selector || exec.hadException() || !selector.isString()) {
104             WTFLogAlways("css-display-none action type requires a selector");
105             return false;
106         }
107         action = Action(ActionType::CSSDisplayNone, selector.toWTFString(&exec));
108     } else {
109         WTFLogAlways("Unrecognized action: \"%s\"", actionType.utf8().data());
110         return false;
111     }
112
113     return true;
114 }
115
116 static void loadRule(ExecState& exec, JSObject& ruleObject, Vector<ContentExtensionRule>& ruleList)
117 {
118     Trigger trigger;
119     if (!loadTrigger(exec, ruleObject, trigger))
120         return;
121
122     Action action;
123     if (!loadAction(exec, ruleObject, action))
124         return;
125
126     ruleList.append(ContentExtensionRule(trigger, action));
127 }
128
129 static Vector<ContentExtensionRule> loadEncodedRules(ExecState& exec, const String& rules)
130 {
131     JSValue decodedRules = JSONParse(&exec, rules);
132
133     if (exec.hadException() || !decodedRules) {
134         WTFLogAlways("Failed to parse the JSON string.");
135         return Vector<ContentExtensionRule>();
136     }
137
138     if (decodedRules.isObject()) {
139         JSObject* topLevelObject = decodedRules.toObject(&exec);
140         if (!topLevelObject || exec.hadException()) {
141             WTFLogAlways("Invalid input, the top level structure is not an object.");
142             return Vector<ContentExtensionRule>();
143         }
144
145         if (!isJSArray(topLevelObject)) {
146             WTFLogAlways("Invalid input, the top level object is not an array.");
147             return Vector<ContentExtensionRule>();
148         }
149
150         Vector<ContentExtensionRule> ruleList;
151         JSArray* topLevelArray = jsCast<JSArray*>(topLevelObject);
152
153         unsigned length = topLevelArray->length();
154         for (unsigned i = 0; i < length; ++i) {
155             JSValue value = topLevelArray->getIndex(&exec, i);
156             if (exec.hadException() || !value) {
157                 WTFLogAlways("Invalid object in the array.");
158                 continue;
159             }
160
161             JSObject* ruleObject = value.toObject(&exec);
162             if (!ruleObject || exec.hadException()) {
163                 WTFLogAlways("Invalid rule");
164                 continue;
165             }
166             loadRule(exec, *ruleObject, ruleList);
167         }
168         return ruleList;
169     }
170     return Vector<ContentExtensionRule>();
171 }
172
173 Vector<ContentExtensionRule> createRuleList(const String& rules)
174 {
175 #if CONTENT_EXTENSIONS_PERFORMANCE_REPORTING
176     double loadExtensionStartTime = monotonicallyIncreasingTime();
177 #endif
178     RefPtr<VM> vm = VM::create();
179
180     JSLockHolder locker(vm.get());
181     JSGlobalObject* globalObject = JSGlobalObject::create(*vm, JSGlobalObject::createStructure(*vm, jsNull()));
182
183     ExecState* exec = globalObject->globalExec();
184     Vector<ContentExtensionRule> ruleList = loadEncodedRules(*exec, rules);
185
186     vm.clear();
187
188     if (ruleList.isEmpty())
189         WTFLogAlways("Empty extension.");
190
191 #if CONTENT_EXTENSIONS_PERFORMANCE_REPORTING
192     double loadExtensionEndTime = monotonicallyIncreasingTime();
193     dataLogF("Time spent loading extension %s: %f\n", identifier.utf8().data(), (loadExtensionEndTime - loadExtensionStartTime));
194 #endif
195
196     return ruleList;
197 }
198
199 } // namespace ExtensionsManager
200 } // namespace ContentExtensions
201 } // namespace WebCore
202
203 #endif // ENABLE(CONTENT_EXTENSIONS)