/* $Id: message.c,v 1.133 2015/12/01 13:26:29 onoe Exp $ */

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

#include "cue.h"

static struct header_type header_type[] = {
	/* sort order unless HIDDEN */
	{ "Subject:",			0,			HT_SUBJECT },
	{ "From:",			HTF_STRUCT,		HT_FROM },
	{ "To:",			HTF_STRUCT,		HT_TO },
	{ "Apparently-To:",		HTF_STRUCT,		0 },
	{ "Cc:",			HTF_STRUCT,		HT_CC },
	{ "Date:",			0,			HT_DATE },
	{ "Authentication-Results", HTF_NOENCODE|HTF_HIDDEN|HTF_NOSEND,	0 },
	{ "DomainKey-Status:",      HTF_NOENCODE|HTF_HIDDEN|HTF_NOSEND,	0 },
	{ "DomainKey-Signature:",   HTF_NOENCODE|HTF_HIDDEN|HTF_NOSEND,	0 },
	{ "DKIM-Signature:",        HTF_NOENCODE|HTF_HIDDEN|HTF_NOSEND,	0 },
	{ "IIM-SIG",	   HTF_NOENCODE|HTF_HIDDEN|HTF_NOSEND,	0 },
	{ "Reply-To:",			HTF_STRUCT,		HT_REPLYTO },
	{ "Resent-From:",		HTF_STRUCT|HTF_NOSEND,	0 },
	{ "Resent-To:",			HTF_STRUCT|HTF_NOSEND,	0 },
	{ "Resent-Cc:",			HTF_STRUCT|HTF_NOSEND,	0 },
	{ "Resent-Date:",		HTF_NOSEND,		0 },
	{ "Resent-Message-Id:",		HTF_HIDDEN|HTF_NOSEND,		0 },
	{ "Resent-Sender:",		HTF_HIDDEN||HTF_NOSEND|HTF_STRUCT, 0 },
	{ "Return-Path:",		HTF_HIDDEN,		0 },
	{ "Received:",	   HTF_NOENCODE|HTF_HIDDEN|HTF_NOSEND,	0 },
	{ "Received-SPF:", HTF_NOENCODE|HTF_HIDDEN|HTF_NOSEND,	0 },
	{ "Message-Id:",		HTF_HIDDEN,		HT_MSGID },
	{ "Errors-To:",			HTF_HIDDEN|HTF_STRUCT,	0 },
	{ "Sender:",			HTF_HIDDEN|HTF_STRUCT,	0 },
	{ "Precedence:",		HTF_HIDDEN,		0 },
	{ "Mime-Version:",		HTF_HIDDEN,		HT_MIME },
	{ "Content-Type:", HTF_NOENCODE|HTF_HIDDEN|HTF_OVERWRITE, HT_CTYPE },
	{ "Content-Transfer-Encoding:", HTF_HIDDEN|HTF_OVERWRITE, HT_CTE },
	{ "Content-Description:",	HTF_HIDDEN|HTF_OVERWRITE, HT_CDESC },
	{ "Content-Disposition:",	HTF_HIDDEN|HTF_OVERWRITE, HT_CDISP },
	{ "Content-ID:",		HTF_HIDDEN|HTF_OVERWRITE, 0 },
	{ "In-Reply-To:",		HTF_HIDDEN,		0 },
	{ "References:",		HTF_HIDDEN,		0 },
	{ "Lines:",			HTF_HIDDEN,		0 },
	{ "Posted:",			HTF_HIDDEN,		0 },
	{ "Status:",			HTF_HIDDEN|HTF_NOSEND,	0 },
	{ "Mailing-List:",		HTF_HIDDEN|HTF_NOSEND,	0 },
	{ "Delivered-To:",		HTF_HIDDEN|HTF_NOSEND|HTF_STRUCT, 0 },
	{ "User-Agent:",		HTF_HIDDEN,		0 },
	{ "Replied:",			HTF_HIDDEN|HTF_NOSEND,	HT_REPLIED },
	{ "Forwarded:",			HTF_HIDDEN|HTF_NOSEND,	HT_FORWARDED },
	{ "Resent:",			HTF_HIDDEN|HTF_NOSEND,	HT_RESENT },
	{ "Thread-Index:",		HTF_HIDDEN|HTF_NOSEND,	0 },
	{ "Thread-Topic:",		HTF_HIDDEN|HTF_NOSEND,	0 },
	{ "Content-class:",		HTF_HIDDEN|HTF_NOSEND,	0 },
	{ "X-UIDL:",			HTF_HIDDEN|HTF_NOSEND,	0 },
	{ "X-Bogosity:",		HTF_HIDDEN|HTF_NOSEND,	0 },
	{ "X-Spam-",			HTF_HIDDEN|HTF_NOSEND,	0 },
	{ "SpamDiagnostic",		HTF_HIDDEN|HTF_NOSEND,	0 },
	{ "X-MS-Exchange-",		HTF_HIDDEN|HTF_NOSEND|HTF_MATCHLINE, 0 },
	{ "X-",				HTF_HIDDEN,		0 },
	{ "List-",			HTF_HIDDEN,		0 },
	{ "Accept-Language:",		HTF_HIDDEN,		0 },
	{ "Content-Language:",		HTF_HIDDEN,		0 },
	{ "acceptlanguage:",		HTF_HIDDEN,		0 },
	{ NULL }
};

static ccbuf_t *
message_ctype_param_ext(struct filedb *fdb, const char *name, const char *p, const char *ep)
{
	int i, len, ext;
	ccbuf_t *cb, *tcb;
	char *exname;

	len = strlen(name);
	exname = malloc(len + 12);
	/*
	 * RFC2231
	 *	name=val
	 *	name*=charset'language'encoded-value (%20)
	 *	name*0=value0
	 *	name*1=value1
	 *	name*0*=charset'language'encoded-value#0
	 *	name*1*=encoded-value#1
	 *	name*2=value2
	 */
	if (isdigit((u_char)p[len+1])) {
		cb = NULL;
		for (i = 0; ; i++) {
			ext = 1;
			snprintf(exname, len + 12, "%s*%u*", name, i);
			tcb = message_ctype_param(fdb, exname, p, ep);
			if (tcb == NULL) {
				ext = 0;
				snprintf(exname, len + 12, "%s*%u", name, i);
				tcb = message_ctype_param(fdb, exname, p, ep);
				if (tcb == NULL)
					break;
			}
			cb = tcb;
			(void)decode_param(cb, i, ext);
		}
		if (cb != NULL)
			cb->ptr = copy_kbuf(&fdb->kbuf, cb->ptr, cb->len);
	} else {
		snprintf(exname, len + 12, "%s*", name);
		cb = message_ctype_param(fdb, exname, p, ep);
		if (cb != NULL && decode_param(cb, 0, 1) == 1)
			cb->ptr = copy_kbuf(&fdb->kbuf, cb->ptr, cb->len);
	}
	return cb;
}

