view src/lib-index/mbox/mbox-index.c @ 1870:c972ea085643 HEAD

istream rewrite. instead of directly setting any limits to stream, you now have to use i_stream_create_limit() to existing stream. this should make the istreams much easier to create and understand how they work.
author Timo Sirainen <tss@iki.fi>
date Sun, 09 Nov 2003 20:26:25 +0200
parents e42d97a85653
children
line wrap: on
line source

/* Copyright (C) 2002 Timo Sirainen */

#include "lib.h"
#include "buffer.h"
#include "istream.h"
#include "message-part-serialize.h"
#include "mbox-index.h"
#include "mbox-lock.h"
#include "mail-index-util.h"
#include "mail-custom-flags.h"
#include "mail-cache.h"

#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>

/* Don't try reading more custom flags than this. */
#define MAX_CUSTOM_FLAGS 1024

extern struct mail_index mbox_index;

int mbox_set_syscall_error(struct mail_index *index, const char *function)
{
	i_assert(function != NULL);

	index_set_error(index, "%s failed with mbox file %s: %m",
			function, index->mailbox_path);
	return FALSE;
}

int mbox_file_open(struct mail_index *index)
{
	struct stat st;
	int fd;

	i_assert(index->mbox_fd == -1);

	fd = open(index->mailbox_path, index->mailbox_readonly ?
		  O_RDONLY : O_RDWR);
	if (fd == -1) {
		mbox_set_syscall_error(index, "open()");
		return FALSE;
	}

	if (fstat(fd, &st) < 0) {
		mbox_set_syscall_error(index, "fstat()");
		(void)close(fd);
		return FALSE;
	}

	index->mbox_fd = fd;
	index->mbox_dev = st.st_dev;
	index->mbox_ino = st.st_ino;
	return TRUE;
}

struct istream *mbox_get_stream(struct mail_index *index,
				enum mail_lock_type lock_type)
{
	switch (lock_type) {
	case MAIL_LOCK_SHARED:
	case MAIL_LOCK_EXCLUSIVE:
		/* don't drop exclusive lock, it may be there for a reason */
		if (index->mbox_lock_type != MAIL_LOCK_EXCLUSIVE) {
			if (!mbox_lock(index, lock_type))
				return NULL;
		}
		break;
	default:
		if (index->mbox_fd == -1) {
			if (!mbox_file_open(index))
				return NULL;
		}
		break;
	}

	if (index->mbox_stream == NULL) {
		if (index->mail_read_mmaped) {
			index->mbox_stream =
				i_stream_create_mmap(index->mbox_fd,
						     default_pool,
						     MAIL_MMAP_BLOCK_SIZE,
						     0, 0, FALSE);
		} else {
			index->mbox_stream =
				i_stream_create_file(index->mbox_fd,
						     default_pool,
						     MAIL_READ_BLOCK_SIZE,
						     FALSE);
		}
	}

	i_stream_seek(index->mbox_stream, 0);
	i_stream_ref(index->mbox_stream);
	return index->mbox_stream;
}

void mbox_file_close_stream(struct mail_index *index)
{
	if (index->mbox_stream != NULL) {
		i_stream_close(index->mbox_stream);
		i_stream_unref(index->mbox_stream);
		index->mbox_stream = NULL;
	}
}

void mbox_file_close_fd(struct mail_index *index)
{
	mbox_file_close_stream(index);

	if (index->mbox_fd != -1) {
		if (close(index->mbox_fd) < 0)
			i_error("close(mbox) failed: %m");
		index->mbox_fd = -1;
	}
}

void mbox_header_init_context(struct mbox_header_context *ctx,
			      struct mail_index *index,
			      struct istream *input)
{
	memset(ctx, 0, sizeof(struct mbox_header_context));
	md5_init(&ctx->md5);

	ctx->index = index;
	ctx->input = input;
	ctx->custom_flags = mail_custom_flags_list_get(index->custom_flags);
	ctx->content_length = (uoff_t)-1;
}

