Coverage Report

Created: 2026-07-30 06:43

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/pigeonhole/src/lib-sieve/plugins/body/tst-body.c
Line
Count
Source
1
/* Copyright (c) Pigeonhole authors, see top-level COPYING file */
2
3
#include "sieve-extensions.h"
4
#include "sieve-commands.h"
5
#include "sieve-stringlist.h"
6
#include "sieve-code.h"
7
#include "sieve-comparators.h"
8
#include "sieve-match-types.h"
9
#include "sieve-address-parts.h"
10
11
#include "sieve-validator.h"
12
#include "sieve-generator.h"
13
#include "sieve-binary.h"
14
#include "sieve-interpreter.h"
15
#include "sieve-dump.h"
16
#include "sieve-match.h"
17
18
#include "ext-body-common.h"
19
20
/*
21
 * Body test
22
 *
23
 * Syntax
24
 *   body [COMPARATOR] [MATCH-TYPE] [BODY-TRANSFORM]
25
 *     <key-list: string-list>
26
 */
27
28
static bool
29
tst_body_registered(struct sieve_validator *valdtr,
30
        const struct sieve_extension *ext,
31
        struct sieve_command_registration *cmd_reg);
32
static bool
33
tst_body_validate(struct sieve_validator *valdtr, struct sieve_command *tst);
34
static bool
35
tst_body_generate(const struct sieve_codegen_env *cgenv,
36
      struct sieve_command *ctx);
37
38
const struct sieve_command_def body_test = {
39
  .identifier = "body",
40
  .type = SCT_TEST,
41
  .positional_args = 1,
42
  .subtests = 0,
43
  .block_allowed = FALSE,
44
  .block_required = FALSE,
45
  .registered = tst_body_registered,
46
  .validate = tst_body_validate,
47
  .generate = tst_body_generate
48
};
49
50
/*
51
 * Body operation
52
 */
53
54
static bool
55
ext_body_operation_dump(const struct sieve_dumptime_env *denv,
56
      sieve_size_t *address);
57
static int
58
ext_body_operation_execute(const struct sieve_runtime_env *renv,
59
         sieve_size_t *address);
60
61
const struct sieve_operation_def body_operation = {
62
  .mnemonic = "body",
63
  .ext_def = &body_extension,
64
  .dump = ext_body_operation_dump,
65
  .execute = ext_body_operation_execute
66
};
67
68
/*
69
 * Optional operands
70
 */
71
72
enum tst_body_optional {
73
  OPT_BODY_TRANSFORM = SIEVE_MATCH_OPT_LAST
74
};
75
76
/*
77
 * Tagged arguments
78
 */
79
80
/* Forward declarations */
81
82
static bool
83
tag_body_transform_validate(struct sieve_validator *valdtr,
84
          struct sieve_ast_argument **arg,
85
          struct sieve_command *cmd);
86
static bool
87
tag_body_transform_generate(const struct sieve_codegen_env *cgenv,
88
          struct sieve_ast_argument *arg,
89
          struct sieve_command *cmd);
90
91
/* Argument objects */
92
93
static const struct sieve_argument_def body_raw_tag = {
94
  .identifier = "raw",
95
  .validate = tag_body_transform_validate,
96
  .generate = tag_body_transform_generate
97
};
98
99
static const struct sieve_argument_def body_content_tag = {
100
  .identifier = "content",
101
  .validate = tag_body_transform_validate,
102
  .generate = tag_body_transform_generate
103
};
104
105
static const struct sieve_argument_def body_text_tag = {
106
  .identifier = "text",
107
  .validate = tag_body_transform_validate,
108
  .generate = tag_body_transform_generate
109
};
110
111
/* Argument implementation */
112
113
static bool
114
tag_body_transform_validate(struct sieve_validator *valdtr,
115
          struct sieve_ast_argument **arg,
116
          struct sieve_command *cmd)
117
0
{
118
0
  enum tst_body_transform transform;
119
0
  struct sieve_ast_argument *tag = *arg;
120
121
  /* BODY-TRANSFORM:
122
       :raw
123
         / :content <content-types: string-list>
124
         / :text
125
   */
126
0
  if ((bool)cmd->data) {
127
0
    sieve_argument_validate_error(
128
0
      valdtr, *arg,
129
0
      "the :raw, :content and :text arguments for the body test are mutually "
130
0
      "exclusive, but more than one was specified");
131
0
    return FALSE;
132
0
  }
133
134
  /* Skip tag */
135
0
  *arg = sieve_ast_argument_next(*arg);
136
137
  /* :content tag has a string-list argument */
138
0
  if (sieve_argument_is(tag, body_raw_tag))
139
0
    transform = TST_BODY_TRANSFORM_RAW;
140
0
  else if (sieve_argument_is(tag, body_text_tag))
141
0
    transform = TST_BODY_TRANSFORM_TEXT;
142
0
  else if (sieve_argument_is(tag, body_content_tag)) {
143
    /* Check syntax:
144
     *   :content <content-types: string-list>
145
     */
146
0
    if (!sieve_validate_tag_parameter(valdtr, cmd, tag, *arg,
147
0
              NULL, 0, SAAT_STRING_LIST,
148
0
              FALSE))
149
0
      return FALSE;
150
151
    /* Assign tag parameters */
152
0
    tag->parameters = *arg;
153
0
    *arg = sieve_ast_arguments_detach(*arg,1);
154
155
0
    transform = TST_BODY_TRANSFORM_CONTENT;
156
0
  } else {
157
0
    return FALSE;
158
0
  }
159
160
  /* Signal the presence of this tag */
161
0
  cmd->data = (void *)TRUE;
162
163
  /* Assign context data */
164
0
  tag->argument->data = (void *)transform;
165
166
0
  return TRUE;
167
0
}
168
169
/*
170
 * Command Registration
171
 */
