/* Copyright (c) 2004-2018 Dovecot authors, see the included COPYING file */

#include "lib.h"
#include "md5.h"
#include "message-parser.h"
#include "mbox-md5.h"


struct mbox_md5_context {
	struct md5_context hdr_md5_ctx;
	bool seen_received_hdr;
};

struct mbox_md5_header_func {
	const char *header;
	bool (*func)(struct mbox_md5_context *ctx,
		     struct message_header_line *hdr);
};

static bool parse_date(struct mbox_md5_context *ctx,
		       struct message_header_line *hdr)
{
	if (!ctx->seen_received_hdr) {
		/* Received-header contains date too, and more trusted one */
		md5_update(&ctx->hdr_md5_ctx, hdr->value, hdr->value_len);
	}
	return TRUE;
}

static bool parse_delivered_to(struct mbox_md5_context *ctx,
			       struct message_header_line *hdr)
{
	md5_update(&ctx->hdr_md5_ctx, hdr->value, hdr->value_len);
	return TRUE;
}

static bool parse_message_id(struct mbox_md5_context *ctx,
			     struct message_header_line *hdr)
{
	if (!ctx->seen_received_hdr) {
		/* Received-header contains unique ID too,
		   and more trusted one */
		md5_update(&ctx->hdr_md5_ctx, hdr->value, hdr->value_len);
	}
	return TRUE;
}

static bool parse_received(struct mbox_md5_context *ctx,
			   struct message_header_line *hdr)
{
	if (!ctx->seen_received_hdr) {
		/* get only the first received-header */
		md5_update(&ctx->hdr_md5_ctx, hdr->value, hdr->value_len);
		if (!hdr->continues)
			ctx->seen_received_hdr = TRUE;
	}
	return TRUE;
}

static bool parse_x_delivery_id(struct mbox_md5_context *ctx,
				struct message_header_line *hdr)
{
	/* Let the local delivery agent help generate unique ID's but don't
	   blindly trust this header alone as it could just as easily come from
	   the remote. */
	md5_update(&ctx->hdr_md5_ctx, hdr->value, hdr->value_len);
	return TRUE;
}


static struct mbox_md5_header_func md5_header_funcs[] = {
	{ "Date", parse_date },
	{ "Delivered-To", parse_delivered_to },
	{ "Message-ID", parse_message_id },
	{ "Received", parse_received },
	{ "X-Delivery-ID", parse_x_delivery_id }
};

static int bsearch_header_func_cmp(const void *p1, const void *p2)
{
	const char *key = p1;
	const struct mbox_md5_header_func *func = p2;

	return strcasecmp(key, func->header);
}

static struct mbox_md5_context *mbox_md5_apop3d_init(void)
{
	struct mbox_md5_context *ctx;

	ctx = i_new(struct mbox_md5_context, 1);
	md5_init(&ctx->hdr_md5_ctx);
	return ctx;
}

static void mbox_md5_apop3d_more(struct mbox_md5_context *ctx,
				 struct message_header_line *hdr)
{
	struct mbox_md5_header_func *func;

	func = bsearch(hdr->name, md5_header_funcs,
		       N_ELEMENTS(md5_header_funcs), sizeof(*md5_header_funcs),
		       bsearch_header_func_cmp);
	if (func != NULL)
		(void)func->func(ctx, hdr);
}

static void mbox_md5_apop3d_finish(struct mbox_md5_context *ctx,
				   unsigned char result[STATIC_ARRAY 16])
{
	md5_final(&ctx->hdr_md5_ctx, result);
	i_free(ctx);
}

struct mbox_md5_vfuncs mbox_md5_apop3d = {
	mbox_md5_apop3d_init,
	mbox_md5_apop3d_more,
	mbox_md5_apop3d_finish
};
