view src/lib-storage/mail-search.c @ 953:411006be3c66 HEAD

Naming change for function typedefs.
author Timo Sirainen <tss@iki.fi>
date Sat, 11 Jan 2003 21:55:56 +0200
parents fd8888f6f037
children 8028c4dcf38f
line wrap: on
line source

/* Copyright (C) 2002 Timo Sirainen */

#include "lib.h"
#include "mail-search.h"

struct search_build_data {
	pool_t pool;
	const char *error;
};

static struct mail_search_arg *
search_arg_new(pool_t pool, enum mail_search_arg_type type)
{
	struct mail_search_arg *arg;

	arg = p_new(pool, struct mail_search_arg, 1);
	arg->type = type;

	return arg;
}

#define ARG_NEW(type, value) \
	arg_new(data, args, next_sarg, type, value)

static int arg_new(struct search_build_data *data, struct imap_arg **args,
		   struct mail_search_arg **next_sarg,
		   enum mail_search_arg_type type, int value)
{
	struct mail_search_arg *sarg;

	*next_sarg = sarg = search_arg_new(data->pool, type);
	if (value == 0)
		return TRUE;

	/* first arg */
	if ((*args)->type == IMAP_ARG_EOL) {
		data->error = "Missing parameter for argument";
		return FALSE;
	}

	if ((*args)->type != IMAP_ARG_ATOM &&
	    (*args)->type != IMAP_ARG_STRING) {
		data->error = "Invalid parameter for argument";
		return FALSE;
	}

	sarg->value.str = str_ucase(IMAP_ARG_STR(*args));
	*args += 1;

	/* second arg */
	if (value == 2) {
		if ((*args)->type == IMAP_ARG_EOL) {
			data->error = "Missing parameter for argument";
			return FALSE;
		}

		if ((*args)->type != IMAP_ARG_ATOM &&
		    (*args)->type != IMAP_ARG_STRING) {
			data->error = "Invalid parameter for argument";
			return FALSE;
		}

                sarg->hdr_field_name = sarg->value.str;
		sarg->value.str = str_ucase(IMAP_ARG_STR(*args));
		*args += 1;
	}

	return TRUE;
}

static int search_arg_build(struct search_build_data *data,
			    struct imap_arg **args,
			    struct mail_search_arg **next_sarg)
{
	struct mail_search_arg **subargs;
	struct imap_arg *arg;
	char *str;

	if ((*args)->type == IMAP_ARG_EOL) {
		data->error = "Missing argument";
		return FALSE;
	}

	arg = *args;

	if (arg->type == IMAP_ARG_NIL) {
		/* NIL not allowed */
		data->error = "NIL not allowed";
		return FALSE;
	}

	if (arg->type == IMAP_ARG_LIST) {
		struct imap_arg *listargs = IMAP_ARG_LIST(arg)->args;

		*next_sarg = search_arg_new(data->pool, SEARCH_SUB);
		subargs = &(*next_sarg)->value.subargs;
		while (listargs->type != IMAP_ARG_EOL) {
			if (!search_arg_build(data, &listargs, subargs))
				return FALSE;
			subargs = &(*subargs)->next;
		}

		*args += 1;
		return TRUE;
	}

	i_assert(arg->type == IMAP_ARG_ATOM ||
		 arg->type == IMAP_ARG_STRING);

	/* string argument - get the name and jump to next */
	str = IMAP_ARG_STR(arg);
	*args += 1;
	str_ucase(str);