172
173
static bool
174
tst_body_registered(struct sieve_validator *valdtr,
175
        const struct sieve_extension *ext,
176
        struct sieve_command_registration *cmd_reg)
177
0
{
178
  /* The order of these is not significant */
179
0
  sieve_comparators_link_tag(valdtr, cmd_reg, SIEVE_MATCH_OPT_COMPARATOR);
180
0
  sieve_match_types_link_tags(valdtr, cmd_reg,
181
0
            SIEVE_MATCH_OPT_MATCH_TYPE);
182
183
0
  sieve_validator_register_tag(valdtr, cmd_reg, ext,
184
0
             &body_raw_tag, OPT_BODY_TRANSFORM);
185
0
  sieve_validator_register_tag(valdtr, cmd_reg, ext,
186
0
             &body_content_tag, OPT_BODY_TRANSFORM);
187
0
  sieve_validator_register_tag(valdtr, cmd_reg, ext,
188
0
             &body_text_tag, OPT_BODY_TRANSFORM);
189
190
0
  return TRUE;
191
0
}
192
193
/*
194
 * Validation
195
 */
196
197
static bool
198
tst_body_validate(struct sieve_validator *valdtr, struct sieve_command *tst)
199
0
{
200
0
  struct sieve_ast_argument *arg = tst->first_positional;
201
0
  const struct sieve_match_type mcht_default =
202
0
    SIEVE_MATCH_TYPE_DEFAULT(is_match_type);
203
0
  const struct sieve_comparator cmp_default =
204
0
    SIEVE_COMPARATOR_DEFAULT(i_ascii_casemap_comparator);
205
206
0
  if (!sieve_validate_positional_argument(valdtr, tst, arg, "key list",
207
0
            1, SAAT_STRING_LIST))
208
0
    return FALSE;
209
210
0
  if (!sieve_validator_argument_activate(valdtr, tst, arg, FALSE))
211
0
    return FALSE;
212
213
  /* Validate the key argument to a specified match type */
214
0
  return sieve_match_type_validate(valdtr, tst, arg,
215
0
           &mcht_default, &cmp_default);
216
0
}
217
218
/*
219
 * Code generation
220
 */
221
222
static bool
223
tst_body_generate(const struct sieve_codegen_env *cgenv,
224
      struct sieve_command *cmd)
225
0
{
226
0
  (void)sieve_operation_emit(cgenv->sblock, cmd->ext, &body_operation);
227
228
  /* Generate arguments */
229
0
  return sieve_generate_arguments(cgenv, cmd, NULL);
230
0
}
231
232
static bool
233
tag_body_transform_generate(const struct sieve_codegen_env *cgenv,
234
          struct sieve_ast_argument *arg,
235
          struct sieve_command *cmd ATTR_UNUSED)
236
0
{
237
0
  enum tst_body_transform transform =
238
0
    POINTER_CAST_TO(arg->argument->data, enum tst_body_transform);
239
240
0
  sieve_binary_emit_byte(cgenv->sblock, transform);
241
0
  sieve_generate_argument_parameters(cgenv, cmd, arg);
242
0
  return TRUE;
243
0
}
244
245
/*
246
 * Code dump
247
 */
248
249
static bool
250
ext_body_operation_dump(const struct sieve_dumptime_env *denv,
251
      sieve_size_t *address)
