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

#include "lib.h"
#include "str.h"
#include "str-sanitize.h"

#include "sieve-common.h"
#include "sieve-limits.h"
#include "sieve-extensions.h"
#include "sieve-stringlist.h"
#include "sieve-actions.h"
#include "sieve-binary.h"
#include "sieve-generator.h"
#include "sieve-interpreter.h"
#include "sieve-dump.h"

#include "sieve-code.h"

#include <stdio.h>

/*
 * Code stringlist
 */

/* Forward declarations */

static int
sieve_code_stringlist_next_item(struct sieve_stringlist *_strlist,
				string_t **str_r);
static void sieve_code_stringlist_reset(struct sieve_stringlist *_strlist);
static int sieve_code_stringlist_get_length(struct sieve_stringlist *_strlist);

/* Coded stringlist object */

struct sieve_code_stringlist {
	struct sieve_stringlist strlist;

	sieve_size_t start_address;
	sieve_size_t end_address;
	sieve_size_t current_offset;
	int length;
	int index;
};

static struct sieve_stringlist *
sieve_code_stringlist_create(const struct sieve_runtime_env *renv,
			     sieve_size_t start_address, unsigned int length,
			     sieve_size_t end)
{
	struct sieve_code_stringlist *strlist;

	if (end > sieve_binary_block_get_size(renv->sblock))
  		return NULL;

	strlist = t_new(struct sieve_code_stringlist, 1);
	strlist->strlist.runenv = renv;
	strlist->strlist.exec_status = SIEVE_EXEC_OK;
	strlist->strlist.next_item = sieve_code_stringlist_next_item;
	strlist->strlist.reset = sieve_code_stringlist_reset;
	strlist->strlist.get_length = sieve_code_stringlist_get_length;
	strlist->start_address = start_address;
	strlist->current_offset = start_address;
	strlist->end_address = end;
	strlist->length = length;
	strlist->index = 0;

	return &strlist->strlist;
}

/* Stringlist implementation */

static int
sieve_code_stringlist_next_item(struct sieve_stringlist *_strlist,
				string_t **str_r)
{
	struct sieve_code_stringlist *strlist =
		(struct sieve_code_stringlist *)_strlist;
	sieve_size_t address;
	*str_r = NULL;
	int ret;

	/* Check for end of list */
	if (strlist->index >= strlist->length)
		return 0;

	/* Read next item */
	address = strlist->current_offset;
	if ((ret = sieve_opr_string_read(_strlist->runenv, &address,
					 NULL, str_r)) == SIEVE_EXEC_OK) {
		strlist->index++;
		strlist->current_offset = address;
		return 1;
	}

	_strlist->exec_status = ret;
	return -1;
}

static void sieve_code_stringlist_reset(struct sieve_stringlist *_strlist)
{
	struct sieve_code_stringlist *strlist =
		(struct sieve_code_stringlist *)_strlist;

	strlist->current_offset = strlist->start_address;
	strlist->index = 0;
}

static int sieve_code_stringlist_get_length(struct sieve_stringlist *_strlist)
{
	struct sieve_code_stringlist *strlist =
		(struct sieve_code_stringlist *)_strlist;

	return strlist->length;
}

static bool
sieve_code_stringlist_dump(const struct sieve_dumptime_env *denv,
			   sieve_size_t *address, unsigned int length,
			   sieve_size_t end, const char *field_name)
{
	unsigned int i;

	if (end > sieve_binary_block_get_size(denv->sblock))
  		return FALSE;

	if (field_name != NULL) {
		sieve_code_dumpf(denv, "%s: STRLIST [%u] (end: %08llx)",
				 field_name, length, (unsigned long long)end);
	} else {
		sieve_code_dumpf(denv, "STRLIST [%u] (end: %08llx)",
				 length, (unsigned long long)end);
	}

	sieve_code_descend(denv);

	for (i = 0; i < length; i++) {
		bool success = TRUE;

		T_BEGIN {
			success = sieve_opr_string_dump(denv, address, NULL);
		} T_END;

		if (!success || *address > end)
			return FALSE;
	}

	if (*address != end)
		return FALSE;

	sieve_code_ascend(denv);
	return TRUE;
}

/*
 * Core operands
 */

extern const struct sieve_operand_def comparator_operand;
extern const struct sieve_operand_def match_type_operand;
extern const struct sieve_operand_def address_part_operand;

