static void
index_mailbox_expunge_unseen_recent(struct index_mailbox_sync_context *ctx)
{
	struct index_mailbox_context *ibox =
		INDEX_STORAGE_CONTEXT(ctx->ctx.box);
	struct mail_index_view *view = ctx->ctx.box->view;
	const struct mail_index_header *hdr;
	uint32_t seq, start_uid, uid;

	if (!array_is_created(&ibox->recent_flags))
		return;

	/* expunges array contained expunges for the messages that were already
	   visible in this view, but append+expunge would be invisible.
	   recent_flags may however contain the append UID, so we'll have to
	   remove it separately */
	hdr = mail_index_get_header(view);
	if (ctx->messages_count == 0)
		uid = 0;
	else if (ctx->messages_count <= hdr->messages_count)
		mail_index_lookup_uid(view, ctx->messages_count, &uid);
	else {
		i_assert(mail_index_view_is_inconsistent(view));
		return;
	}

	for (seq = ctx->messages_count + 1; seq <= hdr->messages_count; seq++) {
		start_uid = uid;
		mail_index_lookup_uid(view, seq, &uid);
		if (start_uid + 1 > uid - 1)
			continue;

		ibox->recent_flags_count -=
			seq_range_array_remove_range(&ibox->recent_flags,
						     start_uid + 1, uid - 1);
	}

	if (uid + 1 < hdr->next_uid) {
		ibox->recent_flags_count -=
			seq_range_array_remove_range(&ibox->recent_flags,
						     uid + 1,
						     hdr->next_uid - 1);
	}
#ifdef DEBUG
	if (!mail_index_view_is_inconsistent(view)) {
		const struct seq_range *range;
		unsigned int i, count;

		range = array_get(&ibox->recent_flags, &count);
		for (i = 0; i < count; i++) {
			for (uid = range[i].seq1; uid <= range[i].seq2; uid++) {
				if (uid >= hdr->next_uid)
					break;
				(void)mail_index_lookup_seq(view, uid, &seq);
				i_assert(seq != 0);
			}
		}
	}
#endif
}
static int sync_pvt_expunges(struct index_mailbox_sync_pvt_context *ctx)
{
	uint32_t seq_shared, seq_pvt, count_shared, count_pvt;
	uint32_t uid_shared, uid_pvt;

	count_shared = mail_index_view_get_messages_count(ctx->view_shared);
	count_pvt = mail_index_view_get_messages_count(ctx->view_pvt);
	seq_shared = seq_pvt = 1;
	while (seq_pvt <= count_pvt && seq_shared <= count_shared) {
		mail_index_lookup_uid(ctx->view_pvt, seq_pvt, &uid_pvt);
		mail_index_lookup_uid(ctx->view_shared, seq_shared, &uid_shared);
		if (uid_pvt == uid_shared) {
			seq_pvt++;
			seq_shared++;
		} else if (uid_pvt < uid_shared) {
			/* message expunged */
			mail_index_expunge(ctx->trans_pvt, seq_pvt);
			seq_pvt++;
		} else {
			mail_storage_set_critical(ctx->box->storage,
				"%s: Message UID=%u unexpectedly inserted to mailbox",
				ctx->box->index_pvt->filepath, uid_shared);
			return -1;
		}
	}
	return 0;
}
Beispiel #3
0
static void virtual_mail_set_seq(struct mail *mail, uint32_t seq)
{
	struct virtual_mail *vmail = (struct virtual_mail *)mail;
	struct virtual_mailbox *mbox = (struct virtual_mailbox *)mail->box;
	struct virtual_backend_box *bbox;
	const struct virtual_mail_index_record *vrec;
	const void *data;
	bool expunged;

	mail_index_lookup_ext(mail->box->view, seq, mbox->virtual_ext_id,
			      &data, &expunged);
	vrec = data;

	bbox = virtual_backend_box_lookup(mbox, vrec->mailbox_id);
	vmail->backend_mail = backend_mail_find(vmail, bbox->box);
	if (vmail->backend_mail == NULL)
		virtual_mail_set_backend_mail(mail, bbox);
	vmail->lost = !mail_set_uid(vmail->backend_mail, vrec->real_uid);
	memset(&vmail->imail.data, 0, sizeof(vmail->imail.data));
	p_clear(vmail->imail.data_pool);

	vmail->imail.data.seq = seq;
	mail->seq = seq;
	mail_index_lookup_uid(mail->box->view, seq, &mail->uid);

	if (!vmail->lost) {
		mail->expunged = vmail->backend_mail->expunged;
		mail->has_nuls = vmail->backend_mail->has_nuls;
		mail->has_no_nuls = vmail->backend_mail->has_no_nuls;
	} else {
		mail->expunged = TRUE;
		mail->has_nuls = FALSE;
		mail->has_no_nuls = FALSE;
	}
}
Beispiel #4
0
int mdbox_map_view_lookup_rec(struct mdbox_map *map,
			      struct mail_index_view *view, uint32_t seq,
			      struct dbox_mail_lookup_rec *rec_r)
{
	const uint16_t *ref16_p;
	const void *data;

