%{
/*
 * RFC 822 address parsing routines.
 */

#ifndef lint
static char sccsid[] = "%W%";
#endif lint

#include "mace.h"
#include "headers.h"
#include "flex.h"
#include "addr.h"

#define panic(s) abort(s)
#define nomem() abort("nomem")

static flex f = { NULLSTR, NULLSTR, NULLSTR, };

/* $Log: address.y,v $
 * Revision 1.5 88/12/29 00:44:14 john
 * Moved to MIPS
 * 
 * Revision 1.5 88/12/29 00:44:14 john
 * Move eatcomment() into address.y where it belongs, and fix it to
 * handle \-quoting of parens inside comments.
 * 
 * Revision 1.4 88/12/28 22:13:40 john
 * Fix \ quoting within quoted-strings and domain-literals (it was
 * missing). Also fix missing cases for ']' as an atom.
 * 
 * Revision 1.3 88/12/28 00:14:03 john
 * Fix lexical analysis bug, parentheses were not being correctly
 * recognized as atoms.
 * 
 * Revision 1.2 88/10/30 07:46:22 john
 * Improve compliance of grammar to RFC822:
 * - insist on a domain at the end of a source-route
 * Change makeaddress() to do the right thing with a
 * source-routed address (i.e., make it an actual textual
 * representation of the source-routed address).
 * Fix bug in putdom.
 * 
 * Revision 1.1 88/10/26 09:54:04 john
 * Initial revision
 * 
 */

static char RCSid[] =
    "$Header: address.y,v 1.5 88/12/29 00:44:14 john Exp $";

static char *errstr;
static char *comstr;
static char *cp;
static char *savecp;
static char *saveline;
static flex errbuf = { NULLSTR, NULLSTR, NULLSTR };
static flex combuf = { NULLSTR, NULLSTR, NULLSTR };
static int iseol;

static char *text;
Addr *adrlist;
Addr *errlist;
%}

%union {
   char yChar;
   char *yString;
   Dom *yDom;
   Addr *yAddr;
}

%token EOL ATOM LIT_DOMAIN QUOTED_STRING

%type <yString> word domain_ref sub_domain local_part phrase
%type <yDom> domain route_list route
%type <yAddr> addr_spec non_local_addr_spec route_addr mailbox mbox_list
group address

%start addr_list

%%
addr_list: addr_lel
       | addr_list addr_lel
       ;

addr_lel: address EOL {
	    $1->comment = comstr;
	    $1->error = errstr;
	    comstr = NULL;
	    errstr = NULL;
	    appAddr(&adrlist, $1);
	}
	| address ',' {
	    $1->comment = comstr;
	    $1->error = errstr;
	    comstr = NULL;
	    errstr = NULL;
	    appAddr(&adrlist, $1);
	}
	| error {
	    register Addr *ap;

	    ap = newAddr();
	    if (savecp > saveline) {
		flex_str(&errbuf, " after \"");
		flex_nstr(&errbuf, saveline, savecp - saveline);
		flex_char(&errbuf, '"');
		if (cp && *cp) {
		    flex_str(&errbuf, ", before \"");
		    flex_str(&errbuf, cp);
		    flex_char(&errbuf, '"');
		}
	    }
	    else if (cp && *cp) {
		flex_str(&errbuf, " before \"");
		flex_str(&errbuf, cp);
		flex_char(&errbuf, '"');
	    }

	    if (errbuf.f_ptr != errbuf.f_str) {
		flex_char(&errbuf, '\0');
		errstr = newstring2(errstr, errbuf.f_str);
		flex_end(&errbuf);
	    }

	    ap->error = errstr;
	    errstr = NULL;
	    comstr = NULL;
	    appAddr(&errlist, ap);
	}
	;

address: mailbox {
	    $$ = $1;
	}
	| group {
	    $$ = $1;
	}
	;

group : phrase ':' mbox_list ';' {
	    register Addr *a;

	    for (a = $3; a; a = a->next)
		a->group = $1;
	    $$ = $3;
	}
	;

mbox_list: mailbox {
	    $$ = $1;
	}
	| mbox_list ',' mailbox {
	    $3->comment = comstr;
	    $3->error = errstr;
	    comstr = NULL;
	    errstr = NULL;
	    appAddr(&($1), $3);
	    $$ = $1;
	}
	;