static enum mail_flags
mbox_get_status_flags(const unsigned char *value, size_t len)
{
	enum mail_flags flags;
	size_t i;

	flags = 0;
	for (i = 0; i < len; i++) {
		switch (value[i]) {
		case 'A':
			flags |= MAIL_ANSWERED;
			break;
		case 'F':
			flags |= MAIL_FLAGGED;
			break;
		case 'T':
			flags |= MAIL_DRAFT;
			break;
		case 'R':
			flags |= MAIL_SEEN;
			break;
		case 'D':
			flags |= MAIL_DELETED;
			break;
		}
	}

	return flags;
}

static void mbox_update_custom_flags(const unsigned char *value __attr_unused__,
				     size_t len __attr_unused__,
				     int index, void *context)
{
	enum mail_flags *flags = context;

	if (index >= 0)
		*flags |= 1 << (index + MAIL_CUSTOM_FLAG_1_BIT);
}

static enum mail_flags
mbox_get_keyword_flags(const unsigned char *value, size_t len,
		       const char *custom_flags[MAIL_CUSTOM_FLAGS_COUNT])
{
	enum mail_flags flags;

	flags = 0;
	mbox_keywords_parse(value, len, custom_flags,
			    mbox_update_custom_flags, &flags);
	return flags;
}

static void mbox_parse_imapbase(const unsigned char *value, size_t len,
				struct mbox_header_context *ctx)
{
	const char *flag, *str;
	char *end;
	buffer_t *buf;
	size_t pos, start;
	enum mail_flags flags;
	unsigned int count;
	int ret;

	t_push();

	/* <uid validity> <last uid> */
	str = t_strndup(value, len);
	ctx->uid_validity = strtoul(str, &end, 10);
	ctx->uid_last = strtoul(end, &end, 10);
	pos = end - str;

	while (pos < len && value[pos] == ' ')
		pos++;

	if (pos == len) {
		t_pop();
		return;
	}

	/* we're at the 3rd field now, which begins the list of custom flags */
	buf = buffer_create_dynamic(pool_datastack_create(),
				    MAIL_CUSTOM_FLAGS_COUNT *
				    sizeof(const char *),
				    MAX_CUSTOM_FLAGS * sizeof(const char *));
	for (start = pos; ; pos++) {
		if (pos == len || value[pos] == ' ' || value[pos] == '\t') {
			if (start != pos) {
				flag = t_strdup_until(value+start, value+pos);
				if (buffer_append(buf, &flag,
						  sizeof(flag)) == 0)
					break;
			}
			start = pos+1;

			if (pos == len)
				break;
		}
	}

	flags = MAIL_CUSTOM_FLAGS_MASK;
	count = buffer_get_used_size(buf) / sizeof(const char *);
	ret = mail_custom_flags_fix_list(ctx->index->custom_flags, &flags,
					 buffer_free_without_data(buf), count);

	t_pop();
}

void mbox_header_cb(struct message_part *part __attr_unused__,
		    struct message_header_line *hdr, void *context)
{
	struct mbox_header_context *ctx = context;
	size_t i;
	int fixed = FALSE;

	if (hdr == NULL || hdr->eoh)
		return;

