view src/lib-storage/index/index-fetch-section.c @ 903:fd8888f6f037 HEAD

Naming style changes, finally got tired of most of the typedefs. Also the previous enum -> macro change reverted so that we don't use the highest bit anymore, that's incompatible with old indexes so they will be rebuilt.
author Timo Sirainen <tss@iki.fi>
date Sun, 05 Jan 2003 15:09:51 +0200
parents d3075affded9
children
line wrap: on
line source

/* Copyright (C) 2002 Timo Sirainen */

#include "lib.h"
#include "str.h"
#include "istream.h"
#include "ostream.h"
#include "message-send.h"
#include "index-storage.h"
#include "index-fetch.h"

#include <ctype.h>
#include <unistd.h>

struct fetch_header_field_context {
	string_t *dest;
	struct ostream *output;
	uoff_t dest_size;

	uoff_t skip, max_size;
	const char *const *fields;
	int (*match_func) (const char *const *, const unsigned char *, size_t);
};

/* For FETCH[HEADER.FIELDS*] we need to modify the header data before sending
   it. We can either save it in memory and then send it, or we can parse it
   twice, first calculating the size and then send it. This value specifies
   the maximum amount of memory we allow to allocate before using
   double-parsing. */
#define MAX_HEADER_BUFFER_SIZE (32*1024)

#define UNSIGNED_CRLF (const unsigned char *) "\r\n"

enum imap_cache_field index_fetch_body_get_cache(const char *section)
{
	if (*section >= '0' && *section <= '9')
		return IMAP_CACHE_MESSAGE_PART | IMAP_CACHE_MESSAGE_OPEN;

	if (*section == '\0' || strcasecmp(section, "TEXT") == 0) {
		/* no IMAP_CACHE_MESSAGE_BODY_SIZE, so that we don't
		   uselessly check it when we want to read partial data */
		return IMAP_CACHE_MESSAGE_OPEN;
	}

	if (strncasecmp(section, "HEADER", 6) == 0 ||
	    strcasecmp(section, "MIME") == 0)
		return IMAP_CACHE_MESSAGE_HDR_SIZE | IMAP_CACHE_MESSAGE_OPEN;

	/* error */
	return 0;
}

/* fetch BODY[] or BODY[TEXT] */
static int fetch_body(struct mail_index_record *rec,
		      struct mail_fetch_body_data *sect,
		      struct fetch_context *ctx,
		      const char *prefix, int fetch_header)
{
	struct message_size size;
	struct istream *input;
	const char *str;
	int cr_skipped;

	if (!imap_msgcache_get_rfc822_partial(ctx->cache, sect->skip,
					      sect->max_size, fetch_header,
					      &size, &input, &cr_skipped)) {
		i_error("Couldn't get BODY[] for UID %u (index %s)",
			rec->uid, ctx->index->filepath);
		return FALSE;
	}

	str = t_strdup_printf("%s {%"PRIuUOFF_T"}\r\n",
			      prefix, size.virtual_size);
	if (o_stream_send_str(ctx->output, str) < 0)
		return FALSE;

	if (cr_skipped)
		size.virtual_size++;

	return message_send(ctx->output, input, &size,
			    cr_skipped ? 1 : 0, sect->max_size);
}

static const char **get_fields_array(const char *fields)
{
	const char **field_list, **field;

	while (*fields == ' ')
		fields++;
	if (*fields == '(')
		fields++;

	field_list = t_strsplit(fields, " )");

	/* array ends at ")" element */
	for (field = field_list; *field != NULL; field++) {
		if (strcasecmp(*field, ")") == 0)
			*field = NULL;
	}

	return field_list;
}

