/*-
 * Copyright 2003 John-Mark Gurney.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 *	$Id: message.c,v 1.4 2003/09/15 23:37:19 jmg Exp $
 *
 */

#include <ctype.h>
#include <stdlib.h>
#include <string.h>

#include <mime_priv.h>
#include <ct.h>
#include <util.h>

static struct mime_message *
getmminit()
{
	struct mime_message *ret;

	ret = malloc(sizeof *ret);

	ret->mm_headers = NULL;
	ret->mm_body = NULL;
	ret->mm_bodylen = 0;
	ret->mm_attachments = NULL;
	ret->mm_nattachments = 0;
	ret->mm_prolog = NULL;
	ret->mm_prologlen = 0;
	ret->mm_epilog = NULL;
	ret->mm_epiloglen = 0;

	return ret;
}

struct mime_message *
mime_readmessage(const char *msg, size_t len, const char *crlfpair)
{
	const char *bd;
	char *bdcm;
	const char *pos;
	char *bdlc;
	const char *ct;
	struct attrib *a;
	struct mime_message *ret;

	ret = getmminit();

	if ((ret->mm_headers = mime_parseheader(msg, len, &pos, crlfpair))
	    == NULL)
		goto mrm;

	ct = mime_getvalue(ret->mm_headers, "content-type");

	if (ct == NULL || ct_cmptype(ct, "multipart") != 0) {
		/* not a multipart of no content-type */
		ret->mm_body = malloc(sizeof *ret->mm_body
		    * (len - (pos - msg)));
		memcpy(ret->mm_body, pos, sizeof *ret->mm_body
		    * (len - (pos - msg)));
		ret->mm_bodylen = len - (pos - msg);
	} else {
		/* multipart message */
		a = mime_getattrib(ct);
		bd = attrib_get(a, "boundary", NULL);
		/*
		 * XXX - need to be fixed to support "\r\n"
		 */
		bdcm = bd_makenorm(bd, crlfpair);
		bdlc = memmemory(pos, len - (pos - msg), bdcm, strlen(bdcm));
		free(bdcm);

		if (bdlc != pos) {
			ret->mm_prolog = malloc(bdlc - pos);
			memcpy(ret->mm_prolog, pos, bdlc - pos);
			ret->mm_prologlen = pos - bdlc;
		}

		ret->mm_attachments = mime_parsemultipart(bdlc + 1,
		    len - (bdlc + 1 - msg), bd, &ret->mm_nattachments, &pos,
		    crlfpair);

		if (pos - msg < len) {
			ret->mm_epilog = malloc(len - (pos - msg));
			memcpy(ret->mm_epilog, msg, len - (pos - msg));
			ret->mm_epiloglen = len - (pos - msg);
		}

	}

	return ret;
mrm:

	free(ret);
	return NULL;
}

struct mime_message **
mime_parsemultipart(const char *msg, size_t len, const char *bd, int *cnt,
    const char **endpos, const char *crlfpair)
{
	char *next;
	char *bdtail;
	char *bdnorm;
	struct mime_message **ret;
	int bdnormlen;
	int bdtaillen;

	ret = NULL;
	*cnt = 0;

	bdnorm = bd_makenorm(bd, crlfpair);
	bdnormlen = strlen(bdnorm);
	bdtail = bd_maketail(bd, crlfpair);
	bdtaillen = strlen(bdtail);

	if (memcmp(msg, strstr(bdnorm, "--"),
	    strlen(strstr(bdnorm, "--"))) != 0)
		return NULL;

	msg += strlen(strstr(bdnorm, "--"));
	len -= strlen(strstr(bdnorm, "--"));

	for (;;) {
		ret = realloc(ret, sizeof *ret * *cnt + 1);

		if ((next = memmemory(msg, len, bdnorm, bdnormlen)) == NULL)
			next = memmemory(msg, len, bdtail, bdtaillen);

		ret[(*cnt)++] = mime_readmessage(msg, next - msg, crlfpair);

		len -= next - msg;
		msg += next - msg;

		if (memcmp(msg, bdtail, strlen(bdtail)) == 0)
			break;

		msg += bdnormlen;
		len -= bdnormlen;
	}

	msg += bdnormlen;
	len -= bdnormlen;

	if (endpos != NULL)
		*endpos = msg;

	return ret;
}

struct mime_header *
mime_getmsgheaders(struct mime_message *mm)
{
	return mm->mm_headers;
}

static int
hexdigit(char a)
{
	if (a >= '0' && a <= '9')
		return a - '0';
	if (a >= 'a' && a <= 'f')
		return a - 'a' + 10;
	if (a >= 'A' && a <= 'F')
		return a - 'A' + 10;

	abort();
}

static int
quoted_decode(char *in, int len, char *out, int crlfsize)
{
	int inpos;
	int cnt;

	cnt = 0;

	for (inpos = 0; inpos < len; inpos++) {
		if (in[inpos] == '=') {
			/* special handling */
			inpos++;
			if ((in[inpos] >= '0' && in[inpos] <= '9')
			    || (in[inpos] >= 'A' && in[inpos] <= 'F')
			    || (in[inpos] >= 'a' && in[inpos] <= 'f')) {
				/* encoding a special char */
				*out++ = hexdigit(in[inpos]) << 4
				    | hexdigit(in[inpos + 1]);
				cnt++;
			} else
				inpos += crlfsize;	/* crlf size */
		} else {
			*out++ = in[inpos++];
			cnt++;
		}
	}

	return cnt;
}

