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/variables/ext-variables-modifiers.c
Line
Count
Source
1
/* Copyright (c) Pigeonhole authors, see top-level COPYING file */
2
3
#include "lib.h"
4
#include "unichar.h"
5
#include "str-sanitize.h"
6
7
#include "sieve-common.h"
8
#include "sieve-commands.h"
9
#include "sieve-code.h"
10
#include "sieve-binary.h"
11
#include "sieve-validator.h"
12
#include "sieve-generator.h"
13
#include "sieve-runtime.h"
14
15
#include "ext-variables-common.h"
16
#include "ext-variables-limits.h"
17
#include "ext-variables-modifiers.h"
18
19
#include <ctype.h>
20
21
/*
22
 * Core modifiers
23
 */
24
25
extern const struct sieve_variables_modifier_def lower_modifier;
26
extern const struct sieve_variables_modifier_def upper_modifier;
27
extern const struct sieve_variables_modifier_def lowerfirst_modifier;
28
extern const struct sieve_variables_modifier_def upperfirst_modifier;
29
extern const struct sieve_variables_modifier_def quotewildcard_modifier;
30
extern const struct sieve_variables_modifier_def length_modifier;
31
32
enum ext_variables_modifier_code {
33
    EXT_VARIABLES_MODIFIER_LOWER,
34
    EXT_VARIABLES_MODIFIER_UPPER,
35
    EXT_VARIABLES_MODIFIER_LOWERFIRST,
36
    EXT_VARIABLES_MODIFIER_UPPERFIRST,
37
    EXT_VARIABLES_MODIFIER_QUOTEWILDCARD,
38
    EXT_VARIABLES_MODIFIER_LENGTH,
39
    EXT_VARIABLES_MODIFIER_COUNT
40
};
41
42
const struct sieve_variables_modifier_def *ext_variables_core_modifiers[] = {
43
  &lower_modifier,
44
  &upper_modifier,
45
  &lowerfirst_modifier,
46
  &upperfirst_modifier,
47
  &quotewildcard_modifier,
48
  &length_modifier,
49
};
50
static_assert_array_size(ext_variables_core_modifiers,
51
       EXT_VARIABLES_MODIFIER_COUNT);
52
53
const unsigned int ext_variables_core_modifiers_count =
54
    N_ELEMENTS(ext_variables_core_modifiers);
55
56
#define ext_variables_modifier_name(modf) \
57
  (modf)->object->def->name
58
#define ext_variables_modifiers_equal(modf1, modf2) \
59
  ((modf1)->def == (modf2)->def)
60
#define ext_variables_modifiers_equal_precedence(modf1, modf2) \
61
  ((modf1)->def->precedence == (modf2)->def->precendence)
62
63
/*
64
 * Modifier registry
65
 */
66
67
void sieve_variables_modifier_register(
68
  const struct sieve_extension *var_ext, struct sieve_validator *valdtr,
69
  const struct sieve_extension *ext,
70
  const struct sieve_variables_modifier_def *smodf_def)
71
0
{
72
0
  struct ext_variables_validator_context *ctx =
73
0
    ext_variables_validator_context_get(var_ext, valdtr);
74
75
0
  sieve_validator_object_registry_add(ctx->modifiers, ext,
76
0
              &smodf_def->obj_def);
77
0
}
78
79
bool ext_variables_modifier_exists(const struct sieve_extension *var_ext,
80
           struct sieve_validator *valdtr,
81
           const char *identifier)
82
0
{
83
0
  struct ext_variables_validator_context *ctx =
84
0
    ext_variables_validator_context_get(var_ext, valdtr);
85
86
0
  return sieve_validator_object_registry_find(ctx->modifiers,
87
0
                identifier, NULL);
88
0
}
89
90
const struct sieve_variables_modifier *
91
ext_variables_modifier_create_instance(const struct sieve_extension *var_ext,
92
               struct sieve_validator *valdtr,
93
               struct sieve_command *cmd,
94
               const char *identifier)
95
0
{
96
0
  struct ext_variables_validator_context *ctx =
97
0
    ext_variables_validator_context_get(var_ext, valdtr);
98
0
  struct sieve_object object;
99
0
  struct sieve_variables_modifier *modf;
100
0
  pool_t pool;
101
102
0
  if (!sieve_validator_object_registry_find(ctx->modifiers, identifier,
103
0
              &object))
104
0
    return NULL;
105
106
0
  pool = sieve_command_pool(cmd);
107
0
  modf = p_new(pool, struct sieve_variables_modifier, 1);
108
0
  modf->object = object;
109
0
  modf->var_ext = var_ext;
110
0
  modf->def = (const struct sieve_variables_modifier_def *) object.def;
111
112
0
  return modf;
113
0
}
114
115
void ext_variables_register_core_modifiers(
116
  const struct sieve_extension *ext,
117
  struct ext_variables_validator_context *ctx)