const struct sieve_operand_def *sieve_operands[] = {
	&omitted_operand, /* SIEVE_OPERAND_OPTIONAL */
	&number_operand,
	&string_operand,
	&stringlist_operand,
	&comparator_operand,
	&match_type_operand,
	&address_part_operand,
	&catenated_string_operand
};

const unsigned int sieve_operand_count = N_ELEMENTS(sieve_operands);

/*
 * Operand functions
 */

sieve_size_t sieve_operand_emit(struct sieve_binary_block *sblock,
				const struct sieve_extension *ext,
				const struct sieve_operand_def *opr_def)
{
	sieve_size_t address;

	if (ext != NULL) {
		address = sieve_binary_emit_extension(
			sblock, ext, sieve_operand_count);

		sieve_binary_emit_extension_object(
			sblock, &opr_def->ext_def->operands, opr_def->code);

		return address;
	}

	return sieve_binary_emit_byte(sblock, opr_def->code);
}

bool sieve_operand_read(struct sieve_binary_block *sblock,
			sieve_size_t *address, const char *field_name,
			struct sieve_operand *operand)
{
	unsigned int code = sieve_operand_count;

	operand->address = *address;
	operand->field_name = field_name;
	operand->ext = NULL;
	operand->def = NULL;

	if (!sieve_binary_read_extension(sblock, address, &code, &operand->ext))
		return FALSE;

	if (operand->ext == NULL) {
		if (code < sieve_operand_count)
			operand->def = sieve_operands[code];

		return (operand->def != NULL);
	}

	if (operand->ext->def == NULL)
		return FALSE;

	operand->def = (const struct sieve_operand_def *)
		sieve_binary_read_extension_object(
			sblock, address, &operand->ext->def->operands);

	return (operand->def != NULL);
}

/*
 * Optional operand
 */

int sieve_opr_optional_next(struct sieve_binary_block *sblock,
			    sieve_size_t *address, signed int *opt_code)
{
	/* Start of optional operand block */
	if (*opt_code == 0) {
		sieve_size_t tmp_addr = *address;
		unsigned int op;

		if (!sieve_binary_read_byte(sblock, &tmp_addr, &op) ||
		    op != SIEVE_OPERAND_OPTIONAL)
			return 0;

		*address = tmp_addr;
	}

	/* Read optional operand code */
	if (!sieve_binary_read_code(sblock, address, opt_code))
		return -1;

	/* Return 0 at end of list */
	return (*opt_code != 0 ? 1 : 0);
}

/*
 * Operand definitions
 */

/* Omitted */

const struct sieve_operand_class omitted_class =
	{ "OMITTED" };

const struct sieve_operand_def omitted_operand = {
	.name = "@OMITTED",
	.code = SIEVE_OPERAND_OPTIONAL,
	.class = &omitted_class
};

/* Number */

static bool
opr_number_dump(const struct sieve_dumptime_env *denv,
		const struct sieve_operand *oprnd, sieve_size_t *address);
static int
opr_number_read(const struct sieve_runtime_env *renv,
		const struct sieve_operand *oprnd, sieve_size_t *address,
		sieve_number_t *number_r);

const struct sieve_opr_number_interface number_interface = {
	opr_number_dump,
	opr_number_read
};

const struct sieve_operand_class number_class =
	{ "number" };

const struct sieve_operand_def number_operand = {
	.name = "@number",
	.code = SIEVE_OPERAND_NUMBER,
	.class = &number_class,
	.interface = &number_interface
};

/* String */

static bool
opr_string_dump(const struct sieve_dumptime_env *denv,
		const struct sieve_operand *oprnd, sieve_size_t *address);
static int
opr_string_read(const struct sieve_runtime_env *renv,
		const struct sieve_operand *oprnd, sieve_size_t *address,
		string_t **str_r);

const struct sieve_opr_string_interface string_interface ={
	opr_string_dump,
	opr_string_read
};

const struct sieve_operand_class string_class =
	{ "string" };

const struct sieve_operand_def string_operand = {
	.name = "@string",
	.code = SIEVE_OPERAND_STRING,
	.class = &string_class,
	.interface = &string_interface
};

/* String List */

static bool
opr_stringlist_dump(const struct sieve_dumptime_env *denv,
		    const struct sieve_operand *oprnd, sieve_size_t *address);
static int
opr_stringlist_read(const struct sieve_runtime_env *renv,
		    const struct sieve_operand *oprnd, sieve_size_t *address,
		    struct sieve_stringlist **strlist_r);

const struct sieve_opr_stringlist_interface stringlist_interface = {
	opr_stringlist_dump,
	opr_stringlist_read
};