ccbuf_t *
message_ctype_param(struct filedb *fdb, const char *name, const char *p, const char *ep)
{
	int q;
	int len;
	char *ext;
	static ccbuf_t ret;
	static const char tspecials[] = "()<>@,;:\\\"/[]?=";

	ext = strchr(name, '*');
	for (len = strlen(name); p + len + 1 < ep; p++) {
		if (strncasecmp(p, name, len) != 0)
			continue;
		if (fdb != NULL && ext == NULL && p[len] == '*')
			return message_ctype_param_ext(fdb, name, p, ep);
		if (p[len] != '=')
			continue;
		p += len + 1;
		for (;;) {
			if (*p == '\n' && p+1 < ep &&
			    (p[1] == ' ' || p[1] == '\t')) {
				p += 2;
				continue;
			}
			if (*p != ' ' && *p != '\t')
				break;
			if (++p >= ep)
				return NULL;
		}
		q = (*p == '"');
		if (q)
			p++;
		for (ret.ptr = p; p < ep; p++) {
			if (*p == '"')
				break;
			if (q)
				continue;
			if (*p <= ' ')
				break;
			/*
			 * tspecials MUST NOT be included even in encoded
			 * value of RFC2231.  But Thunderbird includes "/".
			 * Still ";" should be handled as a separator...
			 */
			if (ext == NULL && strchr(tspecials, *p) != NULL)
				break;
			if (*p == ';')
				break;
		}
		ret.len = p - ret.ptr;
		return &ret;
	}
	return NULL;
}

static void
message_header_ctype(struct filedb *fdb, ccbuf_t *buf)
{
	const char *p, *ep;
	ccbuf_t *pbuf;

	p = buf->ptr;
	ep = p + buf->len;
	while (*p++ != ':') if (p >= ep) return;
	for (;;) {
		while (*p == ' ' || *p == '\t') {
			if (++p >= ep)
				break;
		}
		if (p >= ep)
			return;
		if (*p != '\n')
			break;
		if (++p >= ep)
			return;
	}
	fdb->hdr_val[HT_CTYPE].ptr = p;
	fdb->hdr_val[HT_CTYPE].len = ep - p;
	fdb->type.ptr = p;
	while (p < ep && *p != '\n' && *p != ';') p++;
	fdb->type.len = p - fdb->type.ptr;
	if (CSUBMATCH("Multipart/", &fdb->type)) {
		if ((pbuf = message_ctype_param(fdb, "boundary", p, ep)) != NULL) {
			fdb->boundary = *pbuf;
			fdb->flags |= FDB_MULTIPART;
			if (CMATCH("Multipart/Signed", &fdb->type)) {
				pbuf = message_ctype_param(fdb, "protocol", p, ep);
				if (pbuf != NULL) {
					fdb->sproto = *pbuf;
					fdb->flags |= FDB_SIGNED;
				}
			} else
			if (CMATCH("Multipart/Encrypted", &fdb->type)) {
				pbuf = message_ctype_param(fdb, "protocol", p, ep);
				if (pbuf != NULL) {
					fdb->sproto = *pbuf;
					fdb->flags |= FDB_ENCRYPTED;
				}
			}
			if (CMATCH("Multipart/Alternative", &fdb->type))
				fdb->flags |= FDB_ALTERNATIVE;
		}
	}

	if (CSUBMATCH("Text/", &fdb->type)) {
		fdb->flags &= ~FDB_NOTEXT;
		if (CMATCH("Text/HTML", &fdb->type))
			fdb->flags |= FDB_HTML;
		else if (CMATCH("Text/Calendar", &fdb->type))
			fdb->flags |= FDB_CALENDAR;
		if ((pbuf = message_ctype_param(fdb, "charset", p, ep)) != NULL)
			fdb->flags |= charset_id(pbuf);
		else if (CMATCH("Text/Calendar", &fdb->type))
			fdb->flags |= FDB_CS_UTF8;
	} else if ((fdb->flags & FDB_MAIL) &&
	    !(fdb->flags & (FDB_MULTIPART|FDB_INLINE)) &&
	    CSUBMATCH("message/rfc822", &fdb->type)) {
		fdb->flags |= FDB_MULTIPART | FDB_INLINE;
	} else {
#if 0
		fdb->flags |= FDB_NOTEXT;
#endif
		if (CMATCH("Application/x-pkcs7-mime", &fdb->type)
		||  CMATCH("Application/pkcs7-mime", &fdb->type))
			fdb->flags |= FDB_ENCRYPTED;
		else
		if (CMATCH("Application/pgp", &fdb->type))
			fdb->flags |= FDB_ENCRYPTED;
		else
		if (CMATCH("Application/mac-binhex40", &fdb->type))
			fdb->flags |= FDB_ENC_BINHEX | FDB_NOTEXT;	/* XXX: CTE */
		else
		if (CMATCH("Application/x-zip-compressed", &fdb->type) ||
		    CMATCH("Application/zip", &fdb->type))
			fdb->flags |= FDB_ENC_ZIP | FDB_NOTEXT;	/* XXX: CTE */
	}
}

static int
message_header_cte(struct filedb *fdb, ccbuf_t *buf)
{
	const char *p, *ep;
	ccbuf_t *cb;

	p = buf->ptr;
	ep = p + buf->len;
	while (*p++ != ':') if (p >= ep) return 0;
	while (*p == ' ' || *p == '\t') if (++p >= ep) return 0;
	cb = &fdb->hdr_val[HT_CTE];
	cb->ptr = p;
	while (p < ep && *p != ' ' && *p != ';') p++;
	cb->len = p - cb->ptr;
	if (CMATCH("quoted-printable", cb))
		return FDB_ENC_Q;
	if (CMATCH("base64", cb))
		return FDB_ENC_B64;
	if (CMATCH("x-gzip64", cb))
		return FDB_ENC_GZ64;
	if (CMATCH("x-uuencode", cb))
		return FDB_ENC_UU;
	return 0;
}

