Coverage Report

Created: 2026-06-09 06:49

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/pigeonhole/src/lib-sieve/plugins/editheader/cmd-deleteheader.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
#include "mail-storage.h"
7
8
#include "rfc2822.h"
9
#include "edit-mail.h"
10
11
#include "sieve-common.h"
12
#include "sieve-commands.h"
13
#include "sieve-code.h"
14
#include "sieve-message.h"
15
#include "sieve-comparators.h"
16
#include "sieve-match-types.h"
17
18
#include "sieve-validator.h"
19
#include "sieve-generator.h"
20
#include "sieve-interpreter.h"
21
#include "sieve-dump.h"
22
#include "sieve-match.h"
23
24
#include "ext-editheader-common.h"
25
26
/*
27
 * Deleteheader command
28
 *
29
 * Syntax:
30
 *   deleteheader [":index" <fieldno: number> [":last"]]
31
 *                [COMPARATOR] [MATCH-TYPE]
32
 *                <field-name: string> [<value-patterns: string-list>]
33
 */
34
35
static bool
36
cmd_deleteheader_registered(struct sieve_validator *valdtr,
37
          const struct sieve_extension *ext,
38
          struct sieve_command_registration *cmd_reg);
39
static bool
40
cmd_deleteheader_validate(struct sieve_validator *valdtr,
41
        struct sieve_command *cmd);
42
static bool
43
cmd_deleteheader_generate(const struct sieve_codegen_env *cgenv,
44
        struct sieve_command *cmd);
45
46
const struct sieve_command_def deleteheader_command = {
47
  .identifier = "deleteheader",
48
  .type = SCT_COMMAND,
49
  .positional_args = -1, /* We check positional arguments ourselves */
50
  .subtests = 0,
51
  .block_allowed = FALSE,
52
  .block_required = FALSE,
53
  .registered = cmd_deleteheader_registered,
54
  .validate = cmd_deleteheader_validate,
55
  .generate = cmd_deleteheader_generate,
56
};
57
58
/*
59
 * Deleteheader command tags
60
 */
61
62
/* Forward declarations */
63
64
static bool
65
cmd_deleteheader_validate_index_tag(struct sieve_validator *valdtr,
66
            struct sieve_ast_argument **arg,
67
            struct sieve_command *cmd);
68
static bool
69
cmd_deleteheader_validate_last_tag(struct sieve_validator *valdtr,
70
           struct sieve_ast_argument **arg,
71
           struct sieve_command *cmd);
72
73
/* Argument objects */
74
75
static const struct sieve_argument_def deleteheader_index_tag = {
76
  .identifier = "index",
77
  .validate = cmd_deleteheader_validate_index_tag,
78
};
79
80
static const struct sieve_argument_def deleteheader_last_tag = {
81
  .identifier = "last",
82
  .validate = cmd_deleteheader_validate_last_tag,
83
};
84
85
/* Codes for optional arguments */
86
87
enum cmd_deleteheader_optional {
88
  OPT_INDEX = SIEVE_MATCH_OPT_LAST,
89
  OPT_LAST,
90
};
91
92
/*
93
 * Deleteheader operation
94
 */
95
96
static bool
97
cmd_deleteheader_operation_dump(const struct sieve_dumptime_env *denv,
98
        sieve_size_t *address);
99
static int
100
cmd_deleteheader_operation_execute(const struct sieve_runtime_env *renv,
101
           sieve_size_t *address);
102
103
const struct sieve_operation_def deleteheader_operation = {
104
  .mnemonic = "DELETEHEADER",
105
  .ext_def = &editheader_extension,
106
  .code = EXT_EDITHEADER_OPERATION_DELETEHEADER,
107
  .dump = cmd_deleteheader_operation_dump,
108
  .execute = cmd_deleteheader_operation_execute,
109
};
110
111
/*
112
 * Command registration
113
 */
114
115
static bool
116
cmd_deleteheader_registered(struct sieve_validator *valdtr,
117
          const struct sieve_extension *ext ATTR_UNUSED,
118
          struct sieve_command_registration *cmd_reg)
