/* Copyright (c) Pigeonhole authors, see top-level COPYING file */

#include "lib.h"
#include "str.h"
#include "str-sanitize.h"
#include "array.h"
#include "buffer.h"
#include "mempool.h"
#include "hash.h"

#include "sieve-common.h"
#include "sieve-extensions.h"
#include "sieve-script.h"
#include "sieve-ast.h"
#include "sieve-commands.h"
#include "sieve-validator.h"

#include "sieve-comparators.h"
#include "sieve-address-parts.h"

/*
 * Forward declarations
 */

static void
sieve_validator_register_core_commands(struct sieve_validator *valdtr);
static void
sieve_validator_register_core_tests(struct sieve_validator *valdtr);

/*
 * Types
 */

/* Tag registration */

struct sieve_tag_registration {
	const struct sieve_argument_def *tag_def;
	const struct sieve_extension *ext;

	const char *identifier;
	int id_code;
};

/* Command registration */

struct sieve_command_registration {
	const struct sieve_command_def *cmd_def;
	const struct sieve_extension *ext;

	ARRAY(struct sieve_tag_registration *) normal_tags;
	ARRAY(struct sieve_tag_registration *) instanced_tags;
	ARRAY(struct sieve_tag_registration *) persistent_tags;
};

/* Default (literal) arguments */

struct sieve_default_argument {
	const struct sieve_argument_def *arg_def;
	const struct sieve_extension *ext;

	struct sieve_default_argument *overrides;
};

/*
 * Validator extension
 */

struct sieve_validator_extension_reg {
	const struct sieve_validator_extension *valext;
	const struct sieve_extension *ext;
	struct sieve_ast_argument *arg;
	void *context;

	bool loaded:1;
	bool required:1;
};

/*
 * Validator
 */

struct sieve_validator {
	pool_t pool;

	struct sieve_instance *svinst;
	struct sieve_ast *ast;
	struct sieve_script *script;
	enum sieve_compile_flags flags;

	struct sieve_error_handler *ehandler;

	bool finished_require;

	/* Registries */

	HASH_TABLE(const char *, struct sieve_command_registration *) commands;

	ARRAY(struct sieve_validator_extension_reg) extensions;

	/* This is currently a wee bit ugly and needs more thought */
	struct sieve_default_argument default_arguments[SAT_COUNT];

	/* Default argument processing state (FIXME: ugly) */
	struct sieve_default_argument *current_defarg;
	enum sieve_argument_type current_defarg_type;
	bool current_defarg_constant;

	bool failed:1;
};

/*
 * Validator object
 */

struct sieve_validator *
sieve_validator_create(struct sieve_ast *ast,
		       struct sieve_error_handler *ehandler,
		       enum sieve_compile_flags flags)
{
	pool_t pool;
	struct sieve_validator *valdtr;
	const struct sieve_extension *const *ext_preloaded;
	unsigned int i, ext_count;

	pool = pool_alloconly_create("sieve_validator", 16384);
	valdtr = p_new(pool, struct sieve_validator, 1);
	valdtr->pool = pool;

	valdtr->ehandler = ehandler;
	sieve_error_handler_ref(ehandler);

	valdtr->ast = ast;
	sieve_ast_ref(ast);

	valdtr->script = sieve_ast_script(ast);
	valdtr->svinst = sieve_script_svinst(valdtr->script);
	valdtr->flags = flags;

	/* Setup default arguments */
	valdtr->default_arguments[SAT_NUMBER].arg_def = &number_argument;
	valdtr->default_arguments[SAT_NUMBER].ext = NULL;
	valdtr->default_arguments[SAT_VAR_STRING].arg_def = &string_argument;
	valdtr->default_arguments[SAT_VAR_STRING].ext = NULL;
	valdtr->default_arguments[SAT_CONST_STRING].arg_def = &string_argument;
	valdtr->default_arguments[SAT_CONST_STRING].ext = NULL;
	valdtr->default_arguments[SAT_STRING_LIST].arg_def = &string_list_argument;
	valdtr->default_arguments[SAT_STRING_LIST].ext = NULL;

	/* Setup storage for extension contexts */
	p_array_init(&valdtr->extensions, pool,
		     sieve_extensions_get_count(valdtr->svinst));

	/* Setup command registry */
	hash_table_create(&valdtr->commands, pool, 0, strcase_hash, strcasecmp);
	sieve_validator_register_core_commands(valdtr);
	sieve_validator_register_core_tests(valdtr);

	/* Pre-load core language features implemented as 'extensions' */
	ext_preloaded =
		sieve_extensions_get_preloaded(valdtr->svinst, &ext_count);
	for (i = 0; i < ext_count; i++) {
		const struct sieve_extension_def *ext_def =
			ext_preloaded[i]->def;

		if (ext_def != NULL && ext_def->validator_load != NULL)
			(void)ext_def->validator_load(ext_preloaded[i], valdtr);
	}

	return valdtr;
}

void sieve_validator_free(struct sieve_validator **valdtr)
{
	const struct sieve_validator_extension_reg *extrs;
	unsigned int ext_count, i;

	hash_table_destroy(&(*valdtr)->commands);
	sieve_ast_unref(&(*valdtr)->ast);

	sieve_error_handler_unref(&(*valdtr)->ehandler);

	/* Signal registered extensions that the validator is being destroyed */
	extrs = array_get(&(*valdtr)->extensions, &ext_count);
	for (i = 0; i < ext_count; i++) {
		if (extrs[i].valext != NULL && extrs[i].valext->free != NULL)
			extrs[i].valext->free(extrs[i].ext, *valdtr,
					      extrs[i].context);
	}

	pool_unref(&(*valdtr)->pool);

	*valdtr = NULL;
}

/*
 * Accessors
 */

// FIXME: build validate environment

pool_t sieve_validator_pool(struct sieve_validator *valdtr)
{
	return valdtr->pool;
}

struct sieve_error_handler *
sieve_validator_error_handler(struct sieve_validator *valdtr)
{
	return valdtr->ehandler;
}

struct sieve_ast *sieve_validator_ast(struct sieve_validator *valdtr)
{
	return valdtr->ast;
}

struct sieve_script *sieve_validator_script(struct sieve_validator *valdtr)
{
	return valdtr->script;
}

const char *sieve_validator_script_cause(struct sieve_validator *valdtr)
{
	return sieve_script_cause(valdtr->script);
}

struct sieve_instance *sieve_validator_svinst(struct sieve_validator *valdtr)
{
	return valdtr->svinst;
}

enum sieve_compile_flags
sieve_validator_compile_flags(struct sieve_validator *valdtr)
{
	return valdtr->flags;
}

