/*
* This takes an email address in start, and replaces it in new_text.
* The number of addresses is returned in naddr.
* Return value is 1 if there is a replacement, -1 for error, 0 otherwise.
*/

#include <string.h>
#include <stdlib.h>
#include <ctype.h>
#include "strdup_crsp.h"
#include "rfc822.h"

#include <assert.h>

static struct rfc822token *check_quoted(struct rfc822token *p, int *ntokens)
/*
* Parse an address, until angle bracket or comment comma or end of tokens and return 
*/
{
	assert(p);
	assert(ntokens);

	struct rfc822token *t = p, *rtc = NULL;
	int set_quote = 0, noop = 0, angle = 0, phrase = 1;

	int n = *ntokens;
	char const *end = NULL;
	while (n --> 0 && rtc == NULL)
	{
		if (phrase)
			end = t->ptr - 1;

		switch (t->token)
		{
			case 0: break;
			case '"': break;
			case '(': break;
			case ',': rtc = t + 1; break;
			case '<':
			{
				angle = 1;
				phrase = 0;
				break;
			}
			case '>': angle = 0; break;
			case '@': noop = !angle; break;

			/*
			* Courier chokes on \\.  Rather than trying to mimic an
			* erratic behavior, don't modify addresses with a backslash.
			* This can be justified by noting that use of backslashes
			* implies high acknowledgeability, but then there are not
			* many addresses like that.
			*/

			case '\\': noop = 1; break;
			default: set_quote = !angle; break;
		}
		++t;
	}
	*ntokens = n;

	if (noop || set_quote == 0 || end == NULL)
		return rtc;

	while (end > p->ptr && isspace(*(unsigned char*)end))
		--end;

	p->token='"';
	p->next=0;
	p->len = end - p->ptr;
	return rtc;
}

int replace_courier_wrap(char **new_text, int *naddrs, char *start)
{
	assert(new_text);
	assert(start);

	int rtc = 0;

	if (naddrs)
		*naddrs = 0;
	char **bufptrs = NULL;

	/*
	* rfc822t_alloc_new() should be called with the value of the header,
	* that is after the column.  Here we call it with the header name,
	* which will be counted as if it were a group: syntax.  Thus we have
	* to subtract 1 from naddrs.
	*/

	struct rfc822t *rfcp = rfc822t_alloc_new(start, NULL, NULL);
	if (rfcp)
	{
		int ntokens = rfcp->ntokens - 2, cntaddrs = 0;
		if (ntokens > 0)
			for (struct rfc822token *p = rfcp->tokens + 2; p; p = check_quoted(p, &ntokens))
				++cntaddrs;

		struct rfc822a *rfca = rfcp? rfc822a_alloc(rfcp): NULL;
		if (rfca)
		{
			if (rfca->naddrs)
			{
				if (naddrs)
					*naddrs = rfca->naddrs - 1;

				// not true for "From: ;user@example.com"
				// assert(cntaddrs == rfca->naddrs - 1);

				bufptrs = malloc(sizeof(char*) * rfca->naddrs);
				if (bufptrs)
				{
					for (int i = 0; i<rfca->naddrs; ++i)
					{
						struct rfc822token *tokenp = rfca->addrs[i].tokens;
						if (tokenp == NULL)
						{
							bufptrs[i]=0;
							continue;
						}

						if ((bufptrs[i] = rfc822_gettok(tokenp)) == NULL)
							continue;

						tokenp->next=0;
						tokenp->token=0;
						tokenp->ptr=bufptrs[i];
						tokenp->len=strlen(tokenp->ptr);
					}
				}
			}

			char *new_header = rfc822_getaddrs_wrap(rfca, 70);
			if (new_header)
			{
				if (strcmp(new_header, start) != 0)
				{
					char *p = strdup_crsp(new_header);
					if (p)
					{
						free(new_header);
						*new_text = p;
						rtc = 1;
					}
					else
						rtc = -1;
				}
				else
					free(new_header);
			}
			if (bufptrs)
				for (int i=0; i<rfca->naddrs; ++i)
					if (bufptrs[i]) free(bufptrs[i]);
			rfc822a_free(rfca);
		}

		rfc822t_free(rfcp);
	}
	free(bufptrs);

	return rtc;
}

#if defined TEST_MAIN
#include <stdio.h>

