/*
** mhshowsbr.c -- routines to display the contents of MIME messages
**
** This code is Copyright (c) 2002, by the authors of nmh.  See the
** COPYRIGHT file in the root directory of the nmh distribution for
** complete copyright information.
*/

#include <h/mh.h>
#include <fcntl.h>
#include <h/signals.h>
#include <errno.h>
#include <signal.h>
#include <h/tws.h>
#include <h/mime.h>
#include <h/mhparse.h>
#include <h/utils.h>
#include <sys/wait.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sysexits.h>

extern int debugsw;

int nolist   = 0;
char *formsw = NULL;


/* mhparse.c */
int pidcheck(int);

/* mhmisc.c */
int part_ok(CT, int);
int type_ok(CT, int);
void content_error(char *, CT, char *, ...);
void flush_errors(void);

/* mhlistsbr.c */
int list_switch(CT, int, int, int);
int list_content(CT, int, int, int);

/*
** prototypes
*/
void show_all_messages(CT *);
int show_content_aux(CT, int, char *, char *);

/*
** static prototypes
*/
static void show_single_message(CT, char *);
static void DisplayMsgHeader(CT, char *);
static int show_switch(CT, int);
static int show_content(CT, int);
static int show_content_aux2(CT, int, char *, char *, int, int, int);
static int show_text(CT, int);
static int show_multi(CT, int);
static int show_multi_internal(CT, int);
static int show_multi_aux(CT, int, char *);
static int show_message_rfc822(CT, int);
static int show_partial(CT, int);
static int show_external(CT, int);


/*
** Top level entry point to show/display a group of messages
*/

void
show_all_messages(CT *cts)
{
	CT ct, *ctp;

	/*
	** If form is not specified, then get default form
	** for showing headers of MIME messages.
	*/
	if (!formsw)
		formsw = mh_xstrdup(etcpath("mhl.headers"));

	/*
	** If form is "mhl.null", suppress display of header.
	*/
	if (strcmp(formsw, "mhl.null")==0)
		formsw = NULL;

	for (ctp = cts; *ctp; ctp++) {
		ct = *ctp;

		if (!type_ok(ct, 1)) {  /* top-level type */
			continue;
		}
		if (cts[1]) {
			if (ctp != cts) {
				printf("\n\n");
			}
			printf(">>> Message %s\n\n", ct->c_file);
		}
		show_single_message(ct, formsw);
	}
}


/*
** Entry point to show/display a single message
*/

static void
show_single_message(CT ct, char *form)
{
	/*
	** Allow user executable bit so that temporary directories created by
	** the viewer (e.g., lynx) are going to be accessible
	*/
	umask(ct->c_umask & ~(0100));

	/*
	** If you have a format file, then display
	** the message headers.
	*/
	if (form)
		DisplayMsgHeader(ct, form);

	/* Show the body of the message */
	show_switch(ct, 0);

	if (ct->c_fp) {
		fclose(ct->c_fp);
		ct->c_fp = NULL;
	}
	if (ct->c_ceclosefnx)
		(*ct->c_ceclosefnx) (ct);

	flush_errors();
}


/*
** Use mhl to show the header fields
*/
static void
DisplayMsgHeader(CT ct, char *form)
{
	pid_t child_id;
	int vecp;
	char *vec[8];

	vecp = 0;
	vec[vecp++] = "mhl";
	vec[vecp++] = "-form";
	vec[vecp++] = form;
	vec[vecp++] = "-nobody";
	vec[vecp++] = ct->c_file;
	vec[vecp] = NULL;

	fflush(stdout);

	switch (child_id = fork()) {
	case NOTOK:
		adios(EX_OSERR, "fork", "unable to");
		/* NOTREACHED */

	case OK:
		execvp("mhl", vec);
		fprintf(stderr, "unable to exec ");
		perror("mhl");
		_exit(EX_OSERR);
		/* NOTREACHED */

	default:
		pidcheck(pidwait(child_id, NOTOK));
		break;
	}
}


