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

#include "lib.h"
#include "istream.h"
#include "message-parser-private.h"

static int preparsed_parse_epilogue_init(struct message_parser_ctx *ctx,
					 struct message_block *block_r);
static int preparsed_parse_next_header_init(struct message_parser_ctx *ctx,
					    struct message_block *block_r);

static int preparsed_parse_eof(struct message_parser_ctx *ctx ATTR_UNUSED,
			       struct message_block *block_r ATTR_UNUSED)
{
	return -1;
}

static void preparsed_skip_to_next(struct message_parser_ctx *ctx)
{
	ctx->parse_next_block = preparsed_parse_next_header_init;
	while (ctx->part != NULL) {
		if (ctx->part->next != NULL) {
			ctx->part = ctx->part->next;
			break;
		}

		/* parse epilogue of multipart parent if requested */
		if (ctx->part->parent != NULL &&
		    (ctx->part->parent->flags & MESSAGE_PART_FLAG_MULTIPART) != 0 &&
		    (ctx->flags & MESSAGE_PARSER_FLAG_INCLUDE_MULTIPART_BLOCKS) != 0) {
			/* check for presence of epilogue */
			uoff_t part_end = ctx->part->physical_pos +
				ctx->part->header_size.physical_size +
				ctx->part->body_size.physical_size;
			uoff_t parent_end = ctx->part->parent->physical_pos +
				ctx->part->parent->header_size.physical_size +
				ctx->part->parent->body_size.physical_size;

			if (parent_end > part_end) {
				ctx->parse_next_block = preparsed_parse_epilogue_init;
				break;
			}
		}
		ctx->part = ctx->part->parent;
	}
	if (ctx->part == NULL)
		ctx->parse_next_block = preparsed_parse_eof;
}

static int preparsed_parse_body_finish(struct message_parser_ctx *ctx,
				       struct message_block *block_r)
{
	i_stream_skip(ctx->input, ctx->skip);
	ctx->skip = 0;

	preparsed_skip_to_next(ctx);
	return ctx->parse_next_block(ctx, block_r);
}

static int preparsed_parse_prologue_finish(struct message_parser_ctx *ctx,
					   struct message_block *block_r)
{
	i_stream_skip(ctx->input, ctx->skip);
	ctx->skip = 0;

	ctx->parse_next_block = preparsed_parse_next_header_init;
	ctx->part = ctx->part->children;
	return ctx->parse_next_block(ctx, block_r);
}

static int preparsed_parse_body_more(struct message_parser_ctx *ctx,
				     struct message_block *block_r)
{
	uoff_t end_offset = ctx->part->physical_pos +
		ctx->part->header_size.physical_size +
		ctx->part->body_size.physical_size;
	bool full;
	int ret;

	if ((ret = message_parser_read_more(ctx, block_r, &full)) <= 0)
		return ret;

	if (ctx->input->v_offset + block_r->size >= end_offset) {
		block_r->size = end_offset - ctx->input->v_offset;
		ctx->parse_next_block = preparsed_parse_body_finish;
	}
	ctx->skip = block_r->size;
	return 1;
}

static int preparsed_parse_prologue_more(struct message_parser_ctx *ctx,
					 struct message_block *block_r)
{
	uoff_t boundary_min_start, end_offset;
	const unsigned char *cur;
	bool full;
	int ret;

	i_assert(ctx->part->children != NULL);
	end_offset = ctx->part->children->physical_pos;

	if ((ret = message_parser_read_more(ctx, block_r, &full)) <= 0)
		return ret;

	if (ctx->input->v_offset + block_r->size >= end_offset) {
		/* we've got the full prologue: clip off the initial boundary */
		block_r->size = end_offset - ctx->input->v_offset;
		cur = block_r->data + block_r->size - 1;

		/* [\r]\n--boundary[\r]\n */
		if (block_r->size < 5 || *cur != '\n') {
			ctx->broken_reason = "Prologue boundary end not at expected position";
			return -1;
		}

		cur--;
		if (*cur == '\r') cur--;

		/* find newline just before boundary */
		for (; cur >= block_r->data; cur--) {
			if (*cur == '\n') break;
		}

		if (cur < block_r->data) {
			/* boundary is at the very start of the prologue, i.e.
			   there is no preamble before it (no preceding newline
			   to find). Don't read before block_r->data. */
			if (block_r->data[0] != '-' || block_r->data[1] != '-') {
				ctx->broken_reason = "Prologue boundary beginning not at expected position";
				return -1;
			}
			block_r->size = 0;
		} else {
			if (cur[0] != '\n' || cur[1] != '-' || cur[2] != '-') {
				ctx->broken_reason = "Prologue boundary beginning not at expected position";
				return -1;
			}

			if (cur != block_r->data && cur[-1] == '\r') cur--;

			/* clip boundary */
			block_r->size = cur - block_r->data;
		}

		ctx->parse_next_block = preparsed_parse_prologue_finish;
		ctx->skip = block_r->size;
		return 1;
	}

