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/date/tst-date.c
Line
Count
Source
1
/* Copyright (c) Pigeonhole authors, see top-level COPYING file */
2
3
#include "lib.h"
4
#include "str-sanitize.h"
5
6
#include "sieve-common.h"
7
#include "sieve-commands.h"
8
#include "sieve-code.h"
9
#include "sieve-comparators.h"
10
#include "sieve-match-types.h"
11
#include "sieve-address-parts.h"
12
#include "sieve-message.h"
13
#include "sieve-validator.h"
14
#include "sieve-generator.h"
15
#include "sieve-interpreter.h"
16
#include "sieve-dump.h"
17
#include "sieve-match.h"
18
19
#include "ext-date-common.h"
20
21
#include <time.h>
22
23
/*
24
 * Tests
25
 */
26
27
static bool
28
tst_date_validate(struct sieve_validator *valdtr, struct sieve_command *tst);
29
static bool
30
tst_date_generate(const struct sieve_codegen_env *cgenv,
31
      struct sieve_command *ctx);
32
33
/* Date test
34
 *
35
 * Syntax:
36
 *    date [<":zone" <time-zone: string>> / ":originalzone"]
37
 *         [COMPARATOR] [MATCH-TYPE] <header-name: string>
38
 *         <date-part: string> <key-list: string-list>
39
 */
40
41
static bool
42
tst_date_registered(struct sieve_validator *valdtr,
43
        const struct sieve_extension *ext,
44
        struct sieve_command_registration *cmd_reg);
45
46
const struct sieve_command_def date_test = {
47
  .identifier = "date",
48
  .type = SCT_TEST,
49
  .positional_args = 3,
50
  .subtests = 0,
51
  .block_allowed = FALSE,
52
  .block_required = FALSE,
53
  .registered = tst_date_registered,
54
  .validate = tst_date_validate,
55
  .generate = tst_date_generate,
56
};
57
58
/* Currentdate test
59
 *
60
 * Syntax:
61
 *    currentdate [":zone" <time-zone: string>]
62
 *                [COMPARATOR] [MATCH-TYPE]
63
 *                <date-part: string> <key-list: string-list>
64
 */
65
66
static bool
67
tst_currentdate_registered(struct sieve_validator *valdtr,
68
         const struct sieve_extension *ext,
69
         struct sieve_command_registration *cmd_reg);
70
71
const struct sieve_command_def currentdate_test = {
72
  .identifier = "currentdate",
73
  .type = SCT_TEST,
74
  .positional_args = 2,
75
  .subtests = 0,
76
  .block_allowed = FALSE,
77
  .block_required = FALSE,
78
  .registered = tst_currentdate_registered,
79
  .validate = tst_date_validate,
80
  .generate = tst_date_generate,
81
};
82
83
/*
84
 * Tagged arguments
85
 */
86
87
/* Forward declarations */
88
89
static bool
90
tag_zone_validate(struct sieve_validator *valdtr,
91
      struct sieve_ast_argument **arg, struct sieve_command *cmd);
92
static bool
93
tag_zone_generate(const struct sieve_codegen_env *cgenv,
94
      struct sieve_ast_argument *arg, struct sieve_command *cmd);
95
96
/* Argument objects */
97
98
static const struct sieve_argument_def date_zone_tag = {
99
  .identifier = "zone",
100
  .validate = tag_zone_validate,
101
  .generate = tag_zone_generate,
102
};
103
104
static const struct sieve_argument_def date_originalzone_tag = {
105
  .identifier = "originalzone",
106
  .validate = tag_zone_validate,
107
  .generate = tag_zone_generate,
108
};
109
110
/*
111
 * Date operation
112
 */
113
114
static bool
115
tst_date_operation_dump(const struct sieve_dumptime_env *denv,
116
      sieve_size_t *address);
117
static int
118
tst_date_operation_execute(const struct sieve_runtime_env *renv,
119
         sieve_size_t *address);
