Coverage Report

Created: 2026-07-16 07:09

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/pigeonhole/src/lib-sieve/plugins/metadata/tst-metadataexists.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 "mail-storage.h"
6
#include "mail-namespace.h"
7
8
#include "sieve-common.h"
9
#include "sieve-commands.h"
10
#include "sieve-actions.h"
11
#include "sieve-stringlist.h"
12
#include "sieve-code.h"
13
#include "sieve-validator.h"
14
#include "sieve-generator.h"
15
#include "sieve-interpreter.h"
16
#include "sieve-dump.h"
17
18
#include "ext-metadata-common.h"
19
20
/*
21
 * Command definitions
22
 */
23
24
/* Forward declarations */
25
26
static bool
27
tst_metadataexists_validate(struct sieve_validator *valdtr,
28
          struct sieve_command *tst);
29
static bool
30
tst_metadataexists_generate(const struct sieve_codegen_env *cgenv,
31
          struct sieve_command *ctx);
32
33
/* Metadataexists command
34
 *
35
 * Syntax:
36
 *    metadataexists <mailbox: string> <annotation-names: string-list>
37
 */
38
39
static bool
40
tst_metadataexists_validate(struct sieve_validator *valdtr,
41
          struct sieve_command *tst);
42
static bool
43
tst_metadataexists_generate(const struct sieve_codegen_env *cgenv,
44
          struct sieve_command *ctx);
45
46
const struct sieve_command_def metadataexists_test = {
47
  .identifier = "metadataexists",
48
  .type = SCT_TEST,
49
  .positional_args = 2,
50
  .subtests = 0,
51
  .block_allowed = FALSE,
52
  .block_required = FALSE,
53
  .validate = tst_metadataexists_validate,
54
  .generate = tst_metadataexists_generate,
55
};
56
57
/* Servermetadataexists command
58
 *
59
 * Syntax:
60
 *    servermetadataexists <annotation-names: string-list>
61
 */
62
63
const struct sieve_command_def servermetadataexists_test = {
64
  .identifier = "servermetadataexists",
65
  .type = SCT_TEST,
66
  .positional_args = 1,
67
  .subtests = 0,
68
  .block_allowed = FALSE,
69
  .block_required = FALSE,
70
  .validate = tst_metadataexists_validate,
71
  .generate = tst_metadataexists_generate,
72
};
73
74
/*
75
 * Opcode definitions
76
 */
77
78
static bool
79
tst_metadataexists_operation_dump(const struct sieve_dumptime_env *denv,
80
          sieve_size_t *address);
81
static int
82
tst_metadataexists_operation_execute(const struct sieve_runtime_env *renv,
83
             sieve_size_t *address);
84
85
/* Metadata operation */
86
87
const struct sieve_operation_def metadataexists_operation = {
88
  .mnemonic = "METADATAEXISTS",
89
  .ext_def = &mboxmetadata_extension,
90
  .code = EXT_METADATA_OPERATION_METADATAEXISTS,
91
  .dump = tst_metadataexists_operation_dump,
92
  .execute = tst_metadataexists_operation_execute,
93
};
94
95
/* Mailboxexists operation */
96
97
const struct sieve_operation_def servermetadataexists_operation = {
98
  .mnemonic = "SERVERMETADATAEXISTS",
99
  .ext_def = &servermetadata_extension,
100
  .code = EXT_METADATA_OPERATION_METADATAEXISTS,
101
  .dump = tst_metadataexists_operation_dump,
102
  .execute = tst_metadataexists_operation_execute,
103
};
104
105
/*
106
 * Test validation
107
 */
108
109
struct _validate_context {
110
  struct sieve_validator *valdtr;
111
  struct sieve_command *tst;
112
};
113
114
static int
115
tst_metadataexists_annotation_validate(void *context,
116
               struct sieve_ast_argument *arg)
117
0
{
118
0
  struct _validate_context *valctx =
119
0
    (struct _validate_context *)context;
120
121
0
  if (sieve_argument_is_string_literal(arg)) {
122
0
    const char *aname = sieve_ast_strlist_strc(arg);
123
0
    const char *error;
124
125
0
    if (!imap_metadata_verify_entry_name(aname, &error)) {
126
0
      sieve_argument_validate_warning(
127
0
        valctx->valdtr, arg, "%s test: "
128
0
        "specified annotation name '%s' is invalid: %s",
129
0
        sieve_command_identifier(valctx->tst),
130
0
        str_sanitize(aname, 256),
131
0
        sieve_error_from_external(error));
132
0
    }
133
0
  }
134
0
  return 1; /* Can't check at compile time */
135
0
}
136
137
static bool
138
tst_metadataexists_validate(struct sieve_validator *valdtr,
139
          struct sieve_command *tst)
