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