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

#include "lib.h"
#include "buffer.h"
#include "base64.h"
#include "str.h"
#include "unichar.h"
#include "charset-utf8.h"
#include "qp-decoder.h"
#include "rfc822-parser.h"
#include "rfc2231-parser.h"
#include "message-parser.h"
#include "message-header-decode.h"
#include "message-address.h"
#include "message-decoder.h"

struct message_decoder_context {
	enum message_decoder_flags flags;
	normalizer_func_t *normalizer;
	struct message_part *prev_part;

	struct message_header_line hdr;
	buffer_t *buf, *buf2;

	char *charset_trans_charset;
	struct charset_translation *charset_trans;
	char translation_buf[CHARSET_MAX_PENDING_BUF_SIZE];
	size_t translation_size;

	struct qp_decoder *qp;
	struct base64_decoder base64_decoder;

	char *content_type, *content_charset;
	enum message_cte message_cte;

	bool binary_input:1;
};

static void
message_decode_body_init_charset(struct message_decoder_context *ctx,
				 struct message_part *part);

struct message_decoder_context *
message_decoder_init(normalizer_func_t *normalizer,
		     enum message_decoder_flags flags)
{
	struct message_decoder_context *ctx;

	ctx = i_new(struct message_decoder_context, 1);
	ctx->flags = flags;
	ctx->normalizer = normalizer;
	ctx->buf = buffer_create_dynamic(default_pool, 8192);
	ctx->buf2 = buffer_create_dynamic(default_pool, 8192);
	base64_decode_init(&ctx->base64_decoder, &base64_scheme, 0);
	return ctx;
}

void message_decoder_deinit(struct message_decoder_context **_ctx)
{
	struct message_decoder_context *ctx = *_ctx;

	*_ctx = NULL;

	if (ctx->charset_trans != NULL)
		charset_to_utf8_end(&ctx->charset_trans);
	if (ctx->qp != NULL)
		qp_decoder_deinit(&ctx->qp);

	buffer_free(&ctx->buf);
	buffer_free(&ctx->buf2);
	i_free(ctx->charset_trans_charset);
	i_free(ctx->content_type);
	i_free(ctx->content_charset);
	i_free(ctx);
}

void message_decoder_set_return_binary(struct message_decoder_context *ctx,
				       bool set)
{
	if (set)
		ctx->flags |= MESSAGE_DECODER_FLAG_RETURN_BINARY;
	else
		ctx->flags &= ENUM_NEGATE(MESSAGE_DECODER_FLAG_RETURN_BINARY);
	message_decode_body_init_charset(ctx, ctx->prev_part);
}

enum message_cte message_decoder_parse_cte(const struct message_header_line *hdr)
{
	struct rfc822_parser_context parser;
	enum message_cte message_cte;
	string_t *value;

	value = t_str_new(64);
	rfc822_parser_init(&parser, hdr->full_value, hdr->full_value_len, NULL);

	rfc822_skip_lwsp(&parser);

	/* Ensure we do not accidentally accept confused values like
	   'base64 binary' or embedded NULs */
	if (rfc822_parse_mime_token(&parser, value) == 1) {
		rfc822_skip_lwsp(&parser);
		/* RFC 2045 does not permit parameters for CTE,
		   but in case someone uses them, we accept
		   parameter separator ';' to be lenient. */
		if (*parser.data != ';')
			return MESSAGE_CTE_UNKNOWN;
	}

	message_cte = MESSAGE_CTE_UNKNOWN;
	switch (str_len(value)) {
	case 4:
		if (i_memcasecmp(str_data(value), "7bit", 4) == 0 ||
		    i_memcasecmp(str_data(value), "8bit", 4) == 0)
			message_cte = MESSAGE_CTE_78BIT;
		break;
	case 6:
		if (i_memcasecmp(str_data(value), "base64", 6) == 0)
			message_cte = MESSAGE_CTE_BASE64;
		else if (i_memcasecmp(str_data(value), "binary", 6) == 0)
			message_cte = MESSAGE_CTE_BINARY;
		break;
	case 16:
		if (i_memcasecmp(str_data(value), "quoted-printable", 16) == 0)
			message_cte = MESSAGE_CTE_QP;
		break;
	}
	rfc822_parser_deinit(&parser);
	return message_cte;
}

static void
parse_content_type(struct message_decoder_context *ctx,
		   struct message_header_line *hdr)
{
	struct rfc822_parser_context parser;
	const char *const *results;
	string_t *str;
	int ret;

	if (ctx->content_type != NULL)
		return;

	rfc822_parser_init(&parser, hdr->full_value, hdr->full_value_len, NULL);
	rfc822_skip_lwsp(&parser);
	str = t_str_new(64);
	ret = rfc822_parse_content_type(&parser, str);
	ctx->content_type = i_strdup(str_c(str));
	if (ret < 0) {
		rfc822_parser_deinit(&parser);
		return;
	}