static const struct addr_test
{
	char const *src;
	char const *result; // NULL if not rewritten
	int rtc;
	int naddrs;
} addr_test[] =
{
	/*
	* NOTES:
	* Address literals are not checked to be valid IPv4 or IPv6.
	* Domain names are not checked for validity.
	* Local-part starting with '.' or with consecutive 's's are allowed.
	* Escaping backslashes are allowed also outside of quoted strings.
	*/
	// Hanno's oobheap
	{"From:\\", "From:", 1, 0},

	{"From: user@example.com", NULL, 0, 1},
	{"From: .user@example.com", NULL, 0, 1},
	{"From: .us..er@example.com", NULL, 0, 1},
	{"From: ;user@example.com", "From: ; user@example.com", 1, 2},
	{"From: us;er@example.com", "From: us; er@example.com", 1, 3},
	{"From: user;@example.com", "From: user; @example.com", 1, 3},
	{"From: user@;example.com", "From: user@; example.com", 1, 3},
	{"From: user@example;com", "From: user@example; com", 1, 3},
	{"From: user@example.com;", NULL, 0, 2},
	{"From: U. Ser <;user@example.com>", "From: \"U. Ser\" <;user@example.com>", 1, 1},
	{"From: U. Ser <us;er@example.com>", "From: \"U. Ser\" <us;er@example.com>", 1, 1},
	{"From: U. Ser <user;@example.com>", "From: \"U. Ser\" <user;@example.com>", 1, 1},
	{"From: U. Ser <user@;example.com>", "From: \"U. Ser\" <user@;example.com>", 1, 1},
	{"From: U. Ser <user@example;com>", "From: \"U. Ser\" <user@example;com>", 1, 1},
	{"From: U. Ser <user@example.com;", "From: \"U. Ser\" <user@example.com;>", 1, 1},
	{"From: U. Ser <user@example.com", "From: \"U. Ser\" <user@example.com>", 1, 1},
	{"From: [user]@example.com", NULL, 0, 1},
	{"From: us[er]@example.com", NULL, 0, 1},
	{"From: U.S. <us[er]@example.com>", "From: \"U.S.\" <us[er]@example.com>", 1, 1},
	{"From: user]@example.com", NULL, 0, 1},
	{"From: U.S. <user]@example.com>", "From: \"U.S.\" <user]@example.com>", 1, 1},
	{"From: user@[]example.com", NULL, 0, 1},
	{"From: U.S. <user@[]example.com>", "From: \"U.S.\" <user@[]example.com>", 1, 1},
	{"From: user@example.[]com", NULL, 0, 1},
	{"From: U.S. <user@example.[]com>", "From: \"U.S.\" <user@example.[]com>", 1, 1},
	{"From: user@example.com[]", NULL, 0, 1},
	{"From: U.S. <user@example.com[]>", "From: \"U.S.\" <user@example.com[]>", 1, 1},
	{"From: U.S. <user@example.com> []", "From: \"U.S.\" <user@example.com>, []", 1, 2},
	{"From: \"user@example.com", "From: \"user@example.com\"", 1, 1},
	{"From: \"user\\\"@example.com", "From: \"user\\\"@example.com\"", 1, 1},
	{"From: \"\"\"@example.com", "From: \"\", \"@example.com\"", 1, 2},
	{"From: \"\"@example.com", NULL, 0, 1},

	{"From: (UTF-8 comment: ꯍ) ꯍ@example.com", "From: ꯍ@example.com (UTF-8 comment: ꯍ)", 1, 1},
	{"From: Empty local part <\"\"@example.com>", NULL, 0, 1},
	{"From: \"\"@ , \"\" , @", "From: \"\"@, \"\", @", 1, 3},
	{"From: <>, <@> , \"foo\" <>", "From: , @, \"foo\" <>", 1, 3},
	{"From: user@example@example.com, user (without @), The <user (without @)>", "From: user@example@example.com, user (without @),\r\n  The <user (without @)>", 1, 3},
	{"From: user@@example.com, user (with trailing @)@, The <user (without @)>@", "From: user@@example.com, user@ (with trailing @),\r\n  The <user (without @)>, @", 1, 4},
	{"From: @1.example @2.example: user@example.com", "From: @1.example@2.example:user@example.com", 1, 1},
	{"From: a@b.c,d@e.f,g@h.i", "From: a@b.c, d@e.f, g@h.i", 1, 3},
	{"From: \"I have spaces\"@example.com", NULL, 0, 1},
	{"From: \"I have  two  spaces\"@example.com", NULL, 0, 1},
	{"To: user@example.com", NULL, 0, 1},
	{"To: U. Ser <user@example.com>", "To: \"U. Ser\" <user@example.com>", 1, 1},
	{"To: U. Ser <user@example.com> not continued", "To: \"U. Ser\" <user@example.com>, not, continued", 1, 3},
	{"To: U\" Ser <user@example.com> not continued", "To: U, \" Ser <user@example.com> not continued\"", 1, 2},
	{"To: \"someone@example.org\" <user@example.com>", NULL, 0, 1},
	{"To: someone\\@example.org <user@example.com>", "To: \"someone\\@example.org\" <user@example.com>", 1, 1},
	{"Bcc: User with a comma <user\\,@example.com>", "Bcc: User with a comma <user,@example.com>", 1, 1},
	{"Bcc: User with a comma <\"user,\"@example.com>", NULL, 0, 1},
	{"Bcc: User with inner comma <us\\,er@example.com>", "Bcc: User with inner comma <us,er@example.com>", 1, 1},
	{"Bcc: User with inner comma <\"us,er\"@example.com>", NULL, 0, 1},
	{"Bcc: User with leading comma <\\,user@example.com>", "Bcc: User with leading comma <,user@example.com>", 1, 1},
	{"Bcc: User with leading comma <\",user\"@example.com>", NULL, 0, 1},
	{"From: user\\,@example.com", "From: user, @example.com", 1, 2},
	{"From: final.comma@example.org ,", "From: final.comma@example.org", 1, 1},
	{"From: final.comma@example.org , ", "From: final.comma@example.org", 1, 1},
	{"From: first@example.org , second@example.com", "From: first@example.org, second@example.com", 1, 2},
	{"From: \"first,last\"@example.org , second(is it?\n)@example.com", "From: \"first,last\"@example.org, second@example.com (is it? )", 1, 2},
	{"From: \"first.last\"@example.org , second@example.com", "From: \"first.last\"@example.org, second@example.com", 1, 2},
	{"From: first.last@example.org , second@example.com", "From: first.last@example.org, second@example.com", 1, 2},
	{"From: first\\,last@example.org , second@example.com", "From: first, last@example.org, second@example.com", 1, 3},
	{"From: first\\@last@example.org , second@example.com", "From: first@last@example.org, second@example.com", 1, 2},
	{"From: \"first@last\"@example.org , second@example.com", "From: \"first@last\"@example.org, second@example.com", 1, 2},
	{"From: first\\@last@example.org,second@example.com", "From: first@last@example.org, second@example.com", 1, 2},
	{"From: first\\\\last@example.org,second@example.com", "From: first, last@example.org, second@example.com", 1, 3},
	{"From: \"first\\last\"@example.org,second@example.com", "From: \"first\\last\"@example.org, second@example.com", 1, 2},
	{"From: first\\@last@>example.org,second@example.com", "From: first@last@example.org, second@example.com", 1, 2},
	{"From: \"(me)\"@example.net, second@example.com", NULL, 0, 2},
	{"From: \\(m(the em)e\\)@example.net, second@example.com", "From: \"(m(the em)e\\)@example.net, second@example.com\"", 1, 1},
	{"From: \"<me>\"@example.net, [second]s@example.com", NULL, 0, 2},
	{"From: \\用(useless to escape)戶@example.net, second@example.com", "From: 用 (useless to escape), 戶@example.net, second@example.com", 1, 3},
	{"From: \\用(never closed戶@example.net, second@example.com", "From: 用, \"(never closed戶@example.net, second@example.com\"", 1, 2},
	{"From: \\用never opened)戶@example.net, second@[(the ip address) 192.0.2.30]", "From: 用never, opened, 戶@example.net,\r\n  second@[192.0.2.30] (the ip address)", 1, 4},
	{"From: mimì@foà.example, second@[2001:db8::1]", NULL, 0, 2},
	{"From: first@[2001:db8::1], second@[IPv6:2001:db8::1], third@[in fact: anything would do]", "From: first@[2001:db8::1], second@[IPv6:2001:db8::1], third@[in,\r\n  fact: anything, would, do]", 1, 7},
	{"From: some user <user@example.com> , all", "From: some user <user@example.com>, all", 1, 2},
	{"From: some user <user@example.com> , \"all\"", "From: some user <user@example.com>, \"all\"", 1, 2},

	// rfc5322 appendix-A.1.2, A.1.3
	{"From: \"Joe Q. Public\" <john.q.public@example.com>", NULL, 0, 1},
	{"To: Mary Smith <mary@x.test>, jdoe@example.org, Who? <one@y.test>", "To: Mary Smith <mary@x.test>, jdoe@example.org, \"Who?\" <one@y.test>", 1, 3},
	{"Cc: <boss@nil.test>, \"Giant; \\\"Big\\\" Box\" <sysservices@example.net>", "Cc: boss@nil.test, \"Giant; \\\"Big\\\" Box\" <sysservices@example.net>", 1, 2},
	{"To: A Group:Ed Jones <c@a.test>,joe@where.test,John <jdoe@one.test>;", "To: A Group: Ed Jones <c@a.test>, joe@where.test,\r\n  John <jdoe@one.test>;", 1, 5},
	{"Cc: Undisclosed recipients:;", "Cc: \"Undisclosed recipients\" recipients: ;", 1, 2},

	// rfc3696 section 3, discarding errata
	{"From: Abc\\@def@example.com", "From: Abc@def@example.com", 1, 1},
	{"From: Fred\\ Bloggs@example.com", "From: Fred, Bloggs@example.com", 1, 2},
	{"From: Joe.\\\\Blow@example.com", "From: Joe.Blow@example.com", 1, 1},
	{"From: \"Abc@def\"@example.com", NULL, 0, 1},
	{"From: \"Fred Bloggs\"@example.com", NULL, 0, 1},
	{"From: user+mailbox@example.com", NULL, 0, 1},
	{"From: customer/department=shipping@example.com", NULL, 0, 1},
	{"From: $A12345@example.com", NULL, 0, 1},
	{"From: !def!xyz%abc@example.com", NULL, 0, 1},
	{"From: _somename@example.com", NULL, 0, 1},
	{"From: _somename@example.com <_somename@example.com>", "From: \"_somename@example.com\" <_somename@example.com>", 1, 1},
	{NULL, NULL, 0, 0}
};