static int header_match(const char *const *fields,
			const unsigned char *name, size_t size)
{
	const unsigned char *name_start, *name_end;
	const char *field;

	if (size == 0)
		return FALSE;

	name_start = name;
	name_end = name + size;

	for (; *fields != NULL; fields++) {
		field = *fields;
		if (*field == '\0')
			continue;

		for (name = name_start; name != name_end; name++) {
			/* field has been uppercased long time ago while
			   parsing FETCH command */
			if (i_toupper(*name) != *field)
				break;

			field++;
			if (*field == '\0') {
				if (name+1 == name_end)
					return TRUE;
				break;
			}
		}
	}

	return FALSE;
}

static int header_match_not(const char *const *fields,
			    const unsigned char *name, size_t size)
{
	return !header_match(fields, name, size);
}

static int header_match_mime(const char *const *fields __attr_unused__,
			     const unsigned char *name, size_t size)
{
	if (size > 8 && memcasecmp(name, "Content-", 8) == 0)
		return TRUE;

	if (size == 12 && memcasecmp(name, "Mime-Version", 12) == 0)
		return TRUE;

	return FALSE;
}

static int fetch_header_append(struct fetch_header_field_context *ctx,
			       const unsigned char *str, size_t size)
{
	if (ctx->skip > 0) {
		if (ctx->skip >= size) {
			ctx->skip -= size;
			return TRUE;
		}

		str += ctx->skip;
		size -= ctx->skip;
		ctx->skip = 0;
	}

	if (ctx->dest_size + size > ctx->max_size) {
		i_assert(ctx->dest_size <= ctx->max_size);
		size = ctx->max_size - ctx->dest_size;
	}

	if (ctx->dest != NULL)
		str_append_n(ctx->dest, str, size);
	ctx->dest_size += size;

	if (ctx->output != NULL) {
		if (o_stream_send(ctx->output, str, size) < 0)
			return FALSE;
	}
	return ctx->dest_size < ctx->max_size;
}

static void fetch_header_field(struct message_part *part __attr_unused__,
			       const unsigned char *name, size_t name_len,
			       const unsigned char *value __attr_unused__,
			       size_t value_len __attr_unused__,
			       void *context)
{
	struct fetch_header_field_context *ctx = context;
	const unsigned char *field_start, *field_end, *cr, *p;

	/* see if we want this field. */
	if (!ctx->match_func(ctx->fields, name, name_len))
		return;

	/* add the field, inserting CRs when needed. FIXME: is this too
	   kludgy? we assume name continues with ": value". but otherwise
	   we wouldn't reply with correct LWSP between ":". */
	field_start = name;
	field_end = value + value_len;

	cr = NULL;
	for (p = field_start; p != field_end; p++) {
		if (*p == '\r')
			cr = p;
		else if (*p == '\n' && cr != p-1) {
			/* missing CR */
			if (!fetch_header_append(ctx, field_start,
						 (size_t) (p-field_start)))
				return;
			if (!fetch_header_append(ctx, UNSIGNED_CRLF, 2))
				return;

			field_start = p+1;
		}
	}

	if (field_start != field_end) {
		if (!fetch_header_append(ctx, field_start,
					 (size_t) (field_end-field_start)))
			return;
	}

	(void)fetch_header_append(ctx, UNSIGNED_CRLF, 2);
}

static int fetch_header_fields(struct istream *input, const char *section,
			       struct fetch_header_field_context *ctx)
{
	if (strncasecmp(section, "HEADER.FIELDS ", 14) == 0) {
		ctx->fields = get_fields_array(section + 14);
		ctx->match_func = header_match;
	} else if (strncasecmp(section, "HEADER.FIELDS.NOT ", 18) == 0) {
		ctx->fields = get_fields_array(section + 18);
		ctx->match_func = header_match_not;
	} else if (strcasecmp(section, "MIME") == 0) {
		/* Mime-Version + Content-* fields */
		ctx->match_func = header_match_mime;
	} else {
		/* invalid section given by user - FIXME: tell user about it */
		return FALSE;
	}

	ctx->dest_size = 0;
	message_parse_header(NULL, input, NULL, fetch_header_field, ctx);

