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

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

#include "sieve-extensions.h"
#include "sieve-code.h"
#include "sieve-address.h"
#include "sieve-commands.h"
#include "sieve-binary.h"
#include "sieve-comparators.h"
#include "sieve-match-types.h"
#include "sieve-validator.h"
#include "sieve-generator.h"
#include "sieve-interpreter.h"
#include "sieve-dump.h"
#include "sieve-match.h"

#include "sieve-address-parts.h"

#include <string.h>

/*
 * Default address parts
 */

const struct sieve_address_part_def *sieve_core_address_parts[] = {
	&all_address_part, &local_address_part, &domain_address_part
};

const unsigned int sieve_core_address_parts_count =
	N_ELEMENTS(sieve_core_address_parts);

/*
 * Address-part 'extension'
 */

static bool
addrp_validator_load(const struct sieve_extension *ext,
		     struct sieve_validator *valdtr);

const struct sieve_extension_def address_part_extension = {
	.name = "@address-parts",
	.validator_load = addrp_validator_load
};

/*
 * Validator context:
 *   name-based address-part registry.
 */

static struct sieve_validator_object_registry *
_get_object_registry(struct sieve_validator *valdtr)
{
	struct sieve_instance *svinst;
	const struct sieve_extension *adrp_ext;

	svinst = sieve_validator_svinst(valdtr);
	adrp_ext = sieve_get_address_part_extension(svinst);
	return sieve_validator_object_registry_get(valdtr, adrp_ext);
}

void sieve_address_part_register(
	struct sieve_validator *valdtr, const struct sieve_extension *ext,
	const struct sieve_address_part_def *addrp_def)
{
	struct sieve_validator_object_registry *regs =
		_get_object_registry(valdtr);

	sieve_validator_object_registry_add(regs, ext, &addrp_def->obj_def);
}

static bool
sieve_address_part_exists(struct sieve_validator *valdtr,
			  const char *identifier)
{
	struct sieve_validator_object_registry *regs =
		_get_object_registry(valdtr);

	return sieve_validator_object_registry_find(regs, identifier, NULL);
}

static const struct sieve_address_part *
sieve_address_part_create_instance(struct sieve_validator *valdtr,
				   struct sieve_command *cmd,
				   const char *identifier)
{
	struct sieve_validator_object_registry *regs =
		_get_object_registry(valdtr);
	struct sieve_object object;
	struct sieve_address_part *addrp;

	if (!sieve_validator_object_registry_find(regs, identifier, &object))
		return NULL;

	addrp = p_new(sieve_command_pool(cmd), struct sieve_address_part, 1);
	addrp->object = object;
	addrp->def = (const struct sieve_address_part_def *) object.def;

	return addrp;
}

static bool
addrp_validator_load(const struct sieve_extension *ext,
		     struct sieve_validator *valdtr)
{
	struct sieve_validator_object_registry *regs =
		sieve_validator_object_registry_init(valdtr, ext);
	unsigned int i;

	/* Register core address-parts */
	for (i = 0; i < sieve_core_address_parts_count; i++) {
		sieve_validator_object_registry_add(
			regs, NULL, &(sieve_core_address_parts[i]->obj_def));
	}

	return TRUE;
}

void sieve_address_parts_link_tags(struct sieve_validator *valdtr,
				   struct sieve_command_registration *cmd_reg,
				   int id_code)
{
	struct sieve_instance *svinst;
	const struct sieve_extension *adrp_ext;

	svinst = sieve_validator_svinst(valdtr);
	adrp_ext = sieve_get_address_part_extension(svinst);

	sieve_validator_register_tag(valdtr, cmd_reg, adrp_ext,
				     &address_part_tag, id_code);
}

/*
 * Address-part tagged argument
 */

/* Forward declarations */

static bool
tag_address_part_is_instance_of(struct sieve_validator *valdtr,
				struct sieve_command *cmd,
				const struct sieve_extension *ext,
				const char *identifier, void **data);
static bool
tag_address_part_validate(struct sieve_validator *valdtr,
			  struct sieve_ast_argument **arg,
			  struct sieve_command *cmd);
static bool
tag_address_part_generate(const struct sieve_codegen_env *cgenv,
			  struct sieve_ast_argument *arg,
			  struct sieve_command *cmd);

/* Argument object */

const struct sieve_argument_def address_part_tag = {
	.identifier = "ADDRESS-PART",
	.is_instance_of = tag_address_part_is_instance_of,
	.validate = tag_address_part_validate,
	.generate = tag_address_part_generate
};

/* Argument implementation */

static bool
tag_address_part_is_instance_of(struct sieve_validator *valdtr,
				struct sieve_command *cmd,
				const struct sieve_extension *ext ATTR_UNUSED,
				const char *identifier, void **data)
{
	const struct sieve_address_part *addrp;

	if (data == NULL)
		return sieve_address_part_exists(valdtr, identifier);

	if ((addrp = sieve_address_part_create_instance(
		valdtr, cmd, identifier)) == NULL)
		return FALSE;

	*data = (void *)addrp;
	return TRUE;
}

