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/ext-encoded-character.c
Line
Count
Source
1
/* Copyright (c) Pigeonhole authors, see top-level COPYING file */
2
3
/* Extension encoded-character
4
 * ---------------------------
5
 *
6
 * Authors: Stephan Bosch
7
 * Specification: RFC 5228
8
 * Implementation: full
9
 * Status: testing
10
 *
11
 */
12
13
#include "lib.h"
14
#include "unichar.h"
15
16
#include "sieve-extensions.h"
17
#include "sieve-commands.h"
18
#include "sieve-validator.h"
19
20
#include <ctype.h>
21
22
/*
23
 * Extension
24
 */
25
26
static bool
27
ext_encoded_character_validator_load(const struct sieve_extension *ext,
28
             struct sieve_validator *valdtr);
29
30
const struct sieve_extension_def encoded_character_extension = {
31
  .name = "encoded-character",
32
  .validator_load = ext_encoded_character_validator_load,
33
};
34
35
/*
36
 * Encoded string argument
37
 */
38
39
bool arg_encoded_string_validate(struct sieve_validator *valdtr,
40
         struct sieve_ast_argument **arg,
41
         struct sieve_command *context);
42
43
const struct sieve_argument_def encoded_string_argument = {
44
  .identifier = "@encoded-string",
45
  .validate = arg_encoded_string_validate
46
};
47
48
/* Parsing */
49
50
static bool _skip_whitespace(const char **in, const char *inend)
51
0
{
52
0
  while (*in < inend) {
53
0
    if (**in == '\r') {
54
0
      (*in)++;
55
0
      if (**in != '\n')
56
0
        return FALSE;
57
0
      continue;
58
0
    }
59
60
    /* (Loose LF is non-standard) */
61
0
    if (**in != ' ' && **in != '\n' && **in != '\t')
62
0
      break;
63
64
0
    (*in)++;
65
0
  }
66
67
0
  return TRUE;
68
0
}
69
70
static bool
71
_parse_hexint(const char **in, const char *inend, int max_digits,
72
        unsigned int *result)
73
0
{
74
0
  const char *p = *in;
75
0
  int digit = 0;
76
77
  /* Determine the width of the hex run ourselves (bounded by inend and
78
     max_digits) rather than relying on str_parse_uint_hex()'s endp_r,
79
     which is documented undefined on error and, for a hex run long
80
     enough to overflow even its internal 64-bit accumulator, is never
81
     written at all. */
82
0
  while (p < inend && (max_digits == 0 || digit < max_digits) &&
83
0
         i_isxdigit(*p)) {
84
0
    p++;
85
0
    digit++;
86
0
  }
87
88
0
  if (digit == 0)
89
0
    return FALSE;
90
91
0
  if (digit == max_digits && p < inend && i_isxdigit(*p)) {
92
    /* Hex digit _MUST_ end here */
93
0
    return FALSE;
94
0
  }
95
96
  /* An overflowing hex value can never be a valid Unicode code point;
97
     force it out of range rather than silently wrapping. */
98
0
  if (str_parse_uint_hex(*in, result, NULL) < 0)
99
0
    *result = UINT_MAX;
100
101
0
  *in = p;
102
0
  return TRUE;
103
0
}
104
105
static bool _decode_hex(const char **in, const char *inend, string_t *result)
106
0
{
107
0
  int values = 0;
108
109
0
  while (*in < inend) {
110
0
    unsigned int hexpair;
111
112
0
    if (!_skip_whitespace(in, inend))
113
0
      return FALSE;
114
0
    if (!_parse_hexint(in, inend, 2, &hexpair))
115
0
      break;
116
117
0
    str_append_c(result, (unsigned char)hexpair);
118
0
    values++;
119
0
  }
120
121
0
  return (values > 0);
122
0
}
123
124
static bool
125
_decode_unicode(const char **in, const char *inend, string_t *result,
126
    unsigned int *error_hex)
127
0
{
128
0
  int values = 0;
129
0
  bool valid = TRUE;
130
131
0
  while (*in < inend) {
132
0
    unsigned int unicode_hex;
133
134
0
    if (!_skip_whitespace(in, inend))
135
0
      return FALSE;
136
0
    if (!_parse_hexint(in, inend, 0, &unicode_hex))
137
0
      break;
138
139
0
    if (uni_is_valid_ucs4((unichar_t) unicode_hex))
140
0
      uni_ucs4_to_utf8_c((unichar_t)unicode_hex, result);
141
0
    else {
142
0
      if (valid)
143
0
        *error_hex = unicode_hex;
144
0
      valid = FALSE;
145
0
    }
146
0
    values++;
147
0
  }
148
149
0
  return (values > 0);
150
0
}
151
152
bool arg_encoded_string_validate(struct sieve_validator *valdtr,
153
         struct sieve_ast_argument **arg,
154
         struct sieve_command *cmd)
