Coverage Report

Created: 2026-10-28 07:22

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/pigeonhole/src/lib-sieve/plugins/mime/cmd-break.c
Line
Count
Source
1
/* Copyright (c) Pigeonhole authors, see top-level COPYING file */
2
3
#include "sieve-common.h"
4
#include "sieve-code.h"
5
#include "sieve-extensions.h"
6
#include "sieve-commands.h"
7
#include "sieve-validator.h"
8
#include "sieve-generator.h"
9
#include "sieve-interpreter.h"
10
#include "sieve-binary.h"
11
#include "sieve-dump.h"
12
13
#include "ext-mime-common.h"
14
15
#include <ctype.h>
16
17
/* break
18
 *
19
 * Syntax:
20
 *   break [":name" <name: string>]
21
 *
22
 */
23
24
static bool
25
cmd_break_registered(struct sieve_validator *valdtr,
26
         const struct sieve_extension *ext,
27
         struct sieve_command_registration *cmd_reg);
28
static bool
29
cmd_break_pre_validate(struct sieve_validator *valdtr,
30
           struct sieve_command *cmd);
31
static bool
32
cmd_break_validate(struct sieve_validator *valdtr, struct sieve_command *cmd);
33
static bool
34
cmd_break_generate(const struct sieve_codegen_env *cgenv,
35
       struct sieve_command *ctx);
36
37
const struct sieve_command_def cmd_break = {
38
  .identifier = "break",
39
  .type = SCT_COMMAND,
40
  .positional_args = 0,
41
  .subtests = 0,
42
  .block_allowed = FALSE,
43
  .block_required = FALSE,
44
  .registered = cmd_break_registered,
45
  .pre_validate = cmd_break_pre_validate,
46
  .validate = cmd_break_validate,
47
  .generate = cmd_break_generate,
48
};
49
50
/*
51
 * Tagged arguments
52
 */
53
54
/* Forward declarations */
55
56
static bool
57
cmd_break_validate_name_tag(struct sieve_validator *valdtr,
58
          struct sieve_ast_argument **arg,
59
          struct sieve_command *cmd);
60
61
/* Argument objects */
62
63
static const struct sieve_argument_def break_name_tag = {
64
  .identifier = "name",
65
  .validate = cmd_break_validate_name_tag,
66
};
67
68
/*
69
 * Break operation
70
 */
71
72
static bool
73
cmd_break_operation_dump(const struct sieve_dumptime_env *denv,
74
       sieve_size_t *address);
75
static int
76
cmd_break_operation_execute(const struct sieve_runtime_env *renv,
77
          sieve_size_t *address);
78
79
const struct sieve_operation_def break_operation = {
80
  .mnemonic = "BREAK",
81
  .ext_def = &foreverypart_extension,
82
  .code = EXT_FOREVERYPART_OPERATION_BREAK,
83
  .dump = cmd_break_operation_dump,
84
  .execute = cmd_break_operation_execute,
85
};
86
87
/*
88
 * Validation data
89
 */
90
91
struct cmd_break_data {
92
  struct sieve_ast_argument *name;
93
  struct sieve_command *loop_cmd;
94
};
95
96
/*
97
 * Tag validation
98
 */
99
100
static bool
101
cmd_break_validate_name_tag(struct sieve_validator *valdtr,
102
          struct sieve_ast_argument **arg,
103
          struct sieve_command *cmd)
104
0
{
105
0
  struct cmd_break_data *data = (struct cmd_break_data *)cmd->data;
106
0
  struct sieve_ast_argument *tag = *arg;
107
108
  /* Detach the tag itself */
109
0
  *arg = sieve_ast_arguments_detach(*arg, 1);
110
111
  /* Check syntax:
112
   *   :name <string>
113
   */
114
0
  if (!sieve_validate_tag_parameter(valdtr, cmd, tag, *arg, NULL, 0,
115
0
            SAAT_STRING, TRUE))
116
0
    return FALSE;
117
0
  data->name = *arg;
118
119
  /* Skip parameter */
120
0
  *arg = sieve_ast_argument_next(*arg);
121
0
  return TRUE;
122
0
}
123
124
/*
125
 * Command registration
126
 */
127
128
static bool
129
cmd_break_registered(struct sieve_validator *valdtr,
130
         const struct sieve_extension *ext,
131
         struct sieve_command_registration *cmd_reg)
132
0
{
133
0
  sieve_validator_register_tag(valdtr, cmd_reg, ext, &break_name_tag, 0);
134
0
  return TRUE;
135
0
}
136
137
/*
138
 * Command validation
139
 */
140
141
static bool
142
cmd_break_pre_validate(struct sieve_validator *valdtr ATTR_UNUSED,
143
           struct sieve_command *cmd)