const struct sieve_operand_class stringlist_class =
	{ "string-list" };

const struct sieve_operand_def stringlist_operand = {
	.name = "@string-list",
	.code = SIEVE_OPERAND_STRING_LIST,
	.class = &stringlist_class,
	.interface = &stringlist_interface
};

/* Catenated String */

static bool
opr_catenated_string_dump(const struct sieve_dumptime_env *denv,
			  const struct sieve_operand *operand,
			  sieve_size_t *address);
static int
opr_catenated_string_read(const struct sieve_runtime_env *renv,
			  const struct sieve_operand *operand,
			  sieve_size_t *address, string_t **str);

const struct sieve_opr_string_interface catenated_string_interface = {
	opr_catenated_string_dump,
	opr_catenated_string_read
};

const struct sieve_operand_def catenated_string_operand = {
	.name = "@catenated-string",
	.code = SIEVE_OPERAND_CATENATED_STRING,
	.class = &string_class,
	.interface = &catenated_string_interface
};

/*
 * Operand implementations
 */

/* Omitted */

void sieve_opr_omitted_emit(struct sieve_binary_block *sblock)
{
	(void)sieve_operand_emit(sblock, NULL, &omitted_operand);
}

/* Number */

void sieve_opr_number_emit(struct sieve_binary_block *sblock,
			   sieve_number_t number)
{
	(void)sieve_operand_emit(sblock, NULL, &number_operand);
	(void)sieve_binary_emit_integer(sblock, number);
}

bool sieve_opr_number_dump_data(const struct sieve_dumptime_env *denv,
				struct sieve_operand *oprnd,
				sieve_size_t *address, const char *field_name)
{
	const struct sieve_opr_number_interface *intf;

	oprnd->field_name = field_name;

	if (!sieve_operand_is_number(oprnd))
		return FALSE;

	intf = (const struct sieve_opr_number_interface *)oprnd->def->interface;
	if (intf->dump == NULL)
		return FALSE;

	return intf->dump(denv, oprnd, address);
}

bool sieve_opr_number_dump(const struct sieve_dumptime_env *denv,
			   sieve_size_t *address, const char *field_name)
{
	struct sieve_operand operand;

	sieve_code_mark(denv);

	if (!sieve_operand_read(denv->sblock, address, field_name, &operand))
		return FALSE;

	return sieve_opr_number_dump_data(denv, &operand, address, field_name);
}

int sieve_opr_number_read_data(const struct sieve_runtime_env *renv,
			       struct sieve_operand *oprnd,
			       sieve_size_t *address, const char *field_name,
			       sieve_number_t *number_r)
{
	const struct sieve_opr_number_interface *intf;

	oprnd->field_name = field_name;

	if (!sieve_operand_is_number(oprnd)) {
		sieve_runtime_trace_operand_error(
			renv, oprnd,
			"expected number operand but found %s",
			sieve_operand_name(oprnd));
		return SIEVE_EXEC_BIN_CORRUPT;
	}

	intf = (const struct sieve_opr_number_interface *)oprnd->def->interface;
	if (intf->read == NULL) {
		sieve_runtime_trace_operand_error(
			renv, oprnd,
			"number operand not implemented");
		return SIEVE_EXEC_FAILURE;
	}

	return intf->read(renv, oprnd, address, number_r);
}

int sieve_opr_number_read(const struct sieve_runtime_env *renv,
			  sieve_size_t *address, const char *field_name,
			  sieve_number_t *number_r)
{
	struct sieve_operand operand;
	int ret;

	if ((ret = sieve_operand_runtime_read(renv, address, field_name,
					      &operand)) <= 0)
		return ret;

	return sieve_opr_number_read_data(renv, &operand, address, field_name,
					  number_r);
}

static bool
opr_number_dump(const struct sieve_dumptime_env *denv,
		const struct sieve_operand *oprnd, sieve_size_t *address)
{
	sieve_number_t number = 0;

	if (sieve_binary_read_integer(denv->sblock, address, &number)) {
		if (oprnd->field_name != NULL) {
			sieve_code_dumpf(denv, "%s: NUM %llu",
					 oprnd->field_name,
					 (unsigned long long)number);
		} else {
			sieve_code_dumpf(denv, "NUM %llu",
					 (unsigned long long)number);
		}
		return TRUE;
	}
	return FALSE;
}

