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

/*-
 * Copyright (c) 1998-2000 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 <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>

#include "cue.h"

static char *mpart_newsep(struct filedb *fdb);

static struct mimetype {
	char *ext;
	char *type;
} mimetype[] = {
	{ "txt",	"text/plain" },
	{ "html",	"text/html" },
	{ "htm",	"text/html" },
	{ "hqx",	"application/mac-binhex40" },
	{ "zip",	"application/x-zip-compressed" },
	{ "zip",	"application/zip" },
	{ "pdf",	"application/pdf" },
	{ "eps",	"application/postscript" },
	{ "ps",		"application/postscript" },
	{ "ppt",	"application/vnd.ms-powerpoint" },
	{ "xls",	"application/vnd.ms-excel" },
	{ "doc",	"application/msword" },
	{ "au",		"audio/basic" },
	{ "snd",	"audio/basic" },
	{ "gif",	"image/gif" },
	{ "jpg",	"image/jpeg" },
	{ "jpeg",	"image/jpeg" },
	{ "png",	"image/png" },
	{ "tif",	"image/tiff" },
	{ "tiff",	"image/tiff" },
	{ "mpeg",	"video/mpeg" },
	{ "mpg",	"video/mpeg" },
	{ "pptx",	"application/vnd.openxmlformats-officedocument.presentationml.presentation" },
	{ "xlsx",	"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" },
	{ "docx",	"application/vnd.openxmlformats-officedocument.wordprocessingml.document" },
	{ NULL,		"text/plain" },
	{ NULL,		"application/octet-stream" },
	{ NULL,		"multipart/mixed" },
	{ NULL,		"message/rfc822" },
	{ NULL,		NULL },
};

static int
medit_ctype_completion(char *candidate, void (*callback)(void *arg, const char *entry), void *arg)
{
	struct mimetype *mt;
	cbuf_t mname;
	int nmatch = 0;
	int matchlen, len;
	int i;
	int l;

	len = strlen(candidate);
	matchlen = 0;
	mname.ptr = NULL;
	mname.len = 0;
	for (mt = mimetype; mt->type != NULL; mt++) {
		if (mt != mimetype && strcasecmp(mt[-1].type, mt->type) == 0)
			continue;
		l = strlen(mt->type);
		if (l >= len
		&&  strncasecmp(mt->type, candidate, len) == 0) {
			if (nmatch && mname.ptr != NULL) {
				for (i = len; i < matchlen; i++) {
					if (i >= l
					||  mt->type[i] != mname.ptr[i]) {
						matchlen = i;
						break;
					}
				}
			} else {
				matchlen = l;
			}
			nmatch++;
			if (callback)
				callback(arg, mt->type);
			mname.ptr = mt->type;
			mname.len = l;
		}
	}
	if (nmatch && matchlen > len) {
		memcpy(candidate + len, mname.ptr + len, matchlen - len);
		candidate[matchlen] = '\0';
	}
	return nmatch;
}

static char *
medit_ctype(struct state *state, struct filedb *fdb)
{
	char *p, *ep;
	u_char c;
	struct mimetype *mt;
	static char ctype[CHARBLOCK];

	if ((p = strrchr(fdb->name, '.')) != NULL)
		p++;
	else
		p = "";
	for (mt = mimetype; mt->ext != NULL; mt++) {
		if (strcasecmp(mt->ext, p) == 0) {
			strlcpy(ctype, mt->type, sizeof(ctype));
			goto found;
		}
	}
	if (strstr(fdb->name, "/Mail/") != NULL) {
		strlcpy(ctype, "message/rfc822", sizeof(ctype));
		goto found;
	}
	if (CSUBMATCH("-----BEGIN PGP PUBLIC KEY BLOCK-----", &fdb->mmap)) {
		strlcpy(ctype, "application/pgp-keys", sizeof(ctype));
		goto found;
	}
	strlcpy(ctype, "Text/Plain", sizeof(ctype));
	for (p = CP(&fdb->mmap), ep = CE(&fdb->mmap); p < ep; p++) {
		c = *(u_char *)p;
		if (c < 9 || c == 11 || (c>15 && c<27) || (c>27 && c<' ')
		||  (c>=0x7f && c<=0xa0) || c==0xff) {
			strlcpy(ctype, "Application/Octet-stream",
			    sizeof(ctype));
			break;
		}
	}
  found:
	snprintf(state->status, sizeof(state->status),
	    "%s: Content-Type: ", fdb->name);
	if (edit_stline(state, ctype, sizeof(ctype),
			medit_ctype_completion) == NULL) {
		strlcpy(state->status, "Quit", sizeof(state->status));
		return NULL;
	}
	state->status[0] = '\0';
	return ctype;
}

static const char *
medit_charset(struct filedb *edb)
{
	int i;
	ccbuf_t *cb;
	int id, guess;

	guess = FDB_CS_ASCII;
	for (i = edb->hdr_lines; (cb = fdb_read(edb, i)) != NULL; i++) {
		id = charset_guess(CP(cb), CE(cb));
		if (id != FDB_CS_ASCII && id != guess) {
			if (guess != FDB_CS_ASCII) {
#if 0
				guess = FDB_CS_UTF8;
#else
				guess = FDB_CS_JP2;
#endif
				break;
			}
			guess = id;
		}
	}
	return charset_name(guess);
}

void
medit_parse(struct filedb *edb)
{

	edb->flags |= FDB_DRAFT | FDB_NOCONV;
	if (edb->flags & FDB_MAIL) {
		CSETP(&edb->buf_mhdr, edb->ptr);
	} else {
		message_header(edb);
		if (CMATCH("Message/Rfc822", &edb->type))
			edb->flags |= FDB_MAIL;
	}
	if (edb->flags & FDB_MAIL)
		message_header(edb);
	message_parseall(edb);
}

static char *
mpart_newsep(struct filedb *fdb)
{
	time_t now;
	struct tm *tm;
	static pid_t pid;
	static int seq;
	static char sepbuf[LINE_WIDTH];

	if (!pid)
		pid = getpid();

	time(&now);
	tm = localtime(&now);
	snprintf(sepbuf, sizeof(sepbuf),
	    "Boundary-%04d%02d%02d%02d%02d%02d-%05d%02d",
	    tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday,
	    tm->tm_hour, tm->tm_min, tm->tm_sec, pid, seq++ % 10);
	return sepbuf;
}

static void
medit_prepare(FILE *fp, struct filedb *fdb, int subflag, ccbuf_t *ebuf)
{
	int i;
	ccbuf_t *cb;
	struct filedb *edb, *ndb;

	CSETP(ebuf, CP(&fdb->buf_mhdr));
	if (subflag) {
		if (fdb->uppart == NULL)
			CSETE(ebuf, CE(&fdb->mmap));
		else
			CSETE(ebuf, CE(&fdb->buf_body));
	} else {
		if (fdb->flags & FDB_MULTIPART) {
			/* include next separator */
			ndb = fdb->nextpart;
			if (fdb->flags & FDB_INLINE)
				ndb = ndb->nextpart;
			if (ndb != NULL && ndb->partdepth > fdb->partdepth) {
				CSETE(ebuf, CP(&ndb->buf_mhdr));
			} else {
				CSETE(ebuf, CE(&fdb->buf_body));
			}
		} else
			CSETE(ebuf, CE(&fdb->buf_body));
	}

	if (subflag)
		fwrite(CP(ebuf), CL(ebuf), 1, fp);
	else {
		edb = fdb_mkpart(ebuf);
		edb->flags |= FDB_DRAFT|FDB_NOCONV;
		if (fdb->uppart == NULL)
			edb->flags |= FDB_MAIL;
		else
			message_header(edb);
		if (edb->flags & FDB_MAIL)
			message_header(edb);
		if (edb->nextpart)
			edb->nextpart->flags |= FDB_DRAFT|FDB_NOCONV;
		for (i = 0; (cb = fdb_read(edb, i)) != NULL; i++) {
			fwrite(CP(cb), CL(cb), 1, fp);
			putc('\n', fp);
		}
		fdb_clear(edb);
	}
}