void
message_header_settype(struct header *hdr)
{
	struct header_type *ht;

	for (ht = header_type; ht->key; ht++) {
		if (hdr->buf.len > ht->keylen
		&&  strncasecmp(hdr->buf.ptr, ht->key, ht->keylen) == 0)
			break;
		if (ht->flags & HTF_MATCHLINE) {
			int o, found = 0;
			for (o = 1; o + ht->keylen < hdr->buf.len; o++) {
				if (strncasecmp(hdr->buf.ptr + o,
				    ht->key, ht->keylen) == 0) {
					found = 1;
					break;
				}
			}
			if (found)
				break;
		}
	}
	hdr->type = ht;
}

static int
message_header_readbuf(struct filedb *fdb, struct header *hdr, int hdrsize)
{
	const char *p, *ep, *s;
	int kwd, n, flags;
	struct header_type *ht;
	static int first = 1;

	if (first) {
		first = 0;
		for (ht = header_type; ht->key; ht++)
			ht->keylen = strlen(ht->key);
	}

	p = fdb->ptr;
	ep = fdb->eptr;
	/* handle some special case */
	if (p < ep && *p == '\n') {
		fdb->ptr++;
		return 0;
	}
	if (p + 5 < ep && *p == 'F' && memcmp(p, "From ", 5) == 0) {
		while (p < ep && *p++ != '\n')
			;
	}

	hdr->buf.ptr = p;
	n = 0;
	kwd = 0;
	while (p <= ep) {
		if (!kwd) {
			if (p == ep || *p == ' ' || *p == '\t')
				break;
			if (*p == ':')
				kwd = 1;
		}
		if (p < ep && *p++ != '\n')
			continue;
		if (!kwd)
			break;
		if (p < ep && (*p == ' ' || *p == '\t')) {
			p++;
			continue;
		}
		kwd = 0;
		n++;
		if (hdrsize) {
			hdr->buf.len = p - hdr->buf.ptr - 1;	/* '\n' */
			hdr->dbuf = *(cbuf_t *)&hdr->buf;
			message_header_settype(hdr);
			ht = hdr->type;
			if (!(fdb->flags & FDB_NOMIME)
			&&  !(ht->flags & HTF_NOENCODE)) {
				flags = fdb->flags & ~FDB_NOTEXT;
				if ((flags & FDB_CHARSET) == FDB_CS_UTF8 ||
				    (flags & FDB_CHARSET) == FDB_CS_GBK)
					flags &= ~FDB_CHARSET;
				if (decode_header(&hdr->dbuf, flags))
					hdr->dbuf.ptr = copy_kbuf(&fdb->kbuf,
						hdr->dbuf.ptr, hdr->dbuf.len);
			}
			if (ht->flags & HTF_STRUCT)
				hdr->val = hdr->buf;
			else
				hdr->val = *(ccbuf_t *)&hdr->dbuf;
			for (s = hdr->val.ptr + ht->keylen; s < hdr->val.ptr + hdr->val.len; s++) {
				if (*s != ' ' && *s != '\t' && *s != '\n')
					break;
			}
			hdr->val.len -= s - hdr->val.ptr;
			hdr->val.ptr = s;
			if (!(fdb->flags & FDB_NOMIME)) {
				switch (ht->type) {
				case HT_CTYPE:
					message_header_ctype(fdb, &hdr->buf);
					break;
				case HT_CTE:
					fdb->flags |= message_header_cte(fdb, &hdr->buf);
					break;
				}
			}
			fdb->hdr_val[ht->type] = hdr->val;
			fdb->ptr = p;
			if (p == ep)
				break;
			if (*p == '\n') {
				fdb->ptr++;
				break;
			}
			hdr++;
			hdrsize--;
			hdr->buf.ptr = p;
		} else {
			if (p == ep || *p == '\n')
				break;
		}
	}
	return n;
}

static int
message_header_cmp(const void *p1, const void *p2)
{
	const struct header *h1 = p1, *h2 = p2;

	return h1->type - h2->type;
}

static void
message_header_reorder(struct filedb *fdb)
{
	struct header *hdr, *hidesep, save;
	int lines;
	int i;

	lines = fdb->hdrs;
	hdr = fdb->hdr;
	/* reorder headers */
	hidesep = NULL;
	for (i = 0; i < lines; i++, hdr++) {
		if (!(hdr->type->flags & HTF_HIDDEN)) {
			/* visible */
			if (hidesep == NULL)
				hidesep = hdr;
			continue;
		}
		/* invisible */
		if (hidesep == NULL) {
			continue;	/* no visible yet */
		}
		/* move hdr before hidesep */
		save = *hdr;
		memmove(hidesep + 1, hidesep, sizeof(*hdr) * (hdr - hidesep));
		*hidesep = save;
		hidesep++;
	}
	if (hidesep == NULL)
		return;

	/* sort within visible headers */
	qsort(hidesep, lines - (hidesep - fdb->hdr), sizeof(*hdr),
		message_header_cmp);
}

static void
message_header_split(struct filedb *fdb)
{
	int i;
	ccbuf_t buf;
	struct header *hdr;

	hdr = fdb->hdr;
	for (i = 0, hdr = fdb->hdr; i < fdb->hdrs; i++, hdr++) {
		if (hdr->dbuf.ptr == NULL)
			continue;
		fdb_getline(fdb, (ccbuf_t *)&hdr->dbuf);
		if (hdr->type->flags & HTF_HIDDEN)
			fdb->skip_lines = fdb->lines;
	}
	buf.ptr = "\n";
	buf.len = 1;
	fdb_getline(fdb, &buf);
	if (fdb->flags & FDB_DRAFT)
		fdb->skip_lines = 0;
	else if (!(fdb->flags & FDB_MAIL))
		fdb->skip_lines = fdb->lines;
}

static int
message_multipart_skip(struct filedb *fdb, ccbuf_t *sep)
{
	const char *p, *ep;
	int i;

	p = fdb->ptr;
	ep = fdb->eptr;
	i = MPART_END;
	while (p < ep) {
		if ((i = mpart_issep(p, ep, sep)) != MPART_NONE) {
			if (i == MPART_END)
				p += 2 + CL(sep) + 2 + 1;
			break;
		}
		while (p < ep) {
			if (*p++ == '\n')
				break;
		}
	}
	if (p == ep)
		i = MPART_END;
	fdb->ptr = p;
	return i;
}