	/* FIXME: The blank line must not be filtered, says RFC. However, we
	   shouldn't add it if it wasn't there in the first place. Not very
	   easy to know currently so we'll just do it always, it'll be present
	   in all sane messages anyway.. */
	(void)fetch_header_append(ctx, UNSIGNED_CRLF, 2);

	i_assert(ctx->dest_size <= ctx->max_size);
	i_assert(ctx->dest == NULL || str_len(ctx->dest) == ctx->dest_size);
	return TRUE;
}

/* fetch wanted headers from given data */
static int fetch_header_from(struct istream *input, struct ostream *output,
			     const char *prefix, struct message_size *size,
			     const char *section,
			     struct mail_fetch_body_data *sect)
{
	struct fetch_header_field_context ctx;
	const char *str;
	uoff_t start_offset;
	int failed;

	/* HEADER, MIME, HEADER.FIELDS (list), HEADER.FIELDS.NOT (list) */

	if (strcasecmp(section, "HEADER") == 0) {
		/* all headers */
		str = t_strdup_printf("%s {%"PRIuUOFF_T"}\r\n",
				      prefix, size->virtual_size);
		if (o_stream_send_str(output, str) < 0)
			return FALSE;
		return message_send(output, input, size,
				    sect->skip, sect->max_size);
	}

	/* partial headers - copy the wanted fields into memory, inserting
	   missing CRs on the way. If the header is too large, calculate 
	   the size first and then send the data directly to output stream. */

	memset(&ctx, 0, sizeof(ctx));
	ctx.skip = sect->skip;
	ctx.max_size = sect->max_size;

	failed = FALSE;
	start_offset = input->v_offset;

	t_push();

	/* first pass, we need at least the size */
	if (size->virtual_size > MAX_HEADER_BUFFER_SIZE &&
	    sect->max_size > MAX_HEADER_BUFFER_SIZE) {
		if (!fetch_header_fields(input, section, &ctx))
			failed = TRUE;

		i_assert(ctx.dest_size <= size->virtual_size);
	} else {
		ctx.dest = t_str_new(size->virtual_size < 4096 ?
				     size->virtual_size : 4096);
		if (!fetch_header_fields(input, section, &ctx))
			failed = TRUE;
	}

	if (!failed) {
		str = t_strdup_printf("%s {%"PRIuUOFF_T"}\r\n",
				      prefix, ctx.dest_size);
		if (o_stream_send_str(output, str) < 0)
			failed = TRUE;
	}

	if (!failed) {
		if (ctx.dest == NULL) {
			/* second pass, write the data to output stream */
			uoff_t first_size = ctx.dest_size;

			ctx.output = output;
			i_stream_seek(input, start_offset);

			if (!failed &&
			    !fetch_header_fields(input, section, &ctx))
				failed = TRUE;

			i_assert(first_size == ctx.dest_size);
		} else {
			if (o_stream_send(output, str_c(ctx.dest),
					  str_len(ctx.dest)) < 0)
				failed = TRUE;
		}
	}

	t_pop();
	return !failed;
}

/* fetch BODY[HEADER...] */
static int fetch_header(struct mail_fetch_body_data *sect,
			struct fetch_context *ctx, const char *prefix)
{
	struct message_size hdr_size;
	struct istream *input;

	if (!imap_msgcache_get_rfc822(ctx->cache, &input, &hdr_size, NULL))
		return FALSE;

	return fetch_header_from(input, ctx->output, prefix, &hdr_size,
				 sect->section, sect);
}

