Coverage Report

Created: 2026-07-16 07:09

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/pigeonhole/src/lib-sieve/mcht-matches.c
Line
Count
Source
1
/* Copyright (c) Pigeonhole authors, see top-level COPYING file */
2
3
/* Match-type ':matches'
4
 */
5
6
#include "lib.h"
7
#include "str.h"
8
9
#include "sieve-match-types.h"
10
#include "sieve-comparators.h"
11
#include "sieve-interpreter.h"
12
#include "sieve-match.h"
13
14
#include <string.h>
15
#include <stdio.h>
16
17
/*
18
 * Forward declarations
19
 */
20
21
static int
22
mcht_matches_match_key(struct sieve_match_context *mctx,
23
           const char *val, size_t val_size,
24
           const char *key, size_t key_size);
25
26
/*
27
 * Match-type object
28
 */
29
30
const struct sieve_match_type_def matches_match_type = {
31
  SIEVE_OBJECT("matches", &match_type_operand, SIEVE_MATCH_TYPE_MATCHES),
32
  .validate_context = sieve_match_substring_validate_context,
33
  .match_key = mcht_matches_match_key
34
};
35
36
/*
37
 * Match-type implementation
38
 */
39
40
/* Quick 'n dirty debug */
41
//#define MATCH_DEBUG
42
#ifdef MATCH_DEBUG
43
#define debug_printf(...) printf ("match debug: " __VA_ARGS__)
44
#else
45
#define debug_printf(...)
46
#endif
47
48
/* FIXME: Naive implementation, substitute this with dovecot src/lib/str-find.c
49
 *
50
 * The inner loop polls the interpreter CPU time limit periodically so that a
51
 * single O(N*M) search on a large value cannot run for many times the
52
 * configured sieve_max_cpu_time. Returns 1 on match, 0 on exhaustion, or -1
53
 * when the CPU time limit was exceeded (mctx->exec_status is set).
54
 */
55
0
#define SIEVE_MATCHES_CPU_CHECK_INTERVAL 4096
56
57
static int
58
_string_find(struct sieve_match_context *mctx,
59
       const struct sieve_comparator *cmp,
60
       const char **valp, const char *vend,
61
       const char **keyp, const char *kend,
62
       unsigned int *counter)
63
0
{
64
0
  while ((*valp < vend) && (*keyp < kend)) {
65
0
    if (!cmp->def->char_match(cmp, valp, vend, keyp, kend))
66
0
      (*valp)++;
67
68
0
    if (++(*counter) >= SIEVE_MATCHES_CPU_CHECK_INTERVAL) {
69
0
      *counter = 0;
70
0
      if (sieve_runtime_cpu_limit_exceeded(mctx->runenv)) {
71
0
        sieve_runtime_error(
72
0
          mctx->runenv, NULL,
73
0
          "execution exceeded CPU time limit");
74
0
        mctx->exec_status =
75
0
          SIEVE_EXEC_RESOURCE_LIMIT;
76
0
        return -1;
77
0
      }
78
0
    }
79
0
  }
80
81
0
  return (*keyp == kend ? 1 : 0);
82
0
}
83
84
static char
85
_scan_key_section(string_t *section, const char **wcardp, const char *key_end)
86
0
{
87
  /* Find next wildcard and resolve escape sequences */
88
0
  str_truncate(section, 0);
89
0
  while (*wcardp < key_end && **wcardp != '*' && **wcardp != '?') {
90
0
    if (**wcardp == '\\') {
91
0
      (*wcardp)++;
92
0
      if (*wcardp == key_end) {
93
0
        str_append_c(section,'\\');
94
0
        break;
95
0
      }
96
0
    }
97
0
    str_append_c(section, **wcardp);
98
0
    (*wcardp)++;
99
0
  }
100
101
  /* Record wildcard character or \0 */
102
0
  if (*wcardp < key_end) {
103
0
    return **wcardp;
104
0
  }
105
106
0
  i_assert(*wcardp == key_end);
107
0
  return '\0';
108
0
}
109
110
static int
111
mcht_matches_match_key(struct sieve_match_context *mctx,
112
           const char *val, size_t val_size,
113
           const char *key, size_t key_size)
