aboutsummaryrefslogtreecommitdiffstats
path: root/src/name-value.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/name-value.c')
-rw-r--r--src/name-value.c895
1 files changed, 895 insertions, 0 deletions
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;
+}