static bool
tag_address_part_validate(struct sieve_validator *valdtr ATTR_UNUSED,
			  struct sieve_ast_argument **arg,
			  struct sieve_command *cmd ATTR_UNUSED)
{
	/* NOTE: Currenly trivial, but might need to allow for further
		 validation for future extensions.
	 */

	/* Syntax:
	     ":localpart" / ":domain" / ":all" (subject to extension)
   	 */

	/* Skip tag */
	*arg = sieve_ast_argument_next(*arg);

	return TRUE;
}

static bool
tag_address_part_generate(const struct sieve_codegen_env *cgenv,
			  struct sieve_ast_argument *arg,
			  struct sieve_command *cmd ATTR_UNUSED)
{
	struct sieve_address_part *addrp =
		(struct sieve_address_part *)arg->argument->data;

	sieve_opr_address_part_emit(cgenv->sblock, addrp);
	return TRUE;
}

/*
 * Address-part operand
 */

const struct sieve_operand_class sieve_address_part_operand_class =
	{ "address part" };

static const struct sieve_extension_objects core_address_parts =
	SIEVE_EXT_DEFINE_MATCH_TYPES(sieve_core_address_parts);

const struct sieve_operand_def address_part_operand = {
	.name = "address-part",
	.code = SIEVE_OPERAND_ADDRESS_PART,
	.class = &sieve_address_part_operand_class,
	.interface = &core_address_parts
};

/*
 * Address-part string list
 */

static int
sieve_address_part_stringlist_next_item(struct sieve_stringlist *_strlist,
					string_t **str_r);
static void
sieve_address_part_stringlist_reset(struct sieve_stringlist *_strlist);
static int
sieve_address_part_stringlist_get_length(struct sieve_stringlist *_strlist);
static void
sieve_address_part_stringlist_set_trace(struct sieve_stringlist *_strlist,
					bool trace);

struct sieve_address_part_stringlist {
	struct sieve_stringlist strlist;

	const struct sieve_address_part *addrp;
	struct sieve_address_list *addresses;
};

struct sieve_stringlist *
sieve_address_part_stringlist_create(const struct sieve_runtime_env *renv,
				     const struct sieve_address_part *addrp,
				     struct sieve_address_list *addresses)
{
	struct sieve_address_part_stringlist *strlist;

	strlist = t_new(struct sieve_address_part_stringlist, 1);
	strlist->strlist.runenv = renv;
	strlist->strlist.next_item = sieve_address_part_stringlist_next_item;
	strlist->strlist.reset = sieve_address_part_stringlist_reset;
	strlist->strlist.get_length = sieve_address_part_stringlist_get_length;
	strlist->strlist.set_trace = sieve_address_part_stringlist_set_trace;

	strlist->addrp = addrp;
	strlist->addresses = addresses;

	return &strlist->strlist;
}

static int
sieve_address_part_stringlist_next_item(struct sieve_stringlist *_strlist,
					string_t **str_r)
{
	struct sieve_address_part_stringlist *strlist =
		(struct sieve_address_part_stringlist *)_strlist;
	struct smtp_address item;
	string_t *item_unparsed;
	int ret;

	*str_r = NULL;

	while (*str_r == NULL) {
		if ((ret = sieve_address_list_next_item(
			strlist->addresses, &item, &item_unparsed)) <= 0)
			return ret;

		if (item.localpart == NULL) {
			if (item_unparsed != NULL) {
				if (_strlist->trace) {
					sieve_runtime_trace(
						_strlist->runenv, 0,
						"extracting '%s' part from non-address value '%s'",
						sieve_address_part_name(strlist->addrp),
						str_sanitize(str_c(item_unparsed), 80));
				}

				if (str_len(item_unparsed) == 0 ||
				    sieve_address_part_is(strlist->addrp, all_address_part))
					*str_r = item_unparsed;
			}
		} else {
			const struct sieve_address_part *addrp = strlist->addrp;
			const char *part = NULL;

			if (_strlist->trace) {
				sieve_runtime_trace(
					_strlist->runenv, 0,
					"extracting '%s' part from address %s",
					sieve_address_part_name(strlist->addrp),
					smtp_address_encode_path(&item));
			}

			if (addrp->def != NULL &&
			    addrp->def->extract_from != NULL)
				part = addrp->def->extract_from(addrp, &item);

			if (part != NULL)
				*str_r = t_str_new_const(part, strlen(part));
		}
	}

	return 1;
}

static void
sieve_address_part_stringlist_reset(struct sieve_stringlist *_strlist)
{
	struct sieve_address_part_stringlist *strlist =
		(struct sieve_address_part_stringlist *)_strlist;

	sieve_address_list_reset(strlist->addresses);
}

static int
sieve_address_part_stringlist_get_length(struct sieve_stringlist *_strlist)
{
	struct sieve_address_part_stringlist *strlist =
		(struct sieve_address_part_stringlist *)_strlist;

	return sieve_address_list_get_length(strlist->addresses);
}