	switch (*str) {
	case 'A':
		if (strcmp(str, "ANSWERED") == 0)
			return ARG_NEW(SEARCH_ANSWERED, 0);
		else if (strcmp(str, "ALL") == 0)
			return ARG_NEW(SEARCH_ALL, 0);
		break;
	case 'B':
		if (strcmp(str, "BODY") == 0) {
			/* <string> */
			return ARG_NEW(SEARCH_BODY, 1);
		} else if (strcmp(str, "BEFORE") == 0) {
			/* <date> */
			return ARG_NEW(SEARCH_BEFORE, 1);
		} else if (strcmp(str, "BCC") == 0) {
			/* <string> */
			return ARG_NEW(SEARCH_BCC, 1);
		}
		break;
	case 'C':
		if (strcmp(str, "CC") == 0) {
			/* <string> */
			return ARG_NEW(SEARCH_CC, 1);
		}
		break;
	case 'D':
		if (strcmp(str, "DELETED") == 0)
			return ARG_NEW(SEARCH_DELETED, 0);
		else if (strcmp(str, "DRAFT") == 0)
			return ARG_NEW(SEARCH_DRAFT, 0);
		break;
	case 'F':
		if (strcmp(str, "FLAGGED") == 0)
			return ARG_NEW(SEARCH_FLAGGED, 0);
		else if (strcmp(str, "FROM") == 0) {
			/* <string> */
			return ARG_NEW(SEARCH_FROM, 1);
		}
		break;
	case 'H':
		if (strcmp(str, "HEADER") == 0) {
			/* <field-name> <string> */
			const char *key;

			if ((*args)->type == IMAP_ARG_EOL) {
				data->error = "Missing parameter for HEADER";
				return FALSE;
			}
			if ((*args)->type != IMAP_ARG_ATOM &&
			    (*args)->type != IMAP_ARG_STRING) {
				data->error = "Invalid parameter for HEADER";
				return FALSE;
			}

			key = str_ucase(IMAP_ARG_STR(*args));

			if (strcmp(key, "FROM") == 0) {
				*args += 1;
				return ARG_NEW(SEARCH_FROM, 1);
			} else if (strcmp(key, "TO") == 0) {
				*args += 1;
				return ARG_NEW(SEARCH_TO, 1);
			} else if (strcmp(key, "CC") == 0) {
				*args += 1;
				return ARG_NEW(SEARCH_CC, 1);
			} else if (strcmp(key, "BCC") == 0) {
				*args += 1;
				return ARG_NEW(SEARCH_BCC, 1);
			} else if (strcmp(key, "SUBJECT") == 0) {
				*args += 1;
				return ARG_NEW(SEARCH_SUBJECT, 1);
			} else if (strcmp(key, "IN-REPLY-TO") == 0) {
				*args += 1;
				return ARG_NEW(SEARCH_IN_REPLY_TO, 1);
			} else if (strcmp(key, "MESSAGE-ID") == 0) {
				*args += 1;
				return ARG_NEW(SEARCH_MESSAGE_ID, 1);
			} else {
				return ARG_NEW(SEARCH_HEADER, 2);
			}
		}
		break;
	case 'K':
		if (strcmp(str, "KEYWORD") == 0) {
			/* <flag> */
			return ARG_NEW(SEARCH_KEYWORD, 1);
		}
		break;
	case 'L':
		if (strcmp(str, "LARGER") == 0) {
			/* <n> */
			return ARG_NEW(SEARCH_LARGER, 1);
		}
		break;
	case 'N':
		if (strcmp(str, "NOT") == 0) {
			if (!search_arg_build(data, args, next_sarg))
				return FALSE;
			(*next_sarg)->not = !(*next_sarg)->not;
			return TRUE;
		} else if (strcmp(str, "NEW") == 0) {
			/* NEW == (RECENT UNSEEN) */
			*next_sarg = search_arg_new(data->pool, SEARCH_SUB);

			subargs = &(*next_sarg)->value.subargs;
			*subargs = search_arg_new(data->pool, SEARCH_RECENT);
			(*subargs)->next = search_arg_new(data->pool,
							  SEARCH_SEEN);
			(*subargs)->next->not = TRUE;
			return TRUE;
		}
		break;
	case 'O':
		if (strcmp(str, "OR") == 0) {
			/* <search-key1> <search-key2> */
			*next_sarg = search_arg_new(data->pool, SEARCH_OR);

			subargs = &(*next_sarg)->value.subargs;
			for (;;) {
				if (!search_arg_build(data, args, subargs))
					return FALSE;

				subargs = &(*subargs)->next;

				/* <key> OR <key> OR ... <key> - put them all
				   under one SEARCH_OR list. */
				if ((*args)->type == IMAP_ARG_EOL)
					break;

				if ((*args)->type != IMAP_ARG_ATOM ||
				    strcasecmp(IMAP_ARG_STR(*args), "OR") != 0)
					break;

				*args += 1;
			}

			if (!search_arg_build(data, args, subargs))
				return FALSE;
			return TRUE;
		} if (strcmp(str, "ON") == 0) {
			/* <date> */
			return ARG_NEW(SEARCH_ON, 1);
		} if (strcmp(str, "OLD") == 0) {
			/* OLD == NOT RECENT */
			if (!ARG_NEW(SEARCH_RECENT, 0))
				return FALSE;

			(*next_sarg)->not = TRUE;
			return TRUE;
		}
		break;
	case 'R':
		if (strcmp(str, "RECENT") == 0)
			return ARG_NEW(SEARCH_RECENT, 0);
		break;
	case 'S':
		if (strcmp(str, "SEEN") == 0)
			return ARG_NEW(SEARCH_SEEN, 0);
		else if (strcmp(str, "SUBJECT") == 0) {
			/* <string> */
			return ARG_NEW(SEARCH_SUBJECT, 1);
		} else if (strcmp(str, "SENTBEFORE") == 0) {
			/* <date> */
			return ARG_NEW(SEARCH_SENTBEFORE, 1);
		} else if (strcmp(str, "SENTON") == 0) {
			/* <date> */
			return ARG_NEW(SEARCH_SENTON, 1);
		} else if (strcmp(str, "SENTSINCE") == 0) {
			/* <date> */
			return ARG_NEW(SEARCH_SENTSINCE, 1);
		} else if (strcmp(str, "SINCE") == 0) {
			/* <date> */
			return ARG_NEW(SEARCH_SINCE, 1);
		} else if (strcmp(str, "SMALLER") == 0) {
			/* <n> */
			return ARG_NEW(SEARCH_SMALLER, 1);
		}
		break;
	case 'T':
		if (strcmp(str, "TEXT") == 0) {
			/* <string> */
			return ARG_NEW(SEARCH_TEXT, 1);
		} else if (strcmp(str, "TO") == 0) {
			/* <string> */
			return ARG_NEW(SEARCH_TO, 1);
		}
		break;
	case 'U':
		if (strcmp(str, "UID") == 0) {
			/* <message set> */
			return ARG_NEW(SEARCH_UID, 1);
		} else if (strcmp(str, "UNANSWERED") == 0) {
			if (!ARG_NEW(SEARCH_ANSWERED, 0))
				return FALSE;
			(*next_sarg)->not = TRUE;
			return TRUE;
		} else if (strcmp(str, "UNDELETED") == 0) {
			if (!ARG_NEW(SEARCH_DELETED, 0))
				return FALSE;
			(*next_sarg)->not = TRUE;
			return TRUE;
		} else if (strcmp(str, "UNDRAFT") == 0) {
			if (!ARG_NEW(SEARCH_DRAFT, 0))
				return FALSE;
			(*next_sarg)->not = TRUE;
			return TRUE;
		} else if (strcmp(str, "UNFLAGGED") == 0) {
			if (!ARG_NEW(SEARCH_FLAGGED, 0))
				return FALSE;
			(*next_sarg)->not = TRUE;
			return TRUE;
		} else if (strcmp(str, "UNKEYWORD") == 0) {
			if (!ARG_NEW(SEARCH_KEYWORD, 0))
				return FALSE;
			(*next_sarg)->not = TRUE;
			return TRUE;
		} else if (strcmp(str, "UNSEEN") == 0) {
			if (!ARG_NEW(SEARCH_SEEN, 0))
				return FALSE;
			(*next_sarg)->not = TRUE;
			return TRUE;
		}
		break;
	default:
		if (*str == '*' || (*str >= '0' && *str <= '9')) {
			/* <message-set> */
			if (!ARG_NEW(SEARCH_SET, 0))
				return FALSE;

			(*next_sarg)->value.str = str;
			return TRUE;
		}
		break;
	}

