/* $Id: helper.c,v 1.9 2015/09/29 13:17:42 onoe Exp $ */

/*-
 * Copyright (c) 2003 Atsushi Onoe
 * 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.
 * 3. The name of the author may not be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 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.
 */

#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <sys/time.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <signal.h>
#include <unistd.h>
#include <errno.h>
#include <ctype.h>
#include <fcntl.h>

#include "cue.h"

static int
helper_field(char *name, char *cmd, char **value)
{
	char *p, *q;
	int len;

	/* skip cmd */
	for (p = cmd; *p != '\0'; p++) {
		if (*p == ';') {
			p++;
			break;
		}
		if (*p == '\\') {
			if (*++p == '\0')
				break;
		}
	}
	while (*p == ' ' || *p == '\t')
		p++;
	if (*p == '\0')
		return 0;
	len = strlen(name);
	while (*p != '\0') {
		if (strncasecmp(name, p, len) == 0) {
			q = p + len;
			if (*q == '=') {
				if (value != NULL)
					*value = ++q;
				return 1;
			}
			while (*q == ' ' || *q == '\t')
				q++;
			if (*q == '\0' || *q == ';') {
				if (value != NULL)
					*value = NULL;
				return 1;
			}
		}
		/* skip current field */
		for (; *p != '\0'; p++) {
			if (*p == ';') {
				p++;
				break;
			}
			if (*p == '\\') {
				if (*++p == '\0')
					break;
			}
		}
		while (*p == ' ' || *p == '\t')
			p++;
	}
	return 0;
}

static int
helper_parse(ccbuf_t *type, char *buf, char **cmd)
{
	char *p, *q;
	int len;

	/* get typefield */
	if ((p = strchr(buf, ';')) == NULL)
		return -1;
	*p++ = '\0';

	/* check if it is ours */
	if ((q = strchr(buf, '/')) == NULL || q[1] == '*') {
		if (q == NULL)
			len = strlen(p);
		else
			len = q - buf;
		if (CL(type) < len || CP(type)[len] != '/')
			return -1;
	} else {
		len = strlen(buf);
		if (CL(type) != len)
			return -1;
	}
	if (strncasecmp(buf, CP(type), len) != 0)
		return -1;

	while (*p == ' ' || *p == '\t')
		p++;
	if (*p == '\0')
		return -1;
	*cmd = p;
	return 0;
}

static void
helper_cmdline(ccbuf_t *typeval, char *buf, int size, const char *cmd, const char *fname)
{
	char *p, *ep;
	const char *q;
	char eq;
	int len;
	int hasfname;
	ccbuf_t *cb;

	p = buf;
	ep = p + size;
	hasfname = 0;
	while (*cmd != '\0' && p + 1 < ep) {
		if (*cmd == ';')
			break;
		switch (*cmd) {
		case '\\':
			if (*++cmd == '\0')
				break;
			*p++ = *cmd++;
			break;
		case '%':
			switch (cmd[1]) {
			case '\0':
				cmd++;
				break;
			case 's':	/* filename */
			case 'u':	/* used by mosaic? */
				cmd += 2;
				strlcpy(p, fname, ep - p);
				p += strlen(p);
				hasfname++;
				break;
			case 't':	/* Content-Type */
				cmd += 2;
				for (q = CP(typeval); q < CE(typeval); q++) {
					if (*q == ';')
						break;
				}
				len = q - CP(typeval);
				if (p + len >= ep)
					break;
				memcpy(p, CP(typeval), len);
				p += len;
				break;
			case '{':	/* CT parameter */
				cmd += 2;
				eq = '}';
				for (q = cmd; *q != '\0'; q++) {
					if (*q == ' ' || *q == ';')
						break;
					if (*q == eq)
						break;
				}
				if (*q != eq)
					break;
				if (CP(typeval) == NULL) {
					cmd = q + 1;
					break;
				}
				*(char *)q = '\0';	/* XXX */
				cb = message_ctype_param(NULL, cmd, CP(typeval),
				    CE(typeval));
				*(char *)q = eq;
				cmd = q + 1;
				if (cb == NULL)
					break;
				if (p + CL(cb) >= ep)
					break;
				memcpy(p, CP(cb), CL(cb));
				p += CL(cb);
				break;
			default:
				*p++ = *cmd++;
				break;
			}
			break;
		default:
			*p++ = *cmd++;
			break;
		}
	}
	if (!hasfname && p + 3 < ep) {
		*p++ = ' ';
		strlcpy(p, fname, ep - p);
		p += strlen(p);
	}
	*p = '\0';
}

static void
helper_exec_done(void *arg, int status)
{
	(void)unlink(arg);
	free(arg);
}

void
helper_exec(struct state *state)
{
	const char *p, *ep, *tmpdir, *tmpfile;
	char *cmd, *template, *testcmd;
	cbuf_t cbuf;
	ccbuf_t *typeval;
	int fd, len, ptype, wstatus;
	FILE *fp;
	struct filedb *fdb;
	char fname[MAXPATH], tname[MAXPATH];
	char buf[BUFSIZ];
	char cmdbuf[CHARBLOCK];

	fdb = state->message;
	if (fdb == NULL) {
		strlcpy(state->status, "No current message",
		    sizeof(state->status));
		return;
	}
	message_parseall(fdb);
	while (fdb->flags & FDB_INLINE)
		fdb = fdb->nextpart;
	if ((tmpdir = getenv("TMPDIR")) == NULL)
		tmpdir = "/tmp";
	tmpfile = fname;
	snprintf(fname, sizeof(fname), "%s/cue.XXXXXX", tmpdir);
#ifdef HAVE_MKSTEMP
	fd = mkstemp(fname);
#else /* HAVE_MKSTEMP */
	fd = open(mktemp(fname), O_CREAT|O_EXCL|O_RDWR);
#endif /* HAVE_MKSTEMP */
	if (fd < 0) {
		snprintf(state->status, sizeof(state->status),
		    "mkstemp failed: %s: %s", fname, strerror(errno));
		return;
	}
	p = CP(&fdb->buf_body);
	ep = CE(&fdb->buf_body);
	if ((fdb->flags & FDB_ENCODE) != FDB_ENC_NONE) {
		while (p < ep) {
			decode_text(&p, ep, &cbuf, fdb->flags | FDB_NOCONV);
			write(fd, CP(&cbuf), CL(&cbuf));
		}
	} else
		write(fd, p, ep - p);

	typeval = &fdb->hdr_val[HT_CTYPE];
	if (CP(typeval) == NULL)
		typeval = &fdb->type;
	cmd = NULL;
	if (state->config.mailcap[0] == '/' || (p = getenv("HOME")) == NULL)
		strlcpy(tname, state->config.mailcap, sizeof(tname));
	else
		snprintf(tname, sizeof(tname), "%s/%s", p,
		    state->config.mailcap);
	if ((fp = fopen(tname, "r")) != NULL) {
		while (fgets(buf, sizeof(buf), fp)) {
			if (buf[0] == '#')
				continue;
			/* handle concatenation */
			for (;;) {
				len = strlen(buf);
				if (len > 1 && buf[len - 1] == '\n')
					buf[--len] = '\0';
				if (len == 0 || buf[len - 1] != '\\' ||
				    len + 2 > sizeof(buf))
					break;
				buf[--len] = '\0';
				if (fgets(buf + len, sizeof(buf) - len, fp) ==
				    NULL)
					break;
			}
			if (helper_parse(&fdb->type, buf, &cmd))
				continue;
			tmpfile = fname;

			/* nametemplate only support the form "%s.suffix" */
			if (helper_field("nametemplate", cmd, &template) &&
			    template != NULL &&
			    template[0] == '%' && template[1] == 's') {
				template += 2;
				if ((p = strchr(template, ';')) == NULL)
					p = template + strlen(template);
				len = strlen(fname);
				if (len + (p - template) + 1 < sizeof(tname)) {
					memcpy(tname, fname, len);
					memcpy(tname + len, template,
					    p - template);
					tname[len + (p - template)] = '\0';
					if (link(fname, tname) == 0)
						tmpfile = tname;
				}
			}

			/* check if we need to test */
			if (!helper_field("test", cmd, &testcmd) ||
			    testcmd == NULL)
				break;
			helper_cmdline(typeval, cmdbuf, sizeof(cmdbuf),
			    testcmd, tmpfile);
			lseek(fd, SEEK_SET, 0);
			if (proc_exec(PTYPE_TEST, fd, -1, NULL, NULL, "%s", cmdbuf) ||
			    proc_getstate(PTYPE_TEST, &wstatus, NULL, 0) != PTYPE_TEST ||
			    WIFSIGNALED(wstatus) || WEXITSTATUS(wstatus) != 0) {
				if (tmpfile == tname)
					unlink(tname);
				tmpfile = fname;
				cmd = NULL;
				continue;
			}
			break;
		}
		fclose(fp);
	}
	if (tmpfile == tname)
		unlink(fname);
	if (cmd == NULL) {
		strlcpy(state->status, "Execute command: ", sizeof(state->status));
		buf[0] = '\0';
		if (edit_stline(state, buf, sizeof(buf), NULL) == NULL) {
			strlcpy(state->status, "Quit", sizeof(state->status));
			goto exit;
		}
		if (buf[0] == '\0') {
			state->status[0] = '\0';
			goto exit;
		}
		cmd = buf;
	}
	if (helper_field("needsterminal", cmd, NULL))
		ptype = PTYPE_EDITOR;
	else
		ptype = PTYPE_OTHER;
	helper_cmdline(typeval, cmdbuf, sizeof(cmdbuf), cmd, tmpfile);
        snprintf(state->status, sizeof(state->status),
            "Executing %s ... ", cmdbuf);
	disp_update(state);
	lseek(fd, SEEK_SET, 0);
	if (proc_exec(ptype, fd, -1, helper_exec_done, strdup(tmpfile),
	    "%s", cmdbuf) == 0) {
		if (ptype == PTYPE_EDITOR) {
			proc_getstate(PTYPE_EDITOR, NULL, NULL, 0);
			state->status[0] = '\0';
		}
		close(fd);
		return;
	}
	snprintf(state->status, sizeof(state->status), "%s",
	    strerror(errno));
  exit:
	close(fd);
	unlink(tmpfile);
}