	memset(rec_r, 0, sizeof(*rec_r));
	mail_index_lookup_uid(view, seq, &rec_r->map_uid);

	mail_index_lookup_ext(view, seq, map->map_ext_id, &data, NULL);
	if (data == NULL) {
		mdbox_map_set_corrupted(map, "missing map extension");
		return -1;
	}
	memcpy(&rec_r->rec, data, sizeof(rec_r->rec));

	mail_index_lookup_ext(view, seq, map->ref_ext_id, &data, NULL);
	if (data == NULL) {
		mdbox_map_set_corrupted(map, "missing ref extension");
		return -1;
	}
	ref16_p = data;
	rec_r->refcount = *ref16_p;
	return 0;
}
Beispiel #5
0
uint32_t mdbox_map_lookup_uid(struct mdbox_map *map, uint32_t seq)
{
	uint32_t uid;

	mail_index_lookup_uid(map->view, seq, &uid);
	return uid;
}
static unsigned int index_storage_count_pvt_unseen(struct mailbox *box)
{
	const struct mail_index_record *pvt_rec;
	uint32_t shared_seq, pvt_seq, shared_count, pvt_count;
	uint32_t shared_uid;
	unsigned int unseen_count = 0;

	/* we can't trust private index to be up to date. we'll need to go
	   through the shared index and for each existing mail lookup its
	   private flags. if a mail doesn't exist in private index then its
	   flags are 0. */
	shared_count = mail_index_view_get_messages_count(box->view);
	pvt_count = mail_index_view_get_messages_count(box->view_pvt);
	shared_seq = pvt_seq = 1;
	while (shared_seq <= shared_count && pvt_seq <= pvt_count) {
		mail_index_lookup_uid(box->view, shared_seq, &shared_uid);
		pvt_rec = mail_index_lookup(box->view_pvt, pvt_seq);

		if (shared_uid == pvt_rec->uid) {
			if ((pvt_rec->flags & MAIL_SEEN) == 0)
				unseen_count++;
			shared_seq++; pvt_seq++;
		} else if (shared_uid < pvt_rec->uid) {
			shared_seq++;
		} else {
			pvt_seq++;
		}
	}
	unseen_count += (shared_count+1) - shared_seq;
	return unseen_count;
}
Beispiel #7
0
static void
cydir_sync_expunge(struct cydir_sync_context *ctx, uint32_t seq1, uint32_t seq2)
{
	struct mailbox *box = &ctx->mbox->box;
	uint32_t uid;

	if (ctx->path == NULL) {
		ctx->path = cydir_get_path_prefix(ctx->mbox);
		ctx->path_dir_prefix_len = str_len(ctx->path);
	}

	for (; seq1 <= seq2; seq1++) {
		mail_index_lookup_uid(ctx->sync_view, seq1, &uid);

		str_truncate(ctx->path, ctx->path_dir_prefix_len);
		str_printfa(ctx->path, "%u.", uid);
		if (i_unlink_if_exists(str_c(ctx->path)) < 0) {
			/* continue anyway */
		} else {
			if (box->v.sync_notify != NULL) {
				box->v.sync_notify(box, uid,
						   MAILBOX_SYNC_TYPE_EXPUNGE);
			}
			mail_index_expunge(ctx->trans, seq1);
		}
	}
}
Beispiel #8
0
static void
cydir_sync_expunge(struct cydir_sync_context *ctx, uint32_t seq1, uint32_t seq2)
{
	struct mailbox *box = &ctx->mbox->box;
	uint32_t uid;

	if (ctx->path == NULL) {
		ctx->path = cydir_get_path_prefix(ctx->mbox);
		ctx->path_dir_prefix_len = str_len(ctx->path);
	}

	for (; seq1 <= seq2; seq1++) {
		mail_index_lookup_uid(ctx->sync_view, seq1, &uid);

		str_truncate(ctx->path, ctx->path_dir_prefix_len);
		str_printfa(ctx->path, "%u.", uid);
		if (unlink(str_c(ctx->path)) == 0) {
			if (box->v.sync_notify != NULL) {
				box->v.sync_notify(box, uid,
						   MAILBOX_SYNC_TYPE_EXPUNGE);
			}
			mail_index_expunge(ctx->trans, seq1);
		} else if (errno != ENOENT) {
			mail_storage_set_critical(&ctx->mbox->storage->storage,
				"unlink(%s) failed: %m", str_c(ctx->path));
			/* continue anyway */
		}
	}
}
void mail_cache_decision_add(struct mail_cache_view *view, uint32_t seq,
			     unsigned int field)
{
	struct mail_cache *cache = view->cache;
	uint32_t uid;

	i_assert(field < cache->fields_count);

	if (MAIL_CACHE_IS_UNUSABLE(cache) || view->no_decision_updates)
		return;

	if (cache->fields[field].field.decision != MAIL_CACHE_DECISION_NO) {
		/* a) forced decision
		   b) we're already caching it, so it just wasn't in cache */
		return;
	}

	/* field used the first time */
	cache->fields[field].field.decision = MAIL_CACHE_DECISION_TEMP;
	cache->fields[field].decision_dirty = TRUE;
	cache->field_header_write_pending = TRUE;

	mail_index_lookup_uid(view->view, seq, &uid);
	cache->fields[field].uid_highwater = uid;
}
void mail_cache_decision_state_update(struct mail_cache_view *view,
				      uint32_t seq, unsigned int field)
{
	struct mail_cache *cache = view->cache;
	enum mail_cache_decision_type dec;
	const struct mail_index_header *hdr;
	uint32_t uid;

	i_assert(field < cache->fields_count);

	if (view->no_decision_updates)
		return;

	dec = cache->fields[field].field.decision;
	if (dec == (MAIL_CACHE_DECISION_NO | MAIL_CACHE_DECISION_FORCED)) {
		/* don't update last_used */
		return;
	}

	if (ioloop_time - cache->fields[field].field.last_used > 3600*24) {
		/* update last_used about once a day */
		cache->fields[field].field.last_used = (uint32_t)ioloop_time;
		if (cache->field_file_map[field] != (uint32_t)-1)
			cache->field_header_write_pending = TRUE;
	}

	if (dec != MAIL_CACHE_DECISION_TEMP) {
		/* a) forced decision
		   b) not cached, mail_cache_decision_add() will handle this
		   c) permanently cached already, okay. */
		return;
	}

	mail_index_lookup_uid(view->view, seq, &uid);
	hdr = mail_index_get_header(view->view);

	/* see if we want to change decision from TEMP to YES */
	if (uid < cache->fields[field].uid_highwater ||
	    uid < hdr->day_first_uid[7]) {
		/* a) nonordered access within this session. if client doesn't
		      request messages in growing order, we assume it doesn't
		      have a permanent local cache.
		   b) accessing message older than one week. assume it's a
		      client with no local cache. if it was just a new client
		      generating the local cache for the first time, we'll
		      drop back to TEMP within few months. */
		cache->fields[field].field.decision = MAIL_CACHE_DECISION_YES;
		cache->fields[field].decision_dirty = TRUE;

		if (cache->field_file_map[field] != (uint32_t)-1)
			cache->field_header_write_pending = TRUE;
	} else {
		cache->fields[field].uid_highwater = uid;
	}
}
void index_mailbox_set_recent_seq(struct mailbox *box,
				  struct mail_index_view *view,
				  uint32_t seq1, uint32_t seq2)
{
	uint32_t uid;

	for (; seq1 <= seq2; seq1++) {
		mail_index_lookup_uid(view, seq1, &uid);
		index_mailbox_set_recent_uid(box, uid);
	}
}
Beispiel #12
0
static void
imapc_sync_add_missing_deleted_flags(struct imapc_sync_context *ctx,
				     uint32_t seq1, uint32_t seq2)
{
	const struct mail_index_record *rec;
	uint32_t seq, uid1, uid2;
	const char *cmd;

	/* if any of them has a missing \Deleted flag,
	   just add it to all of them. */
	for (seq = seq1; seq <= seq2; seq++) {
		rec = mail_index_lookup(ctx->sync_view, seq);
		if ((rec->flags & MAIL_DELETED) == 0)
			break;
	}

	if (seq <= seq2) {
		mail_index_lookup_uid(ctx->sync_view, seq1, &uid1);
		mail_index_lookup_uid(ctx->sync_view, seq2, &uid2);
		cmd = t_strdup_printf("UID STORE %u:%u +FLAGS \\Deleted",
				      uid1, uid2);
		imapc_sync_cmd(ctx, cmd);
	}
}
static void
index_mailbox_expunge_recent(struct mailbox *box, uint32_t seq1, uint32_t seq2)
{
	struct index_mailbox_context *ibox = INDEX_STORAGE_CONTEXT(box);
	uint32_t uid;

	if (!array_is_created(&ibox->recent_flags))
		return;

	for (; seq1 <= seq2; seq1++) {
		mail_index_lookup_uid(box->view, seq1, &uid);
		if (seq_range_array_remove(&ibox->recent_flags, uid))
			ibox->recent_flags_count--;
	}
}
Beispiel #14
0
static int dbox_sync_mark_expunges(struct mdbox_sync_context *ctx)
{
	enum mail_index_transaction_flags flags =
		MAIL_INDEX_TRANSACTION_FLAG_EXTERNAL;
	struct mailbox *box = &ctx->mbox->box;
	struct mail_index_transaction *trans;
	struct seq_range_iter iter;
	unsigned int n;
	const void *data;
	uint32_t seq, uid;

	/* use a separate transaction here so that we can commit the changes
	   during map transaction */
	trans = mail_index_transaction_begin(ctx->sync_view, flags);
	seq_range_array_iter_init(&iter, &ctx->expunged_seqs); n = 0;
	while (seq_range_array_iter_nth(&iter, n++, &seq)) {
		mail_index_lookup_uid(ctx->sync_view, seq, &uid);
		mail_index_lookup_ext(ctx->sync_view, seq,
				      ctx->mbox->guid_ext_id, &data, NULL);
		mail_index_expunge_guid(trans, seq, data);
	}
	if (mail_index_transaction_commit(&trans) < 0)
		return -1;

	if (box->v.sync_notify != NULL) {
		/* do notifications after commit finished successfully */
		box->tmp_sync_view = ctx->sync_view;
		seq_range_array_iter_init(&iter, &ctx->expunged_seqs); n = 0;
		while (seq_range_array_iter_nth(&iter, n++, &seq)) {
			mail_index_lookup_uid(ctx->sync_view, seq, &uid);
			box->v.sync_notify(box, uid, MAILBOX_SYNC_TYPE_EXPUNGE);
		}
		box->tmp_sync_view = NULL;
	}
	return 0;
}
Beispiel #15
0
static int
mdbox_map_lookup_seq(struct mdbox_map *map, uint32_t seq,
		     const struct mdbox_map_mail_index_record **rec_r)
{
	const struct mdbox_map_mail_index_record *rec;
	const void *data;
	uint32_t uid;

	mail_index_lookup_ext(map->view, seq, map->map_ext_id, &data, NULL);
	rec = data;

	if (rec == NULL || rec->file_id == 0) {
		mail_index_lookup_uid(map->view, seq, &uid);
		mdbox_map_set_corrupted(map, "file_id=0 for map_uid=%u", uid);
		return -1;
	}
	*rec_r = rec;
	return 0;
}
Beispiel #16
0
int mdbox_mail_lookup(struct mdbox_mailbox *mbox, struct mail_index_view *view,
		      uint32_t seq, uint32_t *map_uid_r)
{
	const struct mdbox_mail_index_record *dbox_rec;
	struct mdbox_index_header hdr;
	const void *data;
	uint32_t uid, cur_map_uid_validity;
	bool expunged;

	mail_index_lookup_ext(view, seq, mbox->ext_id, &data, &expunged);
	dbox_rec = data;
	if (dbox_rec == NULL || dbox_rec->map_uid == 0) {
		mail_index_lookup_uid(view, seq, &uid);
		mail_storage_set_critical(&mbox->storage->storage.storage,
			"mdbox %s: map uid lost for uid %u",
			mbox->box.path, uid);
		mdbox_storage_set_corrupted(mbox->storage);
		return -1;
	}

	if (mbox->map_uid_validity == 0) {
		if (mdbox_read_header(mbox, &hdr) < 0) {
			mdbox_storage_set_corrupted(mbox->storage);
			return -1;
		}
		mbox->map_uid_validity = hdr.map_uid_validity;
	}
	if (mdbox_map_open_or_create(mbox->storage->map) < 0)
		return -1;

	cur_map_uid_validity = mdbox_map_get_uid_validity(mbox->storage->map);
	if (cur_map_uid_validity != mbox->map_uid_validity) {
		mail_storage_set_critical(&mbox->storage->storage.storage,
			"mdbox %s: map uidvalidity mismatch (%u vs %u)",
			mbox->box.path, mbox->map_uid_validity,
			cur_map_uid_validity);
		mdbox_storage_set_corrupted(mbox->storage);
		return -1;
	}
	*map_uid_r = dbox_rec->map_uid;
	return 0;
}
Beispiel #17
0
static int
dbox_sync_verify_expunge_guid(struct mdbox_sync_context *ctx, uint32_t seq,
			      const guid_128_t guid_128)
{
	const void *data;
	uint32_t uid;

	mail_index_lookup_uid(ctx->sync_view, seq, &uid);
	mail_index_lookup_ext(ctx->sync_view, seq,
			      ctx->mbox->guid_ext_id, &data, NULL);
	if (guid_128_is_empty(guid_128) ||
	    memcmp(data, guid_128, GUID_128_SIZE) == 0)
		return 0;

	mail_storage_set_critical(&ctx->mbox->storage->storage.storage,
		"Mailbox %s: Expunged GUID mismatch for UID %u: %s vs %s",
		ctx->mbox->box.vname, uid,
		guid_128_to_string(data), guid_128_to_string(guid_128));
	mdbox_storage_set_corrupted(ctx->mbox->storage);
	return -1;
}
Beispiel #18
0
static void imapc_sync_expunge_eom(struct imapc_sync_context *ctx)
{
	struct imapc_mailbox *mbox = ctx->mbox;
	uint32_t lseq, uid, msg_count;

	if (mbox->sync_next_lseq == 0)
		return;

	/* if we haven't seen FETCH reply for some messages at the end of
	   mailbox they've been externally expunged. */
	msg_count = mail_index_view_get_messages_count(ctx->sync_view);
	for (lseq = mbox->sync_next_lseq; lseq <= msg_count; lseq++) {
		mail_index_lookup_uid(ctx->sync_view, lseq, &uid);
		if (uid >= mbox->sync_uid_next) {
			/* another process already added new messages to index
			   that our IMAP connection hasn't seen yet */
			break;
		}
		mail_index_expunge(ctx->trans, lseq);
	}

	mbox->sync_next_lseq = 0;
	mbox->sync_next_rseq = 0;
}
Beispiel #19
0
	array_foreach(changes, range) {
		for (seq = range->seq1; seq <= range->seq2; seq++) {
			mail_index_lookup_uid(ctx->ctx.box->view, seq, &uid);
			seq_range_array_add(&ctx->all_flag_update_uids, uid);
		}
	}