static int
opr_number_read(const struct sieve_runtime_env *renv,
		const struct sieve_operand *oprnd,
		sieve_size_t *address, sieve_number_t *number_r)
{
	if (!sieve_binary_read_integer(renv->sblock, address, number_r)) {
		sieve_runtime_trace_operand_error(
			renv, oprnd, "invalid number operand");
		return SIEVE_EXEC_BIN_CORRUPT;
	}
	return SIEVE_EXEC_OK;
}

/* String */

void sieve_opr_string_emit(struct sieve_binary_block *sblock, string_t *str)
{
	(void)sieve_operand_emit(sblock, NULL, &string_operand);
	(void)sieve_binary_emit_string(sblock, str);
}

bool sieve_opr_string_dump_data(const struct sieve_dumptime_env *denv,
				struct sieve_operand *oprnd,
				sieve_size_t *address, const char *field_name)
{
	const struct sieve_opr_string_interface *intf;

	oprnd->field_name = field_name;

	if (!sieve_operand_is_string(oprnd)) {
		sieve_code_dumpf(denv, "ERROR: INVALID STRING OPERAND %s",
				 sieve_operand_name(oprnd));
		return FALSE;
	}

	intf = (const struct sieve_opr_string_interface *)oprnd->def->interface;
	if (intf->dump == NULL) {
		sieve_code_dumpf(denv, "ERROR: DUMP STRING OPERAND");
		return FALSE;
	}

	return intf->dump(denv, oprnd, address);
}

bool sieve_opr_string_dump(const struct sieve_dumptime_env *denv,
			   sieve_size_t *address, const char *field_name)
{
	struct sieve_operand operand;

	sieve_code_mark(denv);

	if (!sieve_operand_read(denv->sblock, address, field_name, &operand)) {
		sieve_code_dumpf(denv, "ERROR: INVALID OPERAND");
		return FALSE;
	}

	return sieve_opr_string_dump_data(denv, &operand, address, field_name);
}

bool sieve_opr_string_dump_ex(const struct sieve_dumptime_env *denv,
			      sieve_size_t *address, const char *field_name,
			      const char *omitted_value)
{
	struct sieve_operand operand;

	sieve_code_mark(denv);
	if (!sieve_operand_read(denv->sblock, address, field_name, &operand)) {
		sieve_code_dumpf(denv, "ERROR: INVALID OPERAND");
		return FALSE;
	}

	if (omitted_value != NULL && sieve_operand_is_omitted(&operand)) {
		if (*omitted_value != '\0') {
			sieve_code_dumpf(denv, "%s: %s",
					 field_name, omitted_value);
		}
		return TRUE;
	}
	return sieve_opr_string_dump_data(denv, &operand, address, field_name);
}

int sieve_opr_string_read_data(const struct sieve_runtime_env *renv,
			       struct sieve_operand *oprnd,
			       sieve_size_t *address, const char *field_name,
			       string_t **str_r)
{
	const struct sieve_opr_string_interface *intf;

	oprnd->field_name = field_name;

	if (!sieve_operand_is_string(oprnd)) {
		sieve_runtime_trace_operand_error(
			renv, oprnd,
			"expected string operand but found %s",
			sieve_operand_name(oprnd));
		return SIEVE_EXEC_BIN_CORRUPT;
	}

	intf = (const struct sieve_opr_string_interface *)oprnd->def->interface;
	if (intf->read == NULL) {
		sieve_runtime_trace_operand_error(renv, oprnd,
			"string operand not implemented");
		return SIEVE_EXEC_FAILURE;
	}

	return intf->read(renv, oprnd, address, str_r);
}

int sieve_opr_string_read(const struct sieve_runtime_env *renv,
			  sieve_size_t *address, const char *field_name,
			  string_t **str_r)
{
	struct sieve_operand operand;
	int ret;

	if ((ret = sieve_operand_runtime_read(renv, address, field_name,
					      &operand)) <= 0)
		return ret;

	return sieve_opr_string_read_data(renv, &operand, address, field_name,
					  str_r);
}

int sieve_opr_string_read_ex(const struct sieve_runtime_env *renv,
			     sieve_size_t *address, const char *field_name,
			     bool optional, string_t **str_r, bool *literal_r)
{
	struct sieve_operand operand;
	int ret;

	if ((ret = sieve_operand_runtime_read(renv, address, field_name,
					      &operand)) <= 0)
		return ret;

	if (optional && sieve_operand_is_omitted(&operand)) {
		*str_r = NULL;
		return 1;
	}

	if (literal_r != NULL)
		*literal_r = sieve_operand_is_string_literal(&operand);

	return sieve_opr_string_read_data(renv, &operand, address, field_name,
					  str_r);
}