static void
message_external(struct filedb *fdb)
{
	const char *s, *ep, *bstr;
	char *p;
	char buf[CHARBLOCK];
	ccbuf_t *cb;
	cbuf_t cbuf;
	int len;

	cb = &fdb->hdr_val[HT_CTYPE];
	s = CP(cb);
	ep = CE(cb);
	message_header(fdb);
#if 0
	CSETP(cb, s);
	CSETE(cb, ep);
#endif

	p = buf;
	if ((cb = message_ctype_param(fdb, "access-type", s, ep)) == NULL)
		return;
	if (CMATCH("anon-ftp", cb)) {
		bstr = "FTP";
		strlcpy(buf, "ftp://", sizeof(buf));
		p = buf + strlen(buf);
		if ((cb = message_ctype_param(fdb, "site", s, ep)) != NULL) {
			strncpy(p, cb->ptr, cb->len);
			p += cb->len;
			*p++ = '/';
		}
		if ((cb = message_ctype_param(fdb, "directory", s, ep)) != NULL) {
			strncpy(p, cb->ptr, cb->len);
			p += cb->len;
			if (p[-1] != '/')
				*p++ = '/';
		}
		if ((cb = message_ctype_param(fdb, "name", s, ep)) != NULL) {
			strncpy(p, cb->ptr, cb->len);
			p += cb->len;
		}
	} else if (CMATCH("mail-server", cb)) {
		bstr = "MAIL";
		strlcpy(buf, "mailto:", sizeof(buf));
		p = buf + strlen(buf);
		if ((cb = message_ctype_param(fdb, "server", s, ep)) != NULL) {
			strncpy(p, cb->ptr, cb->len);
			p += cb->len;
		}
	} else {
		bstr = NULL;
		fdb->skip_lines = 0;
		strncpy(p, cb->ptr, cb->len);
		p += cb->len;
	}
	*p = '\0';
	if (bstr) {
		cbuf.ptr = banner(bstr);
		strcat(cbuf.ptr, "\n");	/*XXX unsafe*/
		cbuf.len = strlen(cbuf.ptr);
		cbuf.ptr = copy_kbuf(&fdb->kbuf, cbuf.ptr, cbuf.len);
		fdb_getline(fdb, (ccbuf_t *)&cbuf);
	}
	if (p > buf) {
		len = p - buf;
		fdb->hdr_val[HT_SUBJECT].ptr = copy_kbuf(&fdb->kbuf, buf, len);
		fdb->hdr_val[HT_SUBJECT].len = len;
		fdb_getline(fdb, &fdb->hdr_val[HT_SUBJECT]);
	}
}

void
message_multipart_next(struct filedb *fdb, int dir)
{
	struct filedb *nfdb, *ufdb;
	ccbuf_t nbuf;
	int i;
	struct header *hdr, *nhdr, *p;

	fdb->flags |= FDB_EOF;	/* assert? */

	/* adjust separator */
	CSETP(&nbuf, fdb->ptr);
	CSETE(&nbuf, fdb->eptr);
	switch (dir) {
	case MPART_NEW:
		CSETP(&fdb->buf_body, fdb->ptr);
		CADDP(&nbuf, 2 + fdb->boundary.len + 1);
		break;
	case MPART_END:
		CSETE(&fdb->buf_body, fdb->ptr);
		if (*CP(&nbuf) == '\n')
			CADDP(&nbuf, 1);
		CADDP(&nbuf, 2 + fdb->separator.len + 2 + 1);
		break;
	case MPART_NEXT:
		CSETE(&fdb->buf_body, fdb->ptr);
		if (*CP(&nbuf) == '\n')
			CADDP(&nbuf, 1);
		CADDP(&nbuf, 2 + fdb->separator.len + 1);
		break;
	}
	nfdb = fdb_mkpart(&nbuf);
	nfdb->mmap = fdb->mmap;
	nfdb->name = fdb->name;
	nfdb->msgnum = fdb->msgnum;
	nfdb->partnum = fdb->partnum + 1;
	nfdb->uppart = fdb->uppart;
	nfdb->flags |= fdb->flags & FDB_DRAFT;
	nfdb->filetime = fdb->uppart ? fdb->uppart->filetime : fdb->filetime;

	switch (dir) {
	case MPART_NEW:		/* down */
		nfdb->partdepth = fdb->partdepth + 1;
		nfdb->partnum = 0;
		nfdb->uppart = fdb;
		nfdb->separator = fdb->boundary;
		break;
	case MPART_NEXT:	/* next, called from file.c */
		nfdb->partdepth = fdb->partdepth;
		nfdb->partnum = fdb->partnum + 1;
		nfdb->uppart = fdb->uppart;
		nfdb->separator = fdb->separator;
		break;
	case MPART_END:	 	/* up, called from file.c */
		for (ufdb = fdb->uppart; ; ufdb = nfdb->uppart) {
			if (ufdb == NULL) {
				/* all done */
				free(nfdb);
				return;
			}
			CSETE(&ufdb->buf_body, nfdb->ptr);
			nfdb->partdepth = ufdb->partdepth;
			nfdb->partnum = ufdb->partnum + 1;
			nfdb->uppart = ufdb->uppart;
			nfdb->separator = ufdb->separator;
			if (message_multipart_skip(nfdb, &nfdb->separator) != MPART_END)
				break;
		}
		nfdb->ptr += 2 + nfdb->separator.len + 1;
		/* eptr is not changed */
		break;
	}
	fdb->nextpart = nfdb;
	nfdb->prevpart = fdb;
	if (fdb->flags & FDB_INLINE) {
		nfdb->buf_hdr = fdb->buf_body;
		nfdb->ptr = fdb->ptr;
		nfdb->flags |= FDB_MAIL;
		message_header(nfdb);
		return;
	}

	message_header(nfdb);

	if (dir == MPART_NEW
	&&  (fdb->flags & FDB_MAIL)
	&&  CSUBMATCH("Text/", &nfdb->type)) {
		while ((fdb->flags & FDB_HIDDEN) && fdb->uppart != NULL) {
			fdb->flags |= FDB_INLINE;
			fdb = fdb->uppart;
		}
		fdb->flags |= FDB_INLINE;
		i = fdb->hdrs + nfdb->hdrs;
		if (i > fdb->hdr_size) {
			p = realloc(fdb->hdr, sizeof(*p) * i);
			if (!p)
				abort();
			fdb->hdr = p;
			fdb->hdr_size = i;
		}
		hdr = &fdb->hdr[fdb->hdrs];
		for (i = 0, nhdr = nfdb->hdr; i < nfdb->hdrs; i++) {
			*hdr++ = *nhdr++;
		}
		fdb->hdrs += nfdb->hdrs;
		nfdb->skip_lines = nfdb->hdr_lines;	/* even for FDB_DRAFT */
		return;
	}

	if (dir == MPART_NEW
	&&  (fdb->flags & FDB_MAIL)
	&&  !(nfdb->flags & FDB_DRAFT)
	&&  CSUBMATCH("Multipart/", &nfdb->type)) {
		nfdb->flags |= FDB_HIDDEN | FDB_MAIL;
		if (message_multipart_skip(nfdb, &nfdb->boundary) != MPART_END)
			message_multipart_next(nfdb, MPART_NEW);
		return;
	}

	if (nfdb->uppart != NULL
	&&  (nfdb->uppart->flags & FDB_SIGNED)
	&&  nfdb->uppart->sproto.len == nfdb->type.len
	&&  strncasecmp(nfdb->uppart->sproto.ptr, nfdb->type.ptr, nfdb->type.len) == 0) {
		nfdb->flags |= FDB_NOCONV|FDB_HIDDEN;
		return;
	}

	if (CMATCH("Message/Rfc822", &nfdb->type)) {
		nfdb->flags |= FDB_MAIL;
		message_header(nfdb);
		return;
	}

	if (CMATCH("Message/External-Body", &nfdb->type)) {
		message_external(nfdb);
		return;
	}

	if (CSUBMATCH("Multipart/", &nfdb->type)) {
		if (!(nfdb->flags & (FDB_INLINE|FDB_DRAFT)))
			nfdb->flags |= FDB_HIDDEN;
		if (message_multipart_skip(nfdb, &nfdb->boundary) != MPART_END)
			message_multipart_next(nfdb, MPART_NEW);
	}
}