	rfc2231_parse(&parser, &results);
	for (; *results != NULL; results += 2) {
		if (strcasecmp(results[0], "charset") == 0) {
			ctx->content_charset = i_strdup(results[1]);
			break;
		}
	}
	rfc822_parser_deinit(&parser);
}

static bool message_decode_header(struct message_decoder_context *ctx,
				  struct message_header_line *hdr,
				  struct message_block *output)
{
	size_t value_len;

	if (hdr->continues) {
		hdr->use_full_value = TRUE;
		return FALSE;
	}

	T_BEGIN {
		if (hdr->name_len == 12 &&
		    strcasecmp(hdr->name, "Content-Type") == 0)
			parse_content_type(ctx, hdr);
		if (hdr->name_len == 25 &&
		    strcasecmp(hdr->name, "Content-Transfer-Encoding") == 0)
			ctx->message_cte = message_decoder_parse_cte(hdr);
	} T_END;

	buffer_set_used_size(ctx->buf, 0);
	if ((ctx->flags & MESSAGE_DECODER_FLAG_RAW_ADDRESS_HEADERS) != 0 &&
	    message_header_is_address(hdr->name)) {
		/* Pass the raw header value through unchanged so the caller
		   can run message_address_parse() on the original bytes
		   (encoded-words intact) rather than on a string that may
		   contain RFC 5322 special characters after decoding. */
		buffer_append(ctx->buf, hdr->full_value, hdr->full_value_len);
	} else {
		message_header_decode_utf8(hdr->full_value, hdr->full_value_len,
					   ctx->buf, ctx->normalizer);
	}
	value_len = ctx->buf->used;

	if (ctx->normalizer != NULL) {
		(void)ctx->normalizer(hdr->name, hdr->name_len, ctx->buf);
		buffer_append_c(ctx->buf, '\0');
	} else {
		if (!uni_utf8_get_valid_data((const unsigned char *)hdr->name,
					     hdr->name_len, ctx->buf))
			buffer_append_c(ctx->buf, '\0');
	}

	ctx->hdr = *hdr;
	ctx->hdr.full_value = ctx->buf->data;
	ctx->hdr.full_value_len = value_len;
	ctx->hdr.value_len = 0;
	if (ctx->buf->used != value_len) {
		ctx->hdr.name = CONST_PTR_OFFSET(ctx->buf->data,
						 ctx->hdr.full_value_len);
		ctx->hdr.name_len = ctx->buf->used - 1 - value_len;
	}

	output->hdr = &ctx->hdr;
	return TRUE;
}

static void translation_buf_decode(struct message_decoder_context *ctx,
				   const unsigned char **data, size_t *size)
{
	unsigned char trans_buf[CHARSET_MAX_PENDING_BUF_SIZE+1];
	size_t data_wanted, skip;
	size_t trans_size, orig_size;

	/* @UNSAFE: move the previously untranslated bytes to trans_buf
	   and see if we have now enough data to get the next character
	   translated */
	memcpy(trans_buf, ctx->translation_buf, ctx->translation_size);
	data_wanted = sizeof(trans_buf) - ctx->translation_size;
	if (data_wanted > *size)
		data_wanted = *size;
	memcpy(trans_buf + ctx->translation_size, *data, data_wanted);

	orig_size = trans_size = ctx->translation_size + data_wanted;
	enum charset_result result =
		charset_to_utf8(ctx->charset_trans, trans_buf,
				&trans_size, ctx->buf2);

	if (trans_size > ctx->translation_size) {
		/* more input was processed */
	} else if (trans_size == ctx->translation_size &&
		   result == CHARSET_RET_INCOMPLETE_INPUT) {
		/* We get here at least with ISO-2022-JP:
		   1. Incomplete sequence to change encoding state
		   2. The final byte, which is found to be invalid.
		   3. iconv() passes through the first two bytes instead of
		      failing it as invalid input. The new byte starts a new
		      incomplete encoding state.
		   Handle this the same as if more input was processed. */
	} else if (trans_size > 0) {
		/* We get here when:
		   1. Incomplete sequence has been sent to charset_to_utf8()
		      previously, e.g. 3 bytes for a utf-32be character.
		   2. We have 1 new byte to complete the character, which
		      is found to be invalid.
		   3. charset_to_utf8() internally skips 1 byte, hoping to find
		      valid output from the next byte. But now we again have
		      only 3 bytes left, so the input is incomplete to get
		      forward.

		   So we'll assert here that result is
		   CHARSET_RET_INVALID_INPUT. Any other situation is not
		   expected to happen. */
		i_assert(result == CHARSET_RET_INVALID_INPUT);
		trans_size = ctx->translation_size + 1;
	} else {
		/* We should get here only if we didn't get forward in our
		   charset translation, i.e. we need more input to continue
		   translation. */
		i_assert(result == CHARSET_RET_INCOMPLETE_INPUT);
		/* This assert triggers if translation_buf is too small and
		   we can't get forward with the translation. */
		i_assert(orig_size < CHARSET_MAX_PENDING_BUF_SIZE);
		memcpy(ctx->translation_buf, trans_buf, orig_size);
		ctx->translation_size = orig_size;
		*data += *size;
		*size = 0;
		return;
	}
	skip = trans_size - ctx->translation_size;

	i_assert(*size >= skip);
	*data += skip;
	*size -= skip;

	ctx->translation_size = 0;
}

