Coverage Report

Created: 2026-09-14 07:03

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/pigeonhole/src/lib-sieve/plugins/metadata/tst-metadata.c
Line
Count
Source
1
/* Copyright (c) Pigeonhole authors, see top-level COPYING file */
2
3
#include "lib.h"
4
#include "str-sanitize.h"
5
#include "istream.h"
6
7
#include "sieve-common.h"
8
#include "sieve-limits.h"
9
#include "sieve-commands.h"
10
#include "sieve-actions.h"
11
#include "sieve-stringlist.h"
12
#include "sieve-code.h"
13
#include "sieve-comparators.h"
14
#include "sieve-match-types.h"
15
#include "sieve-validator.h"
16
#include "sieve-generator.h"
17
#include "sieve-interpreter.h"
18
#include "sieve-dump.h"
19
#include "sieve-match.h"
20
21
#include "ext-metadata-common.h"
22
23
#define TST_METADATA_MAX_MATCH_SIZE SIEVE_MAX_STRING_LEN
24
25
/*
26
 * Test definitions
27
 */
28
29
/* Forward declarations */
30
31
static bool
32
tst_metadata_registered(struct sieve_validator *valdtr,
33
      const struct sieve_extension *ext,
34
      struct sieve_command_registration *cmd_reg);
35
static bool
36
tst_metadata_validate(struct sieve_validator *valdtr,
37
          struct sieve_command *tst);
38
static bool
39
tst_metadata_generate(const struct sieve_codegen_env *cgenv,
40
          struct sieve_command *cmd);
41
42
/* Metadata test
43
 *
44
 * Syntax:
45
 *   metadata [MATCH-TYPE] [COMPARATOR]
46
 *            <mailbox: string>
47
 *            <annotation-name: string> <key-list: string-list>
48
 */
49
50
const struct sieve_command_def metadata_test = {
51
  .identifier = "metadata",
52
  .type = SCT_TEST,
53
  .positional_args = 3,
54
  .subtests = 0,
55
  .block_allowed = FALSE,
56
  .block_required = FALSE,
57
  .registered = tst_metadata_registered,
58
  .validate = tst_metadata_validate,
59
  .generate = tst_metadata_generate,
60
};
61
62
/* Servermetadata test
63
 *
64
 * Syntax:
65
 *   servermetadata [MATCH-TYPE] [COMPARATOR]
66
 *            <annotation-name: string> <key-list: string-list>
67
 */
68
69
const struct sieve_command_def servermetadata_test = {
70
  .identifier = "servermetadata",
71
  .type = SCT_TEST,
72
  .positional_args = 2,
73
  .subtests = 0,
74
  .block_allowed = FALSE,
75
  .block_required = FALSE,
76
  .registered = tst_metadata_registered,
77
  .validate = tst_metadata_validate,
78
  .generate = tst_metadata_generate,
79
};
80
81
/*
82
 * Opcode definitions
83
 */
84
85
static bool
86
tst_metadata_operation_dump(const struct sieve_dumptime_env *denv,
87
          sieve_size_t *address);
88
static int
89
tst_metadata_operation_execute(const struct sieve_runtime_env *renv,
90
             sieve_size_t *address);
91
92
/* Metadata operation */
93
94
const struct sieve_operation_def metadata_operation = {
95
  .mnemonic = "METADATA",
96
  .ext_def = &mboxmetadata_extension,
97
  .code = EXT_METADATA_OPERATION_METADATA,
98
  .dump = tst_metadata_operation_dump,
99
  .execute = tst_metadata_operation_execute,
100
};
101
102
/* Servermetadata operation */
103
104
const struct sieve_operation_def servermetadata_operation = {
105
  .mnemonic = "SERVERMETADATA",
106
  .ext_def = &servermetadata_extension,
107
  .code = EXT_METADATA_OPERATION_METADATA,
108
  .dump = tst_metadata_operation_dump,
109
  .execute = tst_metadata_operation_execute,
110
};
111
112
/*
113
 * Test registration
114
 */
115
116
static bool
117
tst_metadata_registered(struct sieve_validator *valdtr,
118
      const struct sieve_extension *ext ATTR_UNUSED,
119
      struct sieve_command_registration *cmd_reg)
