Coverage Report

Created: 2026-07-30 06:43

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/pigeonhole/src/lib-sieve/plugins/regex/mcht-regex.c
Line
Count
Source
1
/* Copyright (c) Pigeonhole authors, see top-level COPYING file */
2
3
/* Match-type ':regex'
4
 */
5
6
#include "lib.h"
7
#include "mempool.h"
8
#include "buffer.h"
9
#include "array.h"
10
#include "str.h"
11
#include "str-sanitize.h"
12
13
#include "sieve-common.h"
14
#include "sieve-limits.h"
15
#include "sieve-ast.h"
16
#include "sieve-stringlist.h"
17
#include "sieve-commands.h"
18
#include "sieve-validator.h"
19
#include "sieve-interpreter.h"
20
#include "sieve-comparators.h"
21
#include "sieve-match-types.h"
22
#include "sieve-match.h"
23
24
#include "ext-regex-common.h"
25
#include "dregex.h"
26
27
#include <sys/types.h>
28
#include <ctype.h>
29
30
/*
31
 * Configuration
32
 */
33
34
#define MCHT_REGEX_MAX_SUBSTITUTIONS SIEVE_MAX_MATCH_VALUES
35
36
/*
37
 * Match type
38
 */
39
40
static bool
41
mcht_regex_validate_context(struct sieve_validator *valdtr,
42
          struct sieve_ast_argument *arg,
43
          struct sieve_match_type_context *ctx,
44
          struct sieve_ast_argument *key_arg);
45
46
static void mcht_regex_match_init(struct sieve_match_context *mctx);
47
static int
48
mcht_regex_match_keys(struct sieve_match_context *mctx,
49
          const char *val, size_t val_size,
50
          struct sieve_stringlist *key_list);
51
static void mcht_regex_match_deinit(struct sieve_match_context *mctx);
52
53
const struct sieve_match_type_def regex_match_type = {
54
  SIEVE_OBJECT("regex", &regex_match_type_operand, 0),
55
  .validate_context = mcht_regex_validate_context,
56
  .match_init = mcht_regex_match_init,
57
  .match_keys = mcht_regex_match_keys,
58
  .match_deinit = mcht_regex_match_deinit,
59
};
60
61
/*
62
 * Match type validation
63
 */
64
65
static int
66
mcht_regex_validate_regexp(struct sieve_validator *valdtr,
67
         struct sieve_match_type_context *mtctx ATTR_UNUSED,
68
         struct sieve_ast_argument *key, int cflags)
69
0
{
70
0
  int ret;
71
0
  const char *dregex_str = sieve_ast_argument_strc(key);
72
0
  const char *error;
73
74
0
  struct dregex_code *code = dregex_code_create();
75
0
  ret = dregex_code_compile(code, dregex_str, cflags, &error);
76
0
  dregex_code_free(&code);
77
78
0
  if (ret != 0) {
79
0
    sieve_argument_validate_error(
80
0
      valdtr, key,
81
0
      "invalid regular expression '%s' for regex match: %s",
82
0
      str_sanitize(dregex_str, 128),
83
0
      error);
84
0
    return -1;
85
0
  }
86
87
0
  return 1;
88
0
}
89
90
struct _regex_key_context {
91
  struct sieve_validator *valdtr;
92
  struct sieve_match_type_context *mtctx;
93
  int cflags;
94
};
95
96
static int
97
mcht_regex_validate_key_argument(void *context, struct sieve_ast_argument *key)
98
0
{
99
0
  struct _regex_key_context *keyctx = (struct _regex_key_context *)context;
100
101
  /* FIXME: We can currently only handle string literal argument, so
102
     variables are not allowed.
103
   */
104
0
  if (sieve_argument_is_string_literal(key)) {
105
0
    return mcht_regex_validate_regexp(keyctx->valdtr, keyctx->mtctx,
106
0
              key, keyctx->cflags);
107
0
  }
108
0
  return 1;
109
0
}
110
111
static bool
112
mcht_regex_validate_context(struct sieve_validator *valdtr,
113
          struct sieve_ast_argument *arg ATTR_UNUSED,
114
          struct sieve_match_type_context *mtctx,
115
          struct sieve_ast_argument *key_arg)