118
0
{
119
0
  unsigned int i;
120
121
  /* Register core modifiers*/
122
0
  for (i = 0; i < ext_variables_core_modifiers_count; i++) {
123
0
    sieve_validator_object_registry_add(
124
0
      ctx->modifiers, ext,
125
0
      &(ext_variables_core_modifiers[i]->obj_def));
126
0
  }
127
0
}
128
129
/*
130
 * Core modifiers
131
 */
132
133
/* Forward declarations */
134
135
static bool
136
mod_lower_modify(const struct sieve_variables_modifier *modf,
137
     string_t *in, string_t **result);
138
static bool
139
mod_upper_modify(const struct sieve_variables_modifier *modf,
140
     string_t *in, string_t **result);
141
static bool
142
mod_lowerfirst_modify(const struct sieve_variables_modifier *modf,
143
          string_t *in, string_t **result);
144
static bool
145
mod_upperfirst_modify(const struct sieve_variables_modifier *modf,
146
          string_t *in, string_t **result);
147
static bool
148
mod_length_modify(const struct sieve_variables_modifier *modf,
149
      string_t *in, string_t **result);
150
static bool
151
mod_quotewildcard_modify(const struct sieve_variables_modifier *modf,
152
       string_t *in, string_t **result);
153
154
/* Modifier objects */
155
156
const struct sieve_variables_modifier_def lower_modifier = {
157
  SIEVE_OBJECT("lower", &modifier_operand, EXT_VARIABLES_MODIFIER_LOWER),
158
  40,
159
  mod_lower_modify,
160
};
161
162
const struct sieve_variables_modifier_def upper_modifier = {
163
  SIEVE_OBJECT("upper", &modifier_operand, EXT_VARIABLES_MODIFIER_UPPER),
164
  40,
165
  mod_upper_modify,
166
};
167
168
const struct sieve_variables_modifier_def lowerfirst_modifier = {
169
  SIEVE_OBJECT("lowerfirst", &modifier_operand,
170
         EXT_VARIABLES_MODIFIER_LOWERFIRST),
171
  30,
172
  mod_lowerfirst_modify,
173
};
174
175
const struct sieve_variables_modifier_def upperfirst_modifier = {
176
  SIEVE_OBJECT("upperfirst", &modifier_operand,
177
         EXT_VARIABLES_MODIFIER_UPPERFIRST),
178
  30,
179
  mod_upperfirst_modify,
180
};
181
182
const struct sieve_variables_modifier_def quotewildcard_modifier = {
183
  SIEVE_OBJECT("quotewildcard", &modifier_operand,
184
         EXT_VARIABLES_MODIFIER_QUOTEWILDCARD),
185
  20,
186
  mod_quotewildcard_modify,
187
};
188
189
const struct sieve_variables_modifier_def length_modifier = {
190
  SIEVE_OBJECT("length", &modifier_operand,
191
         EXT_VARIABLES_MODIFIER_LENGTH),
192
  10,
193
  mod_length_modify,
194
};
195
196
/* Modifier implementations */
197
198
static bool
199
mod_upperfirst_modify(const struct sieve_variables_modifier *modf ATTR_UNUSED,
200
          string_t *in, string_t **result)
201
0
{
202
0
  char *content;
203
204
0
  if (str_len(in) == 0) {
205
0
    *result = in;
206
0
    return TRUE;
207
0
  }
208
209
0
  *result = t_str_new(str_len(in));
210
0
  str_append_str(*result, in);
211
212
0
  content = str_c_modifiable(*result);
213
0
  content[0] = i_toupper(content[0]);
214
0
  return TRUE;
215
0
}
216
217
static bool
218
mod_lowerfirst_modify(const struct sieve_variables_modifier *modf ATTR_UNUSED,
219
          string_t *in, string_t **result)
220
0
{
221
0
  char *content;
222
223
0
  if (str_len(in) == 0) {
224
0
    *result = in;
225
0
    return TRUE;
226
0
  }
227
228
0
  *result = t_str_new(str_len(in));
229
0
  str_append_str(*result, in);
230
231
0
  content = str_c_modifiable(*result);
232
0
  content[0] = i_tolower(content[0]);
233
0
  return TRUE;
234
0
}
235
236
static bool
237
mod_upper_modify(const struct sieve_variables_modifier *modf ATTR_UNUSED,
238
     string_t *in, string_t **result)