bool sieve_validator_failed(struct sieve_validator *valdtr)
{
	return valdtr->failed;
}

/*
 * Command registry
 */

/* Dummy command object to mark unknown commands in the registry */

static bool _cmd_unknown_validate(struct sieve_validator *valdtr ATTR_UNUSED,
				  struct sieve_command *cmd ATTR_UNUSED)
{
	i_unreached();
}

static const struct sieve_command_def unknown_command = {
	.identifier = "",
	.type = SCT_NONE,
	.positional_args = 0,
	.subtests = 0,
	.block_allowed = FALSE,
	.block_required = FALSE,
	.validate = _cmd_unknown_validate
};

/* Registration of the core commands of the language */

static void
sieve_validator_register_core_tests(struct sieve_validator *valdtr)
{
	unsigned int i;

	for (i = 0; i < sieve_core_tests_count; i++) {
		sieve_validator_register_command(valdtr, NULL,
						 sieve_core_tests[i]);
	}
}

static void
sieve_validator_register_core_commands(struct sieve_validator *valdtr)
{
	unsigned int i;

	for (i = 0; i < sieve_core_commands_count; i++) {
		sieve_validator_register_command(valdtr, NULL,
						 sieve_core_commands[i]);
	}
}

/* Registry functions */

static struct sieve_command_registration *
sieve_validator_find_command_registration(struct sieve_validator *valdtr,
					  const char *command)
{
	return hash_table_lookup(valdtr->commands, command);
}

static struct sieve_command_registration *
_sieve_validator_register_command(struct sieve_validator *valdtr,
				  const struct sieve_extension *ext,
				  const struct sieve_command_def *cmd_def,
				  const char *identifier)
{
	struct sieve_command_registration *cmd_reg =
		p_new(valdtr->pool, struct sieve_command_registration, 1);

	cmd_reg->cmd_def = cmd_def;
	cmd_reg->ext = ext;

	hash_table_insert(valdtr->commands, identifier, cmd_reg);

	return cmd_reg;
}

void sieve_validator_register_command(struct sieve_validator *valdtr,
				      const struct sieve_extension *ext,
				      const struct sieve_command_def *cmd_def)
{
	struct sieve_command_registration *cmd_reg =
		sieve_validator_find_command_registration(
			valdtr, cmd_def->identifier);

	if (cmd_reg == NULL) {
		cmd_reg = _sieve_validator_register_command(
			valdtr, ext, cmd_def, cmd_def->identifier);
	} else {
		cmd_reg->cmd_def = cmd_def;
		cmd_reg->ext = ext;
	}

	if (cmd_def->registered != NULL)
		cmd_def->registered(valdtr, ext, cmd_reg);
}

static void
sieve_validator_register_unknown_command(struct sieve_validator *valdtr,
					 const char *command)
{
	struct sieve_command_registration *cmd_reg =
		sieve_validator_find_command_registration(valdtr, command);

	if (cmd_reg == NULL) {
		(void)_sieve_validator_register_command(
			valdtr, NULL, &unknown_command, command);
	} else {
		i_assert(cmd_reg->cmd_def == NULL);
		cmd_reg->cmd_def = &unknown_command;
	}
}

/*const struct sieve_command *sieve_validator_find_command
(struct sieve_validator *valdtr, const char *command)
{
  struct sieve_command_registration *cmd_reg =
  	sieve_validator_find_command_registration(valdtr, command);

  return ( record == NULL ? NULL : record->command );
}*/

/*
 * Per-command tagged argument registry
 */

/* Dummy argument object to mark unknown arguments in the registry */

static bool
_unknown_tag_validate(struct sieve_validator *valdtr ATTR_UNUSED,
		      struct sieve_ast_argument **arg ATTR_UNUSED,
		      struct sieve_command *tst ATTR_UNUSED)
{
	i_unreached();
}

static const struct sieve_argument_def _unknown_tag = {
	.identifier = "",
	.validate = _unknown_tag_validate,
};

static inline bool
_tag_registration_is_unknown(struct sieve_tag_registration *tag_reg)
{
	return (tag_reg != NULL && tag_reg->tag_def == &_unknown_tag);
}

/* Registry functions */

static void
_sieve_validator_register_tag(struct sieve_validator *valdtr,
			      struct sieve_command_registration *cmd_reg,
			      const struct sieve_extension *ext,
			      const struct sieve_argument_def *tag_def,
			      const char *identifier, int id_code)
{
	struct sieve_tag_registration *reg;

	reg = p_new(valdtr->pool, struct sieve_tag_registration, 1);
	reg->ext = ext;
	reg->tag_def = tag_def;
	reg->id_code = id_code;
	if (identifier == NULL)
		reg->identifier = tag_def->identifier;
	else
		reg->identifier = p_strdup(valdtr->pool, identifier);

	if (!array_is_created(&cmd_reg->normal_tags))
		p_array_init(&cmd_reg->normal_tags, valdtr->pool, 4);

	array_append(&cmd_reg->normal_tags, &reg, 1);
}

void sieve_validator_register_persistent_tag(
	struct sieve_validator *valdtr, const char *command,
	const struct sieve_extension *ext,
	const struct sieve_argument_def *tag_def)
{
	/* Add the tag to the persistent tags list if necessary */
	if (tag_def->validate_persistent != NULL) {
		struct sieve_command_registration *cmd_reg =
			sieve_validator_find_command_registration(
				valdtr, command);

		if (cmd_reg == NULL) {
			cmd_reg = _sieve_validator_register_command(
				valdtr, NULL, NULL, command);
		}

		struct sieve_tag_registration *reg;

		if (!array_is_created(&cmd_reg->persistent_tags)) {
			p_array_init(&cmd_reg->persistent_tags,
				     valdtr->pool, 4);
		} else {
			struct sieve_tag_registration *reg_idx;

			/* Avoid dupplicate registration */
			array_foreach_elem(&cmd_reg->persistent_tags, reg_idx) {
				if (reg_idx->tag_def == tag_def)
					return;
			}
		}

		reg = p_new(valdtr->pool, struct sieve_tag_registration, 1);
		reg->ext = ext;
		reg->tag_def = tag_def;
		reg->id_code = -1;

		array_append(&cmd_reg->persistent_tags, &reg, 1);
	}
}

void sieve_validator_register_external_tag(
	struct sieve_validator *valdtr, const char *command,
	const struct sieve_extension *ext,
	const struct sieve_argument_def *tag_def, int id_code)
{
	struct sieve_command_registration *cmd_reg =
		sieve_validator_find_command_registration(valdtr, command);

	if (cmd_reg == NULL) {
		cmd_reg = _sieve_validator_register_command(
			valdtr, NULL, NULL, command);
	}

	_sieve_validator_register_tag(valdtr, cmd_reg, ext, tag_def,
				      NULL, id_code);
}