	data->error = t_strconcat("Unknown argument ", str, NULL);
	return FALSE;
}

struct mail_search_arg *
mail_search_args_build(pool_t pool, struct imap_arg *args, const char **error)
{
        struct search_build_data data;
	struct mail_search_arg *first_sarg, **sargs;

	data.pool = pool;
	data.error = NULL;

	/* get the first arg */
	first_sarg = NULL; sargs = &first_sarg;
	while (args->type != IMAP_ARG_EOL) {
		if (!search_arg_build(&data, &args, sargs)) {
			*error = data.error;
			return NULL;
		}
		sargs = &(*sargs)->next;
	}

	*error = NULL;
	return first_sarg;
}

void mail_search_args_reset(struct mail_search_arg *args)
{
	while (args != NULL) {
		if (args->type == SEARCH_OR || args->type == SEARCH_SUB)
			mail_search_args_reset(args->value.subargs);
		args->result = 0;

		args = args->next;
	}
}

static void search_arg_foreach(struct mail_search_arg *arg,
			       mail_search_foreach_callback_t callback,
			       void *context)
{
	struct mail_search_arg *subarg;

	if (arg->result != 0)
		return;

	if (arg->type == SEARCH_SUB) {
		/* sublist of conditions */
		i_assert(arg->value.subargs != NULL);

		arg->result = 1;
		subarg = arg->value.subargs;
		while (subarg != NULL) {
			if (subarg->result == 0)
				search_arg_foreach(subarg, callback, context);

			if (subarg->result == -1) {
				/* failed */
				arg->result = -1;
				break;
			}

			if (subarg->result == 0)
				arg->result = 0;

			subarg = subarg->next;
		}
	} else if (arg->type == SEARCH_OR) {
		/* OR-list of conditions */
		i_assert(arg->value.subargs != NULL);

		subarg = arg->value.subargs;
		arg->result = -1;
		while (subarg != NULL) {
			if (subarg->result == 0)
				search_arg_foreach(subarg, callback, context);

			if (subarg->result == 1) {
				/* matched */
				arg->result = 1;
				break;
			}

			if (subarg->result == 0)
				arg->result = 0;

			subarg = subarg->next;
		}
	} else {
		/* just a single condition */
		callback(arg, context);
	}
}

