[ContentExtensions] Prepare for compiling stylesheets of selectors to be used on...
[WebKit-https.git] / Tools / TestWebKitAPI / Tests / WebCore / ContentExtensions.cpp
1 /*
2  * Copyright (C) 2015 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
28 #include "PlatformUtilities.h"
29 #include <JavaScriptCore/InitializeThreading.h>
30 #include <WebCore/ContentExtensionCompiler.h>
31 #include <WebCore/ContentExtensionsBackend.h>
32 #include <WebCore/NFA.h>
33 #include <WebCore/ResourceLoadInfo.h>
34 #include <WebCore/URL.h>
35 #include <WebCore/URLFilterParser.h>
36 #include <wtf/MainThread.h>
37 #include <wtf/RunLoop.h>
38 #include <wtf/text/CString.h>
39
40 namespace WebCore {
41 namespace ContentExtensions {
42 inline std::ostream& operator<<(std::ostream& os, const ActionType& action)
43 {
44     switch (action) {
45     case ActionType::BlockLoad:
46         return os << "ContentFilterAction::BlockLoad";
47     case ActionType::BlockCookies:
48         return os << "ContentFilterAction::BlockCookies";
49     case ActionType::CSSDisplayNone:
50         return os << "ContentFilterAction::CSSDisplayNone";
51     case ActionType::IgnorePreviousRules:
52         return os << "ContentFilterAction::IgnorePreviousRules";
53     case ActionType::InvalidAction:
54         return os << "ContentFilterAction::InvalidAction";
55     }
56 }
57 }
58 }
59
60 using namespace WebCore;
61
62 namespace TestWebKitAPI {
63
64 class ContentExtensionTest : public testing::Test {
65 public:
66     virtual void SetUp()
67     {
68         WTF::initializeMainThread();
69         JSC::initializeThreading();
70         RunLoop::initializeMainRunLoop();
71     }
72 };
73
74 class InMemoryCompiledContentExtension : public ContentExtensions::CompiledContentExtension {
75 public:
76     static RefPtr<InMemoryCompiledContentExtension> create(ContentExtensions::CompiledContentExtensionData&& data)
77     {
78         return adoptRef(new InMemoryCompiledContentExtension(WTF::move(data)));
79     }
80
81     virtual ~InMemoryCompiledContentExtension()
82     {
83     }
84
85     virtual const ContentExtensions::DFABytecode* bytecode() const override { return m_data.bytecode.data(); }
86     virtual unsigned bytecodeLength() const override { return m_data.bytecode.size(); }
87     virtual const ContentExtensions::SerializedActionByte* actions() const override { return m_data.actions.data(); }
88     virtual unsigned actionsLength() const override { return m_data.actions.size(); }
89
90 private:
91     InMemoryCompiledContentExtension(ContentExtensions::CompiledContentExtensionData&& data)
92         : m_data(WTF::move(data))
93     {
94     }
95
96     ContentExtensions::CompiledContentExtensionData m_data;
97 };
98
99 void static testRequest(ContentExtensions::ContentExtensionsBackend contentExtensionsBackend, const ResourceLoadInfo& resourceLoadInfo, Vector<ContentExtensions::ActionType> expectedActions)
100 {
101     auto actions = contentExtensionsBackend.actionsForResourceLoad(resourceLoadInfo);
102     EXPECT_EQ(expectedActions.size(), actions.size());
103     if (expectedActions.size() != actions.size())
104         return;
105
106     for (unsigned i = 0; i < expectedActions.size(); ++i)
107         EXPECT_EQ(expectedActions[i], actions[i].type());
108 }
109
110 ResourceLoadInfo mainDocumentRequest(const char* url, ResourceType resourceType = ResourceType::Document)
111 {
112     return { URL(URL(), url), URL(URL(), url), resourceType };
113 }
114
115 const char* basicFilter = "[{\"action\":{\"type\":\"block\"},\"trigger\":{\"url-filter\":\".*webkit.org\"}}]";
116
117 TEST_F(ContentExtensionTest, Basic)
118 {
119     auto extensionData = ContentExtensions::compileRuleList(basicFilter);
120     auto extension = InMemoryCompiledContentExtension::create(WTF::move(extensionData));
121
122     ContentExtensions::ContentExtensionsBackend backend;
123     backend.addContentExtension("testFilter", extension);
124
125     testRequest(backend, mainDocumentRequest("http://webkit.org/"), { ContentExtensions::ActionType::BlockLoad });
126 }
127
128 TEST_F(ContentExtensionTest, RangeBasic)
129 {
130     const char* rangeBasicFilter = "[{\"action\":{\"type\":\"block\"},\"trigger\":{\"url-filter\":\".*w[0-9]c\", \"url-filter-is-case-sensitive\":true}},{\"action\":{\"type\":\"block-cookies\"},\"trigger\":{\"url-filter\":\".*[A-H][a-z]cko\", \"url-filter-is-case-sensitive\":true}}]";
131     auto extensionData = ContentExtensions::compileRuleList(rangeBasicFilter);
132     auto extension = InMemoryCompiledContentExtension::create(WTF::move(extensionData));
133
134     ContentExtensions::ContentExtensionsBackend backend;
135     backend.addContentExtension("PatternNestedGroupsFilter", extension);
136
137     testRequest(backend, mainDocumentRequest("http://w3c.org"), { ContentExtensions::ActionType::BlockLoad });
138     testRequest(backend, mainDocumentRequest("w2c://whatwg.org/"), { ContentExtensions::ActionType::BlockLoad });
139     testRequest(backend, mainDocumentRequest("http://webkit.org/w0c"), { ContentExtensions::ActionType::BlockLoad });
140     testRequest(backend, mainDocumentRequest("http://webkit.org/wac"), { });
141     testRequest(backend, mainDocumentRequest("http://webkit.org/wAc"), { });
142
143     // Note: URL parsing and canonicalization lowercase the scheme and hostname.
144     testRequest(backend, mainDocumentRequest("Aacko://webkit.org"), { });
145     testRequest(backend, mainDocumentRequest("aacko://webkit.org"), { });
146     testRequest(backend, mainDocumentRequest("http://gCcko.org/"), { });
147     testRequest(backend, mainDocumentRequest("http://gccko.org/"), { });
148
149     testRequest(backend, mainDocumentRequest("http://webkit.org/Gecko"), { ContentExtensions::ActionType::BlockCookies });
150     testRequest(backend, mainDocumentRequest("http://webkit.org/gecko"), { });
151     testRequest(backend, mainDocumentRequest("http://webkit.org/GEcko"), { });
152 }
153
154 TEST_F(ContentExtensionTest, RangeExclusionGeneratingUniversalTransition)
155 {
156     // Transition of the type ([^X]X) effictively transition on every input.
157     const char* rangeExclusionGeneratingUniversalTransitionFilter = "[{\"action\":{\"type\":\"block\"},\"trigger\":{\"url-filter\":\".*[^a]+afoobar\"}}]";
158     auto extensionData = ContentExtensions::compileRuleList(rangeExclusionGeneratingUniversalTransitionFilter);
159     auto extension = InMemoryCompiledContentExtension::create(WTF::move(extensionData));
160
161     ContentExtensions::ContentExtensionsBackend backend;
162     backend.addContentExtension("PatternNestedGroupsFilter", extension);
163
164     testRequest(backend, mainDocumentRequest("http://w3c.org"), { });
165
166     testRequest(backend, mainDocumentRequest("http://w3c.org/foobafoobar"), { ContentExtensions::ActionType::BlockLoad });
167     testRequest(backend, mainDocumentRequest("http://w3c.org/foobarfoobar"), { });
168     testRequest(backend, mainDocumentRequest("http://w3c.org/FOOBAFOOBAR"), { ContentExtensions::ActionType::BlockLoad });
169     testRequest(backend, mainDocumentRequest("http://w3c.org/FOOBARFOOBAR"), { });
170
171     // The character before the "a" prefix cannot be another "a".
172     testRequest(backend, mainDocumentRequest("http://w3c.org/aafoobar"), { });
173     testRequest(backend, mainDocumentRequest("http://w3c.org/Aafoobar"), { });
174     testRequest(backend, mainDocumentRequest("http://w3c.org/aAfoobar"), { });
175     testRequest(backend, mainDocumentRequest("http://w3c.org/AAfoobar"), { });
176 }
177
178 const char* patternsStartingWithGroupFilter = "[{\"action\":{\"type\":\"block\"},\"trigger\":{\"url-filter\":\"(http://whatwg\\\\.org/)?webkit\134\134.org\"}}]";
179
180 TEST_F(ContentExtensionTest, PatternStartingWithGroup)
181 {
182     auto extensionData = ContentExtensions::compileRuleList(patternsStartingWithGroupFilter);
183     auto extension = InMemoryCompiledContentExtension::create(WTF::move(extensionData));
184
185     ContentExtensions::ContentExtensionsBackend backend;
186     backend.addContentExtension("PatternNestedGroupsFilter", extension);
187
188     testRequest(backend, mainDocumentRequest("http://whatwg.org/webkit.org/"), { ContentExtensions::ActionType::BlockLoad });
189     testRequest(backend, mainDocumentRequest("http://whatwg.org/webkit.org"), { ContentExtensions::ActionType::BlockLoad });
190     testRequest(backend, mainDocumentRequest("http://webkit.org/"), { });
191     testRequest(backend, mainDocumentRequest("http://whatwg.org/"), { });
192     testRequest(backend, mainDocumentRequest("http://whatwg.org"), { });
193 }
194
195 const char* patternNestedGroupsFilter = "[{\"action\":{\"type\":\"block\"},\"trigger\":{\"url-filter\":\"http://webkit\\\\.org/(foo(bar)*)+\"}}]";
196
197 TEST_F(ContentExtensionTest, PatternNestedGroups)
198 {
199     auto extensionData = ContentExtensions::compileRuleList(patternNestedGroupsFilter);
200     auto extension = InMemoryCompiledContentExtension::create(WTF::move(extensionData));
201
202     ContentExtensions::ContentExtensionsBackend backend;
203     backend.addContentExtension("PatternNestedGroupsFilter", extension);
204
205     testRequest(backend, mainDocumentRequest("http://webkit.org/foo"), { ContentExtensions::ActionType::BlockLoad });
206     testRequest(backend, mainDocumentRequest("http://webkit.org/foobar"), { ContentExtensions::ActionType::BlockLoad });
207     testRequest(backend, mainDocumentRequest("http://webkit.org/foobarbar"), { ContentExtensions::ActionType::BlockLoad });
208     testRequest(backend, mainDocumentRequest("http://webkit.org/foofoobar"), { ContentExtensions::ActionType::BlockLoad });
209     testRequest(backend, mainDocumentRequest("http://webkit.org/foobarfoobar"), { ContentExtensions::ActionType::BlockLoad });
210     testRequest(backend, mainDocumentRequest("http://webkit.org/foob"), { ContentExtensions::ActionType::BlockLoad });
211     testRequest(backend, mainDocumentRequest("http://webkit.org/foor"), { ContentExtensions::ActionType::BlockLoad });
212
213     testRequest(backend, mainDocumentRequest("http://webkit.org/"), { });
214     testRequest(backend, mainDocumentRequest("http://webkit.org/bar"), { });
215     testRequest(backend, mainDocumentRequest("http://webkit.org/fobar"), { });
216 }
217
218 const char* matchPastEndOfStringFilter = "[{\"action\":{\"type\":\"block\"},\"trigger\":{\"url-filter\":\".+\"}}]";
219
220 TEST_F(ContentExtensionTest, MatchPastEndOfString)
221 {
222     auto extensionData = ContentExtensions::compileRuleList(matchPastEndOfStringFilter);
223     auto extension = InMemoryCompiledContentExtension::create(WTF::move(extensionData));
224
225     ContentExtensions::ContentExtensionsBackend backend;
226     backend.addContentExtension("MatchPastEndOfString", extension);
227
228     testRequest(backend, mainDocumentRequest("http://webkit.org/"), { ContentExtensions::ActionType::BlockLoad });
229     testRequest(backend, mainDocumentRequest("http://webkit.org/foo"), { ContentExtensions::ActionType::BlockLoad });
230     testRequest(backend, mainDocumentRequest("http://webkit.org/foobar"), { ContentExtensions::ActionType::BlockLoad });
231     testRequest(backend, mainDocumentRequest("http://webkit.org/foobarbar"), { ContentExtensions::ActionType::BlockLoad });
232     testRequest(backend, mainDocumentRequest("http://webkit.org/foofoobar"), { ContentExtensions::ActionType::BlockLoad });
233     testRequest(backend, mainDocumentRequest("http://webkit.org/foobarfoobar"), { ContentExtensions::ActionType::BlockLoad });
234     testRequest(backend, mainDocumentRequest("http://webkit.org/foob"), { ContentExtensions::ActionType::BlockLoad });
235     testRequest(backend, mainDocumentRequest("http://webkit.org/foor"), { ContentExtensions::ActionType::BlockLoad });
236 }
237
238 const char* startOfLineAssertionFilter = "[{\"action\":{\"type\":\"block\"},\"trigger\":{\"url-filter\":\"^foobar\"}}]";
239
240 TEST_F(ContentExtensionTest, StartOfLineAssertion)
241 {
242     auto extensionData = ContentExtensions::compileRuleList(startOfLineAssertionFilter);
243     auto extension = InMemoryCompiledContentExtension::create(WTF::move(extensionData));
244
245     ContentExtensions::ContentExtensionsBackend backend;
246     backend.addContentExtension("StartOfLineAssertion", extension);
247
248     testRequest(backend, mainDocumentRequest("foobar://webkit.org/foobar"), { ContentExtensions::ActionType::BlockLoad });
249     testRequest(backend, mainDocumentRequest("foobars:///foobar"), { ContentExtensions::ActionType::BlockLoad });
250     testRequest(backend, mainDocumentRequest("foobarfoobar:///foobarfoobarfoobar"), { ContentExtensions::ActionType::BlockLoad });
251
252     testRequest(backend, mainDocumentRequest("http://webkit.org/foobarfoo"), { });
253     testRequest(backend, mainDocumentRequest("http://webkit.org/foobarf"), { });
254     testRequest(backend, mainDocumentRequest("http://foobar.org/"), { });
255     testRequest(backend, mainDocumentRequest("http://foobar.org/"), { });
256 }
257
258 const char* endOfLineAssertionFilter = "[{\"action\":{\"type\":\"block\"},\"trigger\":{\"url-filter\":\".*foobar$\"}}]";
259
260 TEST_F(ContentExtensionTest, EndOfLineAssertion)
261 {
262     auto extensionData = ContentExtensions::compileRuleList(endOfLineAssertionFilter);
263     auto extension = InMemoryCompiledContentExtension::create(WTF::move(extensionData));
264
265     ContentExtensions::ContentExtensionsBackend backend;
266     backend.addContentExtension("EndOfLineAssertion", extension);
267
268     testRequest(backend, mainDocumentRequest("http://webkit.org/foobar"), { ContentExtensions::ActionType::BlockLoad });
269     testRequest(backend, mainDocumentRequest("file:///foobar"), { ContentExtensions::ActionType::BlockLoad });
270     testRequest(backend, mainDocumentRequest("file:///foobarfoobarfoobar"), { ContentExtensions::ActionType::BlockLoad });
271
272     testRequest(backend, mainDocumentRequest("http://webkit.org/foobarfoo"), { });
273     testRequest(backend, mainDocumentRequest("http://webkit.org/foobarf"), { });
274 }
275
276 TEST_F(ContentExtensionTest, EndOfLineAssertionWithInvertedCharacterSet)
277 {
278     const char* endOfLineAssertionWithInvertedCharacterSetFilter = "[{\"action\":{\"type\":\"block\"},\"trigger\":{\"url-filter\":\".*[^y]$\"}}]";
279     auto extensionData = ContentExtensions::compileRuleList(endOfLineAssertionWithInvertedCharacterSetFilter);
280     auto extension = InMemoryCompiledContentExtension::create(WTF::move(extensionData));
281
282     ContentExtensions::ContentExtensionsBackend backend;
283     backend.addContentExtension("EndOfLineAssertion", extension);
284
285     testRequest(backend, mainDocumentRequest("http://webkit.org/"), { ContentExtensions::ActionType::BlockLoad });
286     testRequest(backend, mainDocumentRequest("http://webkit.org/a"), { ContentExtensions::ActionType::BlockLoad });
287     testRequest(backend, mainDocumentRequest("http://webkit.org/foobar"), { ContentExtensions::ActionType::BlockLoad });
288     testRequest(backend, mainDocumentRequest("http://webkit.org/Ya"), { ContentExtensions::ActionType::BlockLoad });
289     testRequest(backend, mainDocumentRequest("http://webkit.org/yFoobar"), { ContentExtensions::ActionType::BlockLoad });
290     testRequest(backend, mainDocumentRequest("http://webkit.org/y"), { });
291     testRequest(backend, mainDocumentRequest("http://webkit.org/Y"), { });
292     testRequest(backend, mainDocumentRequest("http://webkit.org/foobary"), { });
293     testRequest(backend, mainDocumentRequest("http://webkit.org/foobarY"), { });
294 }
295     
296 const char* loadTypeFilter = "[{\"action\":{\"type\":\"block\"},\"trigger\":{\"url-filter\":\".*webkit.org\",\"load-type\":[\"third-party\"]}},"
297     "{\"action\":{\"type\":\"block\"},\"trigger\":{\"url-filter\":\".*whatwg.org\",\"load-type\":[\"first-party\"]}},"
298     "{\"action\":{\"type\":\"block\"},\"trigger\":{\"url-filter\":\".*alwaysblock.pdf\"}}]";
299
300 TEST_F(ContentExtensionTest, LoadType)
301 {
302     auto extensionData = ContentExtensions::compileRuleList(loadTypeFilter);
303     auto extension = InMemoryCompiledContentExtension::create(WTF::move(extensionData));
304         
305     ContentExtensions::ContentExtensionsBackend backend;
306     backend.addContentExtension("LoadTypeFilter", extension);
307         
308     testRequest(backend, mainDocumentRequest("http://webkit.org"), { });
309     testRequest(backend, {URL(URL(), "http://webkit.org"), URL(URL(), "http://not_webkit.org"), ResourceType::Document}, { ContentExtensions::ActionType::BlockLoad });
310         
311     testRequest(backend, mainDocumentRequest("http://whatwg.org"), { ContentExtensions::ActionType::BlockLoad });
312     testRequest(backend, {URL(URL(), "http://whatwg.org"), URL(URL(), "http://not_whatwg.org"), ResourceType::Document}, { });
313     
314     testRequest(backend, mainDocumentRequest("http://foobar.org/alwaysblock.pdf"), { ContentExtensions::ActionType::BlockLoad });
315     testRequest(backend, {URL(URL(), "http://foobar.org/alwaysblock.pdf"), URL(URL(), "http://not_foobar.org/alwaysblock.pdf"), ResourceType::Document}, { ContentExtensions::ActionType::BlockLoad });
316 }
317     
318 const char* resourceTypeFilter = "[{\"action\":{\"type\":\"block\"},\"trigger\":{\"url-filter\":\".*block_all_types.org\",\"resource-type\":[\"document\",\"image\",\"style-sheet\",\"script\",\"font\",\"raw\",\"svg-document\",\"media\"]}},"
319     "{\"action\":{\"type\":\"block\"},\"trigger\":{\"url-filter\":\".*block_only_images\",\"resource-type\":[\"image\"]}}]";
320     
321 TEST_F(ContentExtensionTest, ResourceType)
322 {
323     auto extensionData = ContentExtensions::compileRuleList(resourceTypeFilter);
324     auto extension = InMemoryCompiledContentExtension::create(WTF::move(extensionData));
325         
326     ContentExtensions::ContentExtensionsBackend backend;
327     backend.addContentExtension("ResourceTypeFilter", extension);
328
329     testRequest(backend, mainDocumentRequest("http://block_all_types.org", ResourceType::Document), { ContentExtensions::ActionType::BlockLoad });
330     testRequest(backend, mainDocumentRequest("http://block_all_types.org", ResourceType::Image), { ContentExtensions::ActionType::BlockLoad });
331     testRequest(backend, mainDocumentRequest("http://block_all_types.org", ResourceType::StyleSheet), { ContentExtensions::ActionType::BlockLoad });
332     testRequest(backend, mainDocumentRequest("http://block_all_types.org", ResourceType::Script), { ContentExtensions::ActionType::BlockLoad });
333     testRequest(backend, mainDocumentRequest("http://block_all_types.org", ResourceType::Font), { ContentExtensions::ActionType::BlockLoad });
334     testRequest(backend, mainDocumentRequest("http://block_all_types.org", ResourceType::Raw), { ContentExtensions::ActionType::BlockLoad });
335     testRequest(backend, mainDocumentRequest("http://block_all_types.org", ResourceType::SVGDocument), { ContentExtensions::ActionType::BlockLoad });
336     testRequest(backend, mainDocumentRequest("http://block_all_types.org", ResourceType::Media), { ContentExtensions::ActionType::BlockLoad });
337     testRequest(backend, mainDocumentRequest("http://block_only_images.org", ResourceType::Image), { ContentExtensions::ActionType::BlockLoad });
338     testRequest(backend, mainDocumentRequest("http://block_only_images.org", ResourceType::Document), { });
339 }
340
341 static void testPatternStatus(const char* pattern, ContentExtensions::URLFilterParser::ParseStatus status)
342 {
343     ContentExtensions::NFA nfa;
344     ContentExtensions::URLFilterParser parser(nfa);
345     EXPECT_EQ(status, parser.addPattern(ASCIILiteral(pattern), false, 0));
346 }
347     
348 TEST_F(ContentExtensionTest, ParsingFailures)
349 {
350     testPatternStatus("a*b?.*.?[a-z]?[a-z]*", ContentExtensions::URLFilterParser::ParseStatus::MatchesEverything);
351     testPatternStatus("a*b?.*.?[a-z]?[a-z]+", ContentExtensions::URLFilterParser::ParseStatus::Ok);
352     testPatternStatus("a*b?.*.?[a-z]?[a-z]", ContentExtensions::URLFilterParser::ParseStatus::Ok);
353     // FIXME: Add regexes that cause each parse status.
354 }
355
356 } // namespace TestWebKitAPI