155
0
{
156
0
  bool result = TRUE;
157
0
  enum { ST_NONE, ST_OPEN, ST_TYPE, ST_CLOSE } state;
158
0
  string_t *str = sieve_ast_argument_str(*arg);
159
0
  string_t *tmpstr, *newstr = NULL;
160
0
  const char *p, *mark, *strstart, *substart = NULL;
161
0
  const char *strval = str_c(str);
162
0
  const char *strend = strval + str_len(str);
163
0
  unsigned int error_hex = 0;
164
165
0
  state = ST_NONE;
166
0
  T_BEGIN {
167
0
    tmpstr = t_str_new(32);
168
169
0
    p = strval;
170
0
    strstart = p;
171
0
    while (result && p < strend) {
172
0
      switch (state) {
173
      /* Normal string */
174
0
      case ST_NONE:
175
0
        if (*p == '$') {
176
0
          substart = p;
177
0
          state = ST_OPEN;
178
0
        }
179
0
        p++;
180
0
        break;
181
      /* Parsed '$' */
182
0
      case ST_OPEN:
183
0
        if (*p == '{') {
184
0
          state = ST_TYPE;
185
0
          p++;
186
0
        } else
187
0
          state = ST_NONE;
188
0
        break;
189
      /* Parsed '${' */
190
0
      case ST_TYPE:
191
0
        mark = p;
192
        /* Scan for 'hex' or 'unicode' */
193
0
        while (p < strend && i_isalpha(*p))
194
0
          p++;
195
196
0
        if (*p != ':') {
197
0
          state = ST_NONE;
198
0
          break;
199
0
        }
200
201
0
        state = ST_CLOSE;
202
203
0
        str_truncate(tmpstr, 0);
204
0
        if (strncasecmp(mark, "hex", p - mark) == 0) {
205
          /* Hexadecimal */
206
0
          p++;
207
0
          if (!_decode_hex(&p, strend, tmpstr))
208
0
            state = ST_NONE;
209
0
        } else if (strncasecmp(mark, "unicode", p - mark) == 0) {
210
          /* Unicode */
211
0
          p++;
212
0
          if (!_decode_unicode(&p, strend, tmpstr, &error_hex))
213
0
            state = ST_NONE;
214
0
        } else {
215
          /* Invalid encoding */
216
0
          p++;
217
0
          state = ST_NONE;
218
0
        }
219
0
        break;
220
0
      case ST_CLOSE:
221
0
        if (*p == '}') {
222
          /* We now know that the substitution is valid */
223
224
0
          if (error_hex != 0) {
225
0
            sieve_argument_validate_error(
226
0
              valdtr, *arg,
227
0
              "invalid unicode character 0x%08x in encoded character substitution",
228
0
              error_hex);
229
0
            result = FALSE;
230
0
            break;
231
0
          }
232
233
0
          if (newstr == NULL) {
234
0
            newstr = str_new(sieve_ast_pool((*arg)->ast),
235
0
                 str_len(str) * 2);
236
0
          }
237
238
0
          str_append_data(newstr, strstart,
239
0
              substart-strstart);
240
0
          str_append_str(newstr, tmpstr);
241
242
0
          strstart = p + 1;
243
0
          substart = strstart;
244
245
0
          p++;
246
0
        }
247
0
        state = ST_NONE;
248
0
      }
249
0
    }
250
0
  } T_END;
251
252
0
  if (!result)
253
0
    return FALSE;
254
255
0
  if (newstr != NULL) {
256
0
    if (strstart != strend)
257
0
      str_append_data(newstr, strstart, strend - strstart);
258
259
0
    sieve_ast_argument_string_set(*arg, newstr);
260
0
  }
261
262
  /* Pass the processed string to a (possible) next layer of processing */
263
0
  return sieve_validator_argument_activate_super(valdtr, cmd, *arg, TRUE);
264
0
}
265
266
/*
267
 * Extension implementation
268
 */
269
270
static bool
271
ext_encoded_character_validator_load(const struct sieve_extension *ext,
272
             struct sieve_validator *valdtr)
273
0
{
274
  /* Override the constant string argument with our own */
275
0
  sieve_validator_argument_override(valdtr, SAT_CONST_STRING, ext,
276
0
            &encoded_string_argument);
277
0
  return TRUE;
278
0
}