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/tst-size.c
Line
Count
Source
1
/* Copyright (c) Pigeonhole authors, see top-level COPYING file */
2
3
#include "lib.h"
4
#include "mail-storage.h"
5
6
#include "sieve-common.h"
7
#include "sieve-code.h"
8
#include "sieve-message.h"
9
#include "sieve-commands.h"
10
#include "sieve-validator.h"
11
#include "sieve-generator.h"
12
#include "sieve-interpreter.h"
13
#include "sieve-dump.h"
14
15
/*
16
 * Size test
17
 *
18
 * Syntax:
19
 *    size <":over" / ":under"> <limit: number>
20
 */
21
22
static bool
23
tst_size_registered(struct sieve_validator *valdtr,
24
        const struct sieve_extension *ext,
25
        struct sieve_command_registration *cmd_reg);
26
static bool
27
tst_size_pre_validate(struct sieve_validator *valdtr,
28
          struct sieve_command *tst);
29
static bool
30
tst_size_validate(struct sieve_validator *valdtr, struct sieve_command *tst);
31
static bool
32
tst_size_generate(const struct sieve_codegen_env *cgenv,
33
      struct sieve_command *ctx);
34
35
const struct sieve_command_def tst_size = {
36
  .identifier = "size",
37
  .type = SCT_TEST,
38
  .positional_args = 1,
39
  .subtests = 0,
40
  .block_allowed = FALSE,
41
  .block_required = FALSE,
42
  .registered = tst_size_registered,
43
  .pre_validate = tst_size_pre_validate,
44
  .validate = tst_size_validate,
45
  .generate = tst_size_generate
46
};
47
48
/*
49
 * Size operations
50
 */
51
52
static bool
53
tst_size_operation_dump(const struct sieve_dumptime_env *denv,
54
      sieve_size_t *address);
55
static int
56
tst_size_operation_execute(const struct sieve_runtime_env *renv,
57
         sieve_size_t *address);
58
59
const struct sieve_operation_def tst_size_over_operation = {
60
  .mnemonic = "SIZE-OVER",
61
  .code = SIEVE_OPERATION_SIZE_OVER,
62
  .dump = tst_size_operation_dump,
63
  .execute = tst_size_operation_execute
64
};
65
66
const struct sieve_operation_def tst_size_under_operation = {
67
  .mnemonic = "SIZE-UNDER",
68
  .code = SIEVE_OPERATION_SIZE_UNDER,
69
  .dump = tst_size_operation_dump,
70
  .execute = tst_size_operation_execute
71
};
72
73
/*
74
 * Context data
75
 */
76
77
struct tst_size_context_data {
78
  enum { SIZE_UNASSIGNED, SIZE_UNDER, SIZE_OVER } type;
79
};
80
81
#define TST_SIZE_ERROR_DUP_TAG \
82
  "exactly one of the ':under' or ':over' tags must be specified " \
83
  "for the size test, but more were found"
84
85
/*
86
 * Tag validation
87
 */
88
89
static bool
90
tst_size_validate_over_tag(struct sieve_validator *valdtr,
91
         struct sieve_ast_argument **arg,
92
         struct sieve_command *tst)
93
0
{
94
0
  struct tst_size_context_data *ctx_data =
95
0
    (struct tst_size_context_data *)tst->data;
96
97
0
  if (ctx_data->type != SIZE_UNASSIGNED) {
98
0
    sieve_argument_validate_error(valdtr, *arg,
99
0
                TST_SIZE_ERROR_DUP_TAG);
100
0
    return FALSE;
101
0
  }
102
103
0
  ctx_data->type = SIZE_OVER;
104
105
  /* Delete this tag */
106
0
  *arg = sieve_ast_arguments_detach(*arg, 1);
107
108
0
  return TRUE;
109
0
}
110
111
static bool
112
tst_size_validate_under_tag(struct sieve_validator *valdtr,
113
          struct sieve_ast_argument **arg ATTR_UNUSED,
114
          struct sieve_command *tst)
115
0
{
116
0
  struct tst_size_context_data *ctx_data =
117
0
    (struct tst_size_context_data *)tst->data;
118
119
0
  if (ctx_data->type != SIZE_UNASSIGNED) {
120
0
    sieve_argument_validate_error(valdtr, *arg,
121
0
                TST_SIZE_ERROR_DUP_TAG);
122
0
    return FALSE;
123
0
  }
124
125
0
  ctx_data->type = SIZE_UNDER;
126
127
  /* Delete this tag */
128
0
  *arg = sieve_ast_arguments_detach(*arg, 1);
129
130
0
  return TRUE;
131
0
}
132
133
/*
134
 * Test registration
135
 */
136
137
static const struct sieve_argument_def size_over_tag = {
138
  .identifier = "over",
139
  .validate = tst_size_validate_over_tag
140
};
141
142
static const struct sieve_argument_def size_under_tag = {
143
  .identifier = "under",
144
  .validate = tst_size_validate_under_tag,
145
};
146
147
static bool
148
tst_size_registered(struct sieve_validator *valdtr,
149
        const struct sieve_extension *ext ATTR_UNUSED,
150
        struct sieve_command_registration *cmd_reg)
151
0
{
152
  /* Register our tags */
153
0
  sieve_validator_register_tag(valdtr, cmd_reg, NULL, &size_over_tag, 0);
154
0
  sieve_validator_register_tag(valdtr, cmd_reg, NULL, &size_under_tag, 0);
155
156
0
  return TRUE;
157
0
}
158
159
/*
160
 * Test validation
161
 */
