common: Add function to select records etc.
authorWerner Koch <wk@gnupg.org>
Thu, 30 Jun 2016 18:25:46 +0000 (20:25 +0200)
committerWerner Koch <wk@gnupg.org>
Fri, 1 Jul 2016 14:27:43 +0000 (16:27 +0200)
* common/recsel.c, common/recsel.h: New.
* common/t-recsel.c: New.

Signed-off-by: Werner Koch <wk@gnupg.org>
common/Makefile.am
common/recsel.c [new file with mode: 0644]
common/recsel.h [new file with mode: 0644]
common/t-recsel.c [new file with mode: 0644]

index 2451689..6f9d96d 100644 (file)
@@ -91,7 +91,8 @@ common_sources = \
        call-gpg.c call-gpg.h \
        exectool.c exectool.h \
        server-help.c server-help.h \
-       name-value.c name-value.h
+       name-value.c name-value.h \
+       recsel.c recsel.h
 
 if HAVE_W32_SYSTEM
 common_sources += w32-reg.c w32-afunix.c w32-afunix.h
@@ -157,7 +158,7 @@ module_tests = t-stringhelp t-timestuff \
                t-convert t-percent t-gettime t-sysutils t-sexputil \
               t-session-env t-openpgp-oid t-ssh-utils \
               t-mapstrings t-zb32 t-mbox-util t-iobuf t-strlist \
-              t-name-value t-ccparray
+              t-name-value t-ccparray t-recsel
 if !HAVE_W32CE_SYSTEM
 module_tests += t-exechelp
 endif
@@ -208,6 +209,7 @@ t_iobuf_LDADD = $(t_common_ldadd)
 t_strlist_LDADD = $(t_common_ldadd)
 t_name_value_LDADD = $(t_common_ldadd)
 t_ccparray_LDADD = $(t_common_ldadd)
+t_recsel_LDADD = $(t_common_ldadd)
 
 # System specific test
 if HAVE_W32_SYSTEM