/*
	for mpart_edit
		guess CT/charset
		suprress null body
		suprress null body then skip the first (inlined) part
		suprress null body then inline to single part
		assume inline and restore multipart state
	for mpart_single
*/
static void
medit_fix(FILE *fp, struct filedb *fdb, struct filedb *edb)
{
	struct filedb *ndb;
	int i;
	int hasbody, eop;
	const char *charset;
	ccbuf_t *sep, *cb, oldcs;
	struct header *hdr;

	if (!(edb->flags & FDB_PARSED)) {
		if (fdb->uppart == NULL)
			edb->flags |= FDB_MAIL;
		medit_parse(edb);
	}
	hasbody = (fdb_read(edb, edb->hdr_lines) != NULL);
	eop = 1;
	sep = NULL;
	if (fdb->flags & FDB_MULTIPART) {
		ndb = fdb->nextpart;
		if (fdb->flags & FDB_INLINE)
			ndb = ndb->nextpart;
		if (ndb != NULL) {
			if (ndb->partdepth > fdb->partdepth)
				eop = 0;
			if (ndb->partdepth >= fdb->partdepth)
				sep = &fdb->boundary;
		}
	}
	if (edb->flags & FDB_INLINE)
		ndb = edb->nextpart;
	else
		ndb = edb;

	charset = NULL;
	if (hasbody && CSUBMATCH("Text/", &ndb->type)) {
		/* adjust charset */
		CSETP(&oldcs, NULL);
		cb = &ndb->hdr_val[HT_CTYPE];
		if (CP(cb) != NULL
		&&  (cb = message_ctype_param(edb, "charset", CP(cb), CE(cb))) != NULL) {
			oldcs = *cb;
		}
		charset = medit_charset(ndb);
		if (CP(&oldcs)
		&&  CL(&oldcs) == strlen(charset)
		&&  memcmp(CP(&oldcs), charset, CL(&oldcs)) == 0)
			charset = NULL;	/* no need to change */
	}

	if (edb->flags & FDB_MAIL)
		fwrite(CP(&edb->buf_mhdr), CL(&edb->buf_mhdr), 1, fp);

	for (i = 0, hdr = edb->hdr; i < edb->hdrs; i++, hdr++) {
		if ((sep || (edb->flags & FDB_MULTIPART))
		&&  (hdr->type->flags & HTF_OVERWRITE))
			continue;
		if (charset && hdr->type->type == HT_CTYPE)
			continue;
		print_hdr(fp, "%.*s\n", CL(&hdr->dbuf), CP(&hdr->dbuf));
	}
	if (edb->flags & FDB_MAIL) {
		if (CP(&edb->hdr_val[HT_MIME]) == NULL)
			fprintf(fp, "Mime-Version: 1.0\n");
	}

	if (!hasbody && eop)
		sep = NULL;
	if (sep) {
		print_hdr(fp, "Content-Type: Multipart/Mixed; boundary=\"%.*s\"\n", CL(sep), CP(sep));
		putc('\n', fp);

		if (hasbody) {
			fprintf(fp, "--%.*s\n", CL(sep), CP(sep));
			for (i = 0, hdr = edb->hdr; i < edb->hdrs; i++, hdr++) {
				if (!(hdr->type->flags & HTF_OVERWRITE))
					continue;
				if (charset && hdr->type->type == HT_CTYPE)
					continue;
				print_hdr(fp, "%.*s\n", CL(&hdr->dbuf), CP(&hdr->dbuf));
			}
		}
	}
	if (hasbody) {
		if (charset)
			print_hdr(fp, "Content-Type: %.*s; charset=%s\n",
				CL(&ndb->type), CP(&ndb->type), charset);
		putc('\n', fp);
		for (i = edb->hdr_lines; (cb = fdb_read(edb, i)) != NULL; i++) {
			print_jis(fp, "%.*s%s", CL(cb), CP(cb),
				  (fdb_ismore(edb, i) ? "" : "\n"));
		}
	}
	if (sep)
		fprintf(fp, "\n--%.*s%s\n", CL(sep), CP(sep), (eop ? "--" : ""));
}