void sieve_validator_register_tag(
	struct sieve_validator *valdtr,
	struct sieve_command_registration *cmd_reg,
	const struct sieve_extension *ext,
	const struct sieve_argument_def *tag_def, int id_code)
{
	if (tag_def->is_instance_of == NULL) {
		_sieve_validator_register_tag(valdtr, cmd_reg, ext, tag_def,
					      NULL, id_code);
	} else {
		struct sieve_tag_registration *reg =
			p_new(valdtr->pool, struct sieve_tag_registration, 1);
		reg->ext = ext;
		reg->tag_def = tag_def;
		reg->id_code = id_code;

		if (!array_is_created(&cmd_reg->instanced_tags))
			p_array_init(&cmd_reg->instanced_tags, valdtr->pool, 4);

		array_append(&cmd_reg->instanced_tags, &reg, 1);
	}
}

static void
sieve_validator_register_unknown_tag(struct sieve_validator *valdtr,
				     struct sieve_command_registration *cmd_reg,
				     const char *tag)
{
	_sieve_validator_register_tag(valdtr, cmd_reg, NULL,
				      &_unknown_tag, tag, 0);
}

static struct sieve_tag_registration *
_sieve_validator_command_tag_get(struct sieve_validator *valdtr,
				 struct sieve_command *cmd,
				 const char *tag, void **data)
{
	struct sieve_command_registration *cmd_reg = cmd->reg;
	struct sieve_tag_registration *const *regs;
	unsigned int i, reg_count;

	/* First check normal tags */
	if (array_is_created(&cmd_reg->normal_tags)) {
		regs = array_get(&cmd_reg->normal_tags, &reg_count);

		for (i = 0; i < reg_count; i++) {
			if (regs[i]->tag_def != NULL &&
			    strcasecmp(regs[i]->identifier, tag) == 0) {

				return regs[i];
			}
		}
	}

	/* Not found so far, try the instanced tags */
	if (array_is_created(&cmd_reg->instanced_tags)) {
		regs = array_get(&cmd_reg->instanced_tags, &reg_count);

		for (i = 0; i < reg_count; i++) {
			if (regs[i]->tag_def != NULL) {
				if (regs[i]->tag_def->is_instance_of(
					valdtr, cmd, regs[i]->ext, tag, data))
					return regs[i];
			}
		}
	}

	return NULL;
}

static bool
sieve_validator_command_tag_exists(struct sieve_validator *valdtr,
				   struct sieve_command *cmd, const char *tag)
{
	return (_sieve_validator_command_tag_get(valdtr, cmd,
						 tag, NULL) != NULL);
}

static struct sieve_tag_registration *
sieve_validator_command_tag_get(struct sieve_validator *valdtr,
				struct sieve_command *cmd,
				struct sieve_ast_argument *arg, void **data)
{
	const char *tag = sieve_ast_argument_tag(arg);

	return _sieve_validator_command_tag_get(valdtr, cmd, tag, data);
}

/*
 * Extension support
 */

static bool
sieve_validator_extensions_check_conficts(struct sieve_validator *valdtr,
					  struct sieve_ast_argument *ext_arg,
					  const struct sieve_extension *ext)
{
	struct sieve_validator_extension_reg *ext_reg;
	struct sieve_validator_extension_reg *regs;
	unsigned int count, i;

	if (ext->id < 0)
		return TRUE;

	ext_reg = array_idx_get_space(&valdtr->extensions,
				      (unsigned int) ext->id);

	regs = array_get_modifiable(&valdtr->extensions, &count);
	for (i = 0; i < count; i++) {
		bool required = ext_reg->required && regs[i].required;

		if (regs[i].ext == NULL)
			continue;
		if (regs[i].ext == ext)
			continue;
		if (!regs[i].loaded)
			continue;

		/* Check this extension vs other extension */
		if (ext_reg->valext != NULL &&
		    ext_reg->valext->check_conflict != NULL) {
			struct sieve_ast_argument *this_ext_arg =
				(ext_arg == NULL ? regs[i].arg : ext_arg);

			if (!ext_reg->valext->check_conflict(
				ext, valdtr, ext_reg->context, this_ext_arg,
				regs[i].ext, required))
				return FALSE;
		}

		/* Check other extension vs this extension */
		if (regs[i].valext != NULL &&
		    regs[i].valext->check_conflict != NULL) {
			if (!regs[i].valext->check_conflict(
				regs[i].ext, valdtr, regs[i].context,
				regs[i].arg, ext, required))
				return FALSE;
		}
	}
	return TRUE;
}

bool sieve_validator_extension_load(struct sieve_validator *valdtr,
				    struct sieve_command *cmd,
				    struct sieve_ast_argument *ext_arg,
				    const struct sieve_extension *ext,
				    bool required)
{
	const struct sieve_extension_def *extdef = ext->def;
	struct sieve_validator_extension_reg *reg = NULL;

	if (ext->global &&
	    (valdtr->flags & SIEVE_COMPILE_FLAG_NOGLOBAL) != 0) {
		const char *cmd_prefix = (cmd == NULL ? "" :
			t_strdup_printf("%s %s: ",
					sieve_command_identifier(cmd),
					sieve_command_type_name(cmd)));
		sieve_argument_validate_error(
			valdtr, ext_arg,
			"%sfailed to load Sieve capability '%s': "
			"its use is restricted to global scripts",
			cmd_prefix, sieve_extension_name(ext));
		return FALSE;
	}

	/* Register extension no matter what and store the
	 * AST argument registering it */
	if (ext->id >= 0) {
		reg = array_idx_get_space(&valdtr->extensions,
					  (unsigned int)ext->id);
		i_assert(reg->ext == NULL || reg->ext == ext);
		reg->ext = ext;
		reg->required = reg->required || required;
		if (reg->arg == NULL)
			reg->arg = ext_arg;

		if (reg->loaded) {
			/* Already loaded successfully before, e.g. because the
			   script requires the same extension more than once.
			   Loading it again would duplicate the validator state
			   it registers, such as the default argument override
			   chain, which is walked recursively while validating
			   arguments. */
			sieve_ast_extension_link(valdtr->ast, ext,
						 reg->required);
			return TRUE;
		}
	}

	if (extdef->validator_load != NULL &&
	    !extdef->validator_load(ext, valdtr)) {
		const char *cmd_prefix = (cmd == NULL ? "" :
			t_strdup_printf("%s %s: ",
					sieve_command_identifier(cmd),
					sieve_command_type_name(cmd)));
		sieve_argument_validate_error(
			valdtr, ext_arg,
			"%sfailed to load Sieve capability '%s'",
			cmd_prefix, sieve_extension_name(ext));
		return FALSE;
	}

	/* Check conflicts with other extensions */
	if (!sieve_validator_extensions_check_conficts(valdtr, ext_arg, ext))
		return FALSE;

	/* Link extension to AST for use at code generation */
	if (reg != NULL) {
		sieve_ast_extension_link(valdtr->ast, ext, reg->required);
		reg->loaded = TRUE;
	}

	return TRUE;
}

