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