view src/imap/cmd-fetch.c @ 988:8028c4dcf38f HEAD

mail-storage.h interface changes, affects pretty much everything. FETCH, SEARCH, SORT and THREAD handling were pretty much moved from lib-storage/ to imap/ so adding non-index storages would be much easier now. Also POP3 server can now be easily implemented with lib-storage. Not too well tested, and at least one major problem: partial fetching is _slow_.
author Timo Sirainen <tss@iki.fi>
date Mon, 20 Jan 2003 16:52:51 +0200
parents fd8888f6f037
children cc02c93d254e
line wrap: on
line source

/* Copyright (C) 2002 Timo Sirainen */

#include "common.h"
#include "commands.h"
#include "imap-fetch.h"

/* Parse next digits in string into integer. Returns FALSE if the integer
   becomes too big and wraps. */
static int read_uoff_t(char **p, uoff_t *value)
{
	uoff_t prev;

	*value = 0;
	while (**p >= '0' && **p <= '9') {
		prev = *value;
		*value = *value * 10 + (**p - '0');

		if (*value < prev)
			return FALSE;

		(*p)++;
	}

	return TRUE;
}

static int check_header_section(const char *section)
{
	/* HEADER, HEADER.FIELDS (list), HEADER.FIELDS.NOT (list) */
	if (*section == '\0')
		return TRUE;

	if (strncmp(section, ".FIELDS", 7) != 0)
		return FALSE;

	section += 7;
	if (strncmp(section, ".NOT", 4) == 0)
		section += 4;

	while (*section == ' ') section++;
	if (*section++ != '(')
		return FALSE;

	while (*section != '\0' && *section != ')') {
		if (*section == '(')
			return FALSE;
		section++;
	}

	if (*section++ != ')')
		return FALSE;

	if (*section != '\0')
		return FALSE;
	return TRUE;
}

static int check_section(struct client *client, const char *section,
			 enum mail_fetch_field *fetch_data)
{
	if (*section == '\0') {
		*fetch_data |= MAIL_FETCH_STREAM_HEADER |
			MAIL_FETCH_STREAM_BODY;
		return TRUE;
	}

	if (strcmp(section, "TEXT") == 0) {
		*fetch_data |= MAIL_FETCH_STREAM_BODY;
		return TRUE;
	}

	if (strncmp(section, "HEADER", 6) == 0) {
		*fetch_data |= MAIL_FETCH_STREAM_HEADER;
		if (check_header_section(section+6))
			return TRUE;
	} else if (*section >= '0' && *section <= '9') {
		*fetch_data |= MAIL_FETCH_STREAM_BODY |
			MAIL_FETCH_MESSAGE_PARTS;

		while ((*section >= '0' && *section <= '9') ||
		       *section == '.') section++;

		if (*section == '\0')
			return TRUE;
		if (strcmp(section, "MIME") == 0 ||
		    strcmp(section, "TEXT") == 0)
			return TRUE;

		if (strncmp(section, "HEADER", 6) == 0 &&
		    check_header_section(section+6))
			return TRUE;
	}

	client_send_tagline(client, t_strconcat(
		"BAD Invalid BODY[] section: ", section, NULL));
	return FALSE;
}

/* BODY[] and BODY.PEEK[] items. item points to next character after '[' */
static int parse_body_section(struct client *client, const char *item, int peek,
			      enum mail_fetch_field *fetch_data,
			      struct imap_fetch_body_data ***bodies)
{
	/* @UNSAFE */
	struct imap_fetch_body_data *body;
	uoff_t num;
	char *p;

	body = t_new(struct imap_fetch_body_data, 1);
	body->peek = peek;

	p = t_strdup_noconst(item);

	/* read section */
	body->section = p;
	for (; *p != ']'; p++) {
		if (*p == '\0') {
			client_send_tagline(client, t_strconcat(
				"BAD Missing ']' with ", item, NULL));
			return FALSE;
		}
	}
	*p++ = '\0';

	if (!check_section(client, body->section, fetch_data))
		return FALSE;

	/* <start.end> */
	body->skip = 0;
	body->max_size = (uoff_t)-1;
	if (*p != '<' && *p != '\0') {
		client_send_tagline(client, t_strconcat(
			"BAD Unexpected character after ']' with ",
			item, NULL));
	} else if (*p == '<') {
		/* read start */
		p++;

		body->skip_set = TRUE;
		if (!read_uoff_t(&p, &num) || num > OFF_T_MAX) {
			/* wrapped */
			client_send_tagline(client, t_strconcat(
				"BAD Too big partial start with ", item, NULL));
			return FALSE;
		}
		body->skip = num;

		if (*p == '.') {
			/* read end */
			p++;
			if (!read_uoff_t(&p, &num) || num > OFF_T_MAX) {
				/* wrapped */
				client_send_tagline(client, t_strconcat(
					"BAD Too big partial end with ",
					item, NULL));
				return FALSE;
			}

                        body->max_size = num;
		}

		if (*p != '>') {
			client_send_tagline(client, t_strconcat(
				"BAD Invalid partial ", item, NULL));
			return FALSE;
		}
	}

	**bodies = body;
	*bodies = &body->next;
	return TRUE;
}

static int parse_arg(struct client *client, struct imap_arg *arg,
		     enum mail_fetch_field *fetch_data,
		     enum imap_fetch_field *imap_data,
		     struct imap_fetch_body_data ***bodies)
{
	char *item;

	if (arg->type != IMAP_ARG_ATOM) {
		client_send_command_error(client,
					  "FETCH list contains non-atoms.");
		return FALSE;
	}

	item = str_ucase(IMAP_ARG_STR(arg));