const struct sieve_extension *
sieve_validator_extension_load_by_name(struct sieve_validator *valdtr,
				       struct sieve_command *cmd,
				       struct sieve_ast_argument *ext_arg,
				       const char *ext_name)
{
	const struct sieve_extension *ext;

	ext = sieve_extension_get_by_name(valdtr->svinst, ext_name);

	if (ext == NULL || ext->def == NULL || !ext->enabled) {
		unsigned int i;
		bool core_test = FALSE;
		bool core_command = FALSE;

		for (i = 0; !core_command && i < sieve_core_commands_count;
		     i++) {
			if (strcasecmp(sieve_core_commands[i]->identifier,
				       ext_name) == 0)
				core_command = TRUE;
		}

		for (i = 0; !core_test && i < sieve_core_tests_count; i++) {
			if (strcasecmp(sieve_core_tests[i]->identifier,
				       ext_name) == 0)
				core_test = TRUE;
		}

		if (core_test || core_command) {
			sieve_argument_validate_error(
				valdtr, ext_arg,
				"%s %s: '%s' is not known as a Sieve capability, "
				"but it is known as a Sieve %s that is always available",
				sieve_command_identifier(cmd),
				sieve_command_type_name(cmd),
				str_sanitize(ext_name, 128),
				(core_test ? "test" : "command"));
		} else {
			sieve_argument_validate_error(
				valdtr, ext_arg,
				"%s %s: unknown Sieve capability '%s'",
				sieve_command_identifier(cmd),
				sieve_command_type_name(cmd),
				str_sanitize(ext_name, 128));
		}
		return NULL;
	}

	if (!sieve_validator_extension_load(valdtr, cmd, ext_arg, ext, TRUE))
		return NULL;

	return ext;
}

const struct sieve_extension *
sieve_validator_extension_load_implicit(struct sieve_validator *valdtr,
					const char *ext_name)
{
	const struct sieve_extension *ext;

	ext = sieve_extension_get_by_name(valdtr->svinst, ext_name);

	if (ext == NULL || ext->def == NULL)
		return NULL;

	if (!sieve_validator_extension_load(valdtr, NULL, NULL, ext, TRUE))
		return NULL;

	return ext;
}

void sieve_validator_extension_register(
	struct sieve_validator *valdtr,	const struct sieve_extension *ext,
	const struct sieve_validator_extension *valext, void *context)
{
	struct sieve_validator_extension_reg *reg;

	if (ext->id < 0)
		return;

	reg = array_idx_get_space(&valdtr->extensions, (unsigned int) ext->id);
	i_assert(reg->ext == NULL || reg->ext == ext);
	reg->ext = ext;
	reg->valext = valext;
	reg->context = context;
}

bool sieve_validator_extension_loaded(struct sieve_validator *valdtr,
				      const struct sieve_extension *ext)
{
	const struct sieve_validator_extension_reg *reg;

	if (ext->id < 0 || ext->id >= (int) array_count(&valdtr->extensions))
		return FALSE;

	reg = array_idx(&valdtr->extensions, (unsigned int) ext->id);

	return (reg->loaded);
}

void sieve_validator_extension_set_context(struct sieve_validator *valdtr,
					   const struct sieve_extension *ext,
					   void *context)
{
	struct sieve_validator_extension_reg *reg;

	if (ext->id < 0)
		return;

	reg = array_idx_get_space(&valdtr->extensions, (unsigned int) ext->id);
	reg->context = context;
}

void *sieve_validator_extension_get_context(struct sieve_validator *valdtr,
					    const struct sieve_extension *ext)
{
	const struct sieve_validator_extension_reg *reg;

	if (ext->id < 0 || ext->id >= (int) array_count(&valdtr->extensions))
		return NULL;

	reg = array_idx(&valdtr->extensions, (unsigned int) ext->id);

	return reg->context;
}

/*
 * Overriding the default literal arguments
 */

void sieve_validator_argument_override(struct sieve_validator *valdtr,
				       enum sieve_argument_type type,
				       const struct sieve_extension *ext,
				       const struct sieve_argument_def *arg_def)
{
	struct sieve_default_argument *arg;

	if (valdtr->default_arguments[type].arg_def == arg_def &&
	    valdtr->default_arguments[type].ext == ext) {
		/* Already the active override; don't extend the overrides
		   chain with a duplicate. */
		return;
	}

	if (valdtr->default_arguments[type].arg_def != NULL) {
		arg = p_new(valdtr->pool, struct sieve_default_argument, 1);
		*arg = valdtr->default_arguments[type];

		valdtr->default_arguments[type].overrides = arg;
	}

	valdtr->default_arguments[type].arg_def = arg_def;
	valdtr->default_arguments[type].ext = ext;
}

static bool
sieve_validator_argument_default_activate(struct sieve_validator *valdtr,
					  struct sieve_command *cmd,
					  struct sieve_default_argument *defarg,
					  struct sieve_ast_argument *arg)
{
	bool result = TRUE;
	struct sieve_default_argument *prev_defarg;

	prev_defarg = valdtr->current_defarg;
	valdtr->current_defarg = defarg;

	if (arg->argument == NULL) {
		arg->argument = sieve_argument_create(arg->ast, defarg->arg_def,
						      defarg->ext, 0);
	} else {
		arg->argument->def = defarg->arg_def;
		arg->argument->ext = defarg->ext;
	}

	if (defarg->arg_def != NULL && defarg->arg_def->validate != NULL)
		result = defarg->arg_def->validate(valdtr, &arg, cmd);

	valdtr->current_defarg = prev_defarg;

	return result;
}