static int mailbox_list_index_parse_records(struct mailbox_list_index *ilist,
					    struct mail_index_view *view,
					    const char **error_r)
{
	struct mailbox_list_index_node *node;
	const struct mail_index_record *rec;
	const struct mailbox_list_index_record *irec;
	const void *data;
	bool expunged;
	uint32_t seq, uid, count;

	*error_r = NULL;

	count = mail_index_view_get_messages_count(view);
	for (seq = 1; seq <= count; seq++) {
		node = p_new(ilist->mailbox_pool,
			     struct mailbox_list_index_node, 1);
		rec = mail_index_lookup(view, seq);
		node->uid = rec->uid;
		node->flags = rec->flags;

		mail_index_lookup_ext(view, seq, ilist->ext_id,
				      &data, &expunged);
		if (data == NULL) {
			*error_r = "Missing list extension data";
			return -1;
		}
		irec = data;

		node->name_id = irec->name_id;
		node->name = hash_table_lookup(ilist->mailbox_names,
					       POINTER_CAST(irec->name_id));
		if (node->name == NULL) {
			*error_r = "name_id not in index header";
			if (ilist->has_backing_store)
				return -1;
			/* generate a new name and use it */
			mailbox_list_index_generate_name(ilist, node);
		}
		hash_table_insert(ilist->mailbox_hash,
				  POINTER_CAST(node->uid), node);
	}

	/* do a second scan to create the actual mailbox tree hierarchy.
	   this is needed because the parent_uid may be smaller or higher than
	   the current node's uid */
	for (seq = 1; seq <= count; seq++) {
		mail_index_lookup_uid(view, seq, &uid);
		mail_index_lookup_ext(view, seq, ilist->ext_id,
				      &data, &expunged);
		irec = data;

		node = mailbox_list_index_lookup_uid(ilist, uid);
		i_assert(node != NULL);

		if (irec->parent_uid != 0) {
			/* node should have a parent */
			node->parent = mailbox_list_index_lookup_uid(ilist,
							irec->parent_uid);
			if (node->parent != NULL) {
				node->next = node->parent->children;
				node->parent->children = node;
				continue;
			}
			*error_r = "parent_uid points to nonexistent record";
			if (ilist->has_backing_store)
				return -1;
			/* just place it under the root */
		}
		node->next = ilist->mailbox_tree;
		ilist->mailbox_tree = node;
	}
	return *error_r == NULL ? 0 : -1;
}
Beispiel #21
0
bool mbox_sync_parse_match_mail(struct mbox_mailbox *mbox,
				struct mail_index_view *view, uint32_t seq)
{
        struct mbox_sync_mail_context ctx;
	struct message_header_parser_ctx *hdr_ctx;
	struct message_header_line *hdr;
	struct header_func *func;
	struct mbox_md5_context *mbox_md5_ctx;
	const void *data;
	bool expunged;
	uint32_t uid;
	int ret;

