/src/dovecot/src/lib-mail/rfc2231-parser.c
Line | Count | Source |
1 | | /* Copyright (c) Dovecot authors, see top-level COPYING file */ |
2 | | |
3 | | #include "lib.h" |
4 | | #include "array.h" |
5 | | #include "str.h" |
6 | | #include "rfc822-parser.h" |
7 | | #include "strfuncs.h" |
8 | | #include "strnum.h" |
9 | | #include "rfc2231-parser.h" |
10 | | #include "charset-utf8.h" |
11 | | |
12 | | #include <limits.h> |
13 | | |
14 | | /* https://www.rfc-editor.org/rfc/rfc2231 |
15 | | https://www.rfc-editor.org/rfc/rfc822 |
16 | | |
17 | | RFC 2231 parameters use key*<n>[*]=value pairs. This means the following |
18 | | examples are valid and will produce the same pair key=value0value1value2 (or |
19 | | key*=value0value1value2): |
20 | | |
21 | | - key*0=value0; key*1=value1; key*2=value2 |
22 | | - key*2=value2; key*1=value1; key*0=value0 |
23 | | - key*2*=value2; key*1=value1; key*0*=value0 |
24 | | - key*=value0value1value2 |
25 | | |
26 | | In addition, non-extended and non-continued key=value parameters need to |
27 | | continue being supported. |
28 | | |
29 | | The RFC2231 document calls these indexed parameters "continued |
30 | | parameters". We call them indexed here. |
31 | | |
32 | | This implementation tries to avoid some pitfalls by putting pairs in two |
33 | | groups: 1) the results list and 2) a list of indexed pairs. The former will |
34 | | initially contain unextended pairs and extended but unindexed pairs. The |
35 | | latter will contain only extended indexed pairs. After merging the extended |
36 | | indexed pairs, they will be inserted into the results list if a pair with the |
37 | | same key does not already exist in there. This results in some input pairs |
38 | | being rejected if the key is a duplicate of one already in the results |
39 | | list. In short, this implementation will prefer unindexed parameters over |
40 | | indexed ones. Example: |
41 | | |
42 | | - key*0=value0; key*=value; key*1=value1 -> key=value |
43 | | - key=value; key*=value -> key=value |
44 | | - key=value; key*0=value0; key*1=value1 -> key=value |
45 | | |
46 | | The RFC does not mention much regarding invalid inputs or how to handle |
47 | | them. So this implementation does a best-effort "garbage-in garbage-out" sort |
48 | | of thing. */ |
49 | | |
50 | | static inline int decode_hex_digit(char digit) |
51 | 7.18k | { |
52 | 7.18k | if (digit >= '0' && digit <= '9') |
53 | 638 | return digit - '0'; |
54 | 6.54k | else if (digit >= 'a' && digit <= 'f') |
55 | 669 | return digit - 'a' + 0x0a; |
56 | 5.87k | else if (digit >= 'A' && digit <= 'F') |
57 | 2.52k | return digit - 'A' + 0x0A; |
58 | 3.35k | return -1; |
59 | 7.18k | } |
60 | | |
61 | | static inline bool decode_hex_byte(const char *digits, char *result_r) |
62 | 4.27k | { |
63 | 4.27k | int higher = decode_hex_digit(digits[0]); |
64 | 4.27k | if (higher < 0) |
65 | 1.36k | return FALSE; |
66 | | |
67 | 2.90k | int lower = decode_hex_digit(digits[1]); |
68 | 2.90k | if (lower < 0) |
69 | 1.98k | return FALSE; |
70 | | |
71 | 920 | *result_r = (char)((higher << 4) + lower); |
72 | 920 | return TRUE; |
73 | 2.90k | } |
74 | | |
75 | | static string_t *rfc2231_decode_value(const char *value) |
76 | 19.6k | { |
77 | 19.6k | string_t *str = t_str_new(64); |
78 | 19.6k | const char *plast = value; |
79 | 19.6k | const char *p; |
80 | 20.5k | while ((p = strchr(plast, '%')) != NULL) { |
81 | | /* Append whatever we've seen so far. */ |
82 | 4.27k | str_append_data(str, plast, (p - plast)); |
83 | 4.27k | char ch; |
84 | 4.27k | if (!decode_hex_byte(p+1, &ch)) |
85 | 3.35k | return NULL; |
86 | 920 | plast = p + 3; |
87 | 920 | str_append_data(str, &ch, 1); |
88 | 920 | } |
89 | | /* Append whatever remains. */ |
90 | 16.2k | str_append(str, plast); |
91 | 16.2k | return str; |
92 | 19.6k | } |
93 | | |
94 | | struct rfc2231_parameter { |
95 | | const char *key, *value; |
96 | | unsigned int idx; |
97 | | bool extended; |
98 | | }; |
99 | | |
100 | | static int rfc2231_parameter_cmp(const struct rfc2231_parameter *r1, |
101 | | const struct rfc2231_parameter *r2) |
102 | 51.8k | { |
103 | 51.8k | int ret = strcmp(r1->key, r2->key); |
104 | 51.8k | if (ret != 0) |
105 | 15.9k | return ret; |
106 | 35.8k | if (r1->idx < r2->idx) |
107 | 3.71k | return -1; |
108 | 32.1k | else if (r1->idx > r2->idx) |
109 | 14.8k | return 1; |
110 | 17.2k | return 0; |
111 | 35.8k | } |
112 | | |
113 | | static bool result_contains(const ARRAY_TYPE(const_string) *result, |
114 | | const char *key) |
115 | 245k | { |
116 | 245k | unsigned int count; |
117 | 245k | const char *const *p = array_get(result, &count); |
118 | 245k | i_assert((count % 2) == 0); |
119 | 2.23M | for (unsigned int i = 0; i < count; i += 2) { |
120 | 2.08M | if (strcasecmp(key, p[i]) == 0) |
121 | 95.2k | return TRUE; |
122 | 2.08M | } |
123 | 150k | return FALSE; |
124 | 245k | } |
125 | | |
126 | | static void result_append(ARRAY_TYPE(const_string) *result, |
127 | | const char *key, const char *value) |
128 | 245k | { |
129 | 245k | if (!result_contains(result, key)) { |
130 | 150k | array_push_back(result, &key); |
131 | 150k | array_push_back(result, &value); |
132 | 150k | } |
133 | 245k | } |
134 | | |
135 | | static const char *reconstruct_rfc2231_key(const struct rfc2231_parameter *param, |
136 | | const bool with_extended) |
137 | 13.6k | { |
138 | 13.6k | return t_strdup_printf( |
139 | 13.6k | with_extended && param->extended ? "%s*%i*" : "%s*%i", |
140 | 13.6k | param->key, |
141 | 13.6k | param->idx); |
142 | 13.6k | } |
143 | | |
144 | | static const char *find_charset(const char *value, const char **end_r) |
145 | 175k | { |
146 | | /* Note that it is perfectly permissible to leave either the character |
147 | | set or language field blank. Note also that the single quote |
148 | | delimiters MUST be present even when one of the field values is |
149 | | omitted. This is done when either character set, language, or both |
150 | | are not relevant to the parameter value at hand. */ |
151 | 175k | const char *end = strchr(value, '\''); |
152 | 175k | if (end == NULL) |
153 | 154k | return NULL; |
154 | 21.1k | const char *const charset_r = t_strdup_until(value, end); |
155 | | |
156 | | /* We don't do anything with the language info but the format has to be |
157 | | valid, otherwise even the character set - which we have already |
158 | | parsed - is invalid. */ |
159 | 21.1k | end = strchr(end + 1, '\''); |
160 | 21.1k | if (end == NULL) |
161 | 1.59k | return NULL; |
162 | | |
163 | 19.5k | *end_r = end + 1; |
164 | 19.5k | return charset_r; |
165 | 21.1k | } |
166 | | |
167 | | static const char *value_to_utf8(const char *charset, |
168 | | const char *value) |
169 | 19.6k | { |
170 | 19.6k | string_t *utf8_r = t_str_new(128); |
171 | 19.6k | enum charset_result result; |
172 | 19.6k | if (charset_to_utf8_str(charset, NULL, value, utf8_r, &result) < 0 || |
173 | 13.2k | result != CHARSET_RET_OK) |
174 | 14.4k | return NULL; |
175 | 5.19k | return str_c(utf8_r); |
176 | 19.6k | } |
177 | | |
178 | | static const char *rfc2231_decode_charset_value(const char *value, |
179 | | const char **charset_r) |
180 | 176k | { |
181 | 176k | if (*charset_r == NULL) |
182 | 175k | *charset_r = find_charset(value, &value); |
183 | | |
184 | 176k | if (*charset_r != NULL && strcmp(*charset_r, "") != 0) { |
185 | 19.6k | string_t *tmp = rfc2231_decode_value(value); |
186 | 19.6k | const char *decoded = tmp == NULL ? value : str_c(tmp); |
187 | | |
188 | | /* Charset is valid and not empty. */ |
189 | 19.6k | const char *const utf8 = value_to_utf8(*charset_r, decoded); |
190 | 19.6k | if (utf8 != NULL) |
191 | 5.19k | value = utf8; |
192 | 19.6k | } |
193 | 176k | return value; |
194 | 176k | } |
195 | | |
196 | | int rfc2231_parse(struct rfc822_parser_context *ctx, |
197 | | const char *const **result_r) |
198 | 35.8k | { |
199 | 35.8k | ARRAY_TYPE(const_string) result; |
200 | 35.8k | ARRAY(struct rfc2231_parameter) rfc2231_params_arr; |
201 | 35.8k | struct rfc2231_parameter rfc2231_param; |
202 | 35.8k | const char *key, *p, *p2; |
203 | 35.8k | string_t *str; |
204 | 35.8k | unsigned int i, j, count, next, next_idx, params_count = 0; |
205 | 35.8k | bool ok, broken = FALSE; |
206 | 35.8k | const char *prev_replacement_str; |
207 | 35.8k | int ret; |
208 | | |
209 | | /* Temporarily replace the nul_replacement_char while we're parsing |
210 | | the content-params. It'll be restored before we return. */ |
211 | 35.8k | prev_replacement_str = ctx->nul_replacement_str; |
212 | 35.8k | ctx->nul_replacement_str = RFC822_NUL_REPLACEMENT_STR; |
213 | | |
214 | | /* Get a list of all parameters. RFC 2231 uses key*<n>[*]=value pairs, |
215 | | which we want to merge to a key[*]=value pair. Save them to a |
216 | | separate array. */ |
217 | 35.8k | i_zero(&rfc2231_param); |
218 | 35.8k | t_array_init(&result, 8); |
219 | 35.8k | t_array_init(&rfc2231_params_arr, 8); |
220 | 35.8k | str = t_str_new(64); |
221 | 631k | while ((ret = rfc822_parse_content_param(ctx, &key, str)) != 0) { |
222 | 605k | if (++params_count > RFC2231_MAX_PARAMS) |
223 | 1.69k | break; |
224 | 603k | if (ret < 0) { |
225 | | /* try to continue anyway.. */ |
226 | 356k | broken = TRUE; |
227 | 356k | if (ctx->data >= ctx->end) |
228 | 7.77k | break; |
229 | 348k | ctx->data++; |
230 | 348k | continue; |
231 | 356k | } |
232 | 247k | p = strchr(key, '*'); |
233 | 247k | if (p != NULL) { |
234 | 200k | p2 = p; |
235 | 200k | bool is_indexed = str_parse_uint( |
236 | 200k | p + 1, &rfc2231_param.idx, &p) != -1; |
237 | | |
238 | 200k | if (*p == '*') { |
239 | 181k | rfc2231_param.extended = TRUE; |
240 | 181k | p++; |
241 | 181k | } else { |
242 | 18.5k | rfc2231_param.extended = FALSE; |
243 | 18.5k | } |
244 | 200k | if (*p == '\0') { |
245 | 194k | const char *tmp_key = t_strdup_until(key, p2); |
246 | 194k | const char *tmp_val = t_strdup(str_c(str)); |
247 | | |
248 | 194k | if (is_indexed) { |
249 | 22.3k | rfc2231_param.key = tmp_key; |
250 | 22.3k | rfc2231_param.value = tmp_val; |
251 | 22.3k | array_push_back(&rfc2231_params_arr, |
252 | 22.3k | &rfc2231_param); |
253 | 171k | } else { |
254 | 171k | if (rfc2231_param.extended) { |
255 | 170k | const char *charset = NULL; |
256 | 170k | tmp_val = rfc2231_decode_charset_value( |
257 | 170k | tmp_val, &charset); |
258 | 170k | } |
259 | 171k | result_append(&result, tmp_key, tmp_val); |
260 | 171k | } |
261 | 194k | } else { |
262 | 6.25k | p = NULL; |
263 | 6.25k | } |
264 | 200k | } |
265 | 247k | if (p == NULL) |
266 | 53.2k | result_append(&result, key, t_strdup(str_c(str))); |
267 | 247k | } |
268 | 35.8k | ctx->nul_replacement_str = prev_replacement_str; |
269 | | |
270 | 35.8k | if (array_count(&rfc2231_params_arr) == 0) { |
271 | | /* No RFC 2231 parameters */ |
272 | 30.1k | array_append_zero(&result); /* NULL-terminate */ |
273 | 30.1k | *result_r = array_front(&result); |
274 | 30.1k | return broken ? -1 : 0; |
275 | 30.1k | } |
276 | | |
277 | | /* Sort keys primarily by their name and secondarily by their index. */ |
278 | 5.76k | array_sort(&rfc2231_params_arr, rfc2231_parameter_cmp); |
279 | 5.76k | const struct rfc2231_parameter *rfc2231_params = |
280 | 5.76k | array_get(&rfc2231_params_arr, &count); |
281 | | |
282 | | /* Merge the RFC 2231 parameters. If any indexes are missing, fallback |
283 | | to assuming these aren't RFC 2231 encoded parameters. */ |
284 | 16.9k | for (i = 0; i < count; i = next) { |
285 | 11.1k | ok = TRUE; |
286 | 11.1k | next_idx = 0; |
287 | 33.4k | for (j = i; j < count; j++) { |
288 | 27.7k | if (strcasecmp(rfc2231_params[i].key, |
289 | 27.7k | rfc2231_params[j].key) != 0) |
290 | 5.38k | break; |
291 | 22.3k | if (rfc2231_params[j].idx != next_idx) { |
292 | | /* missing indexes */ |
293 | 12.6k | ok = FALSE; |
294 | 12.6k | } |
295 | 22.3k | next_idx++; |
296 | 22.3k | } |
297 | 11.1k | next = j; |
298 | | |
299 | 11.1k | if (!ok) { |
300 | | /* Some indexes are missing so we can assume the keys |
301 | | are unindexed but might be extended. */ |
302 | 17.9k | for (j = i; j < next; j++) { |
303 | 13.6k | const char *val = rfc2231_params[j].value; |
304 | 13.6k | bool with_extended = TRUE; |
305 | 13.6k | if (rfc2231_params[j].extended) { |
306 | | /* Since these values are now assumed to |
307 | | be unrelated - due to their indexing |
308 | | being invalid, we don't really care |
309 | | about the charset being set, or being |
310 | | available only in the first |
311 | | segment. We treat them as unrelated |
312 | | and independent parameters. */ |
313 | 2.13k | const char *charset = NULL; |
314 | 2.13k | val = rfc2231_decode_charset_value(val, &charset); |
315 | 2.13k | with_extended = FALSE; |
316 | 2.13k | } |
317 | 13.6k | result_append( |
318 | 13.6k | &result, |
319 | 13.6k | reconstruct_rfc2231_key( |
320 | 13.6k | &rfc2231_params[j], |
321 | 13.6k | with_extended), |
322 | 13.6k | val); |
323 | 13.6k | } |
324 | 4.27k | continue; |
325 | 4.27k | } |
326 | | |
327 | | /* we have valid indexing */ |
328 | 6.87k | str_truncate(str, 0); |
329 | | |
330 | 6.87k | const char *charset = NULL; |
331 | 15.5k | for (j = i; j < next; j++) { |
332 | 8.69k | const char *val = rfc2231_params[j].value; |
333 | | /* We expect rfc2231_params[j]'s value to contain the |
334 | | charset and language info: |
335 | | |
336 | | (4) The first segment of a continuation MUST be |
337 | | encoded if language and character set information are |
338 | | given. |
339 | | |
340 | | (5) If the first segment of a continued parameter |
341 | | value is encoded the language and character set field |
342 | | delimiters MUST be present even when the fields are |
343 | | left blank. */ |
344 | 8.69k | if (rfc2231_params[j].extended) |
345 | 3.48k | val = rfc2231_decode_charset_value(val, &charset); |
346 | 8.69k | str_append(str, val); |
347 | 8.69k | } |
348 | 6.87k | key = rfc2231_params[i].key; |
349 | 6.87k | result_append(&result, key, t_strdup(str_c(str))); |
350 | 6.87k | } |
351 | 5.76k | array_append_zero(&result); /* NULL-terminate */ |
352 | 5.76k | *result_r = array_front(&result); |
353 | 5.76k | return broken ? -1 : 0; |
354 | 35.8k | } |