119
0
{
120
  /* The order of these is not significant */
121
0
  sieve_comparators_link_tag(valdtr, cmd_reg,
122
0
           SIEVE_MATCH_OPT_COMPARATOR);
123
0
  sieve_match_types_link_tags(valdtr, cmd_reg,
124
0
            SIEVE_MATCH_OPT_MATCH_TYPE);
125
126
0
  sieve_validator_register_tag(valdtr, cmd_reg, ext,
127
0
             &deleteheader_index_tag, OPT_INDEX);
128
0
  sieve_validator_register_tag(valdtr, cmd_reg, ext,
129
0
             &deleteheader_last_tag, OPT_LAST);
130
131
0
  return TRUE;
132
0
}
133
134
/*
135
 * Command validation context
136
 */
137
138
struct cmd_deleteheader_context_data {
139
  struct sieve_ast_argument *arg_index;
140
  struct sieve_ast_argument *arg_last;
141
};
142
143
/*
144
 * Tag validation
145
 */
146
147
static struct cmd_deleteheader_context_data *
148
cmd_deleteheader_get_context(struct sieve_command *cmd)
149
0
{
150
0
  struct cmd_deleteheader_context_data *ctx_data =
151
0
    (struct cmd_deleteheader_context_data *)cmd->data;
152
153
0
  if (ctx_data != NULL)
154
0
    return ctx_data;
155
156
0
  ctx_data = p_new(sieve_command_pool(cmd),
157
0
       struct cmd_deleteheader_context_data, 1);
158
0
  cmd->data = ctx_data;
159
160
0
  return ctx_data;
161
0
}
162
163
static bool
164
cmd_deleteheader_validate_index_tag(struct sieve_validator *valdtr,
165
            struct sieve_ast_argument **arg,
166
            struct sieve_command *cmd)
167
0
{
168
0
  struct sieve_ast_argument *tag = *arg;
169
0
  struct cmd_deleteheader_context_data *ctx_data;
170
0
  sieve_number_t index;
171
172
  /* Detach the tag itself */
173
0
  *arg = sieve_ast_arguments_detach(*arg,1);
174
175
  /* Check syntax:
176
   *   :index number
177
   */
178
0
  if (!sieve_validate_tag_parameter(valdtr, cmd, tag, *arg, NULL, 0,
179
0
            SAAT_NUMBER, FALSE))
180
0
    return FALSE;
181
182
0
  index = sieve_ast_argument_number(*arg);
183
0
  if (index > INT_MAX) {
184
0
    sieve_argument_validate_warning(
185
0
      valdtr, *arg,
186
0
      "the :%s tag for the %s %s has a parameter value '%llu' "
187
0
      "exceeding the maximum (%d)",
188
0
      sieve_argument_identifier(tag),
189
0
      sieve_command_identifier(cmd),
190
0
      sieve_command_type_name(cmd),
191
0
      (unsigned long long)index, INT_MAX);
192
0
    return FALSE;
193
0
  }
194
195
0
  ctx_data = cmd_deleteheader_get_context(cmd);
196
0
  ctx_data->arg_index = *arg;
197
198
  /* Skip parameter */
199
0
  *arg = sieve_ast_argument_next(*arg);
200
201
0
  return TRUE;
202
0
}
203
204
static bool
205
cmd_deleteheader_validate_last_tag(struct sieve_validator *valdtr ATTR_UNUSED,
206
           struct sieve_ast_argument **arg,
207
           struct sieve_command *cmd)
208
0
{
209
0
  struct cmd_deleteheader_context_data *ctx_data;
210
211
0
  ctx_data = cmd_deleteheader_get_context(cmd);
212
0
  ctx_data->arg_last = *arg;
213
214
  /* Skip parameter */
215
0
  *arg = sieve_ast_argument_next(*arg);
216
217
0
  return TRUE;
218
0
}
219
220
/*
221
 * Validation
222
 */
223
224
static bool
225
cmd_deleteheader_validate(struct sieve_validator *valdtr,
226
        struct sieve_command *cmd)