static void
_dump_string(const struct sieve_dumptime_env *denv, string_t *str,
	     const char *field_name)
{
	if (str_len(str) > 80) {
		if (field_name != NULL) {
			sieve_code_dumpf(denv, "%s: STR[%ld] \"%s",
					 field_name, (long)str_len(str),
					 str_sanitize(str_c(str), 80));
		} else {
			sieve_code_dumpf(denv, "STR[%ld] \"%s",
					 (long)str_len(str),
					 str_sanitize(str_c(str), 80));
		}
	} else {
		if (field_name != NULL) {
			sieve_code_dumpf(denv, "%s: STR[%ld] \"%s\"",
					 field_name, (long)str_len(str),
					 str_sanitize(str_c(str), 80));
		} else {
			sieve_code_dumpf(denv, "STR[%ld] \"%s\"",
					 (long)str_len(str),
					 str_sanitize(str_c(str), 80));
		}
	}
}

bool opr_string_dump(const struct sieve_dumptime_env *denv,
		     const struct sieve_operand *oprnd, sieve_size_t *address)
{
	string_t *str;

	if (sieve_binary_read_string(denv->sblock, address, &str)) {
		_dump_string(denv, str, oprnd->field_name);
		return TRUE;
	}
	return FALSE;
}

static int
opr_string_read(const struct sieve_runtime_env *renv,
		const struct sieve_operand *oprnd, sieve_size_t *address,
		string_t **str_r)
{
	if (!sieve_binary_read_string(renv->sblock, address, str_r)) {
		sieve_runtime_trace_operand_error(
			renv, oprnd, "invalid string operand");
		return SIEVE_EXEC_BIN_CORRUPT;
	}
	return SIEVE_EXEC_OK;
}

/* String list */

void
sieve_opr_stringlist_emit_start(struct sieve_binary_block *sblock,
				unsigned int listlen, void **context)
{
	sieve_size_t *end_offset = t_new(sieve_size_t, 1);

	/* Emit byte identifying the type of operand */
	(void)sieve_operand_emit(sblock, NULL, &stringlist_operand);

	/* Give the interpreter an easy way to skip over this string list */
	*end_offset = sieve_binary_emit_offset(sblock, 0);
	*context = end_offset;

	/* Emit the length of the list */
	(void)sieve_binary_emit_unsigned(sblock, listlen);
}

void sieve_opr_stringlist_emit_item(struct sieve_binary_block *sblock,
				    void *context ATTR_UNUSED, string_t *item)
{
	(void)sieve_opr_string_emit(sblock, item);
}

void sieve_opr_stringlist_emit_end(struct sieve_binary_block *sblock,
				   void *context)
{
	sieve_size_t *end_offset = (sieve_size_t *)context;

	(void)sieve_binary_resolve_offset(sblock, *end_offset);
}

bool sieve_opr_stringlist_dump_data(const struct sieve_dumptime_env *denv,
				    struct sieve_operand *oprnd,
				    sieve_size_t *address,
				    const char *field_name)
{
	if (oprnd == NULL || oprnd->def == NULL)
		return FALSE;

	oprnd->field_name = field_name;

	if (oprnd->def->class == &stringlist_class) {
		const struct sieve_opr_stringlist_interface *intf =
			(const struct sieve_opr_stringlist_interface *)
				oprnd->def->interface;

		if (intf->dump == NULL)
			return FALSE;
		return intf->dump(denv, oprnd, address);
	} else if (oprnd->def->class == &string_class) {
		const struct sieve_opr_string_interface *intf =
			(const struct sieve_opr_string_interface *)
				oprnd->def->interface;

		if (intf->dump == NULL)
			return FALSE;
		return intf->dump(denv, oprnd, address);
	}
	return FALSE;
}

bool sieve_opr_stringlist_dump(const struct sieve_dumptime_env *denv,
			       sieve_size_t *address, const char *field_name)
{
	struct sieve_operand operand;

	sieve_code_mark(denv);

	if (!sieve_operand_read(denv->sblock, address, field_name, &operand)) {
		return FALSE;
	}

	return sieve_opr_stringlist_dump_data(denv, &operand, address,
					      field_name);
}