	/* we only wish to be sure that this mail actually is what we expect
	   it to be. If there's X-UID header and it matches our UID, we use it.
	   Otherwise it could mean that the X-UID header is invalid and it's
	   just not yet been rewritten. In that case use MD5 sum, if it
	   exists. */

	mail_index_lookup_uid(view, seq, &uid);
	memset(&ctx, 0, sizeof(ctx));
        mbox_md5_ctx = mbox->md5_v.init();

	hdr_ctx = message_parse_header_init(mbox->mbox_stream, NULL, 0);
	while ((ret = message_parse_header_next(hdr_ctx, &hdr)) > 0) {
		if (hdr->eoh)
			break;

		func = bsearch(hdr->name, header_funcs,
			       N_ELEMENTS(header_funcs), sizeof(*header_funcs),
			       mbox_sync_bsearch_header_func_cmp);
		if (func != NULL) {
			if (strcasecmp(hdr->name, "X-UID") == 0) {
				if (hdr->continues) {
					hdr->use_full_value = TRUE;
					continue;
				}
				(void)parse_x_uid(&ctx, hdr);

				if (ctx.mail.uid == uid)
					break;
			}
		} else {
			mbox->md5_v.more(mbox_md5_ctx, hdr);
		}
	}
	i_assert(ret != 0);
	message_parse_header_deinit(&hdr_ctx);

