]> git.ipfire.org Git - thirdparty/dovecot/core.git/commitdiff
lib-imap-storage: imap-msgpart rewrite and API change.
authorTimo Sirainen <tss@iki.fi>
Thu, 21 Jun 2012 18:50:35 +0000 (21:50 +0300)
committerTimo Sirainen <tss@iki.fi>
Thu, 21 Jun 2012 18:50:35 +0000 (21:50 +0300)
The new API allows first parsing the validity of section strings and later
relying on them being valid without having to re-parse it. The
implementation also fixes a few things and adds "partial fetch cache".

src/lib-imap-storage/Makefile.am
src/lib-imap-storage/imap-msgpart-url.c
src/lib-imap-storage/imap-msgpart.c
src/lib-imap-storage/imap-msgpart.h
src/lib-storage/mail-storage-private.h

index 9b7383c2555f623a12226da53359d7b2bfed07d7..573ba9d9cb3909d2638338f641a5f3cacccf12f1 100644 (file)
@@ -5,6 +5,7 @@ AM_CPPFLAGS = \
        -I$(top_srcdir)/src/lib-test \
        -I$(top_srcdir)/src/lib-charset \
        -I$(top_srcdir)/src/lib-mail \
+       -I$(top_srcdir)/src/lib-index \
        -I$(top_srcdir)/src/lib-storage \
        -I$(top_srcdir)/src/lib-imap
 
index 86ef162f50046638a5cd6dc4c2b9033935c8ea25..1d6228ff3a29727fdb4fe12e9aa16810c097b5b1 100644 (file)
@@ -1,3 +1,5 @@
+/* Copyright (c) 2012 Dovecot authors, see the included COPYING file */
+
 #include "lib.h"
 #include "network.h"
 #include "istream.h"
@@ -162,13 +164,32 @@ int imap_msgpart_url_open_mail(struct imap_msgpart_url *mpurl,
        return 1;
 }
 
