Coverage Report

Created: 2026-04-12 07:00

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
149
(struct sieve_command *cmd)
150
0
{
151
0
  struct cmd_deleteheader_context_data *ctx_data =
152
0
    (struct cmd_deleteheader_context_data *)cmd->data;
153
154
0
  if (ctx_data != NULL)
155
0
    return ctx_data;
156
157
0
  ctx_data = p_new(sieve_command_pool(cmd),
158
0
       struct cmd_deleteheader_context_data, 1);
159
0
  cmd->data = ctx_data;
160
161
0
  return ctx_data;
162
0
}
163
164
static bool
165
cmd_deleteheader_validate_index_tag(struct sieve_validator *valdtr,
166
            struct sieve_ast_argument **arg,
167
            struct sieve_command *cmd)
168
0
{
169
0
  struct sieve_ast_argument *tag = *arg;
170
0
  struct cmd_deleteheader_context_data *ctx_data;
171
0
  sieve_number_t index;
172
173
  /* Detach the tag itself */
174
0
  *arg = sieve_ast_arguments_detach(*arg,1);
175
176
  /* Check syntax:
177
   *   :index number
178
   */
179
0
  if (!sieve_validate_tag_parameter(valdtr, cmd, tag, *arg, NULL, 0,
180
0
            SAAT_NUMBER, FALSE))
181
0
    return FALSE;
182
183
0
  index = sieve_ast_argument_number(*arg);
184
0
  if (index > INT_MAX) {
185
0
    sieve_argument_validate_warning(
186
0
      valdtr, *arg,
187
0
      "the :%s tag for the %s %s has a parameter value '%llu' "
188
0
      "exceeding the maximum (%d)",
189
0
      sieve_argument_identifier(tag),
190
0
      sieve_command_identifier(cmd),
191
0
      sieve_command_type_name(cmd),
192
0
      (unsigned long long)index, INT_MAX);
193
0
    return FALSE;
194
0
  }
195
196
0
  ctx_data = cmd_deleteheader_get_context(cmd);
197
0
  ctx_data->arg_index = *arg;
198
199
  /* Skip parameter */
200
0
  *arg = sieve_ast_argument_next(*arg);
201
202
0
  return TRUE;
203
0
}
204
205
static bool
206
cmd_deleteheader_validate_last_tag(struct sieve_validator *valdtr ATTR_UNUSED,
207
           struct sieve_ast_argument **arg,
208
           struct sieve_command *cmd)
209
0
{
210
0
  struct cmd_deleteheader_context_data *ctx_data;
211
212
0
  ctx_data = cmd_deleteheader_get_context(cmd);
213
0
  ctx_data->arg_last = *arg;
214
215
  /* Skip parameter */
216
0
  *arg = sieve_ast_argument_next(*arg);
217
218
0
  return TRUE;
219
0
}
220
221
/*
222
 * Validation
223
 */
224
225
static bool
226
cmd_deleteheader_validate(struct sieve_validator *valdtr,
227
        struct sieve_command *cmd)