	mbox->md5_v.finish(mbox_md5_ctx, ctx.hdr_md5_sum);

	if (ctx.mail.uid == uid)
		return TRUE;

	/* match by MD5 sum */
	mbox->mbox_save_md5 = TRUE;

	mail_index_lookup_ext(view, seq, mbox->md5hdr_ext_idx,
			      &data, &expunged);
	return data == NULL ? 0 :
		memcmp(data, ctx.hdr_md5_sum, 16) == 0;
}
Beispiel #22
0
void index_mail_set_seq(struct mail *_mail, uint32_t seq)
{
	struct index_mail *mail = (struct index_mail *)_mail;
	struct index_mail_data *data = &mail->data;
	struct mail_cache_field *cache_fields = mail->ibox->cache_fields;
	struct mail_cache_view *cache_view = mail->trans->cache_view;
	const struct mail_index_header *hdr;
	struct istream *input;

	if (data->seq == seq)
		return;

	index_mail_reset(mail);

	data->seq = seq;

	mail->mail.mail.seq = seq;
	mail_index_lookup_uid(_mail->transaction->view, seq,
			      &mail->mail.mail.uid);

	if (mail_index_view_is_inconsistent(_mail->transaction->view)) {
		mail_set_expunged(&mail->mail.mail);
		return;
	}

	if ((mail->wanted_fields & (MAIL_FETCH_NUL_STATE |
				    MAIL_FETCH_IMAP_BODY |
				    MAIL_FETCH_IMAP_BODYSTRUCTURE)) != 0) {
		(void)index_mail_get_fixed_field(mail, MAIL_CACHE_FLAGS,
						 &data->cache_flags,
						 sizeof(data->cache_flags));
		mail->mail.mail.has_nuls =
			(data->cache_flags & MAIL_CACHE_FLAG_HAS_NULS) != 0;
		mail->mail.mail.has_no_nuls =
			(data->cache_flags & MAIL_CACHE_FLAG_HAS_NO_NULS) != 0;
	}

	/* see if wanted_fields can tell us if we need to read/parse
	   header/body */
	if ((mail->wanted_fields & MAIL_FETCH_MESSAGE_PARTS) != 0) {
		unsigned int cache_field =
			cache_fields[MAIL_CACHE_MESSAGE_PARTS].idx;

		if (mail_cache_field_exists(cache_view, seq,
					    cache_field) <= 0) {
			data->access_part |= PARSE_HDR | PARSE_BODY;
			data->save_message_parts = TRUE;
		}
	}

	if ((mail->wanted_fields & MAIL_FETCH_IMAP_ENVELOPE) != 0)
		check_envelope(mail);

	if ((mail->wanted_fields & MAIL_FETCH_IMAP_BODY) != 0 &&
	    (data->cache_flags & MAIL_CACHE_FLAG_TEXT_PLAIN_7BIT_ASCII) == 0) {
		/* we need either imap.body or imap.bodystructure */
		unsigned int cache_field1 =
			cache_fields[MAIL_CACHE_IMAP_BODY].idx;
		unsigned int cache_field2 =
			cache_fields[MAIL_CACHE_IMAP_BODYSTRUCTURE].idx;

		if (mail_cache_field_exists(cache_view,
					    seq, cache_field1) <= 0 &&
		    mail_cache_field_exists(cache_view,
                                            seq, cache_field2) <= 0) {
			data->access_part |= PARSE_HDR | PARSE_BODY;
			data->save_bodystructure_header = TRUE;
			data->save_bodystructure_body = TRUE;
		}
	}

	if ((mail->wanted_fields & MAIL_FETCH_IMAP_BODYSTRUCTURE) != 0 &&
	    (data->cache_flags & MAIL_CACHE_FLAG_TEXT_PLAIN_7BIT_ASCII) == 0) {
		unsigned int cache_field =
			cache_fields[MAIL_CACHE_IMAP_BODYSTRUCTURE].idx;

                if (mail_cache_field_exists(cache_view, seq,
                                            cache_field) <= 0) {
			data->access_part |= PARSE_HDR | PARSE_BODY;
			data->save_bodystructure_header = TRUE;
			data->save_bodystructure_body = TRUE;
		}
	}

	if ((mail->wanted_fields & MAIL_FETCH_DATE) != 0) {
		unsigned int cache_field =
			cache_fields[MAIL_CACHE_SENT_DATE].idx;

		if (mail_cache_field_exists(cache_view, seq,
					    cache_field) <= 0) {
			data->access_part |= PARSE_HDR;
			data->save_sent_date = TRUE;
		}
	}

	if ((mail->wanted_fields & (MAIL_FETCH_STREAM_HEADER |
				    MAIL_FETCH_STREAM_BODY)) != 0) {
		/* open stream immediately to set expunged flag if
		   it's already lost */
		if ((mail->wanted_fields & MAIL_FETCH_STREAM_HEADER) != 0)
			data->access_part |= READ_HDR;
		if ((mail->wanted_fields & MAIL_FETCH_STREAM_BODY) != 0)
			data->access_part |= READ_BODY;

		/* open the stream only if we didn't get here from
		   mailbox_save_init() */
		hdr = mail_index_get_header(_mail->box->view);
		if (!_mail->saving && _mail->uid < hdr->next_uid)
			(void)mail_get_stream(_mail, NULL, NULL, &input);
	}
}