120
121
const struct sieve_operation_def date_operation = {
122
  .mnemonic = "DATE",
123
  .ext_def = &date_extension,
124
  .code = EXT_DATE_OPERATION_DATE,
125
  .dump = tst_date_operation_dump,
126
  .execute = tst_date_operation_execute,
127
};
128
129
const struct sieve_operation_def currentdate_operation = {
130
  .mnemonic = "CURRENTDATE",
131
  .ext_def = &date_extension,
132
  .code = EXT_DATE_OPERATION_CURRENTDATE,
133
  .dump = tst_date_operation_dump,
134
  .execute = tst_date_operation_execute,
135
};
136
137
/*
138
 * Optional operands
139
 */
140
141
enum tst_date_optional {
142
  OPT_DATE_ZONE = SIEVE_AM_OPT_LAST,
143
  OPT_DATE_LAST,
144
};
145
146
/*
147
 * Tag implementation
148
 */
149
150
static bool
151
tag_zone_validate(struct sieve_validator *valdtr,
152
      struct sieve_ast_argument **arg, struct sieve_command *cmd)
153
0
{
154
0
  struct sieve_ast_argument *tag = *arg;
155
156
0
  if ((bool)cmd->data) {
157
0
    if (sieve_command_is(cmd, date_test)) {
158
0
      sieve_argument_validate_error(
159
0
        valdtr, *arg,
160
0
        "multiple :zone or :originalzone arguments specified for "
161
0
        "the currentdate test");
162
0
    } else {
163
0
      sieve_argument_validate_error(
164
0
        valdtr, *arg,
165
0
        "multiple :zone arguments specified for the currentdate test");
166
0
    }
167
0
    return FALSE;
168
0
  }
169
170
  /* Skip tag */
171
0
  *arg = sieve_ast_argument_next(*arg);
172
173
  /* :content tag has a string-list argument */
174
0
  if (sieve_argument_is(tag, date_zone_tag)) {
175
176
    /* Check syntax:
177
     *   :zone <time-zone: string>
178
     */
179
0
    if (!sieve_validate_tag_parameter(valdtr, cmd, tag, *arg,
180
0
              NULL, 0, SAAT_STRING, FALSE))
181
0
      return FALSE;
182
183
    /* Check it */
184
0
    if (sieve_argument_is_string_literal(*arg)) {
185
0
      const char *zone = sieve_ast_argument_strc(*arg);
186
187
0
      if (!ext_date_parse_timezone(zone, NULL)) {
188
0
        sieve_argument_validate_warning(
189
0
          valdtr, *arg,
190
0
          "specified :zone argument '%s' is not a valid timezone",
191
0
          str_sanitize(zone, 40));
192
0
      }
193
0
    }
194
195
    /* Assign tag parameters */
196
0
    tag->parameters = *arg;
197
0
    *arg = sieve_ast_arguments_detach(*arg,1);
198
0
  }
199
200
0
  cmd->data = (void *)TRUE;
201
0
  return TRUE;
202
0
}
203
204
/*
205
 * Test registration
206
 */
207
208
static bool
209
tst_date_registered(struct sieve_validator *valdtr,
210
        const struct sieve_extension *ext,
211
        struct sieve_command_registration *cmd_reg)
212
0
{
213
0
  sieve_comparators_link_tag(valdtr, cmd_reg,
214
0
           SIEVE_MATCH_OPT_COMPARATOR);
215
0
  sieve_match_types_link_tags(valdtr, cmd_reg,
216
0
            SIEVE_MATCH_OPT_MATCH_TYPE);
217
218
0
  sieve_validator_register_tag(valdtr, cmd_reg, ext,
219
0
             &date_zone_tag, OPT_DATE_ZONE);
220
0
  sieve_validator_register_tag(valdtr, cmd_reg, ext,
221
0
             &date_originalzone_tag, OPT_DATE_ZONE);
222
223
0
  return TRUE;
224
0
}
225
226
static bool
227
tst_currentdate_registered(struct sieve_validator *valdtr,
228
         const struct sieve_extension *ext,
229
         struct sieve_command_registration *cmd_reg)