static int
medit_final(struct state *state)
{
	struct filedb *fdb;
	char tmp[MAXPATH], bak[MAXPATH];

	fdb = state->message;
	snprintf(tmp, sizeof(tmp), "%s.tmp", fdb->name);
	snprintf(bak, sizeof(bak), "%s%s", fdb->name, SUF_BAK);
	(void)unlink(bak);
	if (link(fdb->name, bak) < 0 || rename(tmp, fdb->name) < 0) {
		strlcpy(state->status, "Edit failed", sizeof(state->status));
		return 0;
	}
	folder_purge(state, fdb->msgnum);
	fdb_purge(fdb->name);
	state->message = NULL;
	message_open(state, 1);	/* reopen */
	return 1;
}

int
mpart_issep(const char *p, const char *ep, ccbuf_t *sep)
{
	if (sep->ptr == NULL || sep->len == 0)
		return MPART_NONE;
	if (p + 2 + sep->len + 1 > ep)
		return MPART_END;
	if (p[0] != '-' || p[1] != '-')
		return MPART_NONE;
	p += 2;
	if (memcmp(p, sep->ptr, sep->len) != 0)
		return MPART_NONE;
	p += sep->len;
	if (*p == '\n')
		return MPART_NEXT;
	if (p + 2 > ep)
		return MPART_END;
	if (p[0] != '-' || p[1] != '-' || p[2] != '\n')
		return MPART_NONE;
	return MPART_END;
}

