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