	switch (*item) {
	case 'A':
		if (strcmp(item, "ALL") == 0) {
			*fetch_data |= MAIL_FETCH_FLAGS |
				MAIL_FETCH_RECEIVED_DATE |
				MAIL_FETCH_SIZE |
				MAIL_FETCH_IMAP_ENVELOPE;
		} else
			item = NULL;
		break;
	case 'B':
		/* all start with BODY so skip it */
		if (strncmp(item, "BODY", 4) != 0) {
			item = NULL;
			break;
		}
		item += 4;

		if (*item == '\0') {
			/* BODY */
			*fetch_data |= MAIL_FETCH_IMAP_BODY;
		} else if (*item == '[') {
			/* BODY[...] */
			if (!parse_body_section(client, item+1, FALSE,
						fetch_data, bodies))
				return FALSE;
		} else if (strncmp(item, ".PEEK[", 6) == 0) {
			/* BODY.PEEK[..] */
			if (!parse_body_section(client, item+6, TRUE,
						fetch_data, bodies))
				return FALSE;
		} else if (strcmp(item, "STRUCTURE") == 0) {
			/* BODYSTRUCTURE */
			*fetch_data |= MAIL_FETCH_IMAP_BODYSTRUCTURE;
		} else
			item = NULL;
		break;
	case 'E':
		if (strcmp(item, "ENVELOPE") == 0)
			*fetch_data |= MAIL_FETCH_IMAP_ENVELOPE;
		else
			item = NULL;
		break;
	case 'F':
		if (strcmp(item, "FLAGS") == 0)
			*fetch_data |= MAIL_FETCH_FLAGS;
		else if (strcmp(item, "FAST") == 0) {
			*fetch_data |= MAIL_FETCH_FLAGS |
				MAIL_FETCH_RECEIVED_DATE |
				MAIL_FETCH_SIZE;
		} else if (strcmp(item, "FULL") == 0) {
			*fetch_data |= MAIL_FETCH_FLAGS |
				MAIL_FETCH_RECEIVED_DATE |
				MAIL_FETCH_SIZE |
				MAIL_FETCH_IMAP_ENVELOPE |
				MAIL_FETCH_IMAP_BODY;
		} else
			item = NULL;
		break;
	case 'I':
		if (strcmp(item, "INTERNALDATE") == 0)
			*fetch_data |= MAIL_FETCH_RECEIVED_DATE;
		else
			item = NULL;
		break;
	case 'R':
		/* all start with RFC822 so skip it */
		if (strncmp(item, "RFC822", 6) != 0) {
			item = NULL;
			break;
		}
		item += 6;

		if (*item == '\0') {
			/* RFC822 */
			*fetch_data |= MAIL_FETCH_STREAM_HEADER |
				MAIL_FETCH_STREAM_BODY;
			*imap_data |= IMAP_FETCH_RFC822;
			break;
		}

		/* only items beginning with "RFC822." left */
		if (*item != '.') {
			item = NULL;
			break;
		}
		item++;

		if (strcmp(item, "HEADER") == 0) {
			*fetch_data |= MAIL_FETCH_STREAM_HEADER;
			*imap_data |= IMAP_FETCH_RFC822_HEADER;
		} else if (strcmp(item, "TEXT") == 0) {
			*fetch_data |= MAIL_FETCH_STREAM_BODY;
			*imap_data |= IMAP_FETCH_RFC822_TEXT;
		} else if (strcmp(item, "SIZE") == 0)
			*fetch_data |= MAIL_FETCH_SIZE;
		else
			item = NULL;
		break;
	case 'U':
		if (strcmp(item, "UID") == 0)
			*imap_data |= IMAP_FETCH_UID;
		else
			item = NULL;
		break;
	default:
		item = NULL;
		break;
	}

	if (item == NULL) {
		/* unknown item */
		client_send_tagline(client, t_strconcat(
			"BAD Invalid item ", IMAP_ARG_STR(arg), NULL));
		return FALSE;
	}

	return TRUE;
}

int cmd_fetch(struct client *client)
{
	struct imap_arg *args, *listargs;
	enum mail_fetch_field fetch_data;
	enum imap_fetch_field imap_data;
	struct imap_fetch_body_data *bodies, **bodies_p;
	const char *messageset;
	int ret;

	if (!client_read_args(client, 2, 0, &args))
		return FALSE;

	if (!client_verify_open_mailbox(client))
		return TRUE;

	messageset = imap_arg_string(&args[0]);
	if (messageset == NULL ||
	    (args[1].type != IMAP_ARG_LIST && args[1].type != IMAP_ARG_ATOM)) {
		client_send_command_error(client, "Invalid FETCH arguments.");
		return TRUE;
	}

	/* parse items argument */
	fetch_data = 0; imap_data = 0; bodies = NULL; bodies_p = &bodies;
	if (args[1].type == IMAP_ARG_ATOM) {
		if (!parse_arg(client, &args[1], &fetch_data,
			       &imap_data, &bodies_p))
			return TRUE;
	} else {
		listargs = IMAP_ARG_LIST(&args[1])->args;
		while (listargs->type != IMAP_ARG_EOL) {
			if (!parse_arg(client, listargs, &fetch_data,
				       &imap_data, &bodies_p))
				return TRUE;

			listargs++;
		}
	}

	ret = imap_fetch(client, fetch_data, imap_data,
			 bodies, messageset, client->cmd_uid);
	if (ret >= 0) {
		/* NOTE: syncing isn't allowed here */
                client_sync_without_expunges(client);
		client_send_tagline(client, ret > 0 ? "OK Fetch completed." :
			"NO Some of the requested messages no longer exist.");
	} else {
		client_send_storage_error(client);
	}

	return TRUE;
}