#if 0
// code to import addresses from dkim-mailparse.c
static char *rearrange(char *s)
{
	if (s == NULL)
		return NULL;

	unsigned len = strlen(s);
	char *rt = malloc(4*len + 2);
	if (rt == NULL)
		abort();

	int ch;
	char *dest = rt;
	*dest++ = '"';
	do
	{
		ch = *(unsigned char*)s++;
		if (ch)
		{
			if (strchr("\"\r\n\\", ch))
			{
				*dest++ = '\\';
				switch (ch)
				{
					case '"': *dest++ = ch; break;
					case '\n': *dest++ = 'n'; break;
					case '\r': *dest++ = 'r'; break;
					case '\\': *dest++ = '\\'; break;
					default: assert(0); break;
				}
			}
			else *dest++ = ch;
		}
	} while (ch);

	*dest++ = '"';
	*dest = 0;
	return rt;
}

static int do_testsuite(void)
{
	for (struct addr_test const *t = &addr_test[0]; t->src; ++t)
	{
		char *nt = NULL;
		int naddrs = 0;
		unsigned len = strlen(t->src);
		char *col = strchr(t->src, ':'), *src;
		if (col == NULL || col - t->src > 5)
		{
			src = malloc(len + 7);
			if (src)
				strcat(strcpy(src, "From: "), t->src);
		}
		else
			src = strdup(t->src);
		if (src == NULL)
			abort();

		int rtc = replace_courier_wrap(&nt, &naddrs, src);
		char *psrc = rearrange(src);
		char *pres = rearrange(nt);
		printf("\t{%s, %s, %d, %d},\n",
			psrc, pres? pres: "NULL", rtc, naddrs);
		free(pres);
		free(psrc);
		free(src);
		free(nt);
	}

	return 0;
}
#endif