	/* Pretty much copy&pasted from popa3d by Solar Designer */
	switch (*hdr->name) {
	case 'R':
	case 'r':
		if (!ctx->received &&
		    strcasecmp(hdr->name, "Received") == 0) {
			/* get only the first received-header */
			fixed = TRUE;
			if (!hdr->continues)
				ctx->received = TRUE;
		}
		break;

	case 'C':
	case 'c':
		if (strcasecmp(hdr->name, "Content-Length") == 0) {
			/* manual parsing, so we can deal with uoff_t */
			ctx->content_length = 0;
			for (i = 0; i < hdr->value_len; i++) {
				if (hdr->value[i] < '0' ||
				    hdr->value[i] > '9') {
					/* invalid */
					ctx->content_length = 0;
					break;
				}

				ctx->content_length = ctx->content_length * 10 +
					(hdr->value[i] - '0');
			}
		}
		break;

	case 'D':
	case 'd':
		if (strcasecmp(hdr->name, "Delivered-To") == 0)
			fixed = TRUE;
		else if (!ctx->received && strcasecmp(hdr->name, "Date") == 0) {
			/* Received-header contains date too,
			   and more trusted one */
			fixed = TRUE;
		}
		break;

	case 'M':
	case 'm':
		if (!ctx->received &&
		    strcasecmp(hdr->name, "Message-ID") == 0) {
			/* Received-header contains unique ID too,
			   and more trusted one */
			fixed = TRUE;
		}
		break;

	case 'S':
	case 's':
		if (strcasecmp(hdr->name, "Status") == 0) {
			/* update message flags */
			ctx->flags |= mbox_get_status_flags(hdr->value,
							    hdr->value_len);
		}
		break;

	case 'X':
	case 'x':
		if (strcasecmp(hdr->name, "X-Delivery-ID:") == 0) {
			/* Let the local delivery agent help generate unique
			   ID's but don't blindly trust this header alone as
			   it could just as easily come from the remote. */
			fixed = TRUE;
		} else if (strcasecmp(hdr->name, "X-UID") == 0) {
			ctx->uid = 0;
			for (i = 0; i < hdr->value_len; i++) {
				if (hdr->value[i] < '0' ||
				    hdr->value[i] > '9')
					break;
				ctx->uid = ctx->uid * 10 + (hdr->value[i]-'0');
			}
		} else if (strcasecmp(hdr->name, "X-Status") == 0) {
			/* update message flags */
			ctx->flags |= mbox_get_status_flags(hdr->value,
							    hdr->value_len);
		} else if (strcasecmp(hdr->name, "X-Keywords") == 0) {
			/* update custom message flags */
			ctx->flags |= mbox_get_keyword_flags(hdr->value,
							     hdr->value_len,
							     ctx->custom_flags);
		} else if (strcasecmp(hdr->name, "X-IMAPbase") == 0) {
			if (hdr->continues) {
				hdr->use_full_value = TRUE;
				break;
			}
			mbox_parse_imapbase(hdr->full_value,
					    hdr->full_value_len, ctx);
		}
		break;
	}

	if (fixed)
		md5_update(&ctx->md5, hdr->value, hdr->value_len);
}

void mbox_keywords_parse(const unsigned char *value, size_t len,
			 const char *custom_flags[MAIL_CUSTOM_FLAGS_COUNT],
			 void (*func)(const unsigned char *, size_t,
				      int, void *),
			 void *context)
{
	size_t custom_len[MAIL_CUSTOM_FLAGS_COUNT];
	size_t item_len;
	int i;

	/* the value is often empty, so check that first */
	while (len > 0 && IS_LWSP(*value)) {
		value++;
		len--;
	}

	if (len == 0)
		return;

	for (i = 0; i < MAIL_CUSTOM_FLAGS_COUNT; i++) {
		custom_len[i] = custom_flags[i] != NULL ?
			strlen(custom_flags[i]) : 0;
	}

	for (;;) {
		/* skip whitespace */
		while (len > 0 && IS_LWSP(*value)) {
			value++;
			len--;
		}

		if (len == 0)
			break;

		/* find the length of the item */
		for (item_len = 0; item_len < len; item_len++) {
			if (IS_LWSP(value[item_len]))
				break;
		}

		/* check if it's found */
		for (i = 0; i < MAIL_CUSTOM_FLAGS_COUNT; i++) {
			if (custom_len[i] == item_len &&
			    memcasecmp(custom_flags[i], value, item_len) == 0)
				break;
		}

		if (i == MAIL_CUSTOM_FLAGS_COUNT)
			i = -1;

		func(value, item_len, i, context);

		value += item_len;
		len -= item_len;
	}
}

int mbox_skip_crlf(struct istream *input)
{
	const unsigned char *data;
	size_t size, pos;

	pos = 0;
	while (i_stream_read_data(input, &data, &size, pos) > 0) {
		if (pos == 0) {
			if (data[0] == '\n') {
				i_stream_skip(input, 1);
				return TRUE;
			}
			if (data[0] != '\r')
				return FALSE;

			pos++;
		}

		if (size > 1 && pos == 1) {
			if (data[1] != '\n')
				return FALSE;

			i_stream_skip(input, 2);
			return TRUE;
		}
	}

	/* end of file */
	return TRUE;
}