static void
sieve_address_part_stringlist_set_trace(struct sieve_stringlist *_strlist,
					bool trace)
{
	struct sieve_address_part_stringlist *strlist =
		(struct sieve_address_part_stringlist *)_strlist;

	sieve_address_list_set_trace(strlist->addresses, trace);
}

/*
 * Default ADDRESS-PART, MATCH-TYPE, COMPARATOR access
 */

int sieve_addrmatch_opr_optional_dump(const struct sieve_dumptime_env *denv,
				      sieve_size_t *address,
				      signed int *opt_code)
{
	signed int _opt_code = 0;
	bool final = FALSE, opok = TRUE;

	if (opt_code == NULL) {
		opt_code = &_opt_code;
		final = TRUE;
	}

	while (opok) {
		int opt;

		if ((opt = sieve_opr_optional_dump(
			denv, address, opt_code)) <= 0)
			return opt;

		switch (*opt_code) {
		case SIEVE_MATCH_OPT_COMPARATOR:
			opok = sieve_opr_comparator_dump(denv, address);
			break;
		case SIEVE_MATCH_OPT_MATCH_TYPE:
			opok = sieve_opr_match_type_dump(denv, address);
			break;
		case SIEVE_AM_OPT_ADDRESS_PART:
			opok = sieve_opr_address_part_dump(denv, address);
			break;
		default:
			return (final ? -1 : 1);
		}
	}

	return -1;
}

int sieve_addrmatch_opr_optional_read(const struct sieve_runtime_env *renv,
				      sieve_size_t *address,
				      signed int *opt_code, int *exec_status,
				      struct sieve_address_part *addrp,
				      struct sieve_match_type *mtch,
				      struct sieve_comparator *cmp)
{
	signed int _opt_code = 0;
	bool final = FALSE;
	int status = SIEVE_EXEC_OK;

	if (opt_code == NULL) {
		opt_code = &_opt_code;
		final = TRUE;
	}

	if (exec_status != NULL)
		*exec_status = SIEVE_EXEC_OK;

	while (status == SIEVE_EXEC_OK) {
		int opt;

		if ((opt = sieve_opr_optional_read(
			renv, address, opt_code)) <= 0){
			if (opt < 0 && exec_status != NULL)
				*exec_status = SIEVE_EXEC_BIN_CORRUPT;
			return opt;
		}

		switch (*opt_code) {
		case SIEVE_MATCH_OPT_COMPARATOR:
			if (cmp == NULL) {
				sieve_runtime_trace_error(
					renv, "unexpected comparator operand");
				if (exec_status != NULL)
					*exec_status = SIEVE_EXEC_BIN_CORRUPT;
				return -1;
			}
			status = sieve_opr_comparator_read(renv, address, cmp);
			break;
		case SIEVE_MATCH_OPT_MATCH_TYPE:
			if (mtch == NULL) {
				sieve_runtime_trace_error(
					renv, "unexpected match-type operand");
				if (exec_status != NULL)
					*exec_status = SIEVE_EXEC_BIN_CORRUPT;
				return -1;
			}
			status = sieve_opr_match_type_read(renv, address, mtch);
			break;
		case SIEVE_AM_OPT_ADDRESS_PART:
			if (addrp == NULL) {
				sieve_runtime_trace_error(
					renv, "unexpected address-part operand");
				if (exec_status != NULL)
					*exec_status = SIEVE_EXEC_BIN_CORRUPT;
				return -1;
			}
			status = sieve_opr_address_part_read(
				renv, address, addrp);
			break;
		default:
			if (final) {
				sieve_runtime_trace_error(
					renv, "invalid optional operand");
				if (exec_status != NULL)
					*exec_status = SIEVE_EXEC_BIN_CORRUPT;
				return -1;
			}
			return 1;
		}
	}

	if (exec_status != NULL)
		*exec_status = status;
	return -1;
}

/*
 * Core address-part modifiers
 */

static const char *
addrp_all_extract_from(const struct sieve_address_part *addrp ATTR_UNUSED,
		       const struct smtp_address *address)
{
	if (address->localpart == NULL)
		return NULL;
	return smtp_address_encode(address);
}

static const char *
addrp_domain_extract_from(const struct sieve_address_part *addrp ATTR_UNUSED,
			  const struct smtp_address *address)
{
	return address->domain;
}

static const char *
addrp_localpart_extract_from(const struct sieve_address_part *addrp ATTR_UNUSED,
			     const struct smtp_address *address)
{
	return address->localpart;
}

const struct sieve_address_part_def all_address_part = {
	SIEVE_OBJECT("all", &address_part_operand, SIEVE_ADDRESS_PART_ALL),
	.extract_from = addrp_all_extract_from
};

const struct sieve_address_part_def local_address_part = {
	SIEVE_OBJECT("localpart", &address_part_operand,
		     SIEVE_ADDRESS_PART_LOCAL),
	.extract_from = addrp_localpart_extract_from
};

const struct sieve_address_part_def domain_address_part = {
	SIEVE_OBJECT("domain", &address_part_operand,
		     SIEVE_ADDRESS_PART_DOMAIN),
	.extract_from = addrp_domain_extract_from
};