/* Find message_part for section (eg. 1.3.4) */
static struct message_part *
part_find(struct mail_fetch_body_data *sect, struct fetch_context *ctx,
	  const char **section)
{
	struct message_part *part;
	const char *path;
	unsigned int num;

	part = imap_msgcache_get_parts(ctx->cache);

	path = sect->section;
	while (*path >= '0' && *path <= '9' && part != NULL) {
		/* get part number */
		num = 0;
		while (*path != '\0' && *path != '.') {
			if (*path < '0' || *path > '9')
				return NULL;
			num = num*10 + (*path - '0');
			path++;
		}

		if (*path == '.')
			path++;

		if (part->flags & MESSAGE_PART_FLAG_MULTIPART) {
			/* find the part */
			part = part->children;
			for (; num > 1 && part != NULL; num--)
				part = part->next;
		} else {
			/* only 1 allowed with non-multipart messages */
			if (num != 1)
				return NULL;
		}

		if (part != NULL &&
		    (part->flags & MESSAGE_PART_FLAG_MESSAGE_RFC822)) {
			/* skip the message/rfc822 part */
			part = part->children;
		}
	}

	*section = path;
	return part;
}

/* fetch BODY[1.2] or BODY[1.2.TEXT] */
static int fetch_part_body(struct message_part *part,
			   struct mail_fetch_body_data *sect,
			   struct fetch_context *ctx, const char *prefix)
{
	struct istream *input;
	const char *str;
	uoff_t skip_pos;

	if (!imap_msgcache_get_data(ctx->cache, &input))
		return FALSE;

	/* jump to beginning of wanted data */
	skip_pos = part->physical_pos + part->header_size.physical_size;
	i_stream_skip(input, skip_pos);

	str = t_strdup_printf("%s {%"PRIuUOFF_T"}\r\n",
			      prefix, part->body_size.virtual_size);
	if (o_stream_send_str(ctx->output, str) < 0)
		return FALSE;

	/* FIXME: potential performance problem with big messages:
	   FETCH BODY[1]<100000..1024>, hopefully no clients do this */
	return message_send(ctx->output, input, &part->body_size,
			    sect->skip, sect->max_size);
}

/* fetch BODY[1.2.MIME|HEADER...] */
static int fetch_part_header(struct message_part *part, const char *section,
			     struct mail_fetch_body_data *sect,
			     struct fetch_context *ctx, const char *prefix)
{
	struct istream *input;

	if (!imap_msgcache_get_data(ctx->cache, &input))
		return FALSE;

	i_stream_skip(input, part->physical_pos);
	return fetch_header_from(input, ctx->output, prefix, &part->header_size,
				 section, sect);
}

static int fetch_part(struct mail_fetch_body_data *sect,
		      struct fetch_context *ctx, const char *prefix)
{
	struct message_part *part;
	const char *section;

	part = part_find(sect, ctx, &section);
	if (part == NULL)
		return FALSE;

	if (*section == '\0' || strcasecmp(section, "TEXT") == 0)
		return fetch_part_body(part, sect, ctx, prefix);

	if (strncasecmp(section, "HEADER", 6) == 0)
		return fetch_part_header(part, section, sect, ctx, prefix);
	if (strcasecmp(section, "MIME") == 0)
		return fetch_part_header(part, section, sect, ctx, prefix);

	return FALSE;
}

int index_fetch_body_section(struct mail_index_record *rec,
			     struct mail_fetch_body_data *sect,
			     struct fetch_context *ctx)
{
	const char *prefix;

	prefix = !sect->skip_set ?
		t_strdup_printf(" BODY[%s]", sect->section) :
		t_strdup_printf(" BODY[%s]<%"PRIuUOFF_T">",
				sect->section, sect->skip);
	if (ctx->first) {
		prefix++; ctx->first = FALSE;
	}

	if (*sect->section == '\0')
		return fetch_body(rec, sect, ctx, prefix, TRUE);
	if (strcasecmp(sect->section, "TEXT") == 0)
		return fetch_body(rec, sect, ctx, prefix, FALSE);
	if (strncasecmp(sect->section, "HEADER", 6) == 0)
		return fetch_header(sect, ctx, prefix);
	if (*sect->section >= '0' && *sect->section <= '9')
		return fetch_part(sect, ctx, prefix);

	/* FIXME: point the error to user */
	return FALSE;
}