230
0
{
231
0
  sieve_comparators_link_tag(valdtr, cmd_reg,
232
0
           SIEVE_MATCH_OPT_COMPARATOR);
233
0
  sieve_match_types_link_tags(valdtr, cmd_reg,
234
0
            SIEVE_MATCH_OPT_MATCH_TYPE);
235
236
0
  sieve_validator_register_tag(valdtr, cmd_reg, ext,
237
0
             &date_zone_tag, OPT_DATE_ZONE);
238
239
0
  return TRUE;
240
0
}
241
242
/*
243
 * Validation
244
 */
245
246
static bool
247
tst_date_validate(struct sieve_validator *valdtr, struct sieve_command *tst)
248
0
{
249
0
  struct sieve_ast_argument *arg = tst->first_positional;
250
0
  unsigned int arg_offset = 0 ;
251
0
  const struct sieve_match_type mcht_default =
252
0
    SIEVE_MATCH_TYPE_DEFAULT(is_match_type);
253
0
  const struct sieve_comparator cmp_default =
254
0
    SIEVE_COMPARATOR_DEFAULT(i_ascii_casemap_comparator);
255
256
  /* Check header name */
257
258
0
  if (sieve_command_is(tst, date_test)) {
259
0
    arg_offset = 1;
260
261
0
    if (!sieve_validate_positional_argument(
262
0
      valdtr, tst, arg, "header name", 1, SAAT_STRING))
263
0
      return FALSE;
264
0
    if (!sieve_validator_argument_activate(valdtr, tst, arg, FALSE))
265
0
      return FALSE;
266
0
    if (!sieve_command_verify_headers_argument(valdtr, arg))
267
0
      return FALSE;
268
269
0
    arg = sieve_ast_argument_next(arg);
270
0
  }
271
272
  /* Check date part */
273
274
0
  if (!sieve_validate_positional_argument(
275
0
    valdtr, tst, arg, "date part", arg_offset + 1, SAAT_STRING))
276
0
    return FALSE;
277
0
  if (!sieve_validator_argument_activate(valdtr, tst, arg, FALSE))
278
0
    return FALSE;
279
280
0
  if (sieve_argument_is_string_literal(arg)) {
281
0
    const char * part = sieve_ast_argument_strc(arg);
282
283
0
    if (ext_date_part_find(part) == NULL) {
284
0
      sieve_argument_validate_warning(
285
0
        valdtr, arg,
286
0
        "specified date part '%s' is not known",
287
0
        str_sanitize(part, 80));
288
0
    }
289
0
  }
290
291
0
  arg = sieve_ast_argument_next(arg);
292
293
  /* Check key list */
294
295
0
  if (!sieve_validate_positional_argument(
296
0
    valdtr, tst, arg, "key list", arg_offset + 2, SAAT_STRING_LIST))
297
0
    return FALSE;
298
0
  if (!sieve_validator_argument_activate(valdtr, tst, arg, FALSE))
299
0
    return FALSE;
300
301
  /* Validate the key argument to a specified match type */
302
0
  return sieve_match_type_validate(valdtr, tst, arg,
303
0
           &mcht_default, &cmp_default);
304
0
}
305
306
/*
307
 * Code generation
308
 */
309
310
static bool
311
tst_date_generate(const struct sieve_codegen_env *cgenv,
312
      struct sieve_command *tst)
313
0
{
314
0
  if (sieve_command_is(tst, date_test)) {
315
0
    sieve_operation_emit(cgenv->sblock, tst->ext,
316
0
             &date_operation);
317
0
  } else if (sieve_command_is(tst, currentdate_test)) {
318
0
    sieve_operation_emit(cgenv->sblock, tst->ext,
319
0
             &currentdate_operation);
320
0
  } else {
321
0
    i_unreached();
322
0
  }
323
324
  /* Generate arguments */
325
0
  return sieve_generate_arguments(cgenv, tst, NULL);
326
0
}
327
328
static bool
329
tag_zone_generate(const struct sieve_codegen_env *cgenv,
330
      struct sieve_ast_argument *arg, struct sieve_command *cmd)