mailbox: addr_spec {
	    $$ = $1;
	}
	| route_addr {
	    $$ = $1;
	}
	| phrase route_addr {
	    $2->name = $1;
	    $$ = $2;
	}
	;

phrase : word {
	    $$ = $1;
	}
	| phrase word {
	    $$ = newstring3($1, " ", $2);
	    free($1);
	    free($2);
       }
       ;

route_addr: '<' addr_spec '>' {
	   $$ = $2;
       }
       | '<' route non_local_addr_spec '>' {
	   prepDom(&($3->route), $2);
	   $$ = $3;
       }
       ;

route : route_list ':' {
	    $$ = $1;
	}
	;

route_list: '@' domain {
	    $$ = $2;
	}
	| route_list ',' '@' domain {
	    appDom(&($1), $4);
	    $$ = $1;
	}
	;

addr_spec: non_local_addr_spec
	| local_part {
	    register Addr *ap;

	    $$ = ap = newAddr();
	    ap->localp = $1;
	    ap->destdom = NULL;
	}
	;

non_local_addr_spec: local_part '@' domain {
	    register Addr *ap;

	    $$ = ap = newAddr();
	    ap->localp = $1;
	    ap->destdom = $3;
	    ap->route = $3;
	}
	;

local_part: word {
		$$ = $1;
	    }
	    | local_part '.' word {
		$$ = newstring3($1, ".", $3);
		free($1);
		free($3);
	    }
	    | local_part '%' word {
		$$ = newstring3($1, "%", $3);
		free($1);
		free($3);
	    }
	    ;

domain : sub_domain {
	    register Dom *dp;

	    dp = newDom();
	    dp->sub[0] = $1;
	    dp->top = dp->sub;
	    $$ = dp;
	}
	| domain '.' sub_domain {
	    ($1->top)++;
	    *($1->top) = $3;
	    $$ = $1;
	}
	;

sub_domain: domain_ref {
	    $$ = $1;
	}
	| LIT_DOMAIN {
	    $$ = yylval.yString;
	}
	;

domain_ref: ATOM {
	    $$ = yyval.yString;
	}
	;

word : ATOM {
	    $$ = yylval.yString;
	}
	| QUOTED_STRING {
	    $$ = yylval.yString;
	}
	;

%%

#include <stdio.h>
#include <ctype.h>

#define ERROR -2


static char *
newstring3(a, b, c)
    char *a;
    char *b;
    char *c;
{
    char *p;
    char *q;
    int i;

    i = strlen(a) + strlen(b) + strlen(c) + 1;
    if ((p = salloc((MALLOCT)i)) == NULL)
	nomem();
    q = p + strlen(strcpy(p, a));
    q += strlen(strcpy(q, b));
    Strcpy(q, c);
    return(p);
}


static char *
newstring2(a, b)
    char *a;
    char *b;
{
    char *p;
    int i;

    if (a == (char *)0)
	a = "";
    i = strlen(a) + strlen(b) + 1;
    if ((p = salloc((MALLOCT)i)) == NULL)
	nomem();
    Strcpy(p, a);
    Strcat(p, b);
    return(p);
}

/*
 * <mess>: [ "<what>" ] unexpected [ <tail> ]
 */
static void
unexpected(mess, what, tail)
char *mess;
char *what;
char *tail;
{
    flex_str(&errbuf, mess);
    flex_str(&errbuf, ": ");

    if (what != NULLSTR)
    {
	flex_char(&errbuf, '"');
	flex_str(&errbuf, what);
	flex_str(&errbuf, "\" ");
    }

    flex_str(&errbuf, "unexpected");

    if (tail != NULLSTR)
    {
	flex_char(&errbuf, ' ');
	flex_str(&errbuf, tail);
    }

    flex_char(&errbuf, '\0');
}

