aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/Makefile.am1
-rw-r--r--src/gpg-error.def.in16
-rw-r--r--src/gpg-error.h.in81
-rw-r--r--src/gpg-error.vers16
-rw-r--r--src/gpgrt-int.h26
-rw-r--r--src/name-value.c895
-rw-r--r--src/visibility.c92
-rw-r--r--src/visibility.h32
8 files changed, 1155 insertions, 4 deletions
diff --git a/src/Makefile.am b/src/Makefile.am
index d6223d6..e56bb23 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -199,6 +199,7 @@ libgpg_error_la_SOURCES = gettext.h $(arch_sources) \
sysutils.c \
stringutils.c \
strlist.c \
+ name-value.c \
syscall-clamp.c \
logging.c \
b64dec.c b64enc.c \
diff --git a/src/gpg-error.def.in b/src/gpg-error.def.in
index 75e8537..a54aa69 100644
--- a/src/gpg-error.def.in
+++ b/src/gpg-error.def.in
@@ -268,7 +268,21 @@ EXPORTS
gpgrt_strlist_pop @206
gpgrt_strlist_find @207
-
+ gpgrt_nvc_new @208
+ gpgrt_nvc_release @209
+ gpgrt_nvc_get_flag @210
+ gpgrt_nvc_add @211
+ gpgrt_nvc_set @212
+ gpgrt_nve_set @213
+ gpgrt_nvc_delete @214
+ gpgrt_nvc_lookup @215
+ gpgrt_nvc_parse @216
+ gpgrt_nvc_write @217
+ gpgrt_nve_next @218
+ gpgrt_nve_name @219
+ gpgrt_nve_value @220
+ gpgrt_nvc_get_string @221
+ gpgrt_nvc_get_bool @222
;; end of file with public symbols for Windows.
diff --git a/src/gpg-error.h.in b/src/gpg-error.h.in
index 16b2c44..5a95baf 100644
--- a/src/gpg-error.h.in
+++ b/src/gpg-error.h.in
@@ -1041,6 +1041,87 @@ gpgrt_strlist_count (gpgrt_strlist_t list)
}
+/*
+ * Name-value parser and writer
+ */
+
+struct _gpgrt_name_value_container;
+typedef struct _gpgrt_name_value_container *gpgrt_nvc_t;
+
+struct _gpgrt_name_value_entry;
+typedef struct _gpgrt_name_value_entry *gpgrt_nve_t;
+
+#define GPGRT_NVC_WIPE 2 /* Wipe the values on free. */
+#define GPGRT_NVC_PRIVKEY 4 /* Enable private key mode. */
+#define GPGRT_NVC_MODIFIED 256 /* Return the modified flag. */
+
+/* Return a name-value container according to the given flags.
+ * Returns NULL and sets ERRNO on error. */
+gpgrt_nvc_t gpgrt_nvc_new (unsigned int flags);
+
+/* Release a name-value container. */
+void gpgrt_nvc_release (gpgrt_nvc_t cont);
+
+/* Return the specified container FLAG. For the GPGRT_NVC_MODIFIED
+ * flag the CLEAR arg resets the flag after retrieval. */
+int gpgrt_nvc_get_flag (gpgrt_nvc_t cont, unsigned int flag, int clear);
+
+/* Add (NAME, VALUE) to CONT. If an entry with NAME already exists, a
+ * new entry with that name is appended. */
+gpg_err_code_t gpgrt_nvc_add (gpgrt_nvc_t cont,
+ const char *name, const char *value);
+
+/* Add (NAME, VALUE) to CONT. If an entry with NAME already exists,
+ * it is updated by VALUE. If multiple entries with NAME exist, only
+ * the first entry is updated. */
+gpg_err_code_t gpgrt_nvc_set (gpgrt_nvc_t cont,
+ const char *name, const char *value);
+
+/* Update entry E to VALUE. CONT is required to update the internal
+ * modified flag and to pass container flags to the entry. */
+gpg_err_code_t gpgrt_nve_set (gpgrt_nvc_t cont, gpgrt_nve_t e,
+ const char *value);
+
+/* Delete entries from the container CONT. Either ENTRY or NAME must
+ * be given. If ENTRY is given only this entry is deleted; if NAME is
+ * given all entries with this name are deleted. */
+void gpgrt_nvc_delete (gpgrt_nvc_t cont, gpgrt_nve_t entry, const char *name);
+
+/* Get the first entry with the given name. Return NULL if it does
+ * not exist. If NAME is NULL the first non-comment entry is
+ * returned. */
+gpgrt_nve_t gpgrt_nvc_lookup (gpgrt_nvc_t cont, const char *name);
+
+/* Parse STREAM and return a newly allocated container structure at
+ * RESULT. If ERRLINEP is given, the line number the parser was last
+ * considering is stored there. FLAGS are used to allocate the
+ * container. */
+gpg_err_code_t gpgrt_nvc_parse (gpgrt_nvc_t *result, int *errlinep,
+ estream_t stream, unsigned int flags);
+
+/* Write a representation of the container CONT to STREAM. */
+gpg_err_code_t gpgrt_nvc_write (gpgrt_nvc_t cont, estream_t stream);
+
+/* Return the next non-comment entry after ENTRY. If NAME is given
+ * the next entry with that name is returned. */
+gpgrt_nve_t gpgrt_nve_next (gpgrt_nve_t entry, const char *name);
+
+/* Return the name of the entry. */
+const char *gpgrt_nve_name (gpgrt_nve_t entry);
+
+/* Return the value of the entry. */
+const char *gpgrt_nve_value (gpgrt_nve_t entry);
+
+/* Convenience function to return the string for the first entry of
+ * CONT with NAME. If no such entry is found or its value is the
+ * empty string NULL is returned. */
+const char *gpgrt_nvc_get_string (gpgrt_nvc_t cont, const char *name);
+
+/* Convenience function to return true if NAME exists and its value is
+ * true; that is either "yes", "true", or a decimal value != 0. */
+int gpgrt_nvc_get_bool (gpgrt_nvc_t nvc, const char *name);
+
+
/*
* Logging functions
diff --git a/src/gpg-error.vers b/src/gpg-error.vers
index 04a7bee..199a0b9 100644
--- a/src/gpg-error.vers
+++ b/src/gpg-error.vers
@@ -233,6 +233,22 @@ GPG_ERROR_1.0 {
gpgrt_strlist_pop;
gpgrt_strlist_find;
+ gpgrt_nvc_new;
+ gpgrt_nvc_release;
+ gpgrt_nvc_get_flag;
+ gpgrt_nvc_add;
+ gpgrt_nvc_set;
+ gpgrt_nve_set;
+ gpgrt_nvc_delete;
+ gpgrt_nvc_lookup;
+ gpgrt_nvc_parse;
+ gpgrt_nvc_write;
+ gpgrt_nve_next;
+ gpgrt_nve_name;
+ gpgrt_nve_value;
+ gpgrt_nvc_get_string;
+ gpgrt_nvc_get_bool;
+
local:
*;
diff --git a/src/gpgrt-int.h b/src/gpgrt-int.h
index 279a2e2..6694831 100644
--- a/src/gpgrt-int.h
+++ b/src/gpgrt-int.h
@@ -778,9 +778,29 @@ char *_gpgrt_strlist_pop (gpgrt_strlist_t *list);
gpgrt_strlist_t _gpgrt_strlist_find (gpgrt_strlist_t haystack,
const char *needle);
-
-
-
+/*
+ * name-value.c
+ */
+gpgrt_nvc_t _gpgrt_nvc_new (unsigned int flags);
+void _gpgrt_nvc_release (gpgrt_nvc_t cont);
+int _gpgrt_nvc_get_flag (gpgrt_nvc_t cont, unsigned int flags, int clear);
+gpg_err_code_t _gpgrt_nvc_add (gpgrt_nvc_t cont,
+ const char *name, const char *value);
+gpg_err_code_t _gpgrt_nvc_set (gpgrt_nvc_t cont,
+ const char *name, const char *value);
+gpg_err_code_t _gpgrt_nve_set (gpgrt_nvc_t cont, gpgrt_nve_t e,
+ const char *value);
+void _gpgrt_nvc_delete (gpgrt_nvc_t cont, gpgrt_nve_t entry, const char *name);
+gpgrt_nve_t _gpgrt_nvc_lookup (gpgrt_nvc_t cont, const char *name);
+gpg_err_code_t _gpgrt_nvc_parse (gpgrt_nvc_t *result, int *errlinep,
+ estream_t stream, unsigned int flags);
+gpg_err_code_t _gpgrt_nvc_write (gpgrt_nvc_t cont, estream_t stream);
+gpgrt_nve_t _gpgrt_nve_next (gpgrt_nve_t entry, const char *name);
+const char *_gpgrt_nve_name (gpgrt_nve_t entry);
+const char *_gpgrt_nve_value (gpgrt_nve_t entry);
+/* Convenience functions. */
+const char *_gpgrt_nvc_get_string (gpgrt_nvc_t nvc, const char *name);
+int _gpgrt_nvc_get_bool (gpgrt_nvc_t nvc, const char *name);
diff --git a/src/name-value.c b/src/name-value.c
new file mode 100644
index 0000000..82d4810
--- /dev/null
+++ b/src/name-value.c
@@ -0,0 +1,895 @@
+/* name-value.c - Parser and writer for a name-value format.
+ * Copyright (C) 2016, 2025 g10 Code GmbH
+ *
+ * This file is part of Libgpg-error.
+ *
+ * This file 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 file 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses/>.
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ *
+ * This file was originally a part of GnuPG.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+#include <stdlib.h>
+#include <string.h>
+
+#include "gpgrt-int.h"
+
+
+struct _gpgrt_name_value_container
+{
+ struct _gpgrt_name_value_entry *first;
+ struct _gpgrt_name_value_entry *last;
+ unsigned int wipe_on_free:1;
+ unsigned int private_key_mode:1;
+ unsigned int modified:1;
+};
+
+
+struct _gpgrt_name_value_entry
+{
+ struct _gpgrt_name_value_entry *prev;
+ struct _gpgrt_name_value_entry *next;
+
+ unsigned int wipe_on_free:1; /* Copied from the container. */
+
+ /* The name. Comments and blank lines have NAME set to NULL. */
+ char *name;
+
+ /* The value as stored in the file. We store it when we parse
+ * a file so that we can reproduce it. */
+ gpgrt_strlist_t raw_value;
+
+ /* The decoded value. */
+ char *value;
+};
+
+
+/* This macro is a simple ascii only versions of the standard (and
+ * locale affected) isspace(3). This macro does not consider \v and
+ * \f as white space not any non-ascii character. */
+#define ascii_isspace(a) ((a)==' ' || (a)=='\n' || (a)=='\r' || (a)=='\t')
+
+/* Other simple macros. */
+#define spacep(p) (*(p) == ' ' || *(p) == '\t')
+#define digitp(p) (*(p) >= '0' && *(p) <= '9')
+#define alphap(p) ((*(p) >= 'A' && *(p) <= 'Z') \
+ || (*(p) >= 'a' && *(p) <= 'z'))
+#define alnump(p) (alphap (p) || digitp (p))
+
+
+static GPGRT_INLINE int
+ascii_toupper (int c)
+{
+ if (c >= 'a' && c <= 'z')
+ c &= ~0x20;
+ return c;
+}
+
+
+static int
+ascii_strcasecmp (const char *a, const char *b)
+{
+ if (a == b)
+ return 0;
+
+ for (; *a && *b; a++, b++)
+ {
+ if (*a != *b && ascii_toupper(*a) != ascii_toupper(*b))
+ break;
+ }
+ return *a == *b? 0 : (ascii_toupper (*a) - ascii_toupper (*b));
+}
+
+
+
+
+/* Allocate a name value container structure. */
+gpgrt_nvc_t
+_gpgrt_nvc_new (unsigned int flags)
+{
+ gpgrt_nvc_t nvc;
+
+ nvc = xtrycalloc (1, sizeof *nvc);
+ if (!nvc)
+ return NULL;
+ nvc->modified = 1;
+ if ((flags & GPGRT_NVC_PRIVKEY))
+ {
+ nvc->wipe_on_free = 1;
+ nvc->private_key_mode = 1;
+ }
+ else if ((flags & GPGRT_NVC_WIPE))
+ nvc->wipe_on_free = 1;
+
+ return nvc;
+}
+
+
+/* Helper to free an entry. */
+static void
+nve_release (gpgrt_nve_t entry, int with_wipe)
+{
+ if (!entry)
+ return;
+
+ xfree (entry->name);
+ if (entry->value && with_wipe)
+ _gpgrt_wipememory (entry->value, strlen (entry->value));
+ xfree (entry->value);
+ _gpgrt_strlist_free (entry->raw_value);
+ xfree (entry);
+}
+
+
+/* Release a name value container. */
+void
+_gpgrt_nvc_release (gpgrt_nvc_t cont)
+{
+ gpgrt_nve_t e, next;
+
+ if (!cont)
+ return;
+
+ for (e = cont->first; e; e = next)
+ {
+ next = e->next;
+ nve_release (e, cont->wipe_on_free);
+ }
+
+ xfree (cont);
+}
+
+
+/* Return true if the specified flag is set. Only one bit should be
+ * set in FLAGS. If the GPGRT_NVC_MODIFIED flag is requested and
+ * CLEAR is set, that flag is cleared; note that this modified flag is
+ * set for a new container and set with each update. */
+int
+_gpgrt_nvc_get_flag (gpgrt_nvc_t cont, unsigned int flags, int clear)
+{
+ int ret = 0;
+
+ if (!cont)
+ ;
+ else if ((flags & GPGRT_NVC_MODIFIED))
+ {
+ ret = cont->modified;
+ if (clear)
+ cont->modified = 0;
+ }
+ else if ((flags & GPGRT_NVC_PRIVKEY))
+ ret = cont->private_key_mode;
+ else if ((flags & GPGRT_NVC_WIPE))
+ ret = cont->wipe_on_free;
+
+ return !!ret;
+}
+
+
+
+/* Dealing with names and values. */
+
+/* Check whether the given name is valid. Valid names start with a
+ * letter, end with a colon, and contain only alphanumeric characters
+ * and the hyphen. Returns true if NAME is valid. */
+static int
+valid_name (const char *name)
+{
+ size_t i;
+ size_t len = strlen (name);
+
+ if (! alphap (name) || !len || name[len - 1] != ':')
+ return 0;
+
+ for (i = 1; i < len - 1; i++)
+ if (!alnump (name + i) && name[i] != '-')
+ return 0;
+
+ return 1;
+}
+
+
+/* Makes sure that ENTRY has a RAW_VALUE. */
+static gpg_err_code_t
+assert_raw_value (gpgrt_nve_t entry)
+{
+ gpg_err_code_t err = 0;
+ size_t len, offset;
+#define LINELEN 70
+ char buf[LINELEN+3];
+
+ if (entry->raw_value)
+ return 0;
+
+ len = strlen (entry->value);
+ offset = 0;
+ while (len)
+ {
+ size_t amount, linelen = LINELEN;
+
+ /* On the first line we need to subtract space for the name. */
+ if (entry->raw_value == NULL && strlen (entry->name) < linelen)
+ linelen -= strlen (entry->name);
+
+ /* See if the rest of the value fits in this line. */
+ if (len <= linelen)
+ amount = len;
+ else
+ {
+ size_t i;
+
+ /* Find a suitable space to break on. */
+ for (i = linelen - 1; linelen - i < 30; i--)
+ if (ascii_isspace (entry->value[offset+i]))
+ break;
+
+ if (ascii_isspace (entry->value[offset+i]))
+ {
+ /* Found one. */
+ amount = i;
+ }
+ else
+ {
+ /* Just induce a hard break. */
+ amount = linelen;
+ }
+ }
+
+ snprintf (buf, sizeof buf, " %.*s\n", (int) amount,
+ &entry->value[offset]);
+ if (!_gpgrt_strlist_add (&entry->raw_value, buf,
+ (GPGRT_STRLIST_APPEND
+ | (entry->wipe_on_free? GPGRT_STRLIST_WIPE:0))))
+ {
+ err = _gpg_err_code_from_syserror ();
+ goto leave;
+ }
+
+ offset += amount;
+ len -= amount;
+ }
+
+ leave:
+ if (err)
+ {
+ _gpgrt_strlist_free (entry->raw_value);
+ entry->raw_value = NULL;
+ }
+
+ return err;
+#undef LINELEN
+}
+
+
+/* Computes the length of the value encoded as continuation. If
+ * *SWALLOW_WS is set, all whitespace at the beginning of S is
+ * swallowed. If START is given, a pointer to the beginning of the
+ * value is stored there. */
+static size_t
+continuation_length (const char *s, int *swallow_ws, const char **start)
+{
+ size_t len;
+
+ if (*swallow_ws)
+ {
+ /* The previous line was a blank line and we inserted a newline.
+ Swallow all whitespace at the beginning of this line. */
+ while (ascii_isspace (*s))
+ s++;
+ }
+ else
+ {
+ /* Iff a continuation starts with more than one space, it
+ encodes a space. */
+ if (ascii_isspace (*s))
+ s++;
+ }
+
+ /* Strip whitespace at the end. */
+ len = strlen (s);
+ while (len > 0 && ascii_isspace (s[len-1]))
+ len--;
+
+ if (!len)
+ {
+ /* Blank lines encode newlines. */
+ len = 1;
+ s = "\n";
+ *swallow_ws = 1;
+ }
+ else
+ *swallow_ws = 0;
+
+ if (start)
+ *start = s;
+
+ return len;
+}
+
+
+/* Makes sure that ENTRY has a VALUE. */
+static gpg_err_code_t
+assert_value (gpgrt_nve_t entry)
+{
+ size_t len;
+ int swallow_ws;
+ gpgrt_strlist_t s;
+ char *p;
+
+ if (entry->value)
+ return 0;
+
+ len = 0;
+ swallow_ws = 0;
+ for (s = entry->raw_value; s; s = s->next)
+ len += continuation_length (s->d, &swallow_ws, NULL);
+
+ /* Add one for the terminating zero. */
+ len += 1;
+
+ entry->value = p = xtrymalloc (len);
+ if (!entry->value)
+ return _gpg_err_code_from_syserror ();
+
+ swallow_ws = 0;
+ for (s = entry->raw_value; s; s = s->next)
+ {
+ const char *start;
+ size_t l = continuation_length (s->d, &swallow_ws, &start);
+
+ memcpy (p, start, l);
+ p += l;
+ }
+
+ *p++ = 0;
+ gpgrt_assert (p - entry->value == len);
+
+ return 0;
+}
+
+
+/* Get the name. */
+const char *
+_gpgrt_nve_name (gpgrt_nve_t entry)
+{
+ return entry->name;
+}
+
+
+/* Get the value. */
+const char *
+_gpgrt_nve_value (gpgrt_nve_t entry)
+{
+ if (assert_value (entry))
+ return NULL;
+ return entry->value;
+}
+
+
+
+/* Adding and modifying values. */
+
+/* Add (NAME, VALUE, RAW_VALUE) to CONT. NAME may be NULL for comments
+ * and blank lines. At least one of VALUE and RAW_VALUE must be
+ * given. If PRESERVE_ORDER is not given, entries with the same name
+ * are grouped. NAME, VALUE and RAW_VALUE is consumed. */
+static gpg_err_code_t
+do_nvc_add (gpgrt_nvc_t cont, char *name, char *value,
+ gpgrt_strlist_t raw_value, int preserve_order)
+{
+ gpg_err_code_t err = 0;
+ gpgrt_nve_t e;
+
+ gpgrt_assert (value || raw_value);
+
+ if (name && ! valid_name (name))
+ {
+ err = GPG_ERR_INV_NAME;
+ goto leave;
+ }
+
+ if (name
+ && cont->private_key_mode
+ && !ascii_strcasecmp (name, "Key:")
+ && _gpgrt_nvc_lookup (cont, "Key:"))
+ {
+ err = GPG_ERR_INV_NAME;
+ goto leave;
+ }
+
+ e = xtrycalloc (1, sizeof *e);
+ if (!e)
+ {
+ err = _gpg_err_code_from_syserror ();
+ goto leave;
+ }
+
+ e->name = name;
+ e->value = value;
+ e->raw_value = raw_value;
+ e->wipe_on_free = cont->wipe_on_free;
+
+ if (cont->first)
+ {
+ gpgrt_nve_t last;
+
+ if (preserve_order || !name)
+ last = cont->last;
+ else
+ {
+ /* See if there is already an entry with NAME. */
+ last = _gpgrt_nvc_lookup (cont, name);
+
+ /* If so, find the last in that block. */
+ if (last)
+ {
+ while (last->next)
+ {
+ gpgrt_nve_t next = last->next;
+
+ if (next->name && !ascii_strcasecmp (next->name, name))
+ last = next;
+ else
+ break;
+ }
+ }
+ else /* Otherwise, just find the last entry. */
+ last = cont->last;
+ }
+
+ if (last->next)
+ {
+ e->prev = last;
+ e->next = last->next;
+ last->next = e;
+ e->next->prev = e;
+ }
+ else
+ {
+ e->prev = last;
+ last->next = e;
+ cont->last = e;
+ }
+ }
+ else
+ cont->first = cont->last = e;
+
+ cont->modified = 1;
+
+ leave:
+ if (err)
+ {
+ xfree (name);
+ if (value && cont->wipe_on_free)
+ _gpgrt_wipememory (value, strlen (value));
+ xfree (value);
+ _gpgrt_strlist_free (raw_value);
+ }
+
+ return err;
+}
+
+
+/* Add (NAME, VALUE) to CONT. If an entry with NAME already exists, it
+ * is not updated but the new entry is appended. */
+gpg_err_code_t
+_gpgrt_nvc_add (gpgrt_nvc_t cont, const char *name, const char *value)
+{
+ char *k, *v;
+
+ k = xtrystrdup (name);
+ if (!k)
+ return _gpg_err_code_from_syserror ();
+
+ v = xtrystrdup (value);
+ if (!v)
+ {
+ xfree (k);
+ return _gpg_err_code_from_syserror ();
+ }
+
+ return do_nvc_add (cont, k, v, NULL, 0);
+}
+
+
+/* Add (NAME, VALUE) to CONT. If an entry with NAME already exists, it
+ * is updated with VALUE. If multiple entries with NAME exist, the
+ * first entry is updated. */
+gpg_err_code_t
+_gpgrt_nvc_set (gpgrt_nvc_t cont, const char *name, const char *value)
+{
+ gpgrt_nve_t e;
+
+ if (! valid_name (name))
+ return GPG_ERR_INV_NAME;
+
+ e = _gpgrt_nvc_lookup (cont, name);
+ if (e)
+ return _gpgrt_nve_set (cont, e, value);
+ else
+ return _gpgrt_nvc_add (cont, name, value);
+}
+
+
+/* Update entry E to VALUE. CONT is optional; if given its modified
+ * flag will be updated. */
+gpg_err_code_t
+_gpgrt_nve_set (gpgrt_nvc_t cont, gpgrt_nve_t e, const char *value)
+{
+ char *v;
+
+ if (!e)
+ return GPG_ERR_INV_ARG;
+
+ if (e->value && value && !strcmp (e->value, value))
+ {
+ /* Setting same value - ignore this call and don't set the
+ * modified flag (if CONT is given). */
+ return 0;
+ }
+
+ v = xtrystrdup (value? value:"");
+ if (!v)
+ return _gpg_err_code_from_syserror ();
+
+ _gpgrt_strlist_free (e->raw_value);
+ e->raw_value = NULL;
+ if (e->value)
+ _gpgrt_wipememory (e->value, strlen (e->value));
+ xfree (e->value);
+ e->value = v;
+ if (cont)
+ cont->modified = 1;
+
+ return 0;
+}
+
+
+/* Delete the given ENTRY from the container CONT. */
+static void
+do_nvc_delete (gpgrt_nvc_t cont, gpgrt_nve_t entry)
+{
+ if (entry->prev)
+ entry->prev->next = entry->next;
+ else
+ cont->first = entry->next;
+
+ if (entry->next)
+ entry->next->prev = entry->prev;
+ else
+ cont->last = entry->prev;
+
+ nve_release (entry, cont->private_key_mode);
+ cont->modified = 1;
+}
+
+
+/* Delete entries from the container CONT. Either ENTRY or NAME may
+ * be given. IF Entry is given only this entry is removed. if NAME
+ * is given all entries with this name are deleted. */
+void
+_gpgrt_nvc_delete (gpgrt_nvc_t cont, gpgrt_nve_t entry, const char *name)
+{
+ if (entry)
+ do_nvc_delete (cont, entry);
+ else if (valid_name (name))
+ {
+ while ((entry = _gpgrt_nvc_lookup (cont, name)))
+ do_nvc_delete (cont, entry);
+ }
+}
+
+
+
+/* Lookup and iteration. */
+
+/* Get the first entry with the given name. Return NULL if it does
+ * not exist. This function allows to specify NAME without the
+ * trailing colon as long as name is not too long. If NAME is NULL
+ * the first non-comment entry is returned. */
+gpgrt_nve_t
+_gpgrt_nvc_lookup (gpgrt_nvc_t cont, const char *name)
+{
+ gpgrt_nve_t entry;
+ size_t n;
+ char buffer[40];
+
+ if (!cont)
+ return NULL;
+
+ if (!name)
+ {
+ for (entry = cont->first; entry; entry = entry->next)
+ if (entry->name)
+ return entry;
+
+ return NULL;
+ }
+
+ if ((n=strlen (name)) && name[n-1] != ':' && n + 2 < sizeof buffer)
+ {
+ strcpy (stpcpy (buffer, name), ":");
+ name = buffer;
+ }
+
+ for (entry = cont->first; entry; entry = entry->next)
+ if (entry->name && !ascii_strcasecmp (entry->name, name))
+ return entry;
+
+ return NULL;
+}
+
+
+/* Get the next non-comment entry. If NAME is given the next entry
+ * with that name is returned. */
+gpgrt_nve_t
+_gpgrt_nve_next (gpgrt_nve_t entry, const char *name)
+{
+ if (!entry)
+ return NULL;
+
+ if (name)
+ {
+ for (entry = entry->next; entry; entry = entry->next)
+ if (entry->name && !ascii_strcasecmp (entry->name, name))
+ return entry;
+ }
+ else
+ {
+ for (entry = entry->next; entry; entry = entry->next)
+ if (entry->name)
+ return entry;
+ }
+ return NULL;
+}
+
+
+
+/* Parsing and serialization. */
+
+static gpg_err_code_t
+do_nvc_parse (gpgrt_nvc_t *result, int *errlinep, estream_t stream,
+ unsigned int flags)
+{
+ gpg_err_code_t err = 0;
+ gpgrt_ssize_t len;
+ char *buf = NULL;
+ size_t buf_len = 0;
+ char *name = NULL;
+ gpgrt_strlist_t raw_value = NULL;
+ unsigned int slflags;
+
+ *result = _gpgrt_nvc_new (flags);
+ if (!*result)
+ return _gpg_err_code_from_syserror ();
+
+ slflags = (GPGRT_STRLIST_APPEND
+ | ((flags & GPGRT_NVC_WIPE)? GPGRT_STRLIST_WIPE:0));
+
+ if (errlinep)
+ *errlinep = 0;
+ while ((len = _gpgrt_read_line (stream, &buf, &buf_len, NULL)) > 0)
+ {
+ char *p;
+ if (errlinep)
+ *errlinep += 1;
+
+ /* Skip any whitespace. */
+ for (p = buf; *p && ascii_isspace (*p); p++)
+ /* Do nothing. */;
+
+ if (name && (spacep (buf) || !*p))
+ {
+ /* A continuation. */
+ if (!_gpgrt_strlist_add (&raw_value, buf, slflags))
+ {
+ err = _gpg_err_code_from_syserror ();
+ goto leave;
+ }
+ continue;
+ }
+
+ /* No continuation. Add the current entry if any. */
+ if (raw_value)
+ {
+ err = do_nvc_add (*result, name, NULL,
+ raw_value, 1 /*preserve order*/);
+ name = NULL;
+ if (err)
+ goto leave;
+ }
+
+ /* And prepare for the next one. */
+ name = NULL;
+ raw_value = NULL;
+
+ if (*p != 0 && *p != '#')
+ {
+ char *colon, *value, tmp;
+
+ colon = strchr (buf, ':');
+ if (!colon)
+ {
+ err = GPG_ERR_INV_VALUE;
+ goto leave;
+ }
+
+ value = colon + 1;
+ tmp = *value;
+ *value = 0;
+ name = xtrystrdup (p);
+ *value = tmp;
+ if (!name)
+ {
+ err = _gpg_err_code_from_syserror ();
+ goto leave;
+ }
+
+ if (!_gpgrt_strlist_add (&raw_value, value, slflags))
+ {
+ err = _gpg_err_code_from_syserror ();
+ goto leave;
+ }
+ continue;
+ }
+
+ if (!_gpgrt_strlist_add (&raw_value, buf, slflags))
+ {
+ err = _gpg_err_code_from_syserror ();
+ goto leave;
+ }
+ }
+ if (len < 0)
+ {
+ err = _gpg_err_code_from_syserror ();
+ goto leave;
+ }
+
+ /* Add the final entry. */
+ if (raw_value)
+ {
+ err = do_nvc_add (*result, name, NULL, raw_value, 1 /*preserve order*/);
+ name = NULL;
+ }
+
+ leave:
+ xfree (name);
+ xfree (buf);
+ if (err)
+ {
+ _gpgrt_nvc_release (*result);
+ *result = NULL;
+ }
+
+ return err;
+}
+
+
+/* Parse STREAM and return a newly allocated name value container
+ * structure in RESULT. If ERRLINEP is given, the line number the
+ * parser was last considering is stored there. For private key
+ * containers the GPGRT_NVC_PRIVKEY bit should be set in FLAGS. */
+gpg_err_code_t
+_gpgrt_nvc_parse (gpgrt_nvc_t *result, int *errlinep, estream_t stream,
+ unsigned int flags)
+{
+ return do_nvc_parse (result, errlinep, stream, flags);
+}
+
+
+/* Helper for nvc_write. */
+static gpg_err_code_t
+write_one_entry (gpgrt_nve_t entry, estream_t stream)
+{
+ gpg_err_code_t err;
+ gpgrt_strlist_t sl;
+
+ if (entry->name)
+ _gpgrt_fputs (entry->name, stream);
+
+ err = assert_raw_value (entry);
+ if (err)
+ return err;
+
+ for (sl = entry->raw_value; sl; sl = sl->next)
+ _gpgrt_fputs (sl->d, stream);
+
+ if (_gpgrt_ferror (stream))
+ return _gpg_err_code_from_syserror ();
+
+ return 0;
+}
+
+
+/* Write a representation of CONT to STREAM. */
+gpg_err_code_t
+_gpgrt_nvc_write (gpgrt_nvc_t cont, estream_t stream)
+{
+ gpg_err_code_t err = 0;
+ gpgrt_nve_t entry;
+ gpgrt_nve_t keyentry = NULL;
+
+ for (entry = cont->first; entry; entry = entry->next)
+ {
+ if (cont->private_key_mode
+ && entry->name && !ascii_strcasecmp (entry->name, "Key:"))
+ {
+ if (!keyentry)
+ keyentry = entry;
+ continue;
+ }
+
+ err = write_one_entry (entry, stream);
+ if (err)
+ return err;
+ }
+
+ /* In private key mode we write the Key always last. */
+ if (keyentry)
+ err = write_one_entry (keyentry, stream);
+
+ return err;
+}
+
+
+
+/*
+ * Convenience functions.
+ */
+
+/* Return the string for the first entry in NVC with NAME. If an
+ * entry with NAME is missing in NVC or its value is the empty string
+ * NULL is returned. Note that the returned string is a pointer
+ * into NVC. */
+const char *
+_gpgrt_nvc_get_string (gpgrt_nvc_t nvc, const char *name)
+{
+ gpgrt_nve_t item;
+
+ if (!nvc)
+ return NULL;
+ item = _gpgrt_nvc_lookup (nvc, name);
+ if (!item)
+ return NULL;
+ return _gpgrt_nve_value (item);
+}
+
+
+/* Return true (ie. a non-zero value) if NAME exists and its value is
+ * true; that is either "yes", "true", or a decimal value unequal to 0. */
+int
+_gpgrt_nvc_get_bool (gpgrt_nvc_t nvc, const char *name)
+{
+ gpgrt_nve_t item;
+ const char *s;
+ int n;
+
+ if (!nvc)
+ return 0;
+ item = _gpgrt_nvc_lookup (nvc, name);
+ if (!item)
+ return 0;
+ s = _gpgrt_nve_value (item);
+ if (!s)
+ return 0;
+ n = atoi (s);
+ if (n)
+ return n;
+ if (!ascii_strcasecmp (s, "yes") || !ascii_strcasecmp (s, "true"))
+ return 1;
+ return 0;
+}
diff --git a/src/visibility.c b/src/visibility.c
index 367b6ef..54c4e2a 100644
--- a/src/visibility.c
+++ b/src/visibility.c
@@ -1402,6 +1402,98 @@ gpgrt_strlist_find (gpgrt_strlist_t haystack, const char *needle)
}
+
+gpgrt_nvc_t
+gpgrt_nvc_new (unsigned int flags)
+{
+ return _gpgrt_nvc_new (flags);
+}
+
+void
+gpgrt_nvc_release (gpgrt_nvc_t cont)
+{
+ _gpgrt_nvc_release (cont);
+}
+
+int
+gpgrt_nvc_get_flag (gpgrt_nvc_t cont, unsigned int flags, int clear)
+{
+ return _gpgrt_nvc_get_flag (cont, flags, clear);
+}
+
+gpg_err_code_t
+gpgrt_nvc_add (gpgrt_nvc_t cont, const char *name, const char *value)
+{
+ return _gpgrt_nvc_add (cont, name, value);
+}
+
+gpg_err_code_t
+gpgrt_nvc_set (gpgrt_nvc_t cont, const char *name, const char *value)
+{
+ return _gpgrt_nvc_set (cont, name, value);
+}
+
+gpg_err_code_t
+gpgrt_nve_set (gpgrt_nvc_t cont, gpgrt_nve_t e, const char *value)
+{
+ return _gpgrt_nve_set (cont, e, value);
+}
+
+void
+gpgrt_nvc_delete (gpgrt_nvc_t cont, gpgrt_nve_t entry, const char *name)
+{
+ _gpgrt_nvc_delete (cont, entry, name);
+}
+
+gpgrt_nve_t
+gpgrt_nvc_lookup (gpgrt_nvc_t cont, const char *name)
+{
+ return _gpgrt_nvc_lookup (cont, name);
+}
+
+gpg_err_code_t
+gpgrt_nvc_parse (gpgrt_nvc_t *result, int *errlinep,
+ estream_t stream, unsigned int flags)
+{
+ return _gpgrt_nvc_parse (result, errlinep, stream, flags);
+}
+
+gpg_err_code_t
+gpgrt_nvc_write (gpgrt_nvc_t cont, estream_t stream)
+{
+ return _gpgrt_nvc_write (cont, stream);
+}
+
+gpgrt_nve_t
+gpgrt_nve_next (gpgrt_nve_t entry, const char *name)
+{
+ return _gpgrt_nve_next (entry, name);
+}
+
+const char *
+gpgrt_nve_name (gpgrt_nve_t entry)
+{
+ return _gpgrt_nve_name (entry);
+}
+
+const char *
+gpgrt_nve_value (gpgrt_nve_t entry)
+{
+ return _gpgrt_nve_value (entry);
+}
+
+const char *
+gpgrt_nvc_get_string (gpgrt_nvc_t nvc, const char *name)
+{
+ return _gpgrt_nvc_get_string (nvc, name);
+}
+
+int
+gpgrt_nvc_get_bool (gpgrt_nvc_t nvc, const char *name)
+{
+ return _gpgrt_nvc_get_bool (nvc, name);
+}
+
/* For consistency reasons we use function wrappers also for Windows
diff --git a/src/visibility.h b/src/visibility.h
index d466374..8729c01 100644
--- a/src/visibility.h
+++ b/src/visibility.h
@@ -239,6 +239,22 @@ MARK_VISIBLE (gpgrt_strlist_last)
MARK_VISIBLE (gpgrt_strlist_pop)
MARK_VISIBLE (gpgrt_strlist_find)
+MARK_VISIBLE (gpgrt_nvc_new)
+MARK_VISIBLE (gpgrt_nvc_release)
+MARK_VISIBLE (gpgrt_nvc_get_flag)
+MARK_VISIBLE (gpgrt_nvc_add)
+MARK_VISIBLE (gpgrt_nvc_set)
+MARK_VISIBLE (gpgrt_nve_set)
+MARK_VISIBLE (gpgrt_nvc_delete)
+MARK_VISIBLE (gpgrt_nvc_lookup)
+MARK_VISIBLE (gpgrt_nvc_parse)
+MARK_VISIBLE (gpgrt_nvc_write)
+MARK_VISIBLE (gpgrt_nve_next)
+MARK_VISIBLE (gpgrt_nve_name)
+MARK_VISIBLE (gpgrt_nve_value)
+MARK_VISIBLE (gpgrt_nvc_get_string)
+MARK_VISIBLE (gpgrt_nvc_get_bool)
+
MARK_VISIBLE (gpgrt_spawn_actions_new)
MARK_VISIBLE (gpgrt_spawn_actions_release)
MARK_VISIBLE (gpgrt_spawn_actions_set_env_rev)
@@ -469,6 +485,22 @@ MARK_VISIBLE (gpgrt_spawn_actions_set_atfork)
#define gpgrt_strlist_pop _gpgrt_USE_UNDERSCORED_FUNCTION
#define gpgrt_strlist_find _gpgrt_USE_UNDERSCORED_FUNCTION
+#define gpgrt_nvc_new _gpgrt_USE_UNDERSCORED_FUNCTION
+#define gpgrt_nvc_release _gpgrt_USE_UNDERSCORED_FUNCTION
+#define gpgrt_nvc_get_flag _gpgrt_USE_UNDERSCORED_FUNCTION
+#define gpgrt_nvc_add _gpgrt_USE_UNDERSCORED_FUNCTION
+#define gpgrt_nvc_set _gpgrt_USE_UNDERSCORED_FUNCTION
+#define gpgrt_nve_set _gpgrt_USE_UNDERSCORED_FUNCTION
+#define gpgrt_nvc_delete _gpgrt_USE_UNDERSCORED_FUNCTION
+#define gpgrt_nvc_lookup _gpgrt_USE_UNDERSCORED_FUNCTION
+#define gpgrt_nvc_parse _gpgrt_USE_UNDERSCORED_FUNCTION
+#define gpgrt_nvc_write _gpgrt_USE_UNDERSCORED_FUNCTION
+#define gpgrt_nve_next _gpgrt_USE_UNDERSCORED_FUNCTION
+#define gpgrt_nve_name _gpgrt_USE_UNDERSCORED_FUNCTION
+#define gpgrt_nve_value _gpgrt_USE_UNDERSCORED_FUNCTION
+#define gpgrt_nvc_get_string _gpgrt_USE_UNDERSCORED_FUNCTION
+#define gpgrt_nvc_get_bool _gpgrt_USE_UNDERSCORED_FUNCTION
+
/* Windows specific functions. */
#define gpgrt_free_wchar _gpgrt_USE_UNDERSCORED_FUNCTION
#define gpgrt_utf8_to_wchar _gpgrt_USE_UNDERSCORED_FUNCTION