Coverage Report

Created: 2026-10-17 06:32

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/pigeonhole/src/lib-sieve/plugins/duplicate/tst-duplicate.c
Line
Count
Source
1
/* Copyright (c) Pigeonhole authors, see top-level COPYING file */
2
3
#include "lib.h"
4
#include "mail-storage.h"
5
6
#include "sieve-common.h"
7
#include "sieve-code.h"
8
#include "sieve-extensions.h"
9
#include "sieve-commands.h"
10
#include "sieve-validator.h"
11
#include "sieve-generator.h"
12
#include "sieve-interpreter.h"
13
#include "sieve-dump.h"
14
15
#include "ext-duplicate-settings.h"
16
#include "ext-duplicate-common.h"
17
18
/* Duplicate test
19
 *
20
 * Syntax:
21
 *   Usage: "duplicate" [":handle" <handle: string>]
22
 *                      [":header" <header-name: string> /
23
 *                          ":uniqueid" <value: string>]
24
 *                      [":seconds" <timeout: number>] [":last"]
25
 */
26
27
static bool
28
tst_duplicate_registered(struct sieve_validator *valdtr,
29
       const struct sieve_extension *ext,
30
       struct sieve_command_registration *cmd_reg);
31
static bool
32
tst_duplicate_generate(const struct sieve_codegen_env *cgenv,
33
           struct sieve_command *ctx);
34
35
const struct sieve_command_def tst_duplicate = {
36
  .identifier = "duplicate",
37
  .type = SCT_TEST,
38
  .positional_args = 0,
39
  .subtests = 0,
40
  .block_allowed = FALSE,
41
  .block_required = FALSE,
42
  .registered = tst_duplicate_registered,
43
  .generate = tst_duplicate_generate
44
};
45
46
/*
47
 * Duplicate test tags
48
 */
49
50
static bool
51
tst_duplicate_validate_number_tag(struct sieve_validator *valdtr,
52
          struct sieve_ast_argument **arg,
53
          struct sieve_command *cmd);
54
static bool
55
tst_duplicate_validate_string_tag(struct sieve_validator *valdtr,
56
          struct sieve_ast_argument **arg,
57
          struct sieve_command *cmd);
58
59
static const struct sieve_argument_def duplicate_seconds_tag = {
60
  .identifier = "seconds",
61
  .validate = tst_duplicate_validate_number_tag
62
};
63
64
static const struct sieve_argument_def duplicate_header_tag = {
65
  .identifier = "header",
66
  .validate = tst_duplicate_validate_string_tag
67
};
68
69
static const struct sieve_argument_def duplicate_uniqueid_tag = {
70
  .identifier = "uniqueid",
71
  .validate = tst_duplicate_validate_string_tag
72
};
73
74
static const struct sieve_argument_def duplicate_handle_tag = {
75
  .identifier = "handle",
76
  .validate = tst_duplicate_validate_string_tag
77
};
78
79
static const struct sieve_argument_def duplicate_last_tag = {
80
  .identifier = "last"
81
};
82
83
/* Codes for optional arguments */
84
85
enum tst_duplicate_optional {
86
  OPT_END,
87
  OPT_SECONDS,
88
  OPT_HEADER,
89
  OPT_UNIQUEID,
90
  OPT_LAST,
91
  OPT_HANDLE
92
};
93
94
/*
95
 * Duplicate operation
96
 */
97
98
static bool
99
tst_duplicate_operation_dump(const struct sieve_dumptime_env *denv,
100
           sieve_size_t *address);
101
static int
102
tst_duplicate_operation_execute(const struct sieve_runtime_env *renv,
103
        sieve_size_t *address);
104
105
const struct sieve_operation_def tst_duplicate_operation = {
106
  .mnemonic = "DUPLICATE",
107
  .ext_def = &duplicate_extension,
108
  .dump = tst_duplicate_operation_dump,
109
  .execute = tst_duplicate_operation_execute
110
};
111
112
/*
113
 * Tag validation
114
 */
