1/*
2 * Copyright (C) 2019 Igalia S.L.
3 *
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2,1 of the License, or (at your option) any later version.
8 *
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Library General Public License for more details.
13 *
14 * You should have received a copy of the GNU Library General Public License
15 * along with this library; see the file COPYING.LIB. If not, write to
16 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17 * Boston, MA 02110-1301, USA.
18 */
19
20#include "config.h"
21
22#include "TestMain.h"
23#include <wtf/glib/GRefPtr.h>
24#include <wtf/glib/GUniquePtr.h>
25
26static const char* kInvalidJSONSources[] = {
27 // Empty input.
28 "",
29 // Incorrect JSON input.
30 "[",
31 "]",
32 "[{]",
33 "[{ \"trigger: {} }]",
34 "[{ 'trigger': { 'url-filter': '.*' }, 'action': { 'type': 'block' } }]",
35 // Oddball JSON objects.
36 "{}",
37 "[]",
38 "[{}]",
39 // Empty trigger/action.
40 "[{ \"trigger\": {} }]",
41 "[{ \"action\": {} }]",
42 "[{ \"trigger\": {}, \"action\": {} }]",
43 "[{ \"trigger\": { \"url-filter\": \".*\" } }]",
44 "[{ \"trigger\": { \"url-filter\": \".*\" }, \"action\": {} }]",
45 // Empty action type.
46 "[{ \"trigger\": { \"url-filter\": \".*\" }, \"action\": { \"type\": \"\" } }]",
47 // Empty URL filter.
48 "[{ \"trigger\": { \"url-filter\": \"\" }, \"action\": { \"type\": \"block\" } }]",
49};
50
51static const char* kSimpleJSONSource =
52 "[{\n"
53 " \"trigger\": {\n"
54 " \"url-filter\": \".*\"\n"
55 " },\n"
56 " \"action\": {\n"
57 " \"type\": \"block\"\n"
58 " }\n"
59 "}]\n";
60
61namespace WTF {
62
63template <> WebKitUserContentFilter* refGPtr(WebKitUserContentFilter* ptr)
64{
65 if (ptr)
66 webkit_user_content_filter_ref(ptr);
67 return ptr;
68}
69
70template <> void derefGPtr(WebKitUserContentFilter* ptr)
71{
72 if (ptr)
73 webkit_user_content_filter_unref(ptr);
74}
75
76}
77
78class UserContentFilterStoreTest: public Test {
79public:
80 MAKE_GLIB_TEST_FIXTURE(UserContentFilterStoreTest);
81
82 GError* lastError() const
83 {
84 return m_error.get();
85 }
86
87 void resetStore()
88 {
89 auto* oldStore = m_filterStore.get();
90 m_filterStore = newStore();
91 g_assert_true(oldStore != m_filterStore.get());
92 }
93
94 char** fetchIdentifiers()
95 {
96 webkit_user_content_filter_store_fetch_identifiers(m_filterStore.get(), nullptr, [](GObject* sourceObject, GAsyncResult* result, void* userData) {
97 auto* test = static_cast<UserContentFilterStoreTest*>(userData);
98 test->m_filterIds.reset(webkit_user_content_filter_store_fetch_identifiers_finish(WEBKIT_USER_CONTENT_FILTER_STORE(sourceObject), result));
99 g_main_loop_quit(test->m_mainLoop.get());
100 }, this);
101 g_main_loop_run(m_mainLoop.get());
102 g_assert_nonnull(m_filterIds.get());
103 return m_filterIds.get();
104 }
105
106 WebKitUserContentFilter* saveFilter(const char* filterId, const char* source)
107 {
108 GRefPtr<GBytes> sourceBytes = adoptGRef(g_bytes_new_static(source, strlen(source)));
109 webkit_user_content_filter_store_save(m_filterStore.get(), filterId, sourceBytes.get(), nullptr, [](GObject* sourceObject, GAsyncResult* result, void* userData) {
110 auto* test = static_cast<UserContentFilterStoreTest*>(userData);
111 test->m_filter = adoptGRef(webkit_user_content_filter_store_save_finish(WEBKIT_USER_CONTENT_FILTER_STORE(sourceObject), result, &test->m_error.outPtr()));
112 g_main_loop_quit(test->m_mainLoop.get());
113 }, this);
114 g_main_loop_run(m_mainLoop.get());
115 return m_filter.get();
116 }
117
118 WebKitUserContentFilter* saveFilterFromFile(const char* filterId, GFile* file)
119 {
120 webkit_user_content_filter_store_save_from_file(m_filterStore.get(), filterId, file, nullptr, [](GObject* sourceObject, GAsyncResult* result, void* userData) {
121 auto* test = static_cast<UserContentFilterStoreTest*>(userData);
122 test->m_filter = adoptGRef(webkit_user_content_filter_store_save_from_file_finish(WEBKIT_USER_CONTENT_FILTER_STORE(sourceObject), result, &test->m_error.outPtr()));
123 g_main_loop_quit(test->m_mainLoop.get());
124 }, this);
125 g_main_loop_run(m_mainLoop.get());
126 return m_filter.get();
127 }
128
129 WebKitUserContentFilter* loadFilter(const char* filterId)
130 {
131 webkit_user_content_filter_store_load(m_filterStore.get(), filterId, nullptr, [](GObject* sourceObject, GAsyncResult* result, void* userData) {
132 auto* test = static_cast<UserContentFilterStoreTest*>(userData);
133 test->m_filter = adoptGRef(webkit_user_content_filter_store_load_finish(WEBKIT_USER_CONTENT_FILTER_STORE(sourceObject), result, &test->m_error.outPtr()));
134 g_main_loop_quit(test->m_mainLoop.get());
135 }, this);
136 g_main_loop_run(m_mainLoop.get());
137 return m_filter.get();
138 }
139
140 bool removeFilter(const char* filterId)
141 {
142 webkit_user_content_filter_store_remove(m_filterStore.get(), filterId, nullptr, [](GObject* sourceObject, GAsyncResult* result, void* userData) {
143 auto* test = static_cast<UserContentFilterStoreTest*>(userData);
144 test->m_completedOk = !!webkit_user_content_filter_store_remove_finish(WEBKIT_USER_CONTENT_FILTER_STORE(sourceObject), result, &test->m_error.outPtr());
145 g_main_loop_quit(test->m_mainLoop.get());
146 }, this);
147 g_main_loop_run(m_mainLoop.get());
148 return m_completedOk;
149 }
150
151private:
152 GRefPtr<WebKitUserContentFilterStore> newStore()
153 {
154 GUniquePtr<char> storagePath(g_build_filename(dataDirectory(), "content-filters", nullptr));
155 auto store = adoptGRef(webkit_user_content_filter_store_new(storagePath.get()));
156 g_assert_nonnull(store.get());
157 assertObjectIsDeletedWhenTestFinishes(G_OBJECT(store.get()));
158 g_assert_cmpstr(storagePath.get(), ==, webkit_user_content_filter_store_get_path(store.get()));
159 return store;
160 }
161
162 GRefPtr<WebKitUserContentFilterStore> m_filterStore { newStore() };
163 GRefPtr<GMainLoop> m_mainLoop { adoptGRef(g_main_loop_new(nullptr, FALSE)) };
164 GRefPtr<WebKitUserContentFilter> m_filter;
165 GUniquePtr<char*> m_filterIds;
166 GUniqueOutPtr<GError> m_error;
167 bool m_completedOk;
168};
169
170static void testEmptyStore(UserContentFilterStoreTest* test, gconstpointer)
171{
172 g_assert_cmpuint(0, ==, g_strv_length(test->fetchIdentifiers()));
173}
174
175static void testSaveInvalidFilter(UserContentFilterStoreTest* test, gconstpointer)
176{
177 for (unsigned i = 0; i < G_N_ELEMENTS(kInvalidJSONSources); i++) {
178 GUniquePtr<char> filterId(g_strdup_printf("Filter-%u", i));
179 g_assert_null(test->saveFilter(filterId.get(), kInvalidJSONSources[i]));
180 g_assert_error(test->lastError(), WEBKIT_USER_CONTENT_FILTER_ERROR, WEBKIT_USER_CONTENT_FILTER_ERROR_INVALID_SOURCE);
181 }
182}
183
184static void testSaveLoadFilter(UserContentFilterStoreTest* test, gconstpointer)
185{
186 static const char* kFilterId = "SimpleFilter";
187
188 // Trying to load a filter before saving must fail.
189 g_assert_null(test->loadFilter(kFilterId));
190 g_assert_error(test->lastError(), WEBKIT_USER_CONTENT_FILTER_ERROR, WEBKIT_USER_CONTENT_FILTER_ERROR_NOT_FOUND);
191
192 // Then save a filter. Which must succed.
193 g_assert_nonnull(test->saveFilter(kFilterId, kSimpleJSONSource));
194 g_assert_no_error(test->lastError());
195
196 // Now it should be possible to actually load the filter.
197 g_assert_nonnull(test->loadFilter(kFilterId));
198 g_assert_no_error(test->lastError());
199}
200
201static void testSavedFilterIdentifierMatch(UserContentFilterStoreTest* test, gconstpointer)
202{
203 static const char* kFilterId = "SimpleFilter";
204
205 auto* filter = test->saveFilter(kFilterId, kSimpleJSONSource);
206 g_assert_nonnull(filter);
207 g_assert_no_error(test->lastError());
208 g_assert_cmpstr(kFilterId, ==, webkit_user_content_filter_get_identifier(filter));
209}
210
211static void testRemoveFilter(UserContentFilterStoreTest* test, gconstpointer)
212{
213 static const char* kFilterId = "SimpleFilter";
214
215 // Save a filter.
216 g_assert_nonnull(test->saveFilter(kFilterId, kSimpleJSONSource));
217 g_assert_no_error(test->lastError());
218
219 // Now remove it.
220 g_assert_true(test->removeFilter(kFilterId));
221 g_assert_no_error(test->lastError());
222
223 // Load must fail, and should not be listed.
224 g_assert_null(test->loadFilter(kFilterId));
225 g_assert_error(test->lastError(), WEBKIT_USER_CONTENT_FILTER_ERROR, WEBKIT_USER_CONTENT_FILTER_ERROR_NOT_FOUND);
226 g_assert_cmpuint(0, ==, g_strv_length(test->fetchIdentifiers()));
227}
228
229static void testSaveMultipleFilters(UserContentFilterStoreTest* test, gconstpointer)
230{
231 static const char* kFilterIdSpam = "Filter-Spam";
232 static const char* kFilterIdEggs = "Filter-Eggs";
233
234 g_assert_nonnull(test->saveFilter(kFilterIdSpam, kSimpleJSONSource));
235 g_assert_no_error(test->lastError());
236
237 g_assert_nonnull(test->saveFilter(kFilterIdEggs, kSimpleJSONSource));
238 g_assert_no_error(test->lastError());
239
240 auto filterIds = test->fetchIdentifiers();
241 g_assert_true(g_strv_contains(filterIds, kFilterIdSpam));
242 g_assert_true(g_strv_contains(filterIds, kFilterIdEggs));
243 g_assert_cmpuint(2, ==, g_strv_length(filterIds));
244
245 g_assert_nonnull(test->saveFilter(kFilterIdEggs, kSimpleJSONSource));
246 g_assert_no_error(test->lastError());
247
248 filterIds = test->fetchIdentifiers();
249 g_assert_true(g_strv_contains(filterIds, kFilterIdSpam));
250 g_assert_true(g_strv_contains(filterIds, kFilterIdEggs));
251 g_assert_cmpuint(2, ==, g_strv_length(filterIds));
252}
253
254static void testSaveFilterFromFile(UserContentFilterStoreTest* test, gconstpointer)
255{
256 static const char* kFilterId = "SimpleFilter";
257
258 GUniqueOutPtr<GError> error;
259 GUniquePtr<char> filePath(g_build_filename(test->dataDirectory(), "SimpleFilter.json", nullptr));
260 g_assert_true(g_file_set_contents(filePath.get(), kSimpleJSONSource, strlen(kSimpleJSONSource), &error.outPtr()));
261 g_assert_no_error(error.get());
262
263 GRefPtr<GFile> file = adoptGRef(g_file_new_for_path(filePath.get()));
264 g_assert_nonnull(test->saveFilterFromFile(kFilterId, file.get()));
265 g_assert_no_error(test->lastError());
266
267 g_assert_true(g_strv_contains(test->fetchIdentifiers(), kFilterId));
268}
269
270static void testFilterPersistence(UserContentFilterStoreTest* test, gconstpointer)
271{
272 static const char* kFilterId = "PersistedFilter";
273
274 g_assert_nonnull(test->saveFilter(kFilterId, kSimpleJSONSource));
275 g_assert_no_error(test->lastError());
276
277 test->resetStore();
278
279 g_assert_nonnull(test->loadFilter(kFilterId));
280 g_assert_no_error(test->lastError());
281}
282
283void beforeAll()
284{
285 UserContentFilterStoreTest::add("WebKitUserContentFilterStore", "empty-store", testEmptyStore);
286 UserContentFilterStoreTest::add("WebKitUserContentFilterStore", "invalid-filter-source", testSaveInvalidFilter);
287 UserContentFilterStoreTest::add("WebKitUserContentFilterStore", "filter-save-load", testSaveLoadFilter);
288 UserContentFilterStoreTest::add("WebKitUserContentFilterStore", "saved-filter-identifier-match", testSavedFilterIdentifierMatch);
289 UserContentFilterStoreTest::add("WebKitUserContentFilterStore", "remove-filter", testRemoveFilter);
290 UserContentFilterStoreTest::add("WebKitUserContentFilterStore", "save-multiple-filters", testSaveMultipleFilters);
291 UserContentFilterStoreTest::add("WebKitUserContentFilterStore", "save-from-file", testSaveFilterFromFile);
292 UserContentFilterStoreTest::add("WebKitUserContentFilterStore", "filter-persistence", testFilterPersistence);
293}
294
295void afterAll()
296{
297}
298