Coverage Report

Created: 2026-05-30 06:37

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