120
0
{
121
  /* The order of these is not significant */
122
0
  sieve_comparators_link_tag(valdtr, cmd_reg,
123
0
           SIEVE_MATCH_OPT_COMPARATOR);
124
0
  sieve_match_types_link_tags(valdtr, cmd_reg,
125
0
            SIEVE_MATCH_OPT_MATCH_TYPE);
126
0
  return TRUE;
127
0
}
128
129
/*
130
 * Test validation
131
 */
132
133
static bool
134
tst_metadata_validate(struct sieve_validator *valdtr, struct sieve_command *tst)
135
0
{
136
0
  struct sieve_ast_argument *arg = tst->first_positional;
137
0
  const struct sieve_match_type mcht_default =
138
0
    SIEVE_MATCH_TYPE_DEFAULT(is_match_type);
139
0
  const struct sieve_comparator cmp_default =
140
0
    SIEVE_COMPARATOR_DEFAULT(i_ascii_casemap_comparator);
141
0
  unsigned int arg_index = 1;
142
0
  const char *error;
143
144
  /* mailbox */
145
0
  if (sieve_command_is(tst, metadata_test)) {
146
0
    if (!sieve_validate_positional_argument(
147
0
      valdtr, tst, arg, "mailbox", arg_index++,
148
0
      SAAT_STRING))
149
0
      return FALSE;
150
151
0
    if (!sieve_validator_argument_activate(valdtr, tst, arg, FALSE))
152
0
      return FALSE;
153
154
    /* Check name validity when mailbox argument is not a variable
155
     */
156
0
    if (sieve_argument_is_string_literal(arg)) {
157
0
      const char *mailbox = sieve_ast_argument_strc(arg);
158
0
      const char *error;
159
160
0
      if (!sieve_mailbox_check_name(mailbox, &error)) {
161
0
        sieve_argument_validate_warning(
162
0
          valdtr, arg, "%s test: "
163
0
          "invalid mailbox name '%s' specified: %s",
164
0
          sieve_command_identifier(tst),
165
0
          str_sanitize(mailbox, 256), error);
166
0
      }
167
0
    }
168
0
    arg = sieve_ast_argument_next(arg);
169
0
  }
170
171
  /* annotation-name */
172
0
  if (!sieve_validate_positional_argument(valdtr, tst, arg,
173
0
            "annotation-name", arg_index++,
174
0
            SAAT_STRING))
175
0
    return FALSE;
176
177
0
  if (!sieve_validator_argument_activate(valdtr, tst, arg, FALSE))
178
0
    return FALSE;
179
180
0
  if (sieve_argument_is_string_literal(arg)) {
181
0
    string_t *aname = sieve_ast_argument_str(arg);
182
183
0
    if (!imap_metadata_verify_entry_name(str_c(aname), &error)) {
184
0
      sieve_argument_validate_warning(
185
0
        valdtr, arg, "%s test: "
186
0
        "specified annotation name '%s' is invalid: %s",
187
0
        sieve_command_identifier(tst),
188
0
        str_sanitize(str_c(aname), 256),
189
0
        sieve_error_from_external(error));
190
0
    }
191
0
  }
192
193
0
  arg = sieve_ast_argument_next(arg);
194
195
  /* key-list */
196
0
  if (!sieve_validate_positional_argument(valdtr, tst, arg,
197
0
            "key-list", arg_index++,
198
0
            SAAT_STRING_LIST))
199
0
    return FALSE;
200
201
0
  if (!sieve_validator_argument_activate(valdtr, tst, arg, FALSE))
202
0
    return FALSE;
203
204
  /* Validate the key argument to a specified match type */
205
0
  return sieve_match_type_validate(valdtr, tst, arg,
206
0
           &mcht_default, &cmp_default);
207
0
}
208
209
/*
210
 * Test generation
211
 */
212
213
static bool
214
tst_metadata_generate(const struct sieve_codegen_env *cgenv,
215
          struct sieve_command *tst)
216
0
{
217
0
  if (sieve_command_is(tst, metadata_test)) {
218
0
    sieve_operation_emit(cgenv->sblock, tst->ext,
219
0
             &metadata_operation);
220
0
  } else if (sieve_command_is(tst, servermetadata_test)) {
221
0
    sieve_operation_emit(cgenv->sblock, tst->ext,
222
0
             &servermetadata_operation);
223
0
  } else {
224
0
    i_unreached();
225
0
  }
226
227
  /* Generate arguments */
228
0
  if (!sieve_generate_arguments(cgenv, tst, NULL))
229
0
    return FALSE;
230
0
  return TRUE;
231
0
}
232
233
/*
234
 * Code dump
235
 */