bool sieve_validator_argument_activate_super(struct sieve_validator *valdtr,
					     struct sieve_command *cmd,
					     struct sieve_ast_argument *arg,
					     bool constant ATTR_UNUSED)
{
	struct sieve_default_argument *defarg;

	if (valdtr->current_defarg == NULL ||
	    valdtr->current_defarg->overrides == NULL)
		return FALSE;

	if (valdtr->current_defarg->overrides->arg_def == &string_argument) {
		switch (valdtr->current_defarg_type) {
		case SAT_CONST_STRING:
			if (!valdtr->current_defarg_constant) {
				valdtr->current_defarg_type = SAT_VAR_STRING;
				defarg = &valdtr->default_arguments[SAT_VAR_STRING];
			} else {
				defarg = valdtr->current_defarg->overrides;
			}
			break;
		case SAT_VAR_STRING:
			defarg = valdtr->current_defarg->overrides;
			break;
		default:
			return FALSE;
		}
	} else {
		defarg = valdtr->current_defarg->overrides;
	}

	return sieve_validator_argument_default_activate(valdtr, cmd,
							 defarg, arg);
}

/*
 * Argument Validation API
 */

bool sieve_validator_argument_activate(struct sieve_validator *valdtr,
				       struct sieve_command *cmd,
				       struct sieve_ast_argument *arg,
				       bool constant)
{
	struct sieve_default_argument *defarg;

	switch (sieve_ast_argument_type(arg)) {
	case SAAT_NUMBER:
		valdtr->current_defarg_type = SAT_NUMBER;
		break;
	case SAAT_STRING:
		valdtr->current_defarg_type = SAT_CONST_STRING;
		break;
	case SAAT_STRING_LIST:
		valdtr->current_defarg_type = SAT_STRING_LIST;
		break;
	default:
		return FALSE;
	}

	valdtr->current_defarg_constant = constant;
	defarg = &valdtr->default_arguments[valdtr->current_defarg_type];

	if (!constant && defarg->arg_def == &string_argument) {
		valdtr->current_defarg_type = SAT_VAR_STRING;
		defarg = &valdtr->default_arguments[SAT_VAR_STRING];
	}

	return sieve_validator_argument_default_activate(valdtr, cmd,
							 defarg, arg);
}

bool sieve_validate_positional_argument(struct sieve_validator *valdtr,
					struct sieve_command *cmd,
					struct sieve_ast_argument *arg,
					const char *arg_name,
					unsigned int arg_pos,
					enum sieve_ast_argument_type req_type)
{
	i_assert(arg != NULL);

	if (sieve_ast_argument_type(arg) != req_type &&
	    (sieve_ast_argument_type(arg) != SAAT_STRING ||
	     req_type != SAAT_STRING_LIST))
	{
		sieve_argument_validate_error(
			valdtr, arg,
			"the %s %s expects %s as argument %d (%s), "
			"but %s was found",
			sieve_command_identifier(cmd),
			sieve_command_type_name(cmd),
			sieve_ast_argument_type_name(req_type),
			arg_pos, arg_name, sieve_ast_argument_name(arg));
		return FALSE;
	}

	return TRUE;
}

bool sieve_validate_tag_parameter(struct sieve_validator *valdtr,
				  struct sieve_command *cmd,
				  struct sieve_ast_argument *tag,
				  struct sieve_ast_argument *param,
				  const char *arg_name, unsigned int arg_pos,
				  enum sieve_ast_argument_type req_type,
				  bool constant)
{
	i_assert(tag != NULL);

	if (param == NULL) {
		const char *position = (arg_pos == 0 ? "" :
			t_strdup_printf(" %d (%s)", arg_pos, arg_name));

		sieve_argument_validate_error(
			valdtr, tag,
			"the :%s tag for the %s %s requires %s as parameter%s, "
			"but no parameters were found",
			sieve_ast_argument_tag(tag),
			sieve_command_identifier(cmd),
			sieve_command_type_name(cmd),
			sieve_ast_argument_type_name(req_type), position);
		return FALSE;
	}

	if (sieve_ast_argument_type(param) != req_type &&
	    (sieve_ast_argument_type(param) != SAAT_STRING ||
	     req_type != SAAT_STRING_LIST))
	{
		const char *position = (arg_pos == 0 ? "" :
			t_strdup_printf(" %d (%s)", arg_pos, arg_name));

		sieve_argument_validate_error(
			valdtr, param,
			"the :%s tag for the %s %s requires %s as parameter%s, "
			"but %s was found",
			sieve_ast_argument_tag(tag),
			sieve_command_identifier(cmd),
			sieve_command_type_name(cmd),
			sieve_ast_argument_type_name(req_type),	position,
			sieve_ast_argument_name(param));
		return FALSE;
	}

	if (!sieve_validator_argument_activate(valdtr, cmd, param, constant))
		return FALSE;

	param->argument->id_code = tag->argument->id_code;

	return TRUE;
}

/*
 * Command argument validation
 */

static bool
sieve_validate_command_arguments(struct sieve_validator *valdtr,
				 struct sieve_command *cmd)
{
	int arg_count = cmd->def->positional_args;
	int real_count = 0;
	struct sieve_ast_argument *arg;
	struct sieve_command_registration *cmd_reg = cmd->reg;

	/* Resolve tagged arguments */
	arg = sieve_ast_argument_first(cmd->ast_node);
	while (arg != NULL) {
		void *arg_data = NULL;
		struct sieve_tag_registration *tag_reg;
		const struct sieve_argument_def *tag_def;

		if (sieve_ast_argument_type(arg) != SAAT_TAG) {
			arg = sieve_ast_argument_next(arg);
			continue;
		}

		tag_reg = sieve_validator_command_tag_get(valdtr, cmd,
							  arg, &arg_data);

		if (tag_reg == NULL) {
			sieve_argument_validate_error(
				valdtr, arg,
				"unknown tagged argument ':%s' for the %s %s "
				"(reported only once at first occurrence)",
				sieve_ast_argument_tag(arg),
				sieve_command_identifier(cmd),
				sieve_command_type_name(cmd));
			sieve_validator_register_unknown_tag(
				valdtr, cmd_reg, sieve_ast_argument_tag(arg));
			return FALSE;
		}

		/* Check whether previously tagged as unknown */
		if (_tag_registration_is_unknown(tag_reg))
			return FALSE;

		tag_def = tag_reg->tag_def;

		/* Assign the tagged argument type to the ast for later
		   reference */
		arg->argument = sieve_argument_create(
			arg->ast, tag_def, tag_reg->ext, tag_reg->id_code);
		arg->argument->data = arg_data;

		arg = sieve_ast_argument_next(arg);
	}