void
message_header(struct filedb *fdb)
{
	int n;
	struct header *hdr, *p;
	ccbuf_t *subj, *cb;

	/* default */
	if (!(fdb->flags & FDB_MAIL)
	&&  fdb->uppart != NULL
	&&  CMATCH("Multipart/Digest", &fdb->uppart->type)) {
		CSETP(&fdb->type, "Message/Rfc822");
	} else {
		CSETP(&fdb->type, "Text/Plain");
		fdb->flags &= ~FDB_NOTEXT;
	}
	CADDE(&fdb->type, strlen(CP(&fdb->type)));

	cb = (fdb->flags & FDB_MAIL) ? &fdb->buf_hdr : &fdb->buf_mhdr;
	CSETP(cb, fdb->ptr);
	fdb->hdr_size = (fdb->flags & FDB_MAIL) ? LINEBLOCK : 10;
	fdb->hdr = malloc(sizeof(struct header) * fdb->hdr_size);
	n = message_header_readbuf(fdb, fdb->hdr, fdb->hdr_size);
	if (n > fdb->hdr_size) {
		int n2;

		p = realloc(fdb->hdr, sizeof(struct header) * n);
		if (!p)
			abort();
		fdb->hdr = p;
		n2 = message_header_readbuf(fdb, fdb->hdr + fdb->hdr_size,
		    n - fdb->hdr_size);
		if (n != n2 + fdb->hdr_size)
		    abort();
		fdb->hdr_size = n;
	}
	fdb->hdrs = n;
	if (fdb->flags & FDB_MULTIPART) {
		/* CT, CTE should be hidden */
		for (n = 0, hdr = fdb->hdr; n < fdb->hdrs; ) {
			if (hdr->type->flags & HTF_OVERWRITE) {
				fdb->hdrs--;
				if (n < fdb->hdrs)
					memmove(hdr, hdr + 1,
						sizeof(struct header) * (fdb->hdrs - n));
			} else {
				n++;
				hdr++;
			}
		}
	}
	CSETE(cb, fdb->ptr);
	CSETP(&fdb->buf_body, fdb->ptr);
	CSETE(&fdb->buf_body, fdb->eptr);

	if (fdb->flags & FDB_MAIL) {
		if (fdb->flags & FDB_MULTIPART) {
			if (fdb->flags & FDB_INLINE)
				message_multipart_next(fdb, MPART_NEW);
			else if (message_multipart_skip(fdb, &fdb->boundary) != MPART_END)
				message_multipart_next(fdb, MPART_NEW);
		}
	}

	if (!(fdb->flags & FDB_DRAFT))
		message_header_reorder(fdb);
	message_header_split(fdb);
	fdb->hdr_lines = fdb->lines;

	/* filename */
	if (!(fdb->flags & FDB_NOMIME)) {
		cb = &fdb->hdr_val[HT_CDISP];
		if (CP(cb) != NULL
		&&  (cb = message_ctype_param(fdb, "filename", CP(cb), CE(cb))) != NULL) {
			fdb->filename = *cb;
			cb = &fdb->hdr_val[HT_CDISP];
			if ((cb = message_ctype_param(fdb, "modification-date", CP(cb), CE(cb))) != NULL) {
				/*
				 * XXX: Outlook uses GMT as local zone for
				 * sender's system.
				 * mod-date with "GMT" is ignored here.
				 */
				/*
				 * fixed? -- onoe, 2014.8.13
				 if (CL(cb) >= 4 &&
				     strncmp(CE(cb) - 4, " GMT", 4) != 0)
				*/
					fdb->filetime = parse_date(cb);
			}
		}
	}

	/* fix subject */
	subj = &fdb->hdr_val[HT_SUBJECT];
	if (subj->ptr == NULL && !(fdb->flags & FDB_NOMIME)) {
		if (CP(&fdb->hdr_val[HT_CDESC]) != NULL)
			*subj = fdb->hdr_val[HT_CDESC];
		else
			*subj = fdb->filename;
	}
	fdb->flags |= FDB_PARSED;
}

void
message_parseall(struct filedb *fdb)
{

	do {
		while (!(fdb->flags & FDB_EOF))
			(void)fdb_read(fdb, fdb->lines);
	} while ((fdb = fdb->nextpart) != NULL);
}