236
237
static bool
238
tst_metadata_operation_dump(const struct sieve_dumptime_env *denv,
239
          sieve_size_t *address)
240
0
{
241
0
  bool metadata = sieve_operation_is(denv->oprtn, metadata_operation);
242
243
0
  if (metadata)
244
0
    sieve_code_dumpf(denv, "METADATA");
245
0
  else
246
0
    sieve_code_dumpf(denv, "SERVERMETADATA");
247
248
0
  sieve_code_descend(denv);
249
250
  /* Handle any optional arguments */
251
0
  if (sieve_match_opr_optional_dump(denv, address, NULL) != 0)
252
0
    return FALSE;
253
0
  if (metadata && !sieve_opr_string_dump(denv, address, "mailbox"))
254
0
    return FALSE;
255
256
0
  return (sieve_opr_string_dump(denv, address, "annotation-name") &&
257
0
    sieve_opr_stringlist_dump(denv, address, "key list"));
258
0
}
259
260
/*
261
 * Code execution
262
 */
263
264
static int
265
tst_metadata_get_annotation(const struct sieve_runtime_env *renv,
266
          const char *mailbox, const char *aname,
267
          const char **annotation_r)
268
0
{
269
0
  const struct sieve_execute_env *eenv = renv->exec_env;
270
0
  struct mail_user *user = eenv->scriptenv->user;
271
0
  struct mailbox *box;
272
0
  struct imap_metadata_transaction *imtrans;
273
0
  struct mail_attribute_value avalue;
274
0
  int status, ret;
275
276
0
  *annotation_r = NULL;
277
278
0
  if (user == NULL)
279
0
    return SIEVE_EXEC_OK;
280
281
0
  if (mailbox != NULL) {
282
0
    struct mail_namespace *ns;
283
0
    ns = mail_namespace_find(user->namespaces, mailbox);
284
0
    box = mailbox_alloc(ns->list, mailbox, 0);
285
0
    imtrans = imap_metadata_transaction_begin(box);
286
0
  } else {
287
0
    box = NULL;
288
0
    imtrans = imap_metadata_transaction_begin_server(user);
289
0
  }
290
291
0
  status = SIEVE_EXEC_OK;
292
0
  ret = imap_metadata_get(imtrans, aname, &avalue);
293
0
  if (ret < 0) {
294
0
    enum mail_error error_code;
295
0
    const char *error;
296
297
0
    error = imap_metadata_transaction_get_last_error(
298
0
      imtrans, &error_code);
299
300
0
    sieve_runtime_error(
301
0
      renv, NULL, "%s test: "
302
0
      "failed to retrieve annotation '%s': %s%s",
303
0
      (mailbox != NULL ? "metadata" : "servermetadata"),
304
0
      str_sanitize(aname, 256),
305
0
      sieve_error_from_external(error),
306
0
      (error_code == MAIL_ERROR_TEMP ?
307
0
       " (temporary failure)" : ""));
308
309
0
    status = (error_code == MAIL_ERROR_TEMP ?
310
0
        SIEVE_EXEC_TEMP_FAILURE : SIEVE_EXEC_FAILURE);
311
312
0
  } else if (avalue.value != NULL) {
313
0
    *annotation_r = avalue.value;
314
0
  }
315
0
  (void)imap_metadata_transaction_commit(&imtrans, NULL, NULL);
316
0
  if (box != NULL)
317
0
    mailbox_free(&box);
318
0
  return status;
319
0
}
320
321
static int
322
tst_metadata_operation_execute(const struct sieve_runtime_env *renv,
323
             sieve_size_t *address)