228
0
{
229
0
  struct sieve_ast_argument *arg = cmd->first_positional;
230
0
  struct cmd_deleteheader_context_data *ctx_data =
231
0
    (struct cmd_deleteheader_context_data *)cmd->data;
232
0
  struct sieve_comparator cmp_default =
233
0
    SIEVE_COMPARATOR_DEFAULT(i_ascii_casemap_comparator);
234
0
  struct sieve_match_type mcht_default =
235
0
    SIEVE_MATCH_TYPE_DEFAULT(is_match_type);
236
237
0
  if (ctx_data != NULL) {
238
0
    if (ctx_data->arg_last != NULL && ctx_data->arg_index == NULL) {
239
0
      sieve_argument_validate_error(
240
0
        valdtr, ctx_data->arg_last,
241
0
        "the :last tag for the %s %s cannot be specified "
242
0
        "without the :index tag",
243
0
        sieve_command_identifier(cmd),
244
0
        sieve_command_type_name(cmd));
245
0
    }
246
0
  }
247
248
  /* Field name argument */
249
250
0
  if (arg == NULL) {
251
0
    sieve_command_validate_error(
252
0
      valdtr, cmd,
253
0
      "the %s %s expects at least one positional argument, "
254
0
      "but none was found",
255
0
      sieve_command_identifier(cmd),
256
0
      sieve_command_type_name(cmd));
257
0
    return FALSE;
258
0
  }
259
260
0
  if (!sieve_validate_positional_argument(valdtr, cmd, arg, "field name",
261
0
            1, SAAT_STRING_LIST))
262
0
    return FALSE;
263
0
  if (!sieve_validator_argument_activate(valdtr, cmd, arg, FALSE))
264
0
    return FALSE;
265
266
0
  if (sieve_argument_is_string_literal(arg)) {
267
0
    string_t *fname = sieve_ast_argument_str(arg);
268
269
0
    if (!rfc2822_header_field_name_verify(str_c(fname),
270
0
                  str_len(fname))) {
271
0
      sieve_argument_validate_error(
272
0
        valdtr, arg, "deleteheader command:"
273
0
        "specified field name '%s' is invalid",
274
0
        str_sanitize(str_c(fname), 80));
275
0
      return FALSE;
276
0
    }
277
278
0
    if (!ext_editheader_header_allow_delete(
279
0
      cmd->ext, str_c(fname))) {
280
0
      sieve_argument_validate_warning(
281
0
        valdtr, arg, "deleteheader command: "
282
0
        "deleting specified header field '%s' is forbidden; "
283
0
        "modification will be denied",
284
0
        str_sanitize(str_c(fname), 80));
285
0
    }
286
0
  }
287
288
  /* Value patterns argument */
289
290
0
  arg = sieve_ast_argument_next(arg);
291
0
  if (arg == NULL) {
292
    /* There is none; let's not generate code for useless match
293
       arguments */
294
0
    sieve_match_type_arguments_remove(valdtr, cmd);
295
0
    return TRUE;
296
0
  }
297
298
0
  if (!sieve_validate_positional_argument(
299
0
    valdtr, cmd, arg, "value patterns", 2, SAAT_STRING_LIST))
300
0
    return FALSE;
301
0
  if (!sieve_validator_argument_activate(valdtr, cmd, arg, FALSE))
302
0
    return FALSE;
303
304
  /* Validate the value patterns to a specified match type */
305
0
  return sieve_match_type_validate(valdtr, cmd, arg,
306
0
           &mcht_default, &cmp_default);
307
0
}
308
309
/*
310
 * Code generation
311
 */
312
313
static bool
314
cmd_deleteheader_generate(const struct sieve_codegen_env *cgenv,
315
        struct sieve_command *cmd)
316
0
{
317
0
  sieve_operation_emit(cgenv->sblock, cmd->ext, &deleteheader_operation);
318
319
  /* Generate arguments */
320
0
  if (!sieve_generate_arguments(cgenv, cmd, NULL))
321
0
    return FALSE;
322
323
  /* Emit a placeholder when the value-patterns argument is missing */
324
0
  if (sieve_ast_argument_next(cmd->first_positional) == NULL)
325
0
    sieve_opr_omitted_emit(cgenv->sblock);
326
327
0
  return TRUE;
328
0
}
329
330
/*
331
 * Code dump
332
 */
333
334
static bool
335
cmd_deleteheader_operation_dump(const struct sieve_dumptime_env *denv,
336
        sieve_size_t *address)
337
0
{
338
0
  int opt_code = 0;
339
340
0
  sieve_code_dumpf(denv, "DELETEHEADER");
341
0
  sieve_code_descend(denv);
342
343
  /* Optional operands */
344
0
  for (;;) {
345
0
    int opt;
346
347
0
    opt = sieve_match_opr_optional_dump(denv, address, &opt_code);
348
0
    if (opt < 0)
349
0
      return FALSE;
350
0
    if (opt == 0)
351
0
      break;
352
353
0
    switch (opt_code) {
354
0
    case OPT_INDEX:
355
0
      if (!sieve_opr_number_dump(denv, address, "index"))
356
0
        return FALSE;
357
0
      break;
358
0
    case OPT_LAST:
359
0
      sieve_code_dumpf(denv, "last");
360
0
      break;
361
0
    default:
362
0
      return FALSE;
363
0
    }
364
0
  };
365
366
0
  if (!sieve_opr_string_dump(denv, address, "field name"))
367
0
    return FALSE;
368
369
0
  return sieve_opr_stringlist_dump_ex(
370
0
    denv, address, "value patterns", "");
371
0
}
372
373
/*
374
 * Code execution
375
 */