int
mpart_edit(struct state *state, int subflag)
{
	struct filedb *fdb, *edb;
	ccbuf_t ebuf;
	FILE *fp;
	char tmp[MAXPATH], edit[MAXPATH];

	fdb = state->message;
	snprintf(tmp, sizeof(tmp), "%s.tmp", fdb->name);
	snprintf(edit, sizeof(edit), "%s.edit", fdb->name);

	if ((fp = fopen(edit, "w")) == NULL) {
		strlcpy(state->status, "Edit prepare failed", sizeof(state->status));
		return 0;
	}
	medit_prepare(fp, fdb, subflag, &ebuf);
	fclose(fp);

	if (exec_editor(state, state->folder->name.ptr + 1, fdb->msgnum, ".edit") < 0) {
		strlcpy(state->status, "Not edited", sizeof(state->status));
		(void)unlink(edit);
		return 0;
	}
	fdb_purge(edit);	/* XXX: just in case */
	if ((edb = fdb_open(edit)) == NULL) {
		strlcpy(state->status, "Edit reopen failed", sizeof(state->status));
		(void)unlink(edit);
		return 0;
	}

	if ((fp = fopen(tmp, "w")) == NULL) {
		strlcpy(state->status, "Failed", sizeof(state->status));
		(void)unlink(edit);
		fdb_clear(edb);
		return 0;
	}
	fwrite(CP(&fdb->mmap), CP(&ebuf) - CP(&fdb->mmap), 1, fp);
	if (subflag) {
		fwrite(CP(&edb->mmap), CL(&edb->mmap), 1, fp);
	} else {
		medit_fix(fp, fdb, edb);
	}
	fwrite(CE(&ebuf), CE(&fdb->mmap) - CE(&ebuf), 1, fp);

	fdb_clear(edb);
	fclose(fp);
	(void)unlink(edit);
	return medit_final(state);
}

