1
0
mirror of git://git.gnupg.org/gnupg.git synced 2025-05-14 08:13:25 +02:00
gnupg/common/recsel.c
Werner Koch 8968e84903
common: Fix logic for certain recsel conditions.
* common/recsel.c (recsel_select): Change processing of NULL values.
* common/t-recsel.c (run_test_2): Adjust for this change.  Also a type
fix for s/"letter"/"letters"/.
--

The getval function may return NULL which indicates that there is no
useful value available.  For example because the propertyname is not
defined for some external context (e.g. in gpg the packet type).  This
also required to fix the test for boolean tests of a non existing
property name.

Reported-by: shniubobo at gnupg-users on 2025-04-18.
2025-04-30 15:13:11 +02:00

642 lines
16 KiB
C

/* 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. */
unsigned int lefta:1; /* String match is left anchored. */
const char *value; /* (Points into NAME.) */
long numvalue; /* strtol of VALUE. */
char name[1]; /* Name of the property. */
};
/* Global debug variable. */
static int recsel_debug;
/* 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);
}
/* 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.
* -^ The substring match is left anchored.
* -c The string match in this part is done case-sensitive.
* -t Do not trim leading and trailing spaces from VALUE.
* Note that a space after <op> is here required.
* -r RFU: String match uses a regular expression
*
* 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. recsel_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 notrim = 0;
int lefta = 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;
case 't': notrim = 1; break;
case '^': lefta = 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)
{
gpg_error_t err = my_error_from_syserror ();
recsel_release (se_head);
xfree (expr_buffer);
return err;
}
strcpy (se->name, expr);
se->next = NULL;
se->not = 0;
se->disjun = disjun;
se->xcase = xcase;
se->lefta = lefta;
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);
}
if (*s == ' ' || *s == '\t')
s++;
if (!notrim)
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);
}
if (!notrim)
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;
}
}
int
recsel_set_debug (int value)
{
int old = recsel_debug;
recsel_debug = value;
return old;
}
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 '%s'\n",
se==selector? " ": (se->disjun? "||":"&&"),
se->xcase? "-c":" ",
se->lefta? "-^":" ",
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 (recsel_debug)
log_debug ("%s: name=%s got value '%s'\n", __func__, se->name, value);
if (!value)
{
se = se->next;
result = 0;
continue;
}
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 = (gnupg_memstr (value, valuelen, se->value)
&& (!se->lefta
|| (selen <= valuelen
&& !memcmp (value, se->value, selen))));
else
result = (memistr (value, valuelen, se->value)
&& (!se->lefta
|| (selen <= valuelen
&& !memicmp (value, se->value, selen))));
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);
}
}
if (recsel_debug)
log_debug ("%s: result=%d\n", __func__, result);
return result;
}