f962138a1ec333ec71c15021b47f43cc071dfcff
[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, ContentExtensionRule::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, ContentExtensionRule::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.type = ExtensionActionType::BlockLoad;
97     else if (actionType == "ignore-previous-rules")
98         action.type = ExtensionActionType::IgnorePreviousRules;
99     else if (actionType == "block-cookies")
100         action.type = ExtensionActionType::BlockCookies;
101     else if (actionType != "block" && actionType != "") {
102         WTFLogAlways("Unrecognized action: \"%s\"", actionType.utf8().data());
103         return false;
104     }
105
106     return true;
107 }
108
109 static void loadRule(ExecState& exec, JSObject& ruleObject, Vector<ContentExtensionRule>& ruleList)
110 {
111     ContentExtensionRule::Trigger trigger;
112     if (!loadTrigger(exec, ruleObject, trigger))
113         return;
114
115     ContentExtensionRule::Action action;
116     if (!loadAction(exec, ruleObject, action))
117         return;
118
119     ruleList.append(ContentExtensionRule(trigger, action));
120 }
121
122 static Vector<ContentExtensionRule> loadEncodedRules(ExecState& exec, const String& rules)
123 {
124     JSValue decodedRules = JSONParse(&exec, rules);
125
126     if (exec.hadException() || !decodedRules) {
127         WTFLogAlways("Failed to parse the JSON string.");
128         return Vector<ContentExtensionRule>();
129     }
130
131     if (decodedRules.isObject()) {
132         JSObject* topLevelObject = decodedRules.toObject(&exec);
133         if (!topLevelObject || exec.hadException()) {
134             WTFLogAlways("Invalid input, the top level structure is not an object.");
135             return Vector<ContentExtensionRule>();
136         }
137
138         if (!isJSArray(topLevelObject)) {
139             WTFLogAlways("Invalid input, the top level object is not an array.");
140             return Vector<ContentExtensionRule>();
141         }
142
143         Vector<ContentExtensionRule> ruleList;
144         JSArray* topLevelArray = jsCast<JSArray*>(topLevelObject);
145
146         unsigned length = topLevelArray->length();
147         for (unsigned i = 0; i < length; ++i) {
148             JSValue value = topLevelArray->getIndex(&exec, i);
149             if (exec.hadException() || !value) {
150                 WTFLogAlways("Invalid object in the array.");
151                 continue;
152             }
153
154             JSObject* ruleObject = value.toObject(&exec);
155             if (!ruleObject || exec.hadException()) {
156                 WTFLogAlways("Invalid rule");
157                 continue;
158             }
159             loadRule(exec, *ruleObject, ruleList);
160         }
161         return ruleList;
162     }
163     return Vector<ContentExtensionRule>();
164 }
165
166 Vector<ContentExtensionRule> createRuleList(const String& rules)
167 {
168 #if CONTENT_EXTENSIONS_PERFORMANCE_REPORTING
169     double loadExtensionStartTime = monotonicallyIncreasingTime();
170 #endif
171     RefPtr<VM> vm = VM::create();
172
173     JSLockHolder locker(vm.get());
174     JSGlobalObject* globalObject = JSGlobalObject::create(*vm, JSGlobalObject::createStructure(*vm, jsNull()));
175
176     ExecState* exec = globalObject->globalExec();
177     Vector<ContentExtensionRule> ruleList = loadEncodedRules(*exec, rules);
178
179     vm.clear();
180
181     if (ruleList.isEmpty())
182         WTFLogAlways("Empty extension.");
183
184 #if CONTENT_EXTENSIONS_PERFORMANCE_REPORTING
185     double loadExtensionEndTime = monotonicallyIncreasingTime();
186     dataLogF("Time spent loading extension %s: %f\n", identifier.utf8().data(), (loadExtensionEndTime - loadExtensionStartTime));
187 #endif
188
189     return ruleList;
190 }
191
192 } // namespace ExtensionsManager
193 } // namespace ContentExtensions
194 } // namespace WebCore
195
196 #endif // ENABLE(CONTENT_EXTENSIONS)