/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "buffer.h" #include "istream.h" #include "str.h" #include "message-part-data.h" #include "message-parser.h" #include "rfc822-parser.h" #include "rfc2231-parser.h" #include "imap-parser.h" #include "imap-quote.h" #include "imap-envelope.h" #include "imap-bodystructure.h" #define EMPTY_BODY "(\"text\" \"plain\" " \ "(\"charset\" \""MESSAGE_PART_DEFAULT_CHARSET"\") NIL NIL \"7bit\" 0 0)" #define EMPTY_BODYSTRUCTURE "(\"text\" \"plain\" " \ "(\"charset\" \""MESSAGE_PART_DEFAULT_CHARSET"\") NIL NIL \"7bit\" 0 0 " \ "NIL NIL NIL NIL)" /* * IMAP BODY/BODYSTRUCTURE write */ static void params_write(const struct message_part_param *params, unsigned int params_count, string_t *str, bool default_charset) { unsigned int i; bool seen_charset; if (!default_charset && params_count == 0) { str_append(str, "NIL"); return; } str_append_c(str, '('); seen_charset = FALSE; for (i = 0; i < params_count; i++) { i_assert(params[i].name != NULL); i_assert(params[i].value != NULL); if (i > 0) str_append_c(str, ' '); if (default_charset && strcasecmp(params[i].name, "charset") == 0) seen_charset = TRUE; imap_append_string(str, params[i].name); str_append_c(str, ' '); imap_append_string(str, params[i].value); } if (default_charset && !seen_charset) { if (i > 0) str_append_c(str, ' '); str_append(str, "\"charset\" " "\""MESSAGE_PART_DEFAULT_CHARSET"\""); } str_append_c(str, ')'); } static int part_write_bodystructure_siblings(const struct message_part *part, string_t *dest, bool extended, const char **error_r) { for (; part != NULL; part = part->next) { str_append_c(dest, '('); if (imap_bodystructure_write(part, dest, extended, error_r) < 0) return -1; str_append_c(dest, ')'); } return 0; } static void part_write_bodystructure_common(const struct message_part_data *data, string_t *str) { str_append_c(str, ' '); if (data->content_disposition == NULL) str_append(str, "NIL"); else { str_append_c(str, '('); imap_append_string(str, data->content_disposition); str_append_c(str, ' '); params_write(data->content_disposition_params, data->content_disposition_params_count, str, FALSE); str_append_c(str, ')'); } str_append_c(str, ' '); if (data->content_language == NULL) str_append(str, "NIL"); else { const char *const *lang = data->content_language; i_assert(*lang != NULL); str_append_c(str, '('); imap_append_string(str, *lang); lang++; while (*lang != NULL) { str_append_c(str, ' '); imap_append_string(str, *lang); lang++; } str_append_c(str, ')'); } str_append_c(str, ' '); imap_append_nstring_nolf(str, data->content_location); } static int part_write_body_multipart(const struct message_part *part, string_t *str, bool extended, const char **error_r) { const struct message_part_data *data = part->data; i_assert(part->data != NULL); if (part->children != NULL) { if (part_write_bodystructure_siblings(part->children, str, extended, error_r) < 0) return -1; } else { /* no parts in multipart message, that's not allowed. write a single 0-length text/plain structure */ if (!extended) str_append(str, EMPTY_BODY); else str_append(str, EMPTY_BODYSTRUCTURE); } str_append_c(str, ' '); imap_append_string(str, data->content_subtype); if (!extended) return 0; /* BODYSTRUCTURE data */ str_append_c(str, ' '); params_write(data->content_type_params, data->content_type_params_count, str, FALSE); part_write_bodystructure_common(data, str); return 0; } static bool part_is_truncated(const struct message_part *part) { const struct message_part_data *data = part->data; i_assert((part->flags & MESSAGE_PART_FLAG_MESSAGE_RFC822) == 0); i_assert((part->flags & MESSAGE_PART_FLAG_MULTIPART) == 0); if (data->content_type != NULL) { if (strcasecmp(data->content_type, "message") == 0 && strcasecmp(data->content_subtype, "rfc822") == 0) { /* It's message/rfc822, but without MESSAGE_PART_FLAG_MESSAGE_RFC822. */ return TRUE; } if (strcasecmp(data->content_type, "multipart") == 0) { /* It's multipart/, but without MESSAGE_PART_FLAG_MULTIPART. */ return TRUE; } } else { /* No Content-Type */ if (part->parent != NULL && (part->parent->flags & MESSAGE_PART_FLAG_MULTIPART_DIGEST) != 0) { /* Parent is MESSAGE_PART_FLAG_MULTIPART_DIGEST (so this should have been message/rfc822). */ return TRUE; } } return FALSE; } static int part_write_body(const struct message_part *part, string_t *str, bool extended, const char **error_r) { const struct message_part_data *data = part->data; bool text; i_assert(part->data != NULL); if ((part->flags & MESSAGE_PART_FLAG_MESSAGE_RFC822) != 0) { str_append(str, "\"message\" \"rfc822\""); text = FALSE; } else if (part_is_truncated(part)) { /* Maximum MIME part count was reached while parsing the mail. Write this part out as application/octet-stream instead. We're not using text/plain, because it would require message-parser to use MESSAGE_PART_FLAG_TEXT for this part to avoid losing line count in message_part serialization. */ str_append(str, "\"application\" \"octet-stream\""); text = FALSE; } else { /* "content type" "subtype" */ if (data->content_type == NULL) { text = TRUE; str_append(str, "\"text\" \"plain\""); } else { text = (strcasecmp(data->content_type, "text") == 0); imap_append_string(str, data->content_type); str_append_c(str, ' '); imap_append_string(str, data->content_subtype); } bool part_is_text = (part->flags & MESSAGE_PART_FLAG_TEXT) != 0; if (text != part_is_text) { *error_r = "text flag mismatch"; return -1; } } /* ("content type param key" "value" ...) */ str_append_c(str, ' '); params_write(data->content_type_params, data->content_type_params_count, str, text); str_append_c(str, ' '); imap_append_nstring_nolf(str, data->content_id); str_append_c(str, ' '); imap_append_nstring_nolf(str, data->content_description); str_append_c(str, ' '); if (data->content_transfer_encoding != NULL) imap_append_string(str, data->content_transfer_encoding); else str_append(str, "\"7bit\""); str_printfa(str, " %"PRIuUOFF_T, part->body_size.virtual_size); if (text) { /* text/.. contains line count */ str_printfa(str, " %u", part->body_size.lines); } else if ((part->flags & MESSAGE_PART_FLAG_MESSAGE_RFC822) != 0) { /* message/rfc822 contains envelope + body + line count */ const struct message_part_data *child_data; i_assert(part->children != NULL); i_assert(part->children->next == NULL); child_data = part->children->data; str_append(str, " ("); imap_envelope_write(child_data->envelope, str); str_append(str, ") "); if (part_write_bodystructure_siblings(part->children, str, extended, error_r) < 0) return -1; str_printfa(str, " %u", part->body_size.lines); } if (!extended) return 0; /* BODYSTRUCTURE data */ /* "md5" ("content disposition" ("disposition" "params")) ("body" "language" "params") "location" */ str_append_c(str, ' '); imap_append_nstring_nolf(str, data->content_md5); part_write_bodystructure_common(data, str); return 0; } int imap_bodystructure_write(const struct message_part *part, string_t *dest, bool extended, const char **error_r) { if ((part->flags & MESSAGE_PART_FLAG_MULTIPART) != 0) return part_write_body_multipart(part, dest, extended, error_r); else return part_write_body(part, dest, extended, error_r); } /* * IMAP BODYSTRUCTURE parsing */ static int imap_bodystructure_strlist_parse(const struct imap_arg *arg, pool_t pool, const char *const **list_r) { const char *item, **list; const struct imap_arg *list_args; unsigned int list_count, i; if (arg->type == IMAP_ARG_NIL) { *list_r = NULL; return 0; } if (imap_arg_get_nstring(arg, &item)) { list = p_new(pool, const char *, 2); list[0] = p_strdup(pool, item); } else { if (!imap_arg_get_list_full(arg, &list_args, &list_count)) return -1; if (list_count == 0) return -1; list = p_new(pool, const char *, list_count+1); for (i = 0; i < list_count; i++) { if (!imap_arg_get_string(&list_args[i], &item)) return -1; list[i] = p_strdup(pool, item); } } *list_r = list; return 0; } static int imap_bodystructure_params_parse(const struct imap_arg *arg, pool_t pool, const struct message_part_param **params_r, unsigned int *count_r) { struct message_part_param *params; const struct imap_arg *list_args; unsigned int list_count, params_count, i; if (arg->type == IMAP_ARG_NIL) { *params_r = NULL; return 0; } if (!imap_arg_get_list_full(arg, &list_args, &list_count)) return -1; if ((list_count % 2) != 0) return -1; if (list_count == 0) return -1; params_count = list_count/2; params = p_new(pool, struct message_part_param, params_count+1); for (i = 0; i < params_count; i++) { const char *name, *value; if (!imap_arg_get_string(&list_args[i*2+0], &name)) return -1; if (!imap_arg_get_string(&list_args[i*2+1], &value)) return -1; params[i].name = p_strdup(pool, name); params[i].value = p_strdup(pool, value); } *params_r = params; *count_r = params_count; return 0; } static int imap_bodystructure_parse_args_common(struct message_part *part, pool_t pool, const struct imap_arg *args, const char **error_r) { struct message_part_data *data = part->data; const struct imap_arg *list_args; if (args->type == IMAP_ARG_EOL) return 0; if (args->type == IMAP_ARG_NIL) args++; else if (!imap_arg_get_list(args, &list_args)) { *error_r = "Invalid content-disposition list"; return -1; } else { if (!imap_arg_get_string (list_args++, &data->content_disposition)) { *error_r = "Invalid content-disposition"; return -1; } data->content_disposition = p_strdup(pool, data->content_disposition); if (imap_bodystructure_params_parse(list_args, pool, &data->content_disposition_params, &data->content_disposition_params_count) < 0) { *error_r = "Invalid content-disposition params"; return -1; } args++; } if (args->type == IMAP_ARG_EOL) return 0; if (imap_bodystructure_strlist_parse (args++, pool, &data->content_language) < 0) { *error_r = "Invalid content-language"; return -1; } if (args->type == IMAP_ARG_EOL) return 0; if (!imap_arg_get_nstring (args++, &data->content_location)) { *error_r = "Invalid content-location"; return -1; } data->content_location = p_strdup(pool, data->content_location); return 0; } int imap_bodystructure_parse_args(const struct imap_arg *args, pool_t pool, struct message_part **_part, const char **error_r) { struct message_part *part = *_part, *child_part;; struct message_part **child_part_p; struct message_part_data *data; const struct imap_arg *list_args; const char *value, *content_type, *subtype, *error; bool multipart, text, message_rfc822, parsing_tree, has_lines; unsigned int lines; uoff_t vsize; if (part != NULL) { /* parsing with pre-existing message_part tree */ parsing_tree = FALSE; } else { /* parsing message_part tree from BODYSTRUCTURE as well */ part = *_part = p_new(pool, struct message_part, 1); parsing_tree = TRUE; } part->data = data = p_new(pool, struct message_part_data, 1); multipart = FALSE; if (!parsing_tree) { if ((part->flags & MESSAGE_PART_FLAG_MULTIPART) != 0 && part->children == NULL) { struct message_part_data dummy_part_data = { .content_type = "text", .content_subtype = "plain", .content_transfer_encoding = "7bit" }; struct message_part dummy_part = { .parent = part, .data = &dummy_part_data, .flags = MESSAGE_PART_FLAG_TEXT }; struct message_part *dummy_partp = &dummy_part; /* no parts in multipart message, that's not allowed. expect a single 0-length text/plain structure */ if (args->type != IMAP_ARG_LIST || (args+1)->type == IMAP_ARG_LIST) { *error_r = "message_part hierarchy " "doesn't match BODYSTRUCTURE"; return -1; } list_args = imap_arg_as_list(args); if (imap_bodystructure_parse_args(list_args, pool, &dummy_partp, error_r) < 0) return -1; child_part = NULL; multipart = TRUE; args++; } else { child_part = part->children; while (args->type == IMAP_ARG_LIST) { if ((part->flags & MESSAGE_PART_FLAG_MULTIPART) == 0 || child_part == NULL) { *error_r = "message_part hierarchy " "doesn't match BODYSTRUCTURE"; return -1; } list_args = imap_arg_as_list(args); if (imap_bodystructure_parse_args(list_args, pool, &child_part, error_r) < 0) return -1; child_part = child_part->next; multipart = TRUE; args++; } } if (multipart) { if (child_part != NULL) { *error_r = "message_part hierarchy " "doesn't match BODYSTRUCTURE"; return -1; } } else if ((part->flags & MESSAGE_PART_FLAG_MULTIPART) != 0) { *error_r = "message_part multipart flag " "doesn't match BODYSTRUCTURE"; return -1; } } else { child_part_p = &part->children; while (args->type == IMAP_ARG_LIST) { list_args = imap_arg_as_list(args); if (imap_bodystructure_parse_args(list_args, pool, child_part_p, error_r) < 0) return -1; (*child_part_p)->parent = part; child_part_p = &(*child_part_p)->next; multipart = TRUE; args++; } if (multipart) { part->flags |= MESSAGE_PART_FLAG_MULTIPART; } } if (multipart) { data->content_type = "multipart"; if (!imap_arg_get_string(args++, &data->content_subtype)) { *error_r = "Invalid multipart content-type"; return -1; } data->content_subtype = p_strdup(pool, data->content_subtype); if (args->type == IMAP_ARG_EOL) return 0; if (imap_bodystructure_params_parse(args++, pool, &data->content_type_params, &data->content_type_params_count) < 0) { *error_r = "Invalid content params"; return -1; } return imap_bodystructure_parse_args_common (part, pool, args, error_r); } /* "content type" "subtype" */ if (!imap_arg_get_string(&args[0], &content_type) || !imap_arg_get_string(&args[1], &subtype)) { *error_r = "Invalid content-type"; return -1; } data->content_type = p_strdup(pool, content_type); data->content_subtype = p_strdup(pool, subtype); args += 2; text = strcasecmp(content_type, "text") == 0; message_rfc822 = strcasecmp(content_type, "message") == 0 && strcasecmp(subtype, "rfc822") == 0; if (!parsing_tree) { #if 0 /* Disabled for now. Earlier Dovecot versions handled broken Content-Type headers by writing them as "text" "plain" to BODYSTRUCTURE reply, but the message_part didn't have MESSAGE_PART_FLAG_TEXT. */ if (text != ((part->flags & MESSAGE_PART_FLAG_TEXT) != 0)) { *error_r = "message_part text flag " "doesn't match BODYSTRUCTURE"; return -1; } #endif if (message_rfc822 != ((part->flags & MESSAGE_PART_FLAG_MESSAGE_RFC822) != 0)) { *error_r = "message_part message/rfc822 flag " "doesn't match BODYSTRUCTURE"; return -1; } } else { if (text) part->flags |= MESSAGE_PART_FLAG_TEXT; if (message_rfc822) part->flags |= MESSAGE_PART_FLAG_MESSAGE_RFC822; } /* ("content type param key" "value" ...) | NIL */ if (imap_bodystructure_params_parse(args++, pool, &data->content_type_params, &data->content_type_params_count) < 0) { *error_r = "Invalid content params"; return -1; } /* "content id" "content description" "transfer encoding" size */ if (!imap_arg_get_nstring(args++, &data->content_id)) { *error_r = "Invalid content-id"; return -1; } if (!imap_arg_get_nstring(args++, &data->content_description)) { *error_r = "Invalid content-description"; return -1; } if (!imap_arg_get_string(args++, &data->content_transfer_encoding)) { *error_r = "Invalid content-transfer-encoding"; return -1; } data->content_id = p_strdup(pool, data->content_id); data->content_description = p_strdup(pool, data->content_description); data->content_transfer_encoding = p_strdup(pool, data->content_transfer_encoding); if (!imap_arg_get_atom(args++, &value) || str_to_uoff(value, &vsize) < 0) { *error_r = "Invalid size field"; return -1; } if (!parsing_tree) { if (vsize != part->body_size.virtual_size) { *error_r = "message_part virtual_size doesn't match " "size in BODYSTRUCTURE"; return -1; } } else { part->body_size.virtual_size = vsize; } if (text) { /* text/xxx - text lines */ if (!imap_arg_get_atom(args++, &value) || str_to_uint(value, &lines) < 0) { *error_r = "Invalid lines field"; return -1; } i_assert(part->children == NULL); has_lines = TRUE; } else if (message_rfc822) { /* message/rfc822 - envelope + bodystructure + text lines */ if (!parsing_tree) { i_assert(part->children != NULL && part->children->next == NULL); } if (!imap_arg_get_list(&args[1], &list_args)) { *error_r = "Child bodystructure list expected"; return -1; } if (imap_bodystructure_parse_args (list_args, pool, &part->children, error_r) < 0) return -1; if (parsing_tree) { i_assert(part->children != NULL && part->children->next == NULL); part->children->parent = part; } if (!imap_arg_get_list(&args[0], &list_args)) { *error_r = "Envelope list expected"; return -1; } if (!imap_envelope_parse_args(list_args, pool, &part->children->data->envelope, &error)) { *error_r = t_strdup_printf ("Invalid envelope list: %s", error); return -1; } args += 2; if (!imap_arg_get_atom(args++, &value) || str_to_uint(value, &lines) < 0) { *error_r = "Invalid lines field"; return -1; } has_lines = TRUE; } else { i_assert(part->children == NULL); lines = 0; has_lines = FALSE; } if (!parsing_tree) { if (has_lines && lines != part->body_size.lines) { *error_r = "message_part lines " "doesn't match lines in BODYSTRUCTURE"; return -1; } } else { part->body_size.lines = lines; } if (args->type == IMAP_ARG_EOL) return 0; if (!imap_arg_get_nstring(args++, &data->content_md5)) { *error_r = "Invalid content-md5"; return -1; } data->content_md5 = p_strdup(pool, data->content_md5); return imap_bodystructure_parse_args_common (part, pool, args, error_r); } static void imap_bodystructure_reset_data(struct message_part *parts) { for (; parts != NULL; parts = parts->next) { parts->data = NULL; imap_bodystructure_reset_data(parts->children); } } int imap_bodystructure_parse_full(const char *bodystructure, pool_t pool, struct message_part **parts, const char **error_r) { struct istream *input; struct imap_parser *parser; const struct imap_arg *args; int ret; i_assert(*parts == NULL || (*parts)->next == NULL); input = i_stream_create_from_data(bodystructure, strlen(bodystructure)); (void)i_stream_read(input); parser = imap_parser_create(input, NULL, SIZE_MAX); ret = imap_parser_finish_line(parser, 0, IMAP_PARSE_FLAG_LITERAL_TYPE, &args); if (ret < 0) { *error_r = t_strdup_printf("IMAP parser failed: %s", imap_parser_get_error(parser, NULL)); } else if (ret == 0) { *error_r = "Empty bodystructure"; ret = -1; } else { T_BEGIN { ret = imap_bodystructure_parse_args (args, pool, parts, error_r); } T_END_PASS_STR_IF(ret < 0, error_r); } if (ret < 0) { /* Don't leave partially filled data to message_parts. Some of the code expects that if the first message_part->data is filled, they all are. */ imap_bodystructure_reset_data(*parts); } imap_parser_unref(&parser); i_stream_destroy(&input); return ret; } int imap_bodystructure_parse(const char *bodystructure, pool_t pool, struct message_part *parts, const char **error_r) { i_assert(parts != NULL); return imap_bodystructure_parse_full(bodystructure, pool, &parts, error_r); } /* * IMAP BODYSTRUCTURE to BODY conversion */ static bool str_append_nstring(string_t *str, const struct imap_arg *arg) { const char *cstr; if (!imap_arg_get_nstring(arg, &cstr)) return FALSE; switch (arg->type) { case IMAP_ARG_NIL: str_append(str, "NIL"); break; case IMAP_ARG_STRING: str_append_c(str, '"'); /* NOTE: we're parsing with no-unescape flag, so don't double-escape it here */ str_append(str, cstr); str_append_c(str, '"'); break; case IMAP_ARG_LITERAL: { str_printfa(str, "{%zu}\r\n", strlen(cstr)); str_append(str, cstr); break; } default: i_unreached(); return FALSE; } return TRUE; } static void imap_write_envelope_list(const struct imap_arg *args, string_t *str, bool toplevel) { const struct imap_arg *children; /* don't do any typechecking, just write it out */ while (!IMAP_ARG_IS_EOL(args)) { bool list = FALSE; if (!str_append_nstring(str, args)) { if (!imap_arg_get_list(args, &children)) { /* everything is either nstring or list */ i_unreached(); } str_append_c(str, '('); imap_write_envelope_list(children, str, FALSE); str_append_c(str, ')'); list = TRUE; } args++; if ((toplevel || !list) && !IMAP_ARG_IS_EOL(args)) str_append_c(str, ' '); } } static void imap_write_envelope(const struct imap_arg *args, string_t *str) { imap_write_envelope_list(args, str, TRUE); } static int imap_parse_bodystructure_args(const struct imap_arg *args, string_t *str, const char **error_r) { const struct imap_arg *subargs; const struct imap_arg *list_args; const char *value, *content_type, *subtype; bool multipart, text, message_rfc822; int i; multipart = FALSE; while (args->type == IMAP_ARG_LIST) { str_append_c(str, '('); list_args = imap_arg_as_list(args); if (imap_parse_bodystructure_args(list_args, str, error_r) < 0) return -1; str_append_c(str, ')'); multipart = TRUE; args++; } if (multipart) { /* next is subtype of Content-Type. rest is skipped. */ str_append_c(str, ' '); if (!str_append_nstring(str, args)) { *error_r = "Invalid multipart content-type"; return -1; } return 0; } /* "content type" "subtype" */ if (!imap_arg_get_string(&args[0], &content_type) || !imap_arg_get_string(&args[1], &subtype)) { *error_r = "Invalid content-type"; return -1; } if (!str_append_nstring(str, &args[0])) i_unreached(); str_append_c(str, ' '); if (!str_append_nstring(str, &args[1])) i_unreached(); text = strcasecmp(content_type, "text") == 0; message_rfc822 = strcasecmp(content_type, "message") == 0 && strcasecmp(subtype, "rfc822") == 0; args += 2; /* ("content type param key" "value" ...) | NIL */ if (imap_arg_get_list(args, &subargs)) { str_append(str, " ("); while (!IMAP_ARG_IS_EOL(subargs)) { if (!str_append_nstring(str, &subargs[0])) { *error_r = "Invalid content param key"; return -1; } str_append_c(str, ' '); if (!str_append_nstring(str, &subargs[1])) { *error_r = "Invalid content param value"; return -1; } subargs += 2; if (IMAP_ARG_IS_EOL(subargs)) break; str_append_c(str, ' '); } str_append(str, ")"); } else if (args->type == IMAP_ARG_NIL) { str_append(str, " NIL"); } else { *error_r = "list/NIL expected"; return -1; } args++; /* "content id" "content description" "transfer encoding" size */ for (i = 0; i < 3; i++, args++) { str_append_c(str, ' '); if (!str_append_nstring(str, args)) { *error_r = "nstring expected"; return -1; } } if (!imap_arg_get_atom(args, &value)) { *error_r = "atom expected for size"; return -1; } str_printfa(str, " %s", value); args++; if (text) { /* text/xxx - text lines */ if (!imap_arg_get_atom(args, &value)) { *error_r = "Text lines expected"; return -1; } str_append_c(str, ' '); str_append(str, value); } else if (message_rfc822) { /* message/rfc822 - envelope + bodystructure + text lines */ str_append_c(str, ' '); if (!imap_arg_get_list(&args[0], &list_args)) { *error_r = "Envelope list expected"; return -1; } str_append_c(str, '('); imap_write_envelope(list_args, str); str_append(str, ") ("); if (!imap_arg_get_list(&args[1], &list_args)) { *error_r = "Child bodystructure list expected"; return -1; } if (imap_parse_bodystructure_args(list_args, str, error_r) < 0) return -1; str_append(str, ") "); if (!imap_arg_get_atom(&args[2], &value)) { *error_r = "Text lines expected"; return -1; } str_append(str, value); } return 0; } int imap_body_parse_from_bodystructure(const char *bodystructure, string_t *dest, const char **error_r) { struct istream *input; struct imap_parser *parser; const struct imap_arg *args; int ret; input = i_stream_create_from_data(bodystructure, strlen(bodystructure)); (void)i_stream_read(input); parser = imap_parser_create(input, NULL, SIZE_MAX); ret = imap_parser_finish_line(parser, 0, IMAP_PARSE_FLAG_NO_UNESCAPE | IMAP_PARSE_FLAG_LITERAL_TYPE, &args); if (ret < 0) { *error_r = t_strdup_printf("IMAP parser failed: %s", imap_parser_get_error(parser, NULL)); } else if (ret == 0) { *error_r = "Empty bodystructure"; ret = -1; } else { ret = imap_parse_bodystructure_args(args, dest, error_r); } imap_parser_unref(&parser); i_stream_destroy(&input); return ret; }