void mbox_skip_empty_lines(struct istream *input)
{
	const unsigned char *data;
	size_t i, size;

	/* skip empty lines at beginning */
	while (i_stream_read_data(input, &data, &size, 0) > 0) {
		for (i = 0; i < size; i++) {
			if (data[i] != '\r' && data[i] != '\n')
				break;
		}

		i_stream_skip(input, i);

		if (i < size)
			break;
	}
}

static int mbox_is_valid_from(struct istream *input, size_t startpos)
{
	const unsigned char *msg;
	size_t i, size;

	i = startpos;
	while (i_stream_read_data(input, &msg, &size, i) > 0) {
		for (; i < size; i++) {
			if (msg[i] == '\n') {
				msg += startpos;
				i -= startpos;
				return mbox_from_parse_date(msg, size) !=
					(time_t)-1;
			}
		}
	}

	return FALSE;
}

static void mbox_skip_forward(struct istream *input, int header)
{
	const unsigned char *msg;
	size_t i, size, startpos, eoh;
	int lastmsg, state, new_state;

	/* read until "[\r]\nFrom " is found. assume '\n' at beginning of
	   buffer */
	startpos = i = 0; eoh = 0; lastmsg = TRUE;
	state = '\n';
	while (i_stream_read_data(input, &msg, &size, startpos) > 0) {
		for (i = startpos; i < size; i++) {
			new_state = 0;
			switch (state) {
			case '\n':
				if (msg[i] == 'F')
					new_state = 'F';
				else if (header) {
					if (msg[i] == '\n') {
						/* \n\n, but if we have
						   0-byte message body the
						   following \n may belong
						   to "From "-line */
						eoh = i+1;
						header = FALSE;
						new_state = '\n';
					} else if (msg[i] == '\r') {
						/* possibly \n\r\n */
						new_state = '\r';
					}
				}
				break;
			case '\r':
				if (msg[i] == '\n') {
					/* \n\r\n */
					eoh = i+1;
					header = FALSE;
					new_state = '\n';
				}
				break;
			case 'F':
				if (msg[i] == 'r')
					new_state = 'r';
				break;
			case 'r':
				if (msg[i] == 'o')
					new_state = 'o';
				break;
			case 'o':
				if (msg[i] == 'm')
					new_state = 'm';
				break;
			case 'm':
				if (msg[i] == ' ') {
					int valid;

					valid = mbox_is_valid_from(input, i+1);

					/* we may have trashed msg above,
					   get it again */
					msg = i_stream_get_data(input, &size);

					if (valid) {
						/* Go back "From" */
						i -= 4;

						/* Go back \n, unless we're at
						   beginning of buffer */
						if (i > 0)
							i--;

						/* Go back \r if it's there */
						if (i > 0 && msg[i-1] == '\r')
							i--;

						i_stream_skip(input, i);
						return;
					}
				}
				break;
			}

			if (new_state != 0)
				state = new_state;
			else if (eoh == 0)
				state = msg[i] == '\n' ? '\n' : 0;
			else {
				/* end of header position confirmed */
				i_stream_skip(input, eoh);
				return;
			}
		}

		/* Leave enough space to go back "\r\nFrom" plus one for the
		   end-of-headers check */
		startpos = i < 7 ? i : 7;
		i -= startpos;

		if (eoh != 0) {
			i_assert(i < eoh);
			eoh -= i;
		}

		i_stream_skip(input, i);
	}

	if (eoh != 0) {
		/* make sure we didn't end with \n\n or \n\r\n. In these
		   cases the last [\r]\n doesn't belong to our message. */
		if (eoh < size && (msg[eoh] != '\r' || eoh < size-1)) {
			i_stream_skip(input, eoh);
			return;
		}
	}

	/* end of file, leave the last [\r]\n */
	msg = i_stream_get_data(input, &size);
	if (size == startpos && startpos > 0) {
		if (msg[startpos-1] == '\n')
			startpos--;
		if (startpos > 0 && msg[startpos-1] == '\r')
			startpos--;
	}

	i_stream_skip(input, startpos);
}

