Coverage Report

Created: 2026-08-31 07:00

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/pigeonhole/src/lib-sieve/plugins/ihave/tst-ihave.c
Line
Count
Source
1
/* Copyright (c) Pigeonhole authors, see top-level COPYING file */
2
3
#include "lib.h"
4
#include "array.h"
5
6
#include "sieve-common.h"
7
#include "sieve-stringlist.h"
8
#include "sieve-extensions.h"
9
#include "sieve-code.h"
10
#include "sieve-commands.h"
11
#include "sieve-validator.h"
12
#include "sieve-generator.h"
13
#include "sieve-interpreter.h"
14
15
#include "ext-ihave-common.h"
16
17
/*
18
 * Ihave test
19
 *
20
 * Syntax:
21
 *   ihave <capabilities: string-list>
22
 */
23
24
static bool
25
tst_ihave_validate(struct sieve_validator *valdtr, struct sieve_command *tst);
26
static bool
27
tst_ihave_validate_const(struct sieve_validator *valdtr,
28
       struct sieve_command *tst, int *const_current,
29
       int const_next);
30
static bool
31
tst_ihave_generate(const struct sieve_codegen_env *cgenv,
32
       struct sieve_command *tst);
33
34
const struct sieve_command_def ihave_test = {
35
  .identifier = "ihave",
36
  .type = SCT_TEST,
37
  .positional_args = 1,
38
  .subtests = 0,
39
  .block_allowed = FALSE,
40
  .block_required = FALSE,
41
  .validate = tst_ihave_validate,
42
  .validate_const = tst_ihave_validate_const,
43
  .generate = tst_ihave_generate,
44
};
45
46
/*
47
 * Ihave operation
48
 */
49
50
static bool
51
tst_ihave_operation_dump(const struct sieve_dumptime_env *denv,
52
       sieve_size_t *address);
53
static int
54
tst_ihave_operation_execute(const struct sieve_runtime_env *renv,
55
          sieve_size_t *address);
56
57
const struct sieve_operation_def tst_ihave_operation = {
58
  .mnemonic = "IHAVE",
59
  .ext_def = &ihave_extension,
60
  .code = EXT_IHAVE_OPERATION_IHAVE,
61
  .dump = tst_ihave_operation_dump,
62
  .execute = tst_ihave_operation_execute,
63
};
64
65
/*
66
 * Code validation
67
 */
68
69
static bool
70
tst_ihave_validate(struct sieve_validator *valdtr, struct sieve_command *tst)
71
0
{
72
0
  struct _capability {
73
0
    const struct sieve_extension *ext;
74
0
    struct sieve_ast_argument *arg;
75
0
  };
76
0
  struct sieve_ast_argument *arg = tst->first_positional;
77
0
  struct sieve_ast_argument *stritem;
78
0
  enum sieve_compile_flags cpflags =
79
0
    sieve_validator_compile_flags(valdtr);
80
0
  bool no_global = ((cpflags & SIEVE_COMPILE_FLAG_NOGLOBAL) != 0);
81
0
  ARRAY(struct _capability) capabilities;
82
0
  struct _capability capability;
83
0
  const struct _capability *caps;
84
0
  unsigned int i, count;
85
0
  bool all_known = TRUE;
86
87
0
  t_array_init(&capabilities, 64);
88
89
0
  tst->data = (void *)FALSE;
90
91
  /* Check stringlist argument */
92
0
  if (!sieve_validate_positional_argument(valdtr, tst, arg,
93
0
            "capabilities", 1,
94
0
            SAAT_STRING_LIST))
95
0
    return FALSE;
96
97
0
  switch (sieve_ast_argument_type(arg)) {
98
0
  case SAAT_STRING:
99
    /* Single string */
100
0
    capability.arg = arg;
101
0
    capability.ext = sieve_extension_get_by_name(
102
0
      tst->ext->svinst, sieve_ast_argument_strc(arg));
103
104
0
    if (capability.ext == NULL ||
105
0
        (no_global && capability.ext->global)) {
106
0
      all_known = FALSE;
107
108
0
      ext_ihave_ast_add_missing_extension(
109
0
        tst->ext, tst->ast_node->ast,
110
0
        sieve_ast_argument_strc(arg));
111
0
    } else {
112
0
      array_append(&capabilities, &capability, 1);
113
0
    }
114
115
0
    break;
116
117
0
  case SAAT_STRING_LIST:
118
    /* String list */
119
0
    stritem = sieve_ast_strlist_first(arg);
120
121
0
    while (stritem != NULL) {
122
0
      capability.arg = stritem;
123
0
      capability.ext = sieve_extension_get_by_name(
124
0
        tst->ext->svinst,
125
0
        sieve_ast_argument_strc(stritem));
126
127
0
      if (capability.ext == NULL ||
128
0
          (no_global && capability.ext->global)) {
129
0
        all_known = FALSE;
130
131
0
        ext_ihave_ast_add_missing_extension(
132
0
          tst->ext, tst->ast_node->ast,
133
0
          sieve_ast_argument_strc(stritem));
134
0
      } else {
135
0
        array_append(&capabilities, &capability, 1);
136
0
      }
137
0
      stritem = sieve_ast_strlist_next(stritem);
138
0
    }
139
140
0
    break;
141
0
  default:
142
0
    i_unreached();
143
0
  }
144
145
0
  if (!all_known)
146
0
    return TRUE;
147
148
  /* RFC 5463, Section 4, page 4:
149
150
     The "ihave" extension is designed to be used with other extensions
151
     that add tests, actions, comparators, or arguments.  Implementations
152
     MUST NOT allow it to be used with extensions that change the
153
     underlying Sieve grammar, or extensions like encoded-character
154
     [RFC5228], or variables [RFC5229] that change how the content of
155
     Sieve scripts are interpreted.  The test MUST fail and the extension
156
     MUST NOT be enabled if such usage is attempted.
157
158
     FIXME: current implementation of this restriction is hardcoded and
159
     therefore highly inflexible
160
   */
161
0
  caps = array_get(&capabilities, &count);
162
0
  for (i = 0; i < count; i++) {
163
0
    if (sieve_extension_name_is(caps[i].ext, "variables") ||
164
0
        sieve_extension_name_is(caps[i].ext, "encoded-character"))
165
0
      return TRUE;
166
0
  }
167
168
  /* Load all extensions */
169
0
  caps = array_get(&capabilities, &count);
170
0
  for (i = 0; i < count; i++) {
171
0
    if (!sieve_validator_extension_load(valdtr, tst, caps[i].arg,
172
0
                caps[i].ext, FALSE))
173
0
      return FALSE;
174
0
  }
175
176
0
  if (!sieve_validator_argument_activate(valdtr, tst, arg, FALSE))
177
0
    return FALSE;
178
179
0
  tst->data = (void *)TRUE;
180
0
  return TRUE;
181
0
}
182
183
static bool
184
tst_ihave_validate_const(struct sieve_validator *valdtr ATTR_UNUSED,
185
       struct sieve_command *tst, int *const_current,
186
       int const_next ATTR_UNUSED)
