/*
**  Routines to read and write mail and news headers.  The code here
**  is gross and complicated.
*/
#include "gup.h"
#include "rfc822.h"
#include <time.h>
#include <string.h>
#if	defined(RCSID)
static char RCS[] =
	"$Header: /var/src/gup/src/RCS/rfc822.c,v 1.3 93/07/24 01:28:45 andrew Exp Locker: andrew $";
#endif	/* defined(RCSID) */

extern time_t		time();
extern char		*asctime();
extern struct tm	*gmtime();

#define QUESTIONABLE(c) \
	((c) == '"' || (c) == '(' || (c) == ')' || (c) == '\\')

#define HDR_OTHER		-1
#define HDR_END			FALSE
#define HDR_APPROVED		 1
#define HDR_CONTROL		 2
#define HDR_DATE		 3
#define HDR_DISTRIBUTION	 4
#define HDR_EXPIRE		 5
#define HDR_FOLLOWTO		 6
#define HDR_FROM 		 7
#define HDR_KEYWORDS		 8
#define HDR_MESSAGEID		 9
#define HDR_NEWSGROUP 		10
#define HDR_ORGANIZATION	11
#define HDR_REFERENCES		12
#define HDR_REPLYTO		13
#define HDR_SENDER		14
#define HDR_SUMMARY		15
#define HDR_TITLE 		16


#ifdef NotNeeded
/*
**  The list of headers we recognize; all others are stripped.
*/
typedef struct _HTYPE {
    STRING	Name;
    int		Type;
} HTYPE;

STATIC HTYPE	HeaderList[] = {
    {	"Approved:",		HDR_APPROVED		},
    {	"Control:",		HDR_CONTROL		},
    {	"Date:",		HDR_DATE		},
    {	"Posted:",		HDR_DATE		},
    {	"Distribution:",	HDR_DISTRIBUTION	},
    {	"Expires:",		HDR_EXPIRE		},
    {	"Followup-To:",		HDR_FOLLOWTO		},
    {	"From:",		HDR_FROM		},
    {	"Keywords:",		HDR_KEYWORDS		},
    {	"Message-ID:",		HDR_MESSAGEID		},
    {	"Newsgroups:",		HDR_NEWSGROUP		},
    {	"Organization:",	HDR_ORGANIZATION	},
    {	"In-Reply-To:",		HDR_REFERENCES		},
    {	"References:",		HDR_REFERENCES		},
    {	"Reply-To:",		HDR_REPLYTO		},
    {	"Sender:",		HDR_SENDER		},
    {	"Summary:",		HDR_SUMMARY		},
    {	"Subject:",		HDR_TITLE		},
    {	"Title:",		HDR_TITLE		},
};



/*
**  Getline is like fgets, but deals with continuation lines.  It also
**  ensures that even if a line that is too long is received, the
**  remainder of the line is thrown away instead of treated like a second
**  line.
*/
STATIC char *
Getline(buf, len, fp)
    char		*buf;
    int			len;
    FILE		*fp;
{
    register char	*cp;
    register int	c;
    register int	n;

    for (n = 0, cp = buf; n < len && (c = getc(fp)) != EOF && c != '\n'; )
	if (!iscntrl(c) || c == '\b' || c == '\t') {
	    *cp++ = c;
	    n++;
	}
    if (c == EOF && cp == buf)
	return NULL;
    *cp = '\0';

    if (c != '\n')
	/* Line too long - read part didn't fit into a newline */
	while ((c = getc(fp)) != '\n' && c != EOF)
	    continue;
    else if (cp == buf) {
	/* Don't look for continuation of blank lines */
	*cp++ = '\n';
	*cp = '\0';
	return buf;
    }

    while ((c = getc(fp)) == ' ' || c == '\t') {
	/* Continuation line. */
	if ((n += 2) < len) {
	    *cp++ = '\n';
	    *cp++ = c;
	}
	while ((c = getc(fp)) != '\n' && c != EOF)
	    if ((!iscntrl(c) || c == '\b' || c == '\t') && n++ < len)
		*cp++ = c;
    }
    if (n >= len - 1)
	cp = buf + len - 2;
    *cp++ = '\n';
    *cp = '\0';
    if (c != EOF)
	/* push back first char of next header */
	(void)ungetc(c, fp);
    return buf;
}


/*
**  I guess this is basically strncasecmp
*/
STATIC int
prefix(full, pref)
    register char	*full;
    register char	*pref;
{
    register char	fc;
    register char	pc;

    while ((pc = *pref++) != '\0') {
	fc = *full++;
	if (isupper(fc))
	    fc = tolower(fc);
	if (isupper(pc))
	    pc = tolower(pc);
	if (fc != pc)
	    return FALSE;
    }
    return TRUE;
}