239
0
{
240
0
  char *content;
241
242
0
  if (str_len(in) == 0) {
243
0
    *result = in;
244
0
    return TRUE;
245
0
  }
246
247
0
  *result = t_str_new(str_len(in));
248
0
  str_append_str(*result, in);
249
250
0
  content = str_c_modifiable(*result);
251
0
  (void)str_ucase(content);
252
0
  return TRUE;
253
0
}
254
255
static bool
256
mod_lower_modify(const struct sieve_variables_modifier *modf ATTR_UNUSED,
257
     string_t *in, string_t **result)
258
0
{
259
0
  char *content;
260
261
0
  if (str_len(in) == 0) {
262
0
    *result = in;
263
0
    return TRUE;
264
0
  }
265
266
0
  *result = t_str_new(str_len(in));
267
0
  str_append_str(*result, in);
268
269
0
  content = str_c_modifiable(*result);
270
0
  (void)str_lcase(content);
271
0
  return TRUE;
272
0
}
273
274
static bool
275
mod_length_modify(const struct sieve_variables_modifier *modf ATTR_UNUSED,
276
      string_t *in, string_t **result)
277
0
{
278
0
  *result = t_str_new(64);
279
0
  str_printfa(*result, "%llu", (unsigned long long)
280
0
        uni_utf8_strlen_n(str_data(in), str_len(in)));
281
0
  return TRUE;
282
0
}
283
284
static bool
285
mod_quotewildcard_modify(const struct sieve_variables_modifier *modf,
286
       string_t *in, string_t **result)
287
0
{
288
0
  size_t max_val_size =
289
0
    sieve_variables_get_max_value_size(modf->var_ext);
290
0
  const unsigned char *p, *poff, *pend;
291
0
  size_t new_size;
292
293
0
  if (str_len(in) == 0) {
294
    /* Empty string */
295
0
    *result = in;
296
0
    return TRUE;
297
0
  }
298
299
  /* Allocate new string */
300
0
  new_size = str_len(in) + 16;
301
0
  if (new_size > max_val_size)
302
0
    new_size = max_val_size;
303
0
  *result = t_str_new(new_size + 1);
304
305
  /* Escape string */
306
0
  p = str_data(in);
307
0
  pend = p + str_len(in);
308
0
  poff = p;
309
0
  while (p < pend) {
310
0
    unsigned int n = uni_utf8_char_bytes((char)*p);
311
312
0
    if (n == 1 && (*p == '*' || *p == '?' || *p == '\\')) {
313
0
      str_append_data(*result, poff, p - poff);
314
0
      poff = p;
315
316
0
      if (str_len(*result) + 2 > max_val_size)
317
0
        break;
318
319
0
      str_append_c(*result, '\\');
320
0
    } else if ((str_len(*result) + (p - poff) + n) > max_val_size) {
321
0
      break;
322
0
    }
323
0
    if (p + n > pend) {
324
0
      p = pend;
325
0
      break;
326
0
    }
327
0
    p += n;
328
0
  }
329
330
0
  str_append_data(*result, poff, p - poff);
331
332
0
  return TRUE;
333
0
}
334
335
/*
336
 * Modifier argument
337
 */
338
339
/* [MODIFIER]:
340
 *   ":lower" / ":upper" / ":lowerfirst" / ":upperfirst" /
341
 *             ":quotewildcard" / ":length"
342
 */
343
344
/* Forward declarations */
345
346
static bool
347
tag_modifier_is_instance_of(struct sieve_validator *valdtr,
348
          struct sieve_command *cmd,
349
          const struct sieve_extension *ext,
350
          const char *identifier, void **context);
351
352
/* Modifier tag object */
353
354
static const struct sieve_argument_def modifier_tag = {
355
  .identifier = "MODIFIER",
356
  .flags = SIEVE_ARGUMENT_FLAG_MULTIPLE,
357
  .is_instance_of = tag_modifier_is_instance_of,
358
};
359
360
/* Modifier tag implementation */
361
362
static bool
363
tag_modifier_is_instance_of(struct sieve_validator *valdtr,
364
          struct sieve_command *cmd,
365
          const struct sieve_extension *ext,
366
          const char *identifier, void **data)