114
0
{
115
0
  const struct sieve_comparator *cmp = mctx->comparator;
116
0
  struct sieve_match_values *mvalues;
117
0
  string_t *mvalue = NULL, *mchars = NULL;
118
0
  string_t *section, *subsection;
119
0
  const char *vend, *kend, *vp, *kp, *wp, *pvp;
120
0
  bool backtrack = FALSE; /* TRUE: match of '?'-connected sections failed
121
         */
122
0
  char wcard = '\0';      /* Current wildcard */
123
0
  char next_wcard = '\0'; /* Next  widlcard */
124
0
  unsigned int key_offset = 0;
125
0
  unsigned int counter = 0;
126
127
0
  if (cmp->def == NULL || cmp->def->char_match == NULL)
128
0
    return 0;
129
130
  /* Key sections */
131
0
  section = t_str_new(32);    /* Section (after beginning or *) */
132
0
  subsection = t_str_new(32); /* Sub-section (after ?) */
133
134
  /* Mark end of value and key */
135
0
  vend = (const char *) val + val_size;
136
0
  kend = (const char *) key + key_size;
137
138
  /* Initialize pointers */
139
0
  vp = val;                   /* Value pointer */
140
0
  kp = key;                   /* Key pointer */
141
0
  wp = key;                   /* Wildcard (key) pointer */
142
143
  /* Start match values list if requested */
144
0
  if ((mvalues = sieve_match_values_start(mctx->runenv)) != NULL) {
145
    /* Skip ${0} for now; added when match succeeds */
146
0
    sieve_match_values_add(mvalues, NULL);
147
148
0
    mvalue = t_str_new(32);     /* Match value (*) */
149
0
    mchars = t_str_new(32);     /* Match characters (.?..?.??) */
150
0
  }
151
152
  /* Match the pattern:
153
       <pattern> = <section>*<section>*<section>...
154
       <section> = <sub-section>?<sub-section>?<sub-section>...
155
156
     Escape sequences \? and \* need special attention.
157
   */
158
159
0
  debug_printf("=== Start ===\n");
160
0
  debug_printf("  key:   %s\n", t_strdup_until(key, kend));
161
0
  debug_printf("  value: %s\n", t_strdup_until(val, vend));
162
163
  /* Loop until either key or value ends */
164
0
  while (kp < kend && vp < vend) {
165
0
    const char *needle, *nend;
166
167
0
    if (++counter >= SIEVE_MATCHES_CPU_CHECK_INTERVAL) {
168
0
      counter = 0;
169
0
      if (sieve_runtime_cpu_limit_exceeded(mctx->runenv)) {
170
0
        sieve_runtime_error(
171
0
          mctx->runenv, NULL,
172
0
          "execution exceeded CPU time limit");
173
0
        mctx->exec_status =
174
0
          SIEVE_EXEC_RESOURCE_LIMIT;
175
0
        sieve_match_values_abort(&mvalues);
176
0
        return -1;
177
0
      }
178
0
    }
179
180
0
    if (!backtrack) {
181
      /* Search the next '*' wildcard in the key string */
182
183
0
      wcard = next_wcard;
184
185
      /* Find the needle to look for in the string */
186
0
      key_offset = 0;
187
0
      for (;;) {
188
0
        next_wcard =
189
0
          _scan_key_section(section, &wp, kend);
190
191
0
        if (wcard == '\0' || str_len(section) > 0)
192
0
          break;
193
0
        if (next_wcard == '*')
194
0
          break;
195
196
0
        if (wp < kend)
197
0
          wp++;
198
0
        else
199
0
          break;
200
0
        key_offset++;
201
0
      }
202
203
0
      debug_printf("found wildcard '%c' at pos [%d]\n",
204
0
             next_wcard, (int)(wp - key));
205
206
0
      if (mvalues != NULL)
207
0
        str_truncate(mvalue, 0);
208
0
    } else {
209
      /* Backtracked; '*' wildcard is retained */
210
0
      debug_printf("backtracked");
211
0
      backtrack = FALSE;
212
0
    }
213
214
    /* Determine what we are looking for */
215
0
    needle = str_c(section);
216
0
    nend = PTR_OFFSET(needle, str_len(section));
217
218
0
    debug_printf("  section needle:  '%s'\n",
219
0
           t_strdup_until(needle, nend));
220
0
    debug_printf("  section key:     '%s'\n",
221
0
           t_strdup_until(kp, kend));
222
0
    debug_printf("  section remnant: '%s'\n",
223
0
           t_strdup_until(wp, kend));
224
0
    debug_printf("  value remnant:   '%s'\n",
225
0
           t_strdup_until(vp, vend));
226
0
    debug_printf("  key offset:      %d\n", key_offset);
227
228
0
    pvp = vp;
229
0
    if (next_wcard == '\0') {
230
0
      if (wcard == '\0') {
231
        /* No current wildcard; match needs to happen
232
           right at the beginning */
233
0
        debug_printf("next_wcard = NULL && wcard = NUL; "
234
0
               "needle should be equal to value.\n");
235
236
0
        if ((vend - vp) != (nend - needle) ||
237
0
            !cmp->def->char_match(cmp, &vp, vend, &needle, nend)) {
238
0
          debug_printf("  key not equal to value\n");
239
0
          break;
240
0
        }
241
242
0
      } else {
243
0
        const char *qp, *qend;
244
0
        size_t slen;
245
246
        /* No more wildcards; find the needle substring
247
           at the end of string */
248
0
        debug_printf("next_wcard = NUL; "
249
0
               "must find needle at end\n");
250
251
        /* Check if the value is still large enough to
252
           contain both the '?'-matched prefix
253
           (key_offset chars) and the trailing section.
254
         */
255
0
        slen = str_len(section);
256
0
        if ((vp + slen + key_offset) > vend) {
257
0
          debug_printf("  wont match: "
258
0
                 "value is too short\n");
259
0
          break;
260
0
        }
261
262
        /* Move value pointer to where the needle should
263
           be */
264
0
        vp = vend - slen;
265
266
        /* Record match values */
267
0
        qend = vp;
268
0
        qp = vp - key_offset;
269
270
        /* Compare needle to end of value string */
271
0
        if (!cmp->def->char_match(cmp, &vp, vend,
272
0
                 &needle, nend)) {
273
0
          debug_printf("  match at end failed\n");
274
0
          break;
275
0
        }
276
277
        /* Add match values */
278
0
        if (mvalues != NULL) {
279
0
          i_assert(qp >= pvp);
280
0
          str_append_data(mvalue, pvp, qp - pvp);
281
282
          /* Append '*' match value */
283
0
          sieve_match_values_add(mvalues, mvalue);
284
285
          /* Append any initial '?' match values
286
           */
287
0
          for (; qp < qend; qp++) {
288
0
            sieve_match_values_add_char(
289
0
              mvalues, *qp);
290
0
          }
291
0
        }
292
0
      }
293
294
      /* Finish match */
295
0
      kp = kend;
296
0
      vp = vend;
297
298
0
      debug_printf("  matched end of value\n");
299
0
      break;
300
0
    } else {
301
      /* Next wildcard found; match needle before next
302
         wildcard */
303
304
      /* Stored value pointer for backtrack */
305
0
      const char *prv = NULL;
306
      /* Stored key pointer for backtrack */
307
0
      const char *prk = NULL;
308
      /* Stored wildcard pointer for backtrack */
309
0
      const char *prw = NULL;
310
0
      const char *chars;
311
312
      /* Reset '?' match values */
313
0
      if (mvalues != NULL)
314
0
        str_truncate(mchars, 0);
315
316
0
      if (wcard == '\0') {
317
        /* No current wildcard; match needs to happen
318
           right at the beginning */
319
0
        debug_printf("wcard = NUL; "
320
0
               "needle should be found at the beginning.\n");
321
0
        debug_printf("  begin needle: '%s'\n",
322
0
               t_strdup_until(needle, nend));
323
0
        debug_printf("  begin value:  '%s'\n",
324
0
               t_strdup_until(vp, vend));
325
326
0
        if (!cmp->def->char_match(cmp, &vp, vend, &needle, nend)) {
327
0
          debug_printf("  failed to find needle at beginning\n");
328
0
          break;
329
0
        }
330
331
0
      } else {
332
        /* Current wildcard present; match needle
333
           between current and next wildcard */
334
0
        debug_printf("wcard != NUL; "
335
0
               "must find needle at an offset (>= %d).\n",
336
0
               key_offset);
337
338
        /* Match may happen at any offset
339
           (>= key offset): find substring */
340
0
        vp += key_offset;
341
0
        if (vp >= vend) {
342
0
          debug_printf("  failed to find needle at an offset\n");
343
0
          break;
344
0
        }
345
0
        int fres = _string_find(mctx, cmp, &vp, vend,
346
0
              &needle, nend, &counter);
347
0
        if (fres < 0) {
348
0
          sieve_match_values_abort(&mvalues);
349
0
          return -1;
350
0
        }
351
0
        if (fres == 0) {
352
0
          debug_printf("  failed to find needle at an offset\n");
353
0
          break;
354
0
        }
355
356
0
        prv = vp - str_len(section);
357
0
        prk = kp;
358
0
        prw = wp;
359
360
        /* Append match values */
361
0
        if (mvalues != NULL) {
362
0
          const char *qend = vp - str_len(section);
363
0
          const char *qp = qend - key_offset;
364
365
          /* Append '*' match value */
366
0
          str_append_data(mvalue, pvp, qp - pvp);
367
368
          /* Append any initial '?' match values
369
             (those that caused the key offset).
370
           */
371
0
          for (; qp < qend; qp++)
372
0
            str_append_c(mchars, *qp);
373
0
        }
374
0
      }
375
376
      /* Update wildcard and key pointers for next wildcard
377
         scan */
378
0
      if (wp < kend)
379
0
        wp++;
380
0
      kp = wp;
381
382
      /* Scan successive '?' wildcards */
383
0
      while (next_wcard == '?') {
384
0
        bool match_failed_empty = FALSE;
385
386
0
        debug_printf("next_wcard = '?'; "
387
0
               "need to match arbitrary character\n");
388
389
0
        i_assert(vp <= vend);
390
0
        if (vp == vend)
391
0
          match_failed_empty = TRUE;
392
0
        else {
393
          /* Add match value */
394
0
          if (mvalues != NULL)
395
0
            str_append_c(mchars, *vp);
396
397
0
          vp++;
398
399
          /* Scan for next '?' wildcard */
400
0
          next_wcard = _scan_key_section(subsection, &wp, kend);
401
0
          debug_printf("found next wildcard '%c' at pos [%d] "
402
0
                 "(fixed match)\n",
403
0
                 next_wcard, (int)(wp - key));
404
405
          /* Determine what we are looking for */
406
0
          needle = str_c(subsection);
407
0
          nend = needle + str_len(subsection);
408
409
0
          debug_printf("  sub key:       '%s'\n",
410
0
                 t_strdup_until(needle, nend));
411
0
          debug_printf("  value remnant: '%s'\n",
412
0
                 t_strdup_until(vp, vend));
413
0
        }
414
415
        /* Try matching the needle at fixed position */
416
0
        if (match_failed_empty ||
417
0
            (needle == nend && next_wcard == '\0' && vp < vend) ||
418
0
            !cmp->def->char_match(cmp, &vp, vend, &needle, nend)) {
419
420
          /* Match failed: now we have a problem. We need to
421
             backtrack to the previous '*' wildcard occurrence
422
             and start scanning for the next possible match.
423
           */
424
425
0
          debug_printf("  failed fixed match\n");
426
427
          /* Start backtrack */
428
0
          if (prv != NULL && prv + 1 < vend) {
429
            /* Restore pointers */
430
0
            vp = prv;
431
0
            kp = prk;
432
0
            wp = prw;
433
434
            /* Skip forward one value character to scan
435
               the next possible match */
436
0
            if (mvalues != NULL)
437
0
              str_append_c(mvalue, *vp);
438
0
            vp++;
439
440
            /* Set wildcard state appropriately */
441
0
            wcard = '*';
442
0
            next_wcard = '?';
443
444
            /* Backtrack */
445
0
            backtrack = TRUE;
446
447
0
            debug_printf("  BACKTRACK\n");
448
0
          }
449
450
          /* Break '?' wildcard scanning loop */
451
0
          break;
452
0
        }
453
454
        /* Update wildcard and key pointers for next wildcard scan */
455
0
        if (wp < kend)
456
0
          wp++;
457
0
        kp = wp;
458
0
      }
459
460
0
      if (!backtrack) {
461
0
        unsigned int i;
462
463
0
        if (next_wcard == '?') {
464
0
          debug_printf("failed to match '?'\n");
465
0
          break;
466
0
        }
467
468
0
        if (mvalues != NULL) {
469
0
          if (prv != NULL)
470
0
            sieve_match_values_add(mvalues, mvalue);
471
472
0
          chars = (const char *) str_data(mchars);
473
474
0
          for (i = 0; i < str_len(mchars); i++)
475
0
            sieve_match_values_add_char(mvalues, chars[i]);
476
0
        }
477
478
0
        if (next_wcard != '*') {
479
0
          debug_printf("failed to match at end of string\n");
480
0
          break;
481
0
        }
482
0
      }
483
0
    }
484
485
    /* Check whether string ends in a wildcard
486
       (avoid scanning the rest of the string)
487
     */
488
0
    if (kp == kend && next_wcard == '*') {
489
      /* Add the rest of the string as match value */
490
0
      if (mvalues != NULL) {
491
0
        str_truncate(mvalue, 0);
492
0
        i_assert(vend >= vp);
493
0
        str_append_data(mvalue, vp, vend - vp);
494
0
        sieve_match_values_add(mvalues, mvalue);
495
0
      }
496
497
      /* Finish match */
498
0
      kp = kend;
499
0
      vp = vend;
500
501
0
      debug_printf("key ends with '*'\n");
502
0
      break;
503
0
    }
504
505
0
    debug_printf("== Loop ==\n");
506
0
  }
507
508
  /* Eat away a trailing series of *s */
509
0
  if (vp == vend) {
510
0
    while (kp < kend && *kp == '*')
511
0
      kp++;
512
0
  }
513
514
  /* By definition, the match is only successful if both value and key
515
     pattern are exhausted and we're not still trying to match '?' while
516
     the value is empty.
517
   */
518
0
  bool matched = (kp == kend && vp == vend && next_wcard != '?');
519
520
0
  debug_printf("=== Finish ===\n");
521
0
  debug_printf("  result: %s\n", matched ? "true" : "false");
522
523
0
  if (matched) {
524
    /* Activate new match values after successful match */
525
0
    if (mvalues != NULL) {
526
      /* Set ${0} */
527
0
      string_t *matched = str_new_const(
528
0
        pool_datastack_create(), val, val_size);
529
0
      sieve_match_values_set(mvalues, 0, matched);
530
531
      /* Commit new match values */
532
0
      sieve_match_values_commit(mctx->runenv, &mvalues);
533
0
    }
534
0
    return 1;
535
0
  }
536
537
  /* No match; drop collected match values */
538
0
  sieve_match_values_abort(&mvalues);
539
0
  return 0;
540
0
}