	/* Validate tagged arguments */
	arg = sieve_ast_argument_first(cmd->ast_node);
	while (arg != NULL && sieve_ast_argument_type(arg) == SAAT_TAG) {
		const struct sieve_argument_def *tag_def = arg->argument->def;
		struct sieve_ast_argument *parg;

		/* Scan backwards for any duplicates */
		if ((tag_def->flags & SIEVE_ARGUMENT_FLAG_MULTIPLE) == 0) {
			parg = sieve_ast_argument_prev(arg);
			while (parg != NULL) {
				if ((sieve_ast_argument_type(parg) == SAAT_TAG &&
				     parg->argument->def == tag_def) ||
				    (arg->argument->id_code > 0 &&
				     parg->argument != NULL &&
				     parg->argument->id_code == arg->argument->id_code))
				{
					const char *tag_id = sieve_ast_argument_tag(arg);
					const char *tag_desc =
						strcmp(tag_def->identifier, tag_id) != 0 ?
						t_strdup_printf("%s argument (:%s)",
							        tag_def->identifier, tag_id) :
						t_strdup_printf(":%s argument",
								tag_def->identifier);

					sieve_argument_validate_error(
						valdtr, arg,
						"encountered duplicate %s for the %s %s",
						tag_desc, sieve_command_identifier(cmd),
						sieve_command_type_name(cmd));

					return FALSE;
				}

				parg = sieve_ast_argument_prev(parg);
			}
		}

		/* Call the validation function for the tag (if present)
		     Fail if the validation fails:
		       Let's not whine multiple	times about a single command
		       having multiple bad arguments...
		 */
		if (tag_def->validate != NULL) {
			if (!tag_def->validate(valdtr, &arg, cmd))
				return FALSE;
		} else {
			arg = sieve_ast_argument_next(arg);
		}
	}

	/* Remaining arguments should be positional (tags are not allowed
	   here) */
	cmd->first_positional = arg;

	while (arg != NULL) {
		if (sieve_ast_argument_type(arg) == SAAT_TAG) {
			sieve_argument_validate_error(
				valdtr, arg,
				"encountered an unexpected tagged argument ':%s' "
				"while validating positional arguments for the %s %s",
				sieve_ast_argument_tag(arg),
				sieve_command_identifier(cmd),
				sieve_command_type_name(cmd));
			return FALSE;
		}

		real_count++;

		arg = sieve_ast_argument_next(arg);
	}

	/* Check the required count versus the real number of arguments */
	if (arg_count >= 0 && real_count != arg_count) {
		sieve_command_validate_error(
			valdtr, cmd,
			"the %s %s requires %d positional argument(s), "
			"but %d is/are specified",
			sieve_command_identifier(cmd),
			sieve_command_type_name(cmd),
			arg_count, real_count);
		return FALSE;
	}

	/* Call initial validation for persistent arguments */
	if (array_is_created(&cmd_reg->persistent_tags)) {
		struct sieve_tag_registration *const *regs;
		unsigned int i, reg_count;

		regs = array_get(&cmd_reg->persistent_tags, &reg_count);
		for (i = 0; i < reg_count; i++) {
			const struct sieve_argument_def *tag_def =
				regs[i]->tag_def;

			if (tag_def != NULL &&
			    tag_def->validate_persistent != NULL) {
				/* To be sure */
				if (!tag_def->validate_persistent(
					valdtr, cmd, regs[i]->ext))
	  				return FALSE;
			}
		}
	}

	return TRUE;
}

static bool
sieve_validate_arguments_context(struct sieve_validator *valdtr,
				 struct sieve_command *cmd)
{
	struct sieve_ast_argument *arg =
		sieve_command_first_argument(cmd);

	while (arg != NULL) {
		const struct sieve_argument *argument = arg->argument;

		if (argument != NULL && argument->def != NULL &&
		    argument->def->validate_context != NULL) {

			if (!argument->def->validate_context(valdtr, arg, cmd))
				return FALSE;
		}

		arg = sieve_ast_argument_next(arg);
	}

	return TRUE;
}

/*
 * Command Validation API
 */

static bool
sieve_validate_command_subtests(struct sieve_validator *valdtr,
				struct sieve_command *cmd,
				const unsigned int count)
{
	switch (count) {
	case 0:
	 	if (sieve_ast_test_count(cmd->ast_node) > 0) {
			/* Unexpected command specified */
			enum sieve_command_type ctype = SCT_NONE;
			struct sieve_command_registration *cmd_reg;
			struct sieve_ast_node *test =
				sieve_ast_test_first(cmd->ast_node);

			cmd_reg = sieve_validator_find_command_registration(
				valdtr, test->identifier);

			/* First check what we are dealing with */
			if (cmd_reg != NULL && cmd_reg->cmd_def != NULL)
				ctype = cmd_reg->cmd_def->type;

			switch (ctype) {
			case SCT_TEST: /* Spurious test */
			case SCT_HYBRID:
				sieve_command_validate_error(
					valdtr, cmd,
					"the %s %s accepts no sub-tests, "
					"but tests are specified",
					sieve_command_identifier(cmd),
					sieve_command_type_name(cmd));
				break;
			case SCT_NONE: /* Unknown command */
				/* Is it perhaps a tag for which the ':' was
				   omitted ? */
				if (sieve_validator_command_tag_exists(
					valdtr, cmd, test->identifier)) {
					sieve_command_validate_error(
						valdtr, cmd,
						"missing colon ':' before ':%s' tag in %s %s",
						test->identifier,
						sieve_command_identifier(cmd),
						sieve_command_type_name(cmd));
					break;
				}
				/* Fall through */
			case SCT_COMMAND:
				sieve_command_validate_error(
					valdtr, cmd,
					"missing semicolon ';' after %s %s",
					sieve_command_identifier(cmd),
					sieve_command_type_name(cmd));
				break;
			}
			return FALSE;
		}
		break;
	case 1:
		if (sieve_ast_test_count(cmd->ast_node) == 0) {
			sieve_command_validate_error(
				valdtr, cmd,
				"the %s %s requires one sub-test, "
				"but none is specified",
				sieve_command_identifier(cmd),
				sieve_command_type_name(cmd));
			return FALSE;

		} else if (sieve_ast_test_count(cmd->ast_node) > 1 ||
			   cmd->ast_node->test_list) {
			sieve_command_validate_error(
				valdtr, cmd,
				"the %s %s requires one sub-test, "
				"but a list of tests is specified",
				sieve_command_identifier(cmd),
				sieve_command_type_name(cmd));
			return FALSE;
		}
		break;
	default:
		if (sieve_ast_test_count(cmd->ast_node) == 0) {
			sieve_command_validate_error(
				valdtr, cmd,
				"the %s %s requires a list of sub-tests, "
				"but none is specified",
				sieve_command_identifier(cmd),
				sieve_command_type_name(cmd));
			return FALSE;
		} else if (sieve_ast_test_count(cmd->ast_node) == 1 &&
			   !cmd->ast_node->test_list) {
			sieve_command_validate_error(
				valdtr, cmd,
				"the %s %s requires a list of sub-tests, "
				"but a single test is specified",
				sieve_command_identifier(cmd),
				sieve_command_type_name(cmd));
			return FALSE;
		}
		break;
	}

	return TRUE;
}