static int
base64_decode(char *in, int len, char *out, int crlfsize)
{
	int cnt;
	int inpos;
	int i;
	int bits;
	int eqcnt;

	cnt = 0;
	inpos = 0;
	eqcnt = 0;
	while (inpos < len && eqcnt == 0) {
		bits = 0;
		for (i = 0; inpos < len && i < 4; inpos++) {
			switch (in[inpos]) {
			case 'A':  case 'B':  case 'C':  case 'D':  case 'E':
			case 'F':  case 'G':  case 'H':  case 'I':  case 'J':
			case 'K':  case 'L':  case 'M':  case 'N':  case 'O':
			case 'P':  case 'Q':  case 'R':  case 'S':  case 'T':
			case 'U':  case 'V':  case 'W':  case 'X':  case 'Y':
			case 'Z':
				bits = (bits << 6) | (in[inpos] - 'A');
				i++;
				break;
			case 'a':  case 'b':  case 'c':  case 'd':  case 'e':
			case 'f':  case 'g':  case 'h':  case 'i':  case 'j':
			case 'k':  case 'l':  case 'm':  case 'n':  case 'o':
			case 'p':  case 'q':  case 'r':  case 's':  case 't':
			case 'u':  case 'v':  case 'w':  case 'x':  case 'y':
			case 'z':
				bits = (bits << 6) | (in[inpos] - 'a' + 26);
				i++;
				break;
			case '0':  case '1':  case '2':  case '3':  case '4':
			case '5':  case '6':  case '7':  case '8':  case '9':
				bits = (bits << 6) | (in[inpos] - '0' + 52);
				i++;
				break;
			case '+':
				bits = (bits << 6) | 62;
				i++;
				break;
			case '/':
				bits = (bits << 6) | 63;
				i++;
				break;
			case '=':
				bits <<= 6;
				i++;
				eqcnt++;
				break;
			default:
				break;
			}
		}

		if (i == 0 && inpos >= len)
			continue;

		switch (eqcnt) {
		case 0:
			*out++ = (bits >> 16) & 0xff;
			*out++ = (bits >> 8) & 0xff;
			*out++ = bits & 0xff;
			cnt += 3;
			break;
		case 1:
			*out++ = (bits >> 16) & 0xff;
			*out++ = (bits >> 8) & 0xff;
			cnt += 2;
			break;
		case 2:
			*out++ = (bits >> 16) & 0xff;
			cnt += 1;
			break;
		}
	}

	return cnt;
}

static char *no_encode[] = {
	"7bit", "8bit", "binary"
};

static struct {
	char *name;
	float multiple;
	int (*decode)(char *, int, char *, int);
} encode[] = {
	{ "quoted-printable", 1, quoted_decode },
	{ "base64", (float)3 / 4, base64_decode }
};

int
mime_estimaterawsize(struct mime_message *mm)
{
	const char *cte;
	char *t;
	int len;
	int i;

	if (mm->mm_bodylen <= 0)
		return 0;

	if ((cte = mime_getvalue(mm->mm_headers, "content-transfer-encoding"))
	    == NULL)
		return mm->mm_bodylen;

	while (isspace(*cte))
		cte++;

	if ((t = strchr(cte, ';')) == NULL)
		len = strlen(cte);
	else
		len = t - cte;

	for (i = 0; i < sizeof no_encode / sizeof *no_encode; i++)
		if (len == strlen(no_encode[i]) && strncasecmp(cte,
		    no_encode[i], len) == 0)
			return mm->mm_bodylen;

	for (i = 0; i < sizeof encode / sizeof *encode; i++)
		if (len == strlen(encode[i].name) && strncasecmp(cte,
		    encode[i].name, len) == 0)
			return mm->mm_bodylen * encode[i].multiple;

	return -1;
}

int
mime_getrawbody(struct mime_message *mm, char *out, int crlfsize)
{
	const char *cte;
	char *t;
	int len;
	int i;

	if (mm->mm_bodylen <= 0)
		return 0;

	if ((cte = mime_getvalue(mm->mm_headers, "content-transfer-encoding"))
	    == NULL) {
		memcpy(out, mm->mm_body, mm->mm_bodylen);
		return mm->mm_bodylen;
	}

	while (isspace(*cte))
		cte++;

	if ((t = strchr(cte, ';')) == NULL)
		len = strlen(cte);
	else
		len = t - cte;


	for (i = 0; i < sizeof encode / sizeof *encode; i++)
		if (len == strlen(encode[i].name) && strncasecmp(cte,
		    encode[i].name, len) == 0)
			return encode[i].decode(mm->mm_body, mm->mm_bodylen,
			    out, crlfsize);

	return -1;
}

const char *
mime_getmsgbody(struct mime_message *mm)
{
	return mm->mm_body;
}

int
mime_getmsgbodylen(struct mime_message *mm)
{
	return mm->mm_bodylen;
}

struct mime_message *
mime_getattachment(struct mime_message *mm, int pos)
{
	if (pos >= mm->mm_nattachments || pos < 0)
		return NULL;

	return mm->mm_attachments[pos];
}

int
mime_nattachment(struct mime_message *mm)
{
	return mm->mm_nattachments;
}

const char *
mime_getmsgprolog(struct mime_message *mm)
{
	return mm->mm_prolog;
}

int
mime_getmsgprologlen(struct mime_message *mm)
{
	return mm->mm_prologlen;
}

const char *
mime_getmsgepilog(struct mime_message *mm)
{
	return mm->mm_epilog;
}

int
mime_getmsgepiloglen(struct mime_message *mm)
{
	return mm->mm_epiloglen;
}


syntax highlighted by Code2HTML, v. 0.9.1