static void
yyerror(s)
    char *s;
{
   static char c[2] = { '\0', '\0' };

   switch(yychar) {
       default:
	   c[0] = yylval.yChar;
	   unexpected(s, c, NULLSTR);
	   errstr = newstring2(errstr, errbuf.f_str);
	   break;
       case LIT_DOMAIN:
       case QUOTED_STRING:
       case ATOM:
	   unexpected(s, yylval.yString, NULLSTR);
	   errstr = newstring2(errstr, errbuf.f_str);
	   break;
       case EOL:
       case 0: /* EOF */
	   unexpected(s, NULLSTR, "end-of-header");
	   errstr = newstring2(errstr, errbuf.f_str);
	   break;
   }
   flex_end(&errbuf);
}


parseit(line)
    char *line;
{
    saveline = cp = text = line;
    adrlist = NULL;
    errlist = NULL;
    if (combuf.f_str == NULLSTR)
	flex_init(&combuf);
    flex_end(&combuf);
    if (errbuf.f_str == NULLSTR)
	flex_init(&errbuf);
    flex_end(&errbuf);
    (void)yyparse();
}


char *
eatcomment(s)
register char *s;
{
	register int parencount;

	parencount = 0;
	for (;;) {
		if (*s == '\\') { /* quoted-pair */
			s++;
			if (*s == '\0')
				return ((char *)0);
			s++;
			if (*s == '\0')
				return ((char *)0);
			continue;
		}
		if (*s == '(')
			parencount++;
		else if (*s == ')')
			parencount--;

		if (parencount == 0)
			return (++s);
		else if (parencount < 0)
			panic("eatcomment botch");

		if (*++s == '\0')
			return ((char *)0);
	}
}


yylex()
{
    register char *p;

    savecp = cp;
    while (isascii(*cp) && (isspace(*cp) || (*cp == '('))) {
	if (*cp == '(') {
	    p = eatcomment(cp);
	    if (p == (char *)0)
		return (EOF);
	    flex_nstr(&combuf, cp + 1, (p - 2) - cp);
	    flex_char(&combuf, '\0');
	    if (comstr == NULL) {
		if ((comstr = salloc((MALLOCT)(strlen(combuf.f_str) + 1))) ==
		NULL)
		    nomem();
		Strcpy(comstr, combuf.f_str);
	    }
	    else
		comstr = newstring3(comstr, ", ", combuf.f_str);
	    flex_end(&combuf);
	    cp = p;
	}
	else
	    cp++;
    }

    if (!isascii(*cp))
	return(ERROR);

    switch (*cp) {
	case '\0':
	    if (iseol) {
		iseol = 0;
		return(EOF);
	    }
	    iseol = 1;
	    return(EOL);
	case ',':
	case ':':
	case ';':
	case '.':
	case '@':
	case '%':
	case '<':
	case '>':
	case '(':
	case ')':
	case ']':
	    yylval.yChar = *cp;
	    return(*cp++);
	case '[': /* LIT_DOMAIN */
	    for (p = cp + 1; *p && *p != ']'; ) {
		if (*p == '\\') {
		    p++;
		    if (*p == '\0')
			return(EOF);
		}
		p++;
	    }
	    if (*p == '\0')
		return(EOF);
	    if ((yylval.yString = salloc((MALLOCT)(p - cp + 2))) == NULL)
		nomem();
	    Strncpy(yylval.yString, cp, p - cp + 1);
	    yylval.yString[p - cp + 1] = '\0';
	    cp = ++p;
	    return(LIT_DOMAIN);
	case '"': /* QUOTED_STRING */
	    for (p = cp + 1; *p && *p != '"'; ) {
		if (*p == '\\') {
		    p++;
		    if (*p == '\0')
			return(EOF);
		}
		p++;
	    }
	    if (*p == '\0')
		return(EOF);
	    if ((yylval.yString = salloc((MALLOCT)(p - cp))) == NULL)
		nomem();
	    Strncpy(yylval.yString, cp + 1, p - cp);
	    yylval.yString[p - cp - 1] = '\0';
	    cp = ++p;
	    return(QUOTED_STRING);
    }
    for (p = cp; ; p++)
	switch (*p) {
	    case ',':
	    case ':':
	    case ';':
	    case '.':
	    case '@':
	    case '%':
	    case '<':
	    case '>':
	    case '(':
	    case ')':
	    case '[':
	    case ']':
	    case '"':
	    case '\0':
		goto out;
	    default:
		if (isspace(*p))
		    goto out;
	}
out:
    if ((yylval.yString = salloc((MALLOCT)(p - cp + 1))) == NULL)
	nomem();
    Strncpy(yylval.yString, cp, p - cp);
    yylval.yString[p - cp] = '\0';
    cp = p;
    return(ATOM);
}

