summaryrefslogtreecommitdiffstats
path: root/src/lib-imap-storage/imap-msgpart.c
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 09:51:24 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 09:51:24 +0000
commitf7548d6d28c313cf80e6f3ef89aed16a19815df1 (patch)
treea3f6f2a3f247293bee59ecd28e8cd8ceb6ca064a /src/lib-imap-storage/imap-msgpart.c
parentInitial commit. (diff)
downloaddovecot-f7548d6d28c313cf80e6f3ef89aed16a19815df1.tar.xz
dovecot-f7548d6d28c313cf80e6f3ef89aed16a19815df1.zip
Adding upstream version 1:2.3.19.1+dfsg1.upstream/1%2.3.19.1+dfsg1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/lib-imap-storage/imap-msgpart.c')
-rw-r--r--src/lib-imap-storage/imap-msgpart.c860
1 files changed, 860 insertions, 0 deletions
diff --git a/src/lib-imap-storage/imap-msgpart.c b/src/lib-imap-storage/imap-msgpart.c
new file mode 100644
index 0000000..3bce117
--- /dev/null
+++ b/src/lib-imap-storage/imap-msgpart.c
@@ -0,0 +1,860 @@
+/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "str.h"
+#include "array.h"
+#include "istream.h"
+#include "istream-crlf.h"
+#include "istream-nonuls.h"
+#include "istream-base64.h"
+#include "istream-header-filter.h"
+#include "istream-qp.h"
+#include "ostream.h"
+#include "message-parser.h"
+#include "message-decoder.h"
+#include "mail-storage-private.h"
+#include "mail-namespace.h"
+#include "imap-bodystructure.h"
+#include "imap-parser.h"
+#include "imap-msgpart.h"
+
+enum fetch_type {
+ FETCH_FULL,
+ FETCH_MIME,
+ FETCH_MIME_BODY,
+ FETCH_HEADER,
+ FETCH_HEADER_FIELDS,
+ FETCH_HEADER_FIELDS_NOT,
+ FETCH_BODY
+};
+
+struct imap_msgpart {
+ pool_t pool;
+
+ /* "" for root, otherwise e.g. "1.2.3". the .MIME, .HEADER, etc.
+ suffix not included */
+ const char *section_number;
+ enum fetch_type fetch_type;
+ enum mail_fetch_field wanted_fields;
+
+ /* HEADER.FIELDS[.NOT] (list of headers) */
+ struct mailbox_header_lookup_ctx *header_ctx;
+ const char *const *headers;
+
+ /* which part of the message part to fetch (default: 0..UOFF_T_MAX) */
+ uoff_t partial_offset, partial_size;
+
+ bool decode_cte_to_binary:1;
+};
+
+struct imap_msgpart_open_ctx {
+ /* from matching message_part, set after opening: */
+ uoff_t physical_pos;
+ struct message_size mime_hdr_size;
+ struct message_size mime_body_size;
+};
+
+static struct imap_msgpart *imap_msgpart_type(enum fetch_type fetch_type)
+{
+ struct imap_msgpart *msgpart;
+ pool_t pool;
+
+ pool = pool_alloconly_create("imap msgpart", sizeof(*msgpart)+32);
+ msgpart = p_new(pool, struct imap_msgpart, 1);
+ msgpart->pool = pool;
+ msgpart->partial_size = UOFF_T_MAX;
+ msgpart->fetch_type = fetch_type;
+ msgpart->section_number = "";
+ if (fetch_type == FETCH_HEADER || fetch_type == FETCH_FULL)
+ msgpart->wanted_fields |= MAIL_FETCH_STREAM_HEADER;
+ if (fetch_type == FETCH_BODY || fetch_type == FETCH_FULL)
+ msgpart->wanted_fields |= MAIL_FETCH_STREAM_BODY;
+ return msgpart;
+}
+
+struct imap_msgpart *imap_msgpart_full(void)
+{
+ return imap_msgpart_type(FETCH_FULL);
+}
+
+struct imap_msgpart *imap_msgpart_header(void)
+{
+ return imap_msgpart_type(FETCH_HEADER);
+}
+
+struct imap_msgpart *imap_msgpart_body(void)
+{
+ return imap_msgpart_type(FETCH_BODY);
+}
+
+static struct message_part *
+imap_msgpart_find(struct message_part *parts, const char *section)
+{
+ struct message_part *part = parts;
+ const char *path;
+ unsigned int num;
+
+ path = section;
+ while (*path >= '0' && *path <= '9' && part != NULL) {
+ /* get part number, we have already verified its validity */
+ num = 0;
+ while (*path != '\0' && *path != '.') {
+ i_assert(*path >= '0' && *path <= '9');
+
+ num = num*10 + (*path - '0');
+ path++;
+ }
+
+ if (*path == '.')
+ path++;
+
+ if ((part->flags & MESSAGE_PART_FLAG_MULTIPART) != 0) {
+ /* find the part */
+ part = part->children;
+ for (; num > 1 && part != NULL; num--)
+ part = part->next;
+ } else {
+ /* only 1 allowed with non-multipart messages.
+ if the child isn't message/rfc822, the path must be
+ finished after this. */
+ if (num != 1)
+ part = NULL;
+ else if (*path != '\0' &&
+ (part->flags & MESSAGE_PART_FLAG_MESSAGE_RFC822) == 0)
+ part = NULL;
+ }
+
+ if (part != NULL &&
+ (part->flags & MESSAGE_PART_FLAG_MESSAGE_RFC822) != 0 &&
+ (*path >= '0' && *path <= '9')) {
+ /* if we continue inside the message/rfc822, skip this
+ body part */
+ part = part->children;
+ }
+ }
+ i_assert(part == NULL || *path == '\0');
+ return part;
+}
+
+static int
+imap_msgpart_get_header_fields(pool_t pool, const char *header_list,
+ ARRAY_TYPE(const_string) *fields)
+{
+ struct istream *input;
+ struct imap_parser *parser;
+ const struct imap_arg *args, *hdr_list;
+ unsigned int list_count;
+ unsigned int i;
+ int result = 0;
+
+ input = i_stream_create_from_data(header_list, strlen(header_list));
+ parser = imap_parser_create(input, NULL, SIZE_MAX);
+
+ if (imap_parser_finish_line(parser, 0, 0, &args) > 0 &&
+ imap_arg_get_list_full(args, &hdr_list, &list_count) &&
+ args[1].type == IMAP_ARG_EOL &&
+ list_count > 0) {
+ const char *value;
+
+ p_array_init(fields, pool, list_count);
+ for (i = 0; i < list_count; i++) {
+ if (!imap_arg_get_astring(&hdr_list[i], &value)) {
+ result = -1;
+ break;
+ }
+
+ value = p_strdup(pool, t_str_ucase(value));
+ array_push_back(fields, &value);
+ }
+ /* istream-header-filter requires headers to be sorted */
+ array_sort(fields, i_strcasecmp_p);
+ } else {
+ result = -1;
+ }
+
+ imap_parser_unref(&parser);
+ i_stream_unref(&input);
+ return result;
+}
+
+static int
+imap_msgpart_parse_header_fields(struct imap_msgpart *msgpart,
+ const char *header_list)
+{
+ ARRAY_TYPE(const_string) fields;
+
+ if (header_list[0] == ' ')
+ header_list++;
+
+ /* HEADER.FIELDS (list), HEADER.FIELDS.NOT (list) */
+ if (imap_msgpart_get_header_fields(msgpart->pool, header_list,
+ &fields) < 0)
+ return -1;
+
+ array_append_zero(&fields);
+ msgpart->headers = array_front(&fields);
+ return 0;
+}
+
+int imap_msgpart_parse(const char *section, struct imap_msgpart **msgpart_r)
+{
+ struct imap_msgpart *msgpart;
+ pool_t pool;
+ unsigned int i;
+ bool next_digit;
+ int ret;
+
+ pool = pool_alloconly_create("imap msgpart", 1024);
+ msgpart = *msgpart_r = p_new(pool, struct imap_msgpart, 1);
+ msgpart->pool = pool;
+ msgpart->partial_size = UOFF_T_MAX;
+
+ /* get the section number */
+ next_digit = TRUE;
+ for (i = 0; section[i] != '\0'; i++) {
+ if (section[i] >= '0' && section[i] <= '9') {
+ next_digit = FALSE;
+ } else if (!next_digit && section[i] == '.') {
+ next_digit = TRUE;
+ } else {
+ break;
+ }
+ }
+ if (i == 0) {
+ /* [], [HEADER], etc. */
+ msgpart->section_number = "";
+ } else if (section[i] == '\0') {
+ /* [1.2.3] */
+ if (i > 0 && section[i-1] == '.') {
+ pool_unref(&pool);
+ return -1;
+ }
+ msgpart->section_number = p_strdup(pool, section);
+ section = "";
+ } else {
+ /* [1.2.3.MIME], [1.2.3.HEADER], etc */
+ if (section[i-1] != '.') {
+ pool_unref(&pool);
+ return -1;
+ }
+ msgpart->section_number = p_strndup(pool, section, i-1);
+ section += i;
+ }
+
+ if (*section == '\0') {
+ msgpart->wanted_fields |= MAIL_FETCH_STREAM_BODY;
+ if (*msgpart->section_number == '\0') {
+ /* BODY[] - header+body */
+ msgpart->fetch_type = FETCH_FULL;
+ msgpart->wanted_fields |= MAIL_FETCH_STREAM_HEADER;
+ } else {
+ /* BODY[1] - body only */
+ msgpart->fetch_type = FETCH_MIME_BODY;
+ }
+ return 0;
+ }
+ section = t_str_ucase(section);
+
+ if (strcmp(section, "MIME") == 0) {
+ if (msgpart->section_number[0] == '\0')
+ return -1;
+ msgpart->fetch_type = FETCH_MIME;
+ msgpart->wanted_fields |= MAIL_FETCH_STREAM_BODY;
+ } else if (strcmp(section, "TEXT") == 0) {
+ /* body (for root or for message/rfc822) */
+ msgpart->fetch_type = FETCH_BODY;
+ msgpart->wanted_fields |= MAIL_FETCH_STREAM_BODY;
+ } else if (str_begins(section, "HEADER")) {
+ /* header (for root or for message/rfc822) */
+ if (section[6] == '\0') {
+ msgpart->fetch_type = FETCH_HEADER;
+ ret = 0;
+ } else if (str_begins(section, "HEADER.FIELDS.NOT")) {
+ msgpart->fetch_type = FETCH_HEADER_FIELDS_NOT;
+ ret = imap_msgpart_parse_header_fields(msgpart,
+ section+17);
+ } else if (str_begins(section, "HEADER.FIELDS")) {
+ msgpart->fetch_type = FETCH_HEADER_FIELDS;
+ ret = imap_msgpart_parse_header_fields(msgpart,
+ section+13);
+ } else {
+ ret = -1;
+ }
+ if (ret < 0) {
+ imap_msgpart_free(&msgpart);
+ return -1;
+ }
+ if (msgpart->fetch_type == FETCH_HEADER_FIELDS) {
+ /* we may be able to get this from cache, don't give a
+ wanted_fields hint */
+ } else if (*msgpart->section_number == '\0')
+ msgpart->wanted_fields |= MAIL_FETCH_STREAM_HEADER;
+ else
+ msgpart->wanted_fields |= MAIL_FETCH_STREAM_BODY;
+ } else {
+ imap_msgpart_free(&msgpart);
+ return -1;
+ }
+ return 0;
+}
+
+void imap_msgpart_free(struct imap_msgpart **_msgpart)
+{
+ struct imap_msgpart *msgpart = *_msgpart;
+
+ *_msgpart = NULL;
+
+ imap_msgpart_close_mailbox(msgpart);
+ pool_unref(&msgpart->pool);
+}
+
+bool imap_msgpart_contains_body(const struct imap_msgpart *msgpart)
+{
+ switch (msgpart->fetch_type) {
+ case FETCH_HEADER:
+ case FETCH_HEADER_FIELDS:
+ case FETCH_HEADER_FIELDS_NOT:
+ return FALSE;
+ case FETCH_FULL:
+ case FETCH_MIME:
+ case FETCH_MIME_BODY:
+ case FETCH_BODY:
+ break;
+ }
+ return TRUE;
+}
+
+void imap_msgpart_set_decode_to_binary(struct imap_msgpart *msgpart)
+{
+ msgpart->decode_cte_to_binary = TRUE;
+}
+
+void imap_msgpart_set_partial(struct imap_msgpart *msgpart,
+ uoff_t offset, uoff_t size)
+{
+ msgpart->partial_offset = offset;
+ msgpart->partial_size = size;
+}
+
+uoff_t imap_msgpart_get_partial_offset(struct imap_msgpart *msgpart)
+{
+ return msgpart->partial_offset;
+}
+
+uoff_t imap_msgpart_get_partial_size(struct imap_msgpart *msgpart)
+{
+ return msgpart->partial_size;
+}
+
+enum mail_fetch_field imap_msgpart_get_fetch_data(struct imap_msgpart *msgpart)
+{
+ return msgpart->wanted_fields;
+}
+
+void imap_msgpart_get_wanted_headers(struct imap_msgpart *msgpart,
+ ARRAY_TYPE(const_string) *headers)
+{
+ unsigned int i;
+
+ if (msgpart->fetch_type != FETCH_HEADER_FIELDS)
+ return;
+
+ for (i = 0; msgpart->headers[i] != NULL; i++)
+ array_push_back(headers, &msgpart->headers[i]);
+}
+
+static int
+imap_msgpart_get_partial_header(struct mail *mail, struct istream *mail_input,
+ const struct imap_msgpart *msgpart,
+ uoff_t *virtual_size_r, bool *have_crlfs_r,
+ struct imap_msgpart_open_result *result_r)
+{
+ const char *const *hdr_fields = msgpart->headers;
+ unsigned int hdr_count = str_array_length(hdr_fields);
+ struct message_size hdr_size;
+ struct istream *input;
+ bool has_nuls;
+
+ if (msgpart->fetch_type != FETCH_HEADER_FIELDS) {
+ i_assert(msgpart->fetch_type == FETCH_HEADER_FIELDS_NOT);
+ input = i_stream_create_header_filter(mail_input,
+ HEADER_FILTER_EXCLUDE |
+ HEADER_FILTER_HIDE_BODY,
+ hdr_fields, hdr_count,
+ *null_header_filter_callback,
+ NULL);
+ } else if (msgpart->section_number[0] != '\0') {
+ /* fetching partial headers for a message/rfc822 part. */
+ input = i_stream_create_header_filter(mail_input,
+ HEADER_FILTER_INCLUDE |
+ HEADER_FILTER_HIDE_BODY,
+ hdr_fields, hdr_count,
+ *null_header_filter_callback,
+ NULL);
+ } else {
+ /* mail_get_header_stream() already filtered out the
+ unwanted headers. */
+ input = mail_input;
+ i_stream_ref(input);
+ }
+
+ if (message_get_header_size(input, &hdr_size, &has_nuls) < 0) {
+ mail_set_critical(mail,
+ "read(%s) failed: %s", i_stream_get_name(input),
+ i_stream_get_error(input));
+ i_stream_unref(&input);
+ return -1;
+ }
+ i_stream_seek(input, 0);
+ result_r->input = input;
+ result_r->size = hdr_size.virtual_size;
+ result_r->size_field = 0;
+ *virtual_size_r = hdr_size.virtual_size;
+ *have_crlfs_r = hdr_size.physical_size == hdr_size.virtual_size;
+ return 0;
+}
+
+static struct istream *
+imap_msgpart_crlf_seek(struct mail *mail, struct istream *input,
+ const struct imap_msgpart *msgpart)
+{
+ struct mail_msgpart_partial_cache *cache = &mail->box->partial_cache;
+ struct istream *crlf_input, *errinput;
+ uoff_t physical_start = input->v_offset;
+ uoff_t virtual_skip = msgpart->partial_offset;
+ bool cr_skipped;
+
+ if (virtual_skip == 0) {
+ /* no need to seek */
+ } else if (mail->uid > 0 && cache->uid == mail->uid &&
+ cache->physical_start == physical_start &&
+ cache->virtual_pos < virtual_skip) {
+ /* use cache */
+ i_stream_seek(input, physical_start + cache->physical_pos);
+ virtual_skip -= cache->virtual_pos;
+ }
+ if (message_skip_virtual(input, virtual_skip, &cr_skipped) < 0) {
+ errinput = i_stream_create_error_str(errno, "%s", i_stream_get_error(input));
+ i_stream_set_name(errinput, i_stream_get_name(input));
+ i_stream_unref(&input);
+ return errinput;
+ }
+
+ if (mail->uid > 0 &&
+ (msgpart->partial_offset != 0 ||
+ msgpart->partial_size != UOFF_T_MAX) && !input->eof) {
+ /* update cache */
+ cache->uid = mail->uid;
+ cache->physical_start = physical_start;
+ cache->physical_pos = input->v_offset - physical_start;
+ cache->virtual_pos = msgpart->partial_offset;
+ if (cr_skipped) {
+ /* the physical_pos points to virtual CRLF, but
+ virtual_pos already skipped CR. that can't work,
+ so seek back the virtual CR */
+ cache->virtual_pos--;
+ }
+ }
+ crlf_input = i_stream_create_crlf(input);
+ if (cr_skipped)
+ i_stream_skip(crlf_input, 1);
+ i_stream_unref(&input);
+ return crlf_input;
+}
+
+static void
+imap_msgpart_get_partial(struct mail *mail, const struct imap_msgpart *msgpart,
+ bool convert_nuls, bool use_partial_cache,
+ uoff_t virtual_size, bool have_crlfs,
+ struct imap_msgpart_open_result *result)
+{
+ struct istream *input2;
+ uoff_t bytes_left;
+
+ /* input is already seeked to the beginning of the wanted data */
+
+ if (msgpart->partial_offset >= virtual_size) {
+ /* can't seek past the MIME part */
+ i_stream_unref(&result->input);
+ result->input = i_stream_create_from_data("", 0);
+ result->size = 0;
+ return;
+ }
+
+ if (have_crlfs) {
+ /* input has CRLF linefeeds, we can quickly seek to
+ wanted position */
+ i_stream_skip(result->input, msgpart->partial_offset);
+ } else {
+ /* input has LF linefeeds. it can be slow to seek to wanted
+ position, so try to do caching whenever possible */
+ i_assert(use_partial_cache);
+ result->input = imap_msgpart_crlf_seek(mail, result->input,
+ msgpart);
+ }
+
+ bytes_left = virtual_size - msgpart->partial_offset;
+ if (msgpart->partial_size <= bytes_left) {
+ /* limit output to specified number of bytes */
+ result->size = msgpart->partial_size;
+ } else {
+ /* send all bytes */
+ result->size = bytes_left;
+ }
+
+ if (!mail->has_no_nuls && convert_nuls) {
+ /* IMAP literals must not contain NULs. change them to
+ 0x80 characters. */
+ input2 = i_stream_create_nonuls(result->input, '\x80');
+ i_stream_unref(&result->input);
+ result->input = input2;
+ }
+ input2 = i_stream_create_limit(result->input, result->size);
+ i_stream_unref(&result->input);
+ result->input = input2;
+}
+
+static int
+imap_msgpart_find_part(struct mail *mail, const struct imap_msgpart *msgpart,
+ struct message_part **part_r)
+{
+ struct message_part *parts, *part = NULL;
+
+ if (*msgpart->section_number == '\0') {
+ *part_r = NULL;
+ return 1;
+ }
+
+ if (mail_get_parts(mail, &parts) < 0)
+ return -1;
+ part = imap_msgpart_find(parts, msgpart->section_number);
+ if (part == NULL) {
+ /* MIME part not found. */
+ *part_r = NULL;
+ return 0;
+ }
+
+ switch (msgpart->fetch_type) {
+ case FETCH_MIME:
+ /* What to do if this is a message/rfc822? Does it have
+ MIME headers or not? Possibilities are: a) no, return
+ empty string (UW-IMAP does this), b) return the same as
+ HEADER. Dovecot has done b) for a long time and it's not
+ very clear which one is correct, so we'll just continue
+ with b) */
+ case FETCH_FULL:
+ case FETCH_MIME_BODY:
+ break;
+ case FETCH_HEADER:
+ case FETCH_HEADER_FIELDS:
+ case FETCH_HEADER_FIELDS_NOT:
+ case FETCH_BODY:
+ /* fetching message/rfc822 part's header/body */
+ if ((part->flags & MESSAGE_PART_FLAG_MESSAGE_RFC822) == 0) {
+ *part_r = NULL;
+ return 0;
+ }
+ i_assert(part->children != NULL &&
+ part->children->next == NULL);
+ part = part->children;
+ break;
+ }
+ *part_r = part;
+ return 1;
+}
+
+static int
+imap_msgpart_open_normal(struct mail *mail, struct imap_msgpart *msgpart,
+ const struct message_part *part,
+ uoff_t *virtual_size_r, bool *have_crlfs_r,
+ struct imap_msgpart_open_result *result_r)
+{
+ struct message_size hdr_size, body_size, part_size;
+ struct istream *input = NULL;
+ bool unknown_crlfs = FALSE;
+
+ i_zero(&hdr_size);
+ i_zero(&body_size);
+ i_zero(&part_size);
+
+ if (*msgpart->section_number != '\0') {
+ /* find the MIME part */
+ i_assert(part != NULL);
+
+ if (mail_get_stream_because(mail, NULL, NULL, "MIME part", &input) < 0)
+ return -1;
+
+ i_stream_seek(input, part->physical_pos);
+ hdr_size = part->header_size;
+ body_size = part->body_size;
+ } else switch (msgpart->fetch_type) {
+ case FETCH_FULL:
+ /* fetch the whole message */
+ if (mail_get_stream_because(mail, NULL, NULL, "full mail", &input) < 0 ||
+ mail_get_virtual_size(mail, &body_size.virtual_size) < 0)
+ return -1;
+ result_r->size_field = MAIL_FETCH_VIRTUAL_SIZE;
+
+ i_assert(mail->lookup_abort == MAIL_LOOKUP_ABORT_NEVER);
+ mail->lookup_abort = MAIL_LOOKUP_ABORT_READ_MAIL;
+ if (mail_get_physical_size(mail, &body_size.physical_size) < 0)
+ unknown_crlfs = TRUE;
+ mail->lookup_abort = MAIL_LOOKUP_ABORT_NEVER;
+ break;
+ case FETCH_MIME:
+ case FETCH_MIME_BODY:
+ i_unreached();
+ case FETCH_HEADER:
+ case FETCH_HEADER_FIELDS_NOT:
+ /* fetch the message's header */
+ if (mail_get_hdr_stream(mail, &hdr_size, &input) < 0)
+ return -1;
+ result_r->size_field = MAIL_FETCH_MESSAGE_PARTS;
+ break;
+ case FETCH_HEADER_FIELDS:
+ /* try to lookup the headers from cache */
+ if (msgpart->header_ctx == NULL) {
+ msgpart->header_ctx =
+ mailbox_header_lookup_init(mail->box,
+ msgpart->headers);
+ }
+ if (mail_get_header_stream(mail, msgpart->header_ctx,
+ &input) < 0)
+ return -1;
+ result_r->size_field = 0;
+ break;
+ case FETCH_BODY:
+ /* fetch the message's body */
+ if (mail_get_stream_because(mail, &hdr_size, &body_size,
+ "mail body", &input) < 0)
+ return -1;
+ result_r->size_field = MAIL_FETCH_MESSAGE_PARTS;
+ break;
+ }
+
+ if (msgpart->headers != NULL) {
+ /* return specific headers */
+ return imap_msgpart_get_partial_header(mail, input, msgpart,
+ virtual_size_r,
+ have_crlfs_r, result_r);
+ }
+
+ switch (msgpart->fetch_type) {
+ case FETCH_FULL:
+ part_size.physical_size += body_size.physical_size;
+ part_size.virtual_size += body_size.virtual_size;
+ /* fall through */
+ case FETCH_MIME:
+ case FETCH_HEADER:
+ part_size.physical_size += hdr_size.physical_size;
+ part_size.virtual_size += hdr_size.virtual_size;
+ break;
+ case FETCH_HEADER_FIELDS:
+ case FETCH_HEADER_FIELDS_NOT:
+ i_unreached();
+ case FETCH_BODY:
+ case FETCH_MIME_BODY:
+ i_stream_skip(input, hdr_size.physical_size);
+ part_size.physical_size += body_size.physical_size;
+ part_size.virtual_size += body_size.virtual_size;
+ break;
+ }
+
+ result_r->input = input;
+ i_stream_ref(input);
+ *virtual_size_r = part_size.virtual_size;
+ *have_crlfs_r = !unknown_crlfs &&
+ part_size.virtual_size == part_size.physical_size;
+ return 0;
+}
+
+int imap_msgpart_open(struct mail *mail, struct imap_msgpart *msgpart,
+ struct imap_msgpart_open_result *result_r)
+{
+ struct message_part *part;
+ uoff_t virtual_size;
+ bool include_hdr, binary, use_partial_cache, have_crlfs;
+ int ret;
+
+ i_zero(result_r);
+
+ if ((ret = imap_msgpart_find_part(mail, msgpart, &part)) < 0)
+ return -1;
+ if (ret == 0) {
+ /* MIME part not found. return an empty part. */
+ result_r->input = i_stream_create_from_data("", 0);
+ return 0;
+ }
+
+ if (msgpart->decode_cte_to_binary &&
+ (msgpart->fetch_type == FETCH_FULL ||
+ msgpart->fetch_type == FETCH_BODY ||
+ msgpart->fetch_type == FETCH_MIME_BODY)) {
+ /* binary fetch */
+ include_hdr = msgpart->fetch_type == FETCH_FULL;
+ if (part == NULL) {
+ if (mail_get_parts(mail, &part) < 0)
+ return -1;
+ }
+ if (mail_get_binary_stream(mail, part, include_hdr,
+ &virtual_size, &binary,
+ &result_r->input) < 0)
+ return -1;
+ have_crlfs = TRUE;
+ use_partial_cache = FALSE;
+ } else {
+ if (imap_msgpart_open_normal(mail, msgpart, part, &virtual_size,
+ &have_crlfs, result_r) < 0)
+ return -1;
+ binary = FALSE;
+ use_partial_cache = TRUE;
+ }
+
+ if (binary && msgpart->decode_cte_to_binary)
+ result_r->binary_decoded_input_has_nuls = TRUE;
+
+ imap_msgpart_get_partial(mail, msgpart, !binary, use_partial_cache,
+ virtual_size, have_crlfs, result_r);
+ return 0;
+}
+
+int imap_msgpart_size(struct mail *mail, struct imap_msgpart *msgpart,
+ uoff_t *size_r)
+{
+ struct imap_msgpart_open_result result;
+ struct message_part *part;
+ bool include_hdr;
+ unsigned int lines;
+ int ret;
+
+ if (!msgpart->decode_cte_to_binary ||
+ (msgpart->fetch_type != FETCH_FULL &&
+ msgpart->fetch_type != FETCH_BODY &&
+ msgpart->fetch_type != FETCH_MIME_BODY)) {
+ /* generic implementation */
+ if (imap_msgpart_open(mail, msgpart, &result) < 0)
+ return -1;
+ i_stream_unref(&result.input);
+ *size_r = result.size;
+ return 0;
+ }
+
+ /* binary-optimized implementation: */
+ if ((ret = imap_msgpart_find_part(mail, msgpart, &part)) < 0)
+ return -1;
+ if (ret == 0) {
+ /* MIME part not found. return an empty part. */
+ *size_r = 0;
+ return 0;
+ }
+ if (part == NULL) {
+ if (mail_get_parts(mail, &part) < 0)
+ return -1;
+ }
+ include_hdr = msgpart->fetch_type == FETCH_FULL;
+ return mail_get_binary_size(mail, part, include_hdr, size_r, &lines);
+}
+
+static int
+imap_msgpart_parse_bodystructure(struct mail *mail,
+ struct message_part *all_parts)
+{
+ struct mail_private *pmail = (struct mail_private *)mail;
+ const char *bodystructure, *error;
+
+ if (mail_get_special(mail, MAIL_FETCH_IMAP_BODYSTRUCTURE,
+ &bodystructure) < 0)
+ return -1;
+ if (all_parts->context != NULL) {
+ /* we just parsed the bodystructure */
+ return 0;
+ }
+
+ if (imap_bodystructure_parse(bodystructure, pmail->data_pool,
+ all_parts, &error) < 0) {
+ mail_set_cache_corrupted(mail,
+ MAIL_FETCH_IMAP_BODYSTRUCTURE, t_strdup_printf(
+ "Invalid message_part/BODYSTRUCTURE %s: %s",
+ bodystructure, error));
+ return -1;
+ }
+ return 0;
+}
+
+static int
+imap_msgpart_vsizes_to_binary(struct mail *mail, const struct message_part *part,
+ struct message_part **binpart_r)
+{
+ struct message_part **pos;
+ uoff_t size;
+ unsigned int lines;
+
+ if (mail_get_binary_size(mail, part, FALSE, &size, &lines) < 0)
+ return -1;
+
+ *binpart_r = t_new(struct message_part, 1);
+ **binpart_r = *part;
+ (*binpart_r)->body_size.virtual_size = size;
+ (*binpart_r)->body_size.lines = lines;
+
+ pos = &(*binpart_r)->children;
+ for (part = part->children; part != NULL; part = part->next) {
+ if (imap_msgpart_vsizes_to_binary(mail, part, pos) < 0)
+ return -1;
+ pos = &(*pos)->next;
+ }
+ return 0;
+}
+
+int imap_msgpart_bodypartstructure(struct mail *mail,
+ struct imap_msgpart *msgpart,
+ const char **bpstruct_r)
+{
+ struct message_part *all_parts, *part;
+ string_t *bpstruct;
+ const char *error;
+ int ret;
+
+ /* if we start parsing the body in here, make sure we also parse the
+ BODYSTRUCTURE */
+ mail_add_temp_wanted_fields(mail, MAIL_FETCH_IMAP_BODYSTRUCTURE, NULL);
+
+ if ((ret = imap_msgpart_find_part(mail, msgpart, &part)) < 0)
+ return -1;
+ if (ret == 0) {
+ /* MIME part not found. */
+ *bpstruct_r = NULL;
+ return 0;
+ }
+
+ if (mail_get_parts(mail, &all_parts) < 0)
+ return -1;
+ if (all_parts->context == NULL) {
+ if (imap_msgpart_parse_bodystructure(mail, all_parts) < 0)
+ return -1;
+ }
+ if (part == NULL)
+ part = all_parts;
+
+ if (msgpart->decode_cte_to_binary)
+ ret = imap_msgpart_vsizes_to_binary(mail, part, &part);
+
+ if (ret >= 0) {
+ bpstruct = t_str_new(256);
+ if (imap_bodystructure_write(part, bpstruct, TRUE, &error) < 0) {
+ error = t_strdup_printf(
+ "Invalid message_part/BODYSTRUCTURE: %s", error);
+ mail_set_cache_corrupted(mail, MAIL_FETCH_MESSAGE_PARTS,
+ error);
+ return -1;
+ }
+ *bpstruct_r = str_c(bpstruct);
+ }
+ return ret < 0 ? -1 : 1;
+}
+
+
+void imap_msgpart_close_mailbox(struct imap_msgpart *msgpart)
+{
+ mailbox_header_lookup_unref(&msgpart->header_ctx);
+}