	/* retain enough data in the stream buffer to contain initial boundary */
	if (end_offset > BOUNDARY_END_MAX_LEN)
		boundary_min_start = end_offset - BOUNDARY_END_MAX_LEN;
	else
		boundary_min_start = 0;

	if (ctx->input->v_offset + block_r->size >= boundary_min_start) {
		if (boundary_min_start <= ctx->input->v_offset)
			return 0;
		block_r->size = boundary_min_start - ctx->input->v_offset;
	}
	ctx->skip = block_r->size;
	return 1;
}

static int preparsed_parse_epilogue_more(struct message_parser_ctx *ctx,
					 struct message_block *block_r)
{
	uoff_t end_offset = ctx->part->physical_pos +
		ctx->part->header_size.physical_size +
		ctx->part->body_size.physical_size;
	bool full;
	int ret;

	if ((ret = message_parser_read_more(ctx, block_r, &full)) <= 0)
		return ret;

	if (ctx->input->v_offset + block_r->size >= end_offset) {
		block_r->size = end_offset - ctx->input->v_offset;
		ctx->parse_next_block = preparsed_parse_body_finish;
	}
	ctx->skip = block_r->size;
	return 1;
}

static int
preparsed_parse_epilogue_skip_boundary_line(struct message_parser_ctx *ctx,
					    struct message_block *block_r)
{
	uoff_t end_offset = ctx->part->physical_pos +
		ctx->part->header_size.physical_size +
		ctx->part->body_size.physical_size;
	const unsigned char *ptr;
	bool full;
	int ret;

	if ((ret = message_parser_read_more(ctx, block_r, &full)) <= 0)
		return ret;

	/* The boundary line is longer than the buffer could hold at once.
	   Keep skipping until its end, the same way the forward parser's
	   parse_next_body_skip_boundary_line() does. */
	ptr = memchr(block_r->data, '\n', block_r->size);
	ctx->skip = ptr != NULL ?
		(size_t)(ptr - block_r->data) + 1 : block_r->size;

	if (end_offset < ctx->input->v_offset + ctx->skip) {
		ctx->broken_reason = "Epilogue boundary end not at expected position";
		return -1;
	}

	if (ptr != NULL)
		ctx->parse_next_block = preparsed_parse_epilogue_more;
	block_r->size = 0;
	return 0;
}

static int preparsed_parse_epilogue_boundary(struct message_parser_ctx *ctx,
					     struct message_block *block_r)
{
	uoff_t end_offset = ctx->part->physical_pos +
		ctx->part->header_size.physical_size +
		ctx->part->body_size.physical_size;
	const unsigned char *data, *cur;
	size_t size;
	bool full;
	int ret;

	if (end_offset - ctx->input->v_offset < 7) {
		ctx->broken_reason = "Epilogue position is wrong";
		return -1;
	}

	if ((ret = message_parser_read_more(ctx, block_r, &full)) <= 0)
		return ret;

	/* [\r]\n--boundary--[\r]\n */
	if (block_r->size < 7) {
		ctx->want_count = 7;
		return 0;
	}

	data = block_r->data;
	size = block_r->size;
	cur = data;

	if (*cur == '\r') cur++;

	if (cur[0] != '\n' || cur[1] != '-' || cur[2] != '-') {
		ctx->broken_reason = "Epilogue boundary start not at expected position";
		return -1;
	}

	/* find the end of the line */
	cur += 3;
	if ((cur = memchr(cur, '\n', size - (cur-data))) == NULL) {
		if (end_offset < ctx->input->v_offset + size) {
			ctx->broken_reason = "Epilogue boundary end not at expected position";
			return -1;
		} else if (ctx->input->v_offset + size < end_offset &&
			   size < BOUNDARY_END_MAX_LEN &&
			   !ctx->input->eof && !full) {
			ctx->want_count = BOUNDARY_END_MAX_LEN;
			return 0;
		}
		/* The line's end isn't in the data we have and buffering more
		   of it won't help. Skip what we have and keep looking for the
		   end of the line. */
		block_r->size = 0;
		ctx->parse_next_block =
			preparsed_parse_epilogue_skip_boundary_line;
		ctx->skip = size;
		return 0;
	}

	block_r->size = 0;
	ctx->parse_next_block = preparsed_parse_epilogue_more;
	ctx->skip = cur - data + 1;
	return 0;
}

static int preparsed_parse_body_init(struct message_parser_ctx *ctx,
				     struct message_block *block_r)
{
	uoff_t offset = ctx->part->physical_pos +
		ctx->part->header_size.physical_size;

	if (offset < ctx->input->v_offset) {
		/* header was actually larger than the cached size suggested */
		ctx->broken_reason = "Header larger than its cached size";
		return -1;
	}
	i_stream_skip(ctx->input, offset - ctx->input->v_offset);