115
116
static bool
117
tst_duplicate_validate_number_tag(struct sieve_validator *valdtr,
118
          struct sieve_ast_argument **arg,
119
          struct sieve_command *cmd)
120
0
{
121
0
  const struct sieve_extension *ext = sieve_argument_ext(*arg);
122
0
  const struct ext_duplicate_context *extctx = ext->context;
123
0
  struct sieve_ast_argument *tag = *arg;
124
0
  sieve_number_t seconds;
125
126
  /* Detach the tag itself */
127
0
  *arg = sieve_ast_arguments_detach(*arg,1);
128
129
  /* Check syntax:
130
   *   :seconds number
131
   */
132
0
  if (!sieve_validate_tag_parameter(valdtr, cmd, tag, *arg, NULL, 0,
133
0
            SAAT_NUMBER, FALSE))
134
0
    return FALSE;
135
136
0
  seconds = sieve_ast_argument_number(*arg);
137
  /* Enforce :days <= max_period */
138
0
  if (extctx->set->max_period > 0 && seconds > extctx->set->max_period) {
139
0
    seconds = extctx->set->max_period;
140
141
0
    sieve_argument_validate_warning(
142
0
      valdtr, *arg,
143
0
      "specified :seconds value '%llu' is over the maximum",
144
0
      (unsigned long long)seconds);
145
0
  }
146
147
0
  sieve_ast_argument_number_set(*arg, seconds);
148
149
  /* Skip parameter */
150
0
  *arg = sieve_ast_argument_next(*arg);
151
0
  return TRUE;
152
0
}
153
154
static bool
155
tst_duplicate_validate_string_tag(struct sieve_validator *valdtr,
156
          struct sieve_ast_argument **arg,
157
          struct sieve_command *cmd)
158
0
{
159
0
  const struct sieve_extension *ext = cmd->ext;
160
0
  struct sieve_ast_argument *tag = *arg;
161
162
  /* Detach the tag itself */
163
0
  *arg = sieve_ast_arguments_detach(*arg,1);
164
165
  /* Check syntax:
166
       :header <header-name: string>
167
       :value <value: string>
168
       :handle <handle: string>
169
   */
170
0
  if (!sieve_validate_tag_parameter(valdtr, cmd, tag, *arg, NULL, 0,
171
0
            SAAT_STRING, FALSE))
172
0
    return FALSE;
173
174
0
  if (!sieve_argument_is(tag, duplicate_handle_tag) && (bool)cmd->data) {
175
0
    sieve_argument_validate_error(
176
0
      valdtr, *arg,
177
0
      "conflicting :header and %s arguments specified "
178
0
      "for the duplicate test",
179
0
      (sieve_extension_is(ext, duplicate_extension) ?
180
0
       ":uniqueid" : ":value"));
181
0
    return FALSE;
182
0
  }
183
184
  /* :header <header-name: string> */
185
0
  if (sieve_argument_is(tag, duplicate_header_tag)) {
186
0
    if (!sieve_command_verify_headers_argument(valdtr, *arg))
187
0
      return FALSE;
188
0
    cmd->data = (void *)TRUE;
189
  /* :handle <handle: string> */
190
0
  } else if (sieve_argument_is(tag, duplicate_handle_tag)) {
191
    /* nothing to be done */
192
0
  } else if (sieve_argument_is(tag, duplicate_uniqueid_tag)) {
193
0
    i_assert(sieve_extension_is(ext, duplicate_extension));
194
0
    cmd->data = (void *)TRUE;
195
0
  } else {
196
0
    i_unreached();
197
0
  }
198
199
  /* Skip parameter */
200
0
  *arg = sieve_ast_argument_next(*arg);
201
0
  return TRUE;
202
0
}
203
204
/*
205
 * Command registration
206
 */
207
208
static bool
209
tst_duplicate_registered(struct sieve_validator *valdtr,
210
       const struct sieve_extension *ext,
211
       struct sieve_command_registration *cmd_reg)