bool sieve_opr_stringlist_dump_ex(const struct sieve_dumptime_env *denv,
				  sieve_size_t *address, const char *field_name,
				  const char *omitted_value)
{
	struct sieve_operand operand;

	sieve_code_mark(denv);

	if (!sieve_operand_read(denv->sblock, address, field_name, &operand))
		return FALSE;

	if (omitted_value != NULL && sieve_operand_is_omitted(&operand)) {
		if (*omitted_value != '\0') {
			sieve_code_dumpf(denv, "%s: %s",
					 field_name, omitted_value);
		}
		return TRUE;
	}

	return sieve_opr_stringlist_dump_data(denv, &operand, address,
					      field_name);
}

int sieve_opr_stringlist_read_data(const struct sieve_runtime_env *renv,
				   struct sieve_operand *oprnd,
				   sieve_size_t *address,
				   const char *field_name,
				   struct sieve_stringlist **strlist_r)
{
	if (oprnd == NULL || oprnd->def == NULL)
		return SIEVE_EXEC_FAILURE;

	oprnd->field_name = field_name;

	if (oprnd->def->class == &stringlist_class) {
		const struct sieve_opr_stringlist_interface *intf =
			(const struct sieve_opr_stringlist_interface *)
				oprnd->def->interface;
		int ret;

		if (intf->read == NULL) {
			sieve_runtime_trace_operand_error(
				renv, oprnd,
				"stringlist operand not implemented");
			return SIEVE_EXEC_FAILURE;
		}

		if ((ret = intf->read(renv, oprnd, address, strlist_r)) <= 0)
			return ret;
		return SIEVE_EXEC_OK;
	} else if (oprnd->def->class == &string_class) {
		/* Special case, accept single string as string list as well. */
		const struct sieve_opr_string_interface *intf =
			(const struct sieve_opr_string_interface *)
				oprnd->def->interface;
		int ret;

		if (intf->read == NULL) {
			sieve_runtime_trace_operand_error(
				renv, oprnd,
				"stringlist string operand not implemented");
			return SIEVE_EXEC_FAILURE;
		}

		if (strlist_r == NULL) {
			if ((ret = intf->read(renv, oprnd, address,
					      NULL)) <= 0)
				return ret;
		} else {
			string_t *stritem;
			if ((ret = intf->read(renv, oprnd, address,
					      &stritem)) <= 0)
				return ret;

			*strlist_r = sieve_single_stringlist_create(
				renv, stritem, FALSE);
		}
		return SIEVE_EXEC_OK;
	}

	sieve_runtime_trace_operand_error(
		renv, oprnd,
		"expected stringlist or string operand but found %s",
		sieve_operand_name(oprnd));
	return SIEVE_EXEC_BIN_CORRUPT;
}

int sieve_opr_stringlist_read(const struct sieve_runtime_env *renv,
			      sieve_size_t *address, const char *field_name,
			      struct sieve_stringlist **strlist_r)
{
	struct sieve_operand operand;
	int ret;

	if ((ret = sieve_operand_runtime_read(renv, address, field_name,
					      &operand)) <= 0)
		return ret;

	return sieve_opr_stringlist_read_data(renv, &operand, address,
					      field_name, strlist_r);
}

int sieve_opr_stringlist_read_ex(const struct sieve_runtime_env *renv,
				 sieve_size_t *address, const char *field_name,
				 bool optional,
				 struct sieve_stringlist **strlist_r)
{
	struct sieve_operand operand;
	int ret;

	if ((ret = sieve_operand_runtime_read(renv, address, field_name,
					      &operand)) <= 0)
		return ret;

	if (optional && sieve_operand_is_omitted(&operand)) {
		*strlist_r = NULL;
		return 1;
	}

	return sieve_opr_stringlist_read_data(renv, &operand, address,
					      field_name, strlist_r);
}

static bool
opr_stringlist_dump(const struct sieve_dumptime_env *denv,
		    const struct sieve_operand *oprnd, sieve_size_t *address)
{
	sieve_size_t pc = *address;
	sieve_size_t end;
	unsigned int length = 0;
 	sieve_offset_t end_offset;

	if (!sieve_binary_read_offset(denv->sblock, address, &end_offset))
		return FALSE;

	end = pc + end_offset;

	if (!sieve_binary_read_unsigned(denv->sblock, address, &length))
		return FALSE;

	return sieve_code_stringlist_dump(denv, address, length, end,
					  oprnd->field_name);
}