static int strnullcmp(char const *a, char const *b)
{
	if (a && b) return strcmp(a, b);
	return a != b;
}

static int do_testsuite(void)
{
	int errs = 0;

	for (struct addr_test const *t = &addr_test[0]; t->src; ++t)
	{
		char *src = strdup(t->src);
		if (src)
		{
			char *nt = NULL;
			int naddrs = 0;
			int rtc = replace_courier_wrap(&nt, &naddrs, src);
			if (strnullcmp(nt, t->result))
			{
				++errs;
				printf("test failed for %s\nresult=%s\nexpect=%s\n\n",
					t->src, nt, t->result);
			}
			else if (naddrs != t->naddrs || rtc != t->rtc)
			{
				++errs;
				printf("test failed for %s\nnaddrs = %d (expect %d) rtc = %d (expect %d)\n\n",
					t->src, naddrs, t->naddrs, rtc, t->rtc);
			}
			free(nt);
		}
		else abort();
	}

	return errs > 0;
}

int main(int argc, char *argv[])
{
	if (argc == 1)
		return do_testsuite();

	for (int i = 1; i < argc; ++i)
	{
		char *nt = NULL;
		int naddrs = 0;
		int rtc = replace_courier_wrap(&nt, &naddrs, argv[i]);
		printf("%s -> rtc=%d for %d address(es)\n%s\n",
			argv[i], rtc, naddrs, nt? nt: "NULL");
		free(nt);
	}

	return 0;
}
#endif