/*
** Switching routine.  Call the correct routine
** based on content type.
*/

static int
show_switch(CT ct, int alternate)
{
	switch (ct->c_type) {
	case CT_MULTIPART:
		return show_multi(ct, alternate);
		break;

	case CT_MESSAGE:
		switch (ct->c_subtype) {
			case MESSAGE_PARTIAL:
				return show_partial(ct, alternate);
				break;

			case MESSAGE_EXTERNAL:
				return show_external(ct, alternate);
				break;

			case MESSAGE_RFC822:
			default:
				return show_message_rfc822(ct, alternate);
				break;
		}
		break;

	case CT_TEXT:
		return show_text(ct, alternate);
		break;

	case CT_AUDIO:
	case CT_IMAGE:
	case CT_VIDEO:
	case CT_APPLICATION:
		return show_content(ct, alternate);
		break;

	default:
		adios(EX_DATAERR, NULL, "unknown content type %d", ct->c_type);
		break;
	}

	return 0;  /* NOT REACHED */
}


/*
** Generic method for displaying content
*/

static int
show_content(CT ct, int alternate)
{
	char *cp, buffer[BUFSIZ];
	CI ci = &ct->c_ctinfo;

	/* Check for mhshow-show-type/subtype */
	snprintf(buffer, sizeof(buffer), "mhshow-show-%s/%s",
				ci->ci_type, ci->ci_subtype);
	if ((cp = context_find(buffer)) && *cp != '\0')
		return show_content_aux(ct, alternate, cp, NULL);

	/* Check for mhshow-show-type */
	snprintf(buffer, sizeof(buffer), "mhshow-show-%s", ci->ci_type);
	if ((cp = context_find(buffer)) && *cp != '\0')
		return show_content_aux(ct, alternate, cp, NULL);

	if ((cp = ct->c_showproc))
		return show_content_aux(ct, alternate, cp, NULL);

	/* complain if we are not a part of a multipart/alternative */
	if (!alternate)
		return show_content_aux(ct, alternate, "%l", NULL);

	return NOTOK;
}