STATIC int
HeaderType(p)
    register char	*p;
{
    static int		save = HDR_END;
    register HTYPE	*hp;
    char		*colon;
    char		*space;

    /* some consistency checks (i.e. is this really a header line?) */
    if (p == NULL || !isascii(*p) || *p == '\n')
	return save = HDR_END;

    if (WHITE(*p))
	/* Continuation line. */
	return save;

    /* If we don't get a "<no space> <colon> <space>", it's not a header. */
    if ((colon = IDX(p, ':')) == NULL
     || ((space = IDX(p, ' ')) && space < colon))
	return save = HDR_END;

    for (hp = HeaderList; hp < ENDOF(HeaderList); hp++)
	if (prefix(p, hp->Name))
	    return save = hp->Type;
    return save = HDR_OTHER;
}


/*
**  Get the contents of the field of the header line, appending it, with a
**  space delimeter if it's a continuation line.  If there is already
**  something in the header storage, skip this header line and the
**  continuations.
*/
STATIC void
getfield(src, dest, size)
    register char	*src;
    register char	*dest;
    register int	size;
{
    static int		skip = FALSE;
    register char	*p;

    if (src == NULL || dest == NULL)
	return;

    if (WHITE(*src)) {
	/* Continuation line.  If skipping or no room, ignore. */
	if (skip || (size -= strlen(dest)) <= 0)
	    return;
	/* Munch all but one whitespace, append it to header. */
	while (*src && WHITE(*src))
	    src++;
	*--src = ' ';
	(void)strncat(dest, src, size - 1);
    }
    else {
	skip = FALSE;
	if (*dest) {
	    /* Already got a value, so mark this as one to skip. */
	    skip = TRUE;
	    return;
	}
	if ((src = IDX(src, ':')) == NULL)
	    /* Can't happen! */
	    return;
	/* Skip colon, eat whitespace. */
	for (src++; *src && WHITE(*src); )
	    src++;
	(void)strncpy(dest, src, size - 1);
    }

    /* Munch trailing whitespace. */
    for (p = dest + strlen(dest); --p >= dest && (*p == '\n' || WHITE(*p)); )
	continue;
    p[1] = '\0';
}


STATIC time_t
cgtdate(datestr)
    char		*datestr;
{
    static time_t	lasttime;
    static char		save[SM_SIZE];
    char		junk[40];
    char		month[40];
    char		day[30];
    char		tod[60];
    char		year[50];
    char		buf[SM_SIZE];
#if	defined(USE_GETABSDATE)
    extern time_t	getabsdate();
#else
/*    extern time_t	getdate(); */	/* inn parsedate() hack - andrew@werple */
#endif	/* defined(USE_GETABSDATE) */

    if (save[0] && EQ(datestr, save))
	return lasttime;

#if	defined(USE_GETABSDATE)
    lasttime = getabsdate(datestr, (struct timeb *)NULL);
#else
    lasttime = getdate(datestr, (struct timeb *)NULL);
#endif	/* defined(USE_GETABSDATE) */

    if (lasttime < 0 &&
      sscanf(datestr, "%s %s %s %s %s", junk, month, day, tod, year) == 5) {
	(void)sprintf(buf, "%s %s, %s %s", month, day, year, tod);
#if	defined(USE_GETABSDATE)
	lasttime = getabsdate(buf, (struct timeb *)NULL);
#else
	lasttime = getdate(buf, (struct timeb *)NULL);
#endif	/* defined(USE_GETABSDATE) */
    }
    (void)strncpy(save, datestr, sizeof save);
    return lasttime;
}


/*
**  Print the date in ARPA format, not ctime(3) format.
*/
STATIC void
DoDate(fp, ud)
    FILE		*fp;
    register char	*ud;
{
    register char	*p;
    register char	*q;
    register int	i;
    char		buff[SM_SIZE];

    q = buff;

    /* "16" */
    p = &ud[8];
    if (*p == ' ')
	p++;
    else
	*q++ = *p++;
    *q++ = *p++; *q++ = ' ';

    /* "Sep " */
    p = &ud[4]; *q++ = *p++; *q++ = *p++; *q++ = *p++; *q++ = ' ';

    /* "79 " */
    p = &ud[22]; *q++ = *p++; *q++ = *p++; *q++ = ' ';

    /* "01:03:52" */
    for (p = &ud[11], i = 8; i > 0; i--)
	*q++ = *p++;

    /* " GMT" */
    *q++ = ' '; *q++ = 'G'; *q++ = 'M'; *q++ = 'T'; *q = '\0';

    Fprintf(fp, "Date: %s\n", buff);
}
#endif /* NotNeeded */