void mbox_skip_header(struct istream *input)
{
	mbox_skip_forward(input, TRUE);
}

void mbox_skip_message(struct istream *input)
{
	mbox_skip_forward(input, FALSE);
}

int mbox_verify_end_of_body(struct istream *input, uoff_t end_offset)
{
	const unsigned char *data;
	size_t size;

	i_stream_seek(input, end_offset);

	/* read forward a bit */
	if (i_stream_read_data(input, &data, &size, 6) < 0)
		return FALSE;

	/* either there should be the next From-line,
	   or [\r]\n at end of file */
	if (size > 0 && data[0] == '\r') {
		data++; size--;
	}
	if (size > 0) {
		if (data[0] != '\n')
			return FALSE;

		data++; size--;
	}

	return size == 0 ||
		(size >= 5 && strncmp((const char *) data, "From ", 5) == 0);
}

int mbox_mail_get_location(struct mail_index *index,
			   struct mail_index_record *rec,
			   uoff_t *offset, uoff_t *body_size)
{
	struct message_size _body_size;
	const void *data;
	size_t size;

	if (offset != NULL) {
		if (!mail_cache_copy_fixed_field(index->cache, rec,
						 MAIL_CACHE_LOCATION_OFFSET,
						 offset, sizeof(*offset))) {
			mail_cache_set_corrupted(index->cache,
				"Missing location field for record %u",
				rec->uid);
			return FALSE;
		}
	}

	if (body_size != NULL) {
		if (mail_cache_copy_fixed_field(index->cache, rec,
						MAIL_CACHE_PHYSICAL_BODY_SIZE,
						body_size, sizeof(uoff_t)))
			return TRUE;

		if (!mail_cache_lookup_field(index->cache, rec,
					     MAIL_CACHE_MESSAGEPART,
					     &data, &size)) {
			mail_cache_set_corrupted(index->cache,
				"No cached body_size or message_part for "
				"record %u", rec->uid);
			return FALSE;
		}
		if (!message_part_deserialize_size(data, size,
						   NULL, &_body_size)) {
			mail_cache_set_corrupted(index->cache,
				"Corrupted message_part for record %u",
				rec->uid);
			return FALSE;
		}

		if (body_size != NULL)
			*body_size = _body_size.physical_size;
	}

	return TRUE;
}

void mbox_read_headers(struct istream *input, buffer_t *dest)
{
	struct message_header_parser_ctx *hdr_ctx;
	struct message_header_line *hdr;

	hdr_ctx = message_parse_header_init(input, NULL);
	while ((hdr = message_parse_header_next(hdr_ctx)) != NULL) {
		if (hdr->eoh) {
			buffer_append(dest, "\r\n", 2);
			break;
		}

		if ((*hdr->name == 'X' &&
		     (strcasecmp(hdr->name, "X-UID") == 0 ||
		      strcasecmp(hdr->name, "X-IMAPbase") == 0 ||
		      strcasecmp(hdr->name, "X-Status") == 0 ||
		      strcasecmp(hdr->name, "X-Keywords") == 0)) ||
		    strcasecmp(hdr->name, "Content-Length") == 0 ||
		    strcasecmp(hdr->name, "Status") == 0) {
			/* ignore */
		} else {
			if (!hdr->continued) {
				buffer_append(dest, hdr->name, hdr->name_len);
				buffer_append(dest, ": ", 2);
			}
			buffer_append(dest, hdr->value, hdr->value_len);
			buffer_append(dest, "\r\n", 2);
		}
	}
	message_parse_header_deinit(hdr_ctx);
}

struct mail_index *
mbox_index_alloc(const char *mbox_path, const char *index_dir,
		 const char *control_dir)
{
	struct mail_index *index;

	i_assert(mbox_path != NULL);

	index = i_new(struct mail_index, 1);
	memcpy(index, &mbox_index, sizeof(struct mail_index));

	index->mbox_fd = -1;
	index->mbox_sync_counter = (unsigned int)-1;
	index->mailbox_readonly = access(mbox_path, W_OK) < 0;

	index->mailbox_path = i_strdup(mbox_path);
	index->control_dir = i_strdup(control_dir);
	mail_index_init(index, index_dir);
	return index;
}

