diff options
-rw-r--r-- | NEWS | 22 | ||||
-rw-r--r-- | src/Makefile.am | 1 | ||||
-rw-r--r-- | src/gpg-error.def.in | 16 | ||||
-rw-r--r-- | src/gpg-error.h.in | 81 | ||||
-rw-r--r-- | src/gpg-error.vers | 16 | ||||
-rw-r--r-- | src/gpgrt-int.h | 26 | ||||
-rw-r--r-- | src/name-value.c | 895 | ||||
-rw-r--r-- | src/visibility.c | 92 | ||||
-rw-r--r-- | src/visibility.h | 32 | ||||
-rw-r--r-- | tests/Makefile.am | 3 | ||||
-rw-r--r-- | tests/t-name-value.c | 581 |
11 files changed, 1760 insertions, 5 deletions
@@ -8,6 +8,8 @@ Noteworthy changes in version 1.52 (unreleased) [C38/A38/R_] * New simple string list API. + * New API for name value files. + * Interface changes relative to the 1.51 release: ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ gpgrt_w32_reg_query_string NEW (Windows only). @@ -23,6 +25,26 @@ Noteworthy changes in version 1.52 (unreleased) [C38/A38/R_] gpgrt_strlist_find NEW. GPGRT_STRLIST_APPEND NEW const. GPGRT_STRLIST_WIPE NEW const. + gpgrt_nvc_t NEW type. + gpgrt_nve_t NEW type. + gpgrt_nvc_new NEW. + gpgrt_nvc_release NEW. + gpgrt_nvc_get_flag NEW. + gpgrt_nvc_add NEW. + gpgrt_nvc_set NEW. + gpgrt_nve_set NEW. + gpgrt_nvc_delete NEW. + gpgrt_nvc_lookup NEW. + gpgrt_nvc_parse NEW. + gpgrt_nvc_write NEW. + gpgrt_nve_next NEW. + gpgrt_nve_name NEW. + gpgrt_nve_value NEW. + gpgrt_nvc_get_string NEW. + gpgrt_nvc_get_bool NEW. + GPGRT_NVC_WIPE NEW const. + GPGRT_NVC_PRIVKEY NEW const. + GPGRT_NVC_MODIFIED NEW const. Release-info: https://dev.gnupg.org/T7385 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 diff --git a/tests/Makefile.am b/tests/Makefile.am index e96f60e..abe57b3 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -24,7 +24,8 @@ EXTRA_DIST = t-argparse.conf etc/t-argparse.conf gpg_error_lib = ../src/libgpg-error.la TESTS = t-version t-strerror t-syserror t-lock t-printf t-poll t-b64 \ - t-argparse t-logging t-stringutils t-malloc t-spawn t-strlist + t-argparse t-logging t-stringutils t-malloc t-spawn t-strlist \ + t-name-value if HAVE_LOCK_OPTIMIZATION TESTS += t-lock-single-posix diff --git a/tests/t-name-value.c b/tests/t-name-value.c new file mode 100644 index 0000000..7a28bf4 --- /dev/null +++ b/tests/t-name-value.c @@ -0,0 +1,581 @@ +/* t-name-value.c - Check name-value functions + * Copyright (C) 2015, 2025 g10 Code GmbH + * + * This file is part of Libgpg-error. + * + * Libgpg-error 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. + * + * Libgpg-error 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 program; if not, see <https://www.gnu.org/licenses/>. + * SPDX-License-Identifier: LGPL-2.1-or-later + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#define PGM "t-name-value" +#include "t-common.h" + + +static void test_getting_values (gpgrt_nvc_t cont); +static void test_key_extraction (gpgrt_nvc_t pk); +static void test_iteration (gpgrt_nvc_t pk); +static void test_whitespace (gpgrt_nvc_t pk); + +static int private_key_mode; + +static struct +{ + char *value; + void (*test_func) (gpgrt_nvc_t); +} tests[] = + { + { + "# This is a comment followed by an empty line\n" + "\n", + NULL + }, + { + "# This is a comment followed by two empty lines, Windows style\r\n" + "\r\n" + "\r\n", + NULL + }, + { + "# Some name,value pairs\n" + "Comment: Some comment.\n" + "SomeOtherName: Some value.\n", + test_getting_values + }, + { + " # Whitespace is preserved as much as possible\r\n" + "Comment:Some comment.\n" + "SomeOtherName: Some value. \n", + test_getting_values + }, + { + "# Values may be continued in the next line as indicated by leading\n" + "# space\n" + "Comment: Some rather long\n" + " comment that is continued in the next line.\n" + "\n" + " Blank lines with or without whitespace are allowed within\n" + " continuations to allow paragraphs.\n" + "SomeOtherName: Some value.\n", + test_getting_values + }, + { + "# Names may be given multiple times forming an array of values\n" + "Comment: Some comment, element 0.\n" + "Comment: Some comment, element 1.\n" + "Comment: Some comment, element 2.\n" + "SomeOtherName: Some value.\n", + test_iteration + }, + { + "# One whitespace at the beginning of a continuation is swallowed.\n" + "One: Without\n" + " Whitespace\n" + "Two: With\n" + " Whitespace\n" + "Three: Blank lines in continuations encode newlines.\n" + "\n" + " Next paragraph.\n", + test_whitespace + }, + { + "Description: Key to sign all GnuPG released tarballs.\n" + " The key is actually stored on a smart card.\n" + "Use-for-ssh: yes\n" + "OpenSSH-cert: long base64 encoded string wrapped so that this\n" + " key file can be easily edited with a standard editor.\n" + "Key: (shadowed-private-key\n" + " (rsa\n" + " (n #00AA1AD2A55FD8C8FDE9E1941772D9CC903FA43B268CB1B5A1BAFDC900\n" + " 2961D8AEA153424DC851EF13B83AC64FBE365C59DC1BD3E83017C90D4365B4\n" + " 83E02859FC13DB5842A00E969480DB96CE6F7D1C03600392B8E08EF0C01FC7\n" + " 19F9F9086B25AD39B4F1C2A2DF3E2BE317110CFFF21D4A11455508FE407997\n" + " 601260816C8422297C0637BB291C3A079B9CB38A92CE9E551F80AA0EBF4F0E\n" + " 72C3F250461E4D31F23A7087857FC8438324A013634563D34EFDDCBF2EA80D\n" + " F9662C9CCD4BEF2522D8BDFED24CEF78DC6B309317407EAC576D889F88ADA0\n" + " 8C4FFB480981FB68C5C6CA27503381D41018E6CDC52AAAE46B166BDC10637A\n" + " E186A02BA2497FDC5D1221#)\n" + " (e #00010001#)\n" + " (shadowed t1-v1\n" + " (#D2760001240102000005000011730000# OPENPGP.1)\n" + " )))\n", + test_key_extraction + } + }; + + + +static void +test_getting_values (gpgrt_nvc_t pk) +{ + gpgrt_nve_t e; + + e = gpgrt_nvc_lookup (pk, "Comment:"); + gpgrt_assert (e); + + /* Names are case-insensitive. */ + e = gpgrt_nvc_lookup (pk, "comment:"); + gpgrt_assert (e); + e = gpgrt_nvc_lookup (pk, "COMMENT:"); + gpgrt_assert (e); + + e = gpgrt_nvc_lookup (pk, "SomeOtherName:"); + gpgrt_assert (e); +} + + +static void +test_key_extraction (gpgrt_nvc_t pk) +{ + const char *key; + + /* Note that the original test in gnupg used the gnupg specific + * private key extraction function which works only in + * private_key_mode. We can't do this here so we do a mere + * extraction test. */ + key = gpgrt_nve_value (gpgrt_nvc_lookup (pk, "Key:")); + gpgrt_assert (key); + + if (verbose) + fprintf (stdout, "->%s<-\n", key); +} + + +static void +test_iteration (gpgrt_nvc_t pk) +{ + int i; + gpgrt_nve_t e; + + i = 0; + for (e = gpgrt_nvc_lookup (pk, NULL); e; e = gpgrt_nve_next (e, NULL)) + i++; + gpgrt_assert (i == 4); + + i = 0; + for (e = gpgrt_nvc_lookup (pk, "Comment:"); + e; + e = gpgrt_nve_next (e, "Comment:")) + i++; + gpgrt_assert (i == 3); +} + + +static void +test_whitespace (gpgrt_nvc_t pk) +{ + gpgrt_nve_t e; + + e = gpgrt_nvc_lookup (pk, "One:"); + gpgrt_assert (e); + gpgrt_assert (strcmp (gpgrt_nve_value (e), "WithoutWhitespace") == 0); + + e = gpgrt_nvc_lookup (pk, "Two:"); + gpgrt_assert (e); + gpgrt_assert (strcmp (gpgrt_nve_value (e), "With Whitespace") == 0); + + e = gpgrt_nvc_lookup (pk, "Three:"); + gpgrt_assert (e); + gpgrt_assert (strcmp (gpgrt_nve_value (e), + "Blank lines in continuations encode newlines.\n" + "Next paragraph.") == 0); +} + + +/* Convert container PK to a string. Terminates on error. */ +static char * +nvc_to_string (gpgrt_nvc_t pk) +{ + gpg_error_t err; + char *buf; + size_t len; + estream_t sink; + + sink = es_fopenmem (0, "rw"); + gpgrt_assert (sink); + + err = gpgrt_nvc_write (pk, sink); + gpgrt_assert (err == 0); + + len = es_ftell (sink); + buf = xmalloc (len+1); + gpgrt_assert (buf); + + es_fseek (sink, 0, SEEK_SET); + es_read (sink, buf, len, NULL); + buf[len] = 0; + + es_fclose (sink); + return buf; +} + + +static void dummy_free (void *p) { (void) p; } +static void *dummy_realloc (void *p, size_t s) { (void) s; return p; } + +/* + * Run the standard test cases. + */ +static void +run_tests (void) +{ + gpg_error_t err; + gpgrt_nvc_t pk; + int i; + + for (i = 0; i < DIM (tests); i++) + { + estream_t source; + char *buf; + size_t len; + + len = strlen (tests[i].value); + source = es_mopen (tests[i].value, len, len, + 0, dummy_realloc, dummy_free, "r"); + gpgrt_assert (source); + + if (private_key_mode) + err = gpgrt_nvc_parse (&pk, NULL, source, GPGRT_NVC_PRIVKEY); + else + err = gpgrt_nvc_parse (&pk, NULL, source, 0); + gpgrt_assert (err == 0); + gpgrt_assert (pk); + + if (verbose) + { + err = gpgrt_nvc_write (pk, es_stderr); + gpgrt_assert (err == 0); + } + + buf = nvc_to_string (pk); + gpgrt_assert (memcmp (tests[i].value, buf, len) == 0); + + es_fclose (source); + xfree (buf); + + if (tests[i].test_func) + tests[i].test_func (pk); + + gpgrt_nvc_release (pk); + } +} + + +/* + * Run tests checking the modification functions. + */ +static void +run_modification_tests (void) +{ + gpg_error_t err; + gpgrt_nvc_t pk; + gpgrt_nve_t e; + char *buf; + + pk = gpgrt_nvc_new (private_key_mode? GPGRT_NVC_PRIVKEY : 0); + gpgrt_assert (pk); + + err = gpgrt_nvc_set (pk, "Foo:", "Bar"); + gpgrt_assert (!err); + buf = nvc_to_string (pk); + gpgrt_assert (strcmp (buf, "Foo: Bar\n") == 0); + xfree (buf); + + err = gpgrt_nvc_set (pk, "Foo:", "Baz"); + if (err) + fail ("gpgrt_nvc_set failed at line %d: %s\n",__LINE__,gpg_strerror (err)); + buf = nvc_to_string (pk); + gpgrt_assert (strcmp (buf, "Foo: Baz\n") == 0); + xfree (buf); + + err = gpgrt_nvc_set (pk, "Bar:", "Bazzel"); + if (err) + fail ("gpgrt_nvc_set failed at line %d: %s\n",__LINE__,gpg_strerror (err)); + buf = nvc_to_string (pk); + gpgrt_assert (strcmp (buf, "Foo: Baz\nBar: Bazzel\n") == 0); + xfree (buf); + + err = gpgrt_nvc_add (pk, "Foo:", "Bar"); + if (err) + fail ("gpgrt_nvc_add failed at line %d: %s\n",__LINE__,gpg_strerror (err)); + buf = nvc_to_string (pk); + gpgrt_assert (strcmp (buf, "Foo: Baz\nFoo: Bar\nBar: Bazzel\n") == 0); + xfree (buf); + + err = gpgrt_nvc_add (pk, "DontExistYet:", "Bar"); + if (err) + fail ("gpgrt_nvc_set failed at line %d: %s\n",__LINE__,gpg_strerror (err)); + buf = nvc_to_string (pk); + gpgrt_assert (strcmp (buf, ("Foo: Baz\nFoo: Bar\n" + "Bar: Bazzel\nDontExistYet: Bar\n")) == 0); + xfree (buf); + + gpgrt_nvc_delete (pk, gpgrt_nvc_lookup (pk, "DontExistYet:"), NULL); + buf = nvc_to_string (pk); + gpgrt_assert (strcmp (buf, "Foo: Baz\nFoo: Bar\nBar: Bazzel\n") == 0); + xfree (buf); + + gpgrt_nvc_delete (pk, gpgrt_nve_next (gpgrt_nvc_lookup (pk, "Foo:"), + "Foo:"), NULL); + buf = nvc_to_string (pk); + gpgrt_assert (strcmp (buf, "Foo: Baz\nBar: Bazzel\n") == 0); + xfree (buf); + + gpgrt_nvc_delete (pk, gpgrt_nvc_lookup (pk, "Foo:"), NULL); + buf = nvc_to_string (pk); + gpgrt_assert (strcmp (buf, "Bar: Bazzel\n") == 0); + xfree (buf); + + gpgrt_nvc_delete (pk, gpgrt_nvc_lookup (pk, NULL), NULL); + buf = nvc_to_string (pk); + gpgrt_assert (strcmp (buf, "") == 0); + xfree (buf); + + /* Test whether we can delete an entry by name. */ + err = gpgrt_nvc_add (pk, "Key:", "(3:foo)"); + if (err) + fail ("gpgrt_nvc_add failed at line %d: %s\n",__LINE__,gpg_strerror (err)); + e = gpgrt_nvc_lookup (pk, "Key:"); + gpgrt_assert (e); + gpgrt_nvc_delete (pk, NULL, "Kez:"); /* Delete an nonexistent name. */ + e = gpgrt_nvc_lookup (pk, "Key:"); + gpgrt_assert (e); + gpgrt_nvc_delete (pk, NULL, "Key:"); + e = gpgrt_nvc_lookup (pk, "Key:"); + gpgrt_assert (!e); + + /* Ditto but now whether it deletes all entries with that name. We + * don't use "Key" because that name is special in private key mode. */ + err = gpgrt_nvc_add (pk, "AKey:", "A-value"); + if (err) + fail ("gpgrt_nvc_add failed at line %d: %s\n",__LINE__,gpg_strerror (err)); + + err = gpgrt_nvc_add (pk, "AKey:", "B-value"); + if (err) + fail ("gpgrt_nvc_add failed at line %d: %s\n",__LINE__,gpg_strerror (err)); + + e = gpgrt_nvc_lookup (pk, "AKey:"); + gpgrt_assert (e); + + gpgrt_nvc_delete (pk, NULL, "AKey:"); + e = gpgrt_nvc_lookup (pk, "AKey:"); + gpgrt_assert (!e); + + err = gpgrt_nvc_set (pk, "Foo:", + "A really long value spanning across multiple lines" + " that has to be wrapped at a convenient space."); + if (err) + fail ("gpgrt_nvc_set failed at line %d: %s\n",__LINE__,gpg_strerror (err)); + buf = nvc_to_string (pk); + gpgrt_assert (strcmp (buf, "Foo: A really long value spanning across multiple" + " lines that has to be\n wrapped at a convenient space.\n") + == 0); + xfree (buf); + + err = gpgrt_nvc_set (pk, "Foo:", + "XA really long value spanning across multiple lines" + " that has to be wrapped at a convenient space."); + if (err) + fail ("gpgrt_nvc_set failed at line %d: %s\n",__LINE__,gpg_strerror (err)); + buf = nvc_to_string (pk); + gpgrt_assert (strcmp (buf, + "Foo: XA really long value spanning across multiple" + " lines that has to\n be wrapped at a convenient space.\n") + == 0); + xfree (buf); + + err = gpgrt_nvc_set (pk, "Foo:", + "XXXXA really long value spanning across multiple lines" + " that has to be wrapped at a convenient space."); + if (err) + fail ("gpgrt_nvc_set failed at line %d: %s\n",__LINE__,gpg_strerror (err)); + buf = nvc_to_string (pk); + gpgrt_assert (strcmp (buf, + "Foo: XXXXA really long value spanning across multiple" + " lines that has\n to be wrapped at a convenient space.\n") + == 0); + xfree (buf); + + err = gpgrt_nvc_set (pk, "Foo:", + "Areallylongvaluespanningacrossmultiplelines" + "thathastobewrappedataconvenientspacethatisnotthere."); + if (err) + fail ("gpgrt_nvc_set failed at line %d: %s\n",__LINE__,gpg_strerror (err)); + buf = nvc_to_string (pk); + gpgrt_assert (strcmp (buf, + "Foo: Areallylongvaluespanningacrossmultiplelinesthat" + "hastobewrappedataco\n nvenientspacethatisnotthere.\n") + == 0); + xfree (buf); + gpgrt_nvc_release (pk); + + pk = gpgrt_nvc_new (private_key_mode? GPGRT_NVC_PRIVKEY : 0); + gpgrt_assert (pk); + + err = gpgrt_nvc_set (pk, "Key:", "(hello world)"); + gpgrt_assert (err == 0); + buf = nvc_to_string (pk); + gpgrt_assert (strcmp (buf, "Key: (hello world)\n") == 0); + xfree (buf); + gpgrt_nvc_release (pk); +} + + + +static void +parse (const char *fname) +{ + gpg_error_t err; + estream_t source; + char *buf; + gpgrt_nvc_t pk_a, pk_b; + gpgrt_nve_t e; + int line; + + source = es_fopen (fname, "rb"); + if (source == NULL) + { + perror (fname); + exit (1); + } + + if (private_key_mode) + err = gpgrt_nvc_parse (&pk_a, &line, source, GPGRT_NVC_PRIVKEY); + else + err = gpgrt_nvc_parse (&pk_a, &line, source, 0); + if (err) + { + fail ("failed to parse %s line %d: %s\n", + fname, line, gpg_strerror (err)); + exit (1); + } + + buf = nvc_to_string (pk_a); + xfree (buf); + + pk_b = gpgrt_nvc_new (private_key_mode? GPGRT_NVC_PRIVKEY : 0); + gpgrt_assert (pk_b); + + for (e = gpgrt_nvc_lookup (pk_a, NULL); e; e = gpgrt_nve_next (e, NULL)) + { + const char *key = NULL; + + if (private_key_mode && !strcasecmp (gpgrt_nve_name (e), "Key:")) + { + key = gpgrt_nve_value (e); + } + + if (key) + { + err = gpgrt_nve_set (pk_a, e, key); + gpgrt_assert (err == 0); + } + else + { + err = gpgrt_nvc_add (pk_b, gpgrt_nve_name (e), gpgrt_nve_value (e)); + gpgrt_assert (err == 0); + } + } + + buf = nvc_to_string (pk_b); + if (verbose) + fprintf (stdout, "%s", buf); + xfree (buf); +} + + + +static const char * +my_strusage (int level) +{ + const char *p; + + switch (level) + { + case 9: p = "LGPL-2.1-or-later"; break; + case 11: p = PGM; break; + case 12: p = PACKAGE_NAME; break; + case 13: p = PACKAGE_VERSION; break; + case 14: p = GPGRT_STD_COPYRIGHT_LINE; break; + case 19: p = "Please report bugs to <https://bugs.gnupg.org>.\n"; break; + default: p = NULL; + } + return p; +} + + +int +main (int argc, char **argv) +{ + enum { CMD_TEST = 500, CMD_PARSE } command = CMD_TEST; + gpgrt_opt_t opts[] = { + ARGPARSE_x ('v', "verbose", NONE, 0, "Print more diagnostics"), + ARGPARSE_s_n('d', "debug", "Flyswatter"), + ARGPARSE_c (CMD_TEST, "test", "Test mode (default)"), + ARGPARSE_c (CMD_PARSE, "parse", "Parse a file"), + ARGPARSE_end() + }; + gpgrt_argparse_t pargs = { &argc, &argv, 0 }; + + gpgrt_set_strusage (my_strusage); + gpgrt_log_set_prefix (gpgrt_strusage (11), GPGRT_LOG_WITH_PREFIX); + + while (gpgrt_argparse (NULL, &pargs, opts)) + { + switch (pargs.r_opt) + { + case 'v': verbose++; break; + case 'd': debug++; break; + + case CMD_TEST: + case CMD_PARSE: + command = pargs.r_opt; + break; + + default : pargs.err = ARGPARSE_PRINT_ERROR; break; + } + } + gpgrt_argparse (NULL, &pargs, NULL); + + if (command == CMD_TEST) + { + show ("testing name-value functions\n"); + + run_tests (); + run_modification_tests (); + + show ("again in private key mode\n"); + /* Now again in rivate key mode */ + private_key_mode = 1; + run_tests (); + run_modification_tests (); + + show ("testing name-value functions finished\n"); + } + else if (command == CMD_PARSE) + { + if (argc != 1) + gpgrt_usage (1); + parse (*argv); + } + + return !!errorcount; +} |