Coverage Report

Created: 2026-01-09 06:46

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/gnupg/common/recsel.c
Line
Count
Source
1
/* recsel.c - Record selection
2
 * Copyright (C) 2014, 2016 Werner Koch
3
 *
4
 * This file is part of GnuPG.
5
 *
6
 * This file is free software; you can redistribute it and/or modify
7
 * it under the terms of either
8
 *
9
 *   - the GNU Lesser General Public License as published by the Free
10
 *     Software Foundation; either version 3 of the License, or (at
11
 *     your option) any later version.
12
 *
13
 * or
14
 *
15
 *   - the GNU General Public License as published by the Free
16
 *     Software Foundation; either version 2 of the License, or (at
17
 *     your option) any later version.
18
 *
19
 * or both in parallel, as here.
20
 *
21
 * This file is distributed in the hope that it will be useful,
22
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
23
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
24
 * GNU General Public License for more details.
25
 *
26
 * You should have received a copy of the GNU General Public License
27
 * along with this program; if not, see <https://www.gnu.org/licenses/>.
28
 */
29
30
#include <config.h>
31
#include <stdio.h>
32
#include <stdlib.h>
33
#include <string.h>
34
#include <unistd.h>
35
#include <errno.h>
36
37
#include "util.h"
38
#include "recsel.h"
39
40
/* Select operators.  */
41
typedef enum
42
  {
43
    SELECT_SAME,
44
    SELECT_SUB,
45
    SELECT_NONEMPTY,
46
    SELECT_ISTRUE,
47
    SELECT_EQ, /* Numerically equal.  */
48
    SELECT_LE,
49
    SELECT_GE,
50
    SELECT_LT,
51
    SELECT_GT,
52
    SELECT_STRLE, /* String is less or equal.  */
53
    SELECT_STRGE,
54
    SELECT_STRLT,
55
    SELECT_STRGT
56
  } select_op_t;
57
58
59
/* Definition for a select expression.  */
60
struct recsel_expr_s
61
{
62
  recsel_expr_t next;
63
  select_op_t op;       /* Operation code.  */
64
  unsigned int not:1;   /* Negate operators. */
65
  unsigned int disjun:1;/* Start of a disjunction.  */
66
  unsigned int xcase:1; /* String match is case sensitive.  */
67
  unsigned int lefta:1; /* String match is left anchored.  */
68
  const char *value;    /* (Points into NAME.)  */
69
  long numvalue;        /* strtol of VALUE.  */
70
  char name[1];         /* Name of the property.  */
71
};
72
73
74
/* Global debug variable.  */
75
static int recsel_debug;
76
77
78
/* Helper */
79
static inline gpg_error_t
80
my_error_from_syserror (void)
81
0
{
82
0
  return gpg_err_make (default_errsource, gpg_err_code_from_syserror ());
83
0
}
84
85
/* Helper */
86
static inline gpg_error_t
87
my_error (gpg_err_code_t ec)
88
0
{
89
0
  return gpg_err_make (default_errsource, ec);
90
0
}
91
92
93
/* Return a pointer to the next logical connection operator or NULL if
94
 * none.  */