162
163
static bool
164
tst_size_pre_validate(struct sieve_validator *valdtr ATTR_UNUSED,
165
          struct sieve_command *tst)
166
0
{
167
0
  struct tst_size_context_data *ctx_data;
168
169
  /* Assign context */
170
0
  ctx_data = p_new(sieve_command_pool(tst),
171
0
       struct tst_size_context_data, 1);
172
0
  ctx_data->type = SIZE_UNASSIGNED;
173
0
  tst->data = ctx_data;
174
175
0
  return TRUE;
176
0
}
177
178
static bool
179
tst_size_validate(struct sieve_validator *valdtr, struct sieve_command *tst)
180
0
{
181
0
  struct tst_size_context_data *ctx_data =
182
0
    (struct tst_size_context_data *)tst->data;
183
0
  struct sieve_ast_argument *arg = tst->first_positional;
184
185
0
  if (ctx_data->type == SIZE_UNASSIGNED) {
186
0
    sieve_command_validate_error(valdtr, tst,
187
0
      "the size test requires either the :under or the :over tag "
188
0
      "to be specified");
189
0
    return FALSE;
190
0
  }
191
192
0
  if (!sieve_validate_positional_argument(valdtr, tst, arg, "limit", 1,
193
0
            SAAT_NUMBER))
194
0
    return FALSE;
195
196
0
  return sieve_validator_argument_activate(valdtr, tst, arg, FALSE);
197
0
}
198
199
/*
200
 * Code generation
201
 */
202
203
bool tst_size_generate(const struct sieve_codegen_env *cgenv,
204
           struct sieve_command *tst)
205
0
{
206
0
  struct tst_size_context_data *ctx_data =
207
0
    (struct tst_size_context_data *)tst->data;
208
209
0
  if (ctx_data->type == SIZE_OVER) {
210
0
    sieve_operation_emit(cgenv->sblock, NULL,
211
0
             &tst_size_over_operation);
212
0
  } else {
213
0
    sieve_operation_emit(cgenv->sblock, NULL,
214
0
             &tst_size_under_operation);
215
0
  }
216
217
  /* Generate arguments */
218
0
  if (!sieve_generate_arguments(cgenv, tst, NULL))
219
0
    return FALSE;
220
221
0
  return TRUE;
222
0
}
223
224
/*
225
 * Code dump
226
 */
227
228
static bool
229
tst_size_operation_dump(const struct sieve_dumptime_env *denv,
230
      sieve_size_t *address)
231
0
{
232
0
  sieve_code_dumpf(denv, "%s", sieve_operation_mnemonic(denv->oprtn));
233
0
  sieve_code_descend(denv);
234
235
0
  return sieve_opr_number_dump(denv, address, "limit");
236
0
}
237
238
/*
239
 * Code execution
240
 */
241
242
static inline bool
243
tst_size_get(const struct sieve_runtime_env *renv, sieve_number_t *size)
244
0
{
245
0
  struct mail *mail = sieve_message_get_mail(renv->msgctx);
246
0
  uoff_t psize;
247
248
0
  if (mail_get_physical_size(mail, &psize) < 0)
249
0
    return FALSE;
250
251
0
  *size = psize;
252
0
  return TRUE;
253
0
}
254
255
static int
256
tst_size_operation_execute(const struct sieve_runtime_env *renv,
257
         sieve_size_t *address)
258
0
{
259
0
  sieve_number_t mail_size, limit;
260
0
  int ret;
261
262
  /*
263
   * Read operands
264
   */
265
266
  /* Read size limit */
267
0
  if ((ret = sieve_opr_number_read(renv, address, "limit", &limit)) <= 0)
268
0
    return ret;
269
270
  /*
271
   * Perform test
272
   */
273
274
  /* Get the size of the message */
275
0
  if (!tst_size_get(renv, &mail_size)) {
276
    /* FIXME: improve this error */
277
0
    e_error(renv->event, "failed to assess message size");
278
0
    return SIEVE_EXEC_FAILURE;
279
0
  }
280
281
  /* Perform the test */
282
0
  if (sieve_operation_is(renv->oprtn, tst_size_over_operation)) {
283
0
    sieve_runtime_trace(renv, SIEVE_TRLVL_TESTS, "size :over test");
284
285
0
    if (sieve_runtime_trace_active(renv, SIEVE_TRLVL_MATCHING)) {
286
0
      sieve_runtime_trace_descend(renv);
287
288
0
      sieve_runtime_trace(
289
0
        renv, 0, "comparing message size %llu",
290
0
        (unsigned long long)mail_size);
291
0
      sieve_runtime_trace(
292
0
        renv, 0, "with upper limit %llu",
293
0
        (unsigned long long)limit);
294
0
    }
295
296
0
    sieve_interpreter_set_test_result(renv->interp,
297
0
              (mail_size > limit));
298
0
  } else {
299
0
    sieve_runtime_trace(renv, SIEVE_TRLVL_TESTS,
300
0
            "size :under test");
301
302
0
    if (sieve_runtime_trace_active(renv, SIEVE_TRLVL_MATCHING)) {
303
0
      sieve_runtime_trace_descend(renv);
304
305
0
      sieve_runtime_trace(
306
0
        renv, 0, "comparing message size %llu",
307
0
        (unsigned long long)mail_size);
308
0
      sieve_runtime_trace(
309
0
        renv, 0, "with lower limit %llu",
310
0
        (unsigned long long)limit);
311
0
    }
312
313
0
    sieve_interpreter_set_test_result(renv->interp,
314
0
              (mail_size < limit));
315
0
  }
316
0
  return SIEVE_EXEC_OK;
317
0
}