diff options
Diffstat (limited to '')
-rw-r--r-- | lib/gs-app-permissions.c | 430 |
1 files changed, 430 insertions, 0 deletions
diff --git a/lib/gs-app-permissions.c b/lib/gs-app-permissions.c new file mode 100644 index 0000000..bbae07c --- /dev/null +++ b/lib/gs-app-permissions.c @@ -0,0 +1,430 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- + * vi:set noexpandtab tabstop=8 shiftwidth=8: + * + * Copyright (C) 2022 Red Hat <www.redhat.com> + * + * SPDX-License-Identifier: GPL-2.0+ + */ + +/** + * SECTION:gs-app-permissions + * @short_description: A representation of the permissions requested by an app + * + * #GsAppPermissions is an object to represent the permissions requested by an app. + * + * While some common permissions are handled with the #GsAppPermissionsFlags, + * the object allows more detailed permissions to be represented, such as + * specific file system path access. + * + * Since: 43 + */ + +#include "config.h" + +#include <stdlib.h> + +#include <glib.h> +#include <glib-object.h> + +#include "gs-app-permissions.h" + +#define DOES_NOT_CONTAIN ((guint) ~0) + +struct _GsAppPermissions +{ + GObject parent; + + gboolean is_sealed; + GsAppPermissionsFlags flags; + GPtrArray *filesystem_read; /* (owner) (nullable) (element-type utf-8) */ + GPtrArray *filesystem_full; /* (owner) (nullable) (element-type utf-8) */ +}; + +G_DEFINE_TYPE (GsAppPermissions, gs_app_permissions, G_TYPE_OBJECT) + +static gint +cmp_filename_qsort (gconstpointer item1, + gconstpointer item2) +{ + const gchar * const *pitem1 = item1; + const gchar * const *pitem2 = item2; + return strcmp (*pitem1, *pitem2); +} + +static gint +cmp_filename_bsearch (gconstpointer item1, + gconstpointer item2) +{ + return strcmp (item1, item2); +} + +static void +gs_app_permissions_finalize (GObject *object) +{ + GsAppPermissions *self = GS_APP_PERMISSIONS (object); + + g_clear_pointer (&self->filesystem_read, g_ptr_array_unref); + g_clear_pointer (&self->filesystem_full, g_ptr_array_unref); + + G_OBJECT_CLASS (gs_app_permissions_parent_class)->finalize (object); +} + +static void +gs_app_permissions_class_init (GsAppPermissionsClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->finalize = gs_app_permissions_finalize; +} + +static void +gs_app_permissions_init (GsAppPermissions *self) +{ +} + +/** + * gs_app_permissions_new: + * + * Create a new #GsAppPermissions containing the app permissions. + * + * Returns: (transfer full): a new #GsAppPermissions + * Since: 43 + */ +GsAppPermissions * +gs_app_permissions_new (void) +{ + return g_object_new (GS_TYPE_APP_PERMISSIONS, NULL); +} + +/** + * gs_app_permissions_seal: + * @self: a #GsAppPermissions + * + * Seal the @self. After being called, no modifications can be + * done on the @self. + * + * Since: 43 + **/ +void +gs_app_permissions_seal (GsAppPermissions *self) +{ + g_return_if_fail (GS_IS_APP_PERMISSIONS (self)); + + if (self->is_sealed) + return; + + self->is_sealed = TRUE; + + /* Sort the arrays, which will help with searching */ + if (self->filesystem_read) + qsort (self->filesystem_read->pdata, self->filesystem_read->len, sizeof (gpointer), cmp_filename_qsort); + + if (self->filesystem_full) + qsort (self->filesystem_full->pdata, self->filesystem_full->len, sizeof (gpointer), cmp_filename_qsort); +} + +/** + * gs_app_permissions_is_sealed: + * @self: a #GsAppPermissions + * + * Checks whether the @self had been sealed. Once the @self is sealed, + * no modifications can be made to it. + * + * Returns: whether the @self had been sealed + * + * Since: 43 + **/ +gboolean +gs_app_permissions_is_sealed (GsAppPermissions *self) +{ + g_return_val_if_fail (GS_IS_APP_PERMISSIONS (self), TRUE); + + return self->is_sealed; +} + +/** + * gs_app_permissions_set_flags: + * @self: a #GsAppPermissions + * @flags: a #GsAppPermissionsFlags to set + * + * Set the permission flags, overwriting any previously set flags. + * Compare to gs_app_permissions_add_flag() and + * gs_app_permissions_remove_flag(). + * + * Since: 43 + */ +void +gs_app_permissions_set_flags (GsAppPermissions *self, + GsAppPermissionsFlags flags) +{ + g_return_if_fail (GS_IS_APP_PERMISSIONS (self)); + + g_assert (!self->is_sealed); + + self->flags = flags; +} + +/** + * gs_app_permissions_get_flags: + * @self: a #GsAppPermissions + * + * Get the permission flags. + * + * Returns: the permission flags + * Since: 43 + */ +GsAppPermissionsFlags +gs_app_permissions_get_flags (GsAppPermissions *self) +{ + g_return_val_if_fail (GS_IS_APP_PERMISSIONS (self), GS_APP_PERMISSIONS_FLAGS_UNKNOWN); + + return self->flags; +} + +/** + * gs_app_permissions_add_flag: + * @self: a #GsAppPermissions + * @flags: a #GsAppPermissionsFlags to add + * + * Add the @flags into the already set flags. The @flags cannot contain + * #GS_APP_PERMISSIONS_FLAGS_NONE, neither cannot be #GS_APP_PERMISSIONS_FLAGS_UNKNOWN. + * To set these two use gs_app_permissions_set_flags() instead. + * + * In case the current flags contain #GS_APP_PERMISSIONS_FLAGS_NONE, it's + * automatically unset. + * + * Since: 43 + */ +void +gs_app_permissions_add_flag (GsAppPermissions *self, + GsAppPermissionsFlags flags) +{ + g_return_if_fail (GS_IS_APP_PERMISSIONS (self)); + g_return_if_fail (flags != GS_APP_PERMISSIONS_FLAGS_UNKNOWN); + g_return_if_fail ((flags & GS_APP_PERMISSIONS_FLAGS_NONE) == 0); + + g_assert (!self->is_sealed); + + self->flags = (self->flags & (~GS_APP_PERMISSIONS_FLAGS_NONE)) | flags; +} + +/** + * gs_app_permissions_remove_flag: + * @self: a #GsAppPermissions + * @flags: a #GsAppPermissionsFlags to remove + * + * Remove the @flags from the already set flags. The @flags cannot contain + * #GS_APP_PERMISSIONS_FLAGS_NONE, neither cannot be #GS_APP_PERMISSIONS_FLAGS_UNKNOWN. + * To set these two use gs_app_permissions_set_flags() instead. + * + * In case the result of the removal would lead to no flag set the #GS_APP_PERMISSIONS_FLAGS_NONE + * is set automatically. + * + * Since: 43 + */ +void +gs_app_permissions_remove_flag (GsAppPermissions *self, + GsAppPermissionsFlags flags) +{ + g_return_if_fail (GS_IS_APP_PERMISSIONS (self)); + g_return_if_fail (flags != GS_APP_PERMISSIONS_FLAGS_UNKNOWN); + g_return_if_fail ((flags & GS_APP_PERMISSIONS_FLAGS_NONE) == 0); + + g_assert (!self->is_sealed); + + self->flags = (self->flags & (~flags)); + + if (!self->flags) + self->flags = GS_APP_PERMISSIONS_FLAGS_NONE; +} + +static guint +app_permissions_get_array_index (GPtrArray *array, + const gchar *filename) +{ + g_return_val_if_fail (filename != NULL, DOES_NOT_CONTAIN); + + if (array == NULL) + return DOES_NOT_CONTAIN; + + for (guint i = 0; i < array->len; i++) { + const gchar *item = g_ptr_array_index (array, i); + if (g_strcmp0 (item, filename) == 0) + return 0; + } + + return DOES_NOT_CONTAIN; +} + +/** + * gs_app_permissions_add_filesystem_read: + * @self: a #GsAppPermissions + * @filename: a filename to access + * + * Add @filename as a file to access for read. The @filename + * can be either a path or a localized pretty name of it, like "Documents". + * The addition is ignored in case the same @filename is part of + * the read or full access file names. + * + * Since: 43 + */ +void +gs_app_permissions_add_filesystem_read (GsAppPermissions *self, + const gchar *filename) +{ + g_return_if_fail (GS_IS_APP_PERMISSIONS (self)); + g_return_if_fail (filename != NULL); + + g_assert (!self->is_sealed); + + /* Already known */ + if (app_permissions_get_array_index (self->filesystem_read, filename) != DOES_NOT_CONTAIN || + app_permissions_get_array_index (self->filesystem_full, filename) != DOES_NOT_CONTAIN) + return; + + if (self->filesystem_read == NULL) + self->filesystem_read = g_ptr_array_new_with_free_func (g_free); + + g_ptr_array_add (self->filesystem_read, g_strdup (filename)); +} + +/** + * gs_app_permissions_get_filesystem_read: + * @self: a #GsAppPermissions + * + * Get the list of filesystem file names requested for read access using + * gs_app_permissions_add_filesystem_read(). + * The array is owned by the @self and should not be modified by any way. + * It can be %NULL, when no file access was set. + * + * Returns: (nullable) (transfer none) (element-type utf-8): an array of + * file names requesting read access or %NULL, when none was set. + * + * Since: 43 + */ +const GPtrArray * +gs_app_permissions_get_filesystem_read (GsAppPermissions *self) +{ + g_return_val_if_fail (GS_IS_APP_PERMISSIONS (self), NULL); + + return self->filesystem_read; +} + +static gboolean +array_contains_filename (GPtrArray *array, + const gchar *filename) +{ + if (array == NULL) + return FALSE; + + return bsearch (filename, array->pdata, array->len, sizeof (gpointer), cmp_filename_bsearch) != NULL; +} + +/** + * gs_app_permissions_contains_filesystem_read: + * @self: a #GsAppPermissions + * @filename: a file name to search for + * + * Checks whether the @filename is included in the filesystem read permissions. + * This can be called only after the @self is sealed. + * + * Returns: whether the @filename is part of the filesystem read permissions + * + * Since: 43 + **/ +gboolean +gs_app_permissions_contains_filesystem_read (GsAppPermissions *self, + const gchar *filename) +{ + g_return_val_if_fail (GS_IS_APP_PERMISSIONS (self), FALSE); + g_return_val_if_fail (filename != NULL, FALSE); + g_return_val_if_fail (self->is_sealed, FALSE); + + return array_contains_filename (self->filesystem_read, filename); +} + +/** + * gs_app_permissions_add_filesystem_full: + * @self: a #GsAppPermissions + * @filename: a filename to access + * + * Add @filename as a file to access for read and write. The @filename + * can be either a path or a localized pretty name of it, like "Documents". + * The addition is ignored in case the same @filename is include in the list + * already. The @filename is removed from the read list, if it's part of it. + * + * Since: 43 + */ +void +gs_app_permissions_add_filesystem_full (GsAppPermissions *self, + const gchar *filename) +{ + guint read_index; + + g_return_if_fail (GS_IS_APP_PERMISSIONS (self)); + g_return_if_fail (filename != NULL); + + g_assert (!self->is_sealed); + + /* Already known */ + if (app_permissions_get_array_index (self->filesystem_full, filename) != DOES_NOT_CONTAIN) + return; + + if (self->filesystem_full == NULL) + self->filesystem_full = g_ptr_array_new_with_free_func (g_free); + + g_ptr_array_add (self->filesystem_full, g_strdup (filename)); + + /* Remove from the read list and free the read list if becomes empty */ + read_index = app_permissions_get_array_index (self->filesystem_read, filename); + if (read_index != DOES_NOT_CONTAIN) { + g_ptr_array_remove_index (self->filesystem_read, read_index); + if (self->filesystem_read->len == 0) + g_clear_pointer (&self->filesystem_read, g_ptr_array_unref); + } +} + +/** + * gs_app_permissions_get_filesystem_full: + * @self: a #GsAppPermissions + * + * Get the list of filesystem file names requested for read and write access using + * gs_app_permissions_add_filesystem_full(). + * The array is owned by the @self and should not be modified by any way. + * It can be %NULL, when no file access was set. + * + * Returns: (nullable) (transfer none) (element-type utf-8): an array of + * file names requesting read and write access or %NULL, when none was set. + * + * Since: 43 + */ +const GPtrArray * +gs_app_permissions_get_filesystem_full (GsAppPermissions *self) +{ + g_return_val_if_fail (GS_IS_APP_PERMISSIONS (self), NULL); + + return self->filesystem_full; +} + +/** + * gs_app_permissions_contains_filesystem_full: + * @self: a #GsAppPermissions + * @filename: a file name to search for + * + * Checks whether the @filename is included in the filesystem full permissions. + * This can be called only after the @self is sealed. + * + * Returns: whether the @filename is part of the filesystem full permissions + * + * Since: 43 + **/ +gboolean +gs_app_permissions_contains_filesystem_full (GsAppPermissions *self, + const gchar *filename) +{ + g_return_val_if_fail (GS_IS_APP_PERMISSIONS (self), FALSE); + g_return_val_if_fail (filename != NULL, FALSE); + g_return_val_if_fail (self->is_sealed, FALSE); + + return array_contains_filename (self->filesystem_full, filename); +} |