static void
message_decode_body_init_charset(struct message_decoder_context *ctx,
				 struct message_part *part)
{
	ctx->binary_input = ctx->content_charset == NULL &&
		(ctx->flags & MESSAGE_DECODER_FLAG_RETURN_BINARY) != 0 &&
		(part->flags & (MESSAGE_PART_FLAG_TEXT |
				MESSAGE_PART_FLAG_MESSAGE_RFC822)) == 0;

	if (ctx->binary_input)
		return;

	/* If some input was left untranslated in the previous MIME part,
	   it needs to be discarded now so it won't affect the next part. */
	ctx->translation_size = 0;

	if (ctx->charset_trans != NULL && ctx->content_charset != NULL &&
	    strcasecmp(ctx->content_charset, ctx->charset_trans_charset) == 0) {
		/* already have the correct translation selected */
		charset_to_utf8_reset(ctx->charset_trans);
		return;
	}

	if (ctx->charset_trans != NULL)
		charset_to_utf8_end(&ctx->charset_trans);
	i_free_and_null(ctx->charset_trans_charset);

	ctx->charset_trans_charset = i_strdup(ctx->content_charset != NULL ?
					      ctx->content_charset : "UTF-8");
	if (charset_to_utf8_begin(ctx->charset_trans_charset, ctx->normalizer,
				  &ctx->charset_trans) < 0)
		ctx->charset_trans = charset_utf8_to_utf8_begin(ctx->normalizer);
}

static bool message_decode_body(struct message_decoder_context *ctx,
				struct message_block *input,
				struct message_block *output)
{
	const unsigned char *data = NULL;
	size_t pos, size = 0;
	const char *error;

	switch (ctx->message_cte) {
	case MESSAGE_CTE_UNKNOWN:
		/* just skip this body */
		return FALSE;

	case MESSAGE_CTE_78BIT:
	case MESSAGE_CTE_BINARY:
		data = input->data;
		size = input->size;
		break;
	case MESSAGE_CTE_QP: {
		buffer_set_used_size(ctx->buf, 0);
		if (ctx->qp == NULL)
			ctx->qp = qp_decoder_init(ctx->buf);
		(void)qp_decoder_more(ctx->qp, input->data, input->size,
				      &pos, &error);
		data = ctx->buf->data;
		size = ctx->buf->used;
		break;
	}
	case MESSAGE_CTE_BASE64:
		buffer_set_used_size(ctx->buf, 0);
		if (!base64_decode_is_finished(&ctx->base64_decoder)) {
			if (base64_decode_more(&ctx->base64_decoder,
					       input->data, input->size,
					       &pos, ctx->buf) <= 0) {
				/* ignore the rest of the input in this
				   MIME part */
				(void)base64_decode_finish(&ctx->base64_decoder);
			}
		}
		data = ctx->buf->data;
		size = ctx->buf->used;
		break;
	}

	if (ctx->binary_input) {
		output->data = data;
		output->size = size;
	} else {
		buffer_set_used_size(ctx->buf2, 0);
		if (ctx->translation_size != 0)
			translation_buf_decode(ctx, &data, &size);

		pos = size;
		(void)charset_to_utf8(ctx->charset_trans,
				      data, &pos, ctx->buf2);
		if (pos != size) {
			ctx->translation_size = size - pos;
			i_assert(ctx->translation_size <=
				 sizeof(ctx->translation_buf));
			memcpy(ctx->translation_buf, data + pos,
			       ctx->translation_size);
		}
		output->data = ctx->buf2->data;
		output->size = ctx->buf2->used;
	}

	output->hdr = NULL;
	return TRUE;
}

bool message_decoder_decode_next_block(struct message_decoder_context *ctx,
				       struct message_block *input,
				       struct message_block *output)
{
	if (input->part != ctx->prev_part) {
		/* MIME part changed. */
		message_decoder_decode_reset(ctx);
	}

	output->part = input->part;
	ctx->prev_part = input->part;

	if (input->hdr != NULL) {
		output->size = 0;
		return message_decode_header(ctx, input->hdr, output);
	} else if (input->size != 0)
		return message_decode_body(ctx, input, output);
	else {
		output->hdr = NULL;
		output->size = 0;
		message_decode_body_init_charset(ctx, input->part);
		return TRUE;
	}
}

const char *
message_decoder_current_content_type(struct message_decoder_context *ctx)
{
	return ctx->content_type;
}

void message_decoder_decode_reset(struct message_decoder_context *ctx)
{
	const char *error;

	base64_decode_reset(&ctx->base64_decoder);

	if (ctx->qp != NULL)
		(void)qp_decoder_finish(ctx->qp, &error);
	i_free_and_null(ctx->content_type);
	i_free_and_null(ctx->content_charset);
	ctx->message_cte = MESSAGE_CTE_78BIT;
}