static bool
sieve_validate_command_block(struct sieve_validator *valdtr,
			     struct sieve_command *cmd, bool block_allowed,
			     bool block_required)
{
	i_assert(cmd->ast_node->type == SAT_COMMAND);

	if (block_required) {
		if (!cmd->ast_node->block) {
			sieve_command_validate_error(
				valdtr, cmd,
				"the %s command requires a command block, "
				"but it is missing",
				sieve_command_identifier(cmd));
			return FALSE;
		}
	} else if (!block_allowed && cmd->ast_node->block) {
		sieve_command_validate_error(
			valdtr, cmd,
			"the %s command does not accept a command block, "
			"but one is specified anyway",
			sieve_command_identifier(cmd));
		return FALSE;
	}

	return TRUE;
}

/*
 * AST Validation
 */

static bool
sieve_validate_test_list(struct sieve_validator *valdtr,
			 struct sieve_ast_node *test_list, int *const_r);
static bool
sieve_validate_block(struct sieve_validator *valdtr,
		     struct sieve_ast_node *block);
static bool
sieve_validate_command(struct sieve_validator *valdtr,
		       struct sieve_ast_node *cmd_node, int *const_r);

static bool
sieve_validate_command_context(struct sieve_validator *valdtr,
			       struct sieve_ast_node *cmd_node)
{
	enum sieve_ast_type ast_type = sieve_ast_node_type(cmd_node);
	struct sieve_command_registration *cmd_reg;

	i_assert(ast_type == SAT_TEST || ast_type == SAT_COMMAND);

	/* Verify the command specified by this node */
	cmd_reg = sieve_validator_find_command_registration(
		valdtr, cmd_node->identifier);

	if (cmd_reg != NULL && cmd_reg->cmd_def != NULL) {
		const struct sieve_command_def *cmd_def = cmd_reg->cmd_def;

		/* Identifier = "" when the command was previously marked as
		   unknown */
		if (*(cmd_def->identifier) != '\0') {
			if ((cmd_def->type == SCT_COMMAND && ast_type == SAT_TEST) ||
			    (cmd_def->type == SCT_TEST && ast_type == SAT_COMMAND)) {
				sieve_validator_error(
					valdtr, cmd_node->source_line,
					"attempted to use %s '%s' as %s",
					sieve_command_def_type_name(cmd_def),
					cmd_node->identifier,
					sieve_ast_type_name(ast_type));
			 	return FALSE;
			}

			cmd_node->command = sieve_command_create(
				cmd_node, cmd_reg->ext, cmd_def, cmd_reg);
		} else {
			return FALSE;
		}
	} else {
		sieve_validator_error(
			valdtr, cmd_node->source_line,
			"unknown %s '%s' (only reported once at first occurrence)",
			sieve_ast_type_name(ast_type), cmd_node->identifier);

		sieve_validator_register_unknown_command(
			valdtr, cmd_node->identifier);
		return FALSE;
	}

	return TRUE;
}

static bool
sieve_validate_command(struct sieve_validator *valdtr,
		       struct sieve_ast_node *cmd_node, int *const_r)
{
	enum sieve_ast_type ast_type = sieve_ast_node_type(cmd_node);
	struct sieve_command *cmd =
		(cmd_node == NULL ? NULL : cmd_node->command);
	const struct sieve_command_def *cmd_def =
		(cmd != NULL ? cmd->def : NULL);
	bool result = TRUE;

	i_assert(ast_type == SAT_TEST || ast_type == SAT_COMMAND);

	if (cmd_def != NULL && *(cmd_def->identifier) != '\0') {
		if (cmd_def->pre_validate == NULL ||
		    cmd_def->pre_validate(valdtr, cmd)) {
			/* Check argument syntax */
			if (!sieve_validate_command_arguments(valdtr, cmd)) {
				result = FALSE;

				/* A missing ':' causes a tag to become a test.
				   This can be the cause of the arguments
				   validation failing. Therefore we must produce
				   an error for the sub-tests as well if
				   appropriate. */
				(void)sieve_validate_command_subtests(
					valdtr, cmd, cmd_def->subtests);
			} else if (!sieve_validate_command_subtests(
				valdtr, cmd, cmd_def->subtests) ||
				(ast_type == SAT_COMMAND &&
				 !sieve_validate_command_block(
					valdtr, cmd, cmd_def->block_allowed,
					cmd_def->block_required))) {
				result = FALSE;
			} else {
				/* Call command validation function if specified
				 */
				if (cmd_def->validate != NULL) {
					result = cmd_def->validate(valdtr, cmd) &&
						result;
				}
			}
		} else {
			/* If pre-validation fails, don't bother to validate
			   further as context might be missing and doing so is
			   not very useful for further error reporting anyway */
			valdtr->failed = TRUE;
			return FALSE;
		}

		result = result && sieve_validate_arguments_context(valdtr, cmd);
	}
	if (!result)
		valdtr->failed = TRUE;

	/*
	 * Descend further into the AST
	 */

	if (cmd_def != NULL) {
		/* Tests */
		if (cmd_def->subtests > 0) {
			if (result ||
			    sieve_errors_more_allowed(valdtr->ehandler)) {
				result = sieve_validate_test_list(
					valdtr, cmd_node, const_r) && result;
			}
		} else if (result) {
			if (cmd_def->validate_const != NULL) {
				(void)cmd_def->validate_const(
					valdtr, cmd, const_r, -1);
			} else {
				*const_r = -1;
			}
		}

		/* Skip block if result of test is const FALSE */
		if (result && *const_r == 0)
			return TRUE;

		/* Command block */
		if (cmd_def->block_allowed && ast_type == SAT_COMMAND &&
		    (result || sieve_errors_more_allowed(valdtr->ehandler))) {
			result = sieve_validate_block(valdtr, cmd_node) &&
				result;
		}
	}

	return result;
}

static bool
sieve_validate_test_list(struct sieve_validator *valdtr,
			 struct sieve_ast_node *test_node, int *const_r)
{
	struct sieve_command *tst = test_node->command;
	const struct sieve_command_def *tst_def =
		(tst != NULL ? tst->def : NULL);
	struct sieve_ast_node *test;
	bool result = TRUE;

	if (tst_def != NULL && tst_def->validate_const != NULL) {
		if (!tst_def->validate_const(valdtr, tst, const_r, -2))
			return TRUE;
	}

