Branch data Line data Source code
1 : : /* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
2 : : *
3 : : * Copyright © 2018-2019 Endless Mobile, Inc.
4 : : *
5 : : * SPDX-License-Identifier: LGPL-2.1-or-later
6 : : *
7 : : * This library is free software; you can redistribute it and/or
8 : : * modify it under the terms of the GNU Lesser General Public
9 : : * License as published by the Free Software Foundation; either
10 : : * version 2.1 of the License, or (at your option) any later version.
11 : : *
12 : : * This library is distributed in the hope that it will be useful,
13 : : * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 : : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 : : * Lesser General Public License for more details.
16 : : *
17 : : * You should have received a copy of the GNU Lesser General Public
18 : : * License along with this library; if not, write to the Free Software
19 : : * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
20 : : *
21 : : * Authors:
22 : : * - Philip Withnall <withnall@endlessm.com>
23 : : * - Andre Moreira Magalhaes <andre@endlessm.com>
24 : : */
25 : :
26 : : #include "config.h"
27 : :
28 : : #include <glib.h>
29 : : #include <glib-object.h>
30 : : #include <glib/gi18n-lib.h>
31 : : #include <gio/gdesktopappinfo.h>
32 : : #include <gio/gio.h>
33 : : #include <libmalcontent/app-filter.h>
34 : :
35 : : #include "libmalcontent/app-filter-private.h"
36 : :
37 : :
38 : : /* FIXME: Eventually deprecate these compatibility fallbacks. */
39 : : GQuark
40 : 3 : mct_app_filter_error_quark (void)
41 : : {
42 : 3 : return mct_manager_error_quark ();
43 : : }
44 : :
45 : : /* struct _MctAppFilter is defined in app-filter-private.h */
46 : :
47 [ + + + - : 10 : G_DEFINE_BOXED_TYPE (MctAppFilter, mct_app_filter,
+ + ]
48 : : mct_app_filter_ref, mct_app_filter_unref)
49 : :
50 : : /**
51 : : * mct_app_filter_ref:
52 : : * @filter: (transfer none): an #MctAppFilter
53 : : *
54 : : * Increment the reference count of @filter, and return the same pointer to it.
55 : : *
56 : : * Returns: (transfer full): the same pointer as @filter
57 : : * Since: 0.2.0
58 : : */
59 : : MctAppFilter *
60 : 4 : mct_app_filter_ref (MctAppFilter *filter)
61 : : {
62 : 4 : g_return_val_if_fail (filter != NULL, NULL);
63 : 4 : g_return_val_if_fail (filter->ref_count >= 1, NULL);
64 : 4 : g_return_val_if_fail (filter->ref_count <= G_MAXINT - 1, NULL);
65 : :
66 : 4 : filter->ref_count++;
67 : 4 : return filter;
68 : : }
69 : :
70 : : /**
71 : : * mct_app_filter_unref:
72 : : * @filter: (transfer full): an #MctAppFilter
73 : : *
74 : : * Decrement the reference count of @filter. If the reference count reaches
75 : : * zero, free the @filter and all its resources.
76 : : *
77 : : * Since: 0.2.0
78 : : */
79 : : void
80 : 94 : mct_app_filter_unref (MctAppFilter *filter)
81 : : {
82 : 94 : g_return_if_fail (filter != NULL);
83 : 94 : g_return_if_fail (filter->ref_count >= 1);
84 : :
85 : 94 : filter->ref_count--;
86 : :
87 [ + + ]: 94 : if (filter->ref_count <= 0)
88 : : {
89 : 90 : g_strfreev (filter->app_list);
90 : 90 : g_variant_unref (filter->oars_ratings);
91 : 90 : g_free (filter);
92 : : }
93 : : }
94 : :
95 : : /**
96 : : * mct_app_filter_get_user_id:
97 : : * @filter: an #MctAppFilter
98 : : *
99 : : * Get the user ID of the user this #MctAppFilter is for.
100 : : *
101 : : * Returns: user ID of the relevant user, or `(uid_t) -1` if unknown
102 : : * Since: 0.2.0
103 : : */
104 : : uid_t
105 : 5 : mct_app_filter_get_user_id (MctAppFilter *filter)
106 : : {
107 : 5 : g_return_val_if_fail (filter != NULL, FALSE);
108 : 5 : g_return_val_if_fail (filter->ref_count >= 1, FALSE);
109 : :
110 : 5 : return filter->user_id;
111 : : }
112 : :
113 : : static MctAppFilterOarsValue
114 : 38 : oars_str_to_enum (const gchar *value_str)
115 : : {
116 [ + + ]: 38 : if (g_str_equal (value_str, "none"))
117 : 4 : return MCT_APP_FILTER_OARS_VALUE_NONE;
118 [ + + ]: 34 : else if (g_str_equal (value_str, "mild"))
119 : 15 : return MCT_APP_FILTER_OARS_VALUE_MILD;
120 [ + + ]: 19 : else if (g_str_equal (value_str, "moderate"))
121 : 13 : return MCT_APP_FILTER_OARS_VALUE_MODERATE;
122 [ + + ]: 6 : else if (g_str_equal (value_str, "intense"))
123 : 3 : return MCT_APP_FILTER_OARS_VALUE_INTENSE;
124 : : else
125 : 3 : return MCT_APP_FILTER_OARS_VALUE_UNKNOWN;
126 : : }
127 : :
128 : : /**
129 : : * mct_app_filter_is_enabled:
130 : : * @filter: an #MctAppFilter
131 : : *
132 : : * Check whether the app filter is enabled and is going to impose at least one
133 : : * restriction on the user. This gives a high level view of whether app filter
134 : : * parental controls are ‘enabled’ for the given user.
135 : : *
136 : : * Returns: %TRUE if the app filter contains at least one non-default value,
137 : : * %FALSE if it’s entirely default
138 : : * Since: 0.7.0
139 : : */
140 : : gboolean
141 : 51 : mct_app_filter_is_enabled (MctAppFilter *filter)
142 : : {
143 : : gboolean oars_ratings_all_intense_or_unknown;
144 : : GVariantIter iter;
145 : : const gchar *oars_value;
146 : :
147 : 51 : g_return_val_if_fail (filter != NULL, FALSE);
148 : 51 : g_return_val_if_fail (filter->ref_count >= 1, FALSE);
149 : :
150 : : /* The least restrictive OARS filter has all values as intense, or unknown. */
151 : 51 : oars_ratings_all_intense_or_unknown = TRUE;
152 : 51 : g_variant_iter_init (&iter, filter->oars_ratings);
153 : :
154 [ + + ]: 55 : while (g_variant_iter_loop (&iter, "{&s&s}", NULL, &oars_value))
155 : : {
156 : 21 : MctAppFilterOarsValue value = oars_str_to_enum (oars_value);
157 : :
158 [ + + + + ]: 21 : if (value != MCT_APP_FILTER_OARS_VALUE_UNKNOWN &&
159 : : value != MCT_APP_FILTER_OARS_VALUE_INTENSE)
160 : : {
161 : 17 : oars_ratings_all_intense_or_unknown = FALSE;
162 : 17 : break;
163 : : }
164 : : }
165 : :
166 : : /* Check all fields against their default values. Ignore
167 : : * `allow_system_installation` since it’s false by default, so the default
168 : : * value is already the most restrictive. */
169 : 99 : return ((filter->app_list_type == MCT_APP_FILTER_LIST_BLOCKLIST &&
170 [ + + ]: 48 : filter->app_list[0] != NULL) ||
171 [ + + + + ]: 37 : filter->app_list_type == MCT_APP_FILTER_LIST_ALLOWLIST ||
172 [ + + ]: 102 : !oars_ratings_all_intense_or_unknown ||
173 [ + + ]: 25 : !filter->allow_user_installation);
174 : : }
175 : :
176 : : /**
177 : : * mct_app_filter_is_path_allowed:
178 : : * @filter: an #MctAppFilter
179 : : * @path: (type filename): absolute path of a program to check
180 : : *
181 : : * Check whether the program at @path is allowed to be run according to this
182 : : * app filter. @path will be canonicalised without doing any I/O.
183 : : *
184 : : * Returns: %TRUE if the user this @filter corresponds to is allowed to run the
185 : : * program at @path according to the @filter policy; %FALSE otherwise
186 : : * Since: 0.2.0
187 : : */
188 : : gboolean
189 : 66 : mct_app_filter_is_path_allowed (MctAppFilter *filter,
190 : : const gchar *path)
191 : : {
192 : 66 : g_return_val_if_fail (filter != NULL, FALSE);
193 : 66 : g_return_val_if_fail (filter->ref_count >= 1, FALSE);
194 : 66 : g_return_val_if_fail (path != NULL, FALSE);
195 : 66 : g_return_val_if_fail (g_path_is_absolute (path), FALSE);
196 : :
197 : 132 : g_autofree gchar *canonical_path = g_canonicalize_filename (path, "/");
198 : 132 : g_autofree gchar *canonical_path_utf8 = g_filename_to_utf8 (canonical_path, -1,
199 : : NULL, NULL, NULL);
200 : 66 : g_return_val_if_fail (canonical_path_utf8 != NULL, FALSE);
201 : :
202 : 66 : gboolean path_in_list = g_strv_contains ((const gchar * const *) filter->app_list,
203 : : canonical_path_utf8);
204 : :
205 [ + + - ]: 66 : switch (filter->app_list_type)
206 : : {
207 : 64 : case MCT_APP_FILTER_LIST_BLOCKLIST:
208 : 64 : return !path_in_list;
209 : 2 : case MCT_APP_FILTER_LIST_ALLOWLIST:
210 : 2 : return path_in_list;
211 : 0 : default:
212 : : g_assert_not_reached ();
213 : : }
214 : : }
215 : :
216 : : /* Check whether a given @ref is a valid flatpak ref.
217 : : *
218 : : * For simplicity and to avoid duplicating the whole logic behind
219 : : * flatpak_ref_parse() this method will only check whether:
220 : : * - the @ref contains exactly 3 slash chars
221 : : * - the @ref starts with either app/ or runtime/
222 : : * - the name, arch and branch components of the @ref are not empty
223 : : *
224 : : * We avoid using flatpak_ref_parse() to allow for libflatpak
225 : : * to depend on malcontent without causing a cyclic dependency.
226 : : */
227 : : static gboolean
228 : 149 : is_valid_flatpak_ref (const gchar *ref)
229 : : {
230 : 149 : g_auto(GStrv) parts = NULL;
231 : :
232 [ - + ]: 149 : if (ref == NULL)
233 : 0 : return FALSE;
234 : :
235 : 149 : parts = g_strsplit (ref, "/", 0);
236 : 149 : return (g_strv_length (parts) == 4 &&
237 [ + + ]: 92 : (strcmp (parts[0], "app") == 0 ||
238 [ - + ]: 13 : strcmp (parts[0], "runtime") == 0) &&
239 [ + - ]: 79 : *parts[1] != '\0' &&
240 [ + + + - ]: 320 : *parts[2] != '\0' &&
241 [ + - ]: 79 : *parts[3] != '\0');
242 : : }
243 : :
244 : : /**
245 : : * mct_app_filter_is_flatpak_ref_allowed:
246 : : * @filter: an #MctAppFilter
247 : : * @app_ref: flatpak ref for the app, for example `app/org.gnome.Builder/x86_64/master`
248 : : *
249 : : * Check whether the flatpak app with the given @app_ref is allowed to be run
250 : : * according to this app filter.
251 : : *
252 : : * Returns: %TRUE if the user this @filter corresponds to is allowed to run the
253 : : * flatpak called @app_ref according to the @filter policy; %FALSE otherwise
254 : : * Since: 0.2.0
255 : : */
256 : : gboolean
257 : 26 : mct_app_filter_is_flatpak_ref_allowed (MctAppFilter *filter,
258 : : const gchar *app_ref)
259 : : {
260 : 26 : g_return_val_if_fail (filter != NULL, FALSE);
261 : 26 : g_return_val_if_fail (filter->ref_count >= 1, FALSE);
262 : 26 : g_return_val_if_fail (app_ref != NULL, FALSE);
263 : 26 : g_return_val_if_fail (is_valid_flatpak_ref (app_ref), FALSE);
264 : :
265 : 26 : gboolean ref_in_list = g_strv_contains ((const gchar * const *) filter->app_list,
266 : : app_ref);
267 : :
268 [ + + - ]: 26 : switch (filter->app_list_type)
269 : : {
270 : 24 : case MCT_APP_FILTER_LIST_BLOCKLIST:
271 : 24 : return !ref_in_list;
272 : 2 : case MCT_APP_FILTER_LIST_ALLOWLIST:
273 : 2 : return ref_in_list;
274 : 0 : default:
275 : : g_assert_not_reached ();
276 : : }
277 : : }
278 : :
279 : : /**
280 : : * mct_app_filter_is_flatpak_app_allowed:
281 : : * @filter: an #MctAppFilter
282 : : * @app_id: flatpak ID for the app, for example `org.gnome.Builder`
283 : : *
284 : : * Check whether the flatpak app with the given @app_id is allowed to be run
285 : : * according to this app filter. This is a globbing match, matching @app_id
286 : : * against potentially multiple entries in the blocklist, as the blocklist
287 : : * contains flatpak refs (for example, `app/org.gnome.Builder/x86_64/master`)
288 : : * which contain architecture and branch information. App IDs (for example,
289 : : * `org.gnome.Builder`) do not contain architecture or branch information.
290 : : *
291 : : * Returns: %TRUE if the user this @filter corresponds to is allowed to run the
292 : : * flatpak called @app_id according to the @filter policy; %FALSE otherwise
293 : : * Since: 0.2.0
294 : : */
295 : : gboolean
296 : 53 : mct_app_filter_is_flatpak_app_allowed (MctAppFilter *filter,
297 : : const gchar *app_id)
298 : : {
299 : 53 : g_return_val_if_fail (filter != NULL, FALSE);
300 : 53 : g_return_val_if_fail (filter->ref_count >= 1, FALSE);
301 : 53 : g_return_val_if_fail (app_id != NULL, FALSE);
302 : :
303 : 53 : gsize app_id_len = strlen (app_id);
304 : :
305 : 53 : gboolean id_in_list = FALSE;
306 [ + + ]: 150 : for (gsize i = 0; filter->app_list[i] != NULL; i++)
307 : : {
308 [ + + ]: 113 : if (is_valid_flatpak_ref (filter->app_list[i]) &&
309 [ + - - + : 43 : g_str_has_prefix (filter->app_list[i], "app/") &&
+ - + - ]
310 [ + + ]: 43 : strncmp (filter->app_list[i] + strlen ("app/"), app_id, app_id_len) == 0 &&
311 [ + - ]: 16 : filter->app_list[i][strlen ("app/") + app_id_len] == '/')
312 : : {
313 : 16 : id_in_list = TRUE;
314 : 16 : break;
315 : : }
316 : : }
317 : :
318 [ + + - ]: 53 : switch (filter->app_list_type)
319 : : {
320 : 50 : case MCT_APP_FILTER_LIST_BLOCKLIST:
321 : 50 : return !id_in_list;
322 : 3 : case MCT_APP_FILTER_LIST_ALLOWLIST:
323 : 3 : return id_in_list;
324 : 0 : default:
325 : : g_assert_not_reached ();
326 : : }
327 : : }
328 : :
329 : : /* Implement folding of the results of applying the app filter to multiple keys
330 : : * of a #GAppInfo, including short circuiting. If the app filter is a blocklist,
331 : : * we want to short circuit return blocked on the first key which is blocked;
332 : : * otherwise continue to checking the next key. Similarly, if the app filter is
333 : : * an allowlist, we want to short circuit return *allowed* on the first key
334 : : * which is allowed; otherwise continue to checking the next key.
335 : : *
336 : : * @allowed is the result of an app filter check against a single key.
337 : : * The return value from this function indicates whether to short circuit, i.e.
338 : : * whether to return control flow immediately after this function returns. The
339 : : * result of the fold is returned as @allowed_out.
340 : : *
341 : : * The base case for if all keys have been checked and nothing has been allowed
342 : : * or blocked so far is filter_fold_base(). */
343 : : static gboolean
344 : 64 : filter_fold_should_short_circuit (MctAppFilter *filter,
345 : : gboolean allowed,
346 : : gboolean *allowed_out)
347 : : {
348 [ + - - ]: 64 : switch (filter->app_list_type)
349 : : {
350 : 64 : case MCT_APP_FILTER_LIST_BLOCKLIST:
351 [ + + ]: 64 : if (!allowed)
352 : : {
353 : 12 : *allowed_out = FALSE;
354 : 12 : return TRUE;
355 : : }
356 : 52 : break;
357 : 0 : case MCT_APP_FILTER_LIST_ALLOWLIST:
358 [ # # ]: 0 : if (allowed)
359 : : {
360 : 0 : *allowed_out = TRUE;
361 : 0 : return TRUE;
362 : : }
363 : 0 : break;
364 : 0 : default:
365 : : g_assert_not_reached ();
366 : : }
367 : :
368 : 52 : return FALSE;
369 : : }
370 : :
371 : : static gboolean
372 : 14 : filter_fold_base (MctAppFilter *filter)
373 : : {
374 [ + - - ]: 14 : switch (filter->app_list_type)
375 : : {
376 : 14 : case MCT_APP_FILTER_LIST_BLOCKLIST:
377 : 14 : return TRUE;
378 : 0 : case MCT_APP_FILTER_LIST_ALLOWLIST:
379 : 0 : return FALSE;
380 : 0 : default:
381 : : g_assert_not_reached ();
382 : : }
383 : : }
384 : :
385 : : /**
386 : : * mct_app_filter_is_appinfo_allowed:
387 : : * @filter: an #MctAppFilter
388 : : * @app_info: (transfer none): application information
389 : : *
390 : : * Check whether the app with the given @app_info is allowed to be run
391 : : * according to this app filter. This matches on multiple keys potentially
392 : : * present in the #GAppInfo, including the path of the executable.
393 : : *
394 : : * If the appfilter is a blocklist, the @app_info is blocked if any of its
395 : : * keys are blocked. If the appfilter is an allowlist, the @app_info is allowed
396 : : * if any of its keys are allowed.
397 : : *
398 : : * Returns: %TRUE if the user this @filter corresponds to is allowed to run the
399 : : * app represented by @app_info according to the @filter policy; %FALSE
400 : : * otherwise
401 : : * Since: 0.2.0
402 : : */
403 : : gboolean
404 : 26 : mct_app_filter_is_appinfo_allowed (MctAppFilter *filter,
405 : : GAppInfo *app_info)
406 : : {
407 : : const char *exec;
408 : 26 : g_autofree gchar *abs_path = NULL;
409 : 26 : const gchar * const *types = NULL;
410 : 26 : gboolean retval = FALSE;
411 : :
412 : 26 : g_return_val_if_fail (filter != NULL, FALSE);
413 : 26 : g_return_val_if_fail (filter->ref_count >= 1, FALSE);
414 : 26 : g_return_val_if_fail (G_IS_APP_INFO (app_info), FALSE);
415 : :
416 : 26 : exec = g_app_info_get_executable (app_info);
417 [ + - ]: 26 : abs_path = (exec != NULL) ? g_find_program_in_path (exec) : NULL;
418 : :
419 [ + - + + ]: 52 : if (abs_path != NULL &&
420 : 26 : filter_fold_should_short_circuit (filter,
421 : : mct_app_filter_is_path_allowed (filter, abs_path),
422 : : &retval))
423 : 2 : return retval;
424 : :
425 : 24 : types = g_app_info_get_supported_types (app_info);
426 [ + + + + ]: 36 : for (gsize i = 0; types != NULL && types[i] != NULL; i++)
427 : : {
428 [ + + ]: 16 : if (filter_fold_should_short_circuit (filter,
429 : 16 : mct_app_filter_is_content_type_allowed (filter, types[i]),
430 : : &retval))
431 : 4 : return retval;
432 : : }
433 : :
434 [ - + + - : 20 : if (G_IS_DESKTOP_APP_INFO (app_info))
+ - + - ]
435 : : {
436 [ + + ]: 20 : g_autofree gchar *flatpak_app = NULL;
437 [ + + ]: 20 : g_autofree gchar *old_flatpak_apps_str = NULL;
438 : :
439 : : /* This gives `org.gnome.Builder`. */
440 : 20 : flatpak_app = g_desktop_app_info_get_string (G_DESKTOP_APP_INFO (app_info), "X-Flatpak");
441 [ + + ]: 20 : if (flatpak_app != NULL)
442 : 10 : flatpak_app = g_strstrip (flatpak_app);
443 : :
444 [ + + + + ]: 30 : if (flatpak_app != NULL &&
445 : 10 : filter_fold_should_short_circuit (filter,
446 : : mct_app_filter_is_flatpak_app_allowed (filter, flatpak_app),
447 : : &retval))
448 : 2 : return retval;
449 : :
450 : : /* FIXME: This could do with the g_desktop_app_info_get_string_list() API
451 : : * from GLib 2.60. Gives `gimp.desktop;org.gimp.Gimp.desktop;`. */
452 : 18 : old_flatpak_apps_str = g_desktop_app_info_get_string (G_DESKTOP_APP_INFO (app_info), "X-Flatpak-RenamedFrom");
453 [ + + ]: 18 : if (old_flatpak_apps_str != NULL)
454 : : {
455 [ + + ]: 24 : g_auto(GStrv) old_flatpak_apps = g_strsplit (old_flatpak_apps_str, ";", -1);
456 : :
457 [ + + ]: 26 : for (gsize i = 0; old_flatpak_apps[i] != NULL; i++)
458 : : {
459 : 18 : gchar *old_flatpak_app = g_strstrip (old_flatpak_apps[i]);
460 : :
461 [ + - - + : 18 : if (g_str_has_suffix (old_flatpak_app, ".desktop"))
+ + + + ]
462 : 4 : old_flatpak_app[strlen (old_flatpak_app) - strlen (".desktop")] = '\0';
463 : 18 : old_flatpak_app = g_strstrip (old_flatpak_app);
464 : :
465 [ + + + + ]: 30 : if (*old_flatpak_app != '\0' &&
466 : 12 : filter_fold_should_short_circuit (filter,
467 : : mct_app_filter_is_flatpak_app_allowed (filter, old_flatpak_app),
468 : : &retval))
469 : 4 : return retval;
470 : : }
471 : : }
472 : : }
473 : :
474 : 14 : return filter_fold_base (filter);
475 : : }
476 : :
477 : : /* Check whether a given @content_type is valid.
478 : : *
479 : : * For simplicity this method will only check whether:
480 : : * - the @content_type contains exactly 1 slash char
481 : : * - the @content_type does not start with a slash char
482 : : * - the type and subtype components of the @content_type are not empty
483 : : */
484 : : static gboolean
485 : 58 : is_valid_content_type (const gchar *content_type)
486 : : {
487 : 58 : g_auto(GStrv) parts = NULL;
488 : :
489 [ - + ]: 58 : if (content_type == NULL)
490 : 0 : return FALSE;
491 : :
492 : 58 : parts = g_strsplit (content_type, "/", 0);
493 : 58 : return (g_strv_length (parts) == 2 &&
494 [ + - + - ]: 116 : *parts[0] != '\0' &&
495 [ + - ]: 58 : *parts[1] != '\0');
496 : : }
497 : :
498 : : /**
499 : : * mct_app_filter_is_content_type_allowed:
500 : : * @filter: an #MctAppFilter
501 : : * @content_type: content type to check
502 : : *
503 : : * Check whether apps handling the given @content_type are allowed to be run
504 : : * according to this app filter.
505 : : *
506 : : * Note that this method doesn’t match content subtypes. For example, if
507 : : * `application/xml` is added to the blocklist but `application/xspf+xml` is not,
508 : : * a check for whether `application/xspf+xml` is blocklisted would return false.
509 : : *
510 : : * Returns: %TRUE if the user this @filter corresponds to is allowed to run
511 : : * programs handling @content_type according to the @filter policy;
512 : : * %FALSE otherwise
513 : : * Since: 0.4.0
514 : : */
515 : : gboolean
516 : 44 : mct_app_filter_is_content_type_allowed (MctAppFilter *filter,
517 : : const gchar *content_type)
518 : : {
519 : 44 : g_return_val_if_fail (filter != NULL, FALSE);
520 : 44 : g_return_val_if_fail (filter->ref_count >= 1, FALSE);
521 : 44 : g_return_val_if_fail (content_type != NULL, FALSE);
522 : 44 : g_return_val_if_fail (is_valid_content_type (content_type), FALSE);
523 : :
524 : 44 : gboolean ref_in_list = g_strv_contains ((const gchar * const *) filter->app_list,
525 : : content_type);
526 : :
527 [ + + - ]: 44 : switch (filter->app_list_type)
528 : : {
529 : 42 : case MCT_APP_FILTER_LIST_BLOCKLIST:
530 : 42 : return !ref_in_list;
531 : 2 : case MCT_APP_FILTER_LIST_ALLOWLIST:
532 : 2 : return ref_in_list;
533 : 0 : default:
534 : : g_assert_not_reached ();
535 : : }
536 : : }
537 : :
538 : : static gint
539 : 6 : strcmp_cb (gconstpointer a,
540 : : gconstpointer b)
541 : : {
542 : 6 : const gchar *str_a = *((const gchar * const *) a);
543 : 6 : const gchar *str_b = *((const gchar * const *) b);
544 : :
545 : 6 : return g_strcmp0 (str_a, str_b);
546 : : }
547 : :
548 : : /**
549 : : * mct_app_filter_get_oars_sections:
550 : : * @filter: an #MctAppFilter
551 : : *
552 : : * List the OARS sections present in this app filter. The sections are returned
553 : : * in lexicographic order. A section will be listed even if its stored value is
554 : : * %MCT_APP_FILTER_OARS_VALUE_UNKNOWN. The returned list may be empty.
555 : : *
556 : : * Returns: (transfer container) (array zero-terminated=1): %NULL-terminated
557 : : * array of OARS sections
558 : : * Since: 0.2.0
559 : : */
560 : : const gchar **
561 : 13 : mct_app_filter_get_oars_sections (MctAppFilter *filter)
562 : : {
563 : 26 : g_autoptr(GPtrArray) sections = g_ptr_array_new_with_free_func (NULL);
564 : : GVariantIter iter;
565 : : const gchar *oars_section;
566 : :
567 : 13 : g_return_val_if_fail (filter != NULL, NULL);
568 : 13 : g_return_val_if_fail (filter->ref_count >= 1, NULL);
569 : :
570 : 13 : g_variant_iter_init (&iter, filter->oars_ratings);
571 : :
572 [ + + ]: 25 : while (g_variant_iter_loop (&iter, "{&s&s}", &oars_section, NULL))
573 : 12 : g_ptr_array_add (sections, (gpointer) oars_section);
574 : :
575 : : /* Sort alphabetically for easier comparisons later. */
576 : 13 : g_ptr_array_sort (sections, strcmp_cb);
577 : :
578 : 13 : g_ptr_array_add (sections, NULL); /* NULL terminator */
579 : :
580 : 13 : return (const gchar **) g_ptr_array_free (g_steal_pointer (§ions), FALSE);
581 : : }
582 : :
583 : : /**
584 : : * mct_app_filter_get_oars_value:
585 : : * @filter: an #MctAppFilter
586 : : * @oars_section: name of the OARS section to get the value from
587 : : *
588 : : * Get the value assigned to the given @oars_section in the OARS filter stored
589 : : * within @filter. If that section has no value explicitly defined,
590 : : * %MCT_APP_FILTER_OARS_VALUE_UNKNOWN is returned.
591 : : *
592 : : * This value is the most intense value allowed for apps to have in this
593 : : * section, inclusive. Any app with a more intense value for this section must
594 : : * be hidden from the user whose @filter this is.
595 : : *
596 : : * This does not factor in mct_app_filter_is_system_installation_allowed().
597 : : *
598 : : * Returns: an #MctAppFilterOarsValue
599 : : * Since: 0.2.0
600 : : */
601 : : MctAppFilterOarsValue
602 : 43 : mct_app_filter_get_oars_value (MctAppFilter *filter,
603 : : const gchar *oars_section)
604 : : {
605 : : const gchar *value_str;
606 : :
607 : 43 : g_return_val_if_fail (filter != NULL, MCT_APP_FILTER_OARS_VALUE_UNKNOWN);
608 : 43 : g_return_val_if_fail (filter->ref_count >= 1,
609 : : MCT_APP_FILTER_OARS_VALUE_UNKNOWN);
610 : 43 : g_return_val_if_fail (oars_section != NULL && *oars_section != '\0',
611 : : MCT_APP_FILTER_OARS_VALUE_UNKNOWN);
612 : :
613 [ + + ]: 43 : if (!g_variant_lookup (filter->oars_ratings, oars_section, "&s", &value_str))
614 : 26 : return MCT_APP_FILTER_OARS_VALUE_UNKNOWN;
615 : :
616 : 17 : return oars_str_to_enum (value_str);
617 : : }
618 : :
619 : : /**
620 : : * mct_app_filter_is_user_installation_allowed:
621 : : * @filter: an #MctAppFilter
622 : : *
623 : : * Get whether the user is allowed to install to their flatpak user repository.
624 : : * This should be queried in addition to the OARS values
625 : : * (mct_app_filter_get_oars_value()) — if it returns %FALSE, the OARS values
626 : : * should be ignored and app installation should be unconditionally disallowed.
627 : : *
628 : : * Returns: %TRUE if app installation is allowed to the user repository for
629 : : * this user; %FALSE if it is unconditionally disallowed for this user
630 : : * Since: 0.2.0
631 : : */
632 : : gboolean
633 : 17 : mct_app_filter_is_user_installation_allowed (MctAppFilter *filter)
634 : : {
635 : 17 : g_return_val_if_fail (filter != NULL, FALSE);
636 : 17 : g_return_val_if_fail (filter->ref_count >= 1, FALSE);
637 : :
638 : 17 : return filter->allow_user_installation;
639 : : }
640 : :
641 : : /**
642 : : * mct_app_filter_is_system_installation_allowed:
643 : : * @filter: an #MctAppFilter
644 : : *
645 : : * Get whether the user is allowed to install to the flatpak system repository.
646 : : * This should be queried in addition to the OARS values
647 : : * (mct_app_filter_get_oars_value()) — if it returns %FALSE, the OARS values
648 : : * should be ignored and app installation should be unconditionally disallowed.
649 : : *
650 : : * Returns: %TRUE if app installation is allowed to the system repository for
651 : : * this user; %FALSE if it is unconditionally disallowed for this user
652 : : * Since: 0.2.0
653 : : */
654 : : gboolean
655 : 17 : mct_app_filter_is_system_installation_allowed (MctAppFilter *filter)
656 : : {
657 : 17 : g_return_val_if_fail (filter != NULL, FALSE);
658 : 17 : g_return_val_if_fail (filter->ref_count >= 1, FALSE);
659 : :
660 : 17 : return filter->allow_system_installation;
661 : : }
662 : :
663 : : /**
664 : : * _mct_app_filter_build_app_filter_variant:
665 : : * @filter: an #MctAppFilter
666 : : *
667 : : * Build a #GVariant which contains the app filter from @filter, in the format
668 : : * used for storing it in AccountsService.
669 : : *
670 : : * Returns: (transfer floating): a new, floating #GVariant containing the app
671 : : * filter
672 : : */
673 : : static GVariant *
674 : 10 : _mct_app_filter_build_app_filter_variant (MctAppFilter *filter)
675 : : {
676 : 20 : g_auto(GVariantBuilder) builder = G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE ("(bas)"));
677 : :
678 : 10 : g_return_val_if_fail (filter != NULL, NULL);
679 : 10 : g_return_val_if_fail (filter->ref_count >= 1, NULL);
680 : :
681 : 10 : g_variant_builder_add (&builder, "b",
682 : 10 : (filter->app_list_type == MCT_APP_FILTER_LIST_ALLOWLIST));
683 : 10 : g_variant_builder_open (&builder, G_VARIANT_TYPE ("as"));
684 : :
685 [ + + ]: 18 : for (gsize i = 0; filter->app_list[i] != NULL; i++)
686 : 8 : g_variant_builder_add (&builder, "s", filter->app_list[i]);
687 : :
688 : 10 : g_variant_builder_close (&builder);
689 : :
690 : 10 : return g_variant_builder_end (&builder);
691 : : }
692 : :
693 : : /**
694 : : * mct_app_filter_serialize:
695 : : * @filter: an #MctAppFilter
696 : : *
697 : : * Build a #GVariant which contains the app filter from @filter, in an opaque
698 : : * variant format. This format may change in future, but
699 : : * mct_app_filter_deserialize() is guaranteed to always be able to load any
700 : : * variant produced by the current or any previous version of
701 : : * mct_app_filter_serialize().
702 : : *
703 : : * Returns: (transfer floating): a new, floating #GVariant containing the app
704 : : * filter
705 : : * Since: 0.7.0
706 : : */
707 : : GVariant *
708 : 10 : mct_app_filter_serialize (MctAppFilter *filter)
709 : : {
710 : 20 : g_auto(GVariantBuilder) builder = G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE ("a{sv}"));
711 : :
712 : 10 : g_return_val_if_fail (filter != NULL, NULL);
713 : 10 : g_return_val_if_fail (filter->ref_count >= 1, NULL);
714 : :
715 : : /* The serialisation format is exactly the
716 : : * `com.endlessm.ParentalControls.AppFilter` D-Bus interface. */
717 : 10 : g_variant_builder_add (&builder, "{sv}", "AppFilter",
718 : : _mct_app_filter_build_app_filter_variant (filter));
719 : 10 : g_variant_builder_add (&builder, "{sv}", "OarsFilter",
720 : : g_variant_new ("(s@a{ss})", "oars-1.1",
721 : : filter->oars_ratings));
722 : 10 : g_variant_builder_add (&builder, "{sv}", "AllowUserInstallation",
723 : : g_variant_new_boolean (filter->allow_user_installation));
724 : 10 : g_variant_builder_add (&builder, "{sv}", "AllowSystemInstallation",
725 : : g_variant_new_boolean (filter->allow_system_installation));
726 : :
727 : 10 : return g_variant_builder_end (&builder);
728 : : }
729 : :
730 : : /**
731 : : * mct_app_filter_deserialize:
732 : : * @variant: a serialized app filter variant
733 : : * @user_id: the ID of the user the app filter relates to
734 : : * @error: return location for a #GError, or %NULL
735 : : *
736 : : * Deserialize an app filter previously serialized with
737 : : * mct_app_filter_serialize(). This function guarantees to be able to
738 : : * deserialize any serialized form from this version or older versions of
739 : : * libmalcontent.
740 : : *
741 : : * If deserialization fails, %MCT_MANAGER_ERROR_INVALID_DATA will be returned.
742 : : *
743 : : * Returns: (transfer full): deserialized app filter
744 : : * Since: 0.7.0
745 : : */
746 : : MctAppFilter *
747 : 61 : mct_app_filter_deserialize (GVariant *variant,
748 : : uid_t user_id,
749 : : GError **error)
750 : : {
751 : : gboolean is_allowlist;
752 : 61 : g_auto(GStrv) app_list = NULL;
753 : : const gchar *content_rating_kind;
754 : 61 : g_autoptr(GVariant) oars_variant = NULL;
755 : : gboolean allow_user_installation;
756 : : gboolean allow_system_installation;
757 : 61 : g_autoptr(MctAppFilter) app_filter = NULL;
758 : :
759 : 61 : g_return_val_if_fail (variant != NULL, NULL);
760 : 61 : g_return_val_if_fail (error == NULL || *error == NULL, NULL);
761 : :
762 : : /* Check the overall type. */
763 [ + + ]: 61 : if (!g_variant_is_of_type (variant, G_VARIANT_TYPE ("a{sv}")))
764 : : {
765 : 4 : g_set_error (error, MCT_MANAGER_ERROR,
766 : : MCT_MANAGER_ERROR_INVALID_DATA,
767 : : _("App filter for user %u was in an unrecognized format"),
768 : : (guint) user_id);
769 : 4 : return NULL;
770 : : }
771 : :
772 : : /* Extract the properties we care about. The default values here should be
773 : : * kept in sync with those in the `com.endlessm.ParentalControls.AppFilter`
774 : : * D-Bus interface. */
775 [ + + ]: 57 : if (!g_variant_lookup (variant, "AppFilter", "(b^as)",
776 : : &is_allowlist, &app_list))
777 : : {
778 : : /* Default value. */
779 : 40 : is_allowlist = FALSE;
780 : 40 : app_list = g_new0 (gchar *, 1);
781 : : }
782 : :
783 [ + + ]: 57 : if (!g_variant_lookup (variant, "OarsFilter", "(&s@a{ss})",
784 : : &content_rating_kind, &oars_variant))
785 : : {
786 : : /* Default value. */
787 : 33 : content_rating_kind = "oars-1.1";
788 : 33 : oars_variant = g_variant_new ("a{ss}", NULL);
789 : : }
790 : :
791 : : /* Check that the OARS filter is in a format we support. Currently, that’s
792 : : * only oars-1.0 and oars-1.1. */
793 [ + - ]: 57 : if (!g_str_equal (content_rating_kind, "oars-1.0") &&
794 [ + + ]: 57 : !g_str_equal (content_rating_kind, "oars-1.1"))
795 : : {
796 : 2 : g_set_error (error, MCT_MANAGER_ERROR,
797 : : MCT_MANAGER_ERROR_INVALID_DATA,
798 : : _("OARS filter for user %u has an unrecognized kind ‘%s’"),
799 : : (guint) user_id, content_rating_kind);
800 : 2 : return NULL;
801 : : }
802 : :
803 [ + + ]: 55 : if (!g_variant_lookup (variant, "AllowUserInstallation", "b",
804 : : &allow_user_installation))
805 : : {
806 : : /* Default value. */
807 : 43 : allow_user_installation = TRUE;
808 : : }
809 : :
810 [ + + ]: 55 : if (!g_variant_lookup (variant, "AllowSystemInstallation", "b",
811 : : &allow_system_installation))
812 : : {
813 : : /* Default value. */
814 : 43 : allow_system_installation = FALSE;
815 : : }
816 : :
817 : : /* Success. Create an #MctAppFilter object to contain the results. */
818 : 55 : app_filter = g_new0 (MctAppFilter, 1);
819 : 55 : app_filter->ref_count = 1;
820 : 55 : app_filter->user_id = user_id;
821 : 55 : app_filter->app_list = g_steal_pointer (&app_list);
822 : 55 : app_filter->app_list_type =
823 : 55 : is_allowlist ? MCT_APP_FILTER_LIST_ALLOWLIST : MCT_APP_FILTER_LIST_BLOCKLIST;
824 : 55 : app_filter->oars_ratings = g_steal_pointer (&oars_variant);
825 : 55 : app_filter->allow_user_installation = allow_user_installation;
826 : 55 : app_filter->allow_system_installation = allow_system_installation;
827 : :
828 : 55 : return g_steal_pointer (&app_filter);
829 : : }
830 : :
831 : : /**
832 : : * mct_app_filter_equal:
833 : : * @a: (not nullable): an #MctAppFilter
834 : : * @b: (not nullable): an #MctAppFilter
835 : : *
836 : : * Check whether app filters @a and @b are equal.
837 : : *
838 : : * Returns: %TRUE if @a and @b are equal, %FALSE otherwise
839 : : * Since: 0.10.0
840 : : */
841 : : gboolean
842 : 78 : mct_app_filter_equal (MctAppFilter *a,
843 : : MctAppFilter *b)
844 : : {
845 : 78 : g_return_val_if_fail (a != NULL, FALSE);
846 : 78 : g_return_val_if_fail (a->ref_count >= 1, FALSE);
847 : 78 : g_return_val_if_fail (b != NULL, FALSE);
848 : 78 : g_return_val_if_fail (b->ref_count >= 1, FALSE);
849 : :
850 : 136 : return (a->user_id == b->user_id &&
851 [ + + ]: 58 : a->app_list_type == b->app_list_type &&
852 [ + - ]: 42 : a->allow_user_installation == b->allow_user_installation &&
853 [ + + + + ]: 72 : a->allow_system_installation == b->allow_system_installation &&
854 [ + + + + ]: 166 : g_strv_equal ((const gchar * const *) a->app_list, (const gchar * const *) b->app_list) &&
855 : 22 : g_variant_equal (a->oars_ratings, b->oars_ratings));
856 : : }
857 : :
858 : : /*
859 : : * Actual implementation of #MctAppFilterBuilder.
860 : : *
861 : : * All members are %NULL if un-initialised, cleared, or ended.
862 : : */
863 : : typedef struct
864 : : {
865 : : GPtrArray *blocklist; /* (nullable) (owned) (element-type utf8) */
866 : : GHashTable *oars; /* (nullable) (owned) (element-type utf8 MctAppFilterOarsValue) */
867 : : gboolean allow_user_installation;
868 : : gboolean allow_system_installation;
869 : :
870 : : /*< private >*/
871 : : gpointer padding[2];
872 : : } MctAppFilterBuilderReal;
873 : :
874 : : G_STATIC_ASSERT (sizeof (MctAppFilterBuilderReal) ==
875 : : sizeof (MctAppFilterBuilder));
876 : : G_STATIC_ASSERT (__alignof__ (MctAppFilterBuilderReal) ==
877 : : __alignof__ (MctAppFilterBuilder));
878 : :
879 [ + - + - : 6 : G_DEFINE_BOXED_TYPE (MctAppFilterBuilder, mct_app_filter_builder,
+ - ]
880 : : mct_app_filter_builder_copy, mct_app_filter_builder_free)
881 : :
882 : : /**
883 : : * mct_app_filter_builder_init:
884 : : * @builder: an uninitialised #MctAppFilterBuilder
885 : : *
886 : : * Initialise the given @builder so it can be used to construct a new
887 : : * #MctAppFilter. @builder must have been allocated on the stack, and must not
888 : : * already be initialised.
889 : : *
890 : : * Construct the #MctAppFilter by calling methods on @builder, followed by
891 : : * mct_app_filter_builder_end(). To abort construction, use
892 : : * mct_app_filter_builder_clear().
893 : : *
894 : : * Since: 0.2.0
895 : : */
896 : : void
897 : 20 : mct_app_filter_builder_init (MctAppFilterBuilder *builder)
898 : : {
899 : 20 : MctAppFilterBuilder local_builder = MCT_APP_FILTER_BUILDER_INIT ();
900 : 20 : MctAppFilterBuilderReal *_builder = (MctAppFilterBuilderReal *) builder;
901 : :
902 : 20 : g_return_if_fail (_builder != NULL);
903 : 20 : g_return_if_fail (_builder->blocklist == NULL);
904 : 20 : g_return_if_fail (_builder->oars == NULL);
905 : :
906 : 20 : memcpy (builder, &local_builder, sizeof (local_builder));
907 : : }
908 : :
909 : : /**
910 : : * mct_app_filter_builder_clear:
911 : : * @builder: an #MctAppFilterBuilder
912 : : *
913 : : * Clear @builder, freeing any internal state in it. This will not free the
914 : : * top-level storage for @builder itself, which is assumed to be allocated on
915 : : * the stack.
916 : : *
917 : : * If called on an already-cleared #MctAppFilterBuilder, this function is
918 : : * idempotent.
919 : : *
920 : : * Since: 0.2.0
921 : : */
922 : : void
923 : 78 : mct_app_filter_builder_clear (MctAppFilterBuilder *builder)
924 : : {
925 : 78 : MctAppFilterBuilderReal *_builder = (MctAppFilterBuilderReal *) builder;
926 : :
927 : 78 : g_return_if_fail (_builder != NULL);
928 : :
929 [ + + ]: 78 : g_clear_pointer (&_builder->blocklist, g_ptr_array_unref);
930 [ + + ]: 78 : g_clear_pointer (&_builder->oars, g_hash_table_unref);
931 : : }
932 : :
933 : : /**
934 : : * mct_app_filter_builder_new:
935 : : *
936 : : * Construct a new #MctAppFilterBuilder on the heap. This is intended for
937 : : * language bindings. The returned builder must eventually be freed with
938 : : * mct_app_filter_builder_free(), but can be cleared zero or more times with
939 : : * mct_app_filter_builder_clear() first.
940 : : *
941 : : * Returns: (transfer full): a new heap-allocated #MctAppFilterBuilder
942 : : * Since: 0.2.0
943 : : */
944 : : MctAppFilterBuilder *
945 : 12 : mct_app_filter_builder_new (void)
946 : : {
947 : 12 : g_autoptr(MctAppFilterBuilder) builder = NULL;
948 : :
949 : 12 : builder = g_new0 (MctAppFilterBuilder, 1);
950 : 12 : mct_app_filter_builder_init (builder);
951 : :
952 : 12 : return g_steal_pointer (&builder);
953 : : }
954 : :
955 : : /**
956 : : * mct_app_filter_builder_copy:
957 : : * @builder: an #MctAppFilterBuilder
958 : : *
959 : : * Copy the given @builder to a newly-allocated #MctAppFilterBuilder on the
960 : : * heap. This is safe to use with cleared, stack-allocated
961 : : * #MctAppFilterBuilders.
962 : : *
963 : : * Returns: (transfer full): a copy of @builder
964 : : * Since: 0.2.0
965 : : */
966 : : MctAppFilterBuilder *
967 : 4 : mct_app_filter_builder_copy (MctAppFilterBuilder *builder)
968 : : {
969 : 4 : MctAppFilterBuilderReal *_builder = (MctAppFilterBuilderReal *) builder;
970 : 4 : g_autoptr(MctAppFilterBuilder) copy = NULL;
971 : : MctAppFilterBuilderReal *_copy;
972 : :
973 : 4 : g_return_val_if_fail (builder != NULL, NULL);
974 : :
975 : 4 : copy = mct_app_filter_builder_new ();
976 : 4 : _copy = (MctAppFilterBuilderReal *) copy;
977 : :
978 : 4 : mct_app_filter_builder_clear (copy);
979 [ + + ]: 4 : if (_builder->blocklist != NULL)
980 : 2 : _copy->blocklist = g_ptr_array_ref (_builder->blocklist);
981 [ + + ]: 4 : if (_builder->oars != NULL)
982 : 2 : _copy->oars = g_hash_table_ref (_builder->oars);
983 : 4 : _copy->allow_user_installation = _builder->allow_user_installation;
984 : 4 : _copy->allow_system_installation = _builder->allow_system_installation;
985 : :
986 : 4 : return g_steal_pointer (©);
987 : : }
988 : :
989 : : /**
990 : : * mct_app_filter_builder_free:
991 : : * @builder: a heap-allocated #MctAppFilterBuilder
992 : : *
993 : : * Free an #MctAppFilterBuilder originally allocated using
994 : : * mct_app_filter_builder_new(). This must not be called on stack-allocated
995 : : * builders initialised using mct_app_filter_builder_init().
996 : : *
997 : : * Since: 0.2.0
998 : : */
999 : : void
1000 : 12 : mct_app_filter_builder_free (MctAppFilterBuilder *builder)
1001 : : {
1002 : 12 : g_return_if_fail (builder != NULL);
1003 : :
1004 : 12 : mct_app_filter_builder_clear (builder);
1005 : 12 : g_free (builder);
1006 : : }
1007 : :
1008 : : /**
1009 : : * mct_app_filter_builder_end:
1010 : : * @builder: an initialised #MctAppFilterBuilder
1011 : : *
1012 : : * Finish constructing an #MctAppFilter with the given @builder, and return it.
1013 : : * The #MctAppFilterBuilder will be cleared as if mct_app_filter_builder_clear()
1014 : : * had been called.
1015 : : *
1016 : : * Returns: (transfer full): a newly constructed #MctAppFilter
1017 : : * Since: 0.2.0
1018 : : */
1019 : : MctAppFilter *
1020 : 35 : mct_app_filter_builder_end (MctAppFilterBuilder *builder)
1021 : : {
1022 : 35 : MctAppFilterBuilderReal *_builder = (MctAppFilterBuilderReal *) builder;
1023 : 35 : g_autoptr(MctAppFilter) app_filter = NULL;
1024 : 70 : g_auto(GVariantBuilder) oars_builder = G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE ("a{ss}"));
1025 : : GHashTableIter iter;
1026 : : gpointer key, value;
1027 : 35 : g_autoptr(GVariant) oars_variant = NULL;
1028 : :
1029 : 35 : g_return_val_if_fail (_builder != NULL, NULL);
1030 : 35 : g_return_val_if_fail (_builder->blocklist != NULL, NULL);
1031 : 35 : g_return_val_if_fail (_builder->oars != NULL, NULL);
1032 : :
1033 : : /* Ensure the paths list is %NULL-terminated. */
1034 : 35 : g_ptr_array_add (_builder->blocklist, NULL);
1035 : :
1036 : : /* Build the OARS variant. */
1037 : 35 : g_hash_table_iter_init (&iter, _builder->oars);
1038 [ + + ]: 49 : while (g_hash_table_iter_next (&iter, &key, &value))
1039 : : {
1040 : 14 : const gchar *oars_section = key;
1041 : 14 : MctAppFilterOarsValue oars_value = GPOINTER_TO_INT (value);
1042 : 14 : const gchar *oars_value_strs[] =
1043 : : {
1044 : : NULL, /* MCT_APP_FILTER_OARS_VALUE_UNKNOWN */
1045 : : "none",
1046 : : "mild",
1047 : : "moderate",
1048 : : "intense",
1049 : : };
1050 : :
1051 : 14 : g_assert ((int) oars_value >= 0 &&
1052 : : (int) oars_value < (int) G_N_ELEMENTS (oars_value_strs));
1053 : :
1054 [ + - ]: 14 : if (oars_value_strs[oars_value] != NULL)
1055 : 14 : g_variant_builder_add (&oars_builder, "{ss}",
1056 : : oars_section, oars_value_strs[oars_value]);
1057 : : }
1058 : :
1059 : 35 : oars_variant = g_variant_ref_sink (g_variant_builder_end (&oars_builder));
1060 : :
1061 : : /* Build the #MctAppFilter. */
1062 : 35 : app_filter = g_new0 (MctAppFilter, 1);
1063 : 35 : app_filter->ref_count = 1;
1064 : 35 : app_filter->user_id = -1;
1065 : 35 : app_filter->app_list = (gchar **) g_ptr_array_free (g_steal_pointer (&_builder->blocklist), FALSE);
1066 : 35 : app_filter->app_list_type = MCT_APP_FILTER_LIST_BLOCKLIST;
1067 : 35 : app_filter->oars_ratings = g_steal_pointer (&oars_variant);
1068 : 35 : app_filter->allow_user_installation = _builder->allow_user_installation;
1069 : 35 : app_filter->allow_system_installation = _builder->allow_system_installation;
1070 : :
1071 : 35 : mct_app_filter_builder_clear (builder);
1072 : :
1073 : 35 : return g_steal_pointer (&app_filter);
1074 : : }
1075 : :
1076 : : /**
1077 : : * mct_app_filter_builder_blocklist_path:
1078 : : * @builder: an initialised #MctAppFilterBuilder
1079 : : * @path: (type filename): an absolute path to blocklist
1080 : : *
1081 : : * Add @path to the blocklist of app paths in the filter under construction. It
1082 : : * will be canonicalised (without doing any I/O) before being added.
1083 : : * The canonicalised @path will not be added again if it’s already been added.
1084 : : *
1085 : : * Since: 0.2.0
1086 : : */
1087 : : void
1088 : 22 : mct_app_filter_builder_blocklist_path (MctAppFilterBuilder *builder,
1089 : : const gchar *path)
1090 : : {
1091 : 22 : MctAppFilterBuilderReal *_builder = (MctAppFilterBuilderReal *) builder;
1092 : :
1093 : 22 : g_return_if_fail (_builder != NULL);
1094 : 22 : g_return_if_fail (_builder->blocklist != NULL);
1095 : 22 : g_return_if_fail (path != NULL);
1096 : 22 : g_return_if_fail (g_path_is_absolute (path));
1097 : :
1098 [ + - ]: 44 : g_autofree gchar *canonical_path = g_canonicalize_filename (path, "/");
1099 [ + - ]: 44 : g_autofree gchar *canonical_path_utf8 = g_filename_to_utf8 (canonical_path, -1,
1100 : : NULL, NULL, NULL);
1101 : 22 : g_return_if_fail (canonical_path_utf8 != NULL);
1102 : :
1103 [ + - ]: 22 : if (!g_ptr_array_find_with_equal_func (_builder->blocklist,
1104 : : canonical_path_utf8, g_str_equal, NULL))
1105 : 22 : g_ptr_array_add (_builder->blocklist, g_steal_pointer (&canonical_path_utf8));
1106 : : }
1107 : :
1108 : : /**
1109 : : * mct_app_filter_builder_blocklist_flatpak_ref:
1110 : : * @builder: an initialised #MctAppFilterBuilder
1111 : : * @app_ref: a flatpak app ref to blocklist
1112 : : *
1113 : : * Add @app_ref to the blocklist of flatpak refs in the filter under
1114 : : * construction. The @app_ref will not be added again if it’s already been
1115 : : * added.
1116 : : *
1117 : : * Since: 0.2.0
1118 : : */
1119 : : void
1120 : 10 : mct_app_filter_builder_blocklist_flatpak_ref (MctAppFilterBuilder *builder,
1121 : : const gchar *app_ref)
1122 : : {
1123 : 10 : MctAppFilterBuilderReal *_builder = (MctAppFilterBuilderReal *) builder;
1124 : :
1125 : 10 : g_return_if_fail (_builder != NULL);
1126 : 10 : g_return_if_fail (_builder->blocklist != NULL);
1127 : 10 : g_return_if_fail (app_ref != NULL);
1128 : 10 : g_return_if_fail (is_valid_flatpak_ref (app_ref));
1129 : :
1130 [ + - ]: 10 : if (!g_ptr_array_find_with_equal_func (_builder->blocklist,
1131 : : app_ref, g_str_equal, NULL))
1132 : 10 : g_ptr_array_add (_builder->blocklist, g_strdup (app_ref));
1133 : : }
1134 : :
1135 : : /**
1136 : : * mct_app_filter_builder_blocklist_content_type:
1137 : : * @builder: an initialised #MctAppFilterBuilder
1138 : : * @content_type: a content type to blocklist
1139 : : *
1140 : : * Add @content_type to the blocklist of content types in the filter under
1141 : : * construction. The @content_type will not be added again if it’s already been
1142 : : * added.
1143 : : *
1144 : : * Note that this method doesn’t handle content subtypes. For example, if
1145 : : * `application/xml` is added to the blocklist but `application/xspf+xml` is not,
1146 : : * a check for whether `application/xspf+xml` is blocklisted would return false.
1147 : : *
1148 : : * Since: 0.4.0
1149 : : */
1150 : : void
1151 : 14 : mct_app_filter_builder_blocklist_content_type (MctAppFilterBuilder *builder,
1152 : : const gchar *content_type)
1153 : : {
1154 : 14 : MctAppFilterBuilderReal *_builder = (MctAppFilterBuilderReal *) builder;
1155 : :
1156 : 14 : g_return_if_fail (_builder != NULL);
1157 : 14 : g_return_if_fail (_builder->blocklist != NULL);
1158 : 14 : g_return_if_fail (content_type != NULL);
1159 : 14 : g_return_if_fail (is_valid_content_type (content_type));
1160 : :
1161 [ + - ]: 14 : if (!g_ptr_array_find_with_equal_func (_builder->blocklist,
1162 : : content_type, g_str_equal, NULL))
1163 : 14 : g_ptr_array_add (_builder->blocklist, g_strdup (content_type));
1164 : : }
1165 : :
1166 : : /**
1167 : : * mct_app_filter_builder_set_oars_value:
1168 : : * @builder: an initialised #MctAppFilterBuilder
1169 : : * @oars_section: name of the OARS section to set the value for
1170 : : * @value: value to set for the @oars_section
1171 : : *
1172 : : * Set the OARS value for the given @oars_section, indicating the intensity of
1173 : : * content covered by that section which the user is allowed to see (inclusive).
1174 : : * Any apps which have more intense content in this section should not be usable
1175 : : * by the user.
1176 : : *
1177 : : * Since: 0.2.0
1178 : : */
1179 : : void
1180 : 14 : mct_app_filter_builder_set_oars_value (MctAppFilterBuilder *builder,
1181 : : const gchar *oars_section,
1182 : : MctAppFilterOarsValue value)
1183 : : {
1184 : 14 : MctAppFilterBuilderReal *_builder = (MctAppFilterBuilderReal *) builder;
1185 : :
1186 : 14 : g_return_if_fail (_builder != NULL);
1187 : 14 : g_return_if_fail (_builder->oars != NULL);
1188 : 14 : g_return_if_fail (oars_section != NULL && *oars_section != '\0');
1189 : :
1190 : 14 : g_hash_table_insert (_builder->oars, g_strdup (oars_section),
1191 : 14 : GUINT_TO_POINTER (value));
1192 : : }
1193 : :
1194 : : /**
1195 : : * mct_app_filter_builder_set_allow_user_installation:
1196 : : * @builder: an initialised #MctAppFilterBuilder
1197 : : * @allow_user_installation: %TRUE to allow app installation; %FALSE to
1198 : : * unconditionally disallow it
1199 : : *
1200 : : * Set whether the user is allowed to install to their flatpak user repository.
1201 : : * If this is %TRUE, app installation is still subject to the OARS values
1202 : : * (mct_app_filter_builder_set_oars_value()). If it is %FALSE, app installation
1203 : : * is unconditionally disallowed for this user.
1204 : : *
1205 : : * Since: 0.2.0
1206 : : */
1207 : : void
1208 : 10 : mct_app_filter_builder_set_allow_user_installation (MctAppFilterBuilder *builder,
1209 : : gboolean allow_user_installation)
1210 : : {
1211 : 10 : MctAppFilterBuilderReal *_builder = (MctAppFilterBuilderReal *) builder;
1212 : :
1213 : 10 : g_return_if_fail (_builder != NULL);
1214 : :
1215 : 10 : _builder->allow_user_installation = allow_user_installation;
1216 : : }
1217 : :
1218 : : /**
1219 : : * mct_app_filter_builder_set_allow_system_installation:
1220 : : * @builder: an initialised #MctAppFilterBuilder
1221 : : * @allow_system_installation: %TRUE to allow app installation; %FALSE to
1222 : : * unconditionally disallow it
1223 : : *
1224 : : * Set whether the user is allowed to install to the flatpak system repository.
1225 : : * If this is %TRUE, app installation is still subject to the OARS values
1226 : : * (mct_app_filter_builder_set_oars_value()). If it is %FALSE, app installation
1227 : : * is unconditionally disallowed for this user.
1228 : : *
1229 : : * Since: 0.2.0
1230 : : */
1231 : : void
1232 : 10 : mct_app_filter_builder_set_allow_system_installation (MctAppFilterBuilder *builder,
1233 : : gboolean allow_system_installation)
1234 : : {
1235 : 10 : MctAppFilterBuilderReal *_builder = (MctAppFilterBuilderReal *) builder;
1236 : :
1237 : 10 : g_return_if_fail (_builder != NULL);
1238 : :
1239 : 10 : _builder->allow_system_installation = allow_system_installation;
1240 : : }
|