/*
**  Crack an RFC822 from header field into address and fullname.  We do
**  this to make sure we write things out in official form.  "Be liberal
**  in what you accept, conservative in what you generate."  Anyhow, we
**  read things into three buffers, one for all <...> text, one for all
**  (...) text, and a third for stuff not in either.  Either the first or
**  third buffer will be the real address, depending on whether there is
**  anything in buffer two or not.
*/
int
CrackFrom(addr, name, p)
    ADDRCHAR		*addr;
    char		*name;
    char		*p;
{
    register ADDRCHAR	*adp;
    register char	*bp;
    register char	*ap;
    register ADDRCHAR	*cp = NULL;	/* keep gcc 2 happy */
    register ADDRCHAR	CurrChar;
    register int	comment;
    register int	address;
    register int	addrfound;
    register int	QuoteNext;
    register int	InQuotes;
    ADDRCHAR		*xp;
    ADDRCHAR		comm[LG_SIZE];
    ADDRCHAR		commbuf[LG_SIZE];
    ADDRCHAR		addrbuf[LG_SIZE];

    /* Just to make sure. */
    *name = '\0';
    *addr = '\0';

    if (p == NULL)
	return FALSE;

    /* Eat leading white space. */
    while (*p && isspace(*p))
	p++;

    /* Set defaults. */
    comm[0] = '\0';
    commbuf[0] = '\0';
    addrbuf[0] = '\0';
    adp = addrbuf;
    comment = 0;
    addrfound = 0;
    address = 0;
    QuoteNext = 0;
    InQuotes = 0;
    for (; *p; p++) {
	CurrChar = (*p & UNQUOTE_MASK) | QuoteNext;
	QuoteNext = CurrChar == '\\' ? QUOTE_MASK : 0;
	if (!comment && CurrChar == '"')
	    InQuotes = InQuotes ? 0 : QUOTE_MASK;
	else
	    CurrChar |= InQuotes;
	switch (CurrChar) {
	case '(':
	    if (comment == 0) {
		cp = commbuf;
		*cp = '\0';
	    }
	    comment++;
	    break;
	case ')':
	    if (comment > 0 && --comment == 0) {
		*cp = '\0';
		xp = comm;
		if (*xp) {
		    while (*xp)
			xp++;
		    *xp++ = ',';
		    *xp++ = ' ';
		}
		for (cp = &commbuf[1]; (*xp++ = *cp) != '\0'; cp++)
		    continue;
		cp = NULL;
		continue;
	    }
	    break;
	case '<':
	    if (address)
		return FALSE;	/* AWK! Abort! */
	    if (!comment) {
		address++;
		*adp = '\0';
		adp = addr;
	    }
	    break;
	case '>':
	    if (!comment && address) {
		address--;
		addrfound++;
		for (*adp = '\0', adp = addrbuf; *adp; )
		    adp++;
		continue;
	    }
	    break;
	}

	if (comment)
	    *cp++ = CurrChar;
	else if (!address || CurrChar != '<')
	    *adp++ = CurrChar;
	if (*p == '\0')
	    break;
    }

    *adp++ = '\0';

    if (addrfound) {
	for (ap = name, xp = addrbuf; (*ap++ = (*xp & UNQUOTE_MASK)) != 0; xp++)
	    continue;
    }
    else {
	for (cp = addr, xp = addrbuf; (*cp++ = *xp) != 0; xp++)
	    continue;
	*name = '\0';
    }

    /* Just to be sure that we got the full name, we'll take all of
     * the comments. */
    if (*comm) {
	ap = name;
	if (*ap) {
	    while (*ap)
		ap++;
	    *ap++ = ',';
	    *ap++ = ' ';
	}
	for (xp = comm; (*ap = (*xp++ & UNQUOTE_MASK)) != 0; ap++)
	    continue;
    }
    /* Copy back, skipping leading spaces and trailing spaces. */
    for (cp = addr; isspace(*cp); cp++)
	continue;
    for (xp = addr; (*xp = *cp++) != 0; )
	xp++;
    while (*addr && isspace(*--xp))
	*xp = '\0';

    /* Since characters in (comments) are interpreted differently from
     * those in "phrase" <address>, play it safe and remove all
     * questionable characters. */
    for (ap = name, bp = name; isspace(*bp) || QUESTIONABLE(*bp); bp++)
	continue;
    while ((*ap = *bp++) != '\0')
	if (!QUESTIONABLE(*ap))
	    ap++;
	while (*name && isspace(*--ap))
	    *ap = '\0';
    return TRUE;
}