95
static char *
96
find_next_lc (char *string)
97
0
{
98
0
  char *p1, *p2;
99
100
0
  p1 = strchr (string, '&');
101
0
  if (p1 && p1[1] != '&')
102
0
    p1 = NULL;
103
0
  p2 = strchr (string, '|');
104
0
  if (p2 && p2[1] != '|')
105
0
    p2 = NULL;
106
0
  if (p1 && !p2)
107
0
    return p1;
108
0
  if (!p1)
109
0
    return p2;
110
0
  return p1 < p2 ? p1 : p2;
111
0
}
112
113
114
/* Parse an expression.  The expression syntax is:
115
 *
116
 *   [<lc>] {{<flag>} PROPNAME <op> VALUE [<lc>]}
117
 *
118
 * A [] indicates an optional part, a {} a repetition.  PROPNAME and
119
 * VALUE may not be the empty string.  White space between the
120
 * elements is ignored.  Numerical values are computed as long int;
121
 * standard C notation applies.  <lc> is the logical connection
122
 * operator; either "&&" for a conjunction or "||" for a disjunction.
123
 * A conjunction is assumed at the begin of an expression and
124
 * conjunctions have higher precedence than disjunctions.  If VALUE
125
 * starts with one of the characters used in any <op> a space after
126
 * the <op> is required.  A VALUE is terminated by an <lc> unless the
127
 * "--" <flag> is used in which case the VALUE spans to the end of the
128
 * expression.  <op> may be any of
129
 *
130
 *   =~  Substring must match
131
 *   !~  Substring must not match
132
 *   =   The full string must match
133
 *   <>  The full string must not match
134
 *   ==  The numerical value must match
135
 *   !=  The numerical value must not match
136
 *   <=  The numerical value of the field must be LE than the value.
137
 *   <   The numerical value of the field must be LT than the value.
138
 *   >=  The numerical value of the field must be GT than the value.
139
 *   >=  The numerical value of the field must be GE than the value.
140
 *   -n  True if value is not empty (no VALUE parameter allowed).
141
 *   -z  True if value is empty (no VALUE parameter allowed).
142
 *   -t  Alias for "PROPNAME != 0" (no VALUE parameter allowed).
143
 *   -f  Alias for "PROPNAME == 0" (no VALUE parameter allowed).
144
 *
145
 * Values for <flag> must be space separated and any of:
146
 *
147
 *   --  VALUE spans to the end of the expression.
148
 *   -^  The substring match is left anchored.
149
 *   -c  The string match in this part is done case-sensitive.
150
 *   -t  Do not trim leading and trailing spaces from VALUE.
151
 *       Note that a space after <op> is here required.
152
 *   -r  RFU: String match uses a regular expression
153
 *
154
 * For example four calls to recsel_parse_expr() with these values for
155
 * EXPR
156
 *
157
 *  "uid =~ Alfa"
158
 *  "&& uid !~ Test"
159
 *  "|| uid =~ Alpha"
160
 *  "uid !~ Test"
161
 *
162
 * or the equivalent expression
163
 *
164
 *  "uid =~ Alfa" && uid !~ Test" || uid =~ Alpha" && "uid !~ Test"
165
 *
166
 * are making a selector for records where the "uid" property contains
167
 * the strings "Alfa" or "Alpha" but not the String "test".
168
 *
169
 * The caller must pass the address of a selector variable to this
170
 * function and initialize the value of the function to NULL before
171
 * the first call.  recsel_release needs to be called to free the
172
 * selector.
173
 */