/*
** Parse the display string for displaying generic content
*/
int
show_content_aux(CT ct, int alternate, char *cp, char *cracked)
{
	int fd, len, buflen, quoted;
	int xstdin, xlist;
	char *bp, *pp, *file, buffer[BUFSIZ];
	CI ci = &ct->c_ctinfo;

	if (!ct->c_ceopenfnx) {
		if (!alternate)
			content_error(NULL, ct,
					"don't know how to decode content");

		return NOTOK;
	}

	file = NULL;
	if ((fd = (*ct->c_ceopenfnx) (ct, &file)) == NOTOK)
		return NOTOK;
	if (ct->c_showproc && strcmp(ct->c_showproc, "true")==0)
		return (alternate ? DONE : OK);

	xlist  = 0;
	xstdin = 0;

	if (cracked) {
		strncpy(buffer, cp, sizeof(buffer));
		goto got_command;
	}

	/* get buffer ready to go */
	bp = buffer;
	buflen = sizeof(buffer) - 1;
	bp[0] = bp[buflen] = '\0';
	quoted = 0;

	/* Now parse display string */
	for ( ; *cp && buflen > 0; cp++) {
		if (*cp == '%') {
			pp = bp;

			switch (*++cp) {
			case 'a':
				/* insert parameters from Content-Type field */
				{
				char **ap, **ep;
				char *s = "";

				for (ap = ci->ci_attrs, ep = ci->ci_values;
						*ap; ap++, ep++) {
					snprintf(bp, buflen, "%s%s=\"%s\"",
							s, *ap, *ep);
					len = strlen(bp);
					bp += len;
					buflen -= len;
					s = " ";
				}
				}
				break;

			case 'c':
				/* insert charset */
				strncpy(bp, ct->c_charset ? ct->c_charset :
						"US-ASCII", buflen);
				break;

			case 'd':
				/* insert content description */
				if (ct->c_descr) {
					char *s;

					s = trimcpy(ct->c_descr);
					strncpy(bp, s, buflen);
					mh_free0(&s);
				}
				break;

			case 'F':
				/* %f, and stdin is terminal not content */
				xstdin = 1;
				/* and fall... */

			case 'f':
				/* insert filename containing content */
				snprintf(bp, buflen, "'%s'", file);
				/*
				** since we've quoted the file argument,
				** set things up to look past it, to avoid
				** problems with the quoting logic below.
				** (I know, I should figure out what's
				** broken with the quoting logic, but..)
				*/
				len = strlen(bp);
				buflen -= len;
				bp += len;
				pp = bp;
				break;

			case 'l':
				/*
				** display listing prior to displaying content
				*/
				xlist = !nolist;
				break;

			case 's':
				/* insert subtype of content */
				strncpy(bp, ci->ci_subtype, buflen);
				break;

			case '%':
				/* insert character % */
				goto raw;

			default:
				*bp++ = *--cp;
				*bp = '\0';
				buflen--;
				continue;
			}
			len = strlen(bp);
			bp += len;
			buflen -= len;

			/* Did we actually insert something? */
			if (bp != pp) {
				/*
				** Insert single quote if not inside quotes
				** already
				*/
				if (!quoted && buflen) {
					len = strlen(pp);
					memmove(pp + 1, pp, len);
					*pp++ = '\'';
					buflen--;
					bp++;
				}
				/* Escape existing quotes */
				while ((pp = strchr(pp, '\'')) && buflen > 3) {
					len = strlen(pp++);
					memmove(pp + 3, pp, len);
					*pp++ = '\\';
					*pp++ = '\'';
					*pp++ = '\'';
					buflen -= 3;
					bp += 3;
				}
				/*
				** If pp is still set, that means we ran
				** out of space.
				*/
				if (pp)
					buflen = 0;
				if (!quoted && buflen) {
					*bp++ = '\'';
					*bp = '\0';
					buflen--;
				}
			}
		} else {
raw:
			*bp++ = *cp;
			*bp = '\0';
			buflen--;

			if (*cp == '\'')
				quoted = !quoted;
		}
	}

got_command:
	return show_content_aux2(ct, alternate, cracked, buffer,
			fd, xlist, xstdin);
}


/*
** Routine to actually display the content
*/
static int
show_content_aux2(CT ct, int alternate, char *cracked,
	char *buffer, int fd, int xlist, int xstdin)
{
	pid_t child_id;

	if (debugsw || cracked) {
		fflush(stdout);

		fprintf(stderr, "%s msg %s", cracked ? "storing" : "show",
				 ct->c_file);
		if (ct->c_partno)
			fprintf(stderr, " part %s", ct->c_partno);
		if (cracked)
			fprintf(stderr, " using command (cd %s; %s)\n",
					cracked, buffer);
		else
			fprintf(stderr, " using command %s\n", buffer);
	}

	if (xlist) {
		if (ct->c_type == CT_MULTIPART)
			list_content(ct, -1, 0, 0);
		else
			list_switch(ct, -1, 0, 0);
	}

	fflush(stdout);

	switch (child_id = fork()) {
	case NOTOK:
		advise("fork", "unable to");
		(*ct->c_ceclosefnx) (ct);
		return NOTOK;

	case OK:
		if (cracked)
			chdir(cracked);
		if (!xstdin)
			dup2(fd, 0);
		close(fd);
		execlp("/bin/sh", "/bin/sh", "-c", buffer, NULL);
		fprintf(stderr, "unable to exec ");
		perror("/bin/sh");
		_exit(EX_OSERR);
		/* NOTREACHED */

	default:
		pidcheck(pidXwait(child_id, NULL));

		if (fd != NOTOK)
			(*ct->c_ceclosefnx) (ct);
		return (alternate ? DONE : OK);
	}
}