324
0
{
325
0
  bool metadata = sieve_operation_is(renv->oprtn, metadata_operation);
326
0
  struct sieve_match_type mcht =
327
0
    SIEVE_MATCH_TYPE_DEFAULT(is_match_type);
328
0
  struct sieve_comparator cmp =
329
0
    SIEVE_COMPARATOR_DEFAULT(i_ascii_casemap_comparator);
330
0
  string_t *mailbox, *aname;
331
0
  struct sieve_stringlist *value_list, *key_list;
332
0
  const char *annotation = NULL, *error;
333
0
  int match, ret;
334
335
  /*
336
   * Read operands
337
   */
338
339
  /* Handle match-type and comparator operands */
340
0
  if (sieve_match_opr_optional_read(renv, address, NULL,
341
0
            &ret, &cmp, &mcht) < 0)
342
0
    return ret;
343
344
  /* Read mailbox */
345
0
  if (metadata) {
346
0
    ret = sieve_opr_string_read(renv, address, "mailbox", &mailbox);
347
0
    if (ret <= 0)
348
0
      return ret;
349
0
  }
350
351
  /* Read annotation-name */
352
0
  ret = sieve_opr_string_read(renv, address, "annotation-name", &aname);
353
0
  if (ret <= 0)
354
0
    return ret;
355
356
  /* Read key-list */
357
0
  ret = sieve_opr_stringlist_read(renv, address, "key-list", &key_list);
358
0
  if (ret <= 0)
359
0
    return ret;
360
361
  /*
362
   * Perform operation
363
   */
364
365
0
  if (metadata) {
366
0
    sieve_runtime_trace(renv, SIEVE_TRLVL_TESTS,
367
0
            "metadata test");
368
0
  } else {
369
0
    sieve_runtime_trace(renv, SIEVE_TRLVL_TESTS,
370
0
            "servermetadata test");
371
0
  }
372
0
  sieve_runtime_trace_descend(renv);
373
374
0
  if (!imap_metadata_verify_entry_name(str_c(aname), &error)) {
375
0
    sieve_runtime_warning(
376
0
      renv, NULL, "%s test: "
377
0
      "specified annotation name '%s' is invalid: %s",
378
0
      (metadata ? "metadata" : "servermetadata"),
379
0
      str_sanitize(str_c(aname), 256),
380
0
      sieve_error_from_external(error));
381
0
    sieve_interpreter_set_test_result(renv->interp, FALSE);
382
0
    return SIEVE_EXEC_OK;
383
0
  }
384
385
0
  if (metadata) {
386
0
    if (!sieve_mailbox_check_name(str_c(mailbox), &error)) {
387
0
      sieve_runtime_warning(
388
0
        renv, NULL, "metadata test: "
389
0
        "invalid mailbox name '%s' specified: %s",
390
0
        str_sanitize(str_c(mailbox), 256), error);
391
0
      sieve_interpreter_set_test_result(renv->interp, FALSE);
392
0
      return SIEVE_EXEC_OK;
393
0
    }
394
395
0
    sieve_runtime_trace(
396
0
      renv, SIEVE_TRLVL_TESTS,
397
0
      "retrieving annotation '%s' from mailbox '%s'",
398
0
      str_sanitize(str_c(aname), 256),
399
0
      str_sanitize(str_c(mailbox), 80));
400
0
  } else {
401
0
    sieve_runtime_trace(
402
0
      renv, SIEVE_TRLVL_TESTS,
403
0
      "retrieving server annotation '%s'",
404
0
      str_sanitize(str_c(aname), 256));
405
0
  }
406
407
  /* Get annotation */
408
0
  ret = tst_metadata_get_annotation(renv,
409
0
            (metadata ? str_c(mailbox) : NULL),
410
0
            str_c(aname), &annotation);
411
0
  if (ret == SIEVE_EXEC_OK) {
412
    /* Perform match */
413
0
    if (annotation != NULL) {
414
      /* Create value stringlist */
415
0
      value_list = sieve_single_stringlist_create_cstr(
416
0
        renv, annotation, FALSE);
417
418
      /* Perform match */
419
0
      match = sieve_match(renv, &mcht, &cmp,
420
0
              value_list, key_list, &ret);
421
0
      if (ret < 0)
422
0
        return ret;
423
0
    } else {
424
0
      match = 0;
425
0
    }
426
0
  }
427
428
  /* Set test result for subsequent conditional jump */
429
0
  if (ret == SIEVE_EXEC_OK)
430
0
    sieve_interpreter_set_test_result(renv->interp, match > 0);
431
0
  return ret;
432
0
}