140
0
{
141
0
  struct sieve_ast_argument *arg = tst->first_positional;
142
0
  struct sieve_ast_argument *aarg;
143
0
  struct _validate_context valctx;
144
0
  unsigned int arg_index = 1;
145
146
0
  if (sieve_command_is(tst, metadataexists_test)) {
147
0
    if (!sieve_validate_positional_argument(valdtr, tst, arg,
148
0
              "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
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
0
  if (!sieve_validate_positional_argument(valdtr, tst, arg,
172
0
            "annotation-names", arg_index++,
173
0
            SAAT_STRING_LIST))
174
0
    return FALSE;
175
0
  if (!sieve_validator_argument_activate(valdtr, tst, arg, FALSE))
176
0
    return FALSE;
177
178
0
  aarg = arg;
179
0
  i_zero(&valctx);
180
0
  valctx.valdtr = valdtr;
181
0
  valctx.tst = tst;
182
183
0
  return (sieve_ast_stringlist_map(
184
0
    &aarg, &valctx,
185
0
    tst_metadataexists_annotation_validate) >= 0);
186
0
}
187
188
/*
189
 * Test generation
190
 */
191
192
static bool
193
tst_metadataexists_generate(const struct sieve_codegen_env *cgenv,
194
          struct sieve_command *tst)
195
0
{
196
0
  if (sieve_command_is(tst, metadataexists_test)) {
197
0
    sieve_operation_emit(cgenv->sblock, tst->ext,
198
0
             &metadataexists_operation);
199
0
  } else if (sieve_command_is(tst, servermetadataexists_test)) {
200
0
    sieve_operation_emit(cgenv->sblock, tst->ext,
201
0
             &servermetadataexists_operation);
202
0
  } else {
203
0
    i_unreached();
204
0
  }
205
206
  /* Generate arguments */
207
0
  return sieve_generate_arguments(cgenv, tst, NULL);
208
0
}
209
210
/*
211
 * Code dump
212
 */
213
214
static bool
215
tst_metadataexists_operation_dump(const struct sieve_dumptime_env *denv,
216
          sieve_size_t *address)
217
0
{
218
0
  bool metadata = sieve_operation_is(denv->oprtn,
219
0
             metadataexists_operation);
220
221
0
  if (metadata)
222
0
    sieve_code_dumpf(denv, "METADATAEXISTS");
223
0
  else
224
0
    sieve_code_dumpf(denv, "SERVERMETADATAEXISTS");
225
226
0
  sieve_code_descend(denv);
227
228
0
  if (metadata && !sieve_opr_string_dump(denv, address, "mailbox"))
229
0
    return FALSE;
230
231
0
  return sieve_opr_stringlist_dump(denv, address, "annotation-names");
232
0
}
233
234
/*
235
 * Code execution
236
 */
237
238
static int
239
tst_metadataexists_check_annotation(const struct sieve_runtime_env *renv,
240
            struct imap_metadata_transaction *imtrans,
241
            const char *mailbox, const char *aname,
242
            bool *all_exist_r)
243
0
{
244
0
  struct mail_attribute_value avalue;
245
0
  const char *error;
246
0
  int ret;
247
248
0
  if (!imap_metadata_verify_entry_name(aname, &error)) {
249
0
    sieve_runtime_warning(
250
0
      renv, NULL, "%s test: "
251
0
      "specified annotation name '%s' is invalid: %s",
252
0
      (mailbox != NULL ?
253
0
       "metadataexists" : "servermetadataexists"),
254
0
      str_sanitize(aname, 256),
255
0
      sieve_error_from_external(error));
256
0
    *all_exist_r = FALSE;
257
0
    return SIEVE_EXEC_OK;
258
0
  }
259
260
0
  ret = imap_metadata_get(imtrans, aname, &avalue);
261
0
  if (ret < 0) {
262
0
    enum mail_error error_code;
263
0
    const char *error;
264
265
0
    error = imap_metadata_transaction_get_last_error(
266
0
      imtrans, &error_code);
267
0
    sieve_runtime_error(
268
0
      renv, NULL, "%s test: "
269
0
      "failed to retrieve annotation '%s': %s%s",
270
0
      (mailbox != NULL ?
271
0
       "metadataexists" : "servermetadataexists"),
272
0
      str_sanitize(aname, 256),
273
0
      sieve_error_from_external(error),
274
0
      (error_code == MAIL_ERROR_TEMP ?
275
0
       " (temporary failure)" : ""));
276
277
0
    *all_exist_r = FALSE;
278
0
    return (error_code == MAIL_ERROR_TEMP ?
279
0
      SIEVE_EXEC_TEMP_FAILURE : SIEVE_EXEC_FAILURE);
280
0
  }
281
0
  if (avalue.value == NULL && avalue.value_stream == NULL) {
282
0
    sieve_runtime_trace(renv, 0,
283
0
            "annotation '%s': not found", aname);
284
0
    *all_exist_r = FALSE;
285
0
  }
286
287
0
  sieve_runtime_trace(renv, 0, "annotation '%s': found", aname);
288
0
  return SIEVE_EXEC_OK;
289
0
}
290
291
static int
292
tst_metadataexists_check_annotations(const struct sieve_runtime_env *renv,
293
             const char *mailbox,
294
             struct sieve_stringlist *anames,
295
             bool *all_exist_r)
296
0
{
297
0
  const struct sieve_execute_env *eenv = renv->exec_env;
298
0
  struct mail_user *user = eenv->scriptenv->user;
299
0
  struct mailbox *box = NULL;
300
0
  struct imap_metadata_transaction *imtrans;
301
0
  string_t *aname;
302
0
  bool all_exist = TRUE;
303
0
  int ret, sret, status;
304
305
0
  *all_exist_r = FALSE;
306
307
0
  if (user == NULL)
308
0
    return SIEVE_EXEC_OK;
309
310
0
  if (mailbox != NULL) {
311
0
    struct mail_namespace *ns;
312
0
    ns = mail_namespace_find(user->namespaces, mailbox);
313
0
    box = mailbox_alloc(ns->list, mailbox, 0);
314
0
    imtrans = imap_metadata_transaction_begin(box);
315
0
  } else {
316
0
    imtrans = imap_metadata_transaction_begin_server(user);
317
0
  }
318
319
0
  if (mailbox != NULL) {
320
0
    sieve_runtime_trace(
321
0
      renv, SIEVE_TRLVL_TESTS,
322
0
      "checking annotations of mailbox '%s':",
323
0
      str_sanitize(mailbox, 80));
324
0
  } else {
325
0
    sieve_runtime_trace(
326
0
      renv, SIEVE_TRLVL_TESTS,
327
0
      "checking server annotations");
328
0
  }
329
330
0
  aname = NULL;
331
0
  status = SIEVE_EXEC_OK;
332
0
  while (all_exist &&
333
0
         (sret = sieve_stringlist_next_item(anames, &aname)) > 0) {
334
0
    ret = tst_metadataexists_check_annotation(
335
0
      renv, imtrans, mailbox, str_c(aname), &all_exist);
336
0
    if (ret <= 0) {
337
0
      status = ret;
338
0
      break;
339
0
    }
340
0
  }
341
342
0
  if (sret < 0) {
343
0
    sieve_runtime_trace_error(
344
0
      renv, "invalid annotation name stringlist item");
345
0
    status = SIEVE_EXEC_BIN_CORRUPT;
346
0
  }
347
348
0
  (void)imap_metadata_transaction_commit(&imtrans, NULL, NULL);
349
0
  if (box != NULL)
350
0
    mailbox_free(&box);
351
352
0
  *all_exist_r = all_exist;
353
0
  return status;
354
0
}
355
356
static int
357
tst_metadataexists_operation_execute(const struct sieve_runtime_env *renv,
358
             sieve_size_t *address)
359
0
{
360
0
  bool metadata = sieve_operation_is(renv->oprtn,
361
0
             metadataexists_operation);
362
0
  struct sieve_stringlist *anames;
363
0
  string_t *mailbox;
364
0
  bool trace = FALSE, all_exist = TRUE;
365
0
  const char *error;
366
0
  int ret;
367
368
  /*
369
   * Read operands
370
   */
371
372
  /* Read mailbox */
373
0
  if (metadata) {
374
0
    ret = sieve_opr_string_read(renv, address, "mailbox", &mailbox);
375
0
    if (ret <= 0)
376
0
      return ret;
377
0
  }
378
379
  /* Read annotation names */
380
0
  ret = sieve_opr_stringlist_read(renv, address, "annotation-names",
381
0
          &anames);
382
0
  if (ret <= 0)
383
0
    return ret;
384
385
  /*
386
   * Perform operation
387
   */
388
389
0
  if (metadata &&
390
0
      !sieve_mailbox_check_name(str_c(mailbox), &error)) {
391
0
    sieve_runtime_warning(
392
0
      renv, NULL, "metadataexists test: "
393
0
      "invalid mailbox name '%s' specified: %s",
394
0
      str_sanitize(str_c(mailbox), 256), error);
395
0
    sieve_interpreter_set_test_result(renv->interp, FALSE);
396
0
    return SIEVE_EXEC_OK;
397
0
  }
398
399
0
  if (sieve_runtime_trace_active(renv, SIEVE_TRLVL_TESTS)) {
400
0
    if (metadata) {
401
0
      sieve_runtime_trace(renv, SIEVE_TRLVL_TESTS,
402
0
              "metadataexists test");
403
0
    } else {
404
0
      sieve_runtime_trace(renv, SIEVE_TRLVL_TESTS,
405
0
              "servermetadataexists test");
406
0
    }
407
408
0
    sieve_runtime_trace_descend(renv);
409
410
0
    trace = sieve_runtime_trace_active(renv, SIEVE_TRLVL_MATCHING);
411
0
  }
412
413
0
  ret = tst_metadataexists_check_annotations(
414
0
    renv, (metadata ? str_c(mailbox) : NULL), anames, &all_exist);
415
0
  if (ret <= 0)
416
0
    return ret;
417
418
0
  if (trace) {
419
0
    if (all_exist) {
420
0
      sieve_runtime_trace(renv, 0,
421
0
              "all annotations exist");
422
0
    } else {
423
0
      sieve_runtime_trace(renv, 0,
424
0
              "some annotations do not exist");
425
0
    }
426
0
  }
427
428
0
  sieve_interpreter_set_test_result(renv->interp, all_exist);
429
0
  return SIEVE_EXEC_OK;
430
0
}