212
0
{
213
0
  sieve_validator_register_tag(valdtr, cmd_reg, ext,
214
0
             &duplicate_seconds_tag, OPT_SECONDS);
215
0
  sieve_validator_register_tag(valdtr, cmd_reg, ext,
216
0
             &duplicate_last_tag, OPT_LAST);
217
0
  sieve_validator_register_tag(valdtr, cmd_reg, ext,
218
0
             &duplicate_header_tag, OPT_HEADER);
219
0
  sieve_validator_register_tag(valdtr, cmd_reg, ext,
220
0
             &duplicate_uniqueid_tag,
221
0
             OPT_UNIQUEID);
222
0
  sieve_validator_register_tag(valdtr, cmd_reg, ext,
223
0
             &duplicate_handle_tag, OPT_HANDLE);
224
0
  return TRUE;
225
0
}
226
227
/*
228
 * Code generation
229
 */
230
231
static bool
232
tst_duplicate_generate(const struct sieve_codegen_env *cgenv,
233
           struct sieve_command *cmd)
234
0
{
235
0
  sieve_operation_emit(cgenv->sblock, cmd->ext, &tst_duplicate_operation);
236
237
0
  if (!sieve_generate_arguments(cgenv, cmd, NULL))
238
0
    return FALSE;
239
0
  return TRUE;
240
0
}
241
242
/*
243
 * Code dump
244
 */
245
246
static bool
247
tst_duplicate_operation_dump(const struct sieve_dumptime_env *denv,
248
           sieve_size_t *address)
249
0
{
250
0
  const struct sieve_extension *ext = denv->oprtn->ext;
251
0
  int opt_code = 0;
252
253
0
  sieve_code_dumpf(denv, "DUPLICATE");
254
0
  sieve_code_descend(denv);
255
256
  /* Dump optional operands */
257
258
0
  for (;;) {
259
0
    int opt;
260
0
    bool opok = TRUE;
261
262
0
    if ((opt = sieve_opr_optional_dump(denv, address,
263
0
               &opt_code)) < 0)
264
0
      return FALSE;
265
266
0
    if (opt == 0)
267
0
      break;
268
269
0
    switch (opt_code) {
270
0
    case OPT_SECONDS:
271
0
      opok = sieve_opr_number_dump(denv, address, "seconds");
272
0
      break;
273
0
    case OPT_LAST:
274
0
      sieve_code_dumpf(denv, "last");
275
0
      break;
276
0
    case OPT_HEADER:
277
0
      opok = sieve_opr_string_dump(denv, address, "header");
278
0
      break;
279
0
    case OPT_UNIQUEID:
280
0
      if (sieve_extension_is(ext, duplicate_extension)) {
281
0
        opok = sieve_opr_string_dump(denv, address,
282
0
                   "uniqueid");
283
0
      } else {
284
0
        opok = sieve_opr_string_dump(denv, address,
285
0
                   "value");
286
0
      }
287
0
      break;
288
0
    case OPT_HANDLE:
289
0
      opok = sieve_opr_string_dump(denv, address, "handle");
290
0
      break;
291
0
    default:
292
0
      return FALSE;
293
0
    }
294
295
0
    if (!opok)
296
0
      return FALSE;
297
0
  }
298
299
0
  return TRUE;
300
0
}
301
302
/*
303
 * Code execution
304
 */
305
306
static int
307
tst_duplicate_operation_execute(const struct sieve_runtime_env *renv,
308
        sieve_size_t *address ATTR_UNUSED)
