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

#include "lib.h"
#include "buffer.h"
#include "str.h"
#include "randgen.h"
#include "charset-utf8.h"
#include "message-header-encode.h"
#include "message-header-decode.h"
#include "test-common.h"

static void test_message_header_decode(void)
{
	static const char *data[] = {
		" \t=?utf-8?q?=c3=a4?=  =?utf-8?q?=c3=a4?=  b  \t\r\n ", "\xC3\xA4\xC3\xA4  b  \t\r\n ",
		"a =?utf-8?q?=c3=a4?= b", "a \xC3\xA4 b",
		"a =?utf-8?q?=c3=a4?= b", "a \xC3\xA4 b",
		"a =?utf-8?q?=c3=a4?=\t\t\r\n =?utf-8?q?=c3=a4?= b", "a \xC3\xA4\xC3\xA4 b",
		"a =?utf-8?q?=c3=a4?=  x  =?utf-8?q?=c3=a4?= b", "a \xC3\xA4  x  \xC3\xA4 b",
		"a =?utf-8?b?w6TDpCDDpA==?= b", "a \xC3\xA4\xC3\xA4 \xC3\xA4 b",
		"=?utf-8?b?w6Qgw6Q=?=", "\xC3\xA4 \xC3\xA4",
		"a =?utf-8?b?////?= b", "a "UNICODE_REPLACEMENT_CHAR_UTF8" b",
		"a =?utf-16le?b?UADkAGkAdgDkAOQA?= b", "a P\xC3\xA4iv\xC3\xA4\xC3\xA4 b",
		"a =?utf-9?b?UMOkaXbDpMOk?= b", "a P\xC3\xA4iv\xC3\xA4\xC3\xA4 b",

	};
	string_t *dest;
	unsigned int i;

	test_begin("message header decode");

	dest = t_str_new(256);
	for (i = 0; i < N_ELEMENTS(data); i += 2) {
		str_truncate(dest, 0);
		message_header_decode_utf8((const unsigned char *)data[i],
					   strlen(data[i]), dest, NULL);
		test_assert_strcmp_idx(str_c(dest), data[i+1], i / 2);
	}
	test_end();
}

static void test_message_header_decode_read_overflow(void)
{
	const unsigned char input[] = "=?utf-8?Q?=EF?=";
	string_t *dest = t_str_new(32);

	test_begin("message header decode read overflow");
	message_header_decode_utf8(input, sizeof(input)-2, dest, NULL);
	test_end();
}

static void check_encoded(string_t *encoded, unsigned int test_idx)
{
	const unsigned char *enc = str_data(encoded), *p, *pend;
	size_t enc_len = str_len(encoded), cur_line_len = 0;

	p = enc;
	pend = enc + enc_len;
	while (p < pend) {
		if (*p == '\r') {
			p++;
			continue;
		}
		if (*p == '\n') {
			test_assert_idx(cur_line_len <= 76, test_idx);
			cur_line_len = 0;
			p++;
			continue;
		}
		cur_line_len++;
		test_assert_idx((*p >= 0x20 && *p <= 0x7e) || *p == '\t',
				test_idx);
		p++;
	}

	test_assert_idx(cur_line_len <= 76, test_idx);
}

static void
check_encode_decode_result(const unsigned char *inbuf, size_t inbuf_len,
			   string_t *out, unsigned int test_idx)
{
	static const unsigned char *rep_char =
		(const unsigned char *)UNICODE_REPLACEMENT_CHAR_UTF8;
	static const ptrdiff_t rep_char_len =
		UNICODE_REPLACEMENT_CHAR_UTF8_LEN;
	const unsigned char *outbuf = str_data(out);
	size_t outbuf_len = str_len(out);
	const unsigned char *pin, *pinend, *pout, *poutend;
	bool invalid_char = FALSE;

	if (test_has_failed())
		return;

	pin = inbuf;
	pinend = inbuf + inbuf_len;
	pout = outbuf;
	poutend = outbuf + outbuf_len;

	while (pin < pinend) {
		unichar_t ch;
		int nch;

		nch = uni_utf8_get_char_n(pin, pinend - pin, &ch);
		if (nch <= 0) {
			/* Invalid character; check proper substitution of
			   replacement character in encoded/decoded output. */
			pin++;
			if (!invalid_char) {
				/* Only one character is substituted for a run
				   of bad stuff. */
				test_assert_idx(
					(poutend - pout) >= rep_char_len &&
					memcmp(pout, rep_char,
					       rep_char_len) == 0, test_idx);
				pout += rep_char_len;
			}
			invalid_char = TRUE;
		} else {
			/* Valid character; check matching character bytes. */
			invalid_char = FALSE;
			test_assert_idx((pinend - pin) >= nch &&
					(poutend - pout) >= nch &&
					memcmp(pin, pout, nch) == 0, test_idx);
			pin += nch;
			pout += nch;
		}

		if (test_has_failed())
			return;
	}

	/* Both buffers must have reached the end now. */
	test_assert_idx(pin == pinend && pout == poutend, test_idx);
}

static void test_message_header_decode_encode_random(void)
{
	string_t *encoded, *decoded;
	unsigned char buf[1024];
	unsigned int i, j, buflen;

	test_begin("message header encode & decode randomly (7 bit)");

	encoded = t_str_new(256);
	decoded = t_str_new(256);
	for (i = 0; i < 1000; i++) {
		/* fill only with 7bit data so we don't have to worry about
		   the data being valid UTF-8 */
		buflen = i_rand_limit(sizeof(buf));
		for (j = 0; j < buflen; j++)
			buf[j] = i_rand_limit(128);

		str_truncate(encoded, 0);
		str_truncate(decoded, 0);

		/* test Q */
		message_header_encode_q(buf, buflen, encoded, 0);
		check_encoded(encoded, i);
		message_header_decode_utf8(encoded->data, encoded->used,
					   decoded, NULL);
		test_assert_idx(decoded->used == buflen &&
				memcmp(decoded->data, buf, buflen) == 0, i);

		/* test B */
		str_truncate(encoded, 0);
		str_truncate(decoded, 0);

		message_header_encode_b(buf, buflen, encoded, 0);
		check_encoded(encoded, i);
		message_header_decode_utf8(encoded->data, encoded->used,
					   decoded, NULL);
		test_assert_idx(decoded->used == buflen &&
				memcmp(decoded->data, buf, buflen) == 0, i);
	}
	test_end();

	test_begin("message header encode & decode randomly (8 bit)");

	for (i = 0; i < 1000; i++) {
		buflen = i_rand_limit(sizeof(buf));
		random_fill(buf, buflen);

		str_truncate(encoded, 0);
		str_truncate(decoded, 0);

		/* test Q */
		message_header_encode_q(buf, buflen, encoded, 0);
		check_encoded(encoded, i);
		message_header_decode_utf8(encoded->data, encoded->used,
					   decoded, NULL);
		check_encode_decode_result(buf, buflen, decoded, i);

		/* test B */
		str_truncate(encoded, 0);
		str_truncate(decoded, 0);

		message_header_encode_b(buf, buflen, encoded, 0);
		check_encoded(encoded, i);
		message_header_decode_utf8(encoded->data, encoded->used,
					   decoded, NULL);
		check_encode_decode_result(buf, buflen, decoded, i);
	}
	test_end();
}

int main(void)
{
	static void (*const test_functions[])(void) = {
		test_message_header_decode,
		test_message_header_decode_read_overflow,
		test_message_header_decode_encode_random,
		NULL
	};
	return test_run(test_functions);
}