+static int
+imap_msgpart_url_open_part(struct imap_msgpart_url *mpurl, struct mail **mail_r,
+                          struct imap_msgpart **msgpart_r, const char **error_r)
+{
+       int ret;
+
+       if ((ret = imap_msgpart_url_open_mail(mpurl, mail_r, error_r)) <= 0)
+               return ret;
+
+       if (imap_msgpart_parse((*mail_r)->box, mpurl->section, msgpart_r) < 0) {
+               *error_r = "Invalid section";
+               return 0;
+       }
+       imap_msgpart_set_partial(*msgpart_r, mpurl->partial_offset,
+                                mpurl->partial_size == 0 ? (uoff_t)-1 :
+                                mpurl->partial_size);
+       return 1;
+}
+
 int imap_msgpart_url_read_part(struct imap_msgpart_url *mpurl,
                               struct istream **stream_r, uoff_t *size_r,
                               const char **error_r)
 {
        struct mail *mail;
-       struct istream *input;
-       uoff_t part_size;
+       struct imap_msgpart *msgpart;
+       struct imap_msgpart_open_result result;
        int ret;
 
        if (mpurl->input != NULL) {
@@ -179,20 +200,20 @@ int imap_msgpart_url_read_part(struct imap_msgpart_url *mpurl,
        }
 
        /* open mail if it is not yet open */
-       if ((ret = imap_msgpart_url_open_mail(mpurl, &mail, error_r)) <= 0)
+       ret = imap_msgpart_url_open_part(mpurl, &mail, &msgpart, error_r);
+       if (ret <= 0)
                return ret;
 
        /* open the referenced part as a stream */
-       if ((ret = imap_msgpart_open(mail, mpurl->section,
-                                    mpurl->partial_offset, mpurl->partial_size,
-                                    &input, &part_size, error_r)) <= 0)
+       ret = imap_msgpart_open(mail, msgpart, &result);
+       imap_msgpart_free(&msgpart);
+       if (ret < 0) {
+               *error_r = mailbox_get_last_error(mail->box, NULL);
                return ret;
+       }
 
-       mpurl->input = input;
-       mpurl->part_size = part_size;
-
-       *stream_r = input;
-       *size_r = part_size;
+       *stream_r = mpurl->input = result.input;
+       *size_r = mpurl->part_size = result.size;
        return 1;
 }
 
@@ -200,17 +221,17 @@ int imap_msgpart_url_verify(struct imap_msgpart_url *mpurl,
                            const char **error_r)
 {
        struct mail *mail;
+       struct imap_msgpart *msgpart;
        int ret;
 
        if (mpurl->input != NULL)
                return 1;
 
        /* open mail if it is not yet open */
-       if ((ret = imap_msgpart_url_open_mail(mpurl, &mail, error_r)) <= 0)
-               return ret;
-
-       /* open the referenced part as a stream */
-       return imap_msgpart_verify(mail, mpurl->section, error_r);
+       ret = imap_msgpart_url_open_part(mpurl, &mail, &msgpart, error_r);
+       if (ret > 0)
+               imap_msgpart_free(&msgpart);
+       return ret;
 }
 
 void imap_msgpart_url_free(struct imap_msgpart_url **_mpurl)
index 5ff663389f1d762af1677b8436edc24ff5cbf41d..6fb3745b0194a05f2250b790d6b91373620208b2 100644 (file)
@@ -1,25 +1,89 @@
+/* Copyright (c) 2012 Dovecot authors, see the included COPYING file */
+
 #include "lib.h"
 #include "array.h"
 #include "istream.h"
 #include "istream-crlf.h"
+#include "istream-nonuls.h"
 #include "istream-header-filter.h"
 #include "message-parser.h"
-#include "mail-storage.h"
+#include "mail-storage-private.h"
 #include "mail-namespace.h"
 #include "imap-parser.h"
 #include "imap-msgpart.h"
 
-int imap_msgpart_find(struct mail *mail, const char *section,
-                     const struct message_part **part_r,
-                     const char **subsection_r)
+enum fetch_type {
+       FETCH_FULL,
+       FETCH_MIME,
+       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)-1) */
+       uoff_t partial_offset, partial_size;
+};
+
+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 message_part *part;
+       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)-1;
+       msgpart->fetch_type = fetch_type;
+       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;
 
-       if (mail_get_parts(mail, &part) < 0)
-               return -1;
-
        path = section;
        while (*path >= '0' && *path <= '9' && part != NULL) {
                /* get part number, we have already verified its validity */
@@ -53,21 +117,18 @@ int imap_msgpart_find(struct mail *mail, const char *section,
                        part = part->children;
                }
        }
-
-       *part_r = part;
-       *subsection_r = path;
-       return 0;
+       i_assert(part == NULL || *path == '\0');
+       return part;
 }
 
 static int
-imap_msgpart_get_header_fields(const char *header_list,
-                              const char *const **fields_r, size_t *count_r)
+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;
-       ARRAY_TYPE(const_string) fields = ARRAY_INIT;
        unsigned int i;
        int result = 0;
 
@@ -76,304 +137,452 @@ imap_msgpart_get_header_fields(const char *header_list,
 
        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;
                
-               if (fields_r != NULL)
-                       t_array_init(&fields, list_count);
-
+               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;
                        }
 
-                       if (fields_r != NULL) {
-                               value = t_str_ucase(value);
-                               array_append(&fields, &value, 1);
-                       }
-               }
-
-               if (fields_r != NULL) {
-                       *fields_r = array_get(&fields, &list_count);
-                       *count_r = list_count;
+                       value = p_strdup(pool, t_str_ucase(value));
+                       array_append(fields, &value, 1);
                }
        } else {
                result = -1;
        }
 
+       /* istream-header-filter requires headers to be sorted */
+       array_sort(fields, i_strcasecmp_p);
+
        imap_parser_unref(&parser);
        i_stream_unref(&input);
        return result;
 }
 
 static int