309
0
{
310
0
  const struct sieve_execute_env *eenv = renv->exec_env;
311
0
  const struct sieve_extension *ext = renv->oprtn->ext;
312
0
  const struct ext_duplicate_context *extctx = ext->context;
313
0
  struct mail *mail = eenv->msgdata->mail;
314
0
  int opt_code = 0;
315
0
  string_t *handle = NULL, *header = NULL, *uniqueid = NULL;
316
0
  const char *val = NULL;
317
0
  size_t val_len = 0;
318
0
  sieve_number_t seconds = extctx->set->default_period;
319
0
  bool last = FALSE, duplicate = FALSE;
320
0
  int ret;
321
322
  /*
323
   * Read operands
324
   */
325
326
  /* Optional operands */
327
328
0
  for (;;) {
329
0
    int opt;
330
331
0
    if ((opt = sieve_opr_optional_read(renv, address,
332
0
               &opt_code)) < 0)
333
0
      return SIEVE_EXEC_BIN_CORRUPT;
334
335
0
    if (opt == 0)
336
0
      break;
337
338
0
    switch (opt_code) {
339
0
    case OPT_SECONDS:
340
0
      ret = sieve_opr_number_read(renv, address, "seconds",
341
0
                &seconds);
342
0
      break;
343
0
    case OPT_LAST:
344
0
      last = TRUE;
345
0
      ret = SIEVE_EXEC_OK;
346
0
      break;
347
0
    case OPT_HEADER:
348
0
      ret = sieve_opr_string_read(renv, address, "header",
349
0
                &header);
350
0
      break;
351
0
    case OPT_UNIQUEID:
352
0
      if (sieve_extension_is(ext, duplicate_extension)) {
353
0
        ret = sieve_opr_string_read(renv, address,
354
0
                  "uniqueid",
355
0
                  &uniqueid);
356
0
      } else {
357
0
        ret = sieve_opr_string_read(renv, address,
358
0
                  "value", &uniqueid);
359
0
      }
360
0
      break;
361
0
    case OPT_HANDLE:
362
0
      ret = sieve_opr_string_read(renv, address,
363
0
                "handle", &handle);
364
0
      break;
365
0
    default:
366
0
      sieve_runtime_trace_error(
367
0
        renv, "unknown optional operand");
368
0
      ret = SIEVE_EXEC_BIN_CORRUPT;
369
0
    }
370
371
0
    if (ret <= 0)
372
0
      return ret;
373
0
  }
374
375
  /*
376
   * Perform operation
377
   */
378
379
  /* Trace */
380
0
  sieve_runtime_trace(renv, SIEVE_TRLVL_TESTS, "duplicate test");
381
0
  sieve_runtime_trace_descend(renv);
382
383
  /* Get value */
384
0
  if (uniqueid != NULL) {
385
0
    val = str_c(uniqueid);
386
0
    val_len = str_len(uniqueid);
387
0
  } else {
388
0
    if (header == NULL) {
389
0
      ret = mail_get_message_id(mail, &val);
390
0
      if (ret < 0) {
391
0
        return sieve_runtime_mail_error(
392
0
          renv, mail, "duplicate test: "
393
0
          "failed to read header field 'message-id'");
394
0
      }
395
0
    } else {
396
0
      ret = mail_get_first_header_utf8(mail, str_c(header),
397
0
               &val);
398
0
      if (ret < 0) {
399
0
        return sieve_runtime_mail_error(
400
0
          renv, mail, "duplicate test: "
401
0
          "failed to read header field '%s'",
402
0
          str_c(header));
403
0
      }
404
0
    }
405
406
0
    if (ret > 0)
407
0
      val_len = strlen(val);
408
0
  }
409
410
  /* Check duplicate */
411
0
  if (val == NULL) {
412
0
    duplicate = FALSE;
413
0
  } else {
414
0
    ret = ext_duplicate_check(renv, handle, val, val_len,
415
0
            seconds, last, &duplicate);
416
0
    if (ret < SIEVE_EXEC_OK)
417
0
      return ret;
418
0
  }
419
420
  /* Trace */
421
0
  if (duplicate) {
422
0
    sieve_runtime_trace(renv, SIEVE_TRLVL_TESTS,
423
0
            "message is a duplicate");
424
0
  }  else {
425
0
    sieve_runtime_trace(renv, SIEVE_TRLVL_TESTS,
426
0
            "message is not a duplicate");
427
0
  }
428
429
  /* Set test result for subsequent conditional jump */
430
0
  sieve_interpreter_set_test_result(renv->interp, duplicate);
431
0
  return SIEVE_EXEC_OK;
432
0
}