summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRichard Hughes <richard@hughsie.com>2018-03-14 13:29:25 +0000
committerRichard Hughes <richard@hughsie.com>2018-04-11 19:19:47 +0100
commit90341836a64b22104ee41ac4b9acd6299bc8aa3a (patch)
treed7870f815386c4b3e7a5664e0bbbec19fea61950
parent786a2c85c06ba82d41db9eb5490c964d3bd7754d (diff)
downloadappstream-glib-wip/hughsie/privacy-gdpr.tar.gz
Add support for component agreementswip/hughsie/privacy-gdpr
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.c58
-rw-r--r--data/tests/example-eula.metainfo.xml18
-rw-r--r--data/tests/example-gdpr.metainfo.xml396
-rw-r--r--data/tests/example-remote.metainfo.xml28
-rw-r--r--libappstream-glib/as-agreement-private.h46
-rw-r--r--libappstream-glib/as-agreement-section-private.h46
-rw-r--r--libappstream-glib/as-agreement-section.c276
-rw-r--r--libappstream-glib/as-agreement-section.h68
-rw-r--r--libappstream-glib/as-agreement.c330
-rw-r--r--libappstream-glib/as-agreement.h88
-rw-r--r--libappstream-glib/as-app-private.h2
-rw-r--r--libappstream-glib/as-app.c128
-rw-r--r--libappstream-glib/as-app.h11
-rw-r--r--libappstream-glib/as-self-test.c53
-rw-r--r--libappstream-glib/as-tag.c2
-rw-r--r--libappstream-glib/as-tag.gperf2
-rw-r--r--libappstream-glib/as-tag.h4
-rw-r--r--libappstream-glib/meson.build8
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',