/*
** Create and initialize a new address.
*/
Addr *
newAddr()
{
    register Addr *ap;

    if ((ap = (Addr *)salloc((MALLOCT)sizeof *ap)) == NULL)
	nomem();
    memset((char *)ap, '\0', sizeof *ap);
    return(ap);
}


/*
** Append addresslist "addr" to addresslist "head".
*/
appAddr(head, addr)
    Addr **head;
    Addr *addr;
{
    register Addr *ap;
    register char *p;
    register int i;

    if (*head) {
	for (ap = *head; ap->next; ap = ap->next)
	    ;
	ap->next = addr;
    }
    else
	*head = addr;

    if (head == &adrlist)
    {
	while (isspace(*text))
	    text++;

	if (*(p = cp) != '\0')
	{
	    p--;

	    if (isspace(p[-1]))
	    {
		p--;

		while (isspace(*p))
			p--;

		p++;
	    }
	}

	if ((addr->text = salloc((MALLOCT)(p - text + 1))) == NULLSTR)
	    nomem();

	strncpy(addr->text, text, i = p - text);
	addr->text[i] = '\0';

	text = cp;
    }
    else
	addr->text = NULLSTR;
}



/*
** Create and initialize a new domain.
*/
Dom *
newDom()
{
    register Dom *dp;

    if ((dp = (Dom *)salloc((MALLOCT)sizeof *dp)) == NULL)
	nomem();
    memset((char *)dp, '\0', sizeof *dp);
    dp->top = dp->sub;
    return(dp);
}


/*
** Append domainlist "dom" to domainlist "head".
*/
appDom(head, dom)
    Dom **head;
    Dom *dom;
{
    register Dom *dp;

    if (*head) {
	for (dp = *head; dp->next; dp = dp->next)
	    ;
	dp->next = dom;
    }
    else
    *head = dom;
}


/*
** Prepend domainlist "dom" before domainlist "head".
*/
prepDom(head, dom)
    Dom **head;
    Dom *dom;
{
    register Dom *dp;

    for (dp = dom; dp->next; dp = dp->next)
	;
    dp->next = *head;
    *head = dom;
}

static void
putdom(d)
register Dom *d;
{
	register char **p;

	for (p = d->sub; p != d->top; p++)
	{
		flex_str(&f, *p);
		flex_char(&f, RFC_SPEC_DOT);
	}

	flex_str(&f, *p);
}

/*
 * Take an Addr struct and return a textual representation that
 * a delivery agent can use to deliver the message.
 */
char *
make_address(a)
register Addr *a;
{
	register Dom *d;
	register char *p;

	if (f.f_str == NULLSTR)
		flex_init(&f);

	d = a->route;

	while (d != (Dom *)0 && d->next != (Dom *)0)
	{
		/* source route */

		flex_char(&f, RFC_SPEC_AT);
		putdom(d);
		flex_char(&f, d->next->next ? RFC_SPEC_COMMA :
		RFC_SPEC_COLON);
		d = d->next;
	}

	flex_str(&f, a->localp);

	if (d != (Dom *)0)
	{
		flex_char(&f, RFC_SPEC_AT);
		putdom(d);
	}

	flex_char(&f, '\0');
	p = newstr(f.f_str);
	flex_end(&f);

	return p;
}

/*
 * Parse a textual address list and return it as a list of Addr structs.
 *
 * Returns NULLSTR for ok; otherwise an error string.
 */
char *
parse_address(ap, s)
Addr **ap;
char *s;
{
	register Addr *a;

	parseit(s);

	if (errlist != (Addr *)0)
	{
		for (a = errlist; a != (Addr *)0; a = a->next)
		{
			if (a->error != NULLSTR)
				return a->error;
		}

		abort("make_address");
		/* NOTREACHED */
	}

	for (a = adrlist; a != (Addr *)0; a = a->next)
	{
		if (a->localp == (char *)0 || a->localp[0] == '\0')
			return "null local-part in address";
	}

	*ap = adrlist;

	return NULLSTR;
}