#ifdef NotNeeded
/*
**  Write out an RFC822 header, paying no attention to line limits.
**  Ideally, we should do continuations in here...
*/
int
rfc822write(hp, fp)
    register HBUF	*hp;
    register FILE	*fp;
{
    time_t		t;

    if (hp->path[0])
	Fprintf(fp, "Path: %s\n", hp->path);
    if (hp->from[0])
	Fprintf(fp, "From: %s\n", hp->from);
    if (hp->nbuf[0])
	Fprintf(fp, "Newsgroups: %s\n", hp->nbuf);
    if (hp->title[0])
	Fprintf(fp, "Subject: %s\n", hp->title);
    if (hp->ident[0])
	Fprintf(fp, "Message-ID: %s\n", hp->ident);
    /* Get current time. This will be used resolve the timezone. */
    if (hp->subdate[0] == '\0')
	(void)time(&t);
    else if ((t = cgtdate(hp->subdate)) < 0) {
	Fprintf(fp, "X-Unparseable-Date: %s\n", hp->subdate);
	(void)time(&t);
    }
    DoDate(fp, asctime(gmtime(&t)));
    if (hp->expdate[0])
	Fprintf(fp, "Expires: %s\n", hp->expdate);
    if (hp->followid[0])
	Fprintf(fp, "References: %s\n", hp->followid);
    if (hp->ctlmsg[0])
	Fprintf(fp, "Control: %s\n", hp->ctlmsg);
    if (hp->sender[0])
	Fprintf(fp, "Sender: %s\n", hp->sender);
    if (hp->replyto[0])
	Fprintf(fp, "Reply-To: %s\n", hp->replyto);
    if (hp->followto[0])
	Fprintf(fp, "Followup-To: %s\n", hp->followto);
    if (hp->distribution[0])
	Fprintf(fp, "Distribution: %s\n", hp->distribution);
    if (hp->organization[0])
	Fprintf(fp, "Organization: %s\n", hp->organization);
    if (hp->keywords[0])
	Fprintf(fp, "Keywords: %s\n", hp->keywords);
    if (hp->summary[0])
	Fprintf(fp, "Summary: %s\n", hp->summary);
    if (hp->approved[0])
	Fprintf(fp, "Approved: %s\n", hp->approved);
    Fprintf(fp, "\n");
    return !ferror(fp);
}


void
rfc822read(hp, fp, buff, buffsize)
    register HBUF	*hp;
    register FILE	*fp;
    char		*buff;
    int			buffsize;
{
    register int	i;
    long		curpos;

    /* Zap out the headers. */
    hp->approved[0] = '\0';
    hp->ctlmsg[0] = '\0';
    hp->subdate[0] = '\0';
    hp->distribution[0] = '\0';
    hp->expdate[0] = '\0';
    hp->followto[0] = '\0';
    hp->from[0] = '\0';
    hp->followid[0] = '\0';
    hp->keywords[0] = '\0';
    hp->ident[0] = '\0';
    hp->nbuf[0] = '\0';
    hp->organization[0] = '\0';
    hp->title[0] = '\0';
    hp->replyto[0] = '\0';
    hp->summary[0] = '\0';
    hp->path[0] = '\0';
    hp->sender[0] = '\0';

    i = HeaderType(buff);
    do {
	curpos = ftell(fp);
	switch (i) {
	case HDR_APPROVED:
	    getfield(buff, hp->approved, sizeof hp->approved);
	    break;
	case HDR_CONTROL:
	    getfield(buff, hp->ctlmsg, sizeof hp->ctlmsg);
	    break;
	case HDR_DATE:
	    getfield(buff, hp->subdate, sizeof hp->subdate);
	    break;
	case HDR_DISTRIBUTION:
	    getfield(buff, hp->distribution, sizeof hp->distribution);
	    break;
	case HDR_EXPIRE:
	    getfield(buff, hp->expdate, sizeof hp->expdate);
	    break;
	case HDR_FOLLOWTO:
	    getfield(buff, hp->followto, sizeof hp->followto);
	    break;
	case HDR_FROM:
	    getfield(buff, hp->from, sizeof hp->from);
	    break;
	case HDR_KEYWORDS:
	    getfield(buff, hp->keywords, sizeof hp->keywords);
	    break;
	case HDR_MESSAGEID:
	    getfield(buff, hp->ident, sizeof hp->ident);
	    break;
	case HDR_NEWSGROUP:
	    getfield(buff, hp->nbuf, sizeof hp->nbuf);
	    break;
	case HDR_ORGANIZATION:
	    getfield(buff, hp->organization, sizeof hp->organization);
	    break;
	case HDR_REFERENCES:
	    getfield(buff, hp->followid, sizeof hp->followid);
	    break;
	case HDR_REPLYTO:
	    getfield(buff, hp->replyto, sizeof hp->replyto);
	    break;
	case HDR_SENDER:
	    getfield(buff, hp->sender, sizeof hp->sender);
	    break;
	case HDR_SUMMARY:
	    getfield(buff, hp->summary, sizeof hp->summary);
	    break;
	case HDR_TITLE:
	    getfield(buff, hp->title, sizeof hp->title);
	    break;
	}
    } while ((i = HeaderType(Getline(buff, buffsize, fp))) != HDR_END);

    if (*buff != '\n')
	(void)fseek(fp, curpos, 0);
}
#endif