376
377
static int
378
cmd_deleteheader_operation_execute(const struct sieve_runtime_env *renv,
379
           sieve_size_t *address)
380
0
{
381
0
  const struct sieve_extension *this_ext = renv->oprtn->ext;
382
0
  int opt_code = 0;
383
0
  struct sieve_comparator cmp =
384
0
    SIEVE_COMPARATOR_DEFAULT(i_ascii_casemap_comparator);
385
0
  struct sieve_match_type mcht =
386
0
    SIEVE_MATCH_TYPE_DEFAULT(is_match_type);
387
0
  string_t *field_name;
388
0
  struct sieve_stringlist *vpattern_list = NULL;
389
0
  struct edit_mail *edmail;
390
0
  sieve_number_t index_offset = 0;
391
0
  bool index_last = FALSE;
392
0
  bool trace = FALSE;
393
0
  int ret;
394
395
  /*
396
   * Read operands
397
   */
398
399
0
  for (;;) {
400
0
    int opt;
401
402
0
    opt = sieve_match_opr_optional_read(renv, address, &opt_code,
403
0
                &ret, &cmp, &mcht);
404
0
    if (opt < 0)
405
0
      return ret;
406
0
    if (opt == 0)
407
0
      break;
408
409
0
    switch (opt_code) {
410
0
    case OPT_INDEX:
411
0
      ret = sieve_opr_number_read(renv, address, "index",
412
0
                &index_offset);
413
0
      if (ret <= 0)
414
0
        return ret;
415
0
      if (index_offset > INT_MAX) {
416
0
        sieve_runtime_trace_error(
417
0
          renv, "index is > %d", INT_MAX);
418
0
        return SIEVE_EXEC_BIN_CORRUPT;
419
0
      }
420
0
      break;
421
0
    case OPT_LAST:
422
0
      index_last = TRUE;
423
0
      break;
424
0
    default:
425
0
      sieve_runtime_trace_error(
426
0
        renv, "unknown optional operand");
427
0
      return SIEVE_EXEC_BIN_CORRUPT;
428
0
    }
429
0
  }
430
431
  /* Read field-name */
432
0
  ret = sieve_opr_string_read(renv, address, "field-name", &field_name);
433
0
  if (ret <= 0)
434
0
    return ret;
435
436
  /* Read value-patterns */
437
0
  ret = sieve_opr_stringlist_read_ex(renv, address, "value-patterns",
438
0
             TRUE, &vpattern_list);
439
0
  if (ret <= 0)
440
0
    return ret;
441
442
  /*
443
   * Verify arguments
444
   */
445
446
0
  if (!rfc2822_header_field_name_verify(str_c(field_name),
447
0
                str_len(field_name))) {
448
0
    sieve_runtime_error(
449
0
      renv, NULL, "deleteheader action: "
450
0
      "specified field name '%s' is invalid",
451
0
      str_sanitize(str_c(field_name), 80));
452
0
    return SIEVE_EXEC_FAILURE;
453
0
  }
454
455
0
  if (!ext_editheader_header_allow_delete(this_ext, str_c(field_name))) {
456
0
    sieve_runtime_warning(
457
0
      renv, NULL, "deleteheader action: "
458
0
      "deleting specified header field '%s' is forbidden; "
459
0
      "modification denied",
460
0
      str_sanitize(str_c(field_name), 80));
461
0
    return SIEVE_EXEC_OK;
462
0
  }
463
464
  /*
465
   * Execute command
466
   */
467
468
0
  sieve_runtime_trace(renv, SIEVE_TRLVL_COMMANDS, "deleteheader command");
469
470
  /* Start editing the mail */
471
0
  edmail = sieve_message_edit(renv->msgctx);
472
473
0
  trace = sieve_runtime_trace_active(renv, SIEVE_TRLVL_COMMANDS);
474
475
  /* Either do string matching or just kill all/indexed notify action(s)
476
   */
477
0
  if (vpattern_list != NULL) {
478
0
    struct edit_mail_header_iter *edhiter;
479
0
    struct sieve_match_context *mctx;
480
481
0
    if (trace) {
482
0
      sieve_runtime_trace_descend(renv);
483
0
      if (index_offset != 0) {
484
0
        sieve_runtime_trace(
485
0
          renv, 0,
486
0
          "deleting matching occurrences of header '%s' at index %llu%s",
487
0
          str_c(field_name),
488
0
          (unsigned long long)index_offset,
489
0
          (index_last ? " from last": ""));
490
0
      } else {
491
0
        sieve_runtime_trace(
492
0
          renv, 0,
493
0
          "deleting matching occurrences of header '%s'",
494
0
          str_c(field_name));
495
0
      }
496
0
    }
497
498
    /* Iterate through all headers and delete those that match */
499
0
    ret = edit_mail_headers_iterate_init(edmail, str_c(field_name),
500
0
                 index_last, &edhiter);
501
0
    if (ret > 0) {
502
0
      int mret = 0;
503
0
      sieve_number_t pos = 0;
504
505
      /* Initialize match */
506
0
      mctx = sieve_match_begin(renv, &mcht, &cmp);
507
508
      /* Match */
509
0
      for (;;) {
510
0
        pos++;
511
512
        /* Check index if any */
513
0
        if (index_offset == 0 || pos == index_offset) {
514
0
          const char *value;
515
0
          int match;
516
517
          /* Match value against all value patterns */
518
0
          edit_mail_headers_iterate_get(edhiter, &value);
519
0
          match = sieve_match_value(
520
0
            mctx, value, strlen(value),
521
0
            vpattern_list);
522
0
          if (match < 0)
523
0
            break;
524
525
0
          if (match > 0) {
526
            /* Remove it and iterate to next */
527
0
            sieve_runtime_trace(
528
0
              renv, 0,
529
0
              "deleting header with value '%s'",
530
0
              value);
531
532
0
            if (!edit_mail_headers_iterate_remove(edhiter))
533
0
              break;
534
0
            continue;
535
0
          }
536
0
        }
537
538
0
        if (!edit_mail_headers_iterate_next(edhiter))
539
0
          break;
540
0
      }
541
542
      /* Finish match */
543
0
      mret = sieve_match_end(&mctx, &ret);
544
545
0
      edit_mail_headers_iterate_deinit(&edhiter);
546
0
      if (mret < 0)
547
0
        return ret;
548
0
    }
549
550
0
    if (ret == 0) {
551
0
      sieve_runtime_trace(renv, 0, "header '%s' not found",
552
0
              str_c(field_name));
553
0
    } else if (ret < 0) {
554
0
      sieve_runtime_warning(
555
0
        renv, NULL, "deleteheader action: "
556
0
        "failed to delete occurrences of header '%s' "
557
0
        "(this should not happen!)",
558
0
        str_c(field_name));
559
0
    }
560
561
0
  } else {
562
0
    int index = (index_last ?
563
0
           -((int)index_offset) : ((int)index_offset));
564
565
0
    if (trace) {
566
0
      sieve_runtime_trace_descend(renv);
567
0
      if (index_offset != 0) {
568
0
        sieve_runtime_trace(
569
0
          renv, 0,
570
0
          "deleting header '%s' at index %llu%s",
571
0
          str_c(field_name),
572
0
          (unsigned long long)index_offset,
573
0
          (index_last ? " from last": ""));
574
0
      } else {
575
0
        sieve_runtime_trace(
576
0
          renv, 0, "deleting header '%s'",
577
0
          str_c(field_name));
578
0
      }
579
0
    }
580
581
    /* Delete all occurrences of header */
582
0
    ret = edit_mail_header_delete(edmail, str_c(field_name), index);
583
0
    if (ret < 0) {
584
0
      sieve_runtime_warning(
585
0
        renv, NULL, "deleteheader action: "
586
0
        "failed to delete occurrences of header '%s' "
587
0
        "(this should not happen!)",
588
0
        str_c(field_name));
589
0
    } else if (trace) {
590
0
      sieve_runtime_trace(
591
0
        renv, 0,
592
0
        "deleted %d occurrences of header '%s'",
593
0
        ret, str_c(field_name));
594
0
    }
595
0
  }
596
597
0
  return SIEVE_EXEC_OK;
598
0
}