summaryrefslogtreecommitdiffstats
path: root/common/recsel.c
diff options
context:
space:
mode:
Diffstat (limited to 'common/recsel.c')
-rw-r--r--common/recsel.c624
1 files changed, 624 insertions, 0 deletions
diff --git a/common/recsel.c b/common/recsel.c
new file mode 100644
index 0000000..b2b302b
--- /dev/null
+++ b/common/recsel.c
@@ -0,0 +1,624 @@
+/* recsel.c - Record selection
+ * Copyright (C) 2014, 2016 Werner Koch
+ *
+ * This file is part of GnuPG.
+ *
+ * This file is free software; you can redistribute it and/or modify
+ * it under the terms of either
+ *
+ * - the GNU Lesser General Public License as published by the Free
+ * Software Foundation; either version 3 of the License, or (at
+ * your option) any later version.
+ *
+ * or
+ *
+ * - the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at
+ * your option) any later version.
+ *
+ * or both in parallel, as here.
+ *
+ * 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 General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+
+#include "util.h"
+#include "recsel.h"
+
+/* Select operators. */
+typedef enum
+ {
+ SELECT_SAME,
+ SELECT_SUB,
+ SELECT_NONEMPTY,
+ SELECT_ISTRUE,
+ SELECT_EQ, /* Numerically equal. */
+ SELECT_LE,
+ SELECT_GE,
+ SELECT_LT,
+ SELECT_GT,
+ SELECT_STRLE, /* String is less or equal. */
+ SELECT_STRGE,
+ SELECT_STRLT,
+ SELECT_STRGT
+ } select_op_t;
+
+
+/* Definition for a select expression. */
+struct recsel_expr_s
+{
+ recsel_expr_t next;
+ select_op_t op; /* Operation code. */
+ unsigned int not:1; /* Negate operators. */
+ unsigned int disjun:1;/* Start of a disjunction. */
+ unsigned int xcase:1; /* String match is case sensitive. */
+ const char *value; /* (Points into NAME.) */
+ long numvalue; /* strtol of VALUE. */
+ char name[1]; /* Name of the property. */
+};
+
+
+/* Helper */
+static inline gpg_error_t
+my_error_from_syserror (void)
+{
+ return gpg_err_make (default_errsource, gpg_err_code_from_syserror ());
+}
+
+/* Helper */
+static inline gpg_error_t
+my_error (gpg_err_code_t ec)
+{
+ return gpg_err_make (default_errsource, ec);
+}
+
+
+/* This is a case-sensitive version of our memistr. I wonder why no
+ * standard function memstr exists but I better do not use the name
+ * memstr to avoid future conflicts.
+ *
+ * FIXME: Move this to a stringhelp.c
+ */
+static const char *
+my_memstr (const void *buffer, size_t buflen, const char *sub)
+{
+ const unsigned char *buf = buffer;
+ const unsigned char *t = (const unsigned char *)buf;
+ const unsigned char *s = (const unsigned char *)sub;
+ size_t n = buflen;
+
+ for ( ; n ; t++, n-- )
+ {
+ if (*t == *s)
+ {
+ for (buf = t++, buflen = n--, s++; n && *t ==*s; t++, s++, n--)
+ ;
+ if (!*s)
+ return (const char*)buf;
+ t = (const unsigned char *)buf;
+ s = (const unsigned char *)sub ;
+ n = buflen;
+ }
+ }
+ return NULL;
+}
+
+
+/* Return a pointer to the next logical connection operator or NULL if
+ * none. */
+static char *
+find_next_lc (char *string)
+{
+ char *p1, *p2;
+
+ p1 = strchr (string, '&');
+ if (p1 && p1[1] != '&')
+ p1 = NULL;
+ p2 = strchr (string, '|');
+ if (p2 && p2[1] != '|')
+ p2 = NULL;
+ if (p1 && !p2)
+ return p1;
+ if (!p1)
+ return p2;
+ return p1 < p2 ? p1 : p2;
+}
+
+
+/* Parse an expression. The expression syntax is:
+ *
+ * [<lc>] {{<flag>} PROPNAME <op> VALUE [<lc>]}
+ *
+ * A [] indicates an optional part, a {} a repetition. PROPNAME and
+ * VALUE may not be the empty string. White space between the
+ * elements is ignored. Numerical values are computed as long int;
+ * standard C notation applies. <lc> is the logical connection
+ * operator; either "&&" for a conjunction or "||" for a disjunction.
+ * A conjunction is assumed at the begin of an expression and
+ * conjunctions have higher precedence than disjunctions. If VALUE
+ * starts with one of the characters used in any <op> a space after
+ * the <op> is required. A VALUE is terminated by an <lc> unless the
+ * "--" <flag> is used in which case the VALUE spans to the end of the
+ * expression. <op> may be any of
+ *
+ * =~ Substring must match
+ * !~ Substring must not match
+ * = The full string must match
+ * <> The full string must not match
+ * == The numerical value must match
+ * != The numerical value must not match
+ * <= The numerical value of the field must be LE than the value.
+ * < The numerical value of the field must be LT than the value.
+ * >= The numerical value of the field must be GT than the value.
+ * >= The numerical value of the field must be GE than the value.
+ * -n True if value is not empty (no VALUE parameter allowed).
+ * -z True if value is empty (no VALUE parameter allowed).
+ * -t Alias for "PROPNAME != 0" (no VALUE parameter allowed).
+ * -f Alias for "PROPNAME == 0" (no VALUE parameter allowed).
+ *
+ * Values for <flag> must be space separated and any of:
+ *
+ * -- VALUE spans to the end of the expression.
+ * -c The string match in this part is done case-sensitive.
+ *
+ * For example four calls to recsel_parse_expr() with these values for
+ * EXPR
+ *
+ * "uid =~ Alfa"
+ * "&& uid !~ Test"
+ * "|| uid =~ Alpha"
+ * "uid !~ Test"
+ *
+ * or the equivalent expression
+ *
+ * "uid =~ Alfa" && uid !~ Test" || uid =~ Alpha" && "uid !~ Test"
+ *
+ * are making a selector for records where the "uid" property contains
+ * the strings "Alfa" or "Alpha" but not the String "test".
+ *
+ * The caller must pass the address of a selector variable to this
+ * function and initialize the value of the function to NULL before
+ * the first call. recset_release needs to be called to free the
+ * selector.
+ */
+gpg_error_t
+recsel_parse_expr (recsel_expr_t *selector, const char *expression)
+{
+ recsel_expr_t se_head = NULL;
+ recsel_expr_t se, se2;
+ char *expr_buffer;
+ char *expr;
+ char *s0, *s;
+ int toend = 0;
+ int xcase = 0;
+ int disjun = 0;
+ char *next_lc = NULL;
+
+ while (*expression == ' ' || *expression == '\t')
+ expression++;
+
+ expr_buffer = xtrystrdup (expression);
+ if (!expr_buffer)
+ return my_error_from_syserror ();
+ expr = expr_buffer;
+
+ if (*expr == '|' && expr[1] == '|')
+ {
+ disjun = 1;
+ expr += 2;
+ }
+ else if (*expr == '&' && expr[1] == '&')
+ expr += 2;
+
+ next_term:
+ while (*expr == ' ' || *expr == '\t')
+ expr++;
+
+ while (*expr == '-')
+ {
+ switch (*++expr)
+ {
+ case '-': toend = 1; break;
+ case 'c': xcase = 1; break;
+ default:
+ log_error ("invalid flag '-%c' in expression\n", *expr);
+ recsel_release (se_head);
+ xfree (expr_buffer);
+ return my_error (GPG_ERR_INV_FLAG);
+ }
+ expr++;
+ while (*expr == ' ' || *expr == '\t')
+ expr++;
+ }
+
+ next_lc = toend? NULL : find_next_lc (expr);
+ if (next_lc)
+ *next_lc = 0; /* Terminate this term. */
+
+ se = xtrymalloc (sizeof *se + strlen (expr));
+ if (!se)
+ return my_error_from_syserror ();
+ strcpy (se->name, expr);
+ se->next = NULL;
+ se->not = 0;
+ se->disjun = disjun;
+ se->xcase = xcase;
+
+ if (!se_head)
+ se_head = se;
+ else
+ {
+ for (se2 = se_head; se2->next; se2 = se2->next)
+ ;
+ se2->next = se;
+ }
+
+
+ s = strpbrk (expr, "=<>!~-");
+ if (!s || s == expr )
+ {
+ log_error ("no field name given in expression\n");
+ recsel_release (se_head);
+ xfree (expr_buffer);
+ return my_error (GPG_ERR_NO_NAME);
+ }
+ s0 = s;
+
+ if (!strncmp (s, "=~", 2))
+ {
+ se->op = SELECT_SUB;
+ s += 2;
+ }
+ else if (!strncmp (s, "!~", 2))
+ {
+ se->op = SELECT_SUB;
+ se->not = 1;
+ s += 2;
+ }
+ else if (!strncmp (s, "<>", 2))
+ {
+ se->op = SELECT_SAME;
+ se->not = 1;
+ s += 2;
+ }
+ else if (!strncmp (s, "==", 2))
+ {
+ se->op = SELECT_EQ;
+ s += 2;
+ }
+ else if (!strncmp (s, "!=", 2))
+ {
+ se->op = SELECT_EQ;
+ se->not = 1;
+ s += 2;
+ }
+ else if (!strncmp (s, "<=", 2))
+ {
+ se->op = SELECT_LE;
+ s += 2;
+ }
+ else if (!strncmp (s, ">=", 2))
+ {
+ se->op = SELECT_GE;
+ s += 2;
+ }
+ else if (!strncmp (s, "<", 1))
+ {
+ se->op = SELECT_LT;
+ s += 1;
+ }
+ else if (!strncmp (s, ">", 1))
+ {
+ se->op = SELECT_GT;
+ s += 1;
+ }
+ else if (!strncmp (s, "=", 1))
+ {
+ se->op = SELECT_SAME;
+ s += 1;
+ }
+ else if (!strncmp (s, "-z", 2))
+ {
+ se->op = SELECT_NONEMPTY;
+ se->not = 1;
+ s += 2;
+ }
+ else if (!strncmp (s, "-n", 2))
+ {
+ se->op = SELECT_NONEMPTY;
+ s += 2;
+ }
+ else if (!strncmp (s, "-f", 2))
+ {
+ se->op = SELECT_ISTRUE;
+ se->not = 1;
+ s += 2;
+ }
+ else if (!strncmp (s, "-t", 2))
+ {
+ se->op = SELECT_ISTRUE;
+ s += 2;
+ }
+ else if (!strncmp (s, "-le", 3))
+ {
+ se->op = SELECT_STRLE;
+ s += 3;
+ }
+ else if (!strncmp (s, "-ge", 3))
+ {
+ se->op = SELECT_STRGE;
+ s += 3;
+ }
+ else if (!strncmp (s, "-lt", 3))
+ {
+ se->op = SELECT_STRLT;
+ s += 3;
+ }
+ else if (!strncmp (s, "-gt", 3))
+ {
+ se->op = SELECT_STRGT;
+ s += 3;
+ }
+ else
+ {
+ log_error ("invalid operator in expression\n");
+ recsel_release (se_head);
+ xfree (expr_buffer);
+ return my_error (GPG_ERR_INV_OP);
+ }
+
+ /* We require that a space is used if the value starts with any of
+ the operator characters. */
+ if (se->op == SELECT_NONEMPTY || se->op == SELECT_ISTRUE)
+ ;
+ else if (strchr ("=<>!~", *s))
+ {
+ log_error ("invalid operator in expression\n");
+ recsel_release (se_head);
+ xfree (expr_buffer);
+ return my_error (GPG_ERR_INV_OP);
+ }
+
+ while (*s == ' ' || *s == '\t')
+ s++;
+
+ if (se->op == SELECT_NONEMPTY || se->op == SELECT_ISTRUE)
+ {
+ if (*s)
+ {
+ log_error ("value given for -n or -z\n");
+ recsel_release (se_head);
+ xfree (expr_buffer);
+ return my_error (GPG_ERR_SYNTAX);
+ }
+ }
+ else
+ {
+ if (!*s)
+ {
+ log_error ("no value given in expression\n");
+ recsel_release (se_head);
+ xfree (expr_buffer);
+ return my_error (GPG_ERR_MISSING_VALUE);
+ }
+ }
+
+ se->name[s0 - expr] = 0;
+ trim_spaces (se->name);
+ if (!se->name[0])
+ {
+ log_error ("no field name given in expression\n");
+ recsel_release (se_head);
+ xfree (expr_buffer);
+ return my_error (GPG_ERR_NO_NAME);
+ }
+
+ trim_spaces (se->name + (s - expr));
+ se->value = se->name + (s - expr);
+ if (!se->value[0] && !(se->op == SELECT_NONEMPTY || se->op == SELECT_ISTRUE))
+ {
+ log_error ("no value given in expression\n");
+ recsel_release (se_head);
+ xfree (expr_buffer);
+ return my_error (GPG_ERR_MISSING_VALUE);
+ }
+
+ se->numvalue = strtol (se->value, NULL, 0);
+
+ if (next_lc)
+ {
+ disjun = next_lc[1] == '|';
+ expr = next_lc + 2;
+ goto next_term;
+ }
+
+ /* Read:y Append to passes last selector. */
+ if (!*selector)
+ *selector = se_head;
+ else
+ {
+ for (se2 = *selector; se2->next; se2 = se2->next)
+ ;
+ se2->next = se_head;
+ }
+
+ xfree (expr_buffer);
+ return 0;
+}
+
+
+void
+recsel_release (recsel_expr_t a)
+{
+ while (a)
+ {
+ recsel_expr_t tmp = a->next;
+ xfree (a);
+ a = tmp;
+ }
+}
+
+
+void
+recsel_dump (recsel_expr_t selector)
+{
+ recsel_expr_t se;
+
+ log_debug ("--- Begin selectors ---\n");
+ for (se = selector; se; se = se->next)
+ {
+ log_debug ("%s %s %s %s '%s'\n",
+ se==selector? " ": (se->disjun? "||":"&&"),
+ se->xcase? "-c":" ",
+ se->name,
+ se->op == SELECT_SAME? (se->not? "<>":"= "):
+ se->op == SELECT_SUB? (se->not? "!~":"=~"):
+ se->op == SELECT_NONEMPTY?(se->not? "-z":"-n"):
+ se->op == SELECT_ISTRUE? (se->not? "-f":"-t"):
+ se->op == SELECT_EQ? (se->not? "!=":"=="):
+ se->op == SELECT_LT? "< ":
+ se->op == SELECT_LE? "<=":
+ se->op == SELECT_GT? "> ":
+ se->op == SELECT_GE? ">=":
+ se->op == SELECT_STRLT? "-lt":
+ se->op == SELECT_STRLE? "-le":
+ se->op == SELECT_STRGT? "-gt":
+ se->op == SELECT_STRGE? "-ge":
+ /**/ "[oops]",
+ se->value);
+ }
+ log_debug ("--- End selectors ---\n");
+}
+
+
+/* Return true if the record RECORD has been selected. The GETVAL
+ * function is called with COOKIE and the NAME of a property used in
+ * the expression. */
+int
+recsel_select (recsel_expr_t selector,
+ const char *(*getval)(void *cookie, const char *propname),
+ void *cookie)
+{
+ recsel_expr_t se;
+ const char *value;
+ size_t selen, valuelen;
+ long numvalue;
+ int result = 1;
+
+ se = selector;
+ while (se)
+ {
+ value = getval? getval (cookie, se->name) : NULL;
+ if (!value)
+ value = "";
+
+ if (!*value)
+ {
+ /* Field is empty. */
+ result = 0;
+ }
+ else /* Field has a value. */
+ {
+ valuelen = strlen (value);
+ numvalue = strtol (value, NULL, 0);
+ selen = strlen (se->value);
+
+ switch (se->op)
+ {
+ case SELECT_SAME:
+ if (se->xcase)
+ result = (valuelen==selen && !memcmp (value,se->value,selen));
+ else
+ result = (valuelen==selen && !memicmp (value,se->value,selen));
+ break;
+ case SELECT_SUB:
+ if (se->xcase)
+ result = !!my_memstr (value, valuelen, se->value);
+ else
+ result = !!memistr (value, valuelen, se->value);
+ break;
+ case SELECT_NONEMPTY:
+ result = !!valuelen;
+ break;
+ case SELECT_ISTRUE:
+ result = !!numvalue;
+ break;
+ case SELECT_EQ:
+ result = (numvalue == se->numvalue);
+ break;
+ case SELECT_GT:
+ result = (numvalue > se->numvalue);
+ break;
+ case SELECT_GE:
+ result = (numvalue >= se->numvalue);
+ break;
+ case SELECT_LT:
+ result = (numvalue < se->numvalue);
+ break;
+ case SELECT_LE:
+ result = (numvalue <= se->numvalue);
+ break;
+ case SELECT_STRGT:
+ if (se->xcase)
+ result = strcmp (value, se->value) > 0;
+ else
+ result = strcasecmp (value, se->value) > 0;
+ break;
+ case SELECT_STRGE:
+ if (se->xcase)
+ result = strcmp (value, se->value) >= 0;
+ else
+ result = strcasecmp (value, se->value) >= 0;
+ break;
+ case SELECT_STRLT:
+ if (se->xcase)
+ result = strcmp (value, se->value) < 0;
+ else
+ result = strcasecmp (value, se->value) < 0;
+ break;
+ case SELECT_STRLE:
+ if (se->xcase)
+ result = strcmp (value, se->value) <= 0;
+ else
+ result = strcasecmp (value, se->value) <= 0;
+ break;
+ }
+ }
+
+ if (se->not)
+ result = !result;
+
+ if (result)
+ {
+ /* This expression evaluated to true. See whether there are
+ remaining expressions in this conjunction. */
+ if (!se->next || se->next->disjun)
+ break; /* All expressions are true. Return True. */
+ se = se->next; /* Test the next. */
+ }
+ else
+ {
+ /* This expression evaluated to false and thus the
+ * conjunction evaluates to false. We skip over the
+ * remaining expressions of this conjunction and continue
+ * with the next disjunction if any. */
+ do
+ se = se->next;
+ while (se && !se->disjun);
+ }
+ }
+
+ return result;
+}