void
message_open(struct state *state, int force)
{
	char path[MAXPATH];
	struct folder *folder;
	struct filedb *fdb;
	int n;

	folder = state->folder;
	if (folder->nmsg == 0)
		return;
	n = folder->msg[folder->pos].num;
	snprintf(path, sizeof(path), "%s/%d", folder->name.ptr + 1, n);
	if (!force
	&&  ((fdb = state->message) != NULL)
	&&  strcmp(fdb->name, path) == 0)
		return;
	if ((state->message = fdb = fdb_open(path)) == NULL)
		return;
	if (state->nomime)
		fdb->flags |= FDB_NOMIME;
	fdb->msgnum = n;
	if (!(fdb->flags & FDB_PARSED)) {
		fdb->flags |= FDB_MAIL;
		if (folder->flags & FOL_DRAFT)
			fdb->flags |= FDB_DRAFT;
		CSETP(&fdb->buf_mhdr, fdb->ptr);
		message_header(fdb);
	}
	state->msgwin.off = fdb->skip_lines;
	if (fdb->flags & FDB_DRAFT)
		message_parseall(fdb);
}

const char *
message_parse_addr(const char *p, const char *ep, ccbuf_t *rbuf)
{
	int skip = 0, term = 0;

	rbuf->ptr = p;
	rbuf->len = 0;

	for (; p < ep; p++) {
		if (skip) {
			if (*p == skip)
				skip = 0;
			continue;
		}
		if (*p == '<') {
			rbuf->ptr = p + 1;
			rbuf->len = 0;
			term = '>';
			continue;
		}
		if (term > 0) {
			if (*p == term) {
				term = -1;
				continue;
			}
		} else {
			if (*p == ',') {
				p++;
				break;
			}
		}
		if (*p == '(') {
			skip = ')';
			continue;
		} else if (*p == '"') {
			skip = '"';
			continue;
		}
		if (term >= 0 && *p != ' ' && *p != '\t') {
			rbuf->len = p + 1 - rbuf->ptr;
		}
	}
	if (rbuf->len == 0)
		rbuf->ptr = NULL;
	return p;
}

int
message_note(struct filedb *fdb)
{
	int note;

	if (fdb->hdr_val[HT_REPLIED].ptr)
		note = NOTE_REPLIED;
	else if (fdb->hdr_val[HT_FORWARDED].ptr)
		note = NOTE_FORWARD;
	else if (fdb->hdr_val[HT_RESENT].ptr)
		note = NOTE_RESENT;
	else if (fdb->flags & FDB_VERIFIED)
		note = NOTE_VERIFIED;
	else if (fdb->flags & FDB_SIGNED)
		note = NOTE_SIGNED;
	else if (fdb->flags & FDB_DECRYPTED)
		note = NOTE_DECRYPTED;
	else if (fdb->flags & FDB_ENCRYPTED)
		note = NOTE_ENCRYPTED;
	else if ((fdb->flags & FDB_MULTIPART) && !(fdb->flags & FDB_ALTERNATIVE))
		note = NOTE_MULTI;
	else
		note = ' ';
	return note;
}

void
message_scan(struct state *state, struct msginfo *msg)
{
	char path[MAXPATH];
	struct filedb *fdb, *ofdb;
	ccbuf_t *cp;
	ccbuf_t cbuf;
	char datebuf[16], addrbuf[32];
	int w, n, len;
	char c;
	const char *p, *ep;
	char *o;
	char *msgnum;
	struct tm *tm;
	static char outbuf[LINE_WIDTH*3+1];	/* enough to hold LINE_WIDTH */

	snprintf(path, sizeof(path), "%s/%d", state->folder->name.ptr + 1,
	    msg->num);
	if ((msgnum = strrchr(path, '/')) == NULL)
		msgnum = "";
	else {
		len = strlen(++msgnum);
		if (len > state->config.numwidth)
			msgnum = msgnum + len - state->config.numwidth;
	}
	if ((fdb = fdb_open(path)) == NULL) {
		snprintf(outbuf, sizeof(outbuf), SCAN_FMT,
		    state->config.numwidth, msgnum, ' ', "", "");
		goto end;
	}
	if (state->nomime)
		fdb->flags |= FDB_NOMIME;
	fdb->msgnum = msg->num;
	if (!(fdb->flags & FDB_PARSED)) {
		fdb->flags |= FDB_MAIL;
		if (state->folder->flags & FOL_DRAFT)
			fdb->flags |= FDB_DRAFT;
		CSETP(&fdb->buf_mhdr, fdb->ptr);
		message_header(fdb);
	}
	datebuf[0] = addrbuf[0] = '\0';
	cp = &fdb->hdr_val[HT_DATE];
	if (CP(cp) == NULL || (msg->date = parse_date(cp)) < 0)
		msg->date = fdb->stbuf.st_mtime;
	if (fdb->filetime <= 0)
		fdb->filetime = msg->date;
	tm = localtime(&msg->date);
	snprintf(datebuf, sizeof(datebuf), "%02d%02d%02d", tm->tm_year % 100,
	    tm->tm_mon + 1, tm->tm_mday);
	cp = &fdb->hdr_val[HT_FROM];
	if (cp->ptr)
		(void)message_parse_addr(cp->ptr, cp->ptr + cp->len, &cbuf);
	else
		cbuf.ptr = NULL;
	if (cbuf.ptr == NULL || conf_myaddr(state, &cbuf)) {
		cp = &fdb->hdr_val[HT_TO];
		(void)message_parse_addr(cp->ptr, cp->ptr + cp->len, &cbuf);
		strlcpy(addrbuf, "To:", sizeof(addrbuf));
		n = 3;	/* strlen(addrbuf) */
	} else
		n = 0;
	len = cbuf.len;
	if (len + n + 1 > sizeof(addrbuf))
		len = sizeof(addrbuf) - (n + 1);
	memcpy(addrbuf + n, cbuf.ptr, len);
	addrbuf[n + len] = '\0';

	o = outbuf;
	n = snprintf(o, sizeof(outbuf), SCAN_FMT, state->config.numwidth,
	    msgnum, message_note(fdb), datebuf, addrbuf);
	if (n < 0)
		goto end;
	o += n;
	w = LINE_WIDTH - (o - outbuf);
	cbuf = fdb->hdr_val[HT_SUBJECT];	/* structure copy */
	cp = &cbuf;
	while (cp->len > 0) {
		if (*cp->ptr != ' ' && *cp->ptr != '\t')
			break;
		cp->ptr++;
		cp->len--;
	}
	if (cp->ptr) {
		for (p = cp->ptr + cp->len; --p > cp->ptr; ) {
			if (*p != ' ' && *p != '\t')
				break;
		}
		cp->len = p + 1 - cp->ptr;
	}
	while (cp && w > 0) {
		p = cp->ptr;
		ep = p + cp->len;
		while (p < ep && w > 0) {
			if (*(u_char *)p == CS_SS2) {
				if (p + 1 >= ep) {
					w = 0;
					break;
				}
				*o++ = *p++;
				*o++ = *p++;
				w--;
			} else if (*(u_char *)p == CS_SS3) {
				if (p + 2 >= ep) {
					w = 0;
					break;
				}
				*o++ = *p++;
				*o++ = *p++;
				*o++ = *p++;
				w -= 2;
			} else if (*(u_char *)p == CS_SSX) {
				if (p + 1 >= ep) {
					w = 0;
					break;
				}
				if (((u_char *)p)[1] & CS_DWIDTH) {
					if (p + 3 >= ep || w == 1) {
						w = 0;
						break;
					}
					*o++ = *p++;
					*o++ = *p++;
					*o++ = *p++;
					*o++ = *p++;
					w -= 2;
				} else {
					if (p + 2 >= ep) {
						w = 0;
						break;
					}
					*o++ = *p++;
					*o++ = *p++;
					*o++ = *p++;
					w--;
				}
			} else if (*p & 0x80) {
				if (p[0] == (char)0xa1 && p[1] == (char)0xa1) {
					/* space */
					if (o[-1] != ' ') {
						*o++ = ' ';
						w--;
					}
					p += 2;
				} else {
					if (p + 1 >= ep || w == 1) {
						w = 0;
						break;
					}
					*o++ = *p++;
					*o++ = *p++;
					w -= 2;
				}
			} else {
				c = *p++;
				if (c == '\t' || c == '\n')
					c = ' ';
				if (c != ' ' || o[-1] != ' ') {
					*o++ = c;
					w--;
				}
			}
		}
		if (w > 0 && o[-1] != ' ') {
			*o++ = ' ';
			w--;
		}
		if (cp == &cbuf) {
			if (w <= 2)
				break;
			*o++ = '<';
			*o++ = '<';
			w -= 2;
			n = fdb->hdr_lines;
		}
		while  ((cp = fdb_read(fdb, n++))) {
			p = cp->ptr;
			if (cp->len == 0 || *p == '>' || *p == '|') {
				while (fdb_ismore(fdb, n - 1))
					n++;
				continue;
			}
#if 0
			if ((*p >= 'A' && *p <= 'Z') || (*p & 0x80)) {
				ep = p + cp->len;
				while (++p < ep) {
					if (*p == ' ' || *p == '\t') {
						p = ep;
						break;
					}
					if (*p == '>' || *p == ':')
						break;
				}
				if (p < ep)
					continue;
			}
#endif
			break;
		}
	}
	*o = '\0';
  end:
	msg->scan.ptr = strdup(outbuf);
	msg->scan.len = strlen(outbuf);
	if (msg->mark != MARK_REFILE && fdb != NULL) {
		/* XXX: save message */
		ofdb = state->message;
		state->message = fdb;
		(void)refile_guess(state, msg);
		state->message = ofdb;
	}
}

