1 | /* |
2 | * Copyright (C) 2018-2019 Igalia S.L. |
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 "WebKitUserContentFilterStore.h" |
28 | |
29 | #include "APIContentRuleList.h" |
30 | #include "APIContentRuleListStore.h" |
31 | #include "WebKitError.h" |
32 | #include "WebKitUserContent.h" |
33 | #include "WebKitUserContentPrivate.h" |
34 | #include <WebCore/ContentExtensionError.h> |
35 | #include <glib/gi18n-lib.h> |
36 | #include <wtf/CompletionHandler.h> |
37 | #include <wtf/FileSystem.h> |
38 | #include <wtf/RefPtr.h> |
39 | #include <wtf/glib/GRefPtr.h> |
40 | #include <wtf/glib/GUniquePtr.h> |
41 | #include <wtf/glib/WTFGType.h> |
42 | |
43 | /** |
44 | * SECTION: WebKitUserContentFilterStore |
45 | * @Short_description: Handles storage of user content filters on disk. |
46 | * @Title: WebKitUserContentFilterStore |
47 | * |
48 | * The WebKitUserContentFilterStore provides the means to import and save |
49 | * [JSON rule sets](https://webkit.org/blog/3476/content-blockers-first-look/), |
50 | * which can be loaded later in an efficient manner. Once filters are stored, |
51 | * the #WebKitUserContentFilter objects which represent them can be added to |
52 | * a #WebKitUserContentManager with webkit_user_content_manager_add_filter(). |
53 | * |
54 | * JSON rule sets are imported using webkit_user_content_filter_store_save() and stored |
55 | * on disk in an implementation defined format. The contents of a filter store must be |
56 | * managed using the #WebKitUserContentFilterStore: a list of all the stored filters |
57 | * can be obtained with webkit_user_content_filter_store_fetch_identifiers(), |
58 | * webkit_user_content_filter_store_load() can be used to retrieve a previously saved |
59 | * filter, and removed from the store with webkit_user_content_filter_store_remove(). |
60 | * |
61 | * Since: 2.24 |
62 | */ |
63 | |
64 | enum { |
65 | PROP_0, |
66 | |
67 | PROP_PATH, |
68 | }; |
69 | |
70 | static inline GError* toGError(WebKitUserContentFilterError code, const std::error_code error) |
71 | { |
72 | ASSERT(error); |
73 | ASSERT(error.category() == WebCore::ContentExtensions::contentExtensionErrorCategory()); |
74 | return g_error_new_literal(WEBKIT_USER_CONTENT_FILTER_ERROR, code, error.message().c_str()); |
75 | } |
76 | |
77 | struct _WebKitUserContentFilterStorePrivate { |
78 | GUniquePtr<char> storagePath; |
79 | RefPtr<API::ContentRuleListStore> store; |
80 | }; |
81 | |
82 | WEBKIT_DEFINE_TYPE(WebKitUserContentFilterStore, webkit_user_content_filter_store, G_TYPE_OBJECT) |
83 | |
84 | static void webkitUserContentFilterStoreGetProperty(GObject* object, guint propID, GValue* value, GParamSpec* paramSpec) |
85 | { |
86 | WebKitUserContentFilterStore* store = WEBKIT_USER_CONTENT_FILTER_STORE(object); |
87 | |
88 | switch (propID) { |
89 | case PROP_PATH: |
90 | g_value_set_string(value, webkit_user_content_filter_store_get_path(store)); |
91 | break; |
92 | default: |
93 | G_OBJECT_WARN_INVALID_PROPERTY_ID(object, propID, paramSpec); |
94 | } |
95 | } |
96 | |
97 | static void webkitUserContentFilterStoreSetProperty(GObject* object, guint propID, const GValue* value, GParamSpec* paramSpec) |
98 | { |
99 | WebKitUserContentFilterStore* store = WEBKIT_USER_CONTENT_FILTER_STORE(object); |
100 | |
101 | switch (propID) { |
102 | case PROP_PATH: |
103 | store->priv->storagePath.reset(g_value_dup_string(value)); |
104 | break; |
105 | default: |
106 | G_OBJECT_WARN_INVALID_PROPERTY_ID(object, propID, paramSpec); |
107 | } |
108 | } |
109 | |
110 | static void webkitUserContentFilterStoreConstructed(GObject* object) |
111 | { |
112 | G_OBJECT_CLASS(webkit_user_content_filter_store_parent_class)->constructed(object); |
113 | |
114 | WebKitUserContentFilterStore* store = WEBKIT_USER_CONTENT_FILTER_STORE(object); |
115 | store->priv->store = adoptRef(new API::ContentRuleListStore(FileSystem::stringFromFileSystemRepresentation(store->priv->storagePath.get()), false)); |
116 | } |
117 | |
118 | static void webkit_user_content_filter_store_class_init(WebKitUserContentFilterStoreClass* storeClass) |
119 | { |
120 | GObjectClass* gObjectClass = G_OBJECT_CLASS(storeClass); |
121 | |
122 | gObjectClass->get_property = webkitUserContentFilterStoreGetProperty; |
123 | gObjectClass->set_property = webkitUserContentFilterStoreSetProperty; |
124 | gObjectClass->constructed = webkitUserContentFilterStoreConstructed; |
125 | |
126 | /** |
127 | * WebKitUserContentFilterStore:path: |
128 | * |
129 | * The directory used for filter storage. This path is used as the base |
130 | * directory where user content filters are stored on disk. |
131 | * |
132 | * Since: 2.24 |
133 | */ |
134 | g_object_class_install_property( |
135 | gObjectClass, |
136 | PROP_PATH, |
137 | g_param_spec_string( |
138 | "path" , |
139 | _("Storage directory path" ), |
140 | _("The directory where user content filters are stored" ), |
141 | nullptr, |
142 | static_cast<GParamFlags>(WEBKIT_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY))); |
143 | } |
144 | |
145 | /** |
146 | * webkit_user_content_filter_store_new: |
147 | * @storage_path: path where data for filters will be stored on disk |
148 | * |
149 | * Create a new #WebKitUserContentFilterStore to manipulate filters stored at @storage_path. |
150 | * The path must point to a local filesystem, and will be created if needed. |
151 | * |
152 | * Returns: (transfer full): a newly created #WebKitUserContentFilterStore |
153 | * |
154 | * Since: 2.24 |
155 | */ |
156 | WebKitUserContentFilterStore* webkit_user_content_filter_store_new(const gchar* storagePath) |
157 | { |
158 | g_return_val_if_fail(storagePath, nullptr); |
159 | return WEBKIT_USER_CONTENT_FILTER_STORE(g_object_new(WEBKIT_TYPE_USER_CONTENT_FILTER_STORE, "path" , storagePath, nullptr)); |
160 | } |
161 | |
162 | /** |
163 | * webkit_user_content_filter_store_get_path: |
164 | * @store: a #WebKitUserContentFilterStore |
165 | * |
166 | * Returns: (transfer none): The storage path for user content filters. |
167 | * |
168 | * Since: 2.24 |
169 | */ |
170 | const char* webkit_user_content_filter_store_get_path(WebKitUserContentFilterStore* store) |
171 | { |
172 | g_return_val_if_fail(WEBKIT_IS_USER_CONTENT_FILTER_STORE(store), nullptr); |
173 | return store->priv->storagePath.get(); |
174 | } |
175 | |
176 | static void webkitUserContentFilterStoreSaveBytes(GRefPtr<GTask>&& task, String&& identifier, GRefPtr<GBytes>&& source) |
177 | { |
178 | size_t sourceSize; |
179 | const char* sourceData = static_cast<const char*>(g_bytes_get_data(source.get(), &sourceSize)); |
180 | if (!sourceSize) { |
181 | g_task_return_error(task.get(), g_error_new_literal(WEBKIT_USER_CONTENT_FILTER_ERROR, WEBKIT_USER_CONTENT_FILTER_ERROR_INVALID_SOURCE, "Source JSON rule set cannot be empty" )); |
182 | return; |
183 | } |
184 | |
185 | auto* store = WEBKIT_USER_CONTENT_FILTER_STORE(g_task_get_source_object(task.get())); |
186 | store->priv->store->compileContentRuleList(identifier, String::fromUTF8(sourceData, sourceSize), [task = WTFMove(task)](RefPtr<API::ContentRuleList> contentRuleList, std::error_code error) { |
187 | if (g_task_return_error_if_cancelled(task.get())) |
188 | return; |
189 | |
190 | if (error) |
191 | g_task_return_error(task.get(), toGError(WEBKIT_USER_CONTENT_FILTER_ERROR_INVALID_SOURCE, error)); |
192 | else |
193 | g_task_return_pointer(task.get(), webkitUserContentFilterCreate(WTFMove(contentRuleList)), reinterpret_cast<GDestroyNotify>(webkit_user_content_filter_unref)); |
194 | }); |
195 | } |
196 | |
197 | /** |
198 | * webkit_user_content_filter_store_save: |
199 | * @store: a #WebKitUserContentFilterStore |
200 | * @identifier: a string used to identify the saved filter |
201 | * @source: #GBytes containing the rule set in JSON format |
202 | * @cancellable: (allow-none): a #GCancellable or %NULL to ignore |
203 | * @callback: (scope async): a #GAsyncReadyCallback to call when saving is completed |
204 | * @user_data: (closure): the data to pass to the callback function |
205 | * |
206 | * Asynchronously save a content filter from a source rule set in the |
207 | * [WebKit content extesions JSON format](https://webkit.org/blog/3476/content-blockers-first-look/). |
208 | * |
209 | * The @identifier can be used afterwards to refer to the filter when using |
210 | * webkit_user_content_filter_store_remove() and webkit_user_content_filter_store_load(). |
211 | * When the @identifier has been used in the past, the new filter source will replace |
212 | * the one saved beforehand for the same identifier. |
213 | * |
214 | * When the operation is finished, @callback will be invoked, which then can use |
215 | * webkit_user_content_filter_store_save_finish() to obtain the resulting filter. |
216 | * |
217 | * Since: 2.24 |
218 | */ |
219 | void webkit_user_content_filter_store_save(WebKitUserContentFilterStore* store, const gchar* identifier, GBytes* source, GCancellable* cancellable, GAsyncReadyCallback callback, gpointer userData) |
220 | { |
221 | g_return_if_fail(WEBKIT_IS_USER_CONTENT_FILTER_STORE(store)); |
222 | g_return_if_fail(identifier); |
223 | g_return_if_fail(source); |
224 | g_return_if_fail(callback); |
225 | |
226 | GRefPtr<GTask> task = adoptGRef(g_task_new(store, cancellable, callback, userData)); |
227 | webkitUserContentFilterStoreSaveBytes(WTFMove(task), String::fromUTF8(identifier), GRefPtr<GBytes>(source)); |
228 | } |
229 | |
230 | /** |
231 | * webkit_user_content_filter_store_save_finish: |
232 | * @store: a #WebKitUserContentFilterStore |
233 | * @result: a #GAsyncResult |
234 | * @error: return location for error or %NULL to ignore |
235 | * |
236 | * Finishes an asynchronous filter save previously started with |
237 | * webkit_user_content_filter_store_save(). |
238 | * |
239 | * Returns: (transfer full): a #WebKitUserContentFilter, or %NULL if saving failed |
240 | * |
241 | * Since: 2.24 |
242 | */ |
243 | WebKitUserContentFilter* webkit_user_content_filter_store_save_finish(WebKitUserContentFilterStore* store, GAsyncResult* result, GError** error) |
244 | { |
245 | g_return_val_if_fail(WEBKIT_IS_USER_CONTENT_FILTER_STORE(store), nullptr); |
246 | g_return_val_if_fail(result, nullptr); |
247 | return static_cast<WebKitUserContentFilter*>(g_task_propagate_pointer(G_TASK(result), error)); |
248 | } |
249 | |
250 | struct SaveTaskData { |
251 | String identifier; |
252 | }; |
253 | WEBKIT_DEFINE_ASYNC_DATA_STRUCT(SaveTaskData) |
254 | |
255 | /** |
256 | * webkit_user_content_filter_store_save_from_file: |
257 | * @store: a #WebKitUserContentFilterStore |
258 | * @identifier: a string used to identify the saved filter |
259 | * @file: a #GFile containing the rule set in JSON format |
260 | * @cancellable: (allow-none): a #GCancellable or %NULL to ignore |
261 | * @callback: (scope async): a #GAsyncReadyCallback to call when saving is completed |
262 | * @user_data: (closure): the data to pass to the callback function |
263 | * |
264 | * Asynchronously save a content filter from the contents of a file, which must be |
265 | * native to the platform, as checked by g_file_is_native(). See |
266 | * webkit_user_content_filter_store_save() for more details. |
267 | * |
268 | * When the operation is finished, @callback will be invoked, which then can use |
269 | * webkit_user_content_filter_store_save_finish() to obtain the resulting filter. |
270 | * |
271 | * Since: 2.24 |
272 | */ |
273 | void webkit_user_content_filter_store_save_from_file(WebKitUserContentFilterStore* store, const gchar* identifier, GFile* file, GCancellable* cancellable, GAsyncReadyCallback callback, gpointer userData) |
274 | { |
275 | g_return_if_fail(WEBKIT_IS_USER_CONTENT_FILTER_STORE(store)); |
276 | g_return_if_fail(identifier); |
277 | g_return_if_fail(G_IS_FILE(file)); |
278 | g_return_if_fail(callback); |
279 | |
280 | GRefPtr<GTask> task = adoptGRef(g_task_new(store, cancellable, callback, userData)); |
281 | |
282 | // Try mapping the file in memory first, and fall-back to reading the contents if that fails. |
283 | if (g_file_is_native(file)) { |
284 | GUniquePtr<char> filePath(g_file_get_path(file)); |
285 | GRefPtr<GMappedFile> mappedFile = adoptGRef(g_mapped_file_new(filePath.get(), FALSE, nullptr)); |
286 | if (mappedFile) { |
287 | GRefPtr<GBytes> source = adoptGRef(g_mapped_file_get_bytes(mappedFile.get())); |
288 | webkitUserContentFilterStoreSaveBytes(WTFMove(task), String::fromUTF8(identifier), WTFMove(source)); |
289 | return; |
290 | } |
291 | } |
292 | |
293 | // Pass the identifier as task data to be used in the completion callback once the contents have been loaded. |
294 | SaveTaskData* data = createSaveTaskData(); |
295 | data->identifier = String::fromUTF8(identifier); |
296 | g_task_set_task_data(task.get(), data, reinterpret_cast<GDestroyNotify>(destroySaveTaskData)); |
297 | |
298 | g_file_load_contents_async(file, cancellable, [](GObject* sourceObject, GAsyncResult* result, void* userData) { |
299 | GRefPtr<GTask> task = adoptGRef(G_TASK(userData)); |
300 | if (g_task_return_error_if_cancelled(task.get())) |
301 | return; |
302 | |
303 | char* sourceData; |
304 | size_t sourceSize; |
305 | GUniqueOutPtr<GError> error; |
306 | if (g_file_load_contents_finish(G_FILE(sourceObject), result, &sourceData, &sourceSize, nullptr, &error.outPtr())) { |
307 | SaveTaskData* data = static_cast<SaveTaskData*>(g_task_get_task_data(task.get())); |
308 | webkitUserContentFilterStoreSaveBytes(WTFMove(task), WTFMove(data->identifier), GRefPtr<GBytes>(g_bytes_new_take(sourceData, sourceSize))); |
309 | } else |
310 | g_task_return_error(task.get(), error.release().release()); |
311 | }, task.leakRef()); |
312 | } |
313 | |
314 | /** |
315 | * webkit_user_content_filter_store_save_from_file_finish: |
316 | * @store: a #WebKitUserContentFilterStore |
317 | * @result: a #GAsyncResult |
318 | * @error: return location for error or %NULL to ignore |
319 | * |
320 | * Finishes and asynchronous filter save previously started with |
321 | * webkit_user_content_filter_store_save_from_file(). |
322 | * |
323 | * Returns: (transfer full): a #WebKitUserContentFilter, or %NULL if saving failed. |
324 | * |
325 | * Since: 2.24 |
326 | */ |
327 | WebKitUserContentFilter* webkit_user_content_filter_store_save_from_file_finish(WebKitUserContentFilterStore* store, GAsyncResult* result, GError** error) |
328 | { |
329 | g_return_val_if_fail(WEBKIT_IS_USER_CONTENT_FILTER_STORE(store), nullptr); |
330 | g_return_val_if_fail(result, nullptr); |
331 | return static_cast<WebKitUserContentFilter*>(g_task_propagate_pointer(G_TASK(result), error)); |
332 | } |
333 | |
334 | /** |
335 | * webkit_user_content_filter_store_remove: |
336 | * @store: a #WebKitUserContentFilterStore |
337 | * @identifier: a filter identifier |
338 | * @cancellable: (allow-none): a #GCancellable or %NULL to ignore |
339 | * @callback: (scope async): a #GAsyncReadyCallback to call when the removal is completed |
340 | * @user_data: (closure): the data to pass to the callback function |
341 | * |
342 | * Asynchronously remove a content filter given its @identifier. |
343 | * |
344 | * When the operation is finished, @callback will be invoked, which then can use |
345 | * webkit_user_content_filter_store_remove_finish() to check whether the removal was |
346 | * successful. |
347 | * |
348 | * Since: 2.24 |
349 | */ |
350 | void webkit_user_content_filter_store_remove(WebKitUserContentFilterStore* store, const gchar* identifier, GCancellable* cancellable, GAsyncReadyCallback callback, gpointer userData) |
351 | { |
352 | g_return_if_fail(WEBKIT_IS_USER_CONTENT_FILTER_STORE(store)); |
353 | g_return_if_fail(identifier); |
354 | g_return_if_fail(callback); |
355 | |
356 | GRefPtr<GTask> task = adoptGRef(g_task_new(store, cancellable, callback, userData)); |
357 | store->priv->store->removeContentRuleList(String::fromUTF8(identifier), [task = WTFMove(task)](std::error_code error) { |
358 | if (g_task_return_error_if_cancelled(task.get())) |
359 | return; |
360 | |
361 | if (error) { |
362 | ASSERT(static_cast<API::ContentRuleListStore::Error>(error.value()) == API::ContentRuleListStore::Error::RemoveFailed); |
363 | g_task_return_error(task.get(), toGError(WEBKIT_USER_CONTENT_FILTER_ERROR_NOT_FOUND, error)); |
364 | } else |
365 | g_task_return_boolean(task.get(), TRUE); |
366 | }); |
367 | } |
368 | |
369 | /** |
370 | * webkit_user_content_filter_store_remove_finish: |
371 | * @store: a #WebKitUserContentFilterStore |
372 | * @result: a #GAsyncResult |
373 | * @error: return location for error or %NULL to ignore |
374 | * |
375 | * Finishes an asynchronous filter removal previously started with |
376 | * webkit_user_content_filter_store_remove(). |
377 | * |
378 | * Returns: whether the removal was successful |
379 | * |
380 | * Since: 2.24 |
381 | */ |
382 | gboolean webkit_user_content_filter_store_remove_finish(WebKitUserContentFilterStore* store, GAsyncResult* result, GError** error) |
383 | { |
384 | g_return_val_if_fail(WEBKIT_IS_USER_CONTENT_FILTER_STORE(store), FALSE); |
385 | g_return_val_if_fail(result, FALSE); |
386 | return g_task_propagate_boolean(G_TASK(result), error); |
387 | } |
388 | |
389 | /** |
390 | * webkit_user_content_filter_store_load: |
391 | * @store: a #WebKitUserContentFilterStore |
392 | * @identifier: a filter identifier |
393 | * @cancellable: (allow-none): a #GCancellable or %NULL to ignore |
394 | * @callback: (scope async): a #GAsyncReadyCallback to call when the load is completed |
395 | * @user_data: (closure): the data to pass to the callback function |
396 | * |
397 | * Asynchronously load a content filter given its @identifier. The filter must have been |
398 | * previously stored using webkit_user_content_filter_store_save(). |
399 | * |
400 | * When the operation is finished, @callback will be invoked, which then can use |
401 | * webkit_user_content_filter_store_load_finish() to obtain the resulting filter. |
402 | * |
403 | * Since: 2.24 |
404 | */ |
405 | void webkit_user_content_filter_store_load(WebKitUserContentFilterStore* store, const gchar* identifier, GCancellable* cancellable, GAsyncReadyCallback callback, gpointer userData) |
406 | { |
407 | g_return_if_fail(WEBKIT_IS_USER_CONTENT_FILTER_STORE(store)); |
408 | g_return_if_fail(identifier); |
409 | g_return_if_fail(callback); |
410 | |
411 | GRefPtr<GTask> task = adoptGRef(g_task_new(store, cancellable, callback, userData)); |
412 | store->priv->store->lookupContentRuleList(String::fromUTF8(identifier), [task = WTFMove(task)](RefPtr<API::ContentRuleList> contentRuleList, std::error_code error) { |
413 | if (g_task_return_error_if_cancelled(task.get())) |
414 | return; |
415 | |
416 | if (error) { |
417 | ASSERT(static_cast<API::ContentRuleListStore::Error>(error.value()) == API::ContentRuleListStore::Error::LookupFailed |
418 | || static_cast<API::ContentRuleListStore::Error>(error.value()) == API::ContentRuleListStore::Error::VersionMismatch); |
419 | g_task_return_error(task.get(), toGError(WEBKIT_USER_CONTENT_FILTER_ERROR_NOT_FOUND, error)); |
420 | } else |
421 | g_task_return_pointer(task.get(), webkitUserContentFilterCreate(WTFMove(contentRuleList)), reinterpret_cast<GDestroyNotify>(webkit_user_content_filter_unref)); |
422 | }); |
423 | } |
424 | |
425 | /** |
426 | * webkit_user_content_filter_store_load_finish: |
427 | * @store: a #WebKitUserContentFilterStore |
428 | * @result: a #GAsyncResult |
429 | * @error: return location for error or %NULL to ignore |
430 | * |
431 | * Finishes an asynchronous filter load previously started with |
432 | * webkit_user_content_filter_store_load(). |
433 | * |
434 | * Returns: (transfer full): a #WebKitUserContentFilter, or %NULL if the load failed |
435 | * |
436 | * Since: 2.24 |
437 | */ |
438 | WebKitUserContentFilter* webkit_user_content_filter_store_load_finish(WebKitUserContentFilterStore* store, GAsyncResult* result, GError** error) |
439 | { |
440 | g_return_val_if_fail(WEBKIT_IS_USER_CONTENT_FILTER_STORE(store), nullptr); |
441 | g_return_val_if_fail(result, nullptr); |
442 | return static_cast<WebKitUserContentFilter*>(g_task_propagate_pointer(G_TASK(result), error)); |
443 | } |
444 | |
445 | /** |
446 | * webkit_user_content_filter_store_fetch_identifiers: |
447 | * @store: a #WebKitUserContentFilterStore |
448 | * @cancellable: (allow-none): a #GCancellable or %NULL to ignore |
449 | * @callback: (scope async): a #GAsyncReadyCallback to call when the removal is completed |
450 | * @user_data: (closure): the data to pass to the callback function |
451 | * |
452 | * Asynchronously retrieve a list of the identifiers for all the stored filters. |
453 | * |
454 | * When the operation is finished, @callback will be invoked, which then can use |
455 | * webkit_user_content_filter_store_fetch_identifiers_finish() to obtain the list of |
456 | * filter identifiers. |
457 | * |
458 | * Since: 2.24 |
459 | */ |
460 | void webkit_user_content_filter_store_fetch_identifiers(WebKitUserContentFilterStore* store, GCancellable* cancellable, GAsyncReadyCallback callback, gpointer userData) |
461 | { |
462 | g_return_if_fail(WEBKIT_IS_USER_CONTENT_FILTER_STORE(store)); |
463 | g_return_if_fail(callback); |
464 | |
465 | GRefPtr<GTask> task = adoptGRef(g_task_new(store, cancellable, callback, userData)); |
466 | store->priv->store->getAvailableContentRuleListIdentifiers([task = WTFMove(task)](WTF::Vector<WTF::String> identifiers) { |
467 | if (g_task_return_error_if_cancelled(task.get())) |
468 | return; |
469 | |
470 | GStrv result = static_cast<GStrv>(g_new0(gchar*, identifiers.size() + 1)); |
471 | for (size_t i = 0; i < identifiers.size(); ++i) |
472 | result[i] = g_strdup(identifiers[i].utf8().data()); |
473 | g_task_return_pointer(task.get(), result, reinterpret_cast<GDestroyNotify>(g_strfreev)); |
474 | }); |
475 | } |
476 | |
477 | /** |
478 | * webkit_user_content_filter_store_fetch_identifiers_finish: |
479 | * @store: a #WebKitUserContentFilterStore |
480 | * @result: a #GAsyncResult |
481 | * |
482 | * Finishes an asynchronous fetch of the list of identifiers for the stored filters previously |
483 | * started with webkit_user_content_filter_store_fetch_identifiers(). |
484 | * |
485 | * Returns: (transfer full) (array zero-terminated=1) (element-type utf8): a %NULL-terminated list of filter identifiers. |
486 | * |
487 | * Since: 2.24 |
488 | */ |
489 | gchar** webkit_user_content_filter_store_fetch_identifiers_finish(WebKitUserContentFilterStore* store, GAsyncResult* result) |
490 | { |
491 | g_return_val_if_fail(WEBKIT_IS_USER_CONTENT_FILTER_STORE(store), nullptr); |
492 | g_return_val_if_fail(result, nullptr); |
493 | return static_cast<gchar**>(g_task_propagate_pointer(G_TASK(result), nullptr)); |
494 | } |
495 | |