/*
** show content of type "text"
*/

static int
show_text(CT ct, int alternate)
{
	char *cp, buffer[BUFSIZ];
	CI ci = &ct->c_ctinfo;

	/* Check for mhshow-show-type/subtype */
	snprintf(buffer, sizeof(buffer), "mhshow-show-%s/%s",
			ci->ci_type, ci->ci_subtype);
	if ((cp = context_find(buffer)) && *cp != '\0')
		return show_content_aux(ct, alternate, cp, NULL);

	/* Check for mhshow-show-type */
	snprintf(buffer, sizeof(buffer), "mhshow-show-%s", ci->ci_type);
	if ((cp = context_find(buffer)) && *cp != '\0')
		return show_content_aux(ct, alternate, cp, NULL);

	/*
	** Use default method if content is text/plain, or if
	** if it is not a text part of a multipart/alternative
	*/
	if (!alternate || ct->c_subtype == TEXT_PLAIN) {
		if (ct->c_charset && !is_native_charset(ct->c_charset)) {
			snprintf(buffer, sizeof(buffer), "%%liconv -f '%s'",
					ct->c_charset);
		} else {
			snprintf(buffer, sizeof(buffer), "%%lcat");
		}
		ct->c_showproc = mh_xstrdup(buffer);
		return show_content_aux(ct, alternate, ct->c_showproc, NULL);
	}

	return NOTOK;
}


/*
** show message body of type "multipart"
*/

static int
show_multi(CT ct, int alternate)
{
	char *cp, buffer[BUFSIZ];
	CI ci = &ct->c_ctinfo;

	/* Check for mhshow-show-type/subtype */
	snprintf(buffer, sizeof(buffer), "mhshow-show-%s/%s",
			ci->ci_type, ci->ci_subtype);
	if ((cp = context_find(buffer)) && *cp != '\0')
		return show_multi_aux(ct, alternate, cp);

	/* Check for mhshow-show-type */
	snprintf(buffer, sizeof(buffer), "mhshow-show-%s", ci->ci_type);
	if ((cp = context_find(buffer)) && *cp != '\0')
		return show_multi_aux(ct, alternate, cp);

	if ((cp = ct->c_showproc))
		return show_multi_aux(ct, alternate, cp);

	/*
	** Use default method to display this multipart content
	** if it is not a (nested) part of a multipart/alternative,
	** or if it is one of the known subtypes of multipart.
	*/
	if (!alternate || ct->c_subtype != MULTI_UNKNOWN)
		return show_multi_internal(ct, alternate);

	return NOTOK;
}


/*
** show message body of subtypes of multipart that
** we understand directly (mixed, alternate, etc...)
*/

static int
show_multi_internal(CT ct, int alternate)
{
	int alternating, nowalternate, result;
	struct multipart *m = (struct multipart *) ct->c_ctparams;
	struct part *part;
	CT p;

	alternating = 0;
	nowalternate = alternate;

	if (ct->c_subtype == MULTI_ALTERNATE) {
		nowalternate = 1;
		alternating  = 1;
	}

	/*
	** Other possible multipart types are:
	** - multipart/parallel
	** - multipart/mixed
	** - multipart/digest
	** - unknown subtypes of multipart (treat as mixed per rfc2046)
	*/

	/*
	** alternate   -> we are a part inside an multipart/alternative
	** alternating -> we are a multipart/alternative
	*/

	result = alternate ? NOTOK : OK;

	for (part = m->mp_parts; part; part = part->mp_next) {
		p = part->mp_part;

		if (part_ok(p, 1) && type_ok(p, 1)) {
			int inneresult;

			inneresult = show_switch(p, nowalternate);
			switch (inneresult) {
			case NOTOK:
				if (alternate && !alternating) {
					result = NOTOK;
					goto out;
				}
				continue;

			case OK:
			case DONE:
				if (alternating) {
					result = DONE;
					break;
				}
				if (alternate) {
					alternate = nowalternate = 0;
					if (result == NOTOK)
						result = inneresult;
				}
				continue;
			}
			break;
		}
	}

	if (alternating && !part) {
		if (!alternate)
			content_error(NULL, ct, "don't know how to display any of the contents");
		result = NOTOK;
		goto out;
	}
out:
	return result;
}