static int
opr_stringlist_read(const struct sieve_runtime_env *renv,
		    const struct sieve_operand *oprnd,
		    sieve_size_t *address, struct sieve_stringlist **strlist_r)
{
	sieve_size_t pc = *address;
	sieve_size_t end;
	unsigned int length = 0;
	sieve_offset_t end_offset;

	if (!sieve_binary_read_offset(renv->sblock, address, &end_offset)) {
		sieve_runtime_trace_operand_error(
			renv, oprnd, "stringlist corrupt: invalid end offset");
		return SIEVE_EXEC_BIN_CORRUPT;
	}

	end = pc + end_offset;

	if (!sieve_binary_read_unsigned(renv->sblock, address, &length)) {
		sieve_runtime_trace_operand_error(
			renv, oprnd, "stringlist corrupt: invalid length data");
		return SIEVE_EXEC_BIN_CORRUPT;
	}

	if (strlist_r != NULL) {
		*strlist_r = sieve_code_stringlist_create(
			renv, *address, (unsigned int)length, end);
	}

	/* Skip over the string list for now */
	*address = end;

	return SIEVE_EXEC_OK;
}

/* Catenated String */

void sieve_opr_catenated_string_emit(struct sieve_binary_block *sblock,
				     unsigned int elements)
{
	(void)sieve_operand_emit(sblock, NULL, &catenated_string_operand);
	(void)sieve_binary_emit_unsigned(sblock, elements);
}

static bool
opr_catenated_string_dump(const struct sieve_dumptime_env *denv,
			  const struct sieve_operand *oprnd,
			  sieve_size_t *address)
{
	unsigned int elements = 0;
	unsigned int i;

	if (!sieve_binary_read_unsigned(denv->sblock, address, &elements))
		return FALSE;

	if (oprnd->field_name != NULL) {
		sieve_code_dumpf(denv, "%s: CAT-STR [%ld]:",
				 oprnd->field_name, (long)elements);
	} else {
		sieve_code_dumpf(denv, "CAT-STR [%ld]:", (long)elements);
	}

	sieve_code_descend(denv);
	for (i = 0; i < (unsigned int)elements; i++) {
		if (!sieve_opr_string_dump(denv, address, NULL))
			return FALSE;
	}
	sieve_code_ascend(denv);

	return TRUE;
}

static int
opr_catenated_string_read(const struct sieve_runtime_env *renv,
			  const struct sieve_operand *oprnd,
			  sieve_size_t *address, string_t **str)
{
	unsigned int elements = 0;
	unsigned int i;
	int ret;

	if (!sieve_binary_read_unsigned(renv->sblock, address, &elements)) {
		sieve_runtime_trace_operand_error(
			renv, oprnd, "catenated string corrupt: "
			"invalid element count data");
		return SIEVE_EXEC_BIN_CORRUPT;
	}

	/* Parameter str can be NULL if we are requested to only skip and not
	   actually read the argument.
	 */
	if (str == NULL) {
		for (i = 0; i < (unsigned int)elements; i++) {
			if ((ret = sieve_opr_string_read(
				renv, address, NULL, NULL)) <= 0)
				return ret;
		}
	} else {
		string_t *strelm;
		string_t **elm = &strelm;

		*str = t_str_new(128);
		for (i = 0; i < (unsigned int)elements; i++) {

			if ((ret = sieve_opr_string_read(
				renv, address, NULL, elm)) <= 0)
				return ret;

			if (elm != NULL) {
				str_append_str(*str, strelm);

				if (str_len(*str) > SIEVE_MAX_STRING_LEN) {
					str_truncate(*str, SIEVE_MAX_STRING_LEN);
					elm = NULL;
				}
			}
		}
	}

	return SIEVE_EXEC_OK;
}

/*
 * Core operations
 */

/* Forward declarations */

static bool
opc_jmp_dump(const struct sieve_dumptime_env *denv, sieve_size_t *address);

static int
opc_jmp_execute(const struct sieve_runtime_env *renv, sieve_size_t *address);
static int
opc_jmptrue_execute(const struct sieve_runtime_env *renv,
		    sieve_size_t *address);
static int
opc_jmpfalse_execute(const struct sieve_runtime_env *renv,
		     sieve_size_t *address);

/* Operation objects defined in this file */

const struct sieve_operation_def sieve_jmp_operation = {
	.mnemonic = "JMP",
	.code = SIEVE_OPERATION_JMP,
	.dump = opc_jmp_dump,
	.execute = opc_jmp_execute
};

const struct sieve_operation_def sieve_jmptrue_operation = {
	.mnemonic = "JMPTRUE",
	.code = SIEVE_OPERATION_JMPTRUE,
	.dump = opc_jmp_dump,
	.execute = opc_jmptrue_execute
};

