/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 | } |