367
0
{
368
0
  const struct sieve_variables_modifier *modf;
369
370
0
  if (data == NULL)
371
0
    return ext_variables_modifier_exists(ext, valdtr, identifier);
372
373
0
  modf = ext_variables_modifier_create_instance(ext, valdtr, cmd,
374
0
                  identifier);
375
0
  if (modf == NULL)
376
0
    return FALSE;
377
378
0
  *data = (void *)modf;
379
0
  return TRUE;
380
0
}
381
382
/* Registration */
383
384
void sieve_variables_modifiers_link_tag(
385
  struct sieve_validator *valdtr, const struct sieve_extension *var_ext,
386
  struct sieve_command_registration *cmd_reg)
387
0
{
388
0
  sieve_validator_register_tag(valdtr, cmd_reg, var_ext,
389
0
             &modifier_tag, 0);
390
0
}
391
392
/* Validation */
393
394
bool sieve_variables_modifiers_validate(
395
  struct sieve_validator *valdtr, struct sieve_command *cmd,
396
  ARRAY_TYPE(sieve_variables_modifier) *modifiers)
397
0
{
398
0
  struct sieve_ast_argument *arg;
399
400
0
  arg = sieve_command_first_argument(cmd);
401
0
  while (arg != NULL && arg != cmd->first_positional) {
402
0
    const struct sieve_variables_modifier *modfs;
403
0
    const struct sieve_variables_modifier *modf;
404
0
    unsigned int i, modf_count;
405
0
    bool inserted;
406
407
0
    if (!sieve_argument_is(arg, modifier_tag)) {
408
0
      arg = sieve_ast_argument_next(arg);
409
0
      continue;
410
0
    }
411
0
    modf = (const struct sieve_variables_modifier *)
412
0
      arg->argument->data;
413
414
0
    inserted = FALSE;
415
0
    modfs = array_get(modifiers, &modf_count);
416
0
    for (i = 0; i < modf_count && !inserted; i++) {
417
418
0
      if (modfs[i].def->precedence == modf->def->precedence) {
419
0
        sieve_argument_validate_error(
420
0
          valdtr, arg,
421
0
          "modifiers :%s and :%s specified for the set command conflict "
422
0
          "having equal precedence",
423
0
          modfs[i].def->obj_def.identifier,
424
0
          modf->def->obj_def.identifier);
425
0
        return FALSE;
426
0
      }
427
0
      if (modfs[i].def->precedence < modf->def->precedence) {
428
0
        array_insert(modifiers, i, modf, 1);
429
0
        inserted = TRUE;
430
0
      }
431
0
    }
432
433
0
    if (!inserted)
434
0
      array_append(modifiers, modf, 1);
435
436
    /* Added to modifier list; self-destruct to prevent implicit
437
       code generation.
438
     */
439
0
    arg = sieve_ast_arguments_detach(arg, 1);
440
0
  }
441
0
  return TRUE;
442
0
}
443
444
bool sieve_variables_modifiers_generate(
445
  const struct sieve_codegen_env *cgenv,
446
  ARRAY_TYPE(sieve_variables_modifier) *modifiers)
447
0
{
448
0
  struct sieve_binary_block *sblock = cgenv->sblock;
449
0
  const struct sieve_variables_modifier *modfs;
450
0
  unsigned int i, modf_count;
451
452
0
  sieve_binary_emit_byte(sblock, array_count(modifiers));
453
454
0
  modfs = array_get(modifiers, &modf_count);
455
0
  for (i = 0; i < modf_count; i++) {
456
0
    ext_variables_opr_modifier_emit(
457
0
      sblock, modfs[i].object.ext, modfs[i].def);
458
0
  }
459
0
  return TRUE;
460
0
}
461
462
/*
463
 * Modifier coding
464
 */
465
466
const struct sieve_operand_class sieve_variables_modifier_operand_class =
467
  { "modifier" };
468
469
static const struct sieve_extension_objects core_modifiers =
470
  SIEVE_VARIABLES_DEFINE_MODIFIERS(ext_variables_core_modifiers);
471
472
const struct sieve_operand_def modifier_operand = {
473
  .name = "modifier",
474
  .ext_def = &variables_extension,
475
  .code = EXT_VARIABLES_OPERAND_MODIFIER,
476
  .class = &sieve_variables_modifier_operand_class,
477
  .interface = &core_modifiers,
478
};
479
480
bool sieve_variables_modifiers_code_dump(const struct sieve_dumptime_env *denv,
481
           sieve_size_t *address)