const struct sieve_operation_def sieve_jmpfalse_operation = {
	.mnemonic = "JMPFALSE",
	.code = SIEVE_OPERATION_JMPFALSE,
	.dump = opc_jmp_dump,
	.execute = opc_jmpfalse_execute
};

/* Operation objects defined in other files */

extern const struct sieve_operation_def cmd_stop_operation;
extern const struct sieve_operation_def cmd_keep_operation;
extern const struct sieve_operation_def cmd_discard_operation;
extern const struct sieve_operation_def cmd_redirect_operation;

extern const struct sieve_operation_def tst_address_operation;
extern const struct sieve_operation_def tst_header_operation;
extern const struct sieve_operation_def tst_exists_operation;
extern const struct sieve_operation_def tst_size_over_operation;
extern const struct sieve_operation_def tst_size_under_operation;

const struct sieve_operation_def *sieve_operations[] = {
	NULL,

	&sieve_jmp_operation,
	&sieve_jmptrue_operation,
	&sieve_jmpfalse_operation,

	&cmd_stop_operation,
	&cmd_keep_operation,
	&cmd_discard_operation,
	&cmd_redirect_operation,

	&tst_address_operation,
	&tst_header_operation,
	&tst_exists_operation,
	&tst_size_over_operation,
	&tst_size_under_operation
};

const unsigned int sieve_operation_count =
	N_ELEMENTS(sieve_operations);

/*
 * Operation functions
 */

sieve_size_t sieve_operation_emit(struct sieve_binary_block *sblock,
				  const struct sieve_extension *ext,
				  const struct sieve_operation_def *op_def)
{
	sieve_size_t address;

	if (ext != NULL) {
		i_assert(op_def->ext_def != NULL);
		address = sieve_binary_emit_extension(
			sblock, ext, sieve_operation_count);

		sieve_binary_emit_extension_object(
			sblock, &op_def->ext_def->operations, op_def->code);
		return address;
	}

	i_assert(op_def->ext_def == NULL);
	return sieve_binary_emit_byte(sblock, op_def->code);
}

bool sieve_operation_read(struct sieve_binary_block *sblock,
			  sieve_size_t *address, struct sieve_operation *oprtn)
{
	unsigned int code = sieve_operation_count;

	oprtn->address = *address;
	oprtn->def = NULL;
	oprtn->ext = NULL;

	if (!sieve_binary_read_extension(sblock, address, &code, &oprtn->ext))
		return FALSE;

	if (oprtn->ext == NULL) {
		if (code < sieve_operation_count)
			oprtn->def = sieve_operations[code];
		return (oprtn->def != NULL);
	}

	oprtn->def = (const struct sieve_operation_def *)
		sieve_binary_read_extension_object(
			sblock, address, &oprtn->ext->def->operations);
	return (oprtn->def != NULL);
}

/*
 * Jump operations
 */

/* Code dump */

static bool
opc_jmp_dump(const struct sieve_dumptime_env *denv, sieve_size_t *address)
{
	const struct sieve_operation *oprtn = denv->oprtn;
	unsigned int pc = *address;
	sieve_offset_t offset;

	if (sieve_binary_read_offset(denv->sblock, address, &offset)) {
		sieve_code_dumpf(denv, "%s %d [%08x]",
				 sieve_operation_mnemonic(oprtn), offset,
				 pc + offset);
	} else {
		return FALSE;
	}

	return TRUE;
}

/* Code execution */

static int
opc_jmp_execute(const struct sieve_runtime_env *renv,
		sieve_size_t *address ATTR_UNUSED)
{
	return sieve_interpreter_program_jump(renv->interp, TRUE, FALSE);
}

static int
opc_jmptrue_execute(const struct sieve_runtime_env *renv,
		    sieve_size_t *address ATTR_UNUSED)
{
	bool result = sieve_interpreter_get_test_result(renv->interp);

	sieve_runtime_trace(renv, SIEVE_TRLVL_COMMANDS,
			    "jump if result is true");
	sieve_runtime_trace_descend(renv);

	return sieve_interpreter_program_jump(renv->interp, result, FALSE);
}

static int
opc_jmpfalse_execute(const struct sieve_runtime_env *renv,
		     sieve_size_t *address ATTR_UNUSED)
{
	bool result = sieve_interpreter_get_test_result(renv->interp);

	sieve_runtime_trace(renv, SIEVE_TRLVL_COMMANDS,
			    "jump if result is false");
	sieve_runtime_trace_descend(renv);

	return sieve_interpreter_program_jump(renv->interp, !result, FALSE);
}