227
0
{
228
0
  struct sieve_ast_argument *arg = cmd->first_positional;
229
0
  struct cmd_deleteheader_context_data *ctx_data =
230
0
    (struct cmd_deleteheader_context_data *)cmd->data;
231
0
  struct sieve_comparator cmp_default =
232
0
    SIEVE_COMPARATOR_DEFAULT(i_ascii_casemap_comparator);
233
0
  struct sieve_match_type mcht_default =
234
0
    SIEVE_MATCH_TYPE_DEFAULT(is_match_type);
235
236
0
  if (ctx_data != NULL &&
237
0
      ctx_data->arg_last != NULL && ctx_data->arg_index == NULL) {
238
0
    sieve_argument_validate_error(
239
0
      valdtr, ctx_data->arg_last,
240
0
      "the :last tag for the %s %s cannot be specified "
241
0
      "without the :index tag",
242
0
      sieve_command_identifier(cmd),
243
0
      sieve_command_type_name(cmd));
244
0
    return FALSE;
245
0
  }
246
247
  /* Field name argument */
248
249
0
  if (arg == NULL) {
250
0
    sieve_command_validate_error(
251
0
      valdtr, cmd,
252
0
      "the %s %s expects at least one positional argument, "
253
0
      "but none was found",
254
0
      sieve_command_identifier(cmd),
255
0
      sieve_command_type_name(cmd));
256
0
    return FALSE;
257
0
  }
258
259
0
  if (!sieve_validate_positional_argument(valdtr, cmd, arg, "field name",
260
0
            1, SAAT_STRING_LIST))
261
0
    return FALSE;
262
0
  if (!sieve_validator_argument_activate(valdtr, cmd, arg, FALSE))
263
0
    return FALSE;
264
265
0
  if (sieve_argument_is_string_literal(arg)) {
266
0
    string_t *fname = sieve_ast_argument_str(arg);
267
268
0
    if (!rfc2822_header_field_name_verify(str_c(fname),
269
0
                  str_len(fname))) {
270
0
      sieve_argument_validate_error(
271
0
        valdtr, arg, "deleteheader command:"
272
0
        "specified field name '%s' is invalid",
273
0
        str_sanitize(str_c(fname), 80));
274
0
      return FALSE;
275
0
    }
276
277
0
    if (!ext_editheader_header_allow_delete(
278
0
      cmd->ext, str_c(fname))) {
279
0
      sieve_argument_validate_warning(
280
0
        valdtr, arg, "deleteheader command: "
281
0
        "deleting specified header field '%s' is forbidden; "
282
0
        "modification will be denied",
283
0
        str_sanitize(str_c(fname), 80));
284
0
    }
285
0
  }
286
287
  /* Value patterns argument */
288
289
0
  arg = sieve_ast_argument_next(arg);
290
0
  if (arg == NULL) {
291
    /* There is none; let's not generate code for useless match
292
       arguments */
293
0
    sieve_match_type_arguments_remove(valdtr, cmd);
294
0
    return TRUE;
295
0
  }
296
297
0
  if (!sieve_validate_positional_argument(
298
0
    valdtr, cmd, arg, "value patterns", 2, SAAT_STRING_LIST))
299
0
    return FALSE;
300
0
  if (!sieve_validator_argument_activate(valdtr, cmd, arg, FALSE))
301
0
    return FALSE;
302
303
  /* Validate the value patterns to a specified match type */
304
0
  return sieve_match_type_validate(valdtr, cmd, arg,
305
0
           &mcht_default, &cmp_default);
306
0
}
307
308
/*
309
 * Code generation
310
 */
311
312
static bool
313
cmd_deleteheader_generate(const struct sieve_codegen_env *cgenv,
314
        struct sieve_command *cmd)
315
0
{
316
0
  sieve_operation_emit(cgenv->sblock, cmd->ext, &deleteheader_operation);
317
318
  /* Generate arguments */
319
0
  if (!sieve_generate_arguments(cgenv, cmd, NULL))
320
0
    return FALSE;
321
322
  /* Emit a placeholder when the value-patterns argument is missing */
323
0
  if (sieve_ast_argument_next(cmd->first_positional) == NULL)
324
0
    sieve_opr_omitted_emit(cgenv->sblock);
325
326
0
  return TRUE;
327
0
}
328
329
/*
330
 * Code dump
331
 */
332
333
static bool
334
cmd_deleteheader_operation_dump(const struct sieve_dumptime_env *denv,
335
        sieve_size_t *address)
336
0
{
337
0
  int opt_code = 0;
338
339
0
  sieve_code_dumpf(denv, "DELETEHEADER");
340
0
  sieve_code_descend(denv);
341
342
  /* Optional operands */
343
0
  for (;;) {
344
0
    int opt;
345
346
0
    opt = sieve_match_opr_optional_dump(denv, address, &opt_code);
347
0
    if (opt < 0)
348
0
      return FALSE;
349
0
    if (opt == 0)
350
0
      break;
351
352
0
    switch (opt_code) {
353
0
    case OPT_INDEX:
354
0
      if (!sieve_opr_number_dump(denv, address, "index"))
355
0
        return FALSE;
356
0
      break;
357
0
    case OPT_LAST:
358
0
      sieve_code_dumpf(denv, "last");
359
0
      break;
360
0
    default:
361
0
      return FALSE;
362
0
    }
363
0
  };
364
365
0
  if (!sieve_opr_string_dump(denv, address, "field name"))
366
0
    return FALSE;
367
368
0
  return sieve_opr_stringlist_dump_ex(
369
0
    denv, address, "value patterns", "");
370
0
}
371
372
/*
373
 * Code execution
374
 */