482
0
{
483
0
  unsigned int mdfs, i;
484
485
  /* Read the number of applied modifiers we need to read */
486
0
  if (!sieve_binary_read_byte(denv->sblock, address, &mdfs))
487
0
    return FALSE;
488
489
  /* Print all modifiers (sorted during code generation already) */
490
0
  for (i = 0; i < mdfs; i++) {
491
0
    if (!ext_variables_opr_modifier_dump(denv, address))
492
0
      return FALSE;
493
0
  }
494
0
  return TRUE;
495
0
}
496
497
int sieve_variables_modifiers_code_read(
498
  const struct sieve_runtime_env *renv,
499
  const struct sieve_extension *var_ext,
500
  sieve_size_t *address, ARRAY_TYPE(sieve_variables_modifier) *modifiers)
501
0
{
502
0
  unsigned int lprec, mdfs, i;
503
504
0
  if (!sieve_binary_read_byte(renv->sblock, address, &mdfs)) {
505
0
    sieve_runtime_trace_error(renv, "invalid modifier count");
506
0
    return SIEVE_EXEC_BIN_CORRUPT;
507
0
  }
508
509
0
  t_array_init(modifiers, mdfs);
510
511
0
  lprec = (unsigned int)-1;
512
0
  for (i = 0; i < mdfs; i++) {
513
0
    struct sieve_variables_modifier modf;
514
515
0
    if (!ext_variables_opr_modifier_read(renv, var_ext,
516
0
                 address, &modf))
517
0
      return SIEVE_EXEC_BIN_CORRUPT;
518
0
    if (modf.def != NULL) {
519
0
      if (modf.def->precedence >= lprec) {
520
0
        sieve_runtime_trace_error(
521
0
          renv, "unsorted modifier precedence");
522
0
        return SIEVE_EXEC_BIN_CORRUPT;
523
0
      }
524
0
      lprec = modf.def->precedence;
525
0
    }
526
0
    array_append(modifiers, &modf, 1);
527
0
  }
528
529
0
  return SIEVE_EXEC_OK;
530
0
}
531
532
/*
533
 * Modifier application
534
 */
535
536
int sieve_variables_modifiers_apply(
537
  const struct sieve_runtime_env *renv,
538
  const struct sieve_extension *var_ext,
539
  ARRAY_TYPE(sieve_variables_modifier) *modifiers, string_t **value)
540
0
{
541
0
  const struct ext_variables_context *extctx =
542
0
    ext_variables_get_context(var_ext);
543
0
  const struct sieve_variables_modifier *modfs;
544
0
  unsigned int i, modf_count;
545
546
  /* Hold value within limits */
547
0
  if (str_len(*value) > extctx->set->max_value_size) {
548
    /* assume variable originates from code, so copy it first */
549
0
    string_t *new_value =
550
0
      t_str_new(extctx->set->max_value_size+3);
551
0
    str_append_str(new_value, *value);
552
0
    *value = new_value;
553
0
    str_truncate_utf8(*value, extctx->set->max_value_size);
554
0
  }
555
556
0
  if (!array_is_created(modifiers))
557
0
    return SIEVE_EXEC_OK;
558
559
0
  modfs = array_get(modifiers, &modf_count);
560
0
  if (modf_count == 0)
561
0
    return SIEVE_EXEC_OK;
562
563
0
  for (i = 0; i < modf_count; i++) {
564
0
    string_t *new_value;
565
0
    const struct sieve_variables_modifier *modf = &modfs[i];
566
567
0
    if (modf->def != NULL && modf->def->modify != NULL) {
568
0
      if (!modf->def->modify(modf, *value, &new_value))
569
0
        return SIEVE_EXEC_FAILURE;
570
571
0
      *value = new_value;
572
0
      if (*value == NULL)
573
0
        return SIEVE_EXEC_FAILURE;
574
575
0
      sieve_runtime_trace_here(
576
0
        renv, SIEVE_TRLVL_COMMANDS,
577
0
        "modify :%s \"%s\" => \"%s\"",
578
0
        sieve_variables_modifier_name(modf),
579
0
        str_sanitize(str_c(*value), 256),
580
0
        str_sanitize(str_c(new_value), 256));
581
582
      /* Hold value within limits */
583
0
      if (str_len(*value) > extctx->set->max_value_size) {
584
0
        str_truncate_utf8(
585
0
          *value, extctx->set->max_value_size);
586
0
      }
587
0
    }
588
0
  }
589
0
  return SIEVE_EXEC_OK;
590
0
}