187
0
{
188
0
  if ((bool)tst->data == TRUE)
189
0
    *const_current = -1;
190
0
  else
191
0
    *const_current = 0;
192
0
  return TRUE;
193
0
}
194
195
/*
196
 * Code generation
197
 */
198
199
bool tst_ihave_generate(const struct sieve_codegen_env *cgenv,
200
      struct sieve_command *tst)
201
0
{
202
  /* Emit opcode */
203
0
  sieve_operation_emit(cgenv->sblock, tst->ext, &tst_ihave_operation);
204
205
  /* Generate arguments */
206
0
  return sieve_generate_arguments(cgenv, tst, NULL);
207
0
}
208
209
/*
210
 * Code dump
211
 */
212
213
static bool
214
tst_ihave_operation_dump(const struct sieve_dumptime_env *denv,
215
       sieve_size_t *address)
216
0
{
217
0
  sieve_code_dumpf(denv, "IHAVE");
218
0
  sieve_code_descend(denv);
219
220
0
  return sieve_opr_stringlist_dump(denv, address, "capabilities");
221
0
}
222
223
/*
224
 * Code execution
225
 */
226
227
static int
228
tst_ihave_operation_execute(const struct sieve_runtime_env *renv,
229
          sieve_size_t *address)
230
0
{
231
0
  const struct sieve_execute_env *eenv = renv->exec_env;
232
0
  struct sieve_instance *svinst = eenv->svinst;
233
0
  struct sieve_stringlist *capabilities;
234
0
  string_t *cap_item;
235
0
  bool matched;
236
0
  int ret;
237
238
  /*
239
   * Read operands
240
   */
241
242
  /* Read capabilities */
243
0
  if ((ret = sieve_opr_stringlist_read(renv, address, "capabilities",
244
0
               &capabilities)) <= 0)
245
0
    return ret;
246
247
  /*
248
   * Perform test
249
   */
250
251
  /* Perform the test */
252
0
  sieve_runtime_trace(renv, SIEVE_TRLVL_TESTS, "ihave test");
253
0
  sieve_runtime_trace_descend(renv);
254
255
0
  cap_item = NULL;
256
0
  matched = TRUE;
257
0
  while (matched &&
258
0
         (ret = sieve_stringlist_next_item(capabilities,
259
0
             &cap_item)) > 0) {
260
0
    const struct sieve_extension *ext;
261
0
    int sret;
262
263
0
    ext = sieve_extension_get_by_name(svinst, str_c(cap_item));
264
0
    if (ext == NULL) {
265
0
      sieve_runtime_trace_error(
266
0
        renv, "ihave: invalid extension name");
267
0
      return SIEVE_EXEC_BIN_CORRUPT;
268
0
    }
269
0
    sret = sieve_interpreter_extension_start(renv->interp, ext);
270
0
    if (sret == SIEVE_EXEC_FAILURE) {
271
0
      sieve_runtime_trace(
272
0
        renv, SIEVE_TRLVL_TESTS,
273
0
        "extension '%s' not available",
274
0
        sieve_extension_name(ext));
275
0
      matched = FALSE;
276
0
    } else if (sret == SIEVE_EXEC_OK) {
277
0
      sieve_runtime_trace(
278
0
        renv, SIEVE_TRLVL_TESTS,
279
0
        "extension '%s' available",
280
0
        sieve_extension_name(ext));
281
0
    } else {
282
0
      return sret;
283
0
    }
284
0
  }
285
0
  if (ret < 0) {
286
0
    sieve_runtime_trace_error(renv, "invalid capabilities item");
287
0
    return SIEVE_EXEC_BIN_CORRUPT;
288
0
  }
289
290
  /* Set test result for subsequent conditional jump */
291
0
  sieve_interpreter_set_test_result(renv->interp, matched);
292
0
  return SIEVE_EXEC_OK;
293
0
}