/*
** Parse display string for multipart content
** and use external program to display it.
*/

static int
show_multi_aux(CT ct, int alternate, char *cp)
{
	int len, buflen, quoted;
	int xlist;
	char *bp, *pp, *file, buffer[BUFSIZ];
	struct multipart *m = (struct multipart *) ct->c_ctparams;
	struct part *part;
	CI ci = &ct->c_ctinfo;
	CT p;

	for (part = m->mp_parts; part; part = part->mp_next) {
		p = part->mp_part;

		if (!p->c_ceopenfnx) {
			if (!alternate)
				content_error(NULL, p, "don't know how to decode content");
			return NOTOK;
		}

		if (p->c_storage == NULL) {
			file = NULL;
			if ((*p->c_ceopenfnx) (p, &file) == NOTOK)
				return NOTOK;

			/* I'm not sure if this is necessary? */
			p->c_storage = mh_xstrdup(file);

			if (p->c_showproc && strcmp(p->c_showproc, "true")==0)
				return (alternate ? DONE : OK);
			(*p->c_ceclosefnx) (p);
		}
	}

	xlist = 0;

	/* get buffer ready to go */
	bp = buffer;
	buflen = sizeof(buffer) - 1;
	bp[0] = bp[buflen] = '\0';
	quoted = 0;

	/* Now parse display string */
	for ( ; *cp && buflen > 0; cp++) {
		if (*cp == '%') {
			pp = bp;
			switch (*++cp) {
			case 'a':
				/* insert parameters from Content-Type field */
				{
				char **ap, **ep;
				char *s = "";

				for (ap = ci->ci_attrs, ep = ci->ci_values;
						*ap; ap++, ep++) {
					snprintf(bp, buflen, "%s%s=\"%s\"",
							s, *ap, *ep);
					len = strlen(bp);
					bp += len;
					buflen -= len;
					s = " ";
				}
				}
				break;

			case 'c':
				/* insert charset */
				strncpy(bp, ct->c_charset ? ct->c_charset :
						"US-ASCII", buflen);
				break;

			case 'd':
				/* insert content description */
				if (ct->c_descr) {
					char *s;

					s = trimcpy(ct->c_descr);
					strncpy(bp, s, buflen);
					mh_free0(&s);
				}
				break;

			case 'F':
			case 'f':
				/* insert filename(s) containing content */
			{
				char *s = "";

				for (part = m->mp_parts; part;
						part = part->mp_next) {
					p = part->mp_part;

					snprintf(bp, buflen, "%s'%s'",
							s, p->c_storage);
					len = strlen(bp);
					bp += len;
					buflen -= len;
					s = " ";
				}
				/*
				** set our starting pointer back to bp,
				** to avoid requoting the filenames we
				** just added
				*/
				pp = bp;
			}
			break;

			case 'l':
				/*
				** display listing prior to displaying content
				*/
				xlist = !nolist;
				break;

			case 's':
				/* insert subtype of content */
				strncpy(bp, ci->ci_subtype, buflen);
				break;

			case '%':
				/* insert character % */
				goto raw;

			default:
				*bp++ = *--cp;
				*bp = '\0';
				buflen--;
				continue;
			}
			len = strlen(bp);
			bp += len;
			buflen -= len;

			/* Did we actually insert something? */
			if (bp != pp) {
				/*
				** Insert single quote if not inside quotes
				** already
				*/
				if (!quoted && buflen) {
					len = strlen(pp);
					memmove(pp + 1, pp, len);
					*pp++ = '\'';
					buflen--;
					bp++;
				}
				/* Escape existing quotes */
				while ((pp = strchr(pp, '\'')) && buflen > 3) {
					len = strlen(pp++);
					memmove(pp + 3, pp, len);
					*pp++ = '\\';
					*pp++ = '\'';
					*pp++ = '\'';
					buflen -= 3;
					bp += 3;
				}
				/*
				** If pp is still set, that means we ran
				** out of space.
				*/
				if (pp)
					buflen = 0;
				if (!quoted && buflen) {
					*bp++ = '\'';
					*bp = '\0';
					buflen--;
				}
			}
		} else {
raw:
			*bp++ = *cp;
			*bp = '\0';
			buflen--;

			if (*cp == '\'')
				quoted = !quoted;
		}
	}

	return show_content_aux2(ct, alternate, NULL, buffer,
			NOTOK, xlist, 0);
}