144
0
{
145
0
  struct cmd_break_data *data;
146
0
  pool_t pool = sieve_command_pool(cmd);
147
148
0
  data = p_new(pool, struct cmd_break_data, 1);
149
0
  cmd->data = data;
150
0
  return TRUE;
151
0
}
152
153
static bool
154
cmd_break_validate(struct sieve_validator *valdtr, struct sieve_command *cmd)
155
0
{
156
0
  struct cmd_break_data *data = (struct cmd_break_data *)cmd->data;
157
0
  struct sieve_ast_node *node = cmd->ast_node;
158
0
  const char *name = (data->name == NULL ?
159
0
          NULL : sieve_ast_argument_strc(data->name));
160
161
0
  i_assert(node != NULL);
162
0
  while (node != NULL && node->command != NULL) {
163
0
    if (sieve_command_is(node->command, cmd_foreverypart)) {
164
0
      struct ext_foreverypart_loop *loop =
165
0
        (struct ext_foreverypart_loop *)
166
0
          node->command->data;
167
0
      if (name == NULL ||
168
0
          (name != NULL && loop->name != NULL &&
169
0
           strcmp(name, loop->name) == 0)) {
170
0
        data->loop_cmd = node->command;
171
0
        break;
172
0
      }
173
0
    }
174
0
    node = sieve_ast_node_parent(node);
175
0
  }
176
177
0
  if (data->loop_cmd == NULL) {
178
0
    if (name == NULL) {
179
0
      sieve_command_validate_error(
180
0
        valdtr, cmd,
181
0
        "the break command is not placed inside "
182
0
        "a foreverypart loop");
183
0
    } else {
184
0
      sieve_command_validate_error(
185
0
        valdtr, cmd,
186
0
        "the break command is not placed inside "
187
0
        "a foreverypart loop named '%s'", name);
188
0
    }
189
0
    return FALSE;
190
0
  }
191
192
0
  sieve_command_exit_block_unconditionally(cmd);
193
0
  return TRUE;
194
0
}
195
196
/*
197
 * Code generation
198
 */
199
200
static bool
201
cmd_break_generate(const struct sieve_codegen_env *cgenv,
202
       struct sieve_command *cmd)
203
0
{
204
0
  struct cmd_break_data *data = (struct cmd_break_data *)cmd->data;
205
0
  struct ext_foreverypart_loop *loop;
206
207
0
  i_assert(data->loop_cmd != NULL);
208
0
  loop = (struct ext_foreverypart_loop *)data->loop_cmd->data;
209
210
0
  sieve_operation_emit(cgenv->sblock, cmd->ext, &break_operation);
211
0
  sieve_jumplist_add(loop->exit_jumps,
212
0
         sieve_binary_emit_offset(cgenv->sblock, 0));
213
0
  return TRUE;
214
0
}
215
216
/*
217
 * Code dump
218
 */
219
220
static bool
221
cmd_break_operation_dump(const struct sieve_dumptime_env *denv,
222
       sieve_size_t *address)
223
0
{
224
0
  unsigned int pc = *address;
225
0
  sieve_offset_t offset;
226
227
0
  sieve_code_dumpf(denv, "BREAK");
228
0
  sieve_code_descend(denv);
229
230
0
  if (!sieve_binary_read_offset(denv->sblock, address, &offset))
231
0
    return FALSE;
232
233
0
  sieve_code_dumpf(denv, "END: %d [%08x]", offset, pc + offset);
234
0
  return TRUE;
235
0
}
236
237
/*
238
 * Code execution
239
 */
240
241
static int
242
cmd_break_operation_execute(const struct sieve_runtime_env *renv,
243
          sieve_size_t *address)
244
0
{
245
0
  struct sieve_interpreter_loop *loop;
246
0
  unsigned int pc = *address;
247
0
  sieve_offset_t offset;
248
0
  sieve_size_t loop_end;
249
250
  /*
251
   * Read operands
252
   */
253
254
0
  if (!sieve_binary_read_offset(renv->sblock, address, &offset))
255
0
  {
256
0
    sieve_runtime_trace_error(renv, "invalid loop end offset");
257
0
    return SIEVE_EXEC_BIN_CORRUPT;
258
0
  }
259
260
0
  loop_end = pc + offset;
261
262
  /*
263
   * Perform operation
264
   */
265
266
0
  sieve_runtime_trace(renv, SIEVE_TRLVL_ACTIONS, "break command");
267
0
  sieve_runtime_trace_descend(renv);
268
269
0
  loop = sieve_interpreter_loop_get(renv->interp, loop_end,
270
0
            &foreverypart_extension);
271
0
  if (loop == NULL) {
272
0
    sieve_runtime_trace_error(renv, "no matching loop found");
273
0
    return SIEVE_EXEC_BIN_CORRUPT;
274
0
  }
275
276
0
  sieve_interpreter_loop_break(renv->interp, loop);
277
0
  return SIEVE_EXEC_OK;
278
0
}