int
mpart_single(struct state *state)
{
	int i;
	struct filedb *fdb, *edb, *ndb;
	struct header *hdr;
	FILE *fp;
	ccbuf_t ebuf;
	char tmp[MAXPATH];

	fdb = state->message;
	if (!(fdb->flags & FDB_MULTIPART)) {
		strlcpy(state->status, "Not multipart", sizeof(state->status));
		return 0;
	}
	for (edb = fdb->nextpart->nextpart; edb; edb = edb->nextpart) {
		if (edb->uppart == fdb) {
			strlcpy(state->status, "Not single part", sizeof(state->status));
			return 0;
		}
	}

	CSETP(&ebuf, CP(&fdb->buf_mhdr));
	CSETE(&ebuf, CE(&fdb->buf_body));
	edb = fdb_mkpart(&ebuf);
	if (fdb->uppart == NULL)
		edb->flags |= FDB_MAIL;
	edb->separator = fdb->boundary;
	medit_parse(edb);

	snprintf(tmp, sizeof(tmp), "%s.tmp", fdb->name);
	if ((fp = fopen(tmp, "w")) == NULL) {
		fdb_clear(edb);
		strlcpy(state->status, "Failed", sizeof(state->status));
		return 0;
	}
	fwrite(CP(&fdb->mmap), CP(&ebuf) - CP(&fdb->mmap), 1, fp);

	if ((edb->flags & FDB_MAIL) && fdb->uppart) {
		fwrite(CP(&edb->buf_mhdr), CL(&edb->buf_mhdr), 1, fp);
		putc('\n', fp);
	}
	for (i = 0, hdr = edb->hdr; i < edb->hdrs; i++, hdr++) {
		if (hdr->type->flags & HTF_OVERWRITE)
			continue;
		fwrite(CP(&hdr->buf), CL(&hdr->buf), 1, fp);
		putc('\n', fp);
	}
	ndb = edb->nextpart;
	fwrite(CP(&ndb->buf_mhdr), CL(&ndb->buf_mhdr), 1, fp);
	if (CL(&ndb->buf_hdr))
		fwrite(CP(&ndb->buf_hdr), CL(&ndb->buf_hdr), 1, fp);
	fwrite(CP(&ndb->buf_body), CL(&ndb->buf_body), 1, fp);

	fwrite(CE(&ebuf), CE(&fdb->mmap) - CE(&ebuf), 1, fp);

	fdb_clear(edb);
	fclose(fp);
	return medit_final(state);
}

int
mpart_multi(struct state *state)
{
	struct filedb *fdb, *edb;
	int i;
	struct header *hdr;
	ccbuf_t *cb, sep, ebuf;
	FILE *fp;
	char tmp[MAXPATH];

	fdb = state->message;
	if (state->nomime) {
		strlcpy(state->status, "Skip MIME Analysis mode", sizeof(state->status));
		return 0;
	}
	if (fdb->flags & FDB_INLINE)
		fdb = fdb->nextpart;
	CSETP(&ebuf, CP(&fdb->buf_mhdr));
	CSETE(&ebuf, CE(&fdb->buf_body));
	edb = fdb_mkpart(&ebuf);
	if (fdb->uppart == NULL)
		edb->flags |= FDB_MAIL;
	medit_parse(edb);
	sep.ptr = mpart_newsep(fdb);
	sep.len = strlen(sep.ptr);

	snprintf(tmp, sizeof(tmp), "%s.tmp", fdb->name);
	if ((fp = fopen(tmp, "w")) == NULL) {
		strlcpy(state->status, "Edit failed", sizeof(state->status));
		fdb_clear(edb);
		return 0;
	}
	fwrite(CP(&fdb->mmap), CP(&ebuf) - CP(&fdb->mmap), 1, fp);

	if ((edb->flags & FDB_MAIL) && fdb->uppart) {
		fwrite(CP(&edb->buf_mhdr), CL(&edb->buf_mhdr), 1, fp);
		putc('\n', fp);
	}
	for (i = 0, hdr = edb->hdr; i < edb->hdrs; i++, hdr++) {
		if (hdr->type->flags & HTF_OVERWRITE)
			continue;
		fwrite(CP(&hdr->buf), CL(&hdr->buf), 1, fp);
		putc('\n', fp);
	}
	print_hdr(fp, "Content-Type: Multipart/Mixed; boundary=\"%.*s\"\n",
		CL(&sep), CP(&sep));
	fprintf(fp, "\n--%.*s\n", CL(&sep), CP(&sep));
	if (edb->flags & FDB_MULTIPART) {
		cb = &edb->hdr_val[HT_CTYPE];
		print_hdr(fp, "Content-Type: %.*s\n", CL(cb), CP(cb));
	}
	for (i = 0, hdr = edb->hdr; i < edb->hdrs; i++, hdr++) {
		if (!(hdr->type->flags & HTF_OVERWRITE))
			continue;
		fwrite(CP(&hdr->buf), CL(&hdr->buf), 1, fp);
		putc('\n', fp);
	}
	putc('\n', fp);
	fwrite(CP(&edb->buf_body), CL(&edb->buf_body), 1, fp);
	fprintf(fp, "\n--%.*s--\n", CL(&sep), CP(&sep));

	fwrite(CE(&ebuf), CE(&fdb->mmap) - CE(&ebuf), 1, fp);

	fdb_clear(edb);
	fclose(fp);
	return medit_final(state);
}

