diff options
author | Richard Hughes <richard@hughsie.com> | 2018-03-14 13:29:25 +0000 |
---|---|---|
committer | Richard Hughes <richard@hughsie.com> | 2018-04-11 20:50:12 +0100 |
commit | 0bb8ae6f2dec2b3ff6bf6f908955969e1b460d8a (patch) | |
tree | d7870f815386c4b3e7a5664e0bbbec19fea61950 | |
parent | 786a2c85c06ba82d41db9eb5490c964d3bd7754d (diff) | |
download | appstream-glib-0bb8ae6f2dec2b3ff6bf6f908955969e1b460d8a.tar.gz |
Add support for component agreements
This enables a lot of software to comply with the GDPR and also allows us to
show translated warning and EULA text to unsuspecting users.
-rw-r--r-- | client/as-util.c | 58 | ||||
-rw-r--r-- | data/tests/example-eula.metainfo.xml | 18 | ||||
-rw-r--r-- | data/tests/example-gdpr.metainfo.xml | 396 | ||||
-rw-r--r-- | data/tests/example-remote.metainfo.xml | 28 | ||||
-rw-r--r-- | libappstream-glib/as-agreement-private.h | 46 | ||||
-rw-r--r-- | libappstream-glib/as-agreement-section-private.h | 46 | ||||
-rw-r--r-- | libappstream-glib/as-agreement-section.c | 276 | ||||
-rw-r--r-- | libappstream-glib/as-agreement-section.h | 68 | ||||
-rw-r--r-- | libappstream-glib/as-agreement.c | 330 | ||||
-rw-r--r-- | libappstream-glib/as-agreement.h | 88 | ||||
-rw-r--r-- | libappstream-glib/as-app-private.h | 2 | ||||
-rw-r--r-- | libappstream-glib/as-app.c | 128 | ||||
-rw-r--r-- | libappstream-glib/as-app.h | 11 | ||||
-rw-r--r-- | libappstream-glib/as-self-test.c | 53 | ||||
-rw-r--r-- | libappstream-glib/as-tag.c | 2 | ||||
-rw-r--r-- | libappstream-glib/as-tag.gperf | 2 | ||||
-rw-r--r-- | libappstream-glib/as-tag.h | 4 | ||||
-rw-r--r-- | libappstream-glib/meson.build | 8 |
18 files changed, 1562 insertions, 2 deletions
diff --git a/client/as-util.c b/client/as-util.c index 76a3473..10b594d 100644 --- a/client/as-util.c +++ b/client/as-util.c @@ -3611,6 +3611,58 @@ as_util_mirror_local_firmware (AsUtilPrivate *priv, gchar **values, GError **err } static gboolean +as_util_agreement_export (AsUtilPrivate *priv, gchar **values, GError **error) +{ + AsAgreement *pp; + GPtrArray *sections; + g_autoptr(AsApp) app = NULL; + g_autoptr(GFile) file = NULL; + + /* check args */ + if (g_strv_length (values) < 2) { + g_set_error_literal (error, + AS_ERROR, + AS_ERROR_INVALID_ARGUMENTS, + "Not enough arguments, expected: file type, " + "e.g. foo.metainfo.xml eula"); + return FALSE; + } + + /* parse file */ + app = as_app_new (); + if (!as_app_parse_file (app, values[0], AS_APP_PARSE_FLAG_NONE, error)) + return FALSE; + + /* get all policy sections */ + pp = as_app_get_agreement_by_kind (app, as_agreement_kind_from_string (values[1])); + if (pp == NULL) { + g_set_error (error, + AS_ERROR, + AS_ERROR_INVALID_ARGUMENTS, + "no privacy policy with type %s", + values[1]); + return FALSE; + } + sections = as_agreement_get_sections (pp); + for (guint i = 0; i < sections->len; i++) { + AsAgreementSection *ps = g_ptr_array_index (sections, i); + const gchar *tmp; + g_autofree gchar *plain = NULL; + + g_print ("%s\n^^^\n", as_agreement_section_get_name (ps, NULL)); + tmp = as_agreement_section_get_description (ps, NULL); + if (tmp == NULL) + continue; + plain = as_markup_convert_simple (tmp, error); + if (plain == NULL) + return FALSE; + g_print ("%s\n\n", plain); + } + + return TRUE; +} + +static gboolean as_util_replace_screenshots (AsUtilPrivate *priv, gchar **values, GError **error) { GPtrArray *screenshots; @@ -4483,6 +4535,12 @@ main (int argc, char *argv[]) _("Validate an AppData or AppStream file (relaxed)"), as_util_validate_relax); as_util_add (priv->cmd_array, + "agreement-export", + NULL, + /* TRANSLATORS: command description */ + _("Exports the agreement to text"), + as_util_agreement_export); + as_util_add (priv->cmd_array, "validate-strict", NULL, /* TRANSLATORS: command description */ diff --git a/data/tests/example-eula.metainfo.xml b/data/tests/example-eula.metainfo.xml new file mode 100644 index 0000000..516b79e --- /dev/null +++ b/data/tests/example-eula.metainfo.xml @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- Copyright 2018 Richard Hughes <richard@hughsie.com> --> + +<component type="desktop"> + <id>org.gnome.Software</id> + <name>GNOME Software</name> + ...usual contents for a desktop app... + <metadata_license>CC0</metadata_license> + <agreement type="eula" version_id="1.0"> + <agreement_section> + <description> + <p> + If it breaks, you get to keep both pieces. + </p> + </description> + </agreement_section> + </agreement> +</component> diff --git a/data/tests/example-gdpr.metainfo.xml b/data/tests/example-gdpr.metainfo.xml new file mode 100644 index 0000000..2bac8fc --- /dev/null +++ b/data/tests/example-gdpr.metainfo.xml @@ -0,0 +1,396 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- Copyright 2018 Richard Hughes <richard@hughsie.com> --> + +<component type="general"> + <id>org.fwupd.lvfs</id> + <name>Linux Vendor Firmware Service</name> + <metadata_license>CC0</metadata_license> + + <agreement type="privacy" version_id="1.0"> + + <agreement_section type="introduction"> + <name>Introduction</name> + <description> + <p> + We hold personal data about vendors, administrators, clients and other + individuals for a variety of purposes. + This policy sets out how we seek to protect personal data and ensure that + administrators understand the rules governing their use of personal data to + which they have access in the course of their work. + In particular, this policy requires that the Data Protection Officer (DPO) be + consulted before any significant new data processing activity is initiated to + ensure that relevant compliance steps are addressed. + </p> + </description> + </agreement_section> + + <agreement_section type="scope"> + <name>Scope</name> + <description> + <p> + This policy applies to all users who have access to any of the personally + identifiable data. + </p> + </description> + </agreement_section> + + <agreement_section type="dpo"> + <name>Who is responsible for this policy?</name> + <description> + <p> + As the Data Protection Officer, Richard Hughes + has overall responsibility for the day-to-day implementation of this policy. + The DPO is registered with the Information Commissioner’s Office (ICO) in the + United Kingdom as a registered data controller. + </p> + </description> + </agreement_section> + + <agreement_section type="processing"> + <name>Fair and lawful processing</name> + <description> + <p> + We must process personal data fairly and lawfully in accordance with individuals’ rights. + This generally means that we should not process personal data unless the + individual whose details we are processing has consented to this happening, + or where such collection is unavoidable and/or considered pragmatic in the + context, e.g. logging the number of downloads of a particular file. + </p> + <p> + We do not consider an IP address to represent a single user (due to NAT or VPN use), + and as such metadata requests are not considered personal data using the draft GDPR guidelines. + </p> + </description> + </agreement_section> + + <agreement_section type="accuracy"> + <name>Accuracy and relevance</name> + <description> + <p> + We will ensure that any personal data we process is accurate, adequate, + relevant and not excessive, given the purpose for which it was obtained. + We will not process personal data obtained for one purpose for any unconnected + purpose unless the individual concerned has agreed to this or would otherwise + reasonably expect this. + Individuals may ask that we correct inaccurate personal data relating to them. + If you believe that information is inaccurate you should inform the DPO. + </p> + </description> + </agreement_section> + + <agreement_section type="personal"> + <name>Your personal data</name> + <description> + <p> + You must take reasonable steps to ensure that personal data we hold about + hardware vendors is accurate and updated as required. + For example, if your personal circumstances change, please update them using + the profile pages or inform the Data Protection Officer. + </p> + </description> + </agreement_section> + + <agreement_section type="security"> + <name>Data security</name> + <description> + <p> + We keep personal data secure against loss or misuse. + Where other organisations process personal data as a service on our behalf, + the DPO will establish what, if any, additional specific data security + arrangements need to be implemented in contracts with those third party + organisations. + </p> + </description> + </agreement_section> + + <agreement_section type="storage"> + <name>Storing data securely</name> + <description> + <p> + All data is stored electronically. + All documents and code are held on a locked LUKS partition with a password + adhering to security best practices. + </p> + </description> + </agreement_section> + + <agreement_section type="retention"> + <name>Data retention</name> + <description> + <p> + We must retain personal data for no longer than is necessary. + What is necessary will depend on the circumstances of each case, taking into + account the reasons that the personal data was obtained, but should be + determined in a manner consistent with our data retention guidelines. + Anonymized user data (e.g. metadata requests) will be kept for a maximum of + 5 years which allows us to project future service requirements and provide + usage graphs to the vendor. + </p> + </description> + </agreement_section> + + <agreement_section type="transfer"> + <name>Transferring data internationally</name> + <description> + <p> + There are restrictions on international transfers of personal data. + We do not transfer personal data anywhere outside the EU without the approval + of the Data Protection Officer, unless required to do so by law. + </p> + </description> + </agreement_section> + + <agreement_section type="access-requests"> + <name>Subject Access Requests</name> + <description> + <p> + Please note that under the Data Protection Act 1998, individuals are entitled, + subject to certain exceptions, to request access to information held about them. + </p> + <p> + On receiving a subject access request, we will refer that request immediately + to the DPO. We may ask you to help us comply with those requests. + Please also contact the Data Protection Officer if you would like to correct + or request information that we hold about you. + There are also restrictions on the information to which you are entitled under + applicable law. + </p> + </description> + </agreement_section> + + <agreement_section type="processing"> + <name>Processing data in accordance with your rights</name> + <description> + <p> + We will never use identifiable vendor data for direct marketing purposes. + </p> + </description> + </agreement_section> + + <agreement_section type="transparency"> + <name>Transparency of data protection</name> + <description> + <p> + Being transparent and providing accessible information to individuals about how + we will use their personal data is important for our project. + </p> + <p> + We will ensure any use of personal data is justified using at least one of + the conditions for processing and this had been specifically documented. + </p> + </description> + </agreement_section> + + <agreement_section type="consent"> + <name>Consent</name> + <description> + <p> + The data that we collect is subject to active consent by the data subject. + This consent can be revoked at any time, but would end any vendor + relationship with the LVFS. + </p> + </description> + </agreement_section> + + <agreement_section type="portability"> + <name>Data portability</name> + <description> + <p> + Upon request, a data subject should have the right to receive a copy of their + data in a structured format, typically an SQL export. + These requests should be processed within one month, provided there is no + undue burden and it does not compromise the privacy of other individuals. + A data subject may also request that their data is transferred directly to + another system. This is available for free. + </p> + </description> + </agreement_section> + + <agreement_section type="forgotten"> + <name>Right to be forgotten</name> + <description> + <p> + A vendor may request that any information held on them is deleted or removed, + and any third parties who process or use that data must also comply with the request. + An erasure request can only be refused if an exemption applies. + </p> + </description> + </agreement_section> + + <agreement_section type="design"> + <name>Privacy by design and default</name> + <description> + <p> + Privacy by design is an approach to projects that promote privacy and data + protection compliance from the start. + The DPO will be responsible for conducting Privacy Impact Assessments and + ensuring that all changes commence with a privacy plan. + When relevant, and when it does not have a negative impact on the data subject, + privacy settings will be set to the most private by default. + </p> + </description> + </agreement_section> + + <agreement_section type="audit"> + <name>Data audit and register</name> + <description> + <p> + Regular data audits to manage and mitigate risks will inform the data register. + This contains information on what data is held, where it is stored, + how it is used, who is responsible and any further regulations or retention + timescales that may be relevant. + </p> + </description> + </agreement_section> + + <agreement_section type="breaches"> + <name>Reporting breaches</name> + <description> + <p> + All users of the LVFS have an obligation to report actual or potential data + protection compliance failures. This allows us to: + </p> + <ul> + <li>Investigate the failure and take remedial steps if necessary</li> + <li>Maintain a register of compliance failures</li> + <li> + Notify the Supervisory Authority (SA) of any compliance failures that are + material either in their own right or as part of a pattern of failures + </li> + </ul> + <p> + Please refer to the DPO for our reporting procedure. + </p> + </description> + </agreement_section> + + <agreement_section type="monitoring"> + <name>Monitoring</name> + <description> + <p> + Everyone who actively uses the LVFS must observe this policy. + The DPO has overall responsibility for this policy. + They will monitor it regularly to make sure it is being adhered to. + </p> + </description> + </agreement_section> + + <agreement_section type="consequences"> + <name>Consequences of Failing to Comply</name> + <description> + <p> + We take compliance with this policy very seriously. + Failure to comply puts both you and us at risk. + The importance of this policy means that failure to comply with any requirement + may lead to disciplinary action under our procedures. + If you have any questions or concerns about anything in this policy, + do not hesitate to contact the DPO. + </p> + </description> + </agreement_section> + + <agreement_section type="detail-misc"> + <name>Details: Firmware Vendor Information</name> + <description> + <p> + What: The hardware vendor name, password, GPG public key and content of original + uploaded firmware files.. + </p> + <p> + Why colected: Secure authentication, to allow any possible future audit and to provide + authorised users access to signed firmware files. + </p> + <p> + Where stored: MySQL database on fwupd.org. + </p> + <p> + When copied: Backed up to off-site secure LUKS partition weekly. + </p> + <p> + Who has access: The hardware vendor (filtered by the QA group) and the DPO. + </p> + <p> + Wiped: When the vendor requests deletion of the user account. + </p> + </description> + </agreement_section> + + <agreement_section type="detail-misc"> + <name>Details: Service Event Log</name> + <description> + <p> + What: IP address (unhashed) and REST method requested, along with any error. + </p> + <p> + Why colected: Providing an event log for checking what the various hardware vendors are + doing, or trying to do.. + </p> + <p> + Where stored: MySQL database on fwupd.org. + </p> + <p> + When copied: Backed up to off-site secure LUKS partition weekly. + </p> + <p> + Who has access: The hardware vendor (filtered by the QA group) and the DPO. + </p> + <p> + Wiped: When the QA group is deleted. + </p> + </description> + </agreement_section> + + <agreement_section type="detail-misc"> + <name>Details: Firmware Download Log</name> + <description> + <p> + What: IP address (hashed), timestamp, filename of firmware, user-agent of client. + </p> + <p> + Why colected: To know what client versions are being used for download, and to provide + a download count over time for a specific firmware file. + </p> + <p> + Where stored: MySQL database on fwupd.org. + </p> + <p> + When copied: Backed up to off-site secure LUKS partition weekly. + </p> + <p> + Who has access: The hardware vendor (filtered by the QA group) and the DPO. + </p> + <p> + Wiped: When the firmware is deleted. + </p> + </description> + </agreement_section> + + <agreement_section type="detail-misc"> + <name>Details: Firmware Reports</name> + <description> + <p> + What: Machine ID (hashed), failure string and checksum of failing file, + OS distribution name and version. + </p> + <p> + Why colected: Allows the hardware vendor to assess if the firmware update is working on + real hardware. + </p> + <p> + Where stored: MySQL database on fwupd.org. + </p> + <p> + When copied: Backed up to off-site secure LUKS partition weekly. + </p> + <p> + Who has access: The hardware vendor (filtered by the QA group) and the DPO. + </p> + <p> + Wiped: When the firmware is deleted. + </p> + </description> + </agreement_section> + + </agreement> + +</component> diff --git a/data/tests/example-remote.metainfo.xml b/data/tests/example-remote.metainfo.xml new file mode 100644 index 0000000..8da5996 --- /dev/null +++ b/data/tests/example-remote.metainfo.xml @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- Copyright 2018 Richard Hughes <richard@hughsie.com> --> + +<component type="source"> + <id>org.lvfs.stable-remote</id> + <name>Linux Vendor Firmware Service (stable firmware)</name> + <metadata_license>CC0</metadata_license> + + <agreement version_id="1.0"> + <agreement_section> + <name>Warning!</name> + <description> + <p> + The LVFS is a free service that operates as an independent legal + entity and has no connection with your distribution. + Your distributor may not have verified any of the firmware files for + compatibility with your system. + All firmware is provided only by the original equipment manufacturer. + </p> + <p> + Enabling this functionality is done at your own risk, so please do not + file issues with your distributor as they will not be able to help. + </p> + </description> + </agreement_section> + </agreement> + +</component> diff --git a/libappstream-glib/as-agreement-private.h b/libappstream-glib/as-agreement-private.h new file mode 100644 index 0000000..fb6132b --- /dev/null +++ b/libappstream-glib/as-agreement-private.h @@ -0,0 +1,46 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- + * + * Copyright (C) 2018 Richard Hughes <richard@hughsie.com> + * + * Licensed under the GNU Lesser General Public License Version 2.1 + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#if !defined (__APPSTREAM_GLIB_PRIVATE_H) && !defined (AS_COMPILATION) +#error "Only <appstream-glib.h> can be included directly." +#endif + +#ifndef __AS_AGREEMENT_PRIVATE_H +#define __AS_AGREEMENT_PRIVATE_H + +#include <glib-object.h> + +#include "as-agreement.h" +#include "as-node-private.h" + +G_BEGIN_DECLS + +GNode *as_agreement_node_insert (AsAgreement *agreement, + GNode *parent, + AsNodeContext *ctx); +gboolean as_agreement_node_parse (AsAgreement *agreement, + GNode *node, + AsNodeContext *ctx, + GError **error); + +G_END_DECLS + +#endif /* __AS_AGREEMENT_PRIVATE_H */ diff --git a/libappstream-glib/as-agreement-section-private.h b/libappstream-glib/as-agreement-section-private.h new file mode 100644 index 0000000..315972d --- /dev/null +++ b/libappstream-glib/as-agreement-section-private.h @@ -0,0 +1,46 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- + * + * Copyright (C) 2018 Richard Hughes <richard@hughsie.com> + * + * Licensed under the GNU Lesser General Public License Version 2.1 + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#if !defined (__APPSTREAM_GLIB_PRIVATE_H) && !defined (AS_COMPILATION) +#error "Only <appstream-glib.h> can be included directly." +#endif + +#ifndef __AS_AGREEMENT_SECTION_PRIVATE_H +#define __AS_AGREEMENT_SECTION_PRIVATE_H + +#include <glib-object.h> + +#include "as-agreement-section.h" +#include "as-node-private.h" + +G_BEGIN_DECLS + +GNode *as_agreement_section_node_insert (AsAgreementSection *agreement_section, + GNode *parent, + AsNodeContext *ctx); +gboolean as_agreement_section_node_parse (AsAgreementSection *agreement_section, + GNode *node, + AsNodeContext *ctx, + GError **error); + +G_END_DECLS + +#endif /* __AS_AGREEMENT_SECTION_PRIVATE_H */ diff --git a/libappstream-glib/as-agreement-section.c b/libappstream-glib/as-agreement-section.c new file mode 100644 index 0000000..aa29098 --- /dev/null +++ b/libappstream-glib/as-agreement-section.c @@ -0,0 +1,276 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- + * + * Copyright (C) 2018 Richard Hughes <richard@hughsie.com> + * + * Licensed under the GNU Lesser General Public License Version 2.1 + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * SECTION:as-agreement-section + * @short_description: Object representing a agreement section + * @include: appstream-glib.h + * @stability: Unstable + * + * Agreements are typically split up into sections. + * + * See also: #AsAgreement, #AsAgreementDetail + */ + +#include "config.h" + +#include "as-node-private.h" +#include "as-agreement-section-private.h" +#include "as-ref-string.h" +#include "as-tag.h" + +typedef struct { + AsRefString *kind; + AsRefString *name; + AsRefString *desc; +} AsAgreementSectionPrivate; + +G_DEFINE_TYPE_WITH_PRIVATE (AsAgreementSection, as_agreement_section, G_TYPE_OBJECT) + +#define GET_PRIVATE(o) (as_agreement_section_get_instance_private (o)) + +static void +as_agreement_section_finalize (GObject *object) +{ + AsAgreementSection *agreement_section = AS_AGREEMENT_SECTION (object); + AsAgreementSectionPrivate *priv = GET_PRIVATE (agreement_section); + + if (priv->kind != NULL) + as_ref_string_unref (priv->kind); + if (priv->name != NULL) + as_ref_string_unref (priv->name); + if (priv->desc != NULL) + as_ref_string_unref (priv->desc); + + G_OBJECT_CLASS (as_agreement_section_parent_class)->finalize (object); +} + +static void +as_agreement_section_init (AsAgreementSection *agreement_section) +{ +// AsAgreementSectionPrivate *priv = GET_PRIVATE (agreement_section); +} + +static void +as_agreement_section_class_init (AsAgreementSectionClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + object_class->finalize = as_agreement_section_finalize; +} + +/** + * as_agreement_section_get_kind: + * @agreement_section: a #AsAgreementSection instance. + * + * Gets the agreement section kind. + * + * Returns: a string, e.g. "GDPR", or NULL + * + * Since: 0.7.8 + **/ +const gchar * +as_agreement_section_get_kind (AsAgreementSection *agreement_section) +{ + AsAgreementSectionPrivate *priv = GET_PRIVATE (agreement_section); + return priv->kind; +} + +/** + * as_agreement_section_set_kind: + * @agreement_section: a #AsAgreementSection instance. + * @kind: the rating kind, e.g. "GDPR" + * + * Sets the agreement section kind. + * + * Since: 0.7.8 + **/ +void +as_agreement_section_set_kind (AsAgreementSection *agreement_section, const gchar *kind) +{ + AsAgreementSectionPrivate *priv = GET_PRIVATE (agreement_section); + as_ref_string_assign_safe (&priv->kind, kind); +} + +/** + * as_agreement_section_get_name: + * @agreement_section: a #AsAgreementSection instance. + * @locale: (nullable): the locale. e.g. "en_GB" + * + * Gets the agreement section name. + * + * Returns: a string, e.g. "GDPR", or NULL + * + * Since: 0.7.8 + **/ +const gchar * +as_agreement_section_get_name (AsAgreementSection *agreement_section, const gchar *locale) +{ + AsAgreementSectionPrivate *priv = GET_PRIVATE (agreement_section); + return priv->name; +} + +/** + * as_agreement_section_set_name: + * @agreement_section: a #AsAgreementSection instance. + * @locale: (nullable): the locale. e.g. "en_GB" + * @name: the rating name, e.g. "GDPR" + * + * Sets the agreement section name. + * + * Since: 0.7.8 + **/ +void +as_agreement_section_set_name (AsAgreementSection *agreement_section, + const gchar *locale, const gchar *name) +{ + AsAgreementSectionPrivate *priv = GET_PRIVATE (agreement_section); + as_ref_string_assign_safe (&priv->name, name); +} + +/** + * as_agreement_section_get_description: + * @agreement_section: a #AsAgreementSection instance. + * @locale: (nullable): the locale. e.g. "en_GB" + * + * Gets the agreement section desc. + * + * Returns: a string, e.g. "GDPR", or NULL + * + * Since: 0.7.8 + **/ +const gchar * +as_agreement_section_get_description (AsAgreementSection *agreement_section, + const gchar *locale) +{ + AsAgreementSectionPrivate *priv = GET_PRIVATE (agreement_section); + return priv->desc; +} + +/** + * as_agreement_section_set_description: + * @agreement_section: a #AsAgreementSection instance. + * @locale: (nullable): the locale. e.g. "en_GB" + * @desc: the rating desc, e.g. "GDPR" + * + * Sets the agreement section desc. + * + * Since: 0.7.8 + **/ +void +as_agreement_section_set_description (AsAgreementSection *agreement_section, + const gchar *locale, const gchar *desc) +{ + AsAgreementSectionPrivate *priv = GET_PRIVATE (agreement_section); + as_ref_string_assign_safe (&priv->desc, desc); +} + +/** + * as_agreement_section_node_insert: (skip) + * @agreement_section: a #AsAgreementSection instance. + * @parent: the parent #GNode to use.. + * @ctx: the #AsNodeContext + * + * Inserts the agreement_section into the DOM tree. + * + * Returns: (transfer none): A populated #GNode, or %NULL + * + * Since: 0.7.8 + **/ +GNode * +as_agreement_section_node_insert (AsAgreementSection *agreement_section, + GNode *parent, + AsNodeContext *ctx) +{ + AsAgreementSectionPrivate *priv = GET_PRIVATE (agreement_section); + GNode *n = as_node_insert (parent, "agreement_section", NULL, + AS_NODE_INSERT_FLAG_NONE, + NULL); + if (priv->kind != NULL) + as_node_add_attribute (n, "type", priv->kind); + if (priv->desc != NULL) { + as_node_insert (n, "description", priv->desc, + AS_NODE_INSERT_FLAG_PRE_ESCAPED, NULL); + } + + return n; +} + +/** + * as_agreement_section_node_parse: + * @agreement_section: a #AsAgreementSection instance. + * @node: a #GNode. + * @ctx: a #AsNodeContext. + * @error: A #GError or %NULL. + * + * Populates the object from a DOM node. + * + * Returns: %TRUE for success + * + * Since: 0.7.8 + **/ +gboolean +as_agreement_section_node_parse (AsAgreementSection *agreement_section, GNode *node, + AsNodeContext *ctx, GError **error) +{ + const gchar *tmp; + + /* get ID */ + tmp = as_node_get_attribute (node, "type"); + if (tmp != NULL) + as_agreement_section_set_kind (agreement_section, tmp); + + /* get sections and details */ + for (GNode *c = node->children; c != NULL; c = c->next) { + if (as_node_get_tag (c) == AS_TAG_NAME) { + as_agreement_section_set_name (agreement_section, + as_node_get_attribute (c, "xml:lang"), + as_node_get_data (c)); + continue; + } + if (as_node_get_tag (c) == AS_TAG_DESCRIPTION) { + g_autoptr(GString) xml = NULL; + xml = as_node_to_xml (c->children, + AS_NODE_TO_XML_FLAG_INCLUDE_SIBLINGS); + as_agreement_section_set_description (agreement_section, + as_node_get_attribute (c, "xml:lang"), + xml->str); + continue; + } + } + return TRUE; +} + +/** + * as_agreement_section_new: + * + * Creates a new #AsAgreementSection. + * + * Returns: (transfer full): a #AsAgreementSection + * + * Since: 0.7.8 + **/ +AsAgreementSection * +as_agreement_section_new (void) +{ + AsAgreementSection *agreement_section; + agreement_section = g_object_new (AS_TYPE_AGREEMENT_SECTION, NULL); + return AS_AGREEMENT_SECTION (agreement_section); +} diff --git a/libappstream-glib/as-agreement-section.h b/libappstream-glib/as-agreement-section.h new file mode 100644 index 0000000..5f56a57 --- /dev/null +++ b/libappstream-glib/as-agreement-section.h @@ -0,0 +1,68 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- + * + * Copyright (C) 2018 Richard Hughes <richard@hughsie.com> + * + * Licensed under the GNU Lesser General Public License Version 2.1 + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#if !defined (__APPSTREAM_GLIB_H) && !defined (AS_COMPILATION) +#error "Only <appstream-glib.h> can be included directly." +#endif + +#ifndef __AS_AGREEMENT_SECTION_H +#define __AS_AGREEMENT_SECTION_H + +#include <glib-object.h> + +G_BEGIN_DECLS + +#define AS_TYPE_AGREEMENT_SECTION (as_agreement_section_get_type ()) +G_DECLARE_DERIVABLE_TYPE (AsAgreementSection, as_agreement_section, AS, AGREEMENT_SECTION, GObject) + +struct _AsAgreementSectionClass +{ + GObjectClass parent_class; + /*< private >*/ + void (*_as_reserved1) (void); + void (*_as_reserved2) (void); + void (*_as_reserved3) (void); + void (*_as_reserved4) (void); + void (*_as_reserved5) (void); + void (*_as_reserved6) (void); + void (*_as_reserved7) (void); + void (*_as_reserved8) (void); +}; + +AsAgreementSection *as_agreement_section_new (void); + +const gchar *as_agreement_section_get_kind (AsAgreementSection *agreement_section); +void as_agreement_section_set_kind (AsAgreementSection *agreement_section, + const gchar *kind); +const gchar *as_agreement_section_get_name (AsAgreementSection *agreement_section, + const gchar *locale); +void as_agreement_section_set_name (AsAgreementSection *agreement_section, + const gchar *locale, + const gchar *name); +const gchar *as_agreement_section_get_description (AsAgreementSection *agreement_section, + const gchar *locale); +void as_agreement_section_set_description (AsAgreementSection *agreement_section, + const gchar *locale, + const gchar *desc); + +G_END_DECLS + +#endif /* __AS_AGREEMENT_SECTION_H */ diff --git a/libappstream-glib/as-agreement.c b/libappstream-glib/as-agreement.c new file mode 100644 index 0000000..8c78204 --- /dev/null +++ b/libappstream-glib/as-agreement.c @@ -0,0 +1,330 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- + * + * Copyright (C) 2018 Richard Hughes <richard@hughsie.com> + * + * Licensed under the GNU Lesser General Public License Version 2.1 + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * SECTION:as-agreement + * @short_description: Object representing a privacy policy + * @include: appstream-glib.h + * @stability: Unstable + * + * Agreements can be used by components to specify GDPR, EULA or other warnings. + * + * See also: #AsAgreementDetail, #AsAgreementSection + */ + +#include "config.h" + +#include "as-node-private.h" +#include "as-agreement-private.h" +#include "as-agreement-section-private.h" +#include "as-ref-string.h" +#include "as-tag.h" + +typedef struct { + AsAgreementKind kind; + AsRefString *version_id; + GPtrArray *sections; +} AsAgreementPrivate; + +G_DEFINE_TYPE_WITH_PRIVATE (AsAgreement, as_agreement, G_TYPE_OBJECT) + +#define GET_PRIVATE(o) (as_agreement_get_instance_private (o)) + +static void +as_agreement_finalize (GObject *object) +{ + AsAgreement *agreement = AS_AGREEMENT (object); + AsAgreementPrivate *priv = GET_PRIVATE (agreement); + + if (priv->version_id != NULL) + as_ref_string_unref (priv->version_id); + g_ptr_array_unref (priv->sections); + + G_OBJECT_CLASS (as_agreement_parent_class)->finalize (object); +} + +static void +as_agreement_init (AsAgreement *agreement) +{ + AsAgreementPrivate *priv = GET_PRIVATE (agreement); + priv->sections = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref); +} + +static void +as_agreement_class_init (AsAgreementClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + object_class->finalize = as_agreement_finalize; +} + +/** + * as_agreement_kind_to_string: + * @value: the #AsAgreementKind. + * + * Converts the enumerated value to an text representation. + * + * Returns: string version of @value + * + * Since: 0.7.8 + **/ +const gchar * +as_agreement_kind_to_string (AsAgreementKind value) +{ + if (value == AS_AGREEMENT_KIND_GENERIC) + return "generic"; + if (value == AS_AGREEMENT_KIND_EULA) + return "eula"; + if (value == AS_AGREEMENT_KIND_PRIVACY) + return "privacy"; + return "unknown"; +} + +/** + * as_agreement_kind_from_string: + * @value: the string. + * + * Converts the text representation to an enumerated value. + * + * Returns: a #AsAgreementKind or %AS_AGREEMENT_KIND_UNKNOWN for unknown + * + * Since: 0.7.8 + **/ +AsAgreementKind +as_agreement_kind_from_string (const gchar *value) +{ + if (g_strcmp0 (value, "generic") == 0) + return AS_AGREEMENT_KIND_GENERIC; + if (g_strcmp0 (value, "eula") == 0) + return AS_AGREEMENT_KIND_EULA; + if (g_strcmp0 (value, "privacy") == 0) + return AS_AGREEMENT_KIND_PRIVACY; + return AS_AGREEMENT_KIND_UNKNOWN; +} + +/** + * as_agreement_get_kind: + * @agreement: a #AsAgreement instance. + * + * Gets the agreement kind. + * + * Returns: a string, e.g. %AS_AGREEMENT_KIND_EULA + * + * Since: 0.7.8 + **/ +AsAgreementKind +as_agreement_get_kind (AsAgreement *agreement) +{ + AsAgreementPrivate *priv = GET_PRIVATE (agreement); + return priv->kind; +} + +/** + * as_agreement_set_kind: + * @agreement: a #AsAgreement instance. + * @kind: the agreement kind, e.g. %AS_AGREEMENT_KIND_EULA + * + * Sets the agreement kind. + * + * Since: 0.7.8 + **/ +void +as_agreement_set_kind (AsAgreement *agreement, AsAgreementKind kind) +{ + AsAgreementPrivate *priv = GET_PRIVATE (agreement); + priv->kind = kind; +} + +/** + * as_agreement_get_version_id: + * @agreement: a #AsAgreement instance. + * + * Gets the agreement version_id. + * + * Returns: a string, e.g. "1.4a", or NULL + * + * Since: 0.7.8 + **/ +const gchar * +as_agreement_get_version_id (AsAgreement *agreement) +{ + AsAgreementPrivate *priv = GET_PRIVATE (agreement); + return priv->version_id; +} + +/** + * as_agreement_set_version_id: + * @agreement: a #AsAgreement instance. + * @version_id: the agreement version ID, e.g. "1.4a" + * + * Sets the agreement version identifier. + * + * Since: 0.7.8 + **/ +void +as_agreement_set_version_id (AsAgreement *agreement, const gchar *version_id) +{ + AsAgreementPrivate *priv = GET_PRIVATE (agreement); + as_ref_string_assign_safe (&priv->version_id, version_id); +} + +/** + * as_agreement_get_sections: + * @agreement: a #AsAgreement instance. + * + * Gets all the sections in the agreement. + * + * Returns: (transfer container) (element-type AsAgreementSection): array + * + * Since: 0.7.8 + **/ +GPtrArray * +as_agreement_get_sections (AsAgreement *agreement) +{ + AsAgreementPrivate *priv = GET_PRIVATE (agreement); + return priv->sections; +} + +/** + * as_agreement_get_section_default: + * @agreement: a #AsAgreement instance. + * + * Gets the first section in the agreement. + * + * Returns: (transfer none): agreement section, or %NULL + * + * Since: 0.7.8 + **/ +AsAgreementSection * +as_agreement_get_section_default (AsAgreement *agreement) +{ + AsAgreementPrivate *priv = GET_PRIVATE (agreement); + if (priv->sections->len == 0) + return NULL; + return AS_AGREEMENT_SECTION (g_ptr_array_index (priv->sections, 0)); +} + +/** + * as_agreement_add_detail: + * @agreement: a #AsAgreement instance. + * @agreement_section: a #AsAgreementSection instance. + * + * Adds a section to the agreement. + * + * Since: 0.7.8 + **/ +void +as_agreement_add_section (AsAgreement *agreement, AsAgreementSection *agreement_section) +{ + AsAgreementPrivate *priv = GET_PRIVATE (agreement); + g_ptr_array_add (priv->sections, g_object_ref (agreement_section)); +} + +/** + * as_agreement_node_insert: (skip) + * @agreement: a #AsAgreement instance. + * @parent: the parent #GNode to use.. + * @ctx: the #AsNodeContext + * + * Inserts the agreement into the DOM tree. + * + * Returns: (transfer none): A populated #GNode, or %NULL + * + * Since: 0.7.8 + **/ +GNode * +as_agreement_node_insert (AsAgreement *agreement, + GNode *parent, + AsNodeContext *ctx) +{ + AsAgreementPrivate *priv = GET_PRIVATE (agreement); + GNode *n = as_node_insert (parent, "agreement", NULL, + AS_NODE_INSERT_FLAG_NONE, + NULL); + if (priv->kind != AS_AGREEMENT_KIND_UNKNOWN) { + as_node_add_attribute (n, "type", + as_agreement_kind_to_string (priv->kind)); + } + if (priv->version_id != NULL) + as_node_add_attribute (n, "version_id", priv->version_id); + for (guint i = 0; i < priv->sections->len; i++) { + AsAgreementSection *ps = g_ptr_array_index (priv->sections, i); + as_agreement_section_node_insert (ps, n, ctx); + } + + return n; +} + +/** + * as_agreement_node_parse: + * @agreement: a #AsAgreement instance. + * @node: a #GNode. + * @ctx: a #AsNodeContext. + * @error: A #GError or %NULL. + * + * Populates the object from a DOM node. + * + * Returns: %TRUE for success + * + * Since: 0.7.8 + **/ +gboolean +as_agreement_node_parse (AsAgreement *agreement, GNode *node, + AsNodeContext *ctx, GError **error) +{ + const gchar *tmp; + + /* get ID */ + tmp = as_node_get_attribute (node, "type"); + if (tmp != NULL) + as_agreement_set_kind (agreement, as_agreement_kind_from_string (tmp)); + tmp = as_node_get_attribute (node, "version_id"); + if (tmp != NULL) + as_agreement_set_version_id (agreement, tmp); + + /* get sections and details */ + for (GNode *c = node->children; c != NULL; c = c->next) { + if (as_node_get_tag (c) == AS_TAG_AGREEMENT_SECTION) { + g_autoptr(AsAgreementSection) ps = as_agreement_section_new (); + if (!as_agreement_section_node_parse (ps, c, ctx, error)) + return FALSE; + as_agreement_add_section (agreement, ps); + continue; + } + } + return TRUE; +} + +/** + * as_agreement_new: + * + * Creates a new #AsAgreement. + * + * Returns: (transfer full): a #AsAgreement + * + * Since: 0.7.8 + **/ +AsAgreement * +as_agreement_new (void) +{ + AsAgreement *agreement; + agreement = g_object_new (AS_TYPE_AGREEMENT, NULL); + return AS_AGREEMENT (agreement); +} diff --git a/libappstream-glib/as-agreement.h b/libappstream-glib/as-agreement.h new file mode 100644 index 0000000..a832a7a --- /dev/null +++ b/libappstream-glib/as-agreement.h @@ -0,0 +1,88 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- + * + * Copyright (C) 2018 Richard Hughes <richard@hughsie.com> + * + * Licensed under the GNU Lesser General Public License Version 2.1 + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#if !defined (__APPSTREAM_GLIB_H) && !defined (AS_COMPILATION) +#error "Only <appstream-glib.h> can be included directly." +#endif + +#ifndef __AS_AGREEMENT_H +#define __AS_AGREEMENT_H + +#include <glib-object.h> + +#include "as-agreement-section.h" + +G_BEGIN_DECLS + +#define AS_TYPE_AGREEMENT (as_agreement_get_type ()) +G_DECLARE_DERIVABLE_TYPE (AsAgreement, as_agreement, AS, AGREEMENT, GObject) + +struct _AsAgreementClass +{ + GObjectClass parent_class; + /*< private >*/ + void (*_as_reserved1) (void); + void (*_as_reserved2) (void); + void (*_as_reserved3) (void); + void (*_as_reserved4) (void); + void (*_as_reserved5) (void); + void (*_as_reserved6) (void); + void (*_as_reserved7) (void); + void (*_as_reserved8) (void); +}; + +/** + * AsAgreementKind: + * @AS_AGREEMENT_KIND_UNKNOWN: Unknown value + * @AS_AGREEMENT_KIND_GENERIC: A generic agreement without a specific type + * @AS_AGREEMENT_KIND_EULA: An End User License Agreement + * @AS_AGREEMENT_KIND_PRIVACY: A privacy agreement, typically a GDPR statement + * + * The kind of the agreement. + **/ +typedef enum { + AS_AGREEMENT_KIND_UNKNOWN, + AS_AGREEMENT_KIND_GENERIC, + AS_AGREEMENT_KIND_EULA, + AS_AGREEMENT_KIND_PRIVACY, + /*< private >*/ + AS_AGREEMENT_KIND_LAST +} AsAgreementKind; + +AsAgreement *as_agreement_new (void); + +const gchar *as_agreement_kind_to_string (AsAgreementKind value); +AsAgreementKind as_agreement_kind_from_string (const gchar *value); + +AsAgreementKind as_agreement_get_kind (AsAgreement *agreement); +void as_agreement_set_kind (AsAgreement *agreement, + AsAgreementKind kind); +const gchar *as_agreement_get_version_id (AsAgreement *agreement); +void as_agreement_set_version_id (AsAgreement *agreement, + const gchar *version_id); +AsAgreementSection *as_agreement_get_section_default (AsAgreement *agreement); +GPtrArray *as_agreement_get_sections (AsAgreement *agreement); +void as_agreement_add_section (AsAgreement *agreement, + AsAgreementSection *agreement_section); + +G_END_DECLS + +#endif /* __AS_AGREEMENT_H */ diff --git a/libappstream-glib/as-app-private.h b/libappstream-glib/as-app-private.h index b9e0dd2..0d81cd5 100644 --- a/libappstream-glib/as-app-private.h +++ b/libappstream-glib/as-app-private.h @@ -57,6 +57,7 @@ G_BEGIN_DECLS * @AS_APP_PROBLEM_DUPLICATE_RELEASE: More than one release with the same version * @AS_APP_PROBLEM_DUPLICATE_SCREENSHOT: More than one screenshot with the same URL * @AS_APP_PROBLEM_DUPLICATE_CONTENT_RATING: More than one content rating with the same kind + * @AS_APP_PROBLEM_DUPLICATE_AGREEMENT: More than one agreement with the same kind * * The application problems detected when loading. **/ @@ -82,6 +83,7 @@ typedef enum { AS_APP_PROBLEM_DUPLICATE_RELEASE = 1 << 17, AS_APP_PROBLEM_DUPLICATE_SCREENSHOT = 1 << 18, AS_APP_PROBLEM_DUPLICATE_CONTENT_RATING = 1 << 19, + AS_APP_PROBLEM_DUPLICATE_AGREEMENT = 1 << 20, /*< private >*/ AS_APP_PROBLEM_LAST } AsAppProblems; diff --git a/libappstream-glib/as-app.c b/libappstream-glib/as-app.c index ddfc60a..11556e6 100644 --- a/libappstream-glib/as-app.c +++ b/libappstream-glib/as-app.c @@ -1,6 +1,6 @@ /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- * - * Copyright (C) 2014-2017 Richard Hughes <richard@hughsie.com> + * Copyright (C) 2014-2018 Richard Hughes <richard@hughsie.com> * * Licensed under the GNU Lesser General Public License Version 2.1 * @@ -40,6 +40,7 @@ #include "as-app-private.h" #include "as-bundle-private.h" #include "as-content-rating-private.h" +#include "as-agreement-private.h" #include "as-enums.h" #include "as-icon-private.h" #include "as-node-private.h" @@ -87,6 +88,7 @@ typedef struct GPtrArray *screenshots; /* of AsScreenshot */ GPtrArray *reviews; /* of AsReview */ GPtrArray *content_ratings; /* of AsContentRating */ + GPtrArray *agreements; /* of AsAgreement */ GPtrArray *icons; /* of AsIcon */ GPtrArray *bundles; /* of AsBundle */ GPtrArray *translations; /* of AsTranslation */ @@ -454,6 +456,7 @@ as_app_finalize (GObject *object) g_ptr_array_unref (priv->categories); g_ptr_array_unref (priv->compulsory_for_desktops); g_ptr_array_unref (priv->content_ratings); + g_ptr_array_unref (priv->agreements); g_ptr_array_unref (priv->extends); g_ptr_array_unref (priv->kudos); g_ptr_array_unref (priv->permissions); @@ -483,6 +486,7 @@ as_app_init (AsApp *app) priv->categories = g_ptr_array_new_with_free_func ((GDestroyNotify) as_ref_string_unref); priv->compulsory_for_desktops = g_ptr_array_new_with_free_func ((GDestroyNotify) as_ref_string_unref); priv->content_ratings = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref); + priv->agreements = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref); priv->extends = g_ptr_array_new_with_free_func ((GDestroyNotify) as_ref_string_unref); priv->keywords = g_hash_table_new_full (g_str_hash, g_str_equal, (GDestroyNotify) as_ref_string_unref, @@ -1317,6 +1321,68 @@ as_app_get_content_rating (AsApp *app, const gchar *kind) } /** + * as_app_get_agreements: + * @app: a #AsApp instance. + * + * Gets any agreements the application has defined. + * + * Returns: (element-type AsAgreement) (transfer none): an array + * + * Since: 0.7.8 + **/ +GPtrArray * +as_app_get_agreements (AsApp *app) +{ + AsAppPrivate *priv = GET_PRIVATE (app); + return priv->agreements; +} + +/** + * as_app_get_agreement_by_kind: + * @app: a #AsApp instance. + * @kind: an agreement kind, e.g. %AS_AGREEMENT_KIND_EULA + * + * Gets a agreement the application has defined of a specific type. + * + * Returns: (transfer none): a #AsAgreement or NULL for not found + * + * Since: 0.7.8 + **/ +AsAgreement * +as_app_get_agreement_by_kind (AsApp *app, AsAgreementKind kind) +{ + AsAppPrivate *priv = GET_PRIVATE (app); + guint i; + + for (i = 0; i < priv->agreements->len; i++) { + AsAgreement *agreement; + agreement = g_ptr_array_index (priv->agreements, i); + if (as_agreement_get_kind (agreement) == kind) + return agreement; + } + return NULL; +} + +/** + * as_app_get_agreement_default: + * @app: a #AsApp instance. + * + * Gets a privacy policys the application has defined of a specific type. + * + * Returns: (transfer none): a #AsAgreement or NULL for not found + * + * Since: 0.7.8 + **/ +AsAgreement * +as_app_get_agreement_default (AsApp *app) +{ + AsAppPrivate *priv = GET_PRIVATE (app); + if (priv->agreements->len < 1) + return NULL; + return g_ptr_array_index (priv->agreements, 0); +} + +/** * as_app_get_icons: * @app: a #AsApp instance. * @@ -3389,6 +3455,33 @@ as_app_add_content_rating (AsApp *app, AsContentRating *content_rating) g_ptr_array_add (priv->content_ratings, g_object_ref (content_rating)); } +/** + * as_app_add_agreement: + * @app: a #AsApp instance. + * @agreement: a #AsAgreement instance. + * + * Adds a agreement to an application. + * + * Since: 0.7.8 + **/ +void +as_app_add_agreement (AsApp *app, AsAgreement *agreement) +{ + AsAppPrivate *priv = GET_PRIVATE (app); + + /* handle untrusted */ + if ((priv->trust_flags & AS_APP_TRUST_FLAG_CHECK_DUPLICATES) > 0) { + for (guint i = 0; i < priv->agreements->len; i++) { + AsAgreement *cr_tmp = g_ptr_array_index (priv->agreements, i); + if (as_agreement_get_kind (cr_tmp) == as_agreement_get_kind (agreement)) { + priv->problems |= AS_APP_PROBLEM_DUPLICATE_AGREEMENT; + return; + } + } + } + g_ptr_array_add (priv->agreements, g_object_ref (agreement)); +} + static gboolean as_app_check_icon_duplicate (AsIcon *icon1, AsIcon *icon2) { @@ -4087,6 +4180,18 @@ as_app_subsume_private (AsApp *app, AsApp *donor, guint64 flags) } } + /* agreements */ + if (flags & AS_APP_SUBSUME_FLAG_AGREEMENTS) { + if ((flags & AS_APP_SUBSUME_FLAG_REPLACE) > 0 && + priv->agreements->len > 0) + g_ptr_array_set_size (papp->agreements, 0); + for (i = 0; i < priv->agreements->len; i++) { + AsAgreement *agreement; + agreement = g_ptr_array_index (priv->agreements, i); + as_app_add_agreement (app, agreement); + } + } + /* provides */ if (flags & AS_APP_SUBSUME_FLAG_PROVIDES) { if ((flags & AS_APP_SUBSUME_FLAG_REPLACE) > 0 && @@ -4657,6 +4762,15 @@ as_app_node_insert (AsApp *app, GNode *parent, AsNodeContext *ctx) } } + /* <agreements> */ + if (priv->agreements->len > 0) { + for (i = 0; i < priv->agreements->len; i++) { + AsAgreement *agreement; + agreement = g_ptr_array_index (priv->agreements, i); + as_agreement_node_insert (agreement, node_app, ctx); + } + } + /* <releases> */ if (priv->releases->len > 0) { g_ptr_array_sort (priv->releases, as_app_releases_sort_cb); @@ -5127,6 +5241,17 @@ as_app_node_parse_child (AsApp *app, GNode *n, guint32 flags, break; } + /* <agreements> */ + case AS_TAG_AGREEMENT: + { + g_autoptr(AsAgreement) agreement = NULL; + agreement = as_agreement_new (); + if (!as_agreement_node_parse (agreement, n, ctx, error)) + return FALSE; + as_app_add_agreement (app, agreement); + break; + } + /* <releases> */ case AS_TAG_RELEASES: if (!(flags & AS_APP_PARSE_FLAG_APPEND_DATA)) @@ -5280,6 +5405,7 @@ as_app_node_parse_full (AsApp *app, GNode *node, guint32 flags, g_ptr_array_set_size (priv->suggests, 0); g_ptr_array_set_size (priv->requires, 0); g_ptr_array_set_size (priv->content_ratings, 0); + g_ptr_array_set_size (priv->agreements, 0); g_ptr_array_set_size (priv->launchables, 0); g_hash_table_remove_all (priv->keywords); } diff --git a/libappstream-glib/as-app.h b/libappstream-glib/as-app.h index 3b4f1e0..a878479 100644 --- a/libappstream-glib/as-app.h +++ b/libappstream-glib/as-app.h @@ -1,6 +1,6 @@ /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- * - * Copyright (C) 2014-2017 Richard Hughes <richard@hughsie.com> + * Copyright (C) 2014-2018 Richard Hughes <richard@hughsie.com> * * Licensed under the GNU Lesser General Public License Version 2.1 * @@ -40,6 +40,7 @@ #include "as-review.h" #include "as-suggest.h" #include "as-content-rating.h" +#include "as-agreement.h" #include "as-translation.h" G_BEGIN_DECLS @@ -108,6 +109,7 @@ typedef enum { * @AS_APP_SUBSUME_FLAG_SCREENSHOTS: Copy the screenshots * @AS_APP_SUBSUME_FLAG_REVIEWS: Copy the reviews * @AS_APP_SUBSUME_FLAG_CONTENT_RATINGS: Copy the content ratings + * @AS_APP_SUBSUME_FLAG_AGREEMENTS: Copy the agreements * @AS_APP_SUBSUME_FLAG_PROVIDES: Copy the provides * @AS_APP_SUBSUME_FLAG_ICONS: Copy the icons * @AS_APP_SUBSUME_FLAG_MIMETYPES: Copy the mimetypes @@ -170,6 +172,7 @@ typedef enum { AS_APP_SUBSUME_FLAG_SOURCE_KIND = 1ull << 34, /* Since: 0.6.1 */ AS_APP_SUBSUME_FLAG_SUGGESTS = 1ull << 35, /* Since: 0.6.3 */ AS_APP_SUBSUME_FLAG_LAUNCHABLES = 1ull << 36, /* Since: 0.6.13 */ + AS_APP_SUBSUME_FLAG_AGREEMENTS = 1ull << 37, /* Since: 0.7.8 */ /*< private >*/ AS_APP_SUBSUME_FLAG_LAST, } AsAppSubsumeFlags; @@ -182,6 +185,7 @@ typedef enum { AS_APP_SUBSUME_FLAG_COMMENT | \ AS_APP_SUBSUME_FLAG_COMPULSORY | \ AS_APP_SUBSUME_FLAG_CONTENT_RATINGS | \ + AS_APP_SUBSUME_FLAG_AGREEMENTS | \ AS_APP_SUBSUME_FLAG_DESCRIPTION | \ AS_APP_SUBSUME_FLAG_DEVELOPER_NAME | \ AS_APP_SUBSUME_FLAG_EXTENDS | \ @@ -646,6 +650,8 @@ void as_app_add_review (AsApp *app, AsReview *review); void as_app_add_content_rating (AsApp *app, AsContentRating *content_rating); +void as_app_add_agreement (AsApp *app, + AsAgreement *agreement); void as_app_add_icon (AsApp *app, AsIcon *icon); void as_app_add_bundle (AsApp *app, @@ -709,6 +715,9 @@ gboolean as_app_to_file (AsApp *app, GError **error); AsContentRating *as_app_get_content_rating (AsApp *app, const gchar *kind); +AsAgreement *as_app_get_agreement_by_kind (AsApp *app, + AsAgreementKind kind); +AsAgreement *as_app_get_agreement_default (AsApp *app); AsScreenshot *as_app_get_screenshot_default (AsApp *app); AsIcon *as_app_get_icon_default (AsApp *app); AsIcon *as_app_get_icon_for_size (AsApp *app, diff --git a/libappstream-glib/as-self-test.c b/libappstream-glib/as-self-test.c index ead8c81..3c85a12 100644 --- a/libappstream-glib/as-self-test.c +++ b/libappstream-glib/as-self-test.c @@ -27,6 +27,7 @@ #include <string.h> #include <fnmatch.h> +#include "as-agreement-private.h" #include "as-app-private.h" #include "as-app-builder.h" #include "as-bundle-private.h" @@ -1273,6 +1274,57 @@ as_test_image_func (void) g_assert (ret); } +static void +as_test_agreement_func (void) +{ + GError *error = NULL; + AsAgreementSection *sect; + AsNode *n; + AsNode *root; + GString *xml; + const gchar *src = + "<agreement type=\"eula\" version_id=\"1.2.3a\">\n" + "<agreement_section type=\"intro\">\n" + "<description><p>Mighty Fine</p></description>\n" + "</agreement_section>\n" + "</agreement>\n"; + gboolean ret; + g_autoptr(AsAgreement) agreement = NULL; + g_autoptr(AsNodeContext) ctx = NULL; + + agreement = as_agreement_new (); + + /* to object */ + root = as_node_from_xml (src, 0, &error); + g_assert_no_error (error); + g_assert (root != NULL); + n = as_node_find (root, "agreement"); + g_assert (n != NULL); + ctx = as_node_context_new (); + ret = as_agreement_node_parse (agreement, n, ctx, &error); + g_assert_no_error (error); + g_assert (ret); + as_node_unref (root); + + /* verify */ + g_assert_cmpint (as_agreement_get_kind (agreement), ==, AS_AGREEMENT_KIND_EULA); + g_assert_cmpstr (as_agreement_get_version_id (agreement), ==, "1.2.3a"); + sect = as_agreement_get_section_default (agreement); + g_assert_nonnull (sect); + g_assert_cmpstr (as_agreement_section_get_kind (sect), ==, "intro"); + g_assert_cmpstr (as_agreement_section_get_description (sect, NULL), ==, "<p>Mighty Fine</p>"); + + /* back to node */ + root = as_node_new (); + as_node_context_set_version (ctx, 0.4); + n = as_agreement_node_insert (agreement, root, ctx); + xml = as_node_to_xml (n, AS_NODE_TO_XML_FLAG_FORMAT_MULTILINE); + ret = as_test_compare_lines (xml->str, src, &error); + g_assert_no_error (error); + g_assert (ret); + g_string_free (xml, TRUE); + as_node_unref (root); +} static void as_test_review_func (void) @@ -5576,6 +5628,7 @@ main (int argc, char **argv) g_test_add_func ("/AppStream/icon{embedded}", as_test_icon_embedded_func); g_test_add_func ("/AppStream/bundle", as_test_bundle_func); g_test_add_func ("/AppStream/review", as_test_review_func); + g_test_add_func ("/AppStream/agreement", as_test_agreement_func); g_test_add_func ("/AppStream/translation", as_test_translation_func); g_test_add_func ("/AppStream/suggest", as_test_suggest_func); g_test_add_func ("/AppStream/image", as_test_image_func); diff --git a/libappstream-glib/as-tag.c b/libappstream-glib/as-tag.c index 56aad4d..adf3a54 100644 --- a/libappstream-glib/as-tag.c +++ b/libappstream-glib/as-tag.c @@ -190,6 +190,8 @@ as_tag_to_string (AsTag tag) "requires", "custom", "launchable", + "agreement", + "agreement_section", NULL }; if (tag > AS_TAG_LAST) tag = AS_TAG_LAST; diff --git a/libappstream-glib/as-tag.gperf b/libappstream-glib/as-tag.gperf index b090d49..8f70954 100644 --- a/libappstream-glib/as-tag.gperf +++ b/libappstream-glib/as-tag.gperf @@ -67,3 +67,5 @@ suggests, AS_TAG_SUGGESTS requires, AS_TAG_REQUIRES custom, AS_TAG_CUSTOM launchable, AS_TAG_LAUNCHABLE +agreement, AS_TAG_AGREEMENT +agreement_section, AS_TAG_AGREEMENT_SECTION diff --git a/libappstream-glib/as-tag.h b/libappstream-glib/as-tag.h index adb7c79..61af6b8 100644 --- a/libappstream-glib/as-tag.h +++ b/libappstream-glib/as-tag.h @@ -92,6 +92,8 @@ G_BEGIN_DECLS * @AS_TAG_REQUIRES: `requires` * @AS_TAG_CUSTOM: `custom` * @AS_TAG_LAUNCHABLE: `launchable` + * @AS_TAG_AGREEMENT: `agreement` + * @AS_TAG_AGREEMENT_SECTION: `agreement_section` * * The tag type. **/ @@ -156,6 +158,8 @@ typedef enum { AS_TAG_REQUIRES, /* Since: 0.6.7 */ AS_TAG_CUSTOM, /* Since: 0.6.8 */ AS_TAG_LAUNCHABLE, /* Since: 0.6.13 */ + AS_TAG_AGREEMENT, /* Since: 0.7.8 */ + AS_TAG_AGREEMENT_SECTION, /* Since: 0.7.8 */ /*< private >*/ AS_TAG_LAST } AsTag; diff --git a/libappstream-glib/meson.build b/libappstream-glib/meson.build index 19576fa..33e1f61 100644 --- a/libappstream-glib/meson.build +++ b/libappstream-glib/meson.build @@ -50,6 +50,8 @@ headers = [ 'as-markup.h', 'as-monitor.h', 'as-node.h', + 'as-agreement.h', + 'as-agreement-section.h', 'as-problem.h', 'as-profile.h', 'as-provide.h', @@ -84,6 +86,8 @@ sources = [ 'as-monitor.c', 'as-monitor.c', 'as-node.c', + 'as-agreement.c', + 'as-agreement-section.c', 'as-problem.c', 'as-profile.c', 'as-provide.c', @@ -189,6 +193,10 @@ introspection_sources = [ 'as-markup.h', 'as-node.c', 'as-node.h', + 'as-agreement.c', + 'as-agreement.h', + 'as-agreement-section.c', + 'as-agreement-section.h', 'as-problem.c', 'as-problem.h', 'as-provide.c', |