static void mbox_index_free(struct mail_index *index)
{
        mbox_file_close_fd(index);
	mail_index_close(index);
	i_free(index->dir);
	i_free(index->mailbox_path);
	i_free(index->control_dir);
	i_free(index);
}

static int mbox_index_set_lock(struct mail_index *index,
			       enum mail_lock_type lock_type)
{
	if (lock_type == MAIL_LOCK_UNLOCK)
		(void)mbox_unlock(index);
	return mail_index_set_lock(index, lock_type);
}

static int mbox_index_try_lock(struct mail_index *index,
			       enum mail_lock_type lock_type)
{
	if (lock_type == MAIL_LOCK_UNLOCK)
		(void)mbox_unlock(index);
	return mail_index_try_lock(index, lock_type);
}

static int mbox_index_expunge(struct mail_index *index,
			      struct mail_index_record *first_rec,
			      struct mail_index_record *last_rec,
			      unsigned int first_seq, unsigned int last_seq,
			      int external_change)
{
	if (!mail_index_expunge(index, first_rec, last_rec,
				first_seq, last_seq, external_change))
		return FALSE;

	if (first_seq == 1) {
		/* Our message containing X-IMAPbase was deleted.
		   Get it back there. */
		index->header->flags |= MAIL_INDEX_HDR_FLAG_DIRTY_MESSAGES |
			MAIL_INDEX_HDR_FLAG_DIRTY_CUSTOMFLAGS;
	}
	return TRUE;
}

static int mbox_index_update_flags(struct mail_index *index,
				   struct mail_index_record *rec,
				   unsigned int seq,
				   enum modify_type modify_type,
				   enum mail_flags flags,
				   int external_change)
{
        enum mail_index_record_flag index_flags;

	if (!mail_index_update_flags(index, rec, seq,
				     modify_type, flags, external_change))
		return FALSE;

	if (!external_change) {
		/* we'll just mark the message as dirty */
		index_flags = mail_cache_get_index_flags(index->cache, rec);
		if ((index_flags & MAIL_INDEX_FLAG_DIRTY) == 0) {
			if (mail_cache_lock(index->cache, FALSE) <= 0)
				return FALSE;
			mail_cache_unlock_later(index->cache);

			index_flags |= MAIL_INDEX_FLAG_DIRTY;
			mail_cache_update_index_flags(index->cache, rec,
						      index_flags);

			index->header->flags |=
				MAIL_INDEX_HDR_FLAG_DIRTY_MESSAGES;
		}
	}
	return TRUE;
}

static struct mail_index_record *mbox_index_append(struct mail_index *index)
{
	/* update last_uid in X-IMAPbase */
	index->header->flags |= MAIL_INDEX_HDR_FLAG_DIRTY_MESSAGES |
		MAIL_INDEX_HDR_FLAG_DIRTY_CUSTOMFLAGS;

	return mail_index_append(index);
}

static time_t mbox_get_received_date(struct mail_index *index,
				     struct mail_index_record *rec)
{
	time_t date;

	if (mail_cache_copy_fixed_field(index->cache, rec,
					MAIL_CACHE_RECEIVED_DATE,
					&date, sizeof(date)))
		return date;

	mail_cache_set_corrupted(index->cache,
		"Missing internal date for record %u", rec->uid);
	return (time_t)-1;
}

struct mail_index mbox_index = {
	mail_index_open,
	mbox_index_free,
	mbox_index_set_lock,
	mbox_index_try_lock,
        mail_index_set_lock_notify_callback,
	mail_index_rebuild,
	mail_index_fsck,
	mbox_index_sync,
	mail_index_get_header,
	mail_index_lookup,
	mail_index_next,
        mail_index_lookup_uid_range,
	mbox_open_mail,
	mbox_get_received_date,
	mbox_index_expunge,
	mbox_index_update_flags,
	mbox_index_append,
	mail_index_get_last_error,
	mail_index_get_last_error_text,

	MAIL_INDEX_PRIVATE_FILL
};