int
mpart_append(struct state *state, char *inserttype, char *file)
{
	struct filedb *fdb, *tdb, *edb;
	int i;
	FILE *fp;
	const char *p, *pos;
	ccbuf_t *cb;
	struct header *hdr;
	char filebuf[CHARBLOCK];
	char tmp[MAXPATH];

	fdb = state->message;
	if (!(fdb->flags & FDB_MULTIPART)) {
		strlcpy(state->status, "Not multipart.  Type 'e-m' to make multipart first", sizeof(state->status));
		return 0;
	}

	pos = NULL;	/* for lint */
	for (tdb = fdb->nextpart; tdb; tdb = tdb->nextpart) {
		if (tdb->uppart == fdb)
			pos = CE(&tdb->buf_body);
	}

	edb = NULL;
	if (inserttype) {
		if (file == NULL) {
			snprintf(state->status, sizeof(state->status),
			    "Copy file: ");
			strlcpy(filebuf, "~/", sizeof(filebuf));
			if (edit_stline(state, filebuf, sizeof(filebuf),
					fname_completion) == NULL) {
				strlcpy(state->status, "Quit", sizeof(state->status));
				return 0;
			}
			if (filebuf[0] == '\0') {
				state->status[0] = '\0';
				return 0;
			}
			fname_expand(filebuf, filebuf);
			file = filebuf;
		}
		fdb_purge(file);
		if ((edb = fdb_open(file)) == NULL) {
			snprintf(state->status, sizeof(state->status),
			    "%s: Open failed", file);
			return 0;
		}
		edb->flags |= FDB_DRAFT;
		if (*inserttype == '*') {
			if ((inserttype = medit_ctype(state, edb)) == NULL) {
				fdb_clear(edb);
				return 0;
			}
		}
	}

	snprintf(tmp, sizeof(tmp), "%s.tmp", fdb->name);
	if ((fp = fopen(tmp, "w")) == NULL) {
		strlcpy(state->status, "Edit failed", sizeof(state->status));
		if (inserttype)
			fdb_clear(edb);
		return 0;
	}

	fwrite(CP(&fdb->mmap), pos - CP(&fdb->mmap), 1, fp);
	fprintf(fp, "\n--%.*s\n", CL(&fdb->boundary), CP(&fdb->boundary));
	if (inserttype) {
		if (strncasecmp("Text/", inserttype, 5) != 0) {
			edb->flags |= FDB_NOTEXT | FDB_NOCONV;
			if (strncasecmp("Message/", inserttype, 8) == 0)
				edb->flags |= FDB_MAIL;
			else if (strncasecmp("application/pgp-keys", inserttype, 20) != 0)
				edb->flags |= FDB_ENC_B64;
		}

		if (edb->flags & FDB_NOTEXT) {
			print_hdr(fp, "Content-Type: %s\n", inserttype);
		} else {
			print_hdr(fp, "Content-Type: %s; charset=%s\n",
				inserttype, medit_charset(edb));
		}
		if ((edb->flags & FDB_ENCODE) == FDB_ENC_B64)
			fprintf(fp, "Content-Transfer-Encoding: base64\n");
		if (!(edb->flags & FDB_MAIL)) {
			if ((p = strrchr(file, '/')) != NULL)
				p++;
			else
				p = file;
			print_hdr(fp, "Content-Disposition: attachment; filename=\"%s\"\n", p);
		}
		putc('\n', fp);
		if ((edb->flags & FDB_ENCODE) == FDB_ENC_B64)
			save_base64_encode(fp, &edb->mmap);
		else if (edb->flags & FDB_MAIL) {
			edb->flags |= FDB_NOMIME | FDB_NOCONV;
			CSETP(&edb->buf_mhdr, edb->ptr);
			message_header(edb);
			for (i = 0, hdr = edb->hdr; i < edb->hdrs; i++, hdr++) {
				if (hdr->type->flags & HTF_NOSEND)
					continue;
				fwrite(CP(&hdr->buf), CL(&hdr->buf), 1, fp);
				putc('\n', fp);
			}
			putc('\n', fp);
			fwrite(CP(&edb->buf_body), CL(&edb->buf_body), 1, fp);
		} else if (edb->flags & FDB_NOTEXT) {
			fwrite(CP(&edb->mmap), CL(&edb->mmap), 1, fp);
		} else {
			for (i = 0; (cb = fdb_read(edb, i)) != NULL; i++) {
				print_jis(fp, "%.*s%s", CL(cb), CP(cb),
					  (fdb_ismore(edb, i) ? "" : "\n"));
			}
		}
		fdb_clear(edb);
	}
	fwrite(pos, CE(&fdb->mmap) - pos, 1, fp);

	fclose(fp);
	return medit_final(state);
}