diff --git a/common/recsel.c b/common/recsel.c
new file mode 100644 (file)
index 0000000..b35574f
--- /dev/null
@@ -0,0 +1,571 @@
+/* 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 <http://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_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 symtax 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 "NAME != 0" (no VALUE parameter allowed).
+ *   -f  Alias for "NAME == 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
+    {
+      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?      ">=":"[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;
+            }
+        }
+
+      if (se->not)
+        result = !result;
+
+      if (result)
+        {
+          /* This expression evaluated to true.  See wether 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
+           * conjuction 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;
+}
diff --git a/common/recsel.h b/common/recsel.h
new file mode 100644 (file)
index 0000000..be67afc
--- /dev/null
@@ -0,0 +1,43 @@
+/* recsel.c - Record selection
+ * Copyright (C) 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 <http://www.gnu.org/licenses/>.
+ */
+#ifndef GNUPG_COMMON_RECSEL_H
+#define GNUPG_COMMON_RECSEL_H
+
+struct recsel_expr_s;
+typedef struct recsel_expr_s *recsel_expr_t;
+
+gpg_error_t recsel_parse_expr (recsel_expr_t *selector, const char *expr);
+void recsel_release (recsel_expr_t a);
+void recsel_dump (recsel_expr_t selector);
+int recsel_select (recsel_expr_t selector,
+                   const char *(*getval)(void *cookie, const char *propname),
+                   void *cookie);
+
+
+#endif /*GNUPG_COMMON_RECSEL_H*/
diff --git a/common/t-recsel.c b/common/t-recsel.c
new file mode 100644 (file)
index 0000000..fe2a7b9
--- /dev/null
@@ -0,0 +1,405 @@
+/* t-recsel.c - Module test for recsel.c
+ * Copyright (C) 2016 Werner Koch
+ *
+ * This file is part of GnuPG.
+ *
+ * GnuPG is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GnuPG 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 <http://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "util.h"
+#include "init.h"
+#include "recsel.h"
+
+#define PGM  "t-recsel"
+
+#define pass()  do { ; } while(0)
+#define fail(a,e)  do { log_error ("line %d: test %d failed: %s\n",     \
+                                   __LINE__, (a), gpg_strerror ((e)));  \
+                       exit (1);                                        \
+                    } while(0)
+
+static int verbose;
+static int debug;
+
+
+#define FREEEXPR() do { recsel_release (se); se = NULL; } while (0)
+#define ADDEXPR(a) do {                         \
+    err = recsel_parse_expr (&se, (a));         \
+    if (err)                                    \
+      fail (0, err);                            \
+  } while (0)
+
+
+static const char *
+test_1_getval (void *cookie, const char *name)
+{
+  if (strcmp (name, "uid"))
+    fail (0, 0);
+  return cookie;
+}
+
+static void
+run_test_1 (void)
+{
+  static const char *expr[] = {
+    "uid =~ Alfa",
+    "&& uid !~   Test  ",
+    "|| uid =~  Alpha",
+    " uid  !~ Test"
+  };
+  gpg_error_t err;
+  recsel_expr_t se = NULL;
+  int i;
+
+  for (i=0; i < DIM (expr); i++)
+    {
+      err = recsel_parse_expr (&se, expr[i]);
+      if (err)
+        fail (i, err);
+    }
+
+  if (debug)
+    recsel_dump (se);
+
+  /* The example from recsel.c in several variants. */
+  if (!recsel_select (se, test_1_getval, "Alfa"))
+    fail (0, 0);
+  if (!recsel_select (se, test_1_getval, "Alpha"))
+    fail (0, 0);
+  if (recsel_select (se, test_1_getval, "Alfa Test"))
+    fail (0, 0);
+  if (recsel_select (se, test_1_getval, "Alpha Test"))
+    fail (0, 0);
+
+  /* Some modified versions from above.  */
+  if (!recsel_select (se, test_1_getval, " AlfA Tes"))
+    fail (0, 0);
+  if (!recsel_select (se, test_1_getval, " AlfA Tes "))
+    fail (0, 0);
+  if (!recsel_select (se, test_1_getval, " Tes  AlfA"))
+    fail (0, 0);
+  if (!recsel_select (se, test_1_getval, "TesAlfA"))
+    fail (0, 0);
+
+  /* Simple cases. */
+  if (recsel_select (se, NULL, NULL))
+    fail (0, 0);
+  if (recsel_select (se, test_1_getval, NULL))
+    fail (0, 0);
+  if (recsel_select (se, test_1_getval, ""))
+    fail (0, 0);
+
+  FREEEXPR();
+}
+
+
+/* Same as test1 but using a combined expression.. */
+static void
+run_test_1b (void)
+{
+  gpg_error_t err;
+  recsel_expr_t se = NULL;
+
+  err = recsel_parse_expr
+    (&se, "uid =~ Alfa && uid !~   Test  || uid =~  Alpha && uid  !~ Test" );
+  if (err)
+    fail (0, err);
+
+  if (debug)
+    recsel_dump (se);
+
+  /* The example from recsel.c in several variants. */
+  if (!recsel_select (se, test_1_getval, "Alfa"))
+    fail (0, 0);
+  if (!recsel_select (se, test_1_getval, "Alpha"))
+    fail (0, 0);
+  if (recsel_select (se, test_1_getval, "Alfa Test"))
+    fail (0, 0);
+  if (recsel_select (se, test_1_getval, "Alpha Test"))
+    fail (0, 0);
+
+  /* Some modified versions from above.  */
+  if (!recsel_select (se, test_1_getval, " AlfA Tes"))
+    fail (0, 0);
+  if (!recsel_select (se, test_1_getval, " AlfA Tes "))
+    fail (0, 0);
+  if (!recsel_select (se, test_1_getval, " Tes  AlfA"))
+    fail (0, 0);
+  if (!recsel_select (se, test_1_getval, "TesAlfA"))
+    fail (0, 0);
+
+  /* Simple cases. */
+  if (recsel_select (se, NULL, NULL))
+    fail (0, 0);
+  if (recsel_select (se, test_1_getval, NULL))
+    fail (0, 0);
+  if (recsel_select (se, test_1_getval, ""))
+    fail (0, 0);
+
+  FREEEXPR();
+}
+
+
+static const char *
+test_2_getval (void *cookie, const char *name)
+{
+  if (!strcmp (name, "uid"))
+    return "foo@example.org";
+  else if (!strcmp (name, "keyid"))
+    return "0x12345678";
+  else if (!strcmp (name, "zero"))
+    return "0";
+  else if (!strcmp (name, "one"))
+    return "1";
+  else if (!strcmp (name, "blanks"))
+    return "    ";
+  else if (!strcmp (name, "letters"))
+    return "abcde";
+  else
+    return cookie;
+}
+
+static void
+run_test_2 (void)
+{
+  gpg_error_t err;
+  recsel_expr_t se = NULL;
+
+  ADDEXPR ("uid = foo@example.org");
+  if (!recsel_select (se, test_2_getval, NULL))
+    fail (0, 0);
+  FREEEXPR();
+  ADDEXPR ("uid = Foo@example.org");
+  if (!recsel_select (se, test_2_getval, NULL))
+    fail (0, 0);
+  FREEEXPR();
+  ADDEXPR ("-c uid = Foo@example.org");
+  if (recsel_select (se, test_2_getval, NULL))
+    fail (0, 0);
+
+  FREEEXPR();
+  ADDEXPR ("uid =~ foo@example.org");
+  if (!recsel_select (se, test_2_getval, NULL))
+    fail (0, 0);
+  FREEEXPR();
+  ADDEXPR ("uid =~ Foo@example.org");
+  if (!recsel_select (se, test_2_getval, NULL))
+    fail (0, 0);
+  FREEEXPR();
+  ADDEXPR ("-c uid =~ Foo@example.org");
+  if (recsel_select (se, test_2_getval, NULL))
+    fail (0, 0);
+
+  FREEEXPR();
+  ADDEXPR ("uid !~ foo@example.org");
+  if (recsel_select (se, test_2_getval, NULL))
+    fail (0, 0);
+  FREEEXPR();
+  ADDEXPR ("uid !~ Foo@example.org");
+  if (recsel_select (se, test_2_getval, NULL))
+    fail (0, 0);
+  FREEEXPR();
+  ADDEXPR ("-c uid !~ Foo@example.org");
+  if (!recsel_select (se, test_2_getval, NULL))
+    fail (0, 0);
+
+  FREEEXPR();
+  ADDEXPR ("uid =~ @");
+  if (!recsel_select (se, test_2_getval, NULL))
+    fail (0, 0);
+  FREEEXPR();
+  ADDEXPR ("uid =~ @");
+  if (!recsel_select (se, test_2_getval, NULL))
+    fail (0, 0);
+
+  FREEEXPR();
+  ADDEXPR ("keyid == 0x12345678");
+  if (!recsel_select (se, test_2_getval, NULL))
+    fail (0, 0);
+  FREEEXPR();
+  ADDEXPR ("keyid != 0x12345678");
+  if (recsel_select (se, test_2_getval, NULL))
+    fail (0, 0);
+  FREEEXPR();
+  ADDEXPR ("keyid >= 0x12345678");
+  if (!recsel_select (se, test_2_getval, NULL))
+    fail (0, 0);
+  FREEEXPR();
+  ADDEXPR ("keyid <= 0x12345678");
+  if (!recsel_select (se, test_2_getval, NULL))
+    fail (0, 0);
+  FREEEXPR();
+  ADDEXPR ("keyid > 0x12345677");
+  if (!recsel_select (se, test_2_getval, NULL))
+    fail (0, 0);
+  FREEEXPR();
+  ADDEXPR ("keyid < 0x12345679");
+  if (!recsel_select (se, test_2_getval, NULL))
+    fail (0, 0);
+
+  FREEEXPR();
+  ADDEXPR ("keyid > 0x12345678");
+  if (recsel_select (se, test_2_getval, NULL))
+    fail (0, 0);
+  FREEEXPR();
+  ADDEXPR ("keyid < 0x12345678");
+  if (recsel_select (se, test_2_getval, NULL))
+    fail (0, 0);
+
+
+  FREEEXPR();
+  ADDEXPR ("uid -n");
+  if (!recsel_select (se, test_2_getval, NULL))
+    fail (0, 0);
+  FREEEXPR();
+  ADDEXPR ("uid -z");
+  if (recsel_select (se, test_2_getval, NULL))
+    fail (0, 0);
+
+  FREEEXPR();
+  ADDEXPR ("nothing -z");
+  if (!recsel_select (se, test_2_getval, NULL))
+    fail (0, 0);
+  FREEEXPR();
+  ADDEXPR ("nothing -n");
+  if (recsel_select (se, test_2_getval, NULL))
+    fail (0, 0);
+
+  FREEEXPR();
+  ADDEXPR ("blanks -n");
+  if (!recsel_select (se, test_2_getval, NULL))
+    fail (0, 0);
+  FREEEXPR();
+  ADDEXPR ("blanks -z");
+  if (recsel_select (se, test_2_getval, NULL))
+    fail (0, 0);
+
+  FREEEXPR();
+  ADDEXPR ("letters -n");
+  if (!recsel_select (se, test_2_getval, NULL))
+    fail (0, 0);
+  FREEEXPR();
+  ADDEXPR ("letters -z");
+  if (recsel_select (se, test_2_getval, NULL))
+    fail (0, 0);
+
+
+  FREEEXPR();
+  ADDEXPR ("nothing -f");
+  if (!recsel_select (se, test_2_getval, NULL))
+    fail (0, 0);
+  FREEEXPR();
+  ADDEXPR ("nothing -t");
+  if (recsel_select (se, test_2_getval, NULL))
+    fail (0, 0);
+
+  FREEEXPR();
+  ADDEXPR ("zero -f");
+  if (!recsel_select (se, test_2_getval, NULL))
+    fail (0, 0);
+  FREEEXPR();
+  ADDEXPR ("zero -t");
+  if (recsel_select (se, test_2_getval, NULL))
+    fail (0, 0);
+
+  FREEEXPR();
+  ADDEXPR ("one -t");
+  if (!recsel_select (se, test_2_getval, NULL))
+    fail (0, 0);
+  FREEEXPR();
+  ADDEXPR ("one -f");
+  if (recsel_select (se, test_2_getval, NULL))
+    fail (0, 0);
+
+  FREEEXPR();
+  ADDEXPR ("blanks -f");
+  if (!recsel_select (se, test_2_getval, NULL))
+    fail (0, 0);
+  FREEEXPR();
+  ADDEXPR ("blanks -t");
+  if (recsel_select (se, test_2_getval, NULL))
+    fail (0, 0);
+
+  FREEEXPR();
+  ADDEXPR ("letter -f");
+  if (!recsel_select (se, test_2_getval, NULL))
+    fail (0, 0);
+  FREEEXPR();
+  ADDEXPR ("letters -t");
+  if (recsel_select (se, test_2_getval, NULL))
+    fail (0, 0);
+
+
+  FREEEXPR();
+}
+
+
+
+int
+main (int argc, char **argv)
+{
+  int last_argc = -1;
+
+  log_set_prefix (PGM, GPGRT_LOG_WITH_PREFIX);
+  init_common_subsystems (&argc, &argv);
+
+  if (argc)
+    { argc--; argv++; }
+  while (argc && last_argc != argc )
+    {
+      last_argc = argc;
+      if (!strcmp (*argv, "--"))
+        {
+          argc--; argv++;
+          break;
+        }
+      else if (!strcmp (*argv, "--help"))
+        {
+          fputs ("usage: " PGM " [options]\n"
+                 "Options:\n"
+                 "  --verbose       print timings etc.\n"
+                 "  --debug         flyswatter\n",
+                 stdout);
+          exit (0);
+        }
+      else if (!strcmp (*argv, "--verbose"))
+        {
+          verbose++;
+          argc--; argv++;
+        }
+      else if (!strcmp (*argv, "--debug"))
+        {
+          verbose += 2;
+          debug++;
+          argc--; argv++;
+        }
+      else if (!strncmp (*argv, "--", 2))
+        {
+          log_error ("unknown option '%s'\n", *argv);
+          exit (2);
+        }
+    }
+
+  run_test_1 ();
+  run_test_1b ();
+  run_test_2 ();
+  /* Fixme: We should add test for complex conditions.  */
+
+  return 0;
+}