diff --git a/libeos-parental-controls/app-filter.c b/libeos-parental-controls/app-filter.c index cbe7b50..da7efdf 100644 --- a/libeos-parental-controls/app-filter.c +++ b/libeos-parental-controls/app-filter.c @@ -205,6 +205,37 @@ epc_app_filter_get_oars_value (EpcAppFilter *filter, return EPC_APP_FILTER_OARS_VALUE_UNKNOWN; } +/** + * _epc_app_filter_build_app_filter_variant: + * @filter: an #EpcAppFilter + * + * Build a #GVariant which contains the app filter from @filter, in the format + * used for storing it in AccountsService. + * + * Returns: (transfer floating): a new, floating #GVariant containing the app + * filter + * Since: 0.1.0 + */ +static GVariant * +_epc_app_filter_build_app_filter_variant (EpcAppFilter *filter) +{ + g_auto(GVariantBuilder) builder = G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE ("(bas)")); + + g_return_val_if_fail (filter != NULL, NULL); + g_return_val_if_fail (filter->ref_count >= 1, NULL); + + g_variant_builder_add (&builder, "b", + (filter->app_list_type == EPC_APP_FILTER_LIST_WHITELIST)); + g_variant_builder_open (&builder, G_VARIANT_TYPE ("as")); + + for (gsize i = 0; filter->app_list[i] != NULL; i++) + g_variant_builder_add (&builder, "s", filter->app_list[i]); + + g_variant_builder_close (&builder); + + return g_variant_builder_end (&builder); +} + /* Check if @error is a D-Bus remote error matching @expected_error_name. */ static gboolean bus_remote_error_matches (const GError *error, @@ -452,3 +483,524 @@ epc_get_app_filter_finish (GAsyncResult *result, return g_task_propagate_pointer (G_TASK (result), error); } + +static void set_app_filter_get_bus_cb (GObject *obj, + GAsyncResult *result, + gpointer user_data); +static void set_app_filter (GDBusConnection *connection, + GTask *task); +static void set_app_filter_cb (GObject *obj, + GAsyncResult *result, + gpointer user_data); +static void set_oars_filter_cb (GObject *obj, + GAsyncResult *result, + gpointer user_data); +static void set_app_filter_complete (GDBusConnection *connection, + GTask *task); + +typedef struct +{ + uid_t user_id; + EpcAppFilter *app_filter; /* (owned) */ + gboolean allow_interactive_authorization; + + GAsyncResult *set_app_filter_result; /* (nullable) (owned) */ + GAsyncResult *set_oars_filter_result; /* (nullable) (owned) */ +} SetAppFilterData; + +static void +set_app_filter_data_free (SetAppFilterData *data) +{ + epc_app_filter_unref (data->app_filter); + g_clear_object (&data->set_app_filter_result); + g_clear_object (&data->set_oars_filter_result); + g_free (data); +} + +G_DEFINE_AUTOPTR_CLEANUP_FUNC (SetAppFilterData, set_app_filter_data_free) + +/** + * epc_set_app_filter_async: + * @connection: (nullable): a #GDBusConnection to the system bus, or %NULL to + * use the default + * @user_id: ID of the user to set the filter for, typically coming from getuid() + * @app_filter: (transfer none): the app filter to set for the user + * @allow_interactive_authorization: %TRUE to allow interactive polkit + * authorization dialogues to be displayed during the call; %FALSE otherwise + * @callback: a #GAsyncReadyCallback + * @cancellable: (nullable): a #GCancellable, or %NULL + * @user_data: user data to pass to @callback + * + * Asynchronously set the app filter settings for the given @user_id to the + * given @app_filter instance. This will set all fields of the app filter. + * + * @connection should be a connection to the system bus, where accounts-service + * runs. It’s provided mostly for testing purposes, or to allow an existing + * connection to be re-used. Pass %NULL to use the default connection. + * + * On failure, an #EpcAppFilterError, a #GDBusError or a #GIOError will be + * returned. The user’s app filter settings will be left in an undefined state. + * + * Since: 0.1.0 + */ +void +epc_set_app_filter_async (GDBusConnection *connection, + uid_t user_id, + EpcAppFilter *app_filter, + gboolean allow_interactive_authorization, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + g_autoptr(GDBusConnection) connection_owned = NULL; + g_autoptr(GTask) task = NULL; + g_autoptr(SetAppFilterData) data = NULL; + + g_return_if_fail (connection == NULL || G_IS_DBUS_CONNECTION (connection)); + g_return_if_fail (app_filter != NULL); + g_return_if_fail (app_filter->ref_count >= 1); + g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable)); + + task = g_task_new (NULL, cancellable, callback, user_data); + g_task_set_source_tag (task, epc_set_app_filter_async); + + data = g_new0 (SetAppFilterData, 1); + data->user_id = user_id; + data->app_filter = epc_app_filter_ref (app_filter); + data->allow_interactive_authorization = allow_interactive_authorization; + g_task_set_task_data (task, g_steal_pointer (&data), + (GDestroyNotify) set_app_filter_data_free); + + if (connection == NULL) + g_bus_get (G_BUS_TYPE_SYSTEM, cancellable, + set_app_filter_get_bus_cb, g_steal_pointer (&task)); + else + set_app_filter (connection, g_steal_pointer (&task)); +} + +static void +set_app_filter_get_bus_cb (GObject *obj, + GAsyncResult *result, + gpointer user_data) +{ + g_autoptr(GTask) task = G_TASK (user_data); + g_autoptr(GDBusConnection) connection = NULL; + g_autoptr(GError) local_error = NULL; + + connection = g_bus_get_finish (result, &local_error); + + if (local_error != NULL) + g_task_return_error (task, g_steal_pointer (&local_error)); + else + set_app_filter (connection, g_steal_pointer (&task)); +} + +static void +set_app_filter (GDBusConnection *connection, + GTask *task) +{ + g_autofree gchar *object_path = NULL; + g_autoptr(GVariant) app_filter_variant = NULL; + g_autoptr(GVariant) oars_filter_variant = NULL; + GCancellable *cancellable; + + SetAppFilterData *data = g_task_get_task_data (task); + cancellable = g_task_get_cancellable (task); + object_path = g_strdup_printf ("/org/freedesktop/Accounts/User%u", + data->user_id); + + app_filter_variant = _epc_app_filter_build_app_filter_variant (data->app_filter); + oars_filter_variant = g_variant_new ("(s@a{ss})", "oars-1.1", + data->app_filter->oars_ratings); + + g_dbus_connection_call (connection, + "org.freedesktop.Accounts", + object_path, + "org.freedesktop.DBus.Properties", + "Set", + g_variant_new ("(ssv)", + "com.endlessm.ParentalControls.AppFilter", + "app-filter", + g_steal_pointer (&app_filter_variant)), + G_VARIANT_TYPE ("()"), + data->allow_interactive_authorization + ? G_DBUS_CALL_FLAGS_ALLOW_INTERACTIVE_AUTHORIZATION + : G_DBUS_CALL_FLAGS_NONE, + -1, /* timeout, ms */ + cancellable, + set_app_filter_cb, + g_object_ref (task)); + g_dbus_connection_call (connection, + "org.freedesktop.Accounts", + object_path, + "org.freedesktop.DBus.Properties", + "Set", + g_variant_new ("(ssv)", + "com.endlessm.ParentalControls.AppFilter", + "oars-filter", + g_steal_pointer (&oars_filter_variant)), + G_VARIANT_TYPE ("()"), + data->allow_interactive_authorization + ? G_DBUS_CALL_FLAGS_ALLOW_INTERACTIVE_AUTHORIZATION + : G_DBUS_CALL_FLAGS_NONE, + -1, /* timeout, ms */ + cancellable, + set_oars_filter_cb, + g_object_ref (task)); +} + +static void +set_app_filter_cb (GObject *obj, + GAsyncResult *result, + gpointer user_data) +{ + GDBusConnection *connection = G_DBUS_CONNECTION (obj); + g_autoptr(GTask) task = G_TASK (user_data); + + SetAppFilterData *data = g_task_get_task_data (task); + g_assert (data->set_app_filter_result == NULL); + data->set_app_filter_result = g_object_ref (result); + set_app_filter_complete (connection, task); +} + +static void +set_oars_filter_cb (GObject *obj, + GAsyncResult *result, + gpointer user_data) +{ + GDBusConnection *connection = G_DBUS_CONNECTION (obj); + g_autoptr(GTask) task = G_TASK (user_data); + + SetAppFilterData *data = g_task_get_task_data (task); + g_assert (data->set_oars_filter_result == NULL); + data->set_oars_filter_result = g_object_ref (result); + set_app_filter_complete (connection, task); +} + +static void +set_app_filter_complete (GDBusConnection *connection, + GTask *task) +{ + g_autoptr(GVariant) app_filter_result_variant = NULL; + g_autoptr(GVariant) oars_filter_result_variant = NULL; + g_autoptr(GError) app_filter_error = NULL; + g_autoptr(GError) oars_filter_error = NULL; + + SetAppFilterData *data = g_task_get_task_data (task); + + if (data->set_app_filter_result == NULL || + data->set_oars_filter_result == NULL) + return; + + app_filter_result_variant = g_dbus_connection_call_finish (connection, + data->set_app_filter_result, + &app_filter_error); + oars_filter_result_variant = g_dbus_connection_call_finish (connection, + data->set_oars_filter_result, + &oars_filter_error); + + if (app_filter_error != NULL) + { + g_task_return_error (task, bus_error_to_app_filter_error (app_filter_error, + data->user_id)); + return; + } + if (oars_filter_error != NULL) + { + g_task_return_error (task, bus_error_to_app_filter_error (oars_filter_error, + data->user_id)); + return; + } + + g_task_return_boolean (task, TRUE); +} + +/** + * epc_set_app_filter_finish: + * @result: a #GAsyncResult + * @error: return location for a #GError, or %NULL + * + * Finish an asynchronous operation to set the app filter for a user, started + * with epc_set_app_filter_async(). + * + * Returns: %TRUE on success, %FALSE otherwise + * Since: 0.1.0 + */ +gboolean +epc_set_app_filter_finish (GAsyncResult *result, + GError **error) +{ + g_return_val_if_fail (g_task_is_valid (result, NULL), FALSE); + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + + return g_task_propagate_boolean (G_TASK (result), error); +} + +/* + * Actual implementation of #EpcAppFilterBuilder. + * + * All members are %NULL if un-initialised, cleared, or ended. + */ +typedef struct +{ + GPtrArray *paths_blacklist; /* (nullable) (owned) (element-type filename) */ + GHashTable *oars; /* (nullable) (owned) (element-type utf8 EpcAppFilterOarsValue) */ + + /*< private >*/ + gpointer padding[2]; +} EpcAppFilterBuilderReal; + +G_STATIC_ASSERT (sizeof (EpcAppFilterBuilderReal) == + sizeof (EpcAppFilterBuilder)); +G_STATIC_ASSERT (__alignof__ (EpcAppFilterBuilderReal) == + __alignof__ (EpcAppFilterBuilder)); + +G_DEFINE_BOXED_TYPE (EpcAppFilterBuilder, epc_app_filter_builder, + epc_app_filter_builder_copy, epc_app_filter_builder_free) + +/** + * epc_app_filter_builder_init: + * @builder: an uninitialised #EpcAppFilterBuilder + * + * Initialise the given @builder so it can be used to construct a new + * #EpcAppFilter. @builder must have been allocated on the stack, and must not + * already be initialised. + * + * Construct the #EpcAppFilter by calling methods on @builder, followed by + * epc_app_filter_builder_end(). To abort construction, use + * epc_app_filter_builder_clear(). + * + * Since: 0.1.0 + */ +void +epc_app_filter_builder_init (EpcAppFilterBuilder *builder) +{ + EpcAppFilterBuilder local_builder = EPC_APP_FILTER_BUILDER_INIT (); + EpcAppFilterBuilderReal *_builder = (EpcAppFilterBuilderReal *) builder; + + g_return_if_fail (_builder != NULL); + g_return_if_fail (_builder->paths_blacklist == NULL); + g_return_if_fail (_builder->oars == NULL); + + memcpy (builder, &local_builder, sizeof (local_builder)); +} + +/** + * epc_app_filter_builder_clear: + * @builder: an #EpcAppFilterBuilder + * + * Clear @builder, freeing any internal state in it. This will not free the + * top-level storage for @builder itself, which is assumed to be allocated on + * the stack. + * + * If called on an already-cleared #EpcAppFilterBuilder, this function is + * idempotent. + * + * Since: 0.1.0 + */ +void +epc_app_filter_builder_clear (EpcAppFilterBuilder *builder) +{ + EpcAppFilterBuilderReal *_builder = (EpcAppFilterBuilderReal *) builder; + + g_return_if_fail (_builder != NULL); + + g_clear_pointer (&_builder->paths_blacklist, g_ptr_array_unref); + g_clear_pointer (&_builder->oars, g_hash_table_unref); +} + +/** + * epc_app_filter_builder_new: + * + * Construct a new #EpcAppFilterBuilder on the heap. This is intended for + * language bindings. The returned builder must eventually be freed with + * epc_app_filter_builder_free(), but can be cleared zero or more times with + * epc_app_filter_builder_clear() first. + * + * Returns: (transfer full): a new heap-allocated #EpcAppFilterBuilder + * Since: 0.1.0 + */ +EpcAppFilterBuilder * +epc_app_filter_builder_new (void) +{ + g_autoptr(EpcAppFilterBuilder) builder = NULL; + + builder = g_new0 (EpcAppFilterBuilder, 1); + epc_app_filter_builder_init (builder); + + return g_steal_pointer (&builder); +} + +/** + * epc_app_filter_builder_copy: + * @builder: an #EpcAppFilterBuilder + * + * Copy the given @builder to a newly-allocated #EpcAppFilterBuilder on the + * heap. This is safe to use with cleared, stack-allocated + * #EpcAppFilterBuilders. + * + * Returns: (transfer full): a copy of @builder + * Since: 0.1.0 + */ +EpcAppFilterBuilder * +epc_app_filter_builder_copy (EpcAppFilterBuilder *builder) +{ + EpcAppFilterBuilderReal *_builder = (EpcAppFilterBuilderReal *) builder; + g_autoptr(EpcAppFilterBuilder) copy = NULL; + EpcAppFilterBuilderReal *_copy; + + g_return_val_if_fail (builder != NULL, NULL); + + copy = epc_app_filter_builder_new (); + _copy = (EpcAppFilterBuilderReal *) copy; + + epc_app_filter_builder_clear (copy); + if (_builder->paths_blacklist != NULL) + _copy->paths_blacklist = g_ptr_array_ref (_builder->paths_blacklist); + if (_builder->oars != NULL) + _copy->oars = g_hash_table_ref (_builder->oars); + + return g_steal_pointer (©); +} + +/** + * epc_app_filter_builder_free: + * @builder: a heap-allocated #EpcAppFilterBuilder + * + * Free an #EpcAppFilterBuilder originally allocated using + * epc_app_filter_builder_new(). This must not be called on stack-allocated + * builders initialised using epc_app_filter_builder_init(). + * + * Since: 0.1.0 + */ +void +epc_app_filter_builder_free (EpcAppFilterBuilder *builder) +{ + g_return_if_fail (builder != NULL); + + epc_app_filter_builder_clear (builder); + g_free (builder); +} + +/** + * epc_app_filter_builder_end: + * @builder: an initialised #EpcAppFilterBuilder + * + * Finish constructing an #EpcAppFilter with the given @builder, and return it. + * The #EpcAppFilterBuilder will be cleared as if epc_app_filter_builder_clear() + * had been called. + * + * Returns: (transfer full): a newly constructed #EpcAppFilter + * Since: 0.1.0 + */ +EpcAppFilter * +epc_app_filter_builder_end (EpcAppFilterBuilder *builder) +{ + EpcAppFilterBuilderReal *_builder = (EpcAppFilterBuilderReal *) builder; + g_autoptr(EpcAppFilter) app_filter = NULL; + g_auto(GVariantBuilder) oars_builder = G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE ("a{ss}")); + GHashTableIter iter; + gpointer key, value; + g_autoptr(GVariant) oars_variant = NULL; + + g_return_val_if_fail (_builder != NULL, NULL); + g_return_val_if_fail (_builder->paths_blacklist != NULL, NULL); + g_return_val_if_fail (_builder->oars != NULL, NULL); + + /* Ensure the paths list is %NULL-terminated. */ + g_ptr_array_add (_builder->paths_blacklist, NULL); + + /* Build the OARS variant. */ + g_hash_table_iter_init (&iter, _builder->oars); + while (g_hash_table_iter_next (&iter, &key, &value)) + { + const gchar *oars_section = key; + EpcAppFilterOarsValue oars_value = GPOINTER_TO_INT (value); + const gchar *oars_value_strs[] = + { + NULL, /* EPC_APP_FILTER_OARS_VALUE_UNKNOWN */ + "none", + "mild", + "moderate", + "intense", + }; + + g_assert ((int) oars_value >= 0 && + (int) oars_value < (int) G_N_ELEMENTS (oars_value_strs)); + + if (oars_value_strs[oars_value] != NULL) + g_variant_builder_add (&oars_builder, "{ss}", + oars_section, oars_value_strs[oars_value]); + } + + oars_variant = g_variant_ref_sink (g_variant_builder_end (&oars_builder)); + + /* Build the #EpcAppFilter. */ + app_filter = g_new0 (EpcAppFilter, 1); + app_filter->ref_count = 1; + app_filter->user_id = -1; + app_filter->app_list = (gchar **) g_ptr_array_free (g_steal_pointer (&_builder->paths_blacklist), FALSE); + app_filter->app_list_type = EPC_APP_FILTER_LIST_BLACKLIST; + app_filter->oars_ratings = g_steal_pointer (&oars_variant); + + epc_app_filter_builder_clear (builder); + + return g_steal_pointer (&app_filter); +} + +/** + * epc_app_filter_builder_blacklist_path: + * @builder: an initialised #EpcAppFilterBuilder + * @path: (type filename): an absolute path to blacklist + * + * Add @path to the blacklist of app paths in the filter under construction. It + * will be canonicalised (without doing any I/O) before being added. + * The canonicalised @path will not be added again if it’s already been added. + * + * Since: 0.1.0 + */ +void +epc_app_filter_builder_blacklist_path (EpcAppFilterBuilder *builder, + const gchar *path) +{ + EpcAppFilterBuilderReal *_builder = (EpcAppFilterBuilderReal *) builder; + + g_return_if_fail (_builder != NULL); + g_return_if_fail (_builder->paths_blacklist != NULL); + g_return_if_fail (path != NULL); + g_return_if_fail (g_path_is_absolute (path)); + + g_autofree gchar *canonical_path = g_canonicalize_filename (path, "/"); + + if (!g_ptr_array_find_with_equal_func (_builder->paths_blacklist, + canonical_path, g_str_equal, NULL)) + g_ptr_array_add (_builder->paths_blacklist, g_steal_pointer (&canonical_path)); +} + +/** + * epc_app_filter_builder_set_oars_value: + * @builder: an initialised #EpcAppFilterBuilder + * @oars_section: name of the OARS section to set the value for + * @value: value to set for the @oars_section + * + * Set the OARS value for the given @oars_section, indicating the intensity of + * content covered by that section which the user is allowed to see (inclusive). + * Any apps which have more intense content in this section should not be usable + * by the user. + * + * Since: 0.1.0 + */ +void +epc_app_filter_builder_set_oars_value (EpcAppFilterBuilder *builder, + const gchar *oars_section, + EpcAppFilterOarsValue value) +{ + EpcAppFilterBuilderReal *_builder = (EpcAppFilterBuilderReal *) builder; + + g_return_if_fail (_builder != NULL); + g_return_if_fail (_builder->oars != NULL); + g_return_if_fail (oars_section != NULL && *oars_section != '\0'); + + g_hash_table_insert (_builder->oars, g_strdup (oars_section), + GUINT_TO_POINTER (value)); +} diff --git a/libeos-parental-controls/app-filter.h b/libeos-parental-controls/app-filter.h index 71a7e4b..458827b 100644 --- a/libeos-parental-controls/app-filter.h +++ b/libeos-parental-controls/app-filter.h @@ -113,4 +113,74 @@ void epc_get_app_filter_async (GDBusConnection *connection, EpcAppFilter *epc_get_app_filter_finish (GAsyncResult *result, GError **error); +void epc_set_app_filter_async (GDBusConnection *connection, + uid_t user_id, + EpcAppFilter *app_filter, + gboolean allow_interactive_authorization, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +gboolean epc_set_app_filter_finish (GAsyncResult *result, + GError **error); + +/** + * EpcAppFilterBuilder: + * + * #EpcAppFilterBuilder is a stack-allocated mutable structure used to build an + * #EpcAppFilter instance. Use epc_app_filter_builder_init(), various method + * calls to set properties of the app filter, and then + * epc_app_filter_builder_end(), to construct an #EpcAppFilter. + * + * Since: 0.1.0 + */ +typedef struct +{ + /*< private >*/ + gpointer p0; + gpointer p1; + gpointer p2; + gpointer p3; +} EpcAppFilterBuilder; + +GType epc_app_filter_builder_get_type (void); + +/** + * EPC_APP_FILTER_BUILDER_INIT: + * + * Initialise a stack-allocated #EpcAppFilterBuilder instance at declaration + * time. + * + * This is typically used with g_auto(): + * |[ + * g_auto(EpcAppFilterBuilder) builder = EPC_APP_FILTER_BUILDER_INIT (); + * ]| + * + * Since: 0.1.0 + */ +#define EPC_APP_FILTER_BUILDER_INIT() \ + { \ + g_ptr_array_new_with_free_func (g_free), \ + g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL) \ + } + +void epc_app_filter_builder_init (EpcAppFilterBuilder *builder); +void epc_app_filter_builder_clear (EpcAppFilterBuilder *builder); + +G_DEFINE_AUTO_CLEANUP_CLEAR_FUNC (EpcAppFilterBuilder, + epc_app_filter_builder_clear) + +EpcAppFilterBuilder *epc_app_filter_builder_new (void); +EpcAppFilterBuilder *epc_app_filter_builder_copy (EpcAppFilterBuilder *builder); +void epc_app_filter_builder_free (EpcAppFilterBuilder *builder); + +G_DEFINE_AUTOPTR_CLEANUP_FUNC (EpcAppFilterBuilder, epc_app_filter_builder_free) + +EpcAppFilter *epc_app_filter_builder_end (EpcAppFilterBuilder *builder); + +void epc_app_filter_builder_blacklist_path (EpcAppFilterBuilder *builder, + const gchar *path); +void epc_app_filter_builder_set_oars_value (EpcAppFilterBuilder *builder, + const gchar *oars_section, + EpcAppFilterOarsValue value); + G_END_DECLS diff --git a/libeos-parental-controls/tests/app-filter.c b/libeos-parental-controls/tests/app-filter.c index e0b1d28..bd72dad 100644 --- a/libeos-parental-controls/tests/app-filter.c +++ b/libeos-parental-controls/tests/app-filter.c @@ -26,6 +26,7 @@ #include #include #include +#include /* A placeholder smoketest which checks that the error quark works. */ @@ -35,6 +36,149 @@ test_app_filter_error_quark (void) g_assert_cmpint (epc_app_filter_error_quark (), !=, 0); } +/* Fixture for tests which use an #EpcAppFilterBuilder. The builder can either + * be heap- or stack-allocated. @builder will always be a valid pointer to it. + */ +typedef struct +{ + EpcAppFilterBuilder *builder; + EpcAppFilterBuilder stack_builder; +} BuilderFixture; + +static void +builder_set_up_stack (BuilderFixture *fixture, + gconstpointer test_data) +{ + epc_app_filter_builder_init (&fixture->stack_builder); + fixture->builder = &fixture->stack_builder; +} + +static void +builder_tear_down_stack (BuilderFixture *fixture, + gconstpointer test_data) +{ + epc_app_filter_builder_clear (&fixture->stack_builder); + fixture->builder = NULL; +} + +static void +builder_set_up_stack2 (BuilderFixture *fixture, + gconstpointer test_data) +{ + EpcAppFilterBuilder local_builder = EPC_APP_FILTER_BUILDER_INIT (); + memcpy (&fixture->stack_builder, &local_builder, sizeof (local_builder)); + fixture->builder = &fixture->stack_builder; +} + +static void +builder_tear_down_stack2 (BuilderFixture *fixture, + gconstpointer test_data) +{ + epc_app_filter_builder_clear (&fixture->stack_builder); + fixture->builder = NULL; +} + +static void +builder_set_up_heap (BuilderFixture *fixture, + gconstpointer test_data) +{ + fixture->builder = epc_app_filter_builder_new (); +} + +static void +builder_tear_down_heap (BuilderFixture *fixture, + gconstpointer test_data) +{ + g_clear_pointer (&fixture->builder, epc_app_filter_builder_free); +} + +/* Test building a non-empty #EpcAppFilter using an #EpcAppFilterBuilder. */ +static void +test_app_filter_builder_non_empty (BuilderFixture *fixture, + gconstpointer test_data) +{ + g_autoptr(EpcAppFilter) filter = NULL; + + epc_app_filter_builder_blacklist_path (fixture->builder, "/bin/true"); + epc_app_filter_builder_blacklist_path (fixture->builder, "/usr/bin/gnome-software"); + + epc_app_filter_builder_set_oars_value (fixture->builder, "drugs-alcohol", + EPC_APP_FILTER_OARS_VALUE_MILD); + epc_app_filter_builder_set_oars_value (fixture->builder, "language-humor", + EPC_APP_FILTER_OARS_VALUE_MODERATE); + + filter = epc_app_filter_builder_end (fixture->builder); + + g_assert_true (epc_app_filter_is_path_allowed (filter, "/bin/false")); + g_assert_false (epc_app_filter_is_path_allowed (filter, + "/usr/bin/gnome-software")); + + g_assert_cmpint (epc_app_filter_get_oars_value (filter, "drugs-alcohol"), ==, + EPC_APP_FILTER_OARS_VALUE_MILD); + g_assert_cmpint (epc_app_filter_get_oars_value (filter, "language-humor"), ==, + EPC_APP_FILTER_OARS_VALUE_MODERATE); + g_assert_cmpint (epc_app_filter_get_oars_value (filter, "something-else"), ==, + EPC_APP_FILTER_OARS_VALUE_UNKNOWN); +} + +/* Test building an empty #EpcAppFilter using an #EpcAppFilterBuilder. */ +static void +test_app_filter_builder_empty (BuilderFixture *fixture, + gconstpointer test_data) +{ + g_autoptr(EpcAppFilter) filter = NULL; + + filter = epc_app_filter_builder_end (fixture->builder); + + g_assert_true (epc_app_filter_is_path_allowed (filter, "/bin/false")); + g_assert_true (epc_app_filter_is_path_allowed (filter, + "/usr/bin/gnome-software")); + + g_assert_cmpint (epc_app_filter_get_oars_value (filter, "drugs-alcohol"), ==, + EPC_APP_FILTER_OARS_VALUE_UNKNOWN); + g_assert_cmpint (epc_app_filter_get_oars_value (filter, "language-humor"), ==, + EPC_APP_FILTER_OARS_VALUE_UNKNOWN); + g_assert_cmpint (epc_app_filter_get_oars_value (filter, "something-else"), ==, + EPC_APP_FILTER_OARS_VALUE_UNKNOWN); +} + +/* Check that copying a cleared #EpcAppFilterBuilder works, and the copy can + * then be initialised and used to build a filter. */ +static void +test_app_filter_builder_copy_empty (void) +{ + g_autoptr(EpcAppFilterBuilder) builder = epc_app_filter_builder_new (); + g_autoptr(EpcAppFilterBuilder) builder_copy = NULL; + g_autoptr(EpcAppFilter) filter = NULL; + + epc_app_filter_builder_clear (builder); + builder_copy = epc_app_filter_builder_copy (builder); + + epc_app_filter_builder_init (builder_copy); + epc_app_filter_builder_blacklist_path (builder_copy, "/bin/true"); + filter = epc_app_filter_builder_end (builder_copy); + + g_assert_true (epc_app_filter_is_path_allowed (filter, "/bin/false")); + g_assert_false (epc_app_filter_is_path_allowed (filter, "/bin/true")); +} + +/* Check that copying a filled #EpcAppFilterBuilder works, and the copy can be + * used to build a filter. */ +static void +test_app_filter_builder_copy_full (void) +{ + g_autoptr(EpcAppFilterBuilder) builder = epc_app_filter_builder_new (); + g_autoptr(EpcAppFilterBuilder) builder_copy = NULL; + g_autoptr(EpcAppFilter) filter = NULL; + + epc_app_filter_builder_blacklist_path (builder, "/bin/true"); + builder_copy = epc_app_filter_builder_copy (builder); + filter = epc_app_filter_builder_end (builder_copy); + + g_assert_true (epc_app_filter_is_path_allowed (filter, "/bin/false")); + g_assert_false (epc_app_filter_is_path_allowed (filter, "/bin/true")); +} + int main (int argc, char **argv) @@ -44,5 +188,28 @@ main (int argc, g_test_add_func ("/app-filter/error-quark", test_app_filter_error_quark); + g_test_add ("/app-filter/builder/stack/non-empty", BuilderFixture, NULL, + builder_set_up_stack, test_app_filter_builder_non_empty, + builder_tear_down_stack); + g_test_add ("/app-filter/builder/stack/empty", BuilderFixture, NULL, + builder_set_up_stack, test_app_filter_builder_empty, + builder_tear_down_stack); + g_test_add ("/app-filter/builder/stack2/non-empty", BuilderFixture, NULL, + builder_set_up_stack2, test_app_filter_builder_non_empty, + builder_tear_down_stack2); + g_test_add ("/app-filter/builder/stack2/empty", BuilderFixture, NULL, + builder_set_up_stack2, test_app_filter_builder_empty, + builder_tear_down_stack2); + g_test_add ("/app-filter/builder/heap/non-empty", BuilderFixture, NULL, + builder_set_up_heap, test_app_filter_builder_non_empty, + builder_tear_down_heap); + g_test_add ("/app-filter/builder/heap/empty", BuilderFixture, NULL, + builder_set_up_heap, test_app_filter_builder_empty, + builder_tear_down_heap); + g_test_add_func ("/app-filter/builder/copy/empty", + test_app_filter_builder_copy_empty); + g_test_add_func ("/app-filter/builder/copy/full", + test_app_filter_builder_copy_full); + return g_test_run (); }