-imap_msgpart_verify_header_fields(const char *header_list, const char **error_r)
+imap_msgpart_parse_header_fields(struct mailbox *box,
+                                struct imap_msgpart *msgpart,
+                                const char *header_list)
 {
+       ARRAY_TYPE(const_string) fields;
+
        /* HEADER.FIELDS (list), HEADER.FIELDS.NOT (list) */
-       if (imap_msgpart_get_header_fields(header_list, NULL, NULL) < 0) {
-               *error_r = "Invalid header fields";
+       if (imap_msgpart_get_header_fields(msgpart->pool, header_list,
+                                          &fields) < 0)
+               return -1;
+
+       (void)array_append_space(&fields);
+       msgpart->headers = array_idx(&fields, 0);
+       msgpart->header_ctx = mailbox_header_lookup_init(box, msgpart->headers);
+       return 0;
+}
+
+int imap_msgpart_parse(struct mailbox *box, 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", 512);
+       msgpart = *msgpart_r = p_new(pool, struct imap_msgpart, 1);
+       msgpart->pool = pool;
+       msgpart->partial_size = (uoff_t)-1;
+
+       /* 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') {
+               /* full message/MIME part */
+               msgpart->fetch_type = FETCH_FULL;
+               msgpart->wanted_fields |= MAIL_FETCH_STREAM_BODY;
+               if (*msgpart->section_number == '\0')
+                       msgpart->wanted_fields |= MAIL_FETCH_STREAM_HEADER;
                return 0;
        }
-       return 1;
+       section = t_str_ucase(section);
+
+       if (strcmp(section, "MIME") == 0) {
+               msgpart->fetch_type = FETCH_MIME;
+               msgpart->wanted_fields |= MAIL_FETCH_STREAM_BODY;
+       } else if (strcmp(section, "TEXT") == 0) {
+               /* message body */
+               msgpart->fetch_type = FETCH_BODY;
+               msgpart->wanted_fields |= MAIL_FETCH_STREAM_BODY;
+       } else if (strncmp(section, "HEADER", 6) == 0) {
+               /* header */
+               if (section[6] == '\0') {
+                       msgpart->fetch_type = FETCH_HEADER;
+                       ret = 0;
+               } else if (strncmp(section, "HEADER.FIELDS ", 14) == 0) {
+                       msgpart->fetch_type = FETCH_HEADER_FIELDS;
+                       ret = imap_msgpart_parse_header_fields(box, msgpart,
+                                                              section+14);
+               } else if (strncmp(section, "HEADER.FIELDS.NOT ", 18) == 0) {
+                       msgpart->fetch_type = FETCH_HEADER_FIELDS_NOT;
+                       ret = imap_msgpart_parse_header_fields(box, msgpart,
+                                                              section+18);
+               } 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;
+       }
+       return 0;
 }
 