375
376
static int
377
cmd_deleteheader_operation_execute(const struct sieve_runtime_env *renv,
378
           sieve_size_t *address)
379
0
{
380
0
  const struct sieve_extension *this_ext = renv->oprtn->ext;
381
0
  int opt_code = 0;
382
0
  struct sieve_comparator cmp =
383
0
    SIEVE_COMPARATOR_DEFAULT(i_ascii_casemap_comparator);
384
0
  struct sieve_match_type mcht =
385
0
    SIEVE_MATCH_TYPE_DEFAULT(is_match_type);
386
0
  string_t *field_name;
387
0
  struct sieve_stringlist *vpattern_list = NULL;
388
0
  struct edit_mail *edmail;
389
0
  sieve_number_t index_offset = 0;
390
0
  bool index_last = FALSE;
391
0
  bool trace = FALSE;
392
0
  int ret;
393
394
  /*
395
   * Read operands
396
   */
397
398
0
  for (;;) {
399
0
    int opt;
400
401
0
    opt = sieve_match_opr_optional_read(renv, address, &opt_code,
402
0
                &ret, &cmp, &mcht);
403
0
    if (opt < 0)
404
0
      return ret;
405
0
    if (opt == 0)
406
0
      break;
407
408
0
    switch (opt_code) {
409
0
    case OPT_INDEX:
410
0
      ret = sieve_opr_number_read(renv, address, "index",
411
0
                &index_offset);
412
0
      if (ret <= 0)
413
0
        return ret;
414
0
      if (index_offset > INT_MAX) {
415
0
        sieve_runtime_trace_error(
416
0
          renv, "index is > %d", INT_MAX);
417
0
        return SIEVE_EXEC_BIN_CORRUPT;
418
0
      }
419
0
      break;
420
0
    case OPT_LAST:
421
0
      index_last = TRUE;
422
0
      break;
423
0
    default:
424
0
      sieve_runtime_trace_error(
425
0
        renv, "unknown optional operand");
426
0
      return SIEVE_EXEC_BIN_CORRUPT;
427
0
    }
428
0
  }
429
430
  /* Read field-name */
431
0
  ret = sieve_opr_string_read(renv, address, "field-name", &field_name);
432
0
  if (ret <= 0)
433
0
    return ret;
434
435
  /* Read value-patterns */
436
0
  ret = sieve_opr_stringlist_read_ex(renv, address, "value-patterns",
437
0
             TRUE, &vpattern_list);
438
0
  if (ret <= 0)
439
0
    return ret;
440
441
  /*
442
   * Verify arguments
443
   */
444
445
0
  if (!rfc2822_header_field_name_verify(str_c(field_name),
446
0
                str_len(field_name))) {
447
0
    sieve_runtime_error(
448
0
      renv, NULL, "deleteheader action: "
449
0
      "specified field name '%s' is invalid",
450
0
      str_sanitize(str_c(field_name), 80));
451
0
    return SIEVE_EXEC_FAILURE;
452
0
  }
453
454
0
  if (!ext_editheader_header_allow_delete(this_ext, str_c(field_name))) {
455
0
    sieve_runtime_warning(
456
0
      renv, NULL, "deleteheader action: "
457
0
      "deleting specified header field '%s' is forbidden; "
458
0
      "modification denied",
459
0
      str_sanitize(str_c(field_name), 80));
460
0
    return SIEVE_EXEC_OK;
461
0
  }
462
463
  /*
464
   * Execute command
465
   */
466
467
0
  sieve_runtime_trace(renv, SIEVE_TRLVL_COMMANDS, "deleteheader command");
468
469
  /* Start editing the mail */
470
0
  edmail = sieve_message_edit(renv->msgctx);
471
472
0
  trace = sieve_runtime_trace_active(renv, SIEVE_TRLVL_COMMANDS);
473
474
  /* Either do string matching or just kill all/indexed notify action(s)
475
   */
476
0
  if (vpattern_list != NULL) {
477
0
    struct edit_mail_header_iter *edhiter;
478
0
    struct sieve_match_context *mctx;
479
480
0
    if (trace) {
481
0
      sieve_runtime_trace_descend(renv);
482
0
      if (index_offset != 0) {
483
0
        sieve_runtime_trace(
484
0
          renv, 0,
485
0
          "deleting matching occurrences of header '%s' at index %llu%s",
486
0
          str_c(field_name),
487
0
          (unsigned long long)index_offset,
488
0
          (index_last ? " from last": ""));
489
0
      } else {
490
0
        sieve_runtime_trace(
491
0
          renv, 0,
492
0
          "deleting matching occurrences of header '%s'",
493
0
          str_c(field_name));
494
0
      }
495
0
    }
496
497
    /* Iterate through all headers and delete those that match */
498
0
    ret = edit_mail_headers_iterate_init(edmail, str_c(field_name),
499
0
                 index_last, &edhiter);
500
0
    if (ret > 0) {
501
0
      int mret = 0;
502
0
      sieve_number_t pos = 0;
503
504
      /* Initialize match */
505
0
      mctx = sieve_match_begin(renv, &mcht, &cmp);
506
507
      /* Match */
508
0
      for (;;) {
509
0
        pos++;
510
511
        /* Check index if any */
512
0
        if (index_offset == 0 || pos == index_offset) {
513
0
          const char *value;
514
0
          int match;
515
516
          /* Match value against all value patterns */
517
0
          edit_mail_headers_iterate_get(edhiter, &value);
518
0
          match = sieve_match_value(
519
0
            mctx, value, strlen(value),
520
0
            vpattern_list);
521
0
          if (match < 0)
522
0
            break;
523
524
0
          if (match > 0) {
525
            /* Remove it and iterate to next */
526
0
            sieve_runtime_trace(
527
0
              renv, 0,
528
0
              "deleting header with value '%s'",
529
0
              value);
530
531
0
            if (!edit_mail_headers_iterate_remove(edhiter))
532
0
              break;
533
0
            continue;
534
0
          }
535
0
        }
536
537
0
        if (!edit_mail_headers_iterate_next(edhiter))
538
0
          break;
539
0
      }
540
541
      /* Finish match */
542
0
      mret = sieve_match_end(&mctx, &ret);
543
544
0
      edit_mail_headers_iterate_deinit(&edhiter);
545
0
      if (mret < 0)
546
0
        return ret;
547
0
    }
548
549
0
    if (ret == 0) {
550
0
      sieve_runtime_trace(renv, 0, "header '%s' not found",
551
0
              str_c(field_name));
552
0
    } else if (ret < 0) {
553
0
      sieve_runtime_warning(
554
0
        renv, NULL, "deleteheader action: "
555
0
        "failed to delete occurrences of header '%s' "
556
0
        "(this should not happen!)",
557
0
        str_c(field_name));
558
0
    }
559
560
0
  } else {
561
0
    int index = (index_last ?
562
0
           -((int)index_offset) : ((int)index_offset));
563
564
0
    if (trace) {
565
0
      sieve_runtime_trace_descend(renv);
566
0
      if (index_offset != 0) {
567
0
        sieve_runtime_trace(
568
0
          renv, 0,
569
0
          "deleting header '%s' at index %llu%s",
570
0
          str_c(field_name),
571
0
          (unsigned long long)index_offset,
572
0
          (index_last ? " from last": ""));
573
0
      } else {
574
0
        sieve_runtime_trace(
575
0
          renv, 0, "deleting header '%s'",
576
0
          str_c(field_name));
577
0
      }
578
0
    }
579
580
    /* Delete all occurrences of header */
581
0
    ret = edit_mail_header_delete(edmail, str_c(field_name), index);
582
0
    if (ret < 0) {
583
0
      sieve_runtime_warning(
584
0
        renv, NULL, "deleteheader action: "
585
0
        "failed to delete occurrences of header '%s' "
586
0
        "(this should not happen!)",
587
0
        str_c(field_name));
588
0
    } else if (trace) {
589
0
      sieve_runtime_trace(
590
0
        renv, 0,
591
0
        "deleted %d occurrences of header '%s'",
592
0
        ret, str_c(field_name));
593
0
    }
594
0
  }
595
596
0
  return SIEVE_EXEC_OK;
597
0
}