116
0
{
117
0
  const struct sieve_comparator *cmp = mtctx->comparator;
118
0
  int cflags = DREGEX_NOSUB;
119
0
  struct _regex_key_context keyctx;
120
0
  struct sieve_ast_argument *kitem;
121
122
0
  if (cmp != NULL) {
123
0
    if (sieve_comparator_is(cmp, i_ascii_casemap_comparator))
124
0
      cflags |= DREGEX_ICASE | DREGEX_ASCII_ONLY;
125
0
    else if (sieve_comparator_is(cmp, i_octet_comparator))
126
0
      cflags |= DREGEX_ASCII_ONLY;
127
0
    else if (sieve_comparator_is(cmp, i_unicode_casemap_comparator))
128
0
      cflags |= DREGEX_ICASE;
129
0
    else {
130
0
      sieve_argument_validate_error(
131
0
        valdtr, mtctx->argument,
132
0
        "regex match type only supports "
133
0
        "i;octet, i;ascii-casemap and i;unicode-casemap comparators");
134
0
      return FALSE;
135
0
    }
136
0
  }
137
138
  /* Validate regular expression keys */
139
140
0
  keyctx.valdtr = valdtr;
141
0
  keyctx.mtctx = mtctx;
142
0
  keyctx.cflags = cflags;
143
144
0
  kitem = key_arg;
145
0
  if (sieve_ast_stringlist_map(&kitem, &keyctx,
146
0
             mcht_regex_validate_key_argument) <= 0)
147
0
    return FALSE;
148
149
0
  return TRUE;
150
0
}
151
152
/*
153
 * Match type implementation
154
 */
155
156
struct mcht_regex_key {
157
  struct dregex_code *regexp;
158
  int status;
159
};
160
161
struct mcht_regex_context {
162
  ARRAY(struct mcht_regex_key) reg_expressions;
163
  ARRAY_TYPE(const_string) pmatch;
164
  bool all_compiled:1;
165
  bool capture_groups;
166
};
167
168
static void mcht_regex_match_init(struct sieve_match_context *mctx)
169
0
{
170
0
  pool_t pool = mctx->pool;
171
0
  struct mcht_regex_context *ctx;
172
173
  /* Create context */
174
0
  ctx = p_new(pool, struct mcht_regex_context, 1);
175
176
  /* Create storage for match values if match values are requested */
177
0
  ctx->capture_groups = sieve_match_values_are_enabled(mctx->runenv);
178
179
  /* Assign context */
180
0
  mctx->data = ctx;
181
0
}
182
183
static int
184
mcht_regex_match_key(struct sieve_match_context *mctx, const char *val,
185
         struct dregex_code *code)
186
0
{
187
0
  struct mcht_regex_context *ctx =
188
0
    (struct mcht_regex_context *)mctx->data;
189
0
  const char *error;
190
0
  int ret;
191
0
  ARRAY_TYPE(const_string) pmatch = ARRAY_INIT;
192
193
0
  if (ctx->capture_groups)
194
0
    i_array_init(&pmatch, 8);
195
196
  /* Execute regex */
197
198
0
  if (!ctx->capture_groups)
199
0
    ret = dregex_code_match(code, val, &error);
200
0
  else
201
0
    ret = dregex_code_match_groups(code, val, &pmatch, &error);
202
203
  /* Handle match values if necessary */
204
205
0
  if (ret > 0 && ctx->capture_groups && array_count(&pmatch) > 0) {
206
0
    struct sieve_match_values *mvalues;
207
0
    string_t *subst = t_str_new(32);
208
0
    const char *mvalue;
209
210
    /* Start new list of match values */
211
0
    mvalues = sieve_match_values_start(mctx->runenv);
212
213
0
    i_assert(mvalues != NULL);
214
215
0
    array_foreach_elem(&pmatch, mvalue) {
216
0
      str_append(subst, mvalue);
217
0
      sieve_match_values_add(mvalues, subst);
218
0
      str_truncate(subst, 0);
219
0
    }
220
221
    /* Substitute the new match values */
222
0
    sieve_match_values_commit(mctx->runenv, &mvalues);
223
0
  }
224
225
0
  array_free(&pmatch);
226
0
  if (ret > 0)
227
0
    return 1;
228
0
  return 0;
229
0
}
230
231
static int
232
mcht_regex_match_keys(struct sieve_match_context *mctx,
233
          const char *val, size_t val_size ATTR_UNUSED,
234
          struct sieve_stringlist *key_list)