void
message_jump(struct state *state)
{
	char buf[LINE_WIDTH];
	struct folder *fl;
	struct msginfo *msg;
	char *p;
	int i, n;

	fl = state->folder;
	strlcpy(state->status, "Go No.: ", sizeof(state->status));
	buf[0] = '\0';
	if (edit_stline(state, buf, sizeof(buf), NULL) == NULL
	||  buf[0] == '\0') {
		strlcpy(state->status, "Quit", sizeof(state->status));
		return;
	}
	n = strtoul(buf, &p, 0);
	if (n == 0 || *p != '\0') {
		strlcpy(state->status, "Bad message number", sizeof(state->status));
		return;
	}
	for (i = 0, msg = fl->msg; i < fl->nmsg; i++, msg++) {
		if (msg->num >= n) {
			state->folder->pos = i;
			state->status[0] = '\0';
			if (state->msgmode) {
				disp_center(state);
				message_open(state, 1);
			}
			return;
		}
	}
	snprintf(state->status, sizeof(state->status),
	    "Message not found: %d", n);
}

void
message_next(struct state *state, int n, int ismark)
{
	struct filedb *fdb;
	int pos, mark;
	struct folder *folder = state->folder;

	/* multipart */
	if (n
	&&  !ismark
	&&  state->msgmode
	&&  (fdb = state->message)
	&&  (fdb->flags & (FDB_MULTIPART|FDB_SUBPART))) {
		for (;;) {
			if (n > 0) {
				if (fdb->nextpart == NULL) {
					while (!(fdb->flags & FDB_EOF))
						(void)fdb_read(fdb, fdb->lines);
				}
				fdb = fdb->nextpart;
			} else {
				fdb = fdb->prevpart;
			}
			if (fdb == NULL)
				break;
			if (fdb->prevpart == NULL
			||  (!(fdb->flags & FDB_HIDDEN)
			  && !(fdb->prevpart->flags & FDB_INLINE))) {
				state->message = fdb;
				state->msgwin.off = fdb->skip_lines;
				return;
			}
		}
	}

	if (n == 0)
		n = state->prevdir;
	pos = state->folder->pos + n;
	state->prevdir = n = (n >= 0 ? 1 : -1);
	for (; pos >= 0 && pos < folder->nmsg; pos += n) {
		mark = folder->msg[pos].mark;
		if (mark != MARK_DELETE && mark != MARK_REFILE
		&&  mark != MARK_SPAM
		&&  (!ismark || mark == MARK_MARK)) {
			state->folder->pos = pos;
			if (state->msgmode) {
				disp_center(state);
				message_open(state, 1);
			}
			return;
		}
	}
	strlcpy(state->status, "No more message", sizeof(state->status));
	state->message = NULL;
}

void
message_encoding(struct state *state)
{
	struct filedb *fdb;
	ccbuf_t cbuf;
	char buf[CHARBLOCK];

	message_open(state, 0);
	if ((fdb = state->message) == NULL) {
		strlcpy(state->status, "No current message",
		    sizeof(state->status));
		return;
	}
	strlcpy(state->status, "Encoding: ", sizeof(state->status));
	if ((fdb->flags & FDB_CHARSET) == FDB_CS_DEFAULT)
		buf[0] = '\0';
	else
		strlcpy(buf, charset_name(fdb->flags & FDB_CHARSET), sizeof(buf));
	if (edit_stline(state, buf, sizeof(buf), charset_completion) == NULL) {
		strlcpy(state->status, "Quit", sizeof(state->status));
		return;
	}
	state->status[0] = '\0';
	CSETSTR(&cbuf, buf);
	if (fdb->flags & FDB_INLINE)
		fdb = fdb->nextpart;
	fdb->flags = (fdb->flags & ~FDB_CHARSET) | charset_id(&cbuf);

	/* reload body */
	fdb->flags &= ~(FDB_EOF | FDB_NOTEXT);
	fdb_reload(fdb, fdb->hdr_lines, CP(&fdb->buf_body));
}