	/* multipart messages may begin with --boundary--, which makes them
	   not have any children. */
	if ((ctx->part->flags & MESSAGE_PART_FLAG_MULTIPART) == 0 ||
	    ctx->part->children == NULL)
		ctx->parse_next_block = preparsed_parse_body_more;
	else
		ctx->parse_next_block = preparsed_parse_prologue_more;
	return ctx->parse_next_block(ctx, block_r);
}

static int preparsed_parse_epilogue_init(struct message_parser_ctx *ctx,
					 struct message_block *block_r)
{
	uoff_t offset = ctx->part->physical_pos +
		ctx->part->header_size.physical_size +
		ctx->part->body_size.physical_size;

	ctx->part = ctx->part->parent;

	if (offset < ctx->input->v_offset) {
		/* last child was actually larger than the cached size
		   suggested */
		ctx->broken_reason = "Part larger than its cached size";
		return -1;
	}
	i_stream_skip(ctx->input, offset - ctx->input->v_offset);

	ctx->parse_next_block = preparsed_parse_epilogue_boundary;
	return ctx->parse_next_block(ctx, block_r);
}

static int preparsed_parse_finish_header(struct message_parser_ctx *ctx,
					 struct message_block *block_r)
{
	if (ctx->part->children != NULL) {
		if ((ctx->part->flags & MESSAGE_PART_FLAG_MULTIPART) != 0 &&
		    (ctx->flags & MESSAGE_PARSER_FLAG_INCLUDE_MULTIPART_BLOCKS) != 0)
			ctx->parse_next_block = preparsed_parse_body_init;
		else {
			ctx->parse_next_block = preparsed_parse_next_header_init;
			ctx->part = ctx->part->children;
		}
	} else if ((ctx->flags & MESSAGE_PARSER_FLAG_SKIP_BODY_BLOCK) == 0) {
		ctx->parse_next_block = preparsed_parse_body_init;
	} else {
		preparsed_skip_to_next(ctx);
	}
	return ctx->parse_next_block(ctx, block_r);
}

static int preparsed_parse_next_header(struct message_parser_ctx *ctx,
				       struct message_block *block_r)
{
	struct message_header_line *hdr;
	int ret;

	ret = message_parse_header_next(ctx->hdr_parser_ctx, &hdr);
	if (ret == 0 || (ret < 0 && ctx->input->stream_errno != 0)) {
		ctx->want_count = i_stream_get_data_size(ctx->input) + 1;
		return ret;
	}

	if (hdr != NULL) {
		if (!hdr->continues) {
			ctx->all_headers_total_size += hdr->name_len;
			ctx->all_headers_total_size += hdr->middle_len;
		}
		ctx->all_headers_total_size += hdr->value_len;
		block_r->hdr = hdr;
		block_r->size = 0;
		return 1;
	}
	message_parse_header_deinit(&ctx->hdr_parser_ctx);

	ctx->parse_next_block = preparsed_parse_finish_header;

	/* return empty block as end of headers */
	block_r->hdr = NULL;
	block_r->size = 0;

	i_assert(ctx->skip == 0);
	if (ctx->input->v_offset != ctx->part->physical_pos +
	    ctx->part->header_size.physical_size) {
		ctx->broken_reason = "Cached header size mismatch";
		return -1;
	}
	return 1;
}

static int preparsed_parse_next_header_init(struct message_parser_ctx *ctx,
					    struct message_block *block_r)
{
	struct istream *hdr_input;

	i_assert(ctx->hdr_parser_ctx == NULL);

	if (ctx->part->physical_pos < ctx->input->v_offset) {
		/* The cached message_part tree is internally inconsistent:
		   the previous part's size overlaps into this part's
		   position. */
		ctx->broken_reason = "Part starts before its cached position";
		return -1;
	}
	i_stream_skip(ctx->input, ctx->part->physical_pos -
		      ctx->input->v_offset);

	/* the header may become truncated by --boundaries. limit the header
	   stream's size to what it's supposed to be to avoid duplicating (and
	   keeping in sync!) all the same complicated logic as in
	   parse_next_header(). */
	hdr_input = i_stream_create_limit(ctx->input, ctx->part->header_size.physical_size);
	ctx->hdr_parser_ctx =
		message_parse_header_init(hdr_input, NULL, ctx->hdr_flags);
	i_stream_unref(&hdr_input);

	size_t headers_available =
		ctx->all_headers_max_size > ctx->all_headers_total_size ?
		ctx->all_headers_max_size - ctx->all_headers_total_size : 0;
	message_parse_header_lower_limit(ctx->hdr_parser_ctx, headers_available);

	ctx->parse_next_block = preparsed_parse_next_header;
	return preparsed_parse_next_header(ctx, block_r);
}

struct message_parser_ctx *
message_parser_init_from_parts(struct message_part *parts,
			       struct istream *input,
			       const struct message_parser_settings *set)
{
	struct message_parser_ctx *ctx;

	i_assert(parts != NULL);

	ctx = message_parser_init_int(input, set);
	ctx->preparsed = TRUE;
	ctx->parts = ctx->part = parts;
	ctx->parse_next_block = preparsed_parse_next_header_init;
	return ctx;
}