331
0
{
332
0
  if (arg->parameters == NULL) {
333
0
    sieve_opr_omitted_emit(cgenv->sblock);
334
0
    return TRUE;
335
0
  }
336
0
  return sieve_generate_argument_parameters(cgenv, cmd, arg);
337
0
}
338
339
/*
340
 * Code dump
341
 */
342
343
static bool
344
tst_date_operation_dump(const struct sieve_dumptime_env *denv,
345
      sieve_size_t *address)
346
0
{
347
0
  int opt_code = 0;
348
0
  const struct sieve_operation *op = denv->oprtn;
349
350
0
  sieve_code_dumpf(denv, "%s", sieve_operation_mnemonic(op));
351
0
  sieve_code_descend(denv);
352
353
  /* Handle any optional arguments */
354
0
  for (;;) {
355
0
    int opt;
356
357
0
    opt = sieve_message_opr_optional_dump(denv, address, &opt_code);
358
0
    if (opt < 0)
359
0
      return FALSE;
360
0
    if (opt == 0)
361
0
      break;
362
363
0
    switch (opt_code) {
364
0
    case OPT_DATE_ZONE:
365
0
      if (!sieve_opr_string_dump_ex(denv, address,
366
0
                  "zone", "ORIGINAL"))
367
0
        return FALSE;
368
0
      break;
369
0
    default:
370
0
      return FALSE;
371
0
    }
372
0
  }
373
374
0
  if (sieve_operation_is(op, date_operation) &&
375
0
      !sieve_opr_string_dump(denv, address, "header name"))
376
0
    return FALSE;
377
378
0
  return (sieve_opr_string_dump(denv, address, "date part") &&
379
0
    sieve_opr_stringlist_dump(denv, address, "key list"));
380
0
}
381
382
/*
383
 * Code execution
384
 */
385
386
static int
387
tst_date_operation_execute(const struct sieve_runtime_env *renv,
388
         sieve_size_t *address)