174
gpg_error_t
175
recsel_parse_expr (recsel_expr_t *selector, const char *expression)
176
0
{
177
0
  recsel_expr_t se_head = NULL;
178
0
  recsel_expr_t se, se2;
179
0
  char *expr_buffer;
180
0
  char *expr;
181
0
  char *s0, *s;
182
0
  int toend = 0;
183
0
  int xcase = 0;
184
0
  int notrim = 0;
185
0
  int lefta = 0;
186
0
  int disjun = 0;
187
0
  char *next_lc = NULL;
188
189
0
  while (*expression == ' ' || *expression == '\t')
190
0
    expression++;
191
192
0
  expr_buffer = xtrystrdup (expression);
193
0
  if (!expr_buffer)
194
0
    return my_error_from_syserror ();
195
0
  expr = expr_buffer;
196
197
0
  if (*expr == '|' && expr[1] == '|')
198
0
    {
199
0
      disjun = 1;
200
0
      expr += 2;
201
0
    }
202
0
  else if (*expr == '&' && expr[1] == '&')
203
0
    expr += 2;
204
205
0
 next_term:
206
0
  while (*expr == ' ' || *expr == '\t')
207
0
    expr++;
208
209
0
  while (*expr == '-')
210
0
    {
211
0
      switch (*++expr)
212
0
        {
213
0
        case '-': toend = 1; break;
214
0
        case 'c': xcase = 1; break;
215
0
        case 't': notrim = 1; break;
216
0
        case '^': lefta = 1; break;
217
0
        default:
218
0
          log_error ("invalid flag '-%c' in expression\n", *expr);
219
0
          recsel_release (se_head);
220
0
          xfree (expr_buffer);
221
0
          return my_error (GPG_ERR_INV_FLAG);
222
0
        }
223
0
      expr++;
224
0
      while (*expr == ' ' || *expr == '\t')
225
0
        expr++;
226
0
    }
227
228
0
  next_lc = toend? NULL : find_next_lc (expr);
229
0
  if (next_lc)
230
0
    *next_lc = 0;  /* Terminate this term.  */
231
232
0
  se = xtrymalloc (sizeof *se + strlen (expr));
233
0
  if (!se)
234
0
    {
235
0
      gpg_error_t err = my_error_from_syserror ();
236
237
0
      recsel_release (se_head);
238
0
      xfree (expr_buffer);
239
0
      return err;
240
0
    }
241
0
  strcpy (se->name, expr);
242
0
  se->next = NULL;
243
0
  se->not = 0;
244
0
  se->disjun = disjun;
245
0
  se->xcase = xcase;
246
0
  se->lefta = lefta;
247
248
0
  if (!se_head)
249
0
    se_head = se;
250
0
  else
251
0
    {
252
0
      for (se2 = se_head; se2->next; se2 = se2->next)
253
0
        ;
254
0
      se2->next = se;
255
0
    }
256
257
258
0
  s = strpbrk (expr, "=<>!~-");
259
0
  if (!s || s == expr )
260
0
    {
261
0
      log_error ("no field name given in expression\n");
262
0
      recsel_release (se_head);
263
0
      xfree (expr_buffer);
264
0
      return my_error (GPG_ERR_NO_NAME);
265
0
    }
266
0
  s0 = s;
267
268
0
  if (!strncmp (s, "=~", 2))
269
0
    {
270
0
      se->op = SELECT_SUB;
271
0
      s += 2;
272
0
    }
273
0
  else if (!strncmp (s, "!~", 2))
274
0
    {
275
0
      se->op = SELECT_SUB;
276
0
      se->not = 1;
277
0
      s += 2;
278
0
    }
279
0
  else if (!strncmp (s, "<>", 2))
280
0
    {
281
0
      se->op = SELECT_SAME;
282
0
      se->not = 1;
283
0
      s += 2;
284
0
    }
285
0
  else if (!strncmp (s, "==", 2))
286
0
    {
287
0
      se->op = SELECT_EQ;
288
0
      s += 2;
289
0
    }
290
0
  else if (!strncmp (s, "!=", 2))
291
0
    {
292
0
      se->op = SELECT_EQ;
293
0
      se->not = 1;
294
0
      s += 2;
295
0
    }
296
0
  else if (!strncmp (s, "<=", 2))
297
0
    {
298
0
      se->op = SELECT_LE;
299
0
      s += 2;
300
0
    }
301
0
  else if (!strncmp (s, ">=", 2))
302
0
    {
303
0
      se->op = SELECT_GE;
304
0
      s += 2;
305
0
    }
306
0
  else if (!strncmp (s, "<", 1))
307
0
    {
308
0
      se->op = SELECT_LT;
309
0
      s += 1;
310
0
    }
311
0
  else if (!strncmp (s, ">", 1))
312
0
    {
313
0
      se->op = SELECT_GT;
314
0
      s += 1;
315
0
    }
316
0
  else if (!strncmp (s, "=", 1))
317
0
    {
318
0
      se->op = SELECT_SAME;
319
0
      s += 1;
320
0
    }
321
0
  else if (!strncmp (s, "-z", 2))
322
0
    {
323
0
      se->op = SELECT_NONEMPTY;
324
0
      se->not = 1;
325
0
      s += 2;
326
0
    }
327
0
  else if (!strncmp (s, "-n", 2))
328
0
    {
329
0
      se->op = SELECT_NONEMPTY;
330
0
      s += 2;
331
0
    }
332
0
  else if (!strncmp (s, "-f", 2))
333
0
    {
334
0
      se->op = SELECT_ISTRUE;
335
0
      se->not = 1;
336
0
      s += 2;
337
0
    }
338
0
  else if (!strncmp (s, "-t", 2))
339
0
    {
340
0
      se->op = SELECT_ISTRUE;
341
0
      s += 2;
342
0
    }
343
0
  else if (!strncmp (s, "-le", 3))
344
0
    {
345
0
      se->op = SELECT_STRLE;
346
0
      s += 3;
347
0
    }
348
0
  else if (!strncmp (s, "-ge", 3))
349
0
    {
350
0
      se->op = SELECT_STRGE;
351
0
      s += 3;
352
0
    }
353
0
  else if (!strncmp (s, "-lt", 3))
354
0
    {
355
0
      se->op = SELECT_STRLT;
356
0
      s += 3;
357
0
    }
358
0
  else if (!strncmp (s, "-gt", 3))
359
0
    {
360
0
      se->op = SELECT_STRGT;
361
0
      s += 3;
362
0
    }
363
0
  else
364
0
    {
365
0
      log_error ("invalid operator in expression\n");
366
0
      recsel_release (se_head);
367
0
      xfree (expr_buffer);
368
0
      return my_error (GPG_ERR_INV_OP);
369
0
    }
370
371
  /* We require that a space is used if the value starts with any of
372
     the operator characters.  */
373
0
  if (se->op == SELECT_NONEMPTY || se->op == SELECT_ISTRUE)
374
0
    ;
375
0
  else if (strchr ("=<>!~", *s))
376
0
    {
377
0
      log_error ("invalid operator in expression\n");
378
0
      recsel_release (se_head);
379
0
      xfree (expr_buffer);
380
0
      return my_error (GPG_ERR_INV_OP);
381
0
    }
382
383
0
  if (*s == ' ' || *s == '\t')
384
0
    s++;
385
0
  if (!notrim)
386
0
    while (*s == ' ' || *s == '\t')
387
0
      s++;
388
389
0
  if (se->op == SELECT_NONEMPTY || se->op == SELECT_ISTRUE)
390
0
    {
391
0
      if (*s)
392
0
        {
393
0
          log_error ("value given for -n or -z\n");
394
0
          recsel_release (se_head);
395
0
          xfree (expr_buffer);
396
0
          return my_error (GPG_ERR_SYNTAX);
397
0
        }
398
0
    }
399
0
  else
400
0
    {
401
0
      if (!*s)
402
0
        {
403
0
          log_error ("no value given in expression\n");
404
0
          recsel_release (se_head);
405
0
          xfree (expr_buffer);
406
0
          return my_error (GPG_ERR_MISSING_VALUE);
407
0
        }
408
0
    }
409
410
0
  se->name[s0 - expr] = 0;
411
0
  trim_spaces (se->name);
412
0
  if (!se->name[0])
413
0
    {
414
0
      log_error ("no field name given in expression\n");
415
0
      recsel_release (se_head);
416
0
      xfree (expr_buffer);
417
0
      return my_error (GPG_ERR_NO_NAME);
418
0
    }
419
420
0
  if (!notrim)
421
0
    trim_spaces (se->name + (s - expr));
422
0
  se->value = se->name + (s - expr);
423
0
  if (!se->value[0] && !(se->op == SELECT_NONEMPTY || se->op == SELECT_ISTRUE))
424
0
    {
425
0
      log_error ("no value given in expression\n");
426
0
      recsel_release (se_head);
427
0
      xfree (expr_buffer);
428
0
      return my_error (GPG_ERR_MISSING_VALUE);
429
0
    }
430
431
0
  se->numvalue = strtol (se->value, NULL, 0);
432
433
0
  if (next_lc)
434
0
    {
435
0
      disjun = next_lc[1] == '|';
436
0
      expr = next_lc + 2;
437
0
      goto next_term;
438
0
    }
439
440
  /* Read:y Append to passes last selector.  */
441
0
  if (!*selector)
442
0
    *selector = se_head;
443
0
  else
444
0
    {
445
0
      for (se2 = *selector; se2->next; se2 = se2->next)
446
0
        ;
447
0
      se2->next = se_head;
448
0
    }
449
450
0
  xfree (expr_buffer);
451
0
  return 0;
452
0
}
453
454
455
void
456
recsel_release (recsel_expr_t a)
457
0
{
458
0
  while (a)
459
0
    {
460
0
      recsel_expr_t tmp = a->next;
461
0
      xfree (a);
462
0
      a = tmp;
463
0
    }
464
0
}
465
466
467
int
468
recsel_set_debug (int value)
469
0
{
470
0
  int old = recsel_debug;
471
0
  recsel_debug = value;
472
0
  return old;
473
0
}
474
475
476
void
477
recsel_dump (recsel_expr_t selector)
478
0
{
479
0
  recsel_expr_t se;
480
481
0
  log_debug ("--- Begin selectors ---\n");
482
0
  for (se = selector; se; se = se->next)
483
0
    {
484
0
      log_debug ("%s %s %s %s %s '%s'\n",
485
0
                 se==selector? "  ": (se->disjun? "||":"&&"),
486
0
                 se->xcase?  "-c":"  ",
487
0
                 se->lefta?  "-^":"  ",
488
0
                 se->name,
489
0
                 se->op == SELECT_SAME?    (se->not? "<>":"= "):
490
0
                 se->op == SELECT_SUB?     (se->not? "!~":"=~"):
491
0
                 se->op == SELECT_NONEMPTY?(se->not? "-z":"-n"):
492
0
                 se->op == SELECT_ISTRUE?  (se->not? "-f":"-t"):
493
0
                 se->op == SELECT_EQ?      (se->not? "!=":"=="):
494
0
                 se->op == SELECT_LT?      "< ":
495
0
                 se->op == SELECT_LE?      "<=":
496
0
                 se->op == SELECT_GT?      "> ":
497
0
                 se->op == SELECT_GE?      ">=":
498
0
                 se->op == SELECT_STRLT?   "-lt":
499
0
                 se->op == SELECT_STRLE?   "-le":
500
0
                 se->op == SELECT_STRGT?   "-gt":
501
0
                 se->op == SELECT_STRGE?   "-ge":
502
0
                 /**/                      "[oops]",
503
0
                 se->value);
504
0
    }
505
0
  log_debug ("--- End selectors ---\n");
506
0
}
507
508
509
/* Return true if the record RECORD has been selected.  The GETVAL
510
 * function is called with COOKIE and the NAME of a property used in
511
 * the expression.  */
