/src/dovecot/src/lib-imap/imap-bodystructure.c
Line | Count | Source |
1 | | /* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */ |
2 | | |
3 | | #include "lib.h" |
4 | | #include "buffer.h" |
5 | | #include "istream.h" |
6 | | #include "str.h" |
7 | | #include "message-part-data.h" |
8 | | #include "message-parser.h" |
9 | | #include "rfc822-parser.h" |
10 | | #include "rfc2231-parser.h" |
11 | | #include "imap-parser.h" |
12 | | #include "imap-quote.h" |
13 | | #include "imap-envelope.h" |
14 | | #include "imap-bodystructure.h" |
15 | | |
16 | | /* The max level of lists nesting inside the parenthesised representation of a |
17 | | single part with no other parts inside, i.e. the max level of list nesting |
18 | | in representing a single part. According to RFC-3501, this should be in the |
19 | | order of a couple of nestings only, let's keep some margin just in case. */ |
20 | | #define BODYSTRUCTURE_MAX_PARENTHESIS_NESTING \ |
21 | 30.9k | (MESSAGE_PARSER_DEFAULT_MAX_NESTED_MIME_PARTS + 10) |
22 | | |
23 | 0 | #define EMPTY_BODY "(\"text\" \"plain\" " \ |
24 | 0 | "(\"charset\" \""MESSAGE_PART_DEFAULT_CHARSET"\") NIL NIL \"7bit\" 0 0)" |
25 | 0 | #define EMPTY_BODYSTRUCTURE "(\"text\" \"plain\" " \ |
26 | 0 | "(\"charset\" \""MESSAGE_PART_DEFAULT_CHARSET"\") NIL NIL \"7bit\" 0 0 " \ |
27 | 0 | "NIL NIL NIL NIL)" |
28 | | |
29 | | /* |
30 | | * IMAP BODY/BODYSTRUCTURE write |
31 | | */ |
32 | | |
33 | | static void |
34 | | params_write(const struct message_part_param *params, |
35 | | unsigned int params_count, string_t *str, |
36 | | bool default_charset) |
37 | 13.4k | { |
38 | 13.4k | unsigned int i; |
39 | 13.4k | bool seen_charset; |
40 | | |
41 | 13.4k | if (!default_charset && params_count == 0) { |
42 | 11.2k | str_append(str, "NIL"); |
43 | 11.2k | return; |
44 | 11.2k | } |
45 | 2.22k | str_append_c(str, '('); |
46 | | |
47 | 2.22k | seen_charset = FALSE; |
48 | 603k | for (i = 0; i < params_count; i++) { |
49 | 601k | i_assert(params[i].name != NULL); |
50 | 601k | i_assert(params[i].value != NULL); |
51 | | |
52 | 601k | if (i > 0) |
53 | 599k | str_append_c(str, ' '); |
54 | 601k | if (default_charset && |
55 | 1.24k | strcasecmp(params[i].name, "charset") == 0) |
56 | 387 | seen_charset = TRUE; |
57 | 601k | imap_append_string(str, params[i].name); |
58 | 601k | str_append_c(str, ' '); |
59 | 601k | imap_append_string(str, params[i].value); |
60 | 601k | } |
61 | 2.22k | if (default_charset && !seen_charset) { |
62 | 777 | if (i > 0) |
63 | 433 | str_append_c(str, ' '); |
64 | 777 | str_append(str, "\"charset\" " |
65 | 777 | "\""MESSAGE_PART_DEFAULT_CHARSET"\""); |
66 | 777 | } |
67 | 2.22k | str_append_c(str, ')'); |
68 | 2.22k | } |
69 | | |
70 | | static int |
71 | | part_write_bodystructure_siblings(const struct message_part *part, |
72 | | string_t *dest, bool extended, |
73 | | const char **error_r) |
74 | 6.62k | { |
75 | 17.7k | for (; part != NULL; part = part->next) { |
76 | 11.1k | str_append_c(dest, '('); |
77 | 11.1k | if (imap_bodystructure_write(part, dest, extended, error_r) < 0) |
78 | 0 | return -1; |
79 | 11.1k | str_append_c(dest, ')'); |
80 | 11.1k | } |
81 | 6.62k | return 0; |
82 | 6.62k | } |
83 | | |
84 | | static void |
85 | | part_write_bodystructure_common(const struct message_part_data *data, |
86 | | string_t *str) |
87 | 13.0k | { |
88 | 13.0k | str_append_c(str, ' '); |
89 | 13.0k | if (data->content_disposition == NULL) |
90 | 12.6k | str_append(str, "NIL"); |
91 | 394 | else { |
92 | 394 | str_append_c(str, '('); |
93 | 394 | imap_append_string(str, data->content_disposition); |
94 | | |
95 | 394 | str_append_c(str, ' '); |
96 | 394 | params_write(data->content_disposition_params, |
97 | 394 | data->content_disposition_params_count, str, FALSE); |
98 | | |
99 | 394 | str_append_c(str, ')'); |
100 | 394 | } |
101 | | |
102 | 13.0k | str_append_c(str, ' '); |
103 | 13.0k | if (data->content_language == NULL) |
104 | 12.3k | str_append(str, "NIL"); |
105 | 708 | else { |
106 | 708 | const char *const *lang = data->content_language; |
107 | | |
108 | 708 | i_assert(*lang != NULL); |
109 | 708 | str_append_c(str, '('); |
110 | 708 | imap_append_string(str, *lang); |
111 | 708 | lang++; |
112 | 1.60M | while (*lang != NULL) { |
113 | 1.60M | str_append_c(str, ' '); |
114 | 1.60M | imap_append_string(str, *lang); |
115 | 1.60M | lang++; |
116 | 1.60M | } |
117 | 708 | str_append_c(str, ')'); |
118 | 708 | } |
119 | | |
120 | 13.0k | str_append_c(str, ' '); |
121 | 13.0k | imap_append_nstring_nolf(str, data->content_location); |
122 | 13.0k | } |
123 | | |
124 | | static int part_write_body_multipart(const struct message_part *part, |
125 | | string_t *str, bool extended, |
126 | | const char **error_r) |
127 | 5.11k | { |
128 | 5.11k | const struct message_part_data *data = part->data; |
129 | | |
130 | 5.11k | i_assert(part->data != NULL); |
131 | | |
132 | 5.11k | if (part->children != NULL) { |
133 | 5.11k | if (part_write_bodystructure_siblings(part->children, str, |
134 | 5.11k | extended, error_r) < 0) |
135 | 0 | return -1; |
136 | 5.11k | } else { |
137 | | /* no parts in multipart message, |
138 | | that's not allowed. write a single |
139 | | 0-length text/plain structure */ |
140 | 0 | if (!extended) |
141 | 0 | str_append(str, EMPTY_BODY); |
142 | 0 | else |
143 | 0 | str_append(str, EMPTY_BODYSTRUCTURE); |
144 | 0 | } |
145 | | |
146 | 5.11k | str_append_c(str, ' '); |
147 | 5.11k | imap_append_string(str, data->content_subtype); |
148 | | |
149 | 5.11k | if (!extended) |
150 | 0 | return 0; |
151 | | |
152 | | /* BODYSTRUCTURE data */ |
153 | | |
154 | 5.11k | str_append_c(str, ' '); |
155 | 5.11k | params_write(data->content_type_params, |
156 | 5.11k | data->content_type_params_count, str, FALSE); |
157 | | |
158 | 5.11k | part_write_bodystructure_common(data, str); |
159 | 5.11k | return 0; |
160 | 5.11k | } |
161 | | |
162 | | static bool part_is_truncated(const struct message_part *part) |
163 | 6.45k | { |
164 | 6.45k | const struct message_part_data *data = part->data; |
165 | | |
166 | 6.45k | i_assert((part->flags & MESSAGE_PART_FLAG_MESSAGE_RFC822) == 0); |
167 | 6.45k | i_assert((part->flags & MESSAGE_PART_FLAG_MULTIPART) == 0); |
168 | | |
169 | 6.45k | if (data->content_type != NULL) { |
170 | 6.45k | if (strcasecmp(data->content_type, "message") == 0 && |
171 | 522 | strcasecmp(data->content_subtype, "rfc822") == 0) { |
172 | | /* It's message/rfc822, but without |
173 | | MESSAGE_PART_FLAG_MESSAGE_RFC822. */ |
174 | 0 | return TRUE; |
175 | 0 | } |
176 | 6.45k | if (strcasecmp(data->content_type, "multipart") == 0) { |
177 | | /* It's multipart/, but without |
178 | | MESSAGE_PART_FLAG_MULTIPART. */ |
179 | 703 | return TRUE; |
180 | 703 | } |
181 | 6.45k | } else { |
182 | | /* No Content-Type */ |
183 | 0 | if (part->parent != NULL && |
184 | 0 | (part->parent->flags & MESSAGE_PART_FLAG_MULTIPART_DIGEST) != 0) { |
185 | | /* Parent is MESSAGE_PART_FLAG_MULTIPART_DIGEST |
186 | | (so this should have been message/rfc822). */ |
187 | 0 | return TRUE; |
188 | 0 | } |
189 | 0 | } |
190 | 5.75k | return FALSE; |
191 | 6.45k | } |
192 | | |
193 | | static int part_write_body(const struct message_part *part, |
194 | | string_t *str, bool extended, const char **error_r) |
195 | 7.97k | { |
196 | 7.97k | const struct message_part_data *data = part->data; |
197 | 7.97k | bool text; |
198 | | |
199 | 7.97k | i_assert(part->data != NULL); |
200 | | |
201 | 7.97k | if ((part->flags & MESSAGE_PART_FLAG_MESSAGE_RFC822) != 0) { |
202 | 1.51k | str_append(str, "\"message\" \"rfc822\""); |
203 | 1.51k | text = FALSE; |
204 | 6.45k | } else if (part_is_truncated(part)) { |
205 | | /* Maximum MIME part count was reached while parsing the mail. |
206 | | Write this part out as application/octet-stream instead. |
207 | | We're not using text/plain, because it would require |
208 | | message-parser to use MESSAGE_PART_FLAG_TEXT for this part |
209 | | to avoid losing line count in message_part serialization. */ |
210 | 703 | str_append(str, "\"application\" \"octet-stream\""); |
211 | 703 | text = FALSE; |
212 | 5.75k | } else { |
213 | | /* "content type" "subtype" */ |
214 | 5.75k | if (data->content_type == NULL) { |
215 | 0 | text = TRUE; |
216 | 0 | str_append(str, "\"text\" \"plain\""); |
217 | 5.75k | } else { |
218 | 5.75k | text = (strcasecmp(data->content_type, "text") == 0); |
219 | 5.75k | imap_append_string(str, data->content_type); |
220 | 5.75k | str_append_c(str, ' '); |
221 | 5.75k | imap_append_string(str, data->content_subtype); |
222 | 5.75k | } |
223 | 5.75k | bool part_is_text = (part->flags & MESSAGE_PART_FLAG_TEXT) != 0; |
224 | 5.75k | if (text != part_is_text) { |
225 | 0 | *error_r = "text flag mismatch"; |
226 | 0 | return -1; |
227 | 0 | } |
228 | 5.75k | } |
229 | | |
230 | | /* ("content type param key" "value" ...) */ |
231 | 7.97k | str_append_c(str, ' '); |
232 | 7.97k | params_write(data->content_type_params, |
233 | 7.97k | data->content_type_params_count, str, text); |
234 | | |
235 | 7.97k | str_append_c(str, ' '); |
236 | 7.97k | imap_append_nstring_nolf(str, data->content_id); |
237 | 7.97k | str_append_c(str, ' '); |
238 | 7.97k | imap_append_nstring_nolf(str, data->content_description); |
239 | 7.97k | str_append_c(str, ' '); |
240 | 7.97k | if (data->content_transfer_encoding != NULL) |
241 | 7.97k | imap_append_string(str, data->content_transfer_encoding); |
242 | 0 | else |
243 | 0 | str_append(str, "\"7bit\""); |
244 | 7.97k | str_printfa(str, " %"PRIuUOFF_T, part->body_size.virtual_size); |
245 | | |
246 | 7.97k | if (text) { |
247 | | /* text/.. contains line count */ |
248 | 978 | str_printfa(str, " %u", part->body_size.lines); |
249 | 6.99k | } else if ((part->flags & MESSAGE_PART_FLAG_MESSAGE_RFC822) != 0) { |
250 | | /* message/rfc822 contains envelope + body + line count */ |
251 | 1.51k | const struct message_part_data *child_data; |
252 | | |
253 | 1.51k | i_assert(part->children != NULL); |
254 | 1.51k | i_assert(part->children->next == NULL); |
255 | | |
256 | 1.51k | child_data = part->children->data; |
257 | | |
258 | 1.51k | str_append(str, " ("); |
259 | 1.51k | imap_envelope_write(child_data->envelope, str); |
260 | 1.51k | str_append(str, ") "); |
261 | | |
262 | 1.51k | if (part_write_bodystructure_siblings(part->children, str, |
263 | 1.51k | extended, error_r) < 0) |
264 | 0 | return -1; |
265 | 1.51k | str_printfa(str, " %u", part->body_size.lines); |
266 | 1.51k | } |
267 | | |
268 | 7.97k | if (!extended) |
269 | 0 | return 0; |
270 | | |
271 | | /* BODYSTRUCTURE data */ |
272 | | |
273 | | /* "md5" ("content disposition" ("disposition" "params")) |
274 | | ("body" "language" "params") "location" */ |
275 | 7.97k | str_append_c(str, ' '); |
276 | 7.97k | imap_append_nstring_nolf(str, data->content_md5); |
277 | 7.97k | part_write_bodystructure_common(data, str); |
278 | 7.97k | return 0; |
279 | 7.97k | } |
280 | | |
281 | | int imap_bodystructure_write(const struct message_part *part, |
282 | | string_t *dest, bool extended, |
283 | | const char **error_r) |
284 | 13.0k | { |
285 | 13.0k | if ((part->flags & MESSAGE_PART_FLAG_MULTIPART) != 0) |
286 | 5.11k | return part_write_body_multipart(part, dest, extended, error_r); |
287 | 7.97k | else |
288 | 7.97k | return part_write_body(part, dest, extended, error_r); |
289 | 13.0k | } |
290 | | |
291 | | /* |
292 | | * IMAP BODYSTRUCTURE parsing |
293 | | */ |
294 | | |
295 | | static int |
296 | | imap_bodystructure_strlist_parse(const struct imap_arg *arg, |
297 | | pool_t pool, const char *const **list_r) |
298 | 13.8k | { |
299 | 13.8k | const char *item, **list; |
300 | 13.8k | const struct imap_arg *list_args; |
301 | 13.8k | unsigned int list_count, i; |
302 | | |
303 | 13.8k | if (arg->type == IMAP_ARG_NIL) { |
304 | 12.3k | *list_r = NULL; |
305 | 12.3k | return 0; |
306 | 12.3k | } |
307 | 1.44k | if (imap_arg_get_nstring(arg, &item)) { |
308 | 349 | list = p_new(pool, const char *, 2); |
309 | 349 | list[0] = p_strdup(pool, item); |
310 | 1.09k | } else { |
311 | 1.09k | if (!imap_arg_get_list_full(arg, &list_args, &list_count)) |
312 | 3 | return -1; |
313 | 1.09k | if (list_count == 0) |
314 | 1 | return -1; |
315 | | |
316 | 1.09k | list = p_new(pool, const char *, list_count+1); |
317 | 3.20M | for (i = 0; i < list_count; i++) { |
318 | 3.20M | if (!imap_arg_get_string(&list_args[i], &item)) |
319 | 22 | return -1; |
320 | 3.20M | list[i] = p_strdup(pool, item); |
321 | 3.20M | } |
322 | 1.09k | } |
323 | 1.41k | *list_r = list; |
324 | 1.41k | return 0; |
325 | 1.44k | } |
326 | | |
327 | | static int |
328 | | imap_bodystructure_params_parse(const struct imap_arg *arg, |
329 | | pool_t pool, const struct message_part_param **params_r, |
330 | | unsigned int *count_r) |
331 | 24.8k | { |
332 | 24.8k | struct message_part_param *params; |
333 | 24.8k | const struct imap_arg *list_args; |
334 | 24.8k | unsigned int list_count, params_count, i; |
335 | | |
336 | 24.8k | if (arg->type == IMAP_ARG_NIL) { |
337 | 20.3k | *params_r = NULL; |
338 | 20.3k | return 0; |
339 | 20.3k | } |
340 | 4.54k | if (!imap_arg_get_list_full(arg, &list_args, &list_count)) |
341 | 349 | return -1; |
342 | 4.19k | if ((list_count % 2) != 0) |
343 | 2 | return -1; |
344 | 4.19k | if (list_count == 0) |
345 | 10 | return -1; |
346 | | |
347 | 4.18k | params_count = list_count/2; |
348 | 4.18k | params = p_new(pool, struct message_part_param, params_count+1); |
349 | 1.20M | for (i = 0; i < params_count; i++) { |
350 | 1.20M | const char *name, *value; |
351 | | |
352 | 1.20M | if (!imap_arg_get_string(&list_args[i*2+0], &name)) |
353 | 3 | return -1; |
354 | 1.20M | if (!imap_arg_get_string(&list_args[i*2+1], &value)) |
355 | 8 | return -1; |
356 | 1.20M | params[i].name = p_strdup(pool, name); |
357 | 1.20M | params[i].value = p_strdup(pool, value); |
358 | 1.20M | } |
359 | 4.16k | *params_r = params; |
360 | 4.16k | *count_r = params_count; |
361 | 4.16k | return 0; |
362 | 4.18k | } |
363 | | |
364 | | static int |
365 | | imap_bodystructure_parse_args_common(struct message_part *part, |
366 | | pool_t pool, const struct imap_arg *args, |
367 | | const char **error_r) |
368 | 15.4k | { |
369 | 15.4k | struct message_part_data *data = part->data; |
370 | 15.4k | const struct imap_arg *list_args; |
371 | | |
372 | 15.4k | if (args->type == IMAP_ARG_EOL) |
373 | 1.07k | return 0; |
374 | 14.4k | if (args->type == IMAP_ARG_NIL) |
375 | 13.5k | args++; |
376 | 809 | else if (!imap_arg_get_list(args, &list_args)) { |
377 | 2 | *error_r = "Invalid content-disposition list"; |
378 | 2 | return -1; |
379 | 807 | } else { |
380 | 807 | if (!imap_arg_get_string |
381 | 807 | (list_args++, &data->content_disposition)) { |
382 | 2 | *error_r = "Invalid content-disposition"; |
383 | 2 | return -1; |
384 | 2 | } |
385 | 805 | data->content_disposition = p_strdup(pool, data->content_disposition); |
386 | 805 | if (imap_bodystructure_params_parse(list_args, pool, |
387 | 805 | &data->content_disposition_params, |
388 | 805 | &data->content_disposition_params_count) < 0) { |
389 | 8 | *error_r = "Invalid content-disposition params"; |
390 | 8 | return -1; |
391 | 8 | } |
392 | 797 | args++; |
393 | 797 | } |
394 | 14.3k | if (args->type == IMAP_ARG_EOL) |
395 | 574 | return 0; |
396 | 13.8k | if (imap_bodystructure_strlist_parse |
397 | 13.8k | (args++, pool, &data->content_language) < 0) { |
398 | 26 | *error_r = "Invalid content-language"; |
399 | 26 | return -1; |
400 | 26 | } |
401 | 13.7k | if (args->type == IMAP_ARG_EOL) |
402 | 538 | return 0; |
403 | 13.2k | if (!imap_arg_get_nstring |
404 | 13.2k | (args++, &data->content_location)) { |
405 | 2 | *error_r = "Invalid content-location"; |
406 | 2 | return -1; |
407 | 2 | } |
408 | 13.2k | data->content_location = p_strdup(pool, data->content_location); |
409 | 13.2k | return 0; |
410 | 13.2k | } |
411 | | |
412 | | static int |
413 | | imap_bodystructure_parse_args_int( |
414 | | unsigned int nesting, const struct imap_arg *args, pool_t pool, |
415 | | struct message_part **_part, const char **error_r) |
416 | 30.9k | { |
417 | 30.9k | struct message_part *part = *_part, *child_part;; |
418 | 30.9k | struct message_part **child_part_p; |
419 | 30.9k | struct message_part_data *data; |
420 | 30.9k | const struct imap_arg *list_args; |
421 | 30.9k | const char *value, *content_type, *subtype, *error; |
422 | 30.9k | bool multipart, text, message_rfc822, parsing_tree, has_lines; |
423 | 30.9k | unsigned int lines; |
424 | 30.9k | uoff_t vsize; |
425 | | |
426 | 30.9k | ++nesting; |
427 | 30.9k | if (nesting > BODYSTRUCTURE_MAX_PARENTHESIS_NESTING) { |
428 | 2 | *error_r = "Parts hierarchy nested too deep"; |
429 | 2 | return -1; |
430 | 2 | } |
431 | 30.9k | if (part != NULL) { |
432 | | /* parsing with preexisting message_part tree */ |
433 | 0 | parsing_tree = FALSE; |
434 | 30.9k | } else { |
435 | | /* parsing message_part tree from BODYSTRUCTURE as well */ |
436 | 30.9k | part = *_part = p_new(pool, struct message_part, 1); |
437 | 30.9k | parsing_tree = TRUE; |
438 | 30.9k | } |
439 | 30.9k | part->data = data = p_new(pool, struct message_part_data, 1); |
440 | | |
441 | 30.9k | multipart = FALSE; |
442 | 30.9k | if (!parsing_tree) { |
443 | 0 | if ((part->flags & MESSAGE_PART_FLAG_MULTIPART) != 0 && |
444 | 0 | part->children == NULL) { |
445 | 0 | struct message_part_data dummy_part_data = { |
446 | 0 | .content_type = "text", |
447 | 0 | .content_subtype = "plain", |
448 | 0 | .content_transfer_encoding = "7bit" |
449 | 0 | }; |
450 | 0 | struct message_part dummy_part = { |
451 | 0 | .parent = part, |
452 | 0 | .data = &dummy_part_data, |
453 | 0 | .flags = MESSAGE_PART_FLAG_TEXT |
454 | 0 | }; |
455 | 0 | struct message_part *dummy_partp = &dummy_part; |
456 | | |
457 | | /* no parts in multipart message, |
458 | | that's not allowed. expect a single |
459 | | 0-length text/plain structure */ |
460 | 0 | if (args->type != IMAP_ARG_LIST || |
461 | 0 | (args+1)->type == IMAP_ARG_LIST) { |
462 | 0 | *error_r = "message_part hierarchy " |
463 | 0 | "doesn't match BODYSTRUCTURE"; |
464 | 0 | return -1; |
465 | 0 | } |
466 | | |
467 | 0 | list_args = imap_arg_as_list(args); |
468 | 0 | if (imap_bodystructure_parse_args_int( |
469 | 0 | nesting, list_args, pool, &dummy_partp, error_r) < 0) |
470 | 0 | return -1; |
471 | 0 | child_part = NULL; |
472 | |
|
473 | 0 | multipart = TRUE; |
474 | 0 | args++; |
475 | |
|
476 | 0 | } else { |
477 | 0 | child_part = part->children; |
478 | 0 | while (args->type == IMAP_ARG_LIST) { |
479 | 0 | if ((part->flags & MESSAGE_PART_FLAG_MULTIPART) == 0 || |
480 | 0 | child_part == NULL) { |
481 | 0 | *error_r = "message_part hierarchy " |
482 | 0 | "doesn't match BODYSTRUCTURE"; |
483 | 0 | return -1; |
484 | 0 | } |
485 | | |
486 | 0 | list_args = imap_arg_as_list(args); |
487 | 0 | if (imap_bodystructure_parse_args_int( |
488 | 0 | nesting, list_args, pool, &child_part, error_r) < 0) |
489 | 0 | return -1; |
490 | 0 | child_part = child_part->next; |
491 | |
|
492 | 0 | multipart = TRUE; |
493 | 0 | args++; |
494 | 0 | } |
495 | 0 | } |
496 | 0 | if (multipart) { |
497 | 0 | if (child_part != NULL) { |
498 | 0 | *error_r = "message_part hierarchy " |
499 | 0 | "doesn't match BODYSTRUCTURE"; |
500 | 0 | return -1; |
501 | 0 | } |
502 | 0 | } else if ((part->flags & MESSAGE_PART_FLAG_MULTIPART) != 0) { |
503 | 0 | *error_r = "message_part multipart flag " |
504 | 0 | "doesn't match BODYSTRUCTURE"; |
505 | 0 | return -1; |
506 | 0 | } |
507 | 30.9k | } else { |
508 | 30.9k | child_part_p = &part->children; |
509 | 51.2k | while (args->type == IMAP_ARG_LIST) { |
510 | 22.6k | list_args = imap_arg_as_list(args); |
511 | 22.6k | if (imap_bodystructure_parse_args_int( |
512 | 22.6k | nesting, list_args, pool, child_part_p, error_r) < 0) |
513 | 2.32k | return -1; |
514 | 20.2k | (*child_part_p)->parent = part; |
515 | 20.2k | child_part_p = &(*child_part_p)->next; |
516 | | |
517 | 20.2k | multipart = TRUE; |
518 | 20.2k | args++; |
519 | 20.2k | } |
520 | 28.6k | if (multipart) { |
521 | 10.5k | part->flags |= MESSAGE_PART_FLAG_MULTIPART; |
522 | 10.5k | } |
523 | 28.6k | } |
524 | | |
525 | 28.6k | if (multipart) { |
526 | 10.5k | data->content_type = "multipart"; |
527 | 10.5k | if (!imap_arg_get_string(args++, &data->content_subtype)) { |
528 | 19 | *error_r = "Invalid multipart content-type"; |
529 | 19 | return -1; |
530 | 19 | } |
531 | 10.5k | data->content_subtype = p_strdup(pool, data->content_subtype); |
532 | 10.5k | if (args->type == IMAP_ARG_EOL) |
533 | 4.14k | return 0; |
534 | 6.43k | if (imap_bodystructure_params_parse(args++, pool, |
535 | 6.43k | &data->content_type_params, |
536 | 6.43k | &data->content_type_params_count) < 0) { |
537 | 3 | *error_r = "Invalid content params"; |
538 | 3 | return -1; |
539 | 3 | } |
540 | 6.43k | return imap_bodystructure_parse_args_common |
541 | 6.43k | (part, pool, args, error_r); |
542 | 6.43k | } |
543 | | |
544 | | /* "content type" "subtype" */ |
545 | 18.0k | if (!imap_arg_get_string(&args[0], &content_type) || |
546 | 17.7k | !imap_arg_get_string(&args[1], &subtype)) { |
547 | 412 | *error_r = "Invalid content-type"; |
548 | 412 | return -1; |
549 | 412 | } |
550 | 17.6k | data->content_type = p_strdup(pool, content_type); |
551 | 17.6k | data->content_subtype = p_strdup(pool, subtype); |
552 | 17.6k | args += 2; |
553 | | |
554 | 17.6k | text = strcasecmp(content_type, "text") == 0; |
555 | 17.6k | message_rfc822 = strcasecmp(content_type, "message") == 0 && |
556 | 4.36k | strcasecmp(subtype, "rfc822") == 0; |
557 | | |
558 | 17.6k | if (!parsing_tree) { |
559 | | #if 0 |
560 | | /* Disabled for now. Earlier Dovecot versions handled broken |
561 | | Content-Type headers by writing them as "text" "plain" to |
562 | | BODYSTRUCTURE reply, but the message_part didn't have |
563 | | MESSAGE_PART_FLAG_TEXT. */ |
564 | | if (text != ((part->flags & MESSAGE_PART_FLAG_TEXT) != 0)) { |
565 | | *error_r = "message_part text flag " |
566 | | "doesn't match BODYSTRUCTURE"; |
567 | | return -1; |
568 | | } |
569 | | #endif |
570 | 0 | if (message_rfc822 != |
571 | 0 | ((part->flags & MESSAGE_PART_FLAG_MESSAGE_RFC822) != 0)) { |
572 | 0 | *error_r = "message_part message/rfc822 flag " |
573 | 0 | "doesn't match BODYSTRUCTURE"; |
574 | 0 | return -1; |
575 | 0 | } |
576 | 17.6k | } else { |
577 | 17.6k | if (text) |
578 | 2.17k | part->flags |= MESSAGE_PART_FLAG_TEXT; |
579 | 17.6k | if (message_rfc822) |
580 | 3.24k | part->flags |= MESSAGE_PART_FLAG_MESSAGE_RFC822; |
581 | 17.6k | } |
582 | | |
583 | | /* ("content type param key" "value" ...) | NIL */ |
584 | 17.6k | if (imap_bodystructure_params_parse(args++, pool, |
585 | 17.6k | &data->content_type_params, |
586 | 17.6k | &data->content_type_params_count) < 0) { |
587 | 361 | *error_r = "Invalid content params"; |
588 | 361 | return -1; |
589 | 361 | } |
590 | | |
591 | | /* "content id" "content description" "transfer encoding" size */ |
592 | 17.2k | if (!imap_arg_get_nstring(args++, &data->content_id)) { |
593 | 10 | *error_r = "Invalid content-id"; |
594 | 10 | return -1; |
595 | 10 | } |
596 | 17.2k | if (!imap_arg_get_nstring(args++, &data->content_description)) { |
597 | 4 | *error_r = "Invalid content-description"; |
598 | 4 | return -1; |
599 | 4 | } |
600 | 17.2k | if (!imap_arg_get_string(args++, &data->content_transfer_encoding)) { |
601 | 6 | *error_r = "Invalid content-transfer-encoding"; |
602 | 6 | return -1; |
603 | 6 | } |
604 | 17.2k | data->content_id = p_strdup(pool, data->content_id); |
605 | 17.2k | data->content_description = p_strdup(pool, data->content_description); |
606 | 17.2k | data->content_transfer_encoding = |
607 | 17.2k | p_strdup(pool, data->content_transfer_encoding); |
608 | 17.2k | if (!imap_arg_get_atom(args++, &value) || |
609 | 17.2k | str_to_uoff(value, &vsize) < 0) { |
610 | 130 | *error_r = "Invalid size field"; |
611 | 130 | return -1; |
612 | 130 | } |
613 | 17.1k | if (!parsing_tree) { |
614 | 0 | if (vsize != part->body_size.virtual_size) { |
615 | 0 | *error_r = "message_part virtual_size doesn't match " |
616 | 0 | "size in BODYSTRUCTURE"; |
617 | 0 | return -1; |
618 | 0 | } |
619 | 17.1k | } else { |
620 | 17.1k | part->body_size.virtual_size = vsize; |
621 | 17.1k | } |
622 | | |
623 | 17.1k | if (text) { |
624 | | /* text/xxx - text lines */ |
625 | 2.17k | if (!imap_arg_get_atom(args++, &value) || |
626 | 2.17k | str_to_uint(value, &lines) < 0) { |
627 | 210 | *error_r = "Invalid lines field"; |
628 | 210 | return -1; |
629 | 210 | } |
630 | 2.17k | i_assert(part->children == NULL); |
631 | 1.96k | has_lines = TRUE; |
632 | 14.9k | } else if (message_rfc822) { |
633 | | /* message/rfc822 - envelope + bodystructure + text lines */ |
634 | | |
635 | 3.24k | if (!parsing_tree) { |
636 | 0 | i_assert(part->children != NULL && |
637 | 0 | part->children->next == NULL); |
638 | 0 | } |
639 | | |
640 | 3.24k | if (!imap_arg_get_list(&args[1], &list_args)) { |
641 | 3 | *error_r = "Child bodystructure list expected"; |
642 | 3 | return -1; |
643 | 3 | } |
644 | 3.23k | if (imap_bodystructure_parse_args_int( |
645 | 3.23k | nesting, list_args, pool, &part->children, error_r) < 0) |
646 | 84 | return -1; |
647 | 3.15k | if (parsing_tree) { |
648 | 3.15k | i_assert(part->children != NULL && |
649 | 3.15k | part->children->next == NULL); |
650 | 3.15k | part->children->parent = part; |
651 | 3.15k | } |
652 | | |
653 | 3.15k | if (!imap_arg_get_list(&args[0], &list_args)) { |
654 | 1 | *error_r = "Envelope list expected"; |
655 | 1 | return -1; |
656 | 1 | } |
657 | 3.15k | if (!imap_envelope_parse_args(list_args, pool, |
658 | 3.15k | &part->children->data->envelope, &error)) { |
659 | 66 | *error_r = t_strdup_printf |
660 | 66 | ("Invalid envelope list: %s", error); |
661 | 66 | return -1; |
662 | 66 | } |
663 | 3.08k | args += 2; |
664 | 3.08k | if (!imap_arg_get_atom(args++, &value) || |
665 | 3.08k | str_to_uint(value, &lines) < 0) { |
666 | 6 | *error_r = "Invalid lines field"; |
667 | 6 | return -1; |
668 | 6 | } |
669 | 3.08k | has_lines = TRUE; |
670 | 11.7k | } else { |
671 | 11.7k | i_assert(part->children == NULL); |
672 | 11.7k | lines = 0; |
673 | 11.7k | has_lines = FALSE; |
674 | 11.7k | } |
675 | 16.7k | if (!parsing_tree) { |
676 | 0 | if (has_lines && lines != part->body_size.lines) { |
677 | 0 | *error_r = "message_part lines " |
678 | 0 | "doesn't match lines in BODYSTRUCTURE"; |
679 | 0 | return -1; |
680 | 0 | } |
681 | 16.7k | } else { |
682 | 16.7k | part->body_size.lines = lines; |
683 | 16.7k | } |
684 | 16.7k | if (args->type == IMAP_ARG_EOL) |
685 | 7.70k | return 0; |
686 | 9.05k | if (!imap_arg_get_nstring(args++, &data->content_md5)) { |
687 | 5 | *error_r = "Invalid content-md5"; |
688 | 5 | return -1; |
689 | 5 | } |
690 | 9.04k | data->content_md5 = p_strdup(pool, data->content_md5); |
691 | 9.04k | return imap_bodystructure_parse_args_common |
692 | 9.04k | (part, pool, args, error_r); |
693 | 9.05k | } |
694 | | |
695 | | int |
696 | | imap_bodystructure_parse_args(const struct imap_arg *args, pool_t pool, |
697 | | struct message_part **_part, |
698 | | const char **error_r) |
699 | 5.12k | { |
700 | 5.12k | return imap_bodystructure_parse_args_int(0, args, pool, _part, error_r); |
701 | 5.12k | } |
702 | | |
703 | | static void imap_bodystructure_reset_data(struct message_part *parts) |
704 | 6.82k | { |
705 | 11.6k | for (; parts != NULL; parts = parts->next) { |
706 | 4.79k | parts->data = NULL; |
707 | 4.79k | imap_bodystructure_reset_data(parts->children); |
708 | 4.79k | } |
709 | 6.82k | } |
710 | | |
711 | | int imap_bodystructure_parse_full(const char *bodystructure, |
712 | | pool_t pool, struct message_part **parts, |
713 | | const char **error_r) |
714 | 5.87k | { |
715 | 5.87k | struct istream *input; |
716 | 5.87k | struct imap_parser *parser; |
717 | 5.87k | const struct imap_arg *args; |
718 | 5.87k | int ret; |
719 | | |
720 | 5.87k | i_assert(*parts == NULL || (*parts)->next == NULL); |
721 | | |
722 | 5.87k | input = i_stream_create_from_data(bodystructure, strlen(bodystructure)); |
723 | 5.87k | (void)i_stream_read(input); |
724 | | |
725 | 5.87k | parser = imap_parser_create(input, NULL, SIZE_MAX); |
726 | 5.87k | ret = imap_parser_finish_line(parser, 0, |
727 | 5.87k | IMAP_PARSE_FLAG_LITERAL_TYPE, &args); |
728 | 5.87k | if (ret < 0) { |
729 | 369 | *error_r = t_strdup_printf("IMAP parser failed: %s", |
730 | 369 | imap_parser_get_error(parser, NULL)); |
731 | 5.50k | } else if (ret == 0) { |
732 | 384 | *error_r = "Empty bodystructure"; |
733 | 384 | ret = -1; |
734 | 5.12k | } else { |
735 | 5.12k | T_BEGIN { |
736 | 5.12k | ret = imap_bodystructure_parse_args |
737 | 5.12k | (args, pool, parts, error_r); |
738 | 5.12k | } T_END_PASS_STR_IF(ret < 0, error_r); |
739 | 5.12k | } |
740 | | |
741 | 5.87k | if (ret < 0) { |
742 | | /* Don't leave partially filled data to message_parts. Some of |
743 | | the code expects that if the first message_part->data is |
744 | | filled, they all are. */ |
745 | 2.03k | imap_bodystructure_reset_data(*parts); |
746 | 2.03k | } |
747 | | |
748 | 5.87k | imap_parser_unref(&parser); |
749 | 5.87k | i_stream_destroy(&input); |
750 | 5.87k | return ret; |
751 | 5.87k | } |
752 | | |
753 | | int imap_bodystructure_parse(const char *bodystructure, |
754 | | pool_t pool, struct message_part *parts, |
755 | | const char **error_r) |
756 | 0 | { |
757 | 0 | i_assert(parts != NULL); |
758 | | |
759 | 0 | return imap_bodystructure_parse_full(bodystructure, |
760 | 0 | pool, &parts, error_r); |
761 | 0 | } |
762 | | |
763 | | /* |
764 | | * IMAP BODYSTRUCTURE to BODY conversion |
765 | | */ |
766 | | |
767 | | static bool str_append_nstring(string_t *str, const struct imap_arg *arg) |
768 | 0 | { |
769 | 0 | const char *cstr; |
770 | |
|
771 | 0 | if (!imap_arg_get_nstring(arg, &cstr)) |
772 | 0 | return FALSE; |
773 | | |
774 | 0 | switch (arg->type) { |
775 | 0 | case IMAP_ARG_NIL: |
776 | 0 | str_append(str, "NIL"); |
777 | 0 | break; |
778 | 0 | case IMAP_ARG_STRING: |
779 | 0 | str_append_c(str, '"'); |
780 | | /* NOTE: we're parsing with no-unescape flag, |
781 | | so don't double-escape it here */ |
782 | 0 | str_append(str, cstr); |
783 | 0 | str_append_c(str, '"'); |
784 | 0 | break; |
785 | 0 | case IMAP_ARG_LITERAL: { |
786 | 0 | str_printfa(str, "{%zu}\r\n", strlen(cstr)); |
787 | 0 | str_append(str, cstr); |
788 | 0 | break; |
789 | 0 | } |
790 | 0 | default: |
791 | 0 | i_unreached(); |
792 | 0 | return FALSE; |
793 | 0 | } |
794 | 0 | return TRUE; |
795 | 0 | } |
796 | | |
797 | | static void |
798 | | imap_write_envelope_list(const struct imap_arg *args, string_t *str, |
799 | | bool toplevel) |
800 | 0 | { |
801 | 0 | const struct imap_arg *children; |
802 | | |
803 | | /* don't do any typechecking, just write it out */ |
804 | 0 | while (!IMAP_ARG_IS_EOL(args)) { |
805 | 0 | bool list = FALSE; |
806 | |
|
807 | 0 | if (!str_append_nstring(str, args)) { |
808 | 0 | if (!imap_arg_get_list(args, &children)) { |
809 | | /* everything is either nstring or list */ |
810 | 0 | i_unreached(); |
811 | 0 | } |
812 | | |
813 | 0 | str_append_c(str, '('); |
814 | 0 | imap_write_envelope_list(children, str, FALSE); |
815 | 0 | str_append_c(str, ')'); |
816 | |
|
817 | 0 | list = TRUE; |
818 | 0 | } |
819 | 0 | args++; |
820 | |
|
821 | 0 | if ((toplevel || !list) && !IMAP_ARG_IS_EOL(args)) |
822 | 0 | str_append_c(str, ' '); |
823 | 0 | } |
824 | 0 | } |
825 | | |
826 | | static void |
827 | | imap_write_envelope(const struct imap_arg *args, string_t *str) |
828 | 0 | { |
829 | 0 | imap_write_envelope_list(args, str, TRUE); |
830 | 0 | } |
831 | | |
832 | | static int imap_parse_bodystructure_args(const struct imap_arg *args, |
833 | | string_t *str, const char **error_r) |
834 | 0 | { |
835 | 0 | const struct imap_arg *subargs; |
836 | 0 | const struct imap_arg *list_args; |
837 | 0 | const char *value, *content_type, *subtype; |
838 | 0 | bool multipart, text, message_rfc822; |
839 | 0 | int i; |
840 | |
|
841 | 0 | multipart = FALSE; |
842 | 0 | while (args->type == IMAP_ARG_LIST) { |
843 | 0 | str_append_c(str, '('); |
844 | 0 | list_args = imap_arg_as_list(args); |
845 | 0 | if (imap_parse_bodystructure_args(list_args, str, error_r) < 0) |
846 | 0 | return -1; |
847 | 0 | str_append_c(str, ')'); |
848 | |
|
849 | 0 | multipart = TRUE; |
850 | 0 | args++; |
851 | 0 | } |
852 | | |
853 | 0 | if (multipart) { |
854 | | /* next is subtype of Content-Type. rest is skipped. */ |
855 | 0 | str_append_c(str, ' '); |
856 | 0 | if (!str_append_nstring(str, args)) { |
857 | 0 | *error_r = "Invalid multipart content-type"; |
858 | 0 | return -1; |
859 | 0 | } |
860 | 0 | return 0; |
861 | 0 | } |
862 | | |
863 | | /* "content type" "subtype" */ |
864 | 0 | if (!imap_arg_get_string(&args[0], &content_type) || |
865 | 0 | !imap_arg_get_string(&args[1], &subtype)) { |
866 | 0 | *error_r = "Invalid content-type"; |
867 | 0 | return -1; |
868 | 0 | } |
869 | | |
870 | 0 | if (!str_append_nstring(str, &args[0])) |
871 | 0 | i_unreached(); |
872 | 0 | str_append_c(str, ' '); |
873 | 0 | if (!str_append_nstring(str, &args[1])) |
874 | 0 | i_unreached(); |
875 | | |
876 | 0 | text = strcasecmp(content_type, "text") == 0; |
877 | 0 | message_rfc822 = strcasecmp(content_type, "message") == 0 && |
878 | 0 | strcasecmp(subtype, "rfc822") == 0; |
879 | |
|
880 | 0 | args += 2; |
881 | | |
882 | | /* ("content type param key" "value" ...) | NIL */ |
883 | 0 | if (imap_arg_get_list(args, &subargs)) { |
884 | 0 | str_append(str, " ("); |
885 | 0 | while (!IMAP_ARG_IS_EOL(subargs)) { |
886 | 0 | if (!str_append_nstring(str, &subargs[0])) { |
887 | 0 | *error_r = "Invalid content param key"; |
888 | 0 | return -1; |
889 | 0 | } |
890 | 0 | str_append_c(str, ' '); |
891 | 0 | if (!str_append_nstring(str, &subargs[1])) { |
892 | 0 | *error_r = "Invalid content param value"; |
893 | 0 | return -1; |
894 | 0 | } |
895 | | |
896 | 0 | subargs += 2; |
897 | 0 | if (IMAP_ARG_IS_EOL(subargs)) |
898 | 0 | break; |
899 | 0 | str_append_c(str, ' '); |
900 | 0 | } |
901 | 0 | str_append(str, ")"); |
902 | 0 | } else if (args->type == IMAP_ARG_NIL) { |
903 | 0 | str_append(str, " NIL"); |
904 | 0 | } else { |
905 | 0 | *error_r = "list/NIL expected"; |
906 | 0 | return -1; |
907 | 0 | } |
908 | 0 | args++; |
909 | | |
910 | | /* "content id" "content description" "transfer encoding" size */ |
911 | 0 | for (i = 0; i < 3; i++, args++) { |
912 | 0 | str_append_c(str, ' '); |
913 | |
|
914 | 0 | if (!str_append_nstring(str, args)) { |
915 | 0 | *error_r = "nstring expected"; |
916 | 0 | return -1; |
917 | 0 | } |
918 | 0 | } |
919 | 0 | if (!imap_arg_get_atom(args, &value)) { |
920 | 0 | *error_r = "atom expected for size"; |
921 | 0 | return -1; |
922 | 0 | } |
923 | 0 | str_printfa(str, " %s", value); |
924 | 0 | args++; |
925 | |
|
926 | 0 | if (text) { |
927 | | /* text/xxx - text lines */ |
928 | 0 | if (!imap_arg_get_atom(args, &value)) { |
929 | 0 | *error_r = "Text lines expected"; |
930 | 0 | return -1; |
931 | 0 | } |
932 | | |
933 | 0 | str_append_c(str, ' '); |
934 | 0 | str_append(str, value); |
935 | 0 | } else if (message_rfc822) { |
936 | | /* message/rfc822 - envelope + bodystructure + text lines */ |
937 | 0 | str_append_c(str, ' '); |
938 | |
|
939 | 0 | if (!imap_arg_get_list(&args[0], &list_args)) { |
940 | 0 | *error_r = "Envelope list expected"; |
941 | 0 | return -1; |
942 | 0 | } |
943 | 0 | str_append_c(str, '('); |
944 | 0 | imap_write_envelope(list_args, str); |
945 | 0 | str_append(str, ") ("); |
946 | |
|
947 | 0 | if (!imap_arg_get_list(&args[1], &list_args)) { |
948 | 0 | *error_r = "Child bodystructure list expected"; |
949 | 0 | return -1; |
950 | 0 | } |
951 | 0 | if (imap_parse_bodystructure_args(list_args, str, error_r) < 0) |
952 | 0 | return -1; |
953 | | |
954 | 0 | str_append(str, ") "); |
955 | 0 | if (!imap_arg_get_atom(&args[2], &value)) { |
956 | 0 | *error_r = "Text lines expected"; |
957 | 0 | return -1; |
958 | 0 | } |
959 | 0 | str_append(str, value); |
960 | 0 | } |
961 | 0 | return 0; |
962 | 0 | } |
963 | | |
964 | | int imap_body_parse_from_bodystructure(const char *bodystructure, |
965 | | string_t *dest, const char **error_r) |
966 | 0 | { |
967 | 0 | struct istream *input; |
968 | 0 | struct imap_parser *parser; |
969 | 0 | const struct imap_arg *args; |
970 | 0 | int ret; |
971 | |
|
972 | 0 | input = i_stream_create_from_data(bodystructure, strlen(bodystructure)); |
973 | 0 | (void)i_stream_read(input); |
974 | |
|
975 | 0 | parser = imap_parser_create(input, NULL, SIZE_MAX); |
976 | 0 | ret = imap_parser_finish_line(parser, 0, IMAP_PARSE_FLAG_NO_UNESCAPE | |
977 | 0 | IMAP_PARSE_FLAG_LITERAL_TYPE, &args); |
978 | 0 | if (ret < 0) { |
979 | 0 | *error_r = t_strdup_printf("IMAP parser failed: %s", |
980 | 0 | imap_parser_get_error(parser, NULL)); |
981 | 0 | } else if (ret == 0) { |
982 | 0 | *error_r = "Empty bodystructure"; |
983 | 0 | ret = -1; |
984 | 0 | } else { |
985 | 0 | ret = imap_parse_bodystructure_args(args, dest, error_r); |
986 | 0 | } |
987 | |
|
988 | 0 | imap_parser_unref(&parser); |
989 | 0 | i_stream_destroy(&input); |
990 | 0 | return ret; |
991 | 0 | } |