	test = sieve_ast_test_first(test_node);
	while (test != NULL &&
	       (result || sieve_errors_more_allowed(valdtr->ehandler))) {
		int const_value = -2;

		result = sieve_validate_command_context(valdtr, test) &&
			sieve_validate_command(valdtr, test, &const_value) &&
			result;

		if (!result)
			valdtr->failed = TRUE;
		else {
			if (tst_def != NULL &&
			    tst_def->validate_const != NULL) {
				if (!tst_def->validate_const(
					valdtr, tst, const_r, const_value))
					return TRUE;
			} else {
				*const_r = -1;
			}
		}

		if (result && const_value >= 0)
			test = sieve_ast_node_detach(test);
		else
			test = sieve_ast_test_next(test);
	}

	return result;
}

static bool
sieve_validate_block(struct sieve_validator *valdtr,
		     struct sieve_ast_node *block)
{
	bool result = TRUE, fatal = FALSE, already_failed = valdtr->failed;
	struct sieve_ast_node *cmd_node, *next;

	T_BEGIN {
		cmd_node = sieve_ast_command_first(block);
		while (!fatal && cmd_node != NULL &&
		       (result ||
			sieve_errors_more_allowed(valdtr->ehandler))) {
			bool command_success;
			int const_value = -2;

			next = sieve_ast_command_next(cmd_node);

	 		/* Check if this is the first non-require command */
			if (sieve_ast_node_type(block) == SAT_ROOT &&
			    !valdtr->finished_require &&
			    strcasecmp(cmd_node->identifier,
				       cmd_require.identifier) != 0) {
				const struct sieve_validator_extension_reg *extrs;
				const struct sieve_extension *const *exts;
				unsigned int ext_count, i;

				valdtr->finished_require = TRUE;

				/* Load implicit extensions */
				exts = sieve_extensions_get_all(valdtr->svinst, &ext_count);
				for (i = 0; i < ext_count; i++) {
					if (exts[i]->implicit) {
						(void)sieve_validator_extension_load(
							valdtr, NULL, NULL, exts[i], TRUE);
					}
				}

				/* Validate all 'require'd extensions */
				extrs = array_get(&valdtr->extensions, &ext_count);
				for (i = 0; i < ext_count; i++) {
					if (extrs[i].loaded && extrs[i].valext != NULL &&
					    extrs[i].valext->validate != NULL) {
						if (!extrs[i].valext->validate(
							extrs[i].ext, valdtr,
							extrs[i].context, extrs[i].arg,
							extrs[i].required)) {
							fatal = TRUE;
							break;
						}
					}
				}
			}

			command_success =
				sieve_validate_command_context(valdtr, cmd_node);
			result = command_success && result;

			if (!result || fatal)
				valdtr->failed = TRUE;

			result = !fatal &&
				sieve_validate_command(valdtr, cmd_node,
						       &const_value) && result;

			cmd_node = next;
		}
	} T_END;

	/* Assert that this function returns FALSE if failure is caused here. */
	i_assert(!valdtr->failed || already_failed || !result || fatal);
	return result && !fatal;
}

bool sieve_validator_run(struct sieve_validator *valdtr)
{
	return sieve_validate_block(valdtr, sieve_ast_root(valdtr->ast));
}

/*
 * Validator object registry
 */

struct sieve_validator_object_reg {
	const struct sieve_object_def *obj_def;
	const struct sieve_extension *ext;
};

struct sieve_validator_object_registry {
	struct sieve_validator *valdtr;
	ARRAY(struct sieve_validator_object_reg) registrations;
};

struct sieve_validator_object_registry *
sieve_validator_object_registry_get(struct sieve_validator *valdtr,
				    const struct sieve_extension *ext)
{
	return (struct sieve_validator_object_registry *)
		sieve_validator_extension_get_context(valdtr, ext);
}

void sieve_validator_object_registry_add(
	struct sieve_validator_object_registry *regs,
	const struct sieve_extension *ext,
	const struct sieve_object_def *obj_def)
{
	struct sieve_validator_object_reg *reg;

	reg = array_append_space(&regs->registrations);
	reg->ext = ext;
	reg->obj_def = obj_def;
}

bool sieve_validator_object_registry_find(
	struct sieve_validator_object_registry *regs, const char *identifier,
	struct sieve_object *obj)
{
	unsigned int i;

	for (i = 0; i < array_count(&regs->registrations); i++) {
		const struct sieve_validator_object_reg *reg =
			array_idx(&regs->registrations, i);

		if (strcasecmp(reg->obj_def->identifier, identifier) == 0) {
			if (obj != NULL) {
				obj->def = reg->obj_def;
				obj->ext = reg->ext;
			}
			return TRUE;
		}
	}

	return FALSE;
}

struct sieve_validator_object_registry *
sieve_validator_object_registry_create(struct sieve_validator *valdtr)
{
	pool_t pool = valdtr->pool;
	struct sieve_validator_object_registry *regs =
		p_new(pool, struct sieve_validator_object_registry, 1);

	/* Setup registry */
	p_array_init(&regs->registrations, valdtr->pool, 4);

	regs->valdtr = valdtr;

	return regs;
}

struct sieve_validator_object_registry *
sieve_validator_object_registry_init(struct sieve_validator *valdtr,
				     const struct sieve_extension *ext)
{
	struct sieve_validator_object_registry *regs =
		sieve_validator_object_registry_create(valdtr);

	sieve_validator_extension_set_context(valdtr, ext, regs);
	return regs;
}

/*
 * Error handling
 */

#undef sieve_validator_error
void sieve_validator_error(struct sieve_validator *valdtr,
			   const char *csrc_filename, unsigned int csrc_linenum,
			   unsigned int source_line, const char *fmt, ...)
{
	struct sieve_error_params params = {
		.log_type = LOG_TYPE_ERROR,
		.csrc = {
			.filename = csrc_filename,
			.linenum = csrc_linenum,
		},
	};
	va_list args;

	params.location =
		sieve_error_script_location(valdtr->script, source_line);

	va_start(args, fmt);
	sieve_logv(valdtr->ehandler, &params, fmt, args);
	va_end(args);
}

#undef sieve_validator_warning
void sieve_validator_warning(struct sieve_validator *valdtr,
			     const char *csrc_filename,
			     unsigned int csrc_linenum,
			     unsigned int source_line, const char *fmt, ...)
{
	struct sieve_error_params params = {
		.log_type = LOG_TYPE_WARNING,
		.csrc = {
			.filename = csrc_filename,
			.linenum = csrc_linenum,
		},
	};
	va_list args;

	params.location =
		sieve_error_script_location(valdtr->script, source_line);

	va_start(args, fmt);
	sieve_logv(valdtr->ehandler, &params, fmt, args);
	va_end(args);

}