512
int
513
recsel_select (recsel_expr_t selector,
514
               const char *(*getval)(void *cookie, const char *propname),
515
               void *cookie)
516
0
{
517
0
  recsel_expr_t se;
518
0
  const char *value;
519
0
  size_t selen, valuelen;
520
0
  long numvalue;
521
0
  int result = 1;
522
523
0
  se = selector;
524
0
  while (se)
525
0
    {
526
0
      value = getval? getval (cookie, se->name) : NULL;
527
0
      if (recsel_debug)
528
0
        log_debug ("%s: name=%s got value '%s'\n", __func__, se->name, value);
529
0
      if (!value)
530
0
        {
531
0
          se = se->next;
532
0
          result = 0;
533
0
          continue;
534
0
        }
535
536
0
      if (!*value)
537
0
        {
538
          /* Field is empty.  */
539
0
          result = 0;
540
0
        }
541
0
      else /* Field has a value.  */
542
0
        {
543
0
          valuelen = strlen (value);
544
0
          numvalue = strtol (value, NULL, 0);
545
0
          selen = strlen (se->value);
546
547
0
          switch (se->op)
548
0
            {
549
0
            case SELECT_SAME:
550
0
              if (se->xcase)
551
0
                result = (valuelen==selen && !memcmp (value,se->value,selen));
552
0
              else
553
0
                result = (valuelen==selen && !memicmp (value,se->value,selen));
554
0
              break;
555
0
            case SELECT_SUB:
556
0
              if (se->xcase)
557
0
                result = (gnupg_memstr (value, valuelen, se->value)
558
0
                          && (!se->lefta
559
0
                              || (selen <= valuelen
560
0
                                  && !memcmp (value, se->value, selen))));
561
0
              else
562
0
                result = (memistr (value, valuelen, se->value)
563
0
                          && (!se->lefta
564
0
                              || (selen <= valuelen
565
0
                                  && !memicmp (value, se->value, selen))));
566
0
              break;
567
0
            case SELECT_NONEMPTY:
568
0
              result = !!valuelen;
569
0
              break;
570
0
            case SELECT_ISTRUE:
571
0
              result = !!numvalue;
572
0
              break;
573
0
            case SELECT_EQ:
574
0
              result = (numvalue == se->numvalue);
575
0
              break;
576
0
            case SELECT_GT:
577
0
              result = (numvalue > se->numvalue);
578
0
              break;
579
0
            case SELECT_GE:
580
0
              result = (numvalue >= se->numvalue);
581
0
              break;
582
0
            case SELECT_LT:
583
0
              result = (numvalue < se->numvalue);
584
0
              break;
585
0
            case SELECT_LE:
586
0
              result = (numvalue <= se->numvalue);
587
0
              break;
588
0
            case SELECT_STRGT:
589
0
              if (se->xcase)
590
0
                result = strcmp (value, se->value) > 0;
591
0
              else
592
0
                result = strcasecmp (value, se->value) > 0;
593
0
              break;
594
0
            case SELECT_STRGE:
595
0
              if (se->xcase)
596
0
                result = strcmp (value, se->value) >= 0;
597
0
              else
598
0
                result = strcasecmp (value, se->value) >= 0;
599
0
              break;
600
0
            case SELECT_STRLT:
601
0
              if (se->xcase)
602
0
                result = strcmp (value, se->value) < 0;
603
0
              else
604
0
                result = strcasecmp (value, se->value) < 0;
605
0
              break;
606
0
            case SELECT_STRLE:
607
0
              if (se->xcase)
608
0
                result = strcmp (value, se->value) <= 0;
609
0
              else
610
0
                result = strcasecmp (value, se->value) <= 0;
611
0
              break;
612
0
            }
613
0
        }
614
615
0
      if (se->not)
616
0
        result = !result;
617
618
0
      if (result)
619
0
        {
620
          /* This expression evaluated to true.  See whether there are
621
             remaining expressions in this conjunction.  */
622
0
          if (!se->next || se->next->disjun)
623
0
            break; /* All expressions are true.  Return True.  */
624
0
          se = se->next;  /* Test the next.  */
625
0
        }
626
0
      else
627
0
        {
628
          /* This expression evaluated to false and thus the
629
           * conjunction evaluates to false.  We skip over the
630
           * remaining expressions of this conjunction and continue
631
           * with the next disjunction if any.  */
632
0
          do
633
0
            se = se->next;
634
0
          while (se && !se->disjun);
635
0
        }
636
0
    }
637
638
0
  if (recsel_debug)
639
0
    log_debug ("%s: result=%d\n", __func__, result);
640
0
  return result;
641
0
}