static char *
message_debug_show(char *d, const char *hdr, ccbuf_t *cb, int strict)
{
	const char *p, *ep;
	int i, n, mid;

	d += sprintf(d, "\t%-6s ", hdr);
	p = CP(cb);
	if (p == NULL) {
		d += sprintf(d, "<null>\n");
		return d;
	}
	ep = CE(cb);
	n = 40;
	if (strict && n > CL(cb))
		n = CL(cb);
	if (CL(cb) > n)
		mid = n / 2;
	else
		mid = -1;
	for (i = 0; i < n && *p; i++) {
		switch (*p) {
		case '\n':	*d++ = '\\'; *d++ = 'n'; break;
		case '\r':	*d++ = '\\'; *d++ = 'r'; break;
		case '\b':	*d++ = '\\'; *d++ = 'b'; break;
		case '\t':	*d++ = '\\'; *d++ = 't'; break;
		case '\033':	*d++ = '\\'; *d++ = 'e'; break;
		default:
			*d++ = *p;
			break;
		}
		p++;
		if (i == mid) {
			p = ep - (n - i - 1);
			d += sprintf(d, "  ...  ");
		}
	}
	d += sprintf(d, " (%d)\n", CL(cb));
	return d;
}

void
message_debug(struct state *state)
{
	struct filedb *fdb, *hdb;
	int i, n;
	cbuf_t cbuf;
	char *p;
	char buf[65536];	/*XXX: should be allocated dynamically */

	message_open(state, 0);
	if ((fdb = state->message) == NULL) {
		strlcpy(state->status, "No current message",
		    sizeof(state->status));
		return;
	}
	state->msgmode = 1;
	message_parseall(fdb);
	while (fdb->uppart)
		fdb = fdb->uppart;
	p = buf;
	for (; fdb; fdb = fdb->nextpart) {
		for (i = 0; i < fdb->partdepth; i++)
			*p++ = ' ';
		p += sprintf(p, "%d\t", fdb->partnum + 1);
		n = fdb->type.len;
		if (n >= 14)
			n = 14;
		p += sprintf(p, "%-14.*s  ", n, fdb->type.ptr);
		p += sprintf(p, "%2d/%-14.14s ", fdb->flags & FDB_CHARSET,
		    (fdb->flags & FDB_CHARSET) == FDB_CS_DEFAULT ?
		    "default" : charset_name(fdb->flags & FDB_CHARSET));
		if (fdb->flags & FDB_NOTEXT)	p += sprintf(p, "NOTEXT ");
		if (fdb->flags & FDB_NOCONV)	p += sprintf(p, "NOCONV ");
		switch (fdb->flags & FDB_ENCODE) {
		case FDB_ENC_Q:		p += sprintf(p, "ENC_Q ");	break;
		case FDB_ENC_B64:	p += sprintf(p, "ENC_B64 ");	break;
		case FDB_ENC_GZ64:	p += sprintf(p, "ENC_GZ64 ");	break;
		case FDB_ENC_UU:	p += sprintf(p, "ENC_UU ");	break;
		case FDB_ENC_BINHEX:	p += sprintf(p, "ENC_BINHEX ");	break;
		case FDB_ENC_ZIP:	p += sprintf(p, "ENC_ZIP ");	break;
		default:		break;
		}
		if (fdb->flags & FDB_EOF)	p += sprintf(p, "EOF ");
		if (fdb->flags & FDB_INLINE)	p += sprintf(p, "INLINE ");
		if (fdb->flags & FDB_MULTIPART)	p += sprintf(p, "MULTIPART ");
		if (fdb->flags & FDB_SUBPART)	p += sprintf(p, "SUBPART ");
		if (fdb->flags & FDB_ALTERNATIVE) p += sprintf(p, "ALTERNATIVE ");
		if (fdb->flags & FDB_HIDDEN)	p += sprintf(p, "HIDDEN ");
		if (fdb->flags & FDB_MAIL)	p += sprintf(p, "MAIL ");
		if (fdb->flags & FDB_PARSED)	p += sprintf(p, "PARSED ");
		if (fdb->flags & FDB_PURGED)	p += sprintf(p, "PURGED ");
		if (fdb->flags & FDB_DRAFT)	p += sprintf(p, "DRAFT ");
		if (fdb->flags & FDB_SIGNED)	p += sprintf(p, "SIGNED ");
		if (fdb->flags & FDB_ENCRYPTED)	p += sprintf(p, "ENCRYPTED ");
		if (fdb->flags & FDB_VERIFIED)	p += sprintf(p, "VERIFIED ");
		if (fdb->flags & FDB_DECRYPTED)	p += sprintf(p, "DECRYPTED ");
		if (fdb->flags & FDB_NOMMAP)	p += sprintf(p, "NOMMAP ");
		if (fdb->flags & FDB_TARFILE)	p += sprintf(p, "TARFILE ");
		if (fdb->flags & FDB_FORCEDISP)	p += sprintf(p, "FORCEDISP ");
		if (fdb->flags & FDB_NOMIME)	p += sprintf(p, "NOMIME ");
		if (fdb->flags & FDB_HTML)	p += sprintf(p, "HTML ");
		if (fdb->flags & FDB_CALENDAR)	p += sprintf(p, "CALENDAR ");
		if (fdb->flags & FDB_GUESSED)	p += sprintf(p, "GUESSED ");
		*p++ = '\n';
		p += sprintf(p, "\tfiletime\t%s", ctime(&fdb->filetime));
		p = message_debug_show(p, "b_mhdr", &fdb->buf_mhdr, 0);
		p = message_debug_show(p, "b_hdr", &fdb->buf_hdr, 0);
		p = message_debug_show(p, "b_body", &fdb->buf_body, 0);
		p = message_debug_show(p, "sep", &fdb->separator, 1);
		p = message_debug_show(p, "bdry", &fdb->boundary, 1);
		*p++ = '\n';
	}
	cbuf.ptr = buf;
	cbuf.len = p - buf;
	hdb = fdb_mkpart((ccbuf_t *)&cbuf);
	state->help = hdb;
	state->helptitle = "** Message Debug **";
	disp_help(state);
	fdb_clear(hdb);
	state->status[0] = '\0';
}
