Coverage Report

Created: 2026-05-30 06:37

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