summaryrefslogtreecommitdiffstats
path: root/src/lib-storage/index/imapc/imapc-mail-fetch.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib-storage/index/imapc/imapc-mail-fetch.c')
-rw-r--r--src/lib-storage/index/imapc/imapc-mail-fetch.c911
1 files changed, 911 insertions, 0 deletions
diff --git a/src/lib-storage/index/imapc/imapc-mail-fetch.c b/src/lib-storage/index/imapc/imapc-mail-fetch.c
new file mode 100644
index 0000000..1b0eee7
--- /dev/null
+++ b/src/lib-storage/index/imapc/imapc-mail-fetch.c
@@ -0,0 +1,911 @@
+/* Copyright (c) 2011-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "str.h"
+#include "ioloop.h"
+#include "istream.h"
+#include "istream-concat.h"
+#include "istream-header-filter.h"
+#include "message-header-parser.h"
+#include "imap-arg.h"
+#include "imap-util.h"
+#include "imap-date.h"
+#include "imap-quote.h"
+#include "imap-bodystructure.h"
+#include "imap-resp-code.h"
+#include "imapc-mail.h"
+#include "imapc-storage.h"
+
+static void imapc_mail_set_failure(struct imapc_mail *mail,
+ const struct imapc_command_reply *reply)
+{
+ struct imapc_mailbox *mbox = IMAPC_MAILBOX(mail->imail.mail.mail.box);
+
+ mail->last_fetch_reply = p_strdup(mail->imail.mail.pool, reply->text_full);
+
+ switch (reply->state) {
+ case IMAPC_COMMAND_STATE_OK:
+ break;
+ case IMAPC_COMMAND_STATE_NO:
+ if (!IMAPC_BOX_HAS_FEATURE(mbox, IMAPC_FEATURE_FETCH_FIX_BROKEN_MAILS)) {
+ /* fetch-fix-broken-mails feature disabled -
+ fail any mails with missing replies */
+ break;
+ }
+ if (reply->resp_text_key != NULL &&
+ (strcasecmp(reply->resp_text_key, IMAP_RESP_CODE_SERVERBUG) == 0 ||
+ strcasecmp(reply->resp_text_key, IMAP_RESP_CODE_LIMIT) == 0)) {
+ /* this is a temporary error, retrying should work.
+ Yahoo sends * BYE +
+ NO [LIMIT] UID FETCH Rate limit hit. */
+ } else {
+ /* hopefully this is a permanent failure */
+ mail->fetch_ignore_if_missing = TRUE;
+ }
+ break;
+ case IMAPC_COMMAND_STATE_BAD:
+ case IMAPC_COMMAND_STATE_DISCONNECTED:
+ case IMAPC_COMMAND_STATE_AUTH_FAILED:
+ mail->fetch_failed = TRUE;
+ break;
+ }
+}
+
+static void
+imapc_mail_fetch_callback(const struct imapc_command_reply *reply,
+ void *context)
+{
+ struct imapc_fetch_request *request = context;
+ struct imapc_fetch_request *const *requests;
+ struct imapc_mail *mail;
+ struct imapc_mailbox *mbox = NULL;
+ unsigned int i, count;
+
+ array_foreach_elem(&request->mails, mail) {
+ i_assert(mail->fetch_count > 0);
+ imapc_mail_set_failure(mail, reply);
+ if (--mail->fetch_count == 0)
+ mail->fetching_fields = 0;
+ mbox = IMAPC_MAILBOX(mail->imail.mail.mail.box);
+ }
+ i_assert(mbox != NULL);
+
+ requests = array_get(&mbox->fetch_requests, &count);
+ for (i = 0; i < count; i++) {
+ if (requests[i] == request) {
+ array_delete(&mbox->fetch_requests, i, 1);
+ break;
+ }
+ }
+ i_assert(i < count);
+
+ array_free(&request->mails);
+ i_free(request);
+
+ if (reply->state == IMAPC_COMMAND_STATE_OK)
+ ;
+ else if (reply->state == IMAPC_COMMAND_STATE_NO) {
+ imapc_copy_error_from_reply(mbox->storage, MAIL_ERROR_PARAMS,
+ reply);
+ } else if (reply->state == IMAPC_COMMAND_STATE_DISCONNECTED) {
+ /* The disconnection message was already logged */
+ mail_storage_set_internal_error(&mbox->storage->storage);
+ } else {
+ mailbox_set_critical(&mbox->box,
+ "imapc: Mail FETCH failed: %s", reply->text_full);
+ }
+ imapc_client_stop(mbox->storage->client->client);
+}
+
+static bool
+headers_have_subset(const char *const *superset, const char *const *subset)
+{
+ unsigned int i;
+
+ if (superset == NULL)
+ return FALSE;
+ if (subset != NULL) {
+ for (i = 0; subset[i] != NULL; i++) {
+ if (!str_array_icase_find(superset, subset[i]))
+ return FALSE;
+ }
+ }
+ return TRUE;
+}
+
+static const char *const *
+headers_merge(pool_t pool, const char *const *h1, const char *const *h2)
+{
+ ARRAY_TYPE(const_string) headers;
+ const char *value;
+ unsigned int i;
+
+ p_array_init(&headers, pool, 16);
+ if (h1 != NULL) {
+ for (i = 0; h1[i] != NULL; i++) {
+ value = p_strdup(pool, h1[i]);
+ array_push_back(&headers, &value);
+ }
+ }
+ if (h2 != NULL) {
+ for (i = 0; h2[i] != NULL; i++) {
+ if (h1 == NULL || !str_array_icase_find(h1, h2[i])) {
+ value = p_strdup(pool, h2[i]);
+ array_push_back(&headers, &value);
+ }
+ }
+ }
+ array_append_zero(&headers);
+ return array_front(&headers);
+}
+
+static bool
+imapc_mail_try_merge_fetch(struct imapc_mailbox *mbox, string_t *str)
+{
+ const char *s1 = str_c(str);
+ const char *s2 = str_c(mbox->pending_fetch_cmd);
+ const char *p1, *p2;
+
+ i_assert(str_begins(s1, "UID FETCH "));
+ i_assert(str_begins(s2, "UID FETCH "));
+
+ /* skip over UID range */
+ p1 = strchr(s1+10, ' ');
+ p2 = strchr(s2+10, ' ');
+
+ if (null_strcmp(p1, p2) != 0)
+ return FALSE;
+ /* append the new UID to the pending FETCH UID range */
+ str_truncate(str, p1-s1);
+ str_insert(mbox->pending_fetch_cmd, p2-s2, ",");
+ str_insert(mbox->pending_fetch_cmd, p2-s2+1, str_c(str) + 10);
+ return TRUE;
+}
+
+static void
+imapc_mail_delayed_send_or_merge(struct imapc_mail *mail, string_t *str)
+{
+ struct imapc_mailbox *mbox = IMAPC_MAILBOX(mail->imail.mail.mail.box);
+
+ if (mbox->pending_fetch_request != NULL &&
+ !imapc_mail_try_merge_fetch(mbox, str)) {
+ /* send the previous FETCH and create a new one */
+ imapc_mail_fetch_flush(mbox);
+ }
+ if (mbox->pending_fetch_request == NULL) {
+ mbox->pending_fetch_request =
+ i_new(struct imapc_fetch_request, 1);
+ i_array_init(&mbox->pending_fetch_request->mails, 4);
+ i_assert(mbox->pending_fetch_cmd->used == 0);
+ str_append_str(mbox->pending_fetch_cmd, str);
+ }
+ array_push_back(&mbox->pending_fetch_request->mails, &mail);
+
+ if (mbox->to_pending_fetch_send == NULL &&
+ array_count(&mbox->pending_fetch_request->mails) >
+ mbox->box.storage->set->mail_prefetch_count) {
+ /* we're now prefetching the maximum number of mails. this
+ most likely means that we need to flush out the command now
+ before sending anything else. delay it a little bit though
+ in case the sending code doesn't actually use
+ mail_prefetch_count and wants to fetch more.
+
+ note that we don't want to add this timeout too early,
+ because we want to optimize the maximum number of messages
+ placed into a single FETCH. even without timeout the command
+ gets flushed by imapc_mail_fetch() call. */
+ mbox->to_pending_fetch_send =
+ timeout_add_short(0, imapc_mail_fetch_flush, mbox);
+ }
+}
+
+static int
+imapc_mail_send_fetch(struct mail *_mail, enum mail_fetch_field fields,
+ const char *const *headers)
+{
+ struct imapc_mail *mail = IMAPC_MAIL(_mail);
+ struct imapc_mailbox *mbox = IMAPC_MAILBOX(_mail->box);
+ struct mail_index_view *view;
+ string_t *str;
+ uint32_t seq;
+ unsigned int i;
+
+ i_assert(headers == NULL ||
+ IMAPC_BOX_HAS_FEATURE(mbox, IMAPC_FEATURE_FETCH_HEADERS));
+
+ if (!mbox->selected) {
+ mail_storage_set_error(_mail->box->storage,
+ MAIL_ERROR_NOTPOSSIBLE, "Can't fetch mails before selecting mailbox");
+ return -1;
+ }
+
+ if (!mail_stream_access_start(_mail))
+ return -1;
+
+ /* drop any fields that we may already be fetching currently */
+ fields &= ENUM_NEGATE(mail->fetching_fields);
+ if (headers_have_subset(mail->fetching_headers, headers))
+ headers = NULL;
+ if (fields == 0 && headers == NULL)
+ return mail->fetch_sent ? 0 : 1;
+
+ if (!_mail->saving) {
+ /* if we already know that the mail is expunged,
+ don't try to FETCH it */
+ view = mbox->delayed_sync_view != NULL ?
+ mbox->delayed_sync_view : mbox->box.view;
+ if (!mail_index_lookup_seq(view, _mail->uid, &seq) ||
+ mail_index_is_expunged(view, seq)) {
+ mail_set_expunged(_mail);
+ return -1;
+ }
+ } else if (mbox->client_box == NULL) {
+ /* opened as save-only. we'll need to fetch the mail,
+ so actually SELECT/EXAMINE the mailbox */
+ i_assert(mbox->box.opened);
+
+ if (imapc_mailbox_select(mbox) < 0)
+ return -1;
+ }
+
+ if ((fields & MAIL_FETCH_STREAM_BODY) != 0)
+ fields |= MAIL_FETCH_STREAM_HEADER;
+
+ str = t_str_new(64);
+ str_printfa(str, "UID FETCH %u (", _mail->uid);
+ if ((fields & MAIL_FETCH_RECEIVED_DATE) != 0)
+ str_append(str, "INTERNALDATE ");
+ if ((fields & MAIL_FETCH_SAVE_DATE) != 0) {
+ i_assert(HAS_ALL_BITS(mbox->capabilities,
+ IMAPC_CAPABILITY_SAVEDATE));
+ str_append(str, "SAVEDATE ");
+ }
+ if ((fields & (MAIL_FETCH_PHYSICAL_SIZE | MAIL_FETCH_VIRTUAL_SIZE)) != 0)
+ str_append(str, "RFC822.SIZE ");
+ if ((fields & MAIL_FETCH_GUID) != 0) {
+ str_append(str, mbox->guid_fetch_field_name);
+ str_append_c(str, ' ');
+ }
+ if ((fields & MAIL_FETCH_IMAP_BODY) != 0)
+ str_append(str, "BODY ");
+ if ((fields & MAIL_FETCH_IMAP_BODYSTRUCTURE) != 0)
+ str_append(str, "BODYSTRUCTURE ");
+
+ if ((fields & MAIL_FETCH_STREAM_BODY) != 0) {
+ if (!IMAPC_BOX_HAS_FEATURE(mbox, IMAPC_FEATURE_ZIMBRA_WORKAROUNDS))
+ str_append(str, "BODY.PEEK[] ");
+ else {
+ /* BODY.PEEK[] can return different headers than
+ BODY.PEEK[HEADER] (e.g. invalid 8bit chars replaced
+ with '?' in HEADER) - this violates IMAP protocol
+ and messes up dsync since it sometimes fetches the
+ full body and sometimes only the headers. */
+ str_append(str, "BODY.PEEK[HEADER] BODY.PEEK[TEXT] ");
+ }
+ fields |= MAIL_FETCH_STREAM_HEADER;
+ } else if ((fields & MAIL_FETCH_STREAM_HEADER) != 0)
+ str_append(str, "BODY.PEEK[HEADER] ");
+ else if (headers != NULL) {
+ mail->fetching_headers =
+ headers_merge(mail->imail.mail.data_pool, headers,
+ mail->fetching_headers);
+ str_append(str, "BODY.PEEK[HEADER.FIELDS (");
+ for (i = 0; mail->fetching_headers[i] != NULL; i++) {
+ if (i > 0)
+ str_append_c(str, ' ');
+ imap_append_astring(str, mail->fetching_headers[i]);
+ }
+ str_append(str, ")] ");
+ mail->header_list_fetched = FALSE;
+ }
+ str_truncate(str, str_len(str)-1);
+ str_append_c(str, ')');
+
+ mail->fetching_fields |= fields;
+ mail->fetch_count++;
+ mail->fetch_sent = FALSE;
+ mail->fetch_failed = FALSE;
+
+ imapc_mail_delayed_send_or_merge(mail, str);
+ return 1;
+}
+
+static void imapc_mail_cache_get(struct imapc_mail *mail,
+ struct imapc_mail_cache *cache)
+{
+ if (mail->body_fetched)
+ return;
+
+ if (cache->fd != -1) {
+ mail->fd = cache->fd;
+ mail->imail.data.stream = i_stream_create_fd(mail->fd, 0);
+ cache->fd = -1;
+ } else if (cache->buf != NULL) {
+ mail->body = cache->buf;
+ mail->imail.data.stream =
+ i_stream_create_from_data(mail->body->data,
+ mail->body->used);
+ cache->buf = NULL;
+ } else {
+ return;
+ }
+ mail->header_fetched = TRUE;
+ mail->body_fetched = TRUE;
+ /* The stream was already accessed and now it's cached.
+ It still needs to be set accessed to avoid assert-crash. */
+ mail->imail.mail.mail.mail_stream_accessed = TRUE;
+ imapc_mail_init_stream(mail);
+}
+
+static enum mail_fetch_field
+imapc_mail_get_wanted_fetch_fields(struct imapc_mail *mail)
+{
+ struct imapc_mailbox *mbox = IMAPC_MAILBOX(mail->imail.mail.mail.box);
+ struct index_mail_data *data = &mail->imail.data;
+ enum mail_fetch_field fields = 0;
+
+ if ((data->wanted_fields & MAIL_FETCH_RECEIVED_DATE) != 0 &&
+ data->received_date == (time_t)-1)
+ fields |= MAIL_FETCH_RECEIVED_DATE;
+ if ((data->wanted_fields & MAIL_FETCH_SAVE_DATE) != 0 &&
+ data->save_date == (time_t)-1) {
+ if (HAS_ALL_BITS(mbox->capabilities, IMAPC_CAPABILITY_SAVEDATE))
+ fields |= MAIL_FETCH_SAVE_DATE;
+ else
+ fields |= MAIL_FETCH_RECEIVED_DATE;
+ }
+ if ((data->wanted_fields & (MAIL_FETCH_PHYSICAL_SIZE |
+ MAIL_FETCH_VIRTUAL_SIZE)) != 0 &&
+ data->physical_size == UOFF_T_MAX &&
+ IMAPC_BOX_HAS_FEATURE(mbox, IMAPC_FEATURE_RFC822_SIZE))
+ fields |= MAIL_FETCH_PHYSICAL_SIZE | MAIL_FETCH_VIRTUAL_SIZE;
+ if ((data->wanted_fields & MAIL_FETCH_IMAP_BODY) != 0 &&
+ data->body == NULL &&
+ IMAPC_BOX_HAS_FEATURE(mbox, IMAPC_FEATURE_FETCH_BODYSTRUCTURE))
+ fields |= MAIL_FETCH_IMAP_BODY;
+ if ((data->wanted_fields & MAIL_FETCH_IMAP_BODYSTRUCTURE) != 0 &&
+ data->bodystructure == NULL &&
+ IMAPC_BOX_HAS_FEATURE(mbox, IMAPC_FEATURE_FETCH_BODYSTRUCTURE))
+ fields |= MAIL_FETCH_IMAP_BODYSTRUCTURE;
+ if ((data->wanted_fields & MAIL_FETCH_GUID) != 0 &&
+ data->guid == NULL && mbox->guid_fetch_field_name != NULL)
+ fields |= MAIL_FETCH_GUID;
+
+ if (data->stream == NULL && data->access_part != 0) {
+ if ((data->access_part & (READ_BODY | PARSE_BODY)) != 0)
+ fields |= MAIL_FETCH_STREAM_BODY;
+ fields |= MAIL_FETCH_STREAM_HEADER;
+ }
+ return fields;
+}
+
+void imapc_mail_try_init_stream_from_cache(struct imapc_mail *mail)
+{
+ struct mail *_mail = &mail->imail.mail.mail;
+ struct imapc_mailbox *mbox = IMAPC_MAILBOX(_mail->box);
+
+ if (mbox->prev_mail_cache.uid == _mail->uid)
+ imapc_mail_cache_get(mail, &mbox->prev_mail_cache);
+}
+
+bool imapc_mail_prefetch(struct mail *_mail)
+{
+ struct imapc_mail *mail = IMAPC_MAIL(_mail);
+ struct imapc_mailbox *mbox = IMAPC_MAILBOX(_mail->box);
+ struct index_mail_data *data = &mail->imail.data;
+ enum mail_fetch_field fields;
+ const char *const *headers = NULL;
+
+ /* try to get as much from cache as possible */
+ imapc_mail_update_access_parts(&mail->imail);
+ /* If mail is already cached we can avoid re-FETCHing the mail.
+ However, don't initialize the stream if we don't actually want to
+ access the mail. */
+ if (mail->imail.data.access_part != 0)
+ imapc_mail_try_init_stream_from_cache(mail);
+
+ fields = imapc_mail_get_wanted_fetch_fields(mail);
+ if (data->wanted_headers != NULL && data->stream == NULL &&
+ (fields & MAIL_FETCH_STREAM_HEADER) == 0 &&
+ !imapc_mail_has_headers_in_cache(&mail->imail, data->wanted_headers)) {
+ /* fetch specific headers */
+ if (IMAPC_BOX_HAS_FEATURE(mbox, IMAPC_FEATURE_FETCH_HEADERS))
+ headers = data->wanted_headers->name;
+ else
+ fields |= MAIL_FETCH_STREAM_HEADER;
+ }
+ if (fields != 0 || headers != NULL) T_BEGIN {
+ if (imapc_mail_send_fetch(_mail, fields, headers) > 0)
+ mail->imail.data.prefetch_sent = TRUE;
+ } T_END;
+ return !mail->imail.data.prefetch_sent;
+}
+
+static bool
+imapc_mail_have_fields(struct imapc_mail *imail, enum mail_fetch_field fields)
+{
+ struct imapc_mailbox *mbox = IMAPC_MAILBOX(imail->imail.mail.mail.box);
+
+ if ((fields & MAIL_FETCH_RECEIVED_DATE) != 0) {
+ if (imail->imail.data.received_date == (time_t)-1)
+ return FALSE;
+ fields &= ENUM_NEGATE(MAIL_FETCH_RECEIVED_DATE);
+ }
+ if ((fields & MAIL_FETCH_SAVE_DATE) != 0) {
+ i_assert(HAS_ALL_BITS(mbox->capabilities,
+ IMAPC_CAPABILITY_SAVEDATE));
+ if (imail->imail.data.save_date == (time_t)-1)
+ return FALSE;
+ fields &= ENUM_NEGATE(MAIL_FETCH_SAVE_DATE);
+ }
+ if ((fields & (MAIL_FETCH_PHYSICAL_SIZE | MAIL_FETCH_VIRTUAL_SIZE)) != 0) {
+ if (imail->imail.data.physical_size == UOFF_T_MAX)
+ return FALSE;
+ fields &= ENUM_NEGATE(MAIL_FETCH_PHYSICAL_SIZE | MAIL_FETCH_VIRTUAL_SIZE);
+ }
+ if ((fields & MAIL_FETCH_GUID) != 0) {
+ if (imail->imail.data.guid == NULL)
+ return FALSE;
+ fields &= ENUM_NEGATE(MAIL_FETCH_GUID);
+ }
+ if ((fields & MAIL_FETCH_IMAP_BODY) != 0) {
+ if (imail->imail.data.body == NULL)
+ return FALSE;
+ fields &= ENUM_NEGATE(MAIL_FETCH_IMAP_BODY);
+ }
+ if ((fields & MAIL_FETCH_IMAP_BODYSTRUCTURE) != 0) {
+ if (imail->imail.data.bodystructure == NULL)
+ return FALSE;
+ fields &= ENUM_NEGATE(MAIL_FETCH_IMAP_BODYSTRUCTURE);
+ }
+ if ((fields & (MAIL_FETCH_STREAM_HEADER |
+ MAIL_FETCH_STREAM_BODY)) != 0) {
+ if (imail->imail.data.stream == NULL)
+ return FALSE;
+ fields &= ENUM_NEGATE(MAIL_FETCH_STREAM_HEADER | MAIL_FETCH_STREAM_BODY);
+ }
+ i_assert(fields == 0);
+ return TRUE;
+}
+
+int imapc_mail_fetch(struct mail *_mail, enum mail_fetch_field fields,
+ const char *const *headers)
+{
+ struct imapc_mail *imail = IMAPC_MAIL(_mail);
+ struct imapc_mailbox *mbox = IMAPC_MAILBOX(_mail->box);
+ int ret;
+
+ if ((fields & MAIL_FETCH_GUID) != 0 &&
+ mbox->guid_fetch_field_name == NULL) {
+ mail_storage_set_error(_mail->box->storage,
+ MAIL_ERROR_NOTPOSSIBLE,
+ "Message GUID not available in this server");
+ return -1;
+ }
+ if (_mail->saving) {
+ mail_storage_set_error(_mail->box->storage,
+ MAIL_ERROR_NOTPOSSIBLE,
+ "Attempting to issue FETCH for a mail not yet committed");
+ return -1;
+ }
+
+ fields |= imapc_mail_get_wanted_fetch_fields(imail);
+ T_BEGIN {
+ ret = imapc_mail_send_fetch(_mail, fields, headers);
+ } T_END;
+ if (ret < 0)
+ return -1;
+
+ /* we'll continue waiting until we've got all the fields we wanted,
+ or until all FETCH replies have been received (i.e. some FETCHes
+ failed) */
+ if (ret > 0)
+ imapc_mail_fetch_flush(mbox);
+ while (imail->fetch_count > 0 &&
+ (!imapc_mail_have_fields(imail, fields) ||
+ !imail->header_list_fetched)) {
+ imapc_mailbox_run_nofetch(mbox);
+ }
+ if (imail->fetch_failed) {
+ mail_storage_set_internal_error(&mbox->storage->storage);
+ return -1;
+ }
+ return 0;
+}
+
+void imapc_mail_fetch_flush(struct imapc_mailbox *mbox)
+{
+ struct imapc_command *cmd;
+ struct imapc_mail *mail;
+
+ if (mbox->pending_fetch_request == NULL) {
+ i_assert(mbox->to_pending_fetch_send == NULL);
+ return;
+ }
+
+ array_foreach_elem(&mbox->pending_fetch_request->mails, mail)
+ mail->fetch_sent = TRUE;
+
+ cmd = imapc_client_mailbox_cmd(mbox->client_box,
+ imapc_mail_fetch_callback,
+ mbox->pending_fetch_request);
+ imapc_command_set_flags(cmd, IMAPC_COMMAND_FLAG_RETRIABLE);
+ array_push_back(&mbox->fetch_requests, &mbox->pending_fetch_request);
+
+ imapc_command_send(cmd, str_c(mbox->pending_fetch_cmd));
+
+ mbox->pending_fetch_request = NULL;
+ timeout_remove(&mbox->to_pending_fetch_send);
+ str_truncate(mbox->pending_fetch_cmd, 0);
+}
+
+static bool imapc_find_lfile_arg(const struct imapc_untagged_reply *reply,
+ const struct imap_arg *arg, int *fd_r)
+{
+ const struct imap_arg *list;
+ unsigned int i, count;
+
+ for (i = 0; i < reply->file_args_count; i++) {
+ const struct imapc_arg_file *farg = &reply->file_args[i];
+
+ if (farg->parent_arg == arg->parent &&
+ imap_arg_get_list_full(arg->parent, &list, &count) &&
+ farg->list_idx < count && &list[farg->list_idx] == arg) {
+ *fd_r = farg->fd;
+ return TRUE;
+ }
+ }
+ return FALSE;
+}
+
+static void imapc_stream_filter(struct istream **input)
+{
+ static const char *imapc_hide_headers[] = {
+ /* Added by MS Exchange 2010 when \Flagged flag is set.
+ This violates IMAP guarantee of messages being immutable. */
+ "X-Message-Flag"
+ };
+ struct istream *filter_input;
+
+ filter_input = i_stream_create_header_filter(*input,
+ HEADER_FILTER_EXCLUDE,
+ imapc_hide_headers, N_ELEMENTS(imapc_hide_headers),
+ *null_header_filter_callback, NULL);
+ i_stream_unref(input);
+ *input = filter_input;
+}
+
+void imapc_mail_init_stream(struct imapc_mail *mail)
+{
+ struct index_mail *imail = &mail->imail;
+ struct mail *_mail = &imail->mail.mail;
+ struct imapc_mailbox *mbox = IMAPC_MAILBOX(_mail->box);
+ struct istream *input;
+ uoff_t size;
+ int ret;
+
+ i_stream_set_name(imail->data.stream,
+ t_strdup_printf("imapc mail uid=%u", _mail->uid));
+ index_mail_set_read_buffer_size(_mail, imail->data.stream);
+
+ if (!IMAPC_BOX_HAS_FEATURE(mbox, IMAPC_FEATURE_RFC822_SIZE)) {
+ /* enable filtering only when we're not passing through
+ RFC822.SIZE. otherwise we'll get size mismatches. */
+ imapc_stream_filter(&imail->data.stream);
+ }
+ if (imail->mail.v.istream_opened != NULL) {
+ if (imail->mail.v.istream_opened(_mail,
+ &imail->data.stream) < 0) {
+ index_mail_close_streams(imail);
+ return;
+ }
+ }
+ ret = i_stream_get_size(imail->data.stream, TRUE, &size);
+ if (ret < 0) {
+ index_mail_close_streams(imail);
+ return;
+ }
+ i_assert(ret != 0);
+ /* Once message body is fetched, we can be sure of what its size is.
+ If we had already received RFC822.SIZE, overwrite it here in case
+ it's wrong. Also in more special cases the RFC822.SIZE may be
+ smaller than the fetched message header. In this case change the
+ size as well, otherwise reading via istream-mail will fail. */
+ if (mail->body_fetched || imail->data.physical_size < size) {
+ if (mail->body_fetched) {
+ imail->data.inexact_total_sizes = FALSE;
+ /* Don't trust any existing virtual_size. Also don't
+ set it to size, because there's no guarantees about
+ the content having proper CRLF newlines, especially
+ not if istream_opened() has changed the stream. */
+ imail->data.virtual_size = UOFF_T_MAX;
+ }
+ imail->data.physical_size = size;
+ }
+
+ imail->data.stream_has_only_header = !mail->body_fetched;
+ if (index_mail_init_stream(imail, NULL, NULL, &input) < 0)
+ index_mail_close_streams(imail);
+}
+
+static void
+imapc_fetch_stream(struct imapc_mail *mail,
+ const struct imapc_untagged_reply *reply,
+ const struct imap_arg *arg,
+ bool have_header, bool have_body)
+{
+ struct index_mail *imail = &mail->imail;
+ struct imapc_mailbox *mbox = IMAPC_MAILBOX(imail->mail.mail.box);
+ struct istream *hdr_stream = NULL;
+ const char *value;
+ int fd;
+
+ if (imail->data.stream != NULL) {
+ i_assert(mail->header_fetched);
+ if (mail->body_fetched || !have_body)
+ return;
+ if (have_header) {
+ /* replace the existing stream */
+ } else if (mail->fd == -1) {
+ /* append this body stream to the existing
+ header stream */
+ hdr_stream = imail->data.stream;
+ i_stream_ref(hdr_stream);
+ } else {
+ /* append this body stream to the existing
+ header stream. we'll need to recreate the stream
+ with autoclosed fd. */
+ if (lseek(mail->fd, 0, SEEK_SET) < 0)
+ i_error("lseek(imapc) failed: %m");
+ hdr_stream = i_stream_create_fd_autoclose(&mail->fd, 0);
+ }
+ index_mail_close_streams(imail);
+ i_close_fd(&mail->fd);
+ } else {
+ if (!have_header) {
+ /* BODY.PEEK[TEXT] received - we can't currently handle
+ this before receiving BODY.PEEK[HEADER] reply */
+ return;
+ }
+ }
+
+ if (arg->type == IMAP_ARG_LITERAL_SIZE) {
+ if (!imapc_find_lfile_arg(reply, arg, &fd)) {
+ i_stream_unref(&hdr_stream);
+ return;
+ }
+ if ((fd = dup(fd)) == -1) {
+ i_error("dup() failed: %m");
+ i_stream_unref(&hdr_stream);
+ return;
+ }
+ mail->fd = fd;
+ imail->data.stream = i_stream_create_fd(fd, 0);
+ } else {
+ if (!imap_arg_get_nstring(arg, &value))
+ value = NULL;
+ if (value == NULL ||
+ (value[0] == '\0' &&
+ IMAPC_BOX_HAS_FEATURE(mbox, IMAPC_FEATURE_FETCH_EMPTY_IS_EXPUNGED))) {
+ mail_set_expunged(&imail->mail.mail);
+ i_stream_unref(&hdr_stream);
+ return;
+ }
+ if (mail->body == NULL) {
+ mail->body = buffer_create_dynamic(default_pool,
+ arg->str_len + 1);
+ } else if (!have_header && hdr_stream != NULL) {
+ /* header is already in the buffer - add body now
+ without destroying the existing header data */
+ i_stream_unref(&hdr_stream);
+ } else {
+ buffer_set_used_size(mail->body, 0);
+ }
+ buffer_append(mail->body, value, arg->str_len);
+ imail->data.stream = i_stream_create_from_data(mail->body->data,
+ mail->body->used);
+ }
+ if (have_header)
+ mail->header_fetched = TRUE;
+ mail->body_fetched = have_body;
+
+ if (hdr_stream != NULL) {
+ struct istream *inputs[3];
+
+ inputs[0] = hdr_stream;
+ inputs[1] = imail->data.stream;
+ inputs[2] = NULL;
+ imail->data.stream = i_stream_create_concat(inputs);
+ i_stream_unref(&inputs[0]);
+ i_stream_unref(&inputs[1]);
+ }
+
+ imapc_mail_init_stream(mail);
+}
+
+static void
+imapc_fetch_header_stream(struct imapc_mail *mail,
+ const struct imapc_untagged_reply *reply,
+ const struct imap_arg *args)
+{
+ const enum message_header_parser_flags hdr_parser_flags =
+ MESSAGE_HEADER_PARSER_FLAG_SKIP_INITIAL_LWSP |
+ MESSAGE_HEADER_PARSER_FLAG_DROP_CR;
+ const struct imap_arg *hdr_list;
+ struct mailbox_header_lookup_ctx *headers_ctx;
+ struct message_header_parser_ctx *parser;
+ struct message_header_line *hdr;
+ struct istream *input;
+ ARRAY_TYPE(const_string) hdr_arr;
+ const char *value;
+ int ret, fd;
+
+ if (!imap_arg_get_list(args, &hdr_list))
+ return;
+ if (!imap_arg_atom_equals(args+1, "]"))
+ return;
+ args += 2;
+
+ /* see if this is reply to the latest headers list request
+ (parse it even if it's not) */
+ t_array_init(&hdr_arr, 16);
+ while (imap_arg_get_astring(hdr_list, &value)) {
+ array_push_back(&hdr_arr, &value);
+ hdr_list++;
+ }
+ if (hdr_list->type != IMAP_ARG_EOL)
+ return;
+ array_append_zero(&hdr_arr);
+
+ if (headers_have_subset(array_front(&hdr_arr), mail->fetching_headers))
+ mail->header_list_fetched = TRUE;
+
+ if (args->type == IMAP_ARG_LITERAL_SIZE) {
+ if (!imapc_find_lfile_arg(reply, args, &fd))
+ return;
+ input = i_stream_create_fd(fd, 0);
+ } else {
+ if (!imap_arg_get_nstring(args, &value))
+ return;
+ if (value == NULL) {
+ mail_set_expunged(&mail->imail.mail.mail);
+ return;
+ }
+ input = i_stream_create_from_data(value, args->str_len);
+ }
+
+ headers_ctx = mailbox_header_lookup_init(mail->imail.mail.mail.box,
+ array_front(&hdr_arr));
+ index_mail_parse_header_init(&mail->imail, headers_ctx);
+
+ parser = message_parse_header_init(input, NULL, hdr_parser_flags);
+ while ((ret = message_parse_header_next(parser, &hdr)) > 0)
+ index_mail_parse_header(NULL, hdr, &mail->imail);
+ i_assert(ret != 0);
+ index_mail_parse_header(NULL, NULL, &mail->imail);
+ message_parse_header_deinit(&parser);
+
+ mailbox_header_lookup_unref(&headers_ctx);
+ i_stream_destroy(&input);
+}
+
+static const char *
+imapc_args_to_bodystructure(struct imapc_mail *mail,
+ const struct imap_arg *list_arg, bool extended)
+{
+ const struct imap_arg *args;
+ struct message_part *parts = NULL;
+ const char *ret, *error;
+ pool_t pool;
+
+ if (!imap_arg_get_list(list_arg, &args)) {
+ mail_set_critical(&mail->imail.mail.mail,
+ "imapc: Server sent invalid BODYSTRUCTURE parameters");
+ return NULL;
+ }
+
+ pool = pool_alloconly_create("imap bodystructure", 1024);
+ if (imap_bodystructure_parse_args(args, pool, &parts, &error) < 0) {
+ mail_set_critical(&mail->imail.mail.mail,
+ "imapc: Server sent invalid BODYSTRUCTURE: %s", error);
+ ret = NULL;
+ } else {
+ string_t *str = t_str_new(128);
+ if (imap_bodystructure_write(parts, str, extended, &error) < 0) {
+ /* All the input to imap_bodystructure_write() came
+ from imap_bodystructure_parse_args(). We should never
+ get here. Instead, if something is wrong the
+ parsing should have returned an error already. */
+ str_truncate(str, 0);
+ imap_write_args(str, args);
+ i_panic("Failed to write parsed BODYSTRUCTURE: %s "
+ "(original string: '%s')", error, str_c(str));
+ }
+ ret = p_strdup(mail->imail.mail.data_pool, str_c(str));
+ }
+ pool_unref(&pool);
+ return ret;
+}
+
+void imapc_mail_fetch_update(struct imapc_mail *mail,
+ const struct imapc_untagged_reply *reply,
+ const struct imap_arg *args)
+{
+ struct imapc_mailbox *mbox = IMAPC_MAILBOX(mail->imail.mail.mail.box);
+ const char *key, *value;
+ unsigned int i;
+ uoff_t size;
+ time_t t;
+ int tz;
+ bool match = FALSE;
+
+ for (i = 0; args[i].type != IMAP_ARG_EOL; i += 2) {
+ if (!imap_arg_get_atom(&args[i], &key) ||
+ args[i+1].type == IMAP_ARG_EOL)
+ break;
+
+ if (strcasecmp(key, "BODY[]") == 0) {
+ imapc_fetch_stream(mail, reply, &args[i+1], TRUE, TRUE);
+ match = TRUE;
+ } else if (strcasecmp(key, "BODY[HEADER]") == 0) {
+ imapc_fetch_stream(mail, reply, &args[i+1], TRUE, FALSE);
+ match = TRUE;
+ } else if (strcasecmp(key, "BODY[TEXT]") == 0) {
+ imapc_fetch_stream(mail, reply, &args[i+1], FALSE, TRUE);
+ match = TRUE;
+ } else if (strcasecmp(key, "BODY[HEADER.FIELDS") == 0) {
+ imapc_fetch_header_stream(mail, reply, &args[i+1]);
+ match = TRUE;
+ } else if (strcasecmp(key, "INTERNALDATE") == 0) {
+ if (imap_arg_get_astring(&args[i+1], &value) &&
+ imap_parse_datetime(value, &t, &tz)) {
+ mail->imail.data.received_date = t;
+ if (HAS_NO_BITS(mbox->capabilities,
+ IMAPC_CAPABILITY_SAVEDATE))
+ mail->imail.data.save_date = t;
+ }
+ match = TRUE;
+ } else if (strcasecmp(key, "SAVEDATE") == 0) {
+ if (imap_arg_get_astring(&args[i+1], &value)) {
+ if (strcasecmp(value, "NIL") == 0)
+ mail->imail.data.save_date = 0;
+ else if (imap_parse_datetime(value, &t, &tz))
+ mail->imail.data.save_date = t;
+ }
+ match = TRUE;
+ } else if (strcasecmp(key, "BODY") == 0) {
+ if (IMAPC_BOX_HAS_FEATURE(mbox, IMAPC_FEATURE_FETCH_BODYSTRUCTURE)) {
+ mail->imail.data.body =
+ imapc_args_to_bodystructure(mail, &args[i+1], FALSE);
+ }
+ match = TRUE;
+ } else if (strcasecmp(key, "BODYSTRUCTURE") == 0) {
+ if (IMAPC_BOX_HAS_FEATURE(mbox, IMAPC_FEATURE_FETCH_BODYSTRUCTURE)) {
+ mail->imail.data.bodystructure =
+ imapc_args_to_bodystructure(mail, &args[i+1], TRUE);
+ }
+ match = TRUE;
+ } else if (strcasecmp(key, "RFC822.SIZE") == 0) {
+ if (imap_arg_get_atom(&args[i+1], &value) &&
+ str_to_uoff(value, &size) == 0 &&
+ IMAPC_BOX_HAS_FEATURE(mbox, IMAPC_FEATURE_RFC822_SIZE)) {
+ mail->imail.data.physical_size = size;
+ mail->imail.data.virtual_size = size;
+ mail->imail.data.inexact_total_sizes = TRUE;
+ }
+ match = TRUE;
+ } else if (strcasecmp(key, "X-GM-MSGID") == 0 ||
+ strcasecmp(key, "X-GUID") == 0) {
+ if (imap_arg_get_astring(&args[i+1], &value)) {
+ mail->imail.data.guid =
+ p_strdup(mail->imail.mail.pool, value);
+ }
+ match = TRUE;
+ }
+ }
+ if (!match) {
+ /* this is only a FETCH FLAGS update for the wanted mail */
+ } else {
+ imapc_client_stop(mbox->storage->client->client);
+ }
+}