252
0
{
253
0
  unsigned int transform;
254
0
  int opt_code = 0;
255
256
0
  sieve_code_dumpf(denv, "BODY");
257
0
  sieve_code_descend(denv);
258
259
  /* Handle any optional arguments */
260
0
  for (;;) {
261
0
    int opt;
262
263
0
    opt = sieve_match_opr_optional_dump(denv, address, &opt_code);
264
0
    if (opt < 0)
265
0
      return FALSE;
266
0
    if (opt == 0)
267
0
      break;
268
269
0
    switch (opt_code) {
270
0
    case OPT_BODY_TRANSFORM:
271
0
      if (!sieve_binary_read_byte(denv->sblock, address,
272
0
                &transform))
273
0
        return FALSE;
274
275
0
      switch (transform) {
276
0
      case TST_BODY_TRANSFORM_RAW:
277
0
        sieve_code_dumpf(denv, "BODY-TRANSFORM: RAW");
278
0
        break;
279
0
      case TST_BODY_TRANSFORM_TEXT:
280
0
        sieve_code_dumpf(denv, "BODY-TRANSFORM: TEXT");
281
0
        break;
282
0
      case TST_BODY_TRANSFORM_CONTENT:
283
0
        sieve_code_dumpf(
284
0
          denv, "BODY-TRANSFORM: CONTENT");
285
286
0
        sieve_code_descend(denv);
287
0
        if (!sieve_opr_stringlist_dump(denv, address,
288
0
                     "content types"))
289
0
          return FALSE;
290
0
        sieve_code_ascend(denv);
291
0
        break;
292
0
      default:
293
0
        return FALSE;
294
0
      }
295
0
      break;
296
0
    default:
297
0
      return FALSE;
298
0
    }
299
0
  };
300
301
0
  return sieve_opr_stringlist_dump(denv, address, "key list");
302
0
}
303
304
/*
305
 * Interpretation
306
 */
307
308
static int
309
ext_body_operation_execute(const struct sieve_runtime_env *renv,
310
         sieve_size_t *address)
311
0
{
312
0
  int opt_code = 0;
313
0
  struct sieve_comparator cmp =
314
0
    SIEVE_COMPARATOR_DEFAULT(i_ascii_casemap_comparator);
315
0
  struct sieve_match_type mcht =
316
0
    SIEVE_MATCH_TYPE_DEFAULT(is_match_type);
317
0
  unsigned int transform = TST_BODY_TRANSFORM_TEXT;
318
0
  struct sieve_stringlist *ctype_list, *value_list, *key_list;
319
0
  bool mvalues_active;
320
0
  const char *const *content_types = NULL;
321
0
  int match, ret;
322
323
  /*
324
   * Read operands
325
   */
326
327
  /* Optional operands */
328
329
0
  ctype_list = NULL;
330
0
  for (;;) {
331
0
    int opt;
332
333
0
    opt = sieve_match_opr_optional_read(renv, address, &opt_code,
334
0
                &ret, &cmp, &mcht);
335
0
    if (ret < 0)
336
0
      return ret;
337
0
    if (opt == 0)
338
0
      break;
339
340
0
    switch (opt_code) {
341
0
    case OPT_BODY_TRANSFORM:
342
0
      if (!sieve_binary_read_byte(renv->sblock, address,
343
0
                &transform) ||
344
0
          transform > TST_BODY_TRANSFORM_TEXT) {
345
0
        sieve_runtime_trace_error(
346
0
          renv, "invalid body transform type");
347
0
        return SIEVE_EXEC_BIN_CORRUPT;
348
0
      }
349
350
0
      if (transform == TST_BODY_TRANSFORM_CONTENT &&
351
0
          (ret = sieve_opr_stringlist_read(
352
0
        renv, address, "content-type-list",
353
0
        &ctype_list)) <= 0)
354
0
        return ret;
355
0
      break;
356
0
    default:
357
0
      sieve_runtime_trace_error(
358
0
        renv, "unknown optional operand");
359
0
      return SIEVE_EXEC_BIN_CORRUPT;
360
0
    }
361
0
  }
362
363
  /* Read key-list */
364
365
0
  ret = sieve_opr_stringlist_read(renv, address, "key-list", &key_list);
366
0
  if (ret <= 0)
367
0
    return ret;
368
369
0
  if (ctype_list != NULL &&
370
0
      sieve_stringlist_read_all(ctype_list, pool_datastack_create(),
371
0
              &content_types) < 0) {
372
0
    sieve_runtime_trace_error(
373
0
      renv, "failed to read content-type-list operand");
374
0
    return ctype_list->exec_status;
375
0
  }
376
377
  /*
378
   * Perform operation
379
   */
380
381
0
  sieve_runtime_trace(renv, SIEVE_TRLVL_TESTS, "body test");
382
383
  /* Extract requested parts */
384
0
  ret = ext_body_get_part_list(renv, (enum tst_body_transform)transform,
385
0
             content_types, &value_list);
386
0
  if (ret <= 0)
387
0
    return ret;
388
389
  /* Disable match values processing as required by RFC */
390
0
  mvalues_active = sieve_match_values_set_enabled(renv, FALSE);
391
392
  /* Perform match */
393
0
  match = sieve_match(renv, &mcht, &cmp, value_list, key_list, &ret);
394
395
  /* Restore match values processing */
396
0
  (void)sieve_match_values_set_enabled(renv, mvalues_active);
397
398
0
  if (match < 0)
399
0
    return ret;
400
401
  /* Set test result for subsequent conditional jump */
402
0
  sieve_interpreter_set_test_result(renv->interp, match > 0);
403
0
  return SIEVE_EXEC_OK;
404
0
}