-static struct istream *
-imap_msgpart_get_partial_header(struct istream *mail_input, bool exclude,
-                               const char *header_list,
+void imap_msgpart_free(struct imap_msgpart **_msgpart)
+{
+       struct imap_msgpart *msgpart = *_msgpart;
+
+       *_msgpart = NULL;
+
+       if (msgpart->header_ctx != NULL)
+               mailbox_header_lookup_unref(&msgpart->header_ctx);
+       pool_unref(&msgpart->pool);
+}
+
+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;
+}
+
+enum mail_fetch_field imap_msgpart_get_fetch_data(struct imap_msgpart *msgpart)
+{
+       return msgpart->wanted_fields;
+}
+
+static int
+imap_msgpart_get_partial_header(struct mail *mail, struct istream *mail_input,
+                               const struct imap_msgpart *msgpart,
                                struct message_size *hdr_size_r,
-                               const char **error_r)
+                               struct imap_msgpart_open_result *result_r)
 {
-       const char *const *hdr_fields;
-       size_t hdr_count;
+       const char *const *hdr_fields = msgpart->headers;
+       unsigned int hdr_count = str_array_length(hdr_fields);
        struct istream *input;
-       uoff_t old_offset;
-
-       /* HEADER.FIELDS (list), HEADER.FIELDS.NOT (list) */
-       if (imap_msgpart_get_header_fields(header_list,
-                                          &hdr_fields, &hdr_count) < 0) {
-               *error_r = "Invalid header fields";
-               return NULL;
-       }
 
-       if (!exclude) {
+       if (msgpart->fetch_type == FETCH_HEADER_FIELDS) {
                input = i_stream_create_header_filter(mail_input,
-                                                     HEADER_FILTER_INCLUDE,
+                                                     HEADER_FILTER_INCLUDE |
+                                                     HEADER_FILTER_HIDE_BODY,
                                                      hdr_fields, hdr_count,
                                                      null_header_filter_callback, NULL);
        } else {
+               i_assert(msgpart->fetch_type == FETCH_HEADER_FIELDS_NOT);
                input = i_stream_create_header_filter(mail_input,
-                                                     HEADER_FILTER_EXCLUDE,
+                                                     HEADER_FILTER_EXCLUDE |
+                                                     HEADER_FILTER_HIDE_BODY,
                                                      hdr_fields, hdr_count,
                                                      null_header_filter_callback, NULL);
        }
 
-       old_offset = input->v_offset;
        if (message_get_header_size(input, hdr_size_r, NULL) < 0) {
-               *error_r = "Failed to determine header size";
-               return NULL;
+               errno = input->stream_errno;
+               mail_storage_set_critical(mail->box->storage,
+                       "read(%s) failed: %m", i_stream_get_name(mail_input));
+               i_stream_unref(&input);
+               return -1;
        }
-       i_stream_seek(input, old_offset);
-
-       return input;
+       i_stream_seek(input, 0);
+       result_r->input = input;
+       result_r->size = hdr_size_r->virtual_size;
+       return 0;
 }
 
 static void
-imap_msgpart_get_partial(struct istream **input, struct message_size part_size,
-                        uoff_t partial_offset, uoff_t partial_size,
-                        struct istream **stream_r, uoff_t *size_r)
+skip_using_parts(struct mail *mail, struct istream *input,
+                uoff_t *virtual_skip)
 {
-       struct istream *result;
-       uoff_t size = part_size.virtual_size;
-
-       if (partial_offset >= size) {
-               i_stream_unref(input);
-               *size_r = 0;
-               *stream_r = NULL;
+       enum mail_lookup_abort old_lookup_abort;
+       struct message_part *parts, *part;
+       uoff_t vpos;
+       int ret;
+
+       old_lookup_abort = mail->lookup_abort;
+       mail->lookup_abort = MAIL_LOOKUP_ABORT_NOT_IN_CACHE;
+       ret = mail_get_parts(mail, &parts);
+       mail->lookup_abort = old_lookup_abort;
+       if (ret < 0)
                return;
-       }
 
-       if (size != part_size.physical_size) {
-               result = i_stream_create_crlf(*input);
-               i_stream_unref(input);
-               *input = result;
+       for (part = parts, vpos = 0; part != NULL; ) {
+               if (vpos + part->header_size.virtual_size > *virtual_skip)
+                       break;
+               /* skip header */
+               vpos += part->header_size.virtual_size;
+               *virtual_skip -= part->header_size.virtual_size;
+               i_stream_seek(input, part->physical_pos +
+                             part->header_size.physical_size);
+
+               if (vpos + part->body_size.virtual_size <= *virtual_skip) {
+                       /* skip body */
+                       vpos += part->body_size.virtual_size;
+                       *virtual_skip -= part->body_size.virtual_size;
+                       i_stream_seek(input, part->physical_pos +
+                                     part->header_size.physical_size +
+                                     part->body_size.physical_size);
+                       part = part->next;
+               } else {
+                       /* maybe we have a child and can skip using it? */
+                       part = part->children;
+               }
        }
-
-       if (partial_offset > 0)
-               i_stream_seek(*input, (*input)->v_offset + partial_offset);
-
-       size = partial_size > 0 && (size - partial_offset) > partial_size ?
-               partial_size : (size - partial_offset);
-       result = i_stream_create_limit(*input, size);
-       i_stream_unref(input);
-       
-       *size_r = size;
-       *stream_r = result;
 }
 
-int imap_msgpart_open(struct mail *mail, const char *section,
-                     uoff_t partial_offset, uoff_t partial_size,
-                     struct istream **stream_r,
-                     uoff_t *size_r, const char **error_r)
+static struct istream *
+imap_msgpart_crlf_seek(struct mail *mail, struct istream *input,
+                      const struct imap_msgpart *msgpart)
 {
-       struct message_size hdr_size, body_size, part_size;
-       struct istream *input = NULL;
-
-       /* only get stream when we intend to read actual data */
-       if (stream_r != NULL) {
-               if (mail_get_stream(mail, &hdr_size, &body_size, &input) < 0) {
-                       *error_r = "Failed to read message";
-                       return -1;
+       struct mail_msgpart_partial_cache *cache = &mail->box->partial_cache;
+       struct istream *nul_input, *crlf_input;
+       const unsigned char *data;
+       size_t size;
+       uoff_t physical_start = input->v_offset;
+       uoff_t virtual_skip = msgpart->partial_offset;
+
+       i_assert(msgpart->headers == NULL); /* HEADER.FIELDS returns CRLFs */
+
+       if (virtual_skip == 0) {
+               /* no need to seek */
+       } else if (cache->uid == mail->uid &&
+                  cache->physical_start == physical_start &&
+                  cache->virtual_pos < virtual_skip) {
+               /* use cache */
+               i_stream_seek(input, cache->physical_pos);
+               virtual_skip -= cache->virtual_pos;
+       } else {
+               /* can't use cache, but maybe we can skip faster using the
+                  message parts. */
+               skip_using_parts(mail, input, &virtual_skip);
+       }
+       if (!mail->has_no_nuls) {
+               nul_input = i_stream_create_nonuls(input, 0x80);
+               i_stream_unref(&input);
+               input = nul_input;
+       }
+       crlf_input = i_stream_create_crlf(input);
+       i_stream_unref(&input);
+       input = crlf_input;
+       i_stream_skip(input, virtual_skip);
+
+       if ((msgpart->partial_offset != 0 ||
+            msgpart->partial_size != (uoff_t)-1) &&
+           i_stream_read_data(input, &data, &size, 0) > 0) {
+               /* update cache */
+               cache->uid = mail->uid;
+               cache->physical_start = physical_start;
+               cache->physical_pos = input->v_offset;
+               cache->virtual_pos = msgpart->partial_offset;
+               if (data[0] == '\n') {
+                       /* 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--;
                }
        }
+       return input;
+}
 
-       if (section == NULL || *section == '\0') {
-               /* full message */
-               if (stream_r == NULL)
-                       return 1;
-
-               part_size.physical_size =
-                       hdr_size.physical_size + body_size.physical_size;
-               part_size.virtual_size =
-                       hdr_size.virtual_size + body_size.virtual_size;
-
-               i_stream_seek(input, 0);
-               i_stream_ref(input);
-               imap_msgpart_get_partial(&input, part_size,
-                                        partial_offset, partial_size,
-                                        stream_r, size_r);
-               return 1;
+static void
+imap_msgpart_get_partial(struct mail *mail, const struct imap_msgpart *msgpart,
+                        const struct message_size *part_size,
+                        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 >= part_size->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;
        }
 
-       section = t_str_ucase(section);
+       if (part_size->virtual_size == part_size->physical_size) {
+               /* 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 */
+               result->input = imap_msgpart_crlf_seek(mail, result->input,
+                                                      msgpart);
+       }
 
-       if (strcmp(section, "TEXT") == 0) {
-               /* message body */
-               if (stream_r == NULL)
-                       return 1;
-
-               i_stream_seek(input, hdr_size.physical_size);
-               i_stream_ref(input);
-               imap_msgpart_get_partial(&input, body_size,
-                                        partial_offset, partial_size,
-                                        stream_r, size_r);
-               return 1;
+       bytes_left = part_size->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;
        }
+       input2 = i_stream_create_limit(result->input, result->size);
+       i_stream_unref(&result->input);
+       result->input = input2;
+}
 
-       if (strncmp(section, "HEADER", 6) == 0) {
-               /* header */
-               if (stream_r == NULL) {
-                       if (section[6] == '\0') {
-                               return 1;
-                       } else if (strncmp(section, "HEADER.FIELDS ", 14) == 0) {
-                               return imap_msgpart_verify_header_fields(section+14, error_r);
-                       } else if (strncmp(section, "HEADER.FIELDS.NOT ", 18) == 0) {
-                               return imap_msgpart_verify_header_fields(section+18, error_r);
-                       }
-               } else {
-                       i_stream_seek(input, 0);
-
-                       if (section[6] == '\0') {
-                               i_stream_ref(input);
-                       } else if (strncmp(section, "HEADER.FIELDS ", 14) == 0) {
-                               input = imap_msgpart_get_partial_header(input,
-                                       FALSE, section+14, &hdr_size, error_r);
-                       } else if (strncmp(section, "HEADER.FIELDS.NOT ", 18) == 0) {
-                               input = imap_msgpart_get_partial_header(input,
-                                       TRUE, section+18, &hdr_size, error_r);
-                       } else {
-                               input = NULL;
-                       }
+int imap_msgpart_open(struct mail *mail, struct imap_msgpart *msgpart,
+                     struct imap_msgpart_open_result *result_r)
+{
+       struct message_part *parts, *part = NULL;
+       struct message_size hdr_size, body_size, part_size;
+       struct istream *input = NULL;
+       uoff_t physical_pos = 0;
 
-                       if (input != NULL) {
-                               imap_msgpart_get_partial(&input, hdr_size,
-                                       partial_offset, partial_size,
-                                       stream_r, size_r);
-                               return 1;
-                       }
-               }
-       } else if (*section >= '0' && *section <= '9') {
-               const struct message_part *part;
-               const char *subsection;
+       memset(result_r, 0, sizeof(*result_r));
+       memset(&hdr_size, 0, sizeof(hdr_size));
+       memset(&body_size, 0, sizeof(body_size));
+       memset(&part_size, 0, sizeof(part_size));
 
-               if (imap_msgpart_find(mail, section, &part, &subsection) < 0) {
-                       *error_r = "Cannot read message part";
+       if (*msgpart->section_number != '\0') {
+               /* find the MIME part */
+               if (mail_get_parts(mail, &parts) < 0)
                        return -1;
+               part = imap_msgpart_find(parts, msgpart->section_number);
+               if (part != NULL && (msgpart->fetch_type == FETCH_HEADER ||
+                                    msgpart->fetch_type == FETCH_BODY)) {
+                       /* fetching message/rfc822 part's header/body */
+                       if ((part->flags & MESSAGE_PART_FLAG_MESSAGE_RFC822) == 0)
+                               part = NULL;
+                       else {
+                               i_assert(part->children != NULL &&
+                                        part->children->next == NULL);
+                               part = part->children;
+                       }
                }
-
                if (part == NULL) {
-                       *error_r = "Unknown message part";
-                       return 0;
-               }
-
-               if (*subsection == '\0') {
-                       if (stream_r == NULL)
-                               return 1;
-               
-                       /* fetch the whole section */
-                       i_stream_seek(input, part->physical_pos +
-                                     part->header_size.physical_size);
-                       i_stream_ref(input);
-                       imap_msgpart_get_partial(&input, part->body_size,
-                                                partial_offset, partial_size,
-                                                stream_r, size_r);
-                       return 1;
-               }
-
-               if (strcmp(subsection, "MIME") == 0) {
-                       if (stream_r == NULL)
-                               return 1;
-
-                       /* fetch section's MIME header */
-                       i_stream_seek(input, part->physical_pos);
-                       i_stream_ref(input);
-                       imap_msgpart_get_partial(&input, part->header_size,
-                                                partial_offset, partial_size,
-                                                stream_r, size_r);
-                       return 1;
-               }
-
-               /* TEXT and HEADER are only for message/rfc822 parts */
-               if ((part->flags & MESSAGE_PART_FLAG_MESSAGE_RFC822) == 0) {
-                       *error_r = "Invalid section";
+                       /* MIME part not found. return an empty stream. */
+                       result_r->input = i_stream_create_from_data("", 0);
                        return 0;
                }
+               if (mail_get_stream(mail, NULL, NULL, &input) < 0)
+                       return -1;
 
-               i_assert(part->children != NULL && part->children->next == NULL);
-               part = part->children;
+               physical_pos = 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(mail, NULL, NULL, &input) < 0 ||
+                   mail_get_virtual_size(mail, &body_size.virtual_size) < 0 ||
+                   mail_get_physical_size(mail, &body_size.physical_size) < 0)
+                       return -1;
+               result_r->size_field = MAIL_FETCH_VIRTUAL_SIZE;
+               break;
+       case FETCH_MIME:
+               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 */
+               i_assert(msgpart->header_ctx != NULL);
+               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(mail, &hdr_size, &body_size, &input) < 0)
+                       return -1;
+               result_r->size_field = MAIL_FETCH_MESSAGE_PARTS;
+               break;
+       }
+       i_stream_seek(input, physical_pos);
 
-               if (strcmp(subsection, "TEXT") == 0) {
-                       if (stream_r == NULL)
-                               return 1;
+       if (msgpart->headers != NULL) {
+               /* return specific headers */
+               if (imap_msgpart_get_partial_header(mail, input, msgpart,
+                                                   &hdr_size, result_r) < 0)
+                       return -1;
+               imap_msgpart_get_partial(mail, msgpart, &hdr_size, result_r);
+               return 0;
+       }
 
-                       /* sub-message body */
-                       i_stream_seek(input, part->physical_pos +
-                                     part->header_size.physical_size);
-                       i_stream_ref(input);
-                       imap_msgpart_get_partial(&input, part->body_size,
-                                                partial_offset, partial_size,
-                                                stream_r, size_r);
-                       return 1;
-               }       
-
-               if (strncmp(subsection, "HEADER", 6) == 0) {
-                       if (stream_r == NULL) {
-                               if (section[6] == '\0') {
-                                       return 1;
-                               } else if (strncmp(subsection, "HEADER.FIELDS ", 14) == 0) {
-                                       return imap_msgpart_verify_header_fields(subsection+14, error_r);
-                               } else if (strncmp(subsection, "HEADER.FIELDS.NOT ", 18) == 0) {
-                                       return imap_msgpart_verify_header_fields(subsection+18, error_r);
-                               }
-                       } else {
-                               i_stream_seek(input, part->physical_pos);
-
-                               if (subsection[6] == '\0') {
-                                       /* full */
-                                       hdr_size = part->header_size;
-                                       i_stream_ref(input);
-                               } else if (strncmp(subsection, "HEADER.FIELDS ", 14) == 0) {
-                                       input = imap_msgpart_get_partial_header(
-                                                       input, FALSE, section+14,
-                                                       &hdr_size, error_r);
-                               } else if (strncmp(subsection, "HEADER.FIELDS.NOT ", 18) == 0) {
-                                       input = imap_msgpart_get_partial_header(
-                                                       input, TRUE, section+18,
-                                                       &hdr_size, error_r);
-                               } else {
-                                       input = NULL;
-                               }
-
-                               if (input != NULL) {
-                                       imap_msgpart_get_partial(&input, hdr_size,
-                                               partial_offset, partial_size,
-                                               stream_r, size_r);
-                                       return 1;
-                               }
-                       }
-               }
+       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:
+               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;
        }
 
-       *error_r = "Invalid section";
+       result_r->input = input;
+       i_stream_ref(input);
+       imap_msgpart_get_partial(mail, msgpart, &part_size, result_r);
        return 0;
 }
index 03e4b162f02f7252c6abcef53086a427adf5a703..9d4d65101691ca9653df89529a3ab76dce75a6ce 100644 (file)
@@ -1,25 +1,35 @@
 #ifndef IMAP_MSGPART_H
 #define IMAP_MSGPART_H
 
-/* Find message_part for section (eg. 1.3.4). Returns -1 if storage error,
-   0 otherwise. part_r is set to NULL if section doesn't exist. */
-int imap_msgpart_find(struct mail *mail, const char *section,
-                     const struct message_part **part_r,
-                     const char **subsection_r);
+struct imap_msgpart;
 
-/* Open message part refenced by IMAP section as istream. Returns 1 if
-   successful, 0 if section is invalid, -1 if storage error. Returned stream_r
-   stream may be NULL when there is no data to return. */
-int imap_msgpart_open(struct mail *mail, const char *section,
-                     uoff_t partial_offset, uoff_t partial_size,
-                     struct istream **stream_r,
-                     uoff_t *size_r, const char **error_r);
+struct imap_msgpart_open_result {
+       struct istream *input;
+       uoff_t size;
+       enum mail_fetch_field size_field;
+};
 
-static inline int
-imap_msgpart_verify(struct mail *mail, const char *section,
-                   const char **error_r)
-{
-       return imap_msgpart_open(mail, section, 0, 0, NULL, NULL, error_r);
-}
+struct imap_msgpart *imap_msgpart_full(void);
+struct imap_msgpart *imap_msgpart_header(void);
+struct imap_msgpart *imap_msgpart_body(void);
+/* Parse section into imap_msgpart. Returns 0 and msgpart_r on success,
+   -1 if the section isn't valid. The same imap_msgpart can be used to open
+   multiple messages. */
+int imap_msgpart_parse(struct mailbox *box, const char *section,
+                      struct imap_msgpart **msgpart_r);
+void imap_msgpart_free(struct imap_msgpart **msgpart);
+
+/* Set the fetch to be partial. For unlimited size use (uoff_t)-1. */
+void imap_msgpart_set_partial(struct imap_msgpart *msgpart,
+                             uoff_t offset, uoff_t size);
+uoff_t imap_msgpart_get_partial_offset(struct imap_msgpart *msgpart);
+/* Return wanted_fields mask. */
+enum mail_fetch_field imap_msgpart_get_fetch_data(struct imap_msgpart *msgpart);
+
+/* Open message part refenced by IMAP section as istream. Returns 0 if
+   successful, -1 if storage error. Returned istream is initially referenced,
+   so i_stream_unref() must be called for it. */
+int imap_msgpart_open(struct mail *mail, struct imap_msgpart *msgpart,
+                     struct imap_msgpart_open_result *result_r);
 
 #endif
index 930da277dc62c028c7d8111eafa2b1497ce2714e..0f779320a7a97ffcdfec18404417a8a53e98264b 100644 (file)
@@ -203,6 +203,12 @@ union mailbox_module_context {
        struct mail_storage_module_register *reg;
 };
 
+struct mail_msgpart_partial_cache {
+       uint32_t uid;
+       uoff_t physical_start;
+       uoff_t physical_pos, virtual_pos;
+};
+
 struct mailbox {
        const char *name;
        /* mailbox's virtual name (from mail_namespace_get_vname()) */
@@ -244,6 +250,7 @@ struct mailbox {
        enum mailbox_flags flags;
        unsigned int transaction_count;
        enum mailbox_feature enabled_features;
+       struct mail_msgpart_partial_cache partial_cache;
 
        struct mail_index_view *tmp_sync_view;