235
0
{
236
0
  const struct sieve_runtime_env *renv = mctx->runenv;
237
0
  bool trace = sieve_runtime_trace_active(renv, SIEVE_TRLVL_MATCHING);
238
0
  struct mcht_regex_context *ctx =
239
0
    (struct mcht_regex_context *)mctx->data;
240
0
  const struct sieve_comparator *cmp = mctx->comparator;
241
0
  int match;
242
243
0
  if (!ctx->all_compiled) {
244
0
    string_t *key_item = NULL;
245
0
    unsigned int i;
246
0
    int ret;
247
248
    /* Regular expressions still need to be compiled */
249
250
0
    if (!array_is_created(&ctx->reg_expressions))
251
0
      p_array_init(&ctx->reg_expressions, mctx->pool, 16);
252
253
0
    i = 0;
254
0
    match = 0;
255
0
    while (match == 0 &&
256
0
           (ret = sieve_stringlist_next_item(key_list, &key_item)) > 0) {
257
258
0
      T_BEGIN {
259
0
        struct mcht_regex_key *rkey;
260
261
0
        if (i >= array_count(&ctx->reg_expressions)) {
262
0
          int cflags = 0;
263
264
0
          rkey = array_append_space(&ctx->reg_expressions);
265
266
          /* Configure case-sensitivity according to comparator */
267
0
          if (sieve_comparator_is(cmp, i_octet_comparator))
268
0
            cflags |= DREGEX_ASCII_ONLY;
269
0
          else if (sieve_comparator_is(cmp, i_ascii_casemap_comparator))
270
0
            cflags |= (DREGEX_ICASE | DREGEX_ASCII_ONLY);
271
0
          else if (sieve_comparator_is(cmp, i_unicode_casemap_comparator))
272
0
            cflags |= DREGEX_ICASE;
273
0
          else
274
0
            rkey->status = -1; /* Not supported */
275
276
0
          if (rkey->status >= 0) {
277
0
            const char *dregex_str = str_c(key_item);
278
0
            const char *error;
279
0
            int rxret;
280
281
            /* Indicate whether match values need to be produced */
282
0
            if (!ctx->capture_groups)
283
0
              cflags |= DREGEX_NOSUB;
284
285
0
            struct dregex_code *code = dregex_code_create();
286
            /* Compile regular expression */
287
0
            rxret = dregex_code_compile(code, dregex_str, cflags, &error);
288
0
            if (rxret != 0) {
289
0
              sieve_runtime_error(renv, NULL,
290
0
                "invalid regular expression '%s' for regex match: %s",
291
0
                str_sanitize(dregex_str, 128),
292
0
                error);
293
0
              rkey->status = -1;
294
0
              dregex_code_free(&code);
295
0
            } else {
296
0
              rkey->status = 1;
297
0
              rkey->regexp = code;
298
0
            }
299
0
          }
300
0
        } else {
301
0
          rkey = array_idx_modifiable(&ctx->reg_expressions, i);
302
0
        }
303
304
0
        if (rkey->status > 0) {
305
0
          match = mcht_regex_match_key(
306
0
            mctx, val, rkey->regexp);
307
308
0
          if (trace) {
309
0
            sieve_runtime_trace(renv, 0,
310
0
              "with regex '%s' [id=%u] => %d",
311
0
              str_sanitize(str_c(key_item), 80),
312
0
              i, match);
313
0
          }
314
0
        }
315
0
      } T_END;
316
317
0
      i++;
318
0
    }
319
320
0
    if (ret == 0) {
321
0
      ctx->all_compiled = TRUE;
322
0
    } else if (ret < 0) {
323
0
      mctx->exec_status = key_list->exec_status;
324
0
      match = -1;
325
0
    }
326
327
0
  } else {
328
0
    const struct mcht_regex_key *rkeys;
329
0
    unsigned int i, count;
330
331
    /* Regular expressions are compiled */
332
333
0
    rkeys = array_get(&ctx->reg_expressions, &count);
334
335
0
    i = 0;
336
0
    match = 0;
337
0
    while (match == 0 && i < count) {
338
0
      if (rkeys[i].status > 0) {
339
0
        match = mcht_regex_match_key(
340
0
          mctx, val, rkeys[i].regexp);
341
342
0
        if (trace) {
343
0
          sieve_runtime_trace(renv, 0,
344
0
            "with compiled regex [id=%u] => %d",
345
0
            i, match);
346
0
        }
347
0
      }
348
349
0
      i++;
350
0
    }
351
0
  }
352
353
0
  return match;
354
0
}
355
356
void mcht_regex_match_deinit(struct sieve_match_context *mctx)
357
0
{
358
0
  struct mcht_regex_context *ctx =
359
0
    (struct mcht_regex_context *)mctx->data;
360
0
  struct mcht_regex_key *rkeys;
361
0
  unsigned int count, i;
362
363
  /* Clean up compiled regular expressions */
364
0
  if (array_is_created(&ctx->reg_expressions)) {
365
0
    rkeys = array_get_modifiable(&ctx->reg_expressions, &count);
366
0
    for (i = 0; i < count; i++)
367
0
      dregex_code_free(&rkeys[i].regexp);
368
0
  }
369
0
}