389
0
{
390
0
  const struct sieve_operation *op = renv->oprtn;
391
0
  int opt_code = 0;
392
0
  struct sieve_match_type mcht =
393
0
    SIEVE_MATCH_TYPE_DEFAULT(is_match_type);
394
0
  struct sieve_comparator cmp =
395
0
    SIEVE_COMPARATOR_DEFAULT(i_ascii_casemap_comparator);
396
0
  ARRAY_TYPE(sieve_message_override) svmos;
397
0
  string_t *date_part = NULL, *zone = NULL;
398
0
  struct sieve_stringlist *hdr_list = NULL, *hdr_value_list;
399
0
  struct sieve_stringlist *value_list, *key_list;
400
0
  bool zone_specified = FALSE, zone_literal = TRUE;
401
0
  const struct ext_date_part *dpart;
402
0
  int time_zone;
403
0
  int match, ret;
404
405
  /* Read optional operands */
406
0
  for (;;) {
407
0
    int opt;
408
409
    /* Optional operands */
410
0
    i_zero(&svmos);
411
0
    opt = sieve_message_opr_optional_read(
412
0
      renv, address, &opt_code, &ret, NULL,
413
0
      &mcht, &cmp, &svmos);
414
0
    if (opt < 0)
415
0
      return ret;
416
0
    if (opt == 0)
417
0
      break;
418
419
0
    switch (opt_code) {
420
0
    case OPT_DATE_ZONE:
421
0
      ret = sieve_opr_string_read_ex(
422
0
        renv, address, "zone", TRUE,
423
0
        &zone, &zone_literal);
424
0
      if (ret <= 0)
425
0
        return ret;
426
0
      zone_specified = TRUE;
427
0
      break;
428
0
    default:
429
0
      sieve_runtime_trace_error(
430
0
        renv, "unknown optional operand");
431
0
      return SIEVE_EXEC_BIN_CORRUPT;
432
0
    }
433
0
  }
434
435
0
  if (sieve_operation_is(op, date_operation)) {
436
    /* Read header name as stringlist */
437
0
    ret = sieve_opr_stringlist_read(renv, address, "header-name",
438
0
            &hdr_list);
439
0
    if (ret <= 0)
440
0
      return ret;
441
0
  }
442
443
  /* Read date part */
444
0
  ret = sieve_opr_string_read(renv, address, "date-part", &date_part);
445
0
  if (ret <= 0)
446
0
    return ret;
447
448
  /* Read key-list */
449
0
  ret = sieve_opr_stringlist_read(renv, address, "key-list", &key_list);
450
0
  if (ret <= 0)
451
0
    return ret;
452
453
  /* Determine what time zone to use in the result */
454
0
  if (!zone_specified) {
455
0
    time_zone = EXT_DATE_TIMEZONE_LOCAL;
456
0
  } else if (zone == NULL) {
457
0
    time_zone = EXT_DATE_TIMEZONE_ORIGINAL;
458
0
  } else if (!ext_date_parse_timezone(str_c(zone), &time_zone)) {
459
0
    if (!zone_literal) {
460
0
      sieve_runtime_warning(
461
0
        renv, NULL,
462
0
        "specified :zone argument '%s' is not a valid timezone "
463
0
        "(using local zone)",
464
0
        str_sanitize(str_c(zone), 40));
465
0
    }
466
0
    time_zone = EXT_DATE_TIMEZONE_LOCAL;
467
0
  }
468
469
0
  dpart = ext_date_part_find(str_c(date_part));
470
0
  if (dpart == NULL) {
471
0
    sieve_runtime_warning(
472
0
      renv, NULL,
473
0
      "specified date part argument '%s' is not known",
474
0
      str_sanitize(str_c(date_part), 40));
475
0
    sieve_interpreter_set_test_result(renv->interp, FALSE);
476
0
    return SIEVE_EXEC_OK;
477
0
  }
478
479
  /*
480
   * Perform test
481
   */
482
483
0
  if (sieve_operation_is(op, date_operation)) {
484
0
    sieve_runtime_trace(renv, SIEVE_TRLVL_TESTS, "date test");
485
486
    /* Get header */
487
0
    sieve_runtime_trace_descend(renv);
488
0
    ret = sieve_message_get_header_fields(renv, hdr_list, &svmos,
489
0
                  FALSE, &hdr_value_list);
490
0
    if (ret <= 0)
491
0
      return ret;
492
0
    sieve_runtime_trace_ascend(renv);
493
494
    /* Create value stringlist */
495
0
    value_list = ext_date_stringlist_create(renv, hdr_value_list,
496
0
              time_zone, dpart);
497
0
  } else if (sieve_operation_is(op, currentdate_operation)) {
498
    /* Use time stamp recorded at the time the script first started.
499
     */
500
0
    sieve_runtime_trace(renv, SIEVE_TRLVL_TESTS,
501
0
            "currentdatedate test");
502
503
    /* Create value stringlist */
504
0
    value_list = ext_date_stringlist_create(
505
0
      renv, NULL, time_zone, dpart);
506
0
  } else {
507
0
    i_unreached();
508
0
  }
509
510
  /* Perform match */
511
0
  match = sieve_match(renv, &mcht, &cmp, value_list, key_list, &ret);
512
0
  if (match < 0)
513
0
    return ret;
514
515
  /* Set test result for subsequent conditional jump */
516
0
  sieve_interpreter_set_test_result(renv->interp, match > 0);
517
0
  return SIEVE_EXEC_OK;
518
0
}