/*
** show content of type "message/rfc822"
*/

static int
show_message_rfc822(CT ct, int alternate)
{
	char *cp, buffer[BUFSIZ];
	CI ci = &ct->c_ctinfo;

	/* Check for mhshow-show-type/subtype */
	snprintf(buffer, sizeof(buffer), "mhshow-show-%s/%s",
				ci->ci_type, ci->ci_subtype);
	if ((cp = context_find(buffer)) && *cp != '\0')
		return show_content_aux(ct, alternate, cp, NULL);

	/* Check for mhshow-show-type */
	snprintf(buffer, sizeof(buffer), "mhshow-show-%s", ci->ci_type);
	if ((cp = context_find(buffer)) && *cp != '\0')
		return show_content_aux(ct, alternate, cp, NULL);

	if ((cp = ct->c_showproc))
		return show_content_aux(ct, alternate, cp, NULL);

	/* default method for message/rfc822 */
	if (ct->c_subtype == MESSAGE_RFC822) {
		cp = (ct->c_showproc = mh_xstrdup("%lshow -file %F"));
		return show_content_aux(ct, alternate, cp, NULL);
	}

	/* complain if we are not a part of a multipart/alternative */
	if (!alternate)
		return show_content_aux(ct, alternate, "%l", NULL);

	return NOTOK;
}


/*
** Show content of type "message/partial".
*/

static int
show_partial(CT ct, int alternate)
{
	show_content_aux(ct, alternate, "%l", NULL);
	puts("in order to display this message, you must reassemble it");
	return NOTOK;
}


/*
** Show how to retrieve content of type "message/external".
*/
static int
show_external(CT ct, int alternate)
{
	char **ap, **ep;
	char *msg;
	FILE *fp;
	char buf[BUFSIZ];

	msg = add("You need to fetch the contents yourself:\n", NULL);
	ap = ct->c_ctinfo.ci_attrs;
	ep = ct->c_ctinfo.ci_values;
	for (; *ap; ap++, ep++) {
		msg = add(concat("\t", *ap, ": ", *ep, NULL), msg);
	}
	if (!(fp = fopen(ct->c_file, "r"))) {
		adios(EX_IOERR, ct->c_file, "unable to open");
	}
	fseek(fp, ct->c_begin, SEEK_SET);
	while (!feof(fp) && ftell(fp) < ct->c_end) {
		if (!fgets(buf, sizeof buf, fp)) {
			adios(EX_IOERR, ct->c_file, "unable to read");
		}
		*strchr(buf, '\n') = '\0';
		if (!*buf) {
			continue;  /* skip empty lines */
		}
		msg = add(concat("\t", buf, "\n", NULL), msg);
	}
	fclose(fp);
	show_content_aux(ct, alternate, "%l", NULL);
	fputs(msg, stdout);
	return OK;
}