int mail_search_args_foreach(struct mail_search_arg *args,
			     mail_search_foreach_callback_t callback,
			     void *context)
{
	int result;

	result = 1;
	for (; args != NULL; args = args->next) {
		search_arg_foreach(args, callback, context);

		if (args->result == -1) {
			/* failed, abort */
			return -1;
		}

		if (args->result == 0)
			result = 0;
	}

	return result;
}

static void search_arg_analyze(struct mail_search_arg *arg, int *have_headers,
			       int *have_body, int *have_text)
{
	struct mail_search_arg *subarg;

	if (arg->result != 0)
		return;

	switch (arg->type) {
	case SEARCH_OR:
	case SEARCH_SUB:
		subarg = arg->value.subargs;
		while (subarg != NULL) {
			if (subarg->result == 0) {
				search_arg_analyze(subarg, have_headers,
						   have_body, have_text);
			}

			subarg = subarg->next;
		}
		break;
	case SEARCH_SENTBEFORE:
	case SEARCH_SENTON:
	case SEARCH_SENTSINCE:
	case SEARCH_FROM:
	case SEARCH_TO:
	case SEARCH_CC:
	case SEARCH_BCC:
	case SEARCH_SUBJECT:
	case SEARCH_IN_REPLY_TO:
	case SEARCH_MESSAGE_ID:
	case SEARCH_HEADER:
		*have_headers = TRUE;
		break;
	case SEARCH_BODY:
		*have_body = TRUE;
		break;
	case SEARCH_TEXT:
		*have_text = TRUE;
		break;
	default:
		break;
	}
}

void mail_search_args_analyze(struct mail_search_arg *args, int *have_headers,
			      int *have_body, int *have_text)
{
	*have_headers = *have_body = *have_text = FALSE;

	for (; args != NULL; args = args->next)
		search_arg_analyze(args, have_headers, have_body, have_text);
}