int
mpart_delete(struct state *state)
{
	struct filedb *fdb;
	const char *es, *ee;
	FILE *fp;
	char tmp[MAXPATH];

	fdb = state->message;
	if (fdb->flags & FDB_INLINE)
		fdb = fdb->nextpart;
	if (!(fdb->flags & FDB_SUBPART)) {
		strlcpy(state->status, "Cannot delete main part: use 'd' instead", sizeof(state->status));
		return 0;
	}
	if (fdb->prevpart == fdb->uppart
	&&  (fdb->nextpart == NULL || fdb->nextpart->uppart != fdb->uppart)) {
		strlcpy(state->status, "Cannot delete last part", sizeof(state->status));
		return 0;
	}

	if (!edit_yn(state, "Are you sure to delete %s? ",
		     ((fdb->flags & FDB_MULTIPART) ? "whole parts" : "this part")))
		return 0;

	if (fdb->prevpart == fdb->uppart)
		es = CP(&fdb->uppart->buf_body) - 1;	/* XXX: \n */
	else
		es = CE(&fdb->prevpart->buf_body);
	ee = CE(&fdb->buf_body);

	snprintf(tmp, sizeof(tmp), "%s.tmp", fdb->name);
	if ((fp = fopen(tmp, "w")) == NULL) {
		strlcpy(state->status, "Edit failed", sizeof(state->status));
		return 0;
	}
	fwrite(CP(&fdb->mmap), es - CP(&fdb->mmap), 1, fp);
	fwrite(ee, CE(&fdb->mmap) - ee, 1, fp);
	fclose(fp);
	return medit_final(state);
}

int
mpart_undo(struct state *state)
{
	struct filedb *fdb;
	char *undo;
	struct stat stbuf, bstbuf;
	int ret;
	char tmp[MAXPATH], bak[MAXPATH];

	fdb = state->message;
	snprintf(tmp, sizeof(tmp), "%s.tmp", fdb->name);
	snprintf(bak, sizeof(bak), "%s%s", fdb->name, SUF_BAK);
	if (stat(bak, &bstbuf) < 0) {
		strlcpy(state->status, "No backup file", sizeof(state->status));
		return 0;
	}
	if (stat(fdb->name, &stbuf) < 0) {
		strlcpy(state->status, "Undo failed", sizeof(state->status));
		return 0;
	}
	undo = stbuf.st_mtime >= bstbuf.st_mtime ? "Undo" : "Redo";
	(void)unlink(tmp);
	if (link(bak, tmp) < 0) {
		snprintf(state->status, sizeof(state->status),
		    "%s failed", undo);
		return 0;
	}
	ret = medit_final(state);
	snprintf(state->status, sizeof(state->status),
	    "%s %s", undo, ret ? "edit" : "failed");
	return ret;
}

int
mpart_sign(struct state *state, const char *proto, int (*smime_enter)(FILE *fp, void *arg, ccbuf_t *cb), int (*smime_sign_part)(FILE *fp, void *arg), void *arg)
{
	int i;
	ccbuf_t cret;
	char *sep;
	struct filedb *fdb, *edb;
	struct header *hdr;
	FILE *fp;
	char tmp[MAXPATH];

	fdb = state->message;
	snprintf(tmp, sizeof(tmp), "%s.tmp", fdb->name);
	if ((fp = fopen(tmp, "w")) == NULL)
		return 0;

	edb = fdb_mkpart((ccbuf_t *)&fdb->mmap);
	edb->flags |= FDB_MAIL | FDB_NOMIME;
	medit_parse(edb);

	for (i = 0, hdr = edb->hdr; i < edb->hdrs; i++, hdr++) {
		if (hdr->type->flags & HTF_OVERWRITE)
			continue;
		fwrite(CP(&hdr->buf), CL(&hdr->buf), 1, fp);
		putc('\n', fp);
	}
	if (proto) {
		sep = mpart_newsep(fdb);
		print_hdr(fp, "Content-Type: %s; boundary=\"%s\"\n", proto, sep);
		fprintf(fp, "\n--%s\n", sep);
	} else
		sep = NULL;

	cret.ptr = "\n";
	cret.len = 1;
	for (i = 0, hdr = edb->hdr; i < edb->hdrs; i++, hdr++) {
		if (!(hdr->type->flags & HTF_OVERWRITE))
			continue;
		smime_enter(fp, arg, (ccbuf_t *)&hdr->buf);
		smime_enter(fp, arg, &cret);
	}
	smime_enter(fp, arg, &cret);
	smime_enter(fp, arg, &edb->buf_body);
	if (sep)
		fprintf(fp, "\n--%s\n", sep);
	if (smime_sign_part(fp, arg) < 0) {
		fclose(fp);
		return 0;
	}
	if (sep)
		fprintf(fp, "\n--%s--\n", sep);
	fclose(fp);
	return medit_final(state);
}

int
mpart_unsign(struct state *state)
{
	struct filedb *fdb;
	const char *es, *ee;
	ccbuf_t sproto;
	FILE *fp;
	char tmp[MAXPATH];

	fdb = state->message;
	while (fdb->uppart)
		fdb = fdb->uppart;
	if (!(fdb->flags & FDB_SIGNED)) {
		strlcpy(state->status, "Not signed", sizeof(state->status));
		return 0;
	}
	sproto = fdb->sproto;
	while (fdb->nextpart)
		fdb = fdb->nextpart;
	if (fdb->uppart == NULL
	||  fdb->uppart->uppart != NULL
	||  CL(&sproto) != CL(&fdb->type)
	||  strncasecmp(CP(&sproto), CP(&fdb->type), CL(&sproto)) != 0) {
		strlcpy(state->status, "Signed part is not found", sizeof(state->status));
		return 0;
	}
	es = CP(&fdb->buf_mhdr) - (1 + 2 + CL(&fdb->separator) + 1);
	ee = CE(&fdb->buf_body);

	snprintf(tmp, sizeof(tmp), "%s.tmp", fdb->name);
	if ((fp = fopen(tmp, "w")) == NULL) {
		strlcpy(state->status, "Edit failed", sizeof(state->status));
		return 0;
	}
	fwrite(CP(&fdb->mmap), es - CP(&fdb->mmap), 1, fp);
	fwrite(ee, CE(&fdb->mmap) - ee, 1, fp);
	fclose(fp);
	if (medit_final(state) < 0)
		return 0;
	return mpart_single(state);
}
