/*
 * An RFC 2822 address parser.
 *
 * The grammar for this is taken mostly from RFC 2822 directly.  The goal is to
 * support the full syntax, including the obsolete forms.
 *
 * Copyright (c) 2004	Greg A. Woods <woods@planix.com>
 */

%{

/*
 *	C Definitions and declarations for the parser....
 */

#ident "@(#)librfc2822: :address.y,v 1.3 2004/08/14 01:20:02 woods Exp "

#include <sys/cdefs.h>

#include <stdio.h>
#include <string.h>
#include <errno.h>

#include "rfc2822.h"
#include "librfc2822.h"

/*
 * definitions for lexical analyser interfaces
 */
#define yytext		rfc2822_text

/*
 * definitions for private variables
 */
static int     rfc2822_enomem = 0;

/*
 * Prototypes for private functions
 */
static char    *concat(char *, char *);
static char    *concatws(char *, char *);
static int	yyerror(const char *);

/*
 * This macro should only be used in rules code....
 */
#define yy_safe_strdup(target, a) {					\
		target = (YYSTYPE) strdup((char *) a);			\
		if (!target) {						\
			rfc2822_enomem++;				\
			YYABORT;					\
		}							\
	}

#ifdef YYDEBUG
# define YYDPRINTF(x)	if (yydebug) printf x
#else
# define YYDPRINTF(x)	
#endif

#define YYERROR_VERBOSE		1	/* better error messages for some YACCs? */

%}		/* end of C definitions */

/*
 * YACC declarations
 */

/* lexical token declarations */

%token TOK_ATEXT TOK_DTEXT TOK_QTEXT TOK_CTEXT TOK_ILLEGAL

/* grammar variables */

/* precedence and associativity information */

%nonassoc ';'
%nonassoc ','
%nonassoc '.'
%left ':'
%left '@'

%start address_list

%%		/* End of YACC declarations */

/*
 * YACC grammar rules and actions
 */

address_list:					/* (address *("," address)) / obs-addr-list */
	address {
		YYDPRINTF(("address_list: returning address list with one: '%s'\n", $1));
		$$ = $1;
	}
	| address_list ',' address {
		YYDPRINTF(("address_list: returning another address, '%s', for this list: '%s'\n", $3, $1));
		$$ = concat($1, concatws(safe_strdup(","), $3));
	}
	| obs_addr_list {
		YYDPRINTF(("address_list: returning address list with obsolete address list: '%s'\n", $1));
		$$ = $1;
	}
/*
 * Note: the next special hacks avoid having to deal with null addresses in
 * some obvious places....
 */
	| address_list ',' {
		YYDPRINTF(("address_list: ignoring trailing comma on this list: '%s'\n", $1));
		$$ = $1;
	}
	| ',' address_list {
		YYDPRINTF(("address_list: ignoring leading comma on this list: '%s'\n", $2));
		$$ = $2;
	}
/*
 * On the other hand trying to insert commas, at least at this level in the
 * grammar, doesn't work very well and previously working expressions suddenly
 * encounter various syntax errors.
 *
	| address_list address {
		YYDPRINTF(("address_list: returning another address, '%s', for this list: '%s'\n", $2, $1));
		$$ = concat($1, concatws(safe_strdup(","), $2));
	}
 */
	| error ',' {
		yyclearin;		/* XXX necessary? */
		yyerror("invalid partial address list");
	}
	| error {
		yyclearin;		/* XXX necessary? */
		yyerror("invalid address list");
	}
;

address:
	mailbox {
		YYDPRINTF(("address: returning mailbox: '%s'\n", $1));
		$$ = $1;
	}
	| group {
		YYDPRINTF(("address: returning group: '%s'\n", $1));
		$$ = $1;
	}
;

mailbox_list:					/* (mailbox *("," mailbox)) / obs-mbox-list */
	mailbox {
		YYDPRINTF(("mailbox_list: returning address list with one: '%s'\n", $1));
		$$ = $1;
	}
	| mailbox_list ',' mailbox {
		YYDPRINTF(("mailbox_list: returning another address, '%s', for this list: '%s'\n", $3, $1));
		$$ = concat($1, concatws(safe_strdup(","), $3));
	}
	| obs_mbox_list {
		YYDPRINTF(("mailbox_list: returning address list with obsolete mailbox list: '%s'\n", $1));
		$$ = $1;
	}
/*
 * Note: the next special hacks avoid having to deal with null addresses in
 * some obvious places....
 */
	| mailbox_list ',' {
		YYDPRINTF(("mailbox_list: ignoring trailing comma on this list: '%s'\n", $1));
		$$ = $1;
	}
	| ',' mailbox_list {
		YYDPRINTF(("mailbox_list: ignoring leading comma on this list: '%s'\n", $2));
		$$ = $2;
	}
	| error ',' {
		yyclearin;
		yyerror("invalid mailbox in mailbox list");
	}
	| error ';' {
		yyclearin;
		yyerror("invalid mailbox list");
	}
;

group:						/*  display-name ":" [mailbox-list / CFWS] ";" [CFWS] */
	group_display_name ':' mailbox_list ';' { /* mailbox_list  may be empty */
		YYDPRINTF(("group: new group: '%s': with addresses: '%s'\n", $1, $3));
		$$ = concat($1, concatws(safe_strdup(":"), concat($3, safe_strdup(";"))));
	}
	| group_display_name ':' mailbox_list ';' comment {
		YYDPRINTF(("group: new group: '%s': with addresses: '%s', and trailing comment (%s)\n", $1, $3, $5));
		$$ = concat($1, concatws(safe_strdup(":"), concat($3, safe_strdup(";"))));
	}
	| group_display_name ':' comment ';' {
		YYDPRINTF(("group: new group: '%s': with just a comment: '%s'\n", $1, $3));
		$$ = concat($1, concatws(safe_strdup(":"), safe_strdup(";")));
	}
	| group_display_name ':' ';' {
		YYDPRINTF(("group: new group: '%s': with empty list\n", $1));
		$$ = concat($1, concatws(safe_strdup(":"), safe_strdup(";")));
	}
	| error ';' {
		yyclearin;
		yyerror("invalid group");
	}
;

mailbox:					/* name-addr / addr-spec */
	name_addr {
		YYDPRINTF(("mailbox: returning name_addr: '%s'\n", $1));
		$$ = $1;
	}
	| addr_spec {
		YYDPRINTF(("mailbox: returning addr_spec: '%s'\n", $1));
		$$ = $1;
	}
;

name_addr:					/* [display-name] angle-addr */
	angle_addr {
		YYDPRINTF(("name_addr: returning un-named angle_addr: '%s'\n", $1));
		$$ = $1;
	}
	| mailbox_display_name angle_addr {
		YYDPRINTF(("name_addr: returning angle-addr named: '%s': '%s'\n", $1, $2));
		$$ = concatws($1, $2);
	}
;

angle_addr:					/* [CFWS] "<" addr-spec ">" [CFWS] / obs-angle-addr */
	'<' addr_spec '>' {
		YYDPRINTF(("angle_addr: returning: '%s'\n", $2));
		$$ = concat(safe_strdup("<"), concat($2, safe_strdup(">")));
	}
	| comment '<' addr_spec '>' {
		YYDPRINTF(("angle_addr: returning with leading comment: (%s) '%s'\n", $1, $3));
		$$ = concat(safe_strdup("<"), concat($3, safe_strdup(">")));
	}
	| '<' addr_spec '>' comment {
		YYDPRINTF(("angle_addr: returning with trailing comment: '%s' (%s)\n", $2, $4));
		$$ = concat(safe_strdup("<"), concat($2, safe_strdup(">")));
	}
	| comment '<' addr_spec '>' comment {
		YYDPRINTF(("angle_addr: returning with surrounding comment: (%s) '%s' (%s)\n", $1, $3, $5));
		$$ = concat(safe_strdup("<"), concat($3, safe_strdup(">")));
	}
	| obs_angle_addr {
		YYDPRINTF(("angle_addr: returning obsolete angle-addr: '%s'\n", $1));
		$$ = $1;
	}
	| '<' '>' {				/* special exception (normal use is 'group:;') */
		YYDPRINTF(("angle_addr: returning empty angle_addr\n"));
		$$ = safe_strdup("<>");
	}
	| '<' error '>' {
		yyclearin;
		yyerror("invalid angle_addr");
	}
;

addr_spec:					/* local-part "@" domain */
	local_part {				/* we allow plain local-part too */
		YYDPRINTF(("addr_spec: returning a mailbox: '%s'\n", $1));
		$$ = $1;
	}
	| local_part '@' domain {
		YYDPRINTF(("addr_spec: returning local_part '%s' AT domain '%s'\n", $1, $3));
		$$ = concat($1, concat(safe_strdup("@"), $3));
	}
;

group_display_name:
	phrase {
		YYDPRINTF(("group_display_name: returning group name: '%s'\n", $1));
		$$ = $1;
	}
;

mailbox_display_name:
	phrase {
		YYDPRINTF(("mailbox_display_name: returning mailbox name: '%s'\n", $1));
		$$ = $1;
	}
;

phrase:						/* 1*word / obs-phrase */
	word {
		YYDPRINTF(("phrase: returning word: '%s'\n", $1));
		$$ = $1;
	}
	| word phrase {				/* must be right-recursive to work? */
		YYDPRINTF(("phrase: adding another word '%s', to phrase: '%s'\n", $2, $1));
		$$ = concatws($1, $2);
	}
	| obs_phrase {
		YYDPRINTF(("phrase: returning obs_phrase: '%s'\n", $1));
		$$ = $1;
	}
;

local_part:					/* dot-atom / quoted-string / obs-local-part */
	dot_atom {
		YYDPRINTF(("local_part: returning dot_atom: '%s'\n", $1));
		$$ = $1;
	}
	| quoted_string {
		YYDPRINTF(("local_part: returning quoted_string: '%s'\n", $1));
		$$ = $1;
	}
	| obs_local_part {
		YYDPRINTF(("local_part: returning obs_local_part: '%s'\n", $1));
		$$ = $1;
	}
;

domain:						/* dot-atom / domain-literal / obs-domain */
	dot_atom {
		YYDPRINTF(("domain: returning dot_atom: '%s'\n", $1));
		$$ = $1;
	}
	| domain_literal {
		YYDPRINTF(("domain: returning domain_literal", $1));
		$$ = $1;
	}
;

domain_literal:					/* [CFWS] "[" *(dtext / quoted-pair) "]" [CFWS] */
	'[' dtext ']' {
		YYDPRINTF(("domain_literal: returning dtext: [%s]\n", $2));
		$$ = concat(safe_strdup("["), concat($2, safe_strdup("]")));
	}
	| comment '[' dtext ']' {
		YYDPRINTF(("domain_literal: returning dtext with leading comment: (%s) [%s]\n", $1, $3));
		$$ = concat(safe_strdup("["), concat($3, safe_strdup("]")));
	}
	| '[' dtext ']' comment {
		YYDPRINTF(("domain_literal: returning dtext with trailing comment: [%s] (%s)\n", $1, $3));
		$$ = concat(safe_strdup("["), concat($2, safe_strdup("]")));
	}
	| comment '[' dtext ']' comment {
		YYDPRINTF(("domain_literal: returning dtext with leading comment: (%s) [%s] (%s)\n", $1, $3, $5));
		$$ = concat(safe_strdup("["), concat($3, safe_strdup("]")));
	}
	| '[' error ']' {
		yyclearin;
		yyerror("invalid domain literal");
	}
;

dot_atom:					/* [CFWS] dot-atom-text [CFWS] */
	dot_atom_atext {
		YYDPRINTF(("dot_atom: returning plain: '%s'\n", $1));
		$$ = $1;
	}
	| comment dot_atom_atext {
		YYDPRINTF(("dot_atom: returning with leading comment: (%s) '%s'\n", $1, $2));
		$$ = $2;
	}
	| dot_atom_atext comment {
		YYDPRINTF(("dot_atom: returning with trailing comment: '%s' (%s)\n", $1, $2));
		$$ = $1;
	}
	| comment dot_atom_atext comment {
		YYDPRINTF(("dot_atom: returning with surrounding comments: (%s) '%s' (%s)\n", $1, $2, $3));
		$$ = $2;
	}
/*
	| error {
		yyclearin;
		yyerror("invalid dot_atom");
	}
*/
;

dot_atom_atext:					/* 1*atext *("." 1*atext) */
	atext {
		YYDPRINTF(("dot_atom_atext: returning atom: '%s'\n", $1));
		$$ = $1;
	}
	| dot_atom '.' atext {
		YYDPRINTF(("dot_atom_atext: returning another atext : '%s', for dot_atom: \n", $3, $1));
		$$ = concat($1, concat(safe_strdup("."), $3));
	}
;

word:						/* atom / quoted-string */
	atom {
		YYDPRINTF(("word: returning atom: '%s'\n", $1));
		$$ = $1;
	}
	| quoted_string {
		YYDPRINTF(("word: returning quoted_string: '%s'\n", $1));
		$$ = $1;
	}
;

atom:						/* [CFWS] 1*atext [CFWS] */
	atext {
		YYDPRINTF(("atom: returning plain: '%s'\n", $1));
		$$ = $1;
	}
	| comment atext {
		YYDPRINTF(("atom: returning with leading comment: (%s) '%s'\n", $1, $2));
		$$ = $2;
	}
	| atext comment {
		YYDPRINTF(("atom: returning with trailing comment: '%s' (%s)\n", $1, $2));
		$$ = $1;
	}
	| comment atext comment {
		YYDPRINTF(("atom: returning with surrounding comments: (%s) '%s' (%s)\n", $1, $2, $3));
		$$ = $2;
	}
/*
	| error {
		yyclearin;
		yyerror("invalid atom");
	}
*/
;

atext:
	TOK_ATEXT {
		YYDPRINTF(("atext: returning: '%s'\n", yytext));
		$$ = safe_strdup(yytext);
	}
;

quoted_string:					/* [CFWS] DQUOTE *([FWS] qcontent) [FWS] DQUOTE [CFWS] */
	'"' qcontent '"' {
		YYDPRINTF(("qstring: returning quoted string: \"%s\"\n", $2));
		$$ = concat(safe_strdup("\""), concat($2, safe_strdup("\"")));
	}
	| comment '"' qcontent '"' {
		YYDPRINTF(("qstring: returning quoted string with leading comment: (%s) \"%s\"\n", $1, $3));
		$$ = concat(safe_strdup("\""), concat($2, safe_strdup("\"")));
	}
	| '"' qcontent '"' comment {
		YYDPRINTF(("qstring: returning quoted string with trailing comment: \"%s\" (%s)\n", $2, $4));
		$$ = concat(safe_strdup("\""), concat($2, safe_strdup("\"")));
	}
	| comment '"' qcontent '"' comment {
		YYDPRINTF(("qstring: returning quoted string with surrounding comments: (%s) \"%s\" (%s)\n", $1, $3, $5));
		$$ = concat(safe_strdup("\""), concat($2, safe_strdup("\"")));
	}
	| '"' error '"' {
		yyclearin;
		yyerror("invalid quoted string");
	}
;


qcontent:					/* qtext / quoted-pair */
	qtext {	/* ignore the quoted-pair part, lex dealt with it */
		$$ = $1;
	}
;

qtext:
	TOK_QTEXT {
		YYDPRINTF(("qtext: returning: '%s'\n", yytext));
		$$ = safe_strdup(yytext);
	}
;


dtext:
	TOK_DTEXT {
		YYDPRINTF(("dtext: returning: '%s'\n", yytext));
		$$ = safe_strdup(yytext);
	}
;

comment:
	ctext {
		YYDPRINTF(("comment: returning: (%s)\n", $1));
		$$ = $1;
	}
	| ctext comment {
		YYDPRINTF(("comment: returning more: (%s) for (%s)\n", $1, $2));
		/* we need to build a list here so we can re-insert the parens */
		$$ = concat($1, $2);
	}
;

ctext:
	TOK_CTEXT {
		YYDPRINTF(("ctext: "));
		if (rfc2822_lex_ncomm > 1) {
			YYDPRINTF(("nested at level %d: ", rfc2822_lex_ncomm));
		}
		YYDPRINTF(("returning: '%s'\n", yytext));
		$$ = safe_strdup(yytext);
	}
;

obs_addr_list:					/* 1*([address] [CFWS] "," [CFWS]) [address] */
	address {
		YYDPRINTF(("obs_addr_list: returning address list with one: '%s'\n", $1));
		$$ = $1;
	}
	| address comment {
		YYDPRINTF(("obs_addr_list: returning address list with one: '%s', (%s)\n", $1, $2));
		$$ = $1;
	}
	| comment address {
		YYDPRINTF(("obs_addr_list: returning address list with one: (%s) '%s'\n", $1, $2));
		$$ = $2;
	}
	| obs_addr_list ',' address {
		YYDPRINTF(("obs_addr_list: returning another address, '%s', for this list: '%s'\n", $3, $1));
		$$ = concat($1, concatws(safe_strdup(","), $3));
	}
	| obs_addr_list comment ',' address {
		YYDPRINTF(("obs_addr_list: returning another address, '%s', for this list: '%s' (%s)\n", $4, $1, $2));
		$$ = concat($1, concatws(safe_strdup(","), $4));
	}
	| obs_addr_list ',' comment address {
		YYDPRINTF(("obs_addr_list: returning another address, (%s) '%s', for this list: '%s'\n", $3, $4, $1));
		$$ = concat($1, concatws(safe_strdup(","), $4));
	}
	| obs_addr_list comment ',' comment address {
		YYDPRINTF(("obs_addr_list: returning another address, (%s) '%s', for this list: '%s' (%s)\n", $4, $5, $1, $2));
		$$ = concat($1, concatws(safe_strdup(","), $5));
	}
/*
 * Note: the next special hacks avoid having to deal with null addresses in
 * some obvious places....
 */
	| obs_addr_list ',' {
		YYDPRINTF(("obs_addr_list: ignoring trailing comma on this list: '%s'\n", $1));
		$$ = $1;
	}
	| obs_addr_list ',' comment {
		YYDPRINTF(("obs_addr_list: ignoring trailing comma (%s) on this list: '%s'\n", $1, $3));
		$$ = $1;
	}
	| obs_addr_list comment ',' {
		YYDPRINTF(("obs_addr_list: ignoring trailing (%s) comma on this list: '%s'\n", $1, $2));
		$$ = $1;
	}
	| obs_addr_list comment ',' comment {
		YYDPRINTF(("obs_addr_list: ignoring trailing (%s) comma (%s) on this list: '%s'\n", $1, $2, $4));
		$$ = $1;
	}
	| ',' obs_addr_list {
		YYDPRINTF(("obs_addr_list: leading trailing comma on this list: '%s'\n", $2));
		$$ = $2;
	}
	| ',' comment obs_addr_list {
		YYDPRINTF(("obs_addr_list: leading trailing comma (%s) on this list: '%s'\n", $2, $3));
		$$ = $3;
	}
	| comment ',' obs_addr_list {
		YYDPRINTF(("obs_addr_list: leading trailing (%s) comma on this list: '%s'\n", $2, $3));
		$$ = $3;
	}
	| comment ',' comment obs_addr_list {
		YYDPRINTF(("obs_addr_list: leading trailing (%s) comma (%s) on this list: '%s'\n", $1, $3, $4));
		$$ = $4;
	}
/* XXX this juust adds a couple of reduce/reduce conflicts and so far as never been seen
	| error ',' {
		yyclearin;
		yyerror("invalid partial obsolete address list");
	}
*/
/*
	| error {
		yyclearin;
		yyerror("invalid obsolele address list");
	}
*/
;

obs_mbox_list:					/* 1*([mailbox] [CFWS] "," [CFWS]) [mailbox] */
	/* this empty list is very necessary else all kinds of weird stuff happens! */
	/* empty */ {
		YYDPRINTF(("obs_mbox_list: returning empty mailbox list\n"));
		$$ = NULL;
	}
	| mailbox {
		YYDPRINTF(("obs_mbox_list: returning mailbox list with one: '%s'\n", $1));
		$$ = $1;
	}
	| mailbox comment {
		YYDPRINTF(("obs_mbox_list: returning mailbox list with one: '%s' (%s)\n", $1, $2));
		$$ = $1;
	}
	| comment mailbox {
		YYDPRINTF(("obs_mbox_list: returning mailbox list with one: (%s) '%s'\n", $1, $2));
		$$ = $2;
	}
	| obs_mbox_list ',' mailbox {
		YYDPRINTF(("obs_mbox_list: returning another mailbox, '%s', for this list: '%s'\n", $3, $1));
		$$ = concat($1, concatws(safe_strdup(","), $3));
	}
	| obs_mbox_list comment ',' mailbox {
		YYDPRINTF(("obs_mbox_list: returning another mailbox, '%s', for this list: '%s' (%s)\n", $4, $1, $2));
		$$ = concat($1, concatws(safe_strdup(","), $4));
	}
	| obs_mbox_list ',' comment address {
		YYDPRINTF(("obs_mbox_list: returning another mailbox, (%s) '%s', for this list: '%s'\n", $3, $4, $1));
		$$ = concat($1, concatws(safe_strdup(","), $4));
	}
	| obs_mbox_list comment ',' comment mailbox {
		YYDPRINTF(("obs_addr_list: returning another mailbox, (%s) '%s', for this list: '%s' (%s)\n", $4, $5, $1, $2));
		$$ = concat($1, concatws(safe_strdup(","), $5));
	}
/*
 * Note: the next special hacks avoid having to deal with null addresses in
 * some obvious places....
 */
	| obs_mbox_list ',' {
		YYDPRINTF(("obs_mbox_list: ignoring trailing comma on this list: '%s'\n", $1));
		$$ = $1;
	}
	| obs_mbox_list ',' comment {
		YYDPRINTF(("obs_mbox_list: ignoring trailing comma (%s) on this list: '%s'\n", $1, $3));
		$$ = $1;
	}
	| obs_mbox_list comment ',' {
		YYDPRINTF(("obs_mbox_list: ignoring trailing (%s) comma on this list: '%s'\n", $1, $2));
		$$ = $1;
	}
	| obs_mbox_list comment ',' comment {
		YYDPRINTF(("obs_mbox_list: ignoring trailing (%s) comma (%s) on this list: '%s'\n", $1, $2, $4));
		$$ = $1;
	}
	| ',' obs_mbox_list {
		YYDPRINTF(("obs_mbox_list: leading trailing comma on this list: '%s'\n", $2));
		$$ = $2;
	}
	| ',' comment obs_mbox_list {
		YYDPRINTF(("obs_mbox_list: leading trailing comma (%s) on this list: '%s'\n", $2, $3));
		$$ = $3;
	}
	| comment ',' obs_mbox_list {
		YYDPRINTF(("obs_mbox_list: leading trailing (%s) comma on this list: '%s'\n", $2, $3));
		$$ = $3;
	}
	| comment ',' comment obs_mbox_list {
		YYDPRINTF(("obs_mbox_list: leading trailing (%s) comma (%s) on this list: '%s'\n", $1, $3, $4));
		$$ = $4;
	}
/* XXX this juust adds a couple of reduce/reduce conflicts and so far as never been seen
	| error ',' {
		yyclearin;
		yyerror("invalid mailbox in obsolete mailbox list");
	}
*/
/* XXX this juust adds a couple of reduce/reduce conflicts and so far as never been seen
	| error ';' {
		yyclearin;
		yyerror("invalid obsolete mailbox list");
	}
*/
/*
	| error {
		yyclearin;
		yyerror("invalid obsolete mailbox list");
	}
*/
;

obs_angle_addr:					/* [CFWS] "<" [obs-route] addr-spec ">" [CFWS] */
	'<' addr_spec_or_obs_route_and_addr_spec '>' {
		YYDPRINTF(("obs_angle_addr: returning addr_spec_or_obs_route_and_addr_spec: <%s>\n", $2));
		$$ = concat(safe_strdup("<"), concat($2, safe_strdup(">")));
	}
	| comment '<' addr_spec_or_obs_route_and_addr_spec '>' {
		YYDPRINTF(("obs_angle_addr: returning addr_spec_or_obs_route_and_addr_spec with leading comment: (%s) <%s>\n", $1, $3));
		$$ = concat(safe_strdup("<"), concat($3, safe_strdup(">")));
	}
	| '<' addr_spec_or_obs_route_and_addr_spec '>' comment {
		YYDPRINTF(("obs_angle_addr: returning addr_spec_or_obs_route_and_addr_spec with trailing comment: <%s> (%s\n", $2, $4));
		$$ = concat(safe_strdup("<"), concat($2, safe_strdup(">")));
	}
	| comment '<' addr_spec_or_obs_route_and_addr_spec '>' comment {
		YYDPRINTF(("obs_angle_addr: returning addr_spec_or_obs_route_and_addr_spec with surrounding comments: (%s) <%s> %s\n", $1, $3, $5));
		$$ = concat(safe_strdup("<"), concat($3, safe_strdup(">")));
	}
;

addr_spec_or_obs_route_and_addr_spec:
	addr_spec
	| obs_route_and_addr_spec
;

obs_route_and_addr_spec:
	obs_route addr_spec {
		YYDPRINTF(("obs_route_and_addr_spec: returning obs_route: '%s', addr_spec: '%s'\n", $1, $2));
		$$ = concat($1, $2);
	}
;

obs_route:					/* [CFWS] obs-domain-list ":" [CFWS] */
	obs_domain_list ':' {
		YYDPRINTF(("obs_route: returning with obs_domain_list '%s'\n", $1));
		$$ = concat($1, safe_strdup(":"));
	}
;

obs_domain_list:				/* "@" domain *(*(CFWS / "," ) [CFWS] "@" domain) */
	'@' domain {
		YYDPRINTF(("obs_domain_list: returning start of route: '%s'\n", $2));
		$$ = concat(safe_strdup("@"), $2);
	}
	| obs_domain_list ',' '@' domain {
		YYDPRINTF(("obs_domain_list: returning another domain hop '%s', for route: '%s',\n", $4, $1));
		$$ = concat($1, concat(safe_strdup(","), concat(safe_strdup("@"), $4)));
	}
	| obs_domain_list comment '@' domain {
		YYDPRINTF(("obs_domain_list: returning another domain hop '%s', for route: '%s',\n", $4, $1));
		$$ = concat($1, concatws(safe_strdup(","), concat(safe_strdup("@"), $4)));
	}
	| obs_domain_list ',' comment '@' domain {
		YYDPRINTF(("obs_domain_list: returning another domain hop '%s', for route: '%s',\n", $5, $1));
		$$ = concat($1, concatws(safe_strdup(","), concat(safe_strdup("@"), $5)));
	}
/*
	| obs_domain_list '@' comment domain {
		YYDPRINTF(("obs_domain_list: returning another domain hop '%s', for route: '%s',\n", $3, $1));
		$$ = concat($1, concatws(safe_strdup(","), concat(safe_strdup("@"), $3)));
	}
*/
/*
	| error {
		yyclearin;
		yyerror("invalid obsolete domain list");
	}
*/
;

obs_local_part:					/* word *("." word) */
	word {
		YYDPRINTF(("obs_local_part: returning word: '%s'\n", $1));
		$$ = $1;
	}
	| obs_local_part '.' word  {
		YYDPRINTF(("obs_local_part: returning another word '%s' for obs_local_part: '%s'\n", $3, $1));
		$$ = concat($1, concat(safe_strdup("."), $3));
	}
;

obs_domain:					/* atom *("." atom) */
	atom {
		YYDPRINTF(("obs_domain: returning atom: '%s'\n", $1));
		$$ = $1;
	}
	| obs_domain '.' atom {
		YYDPRINTF(("obs_domain: returning another atom '%s' for obs_domain: '%s'\n", $3, $1));
		$$ = concat($1, concat(safe_strdup("."), $3));
	}
;

obs_phrase:					/* word *(word / "." / CFWS) */
	word {
		YYDPRINTF(("obs_phrase: returning word: '%s'\n", $1));
		$$ = $1;
	}
	| word '.' {
		YYDPRINTF(("obs_phrase: returning word with trailing dot: '%s.'\n", $1));
		$$ = concat($1, safe_strdup("."));
	}
	| word comment {
		YYDPRINTF(("obs_phrase: returning word with trailing comment: '%s' (%s)\n", $1, $2));
		$$ = $1;
	}
/*
 * NOTE:  The next three are cheap yacc hacks that may extend the syntax
 * somewhat more loosely than is strictly necessary.
 */
	| obs_phrase word {
		YYDPRINTF(("obs_phrase: returning obs_phrase with trailing word: '%s' '%s'\n", $1, $2));
		$$ = concatws($1, $2);
	}
	| obs_phrase '.' {
		YYDPRINTF(("obs_phrase: returning obs_phrase with trailing dot: '%s.'\n", $1));
		$$ = concat($1, safe_strdup("."));
	}
	| obs_phrase comment {
		YYDPRINTF(("obs_phrase: returning obs_phrase with trailing comment: '%s' (%s)\n", $1, $2));
		$$ = $1;
	}
/*
	| error {
		yyclearin;
		yyerror("invalid obsolete phrase");
	}
*/
;

%%	/* End of the grammar */


/*
 * internal parser routines
 */

static char *
concat(s1, s2)
	char *s1;
	char *s2;
{
	char *new;

	asprintf(&new, "%s%s", s1, s2);
	free((void *) s1);
	free((void *) s2);

	return new;
}

static char *
concatws(s1, s2)
	char *s1;
	char *s2;
{
	char *new;

	asprintf(&new, "%s %s", s1, s2);
	free((void *) s1);
	free((void *) s2);

	return new;
}

static int
yyerror(msg)
	const char *msg;
{
	if (yydebug)
		printf("yyerror(): %s\n", msg);

	/* set the global library error indicator */
	rfc2822_errno = RFC2822_SYNTAX_ERROR;

	return 0;
}

/*
 *
 *	public routines
 *
 */

#include <stdio.h>
#include <assert.h>

/*
 * parse an rfc2822 address
 *
 * RETURN VALUE:
 *
 * One of the following codes will be returned:
 *
 * RETURNS BY PARAMETER VALUE:
 *
 * The parsed, canonical address (i.e. with all extra whitespace & comments
 * removed) in the location specified by the parameter "addrp".
 *
 * If the parser succeeds without error, the local and hostdomain parts of the
 * address will be stored in the location specified by the parameters
 * "mailboxp" and "domainp" respectively.  The "domainp" location may be set to
 * the NULL pointer if the address is given as a local address, e.g. "userid".
 *
 * If the address is syntactically incorrect, or if parsing failed due to an
 * error, the NULL pointer will be stored in all of these locations.
 *
 * If either the "address", "localpart" or "hostpart" parameters are
 * NULL, no result will be stored, what allows you to check an address
 * for syntax errors.
 *
 * NOTE: All strings returned in the parameters have been allocated by
 *  malloc(3) and should be freed by the caller using free(3).
 */

int
rfc2822_parse_buffer(inaddr, addrp)
	const char *inaddr;	/* String containing the input address. */
	char **addrp;		/* a pointer to the location to store the canonical address string. */
{
	int     rc;

	errno = 0;
	rfc2822_errno = RFC2822_OK;

	if (!inaddr || !*inaddr) {
		errno = EINVAL;
		return RFC2822_FATAL_ERROR;
	}

	/*
	 * set up the lexical analyser
	 */
	rfc2822_lex_bufp = safe_strdup(inaddr);
	rfc2822_lex_ncomm = 0;			/* in case we borked in a comment... */
	rfc2822_restart(rfc2822_in);		/* in case flex is in a borked state */
	rfc2822_lex_restart();

	/*
	 * call the yacc-generated parser entry point
	 */
	rc = rfc2822_parse();

	/*
	 * check for parser errors
	 */
	if (addrp) {
		/*
		 * if there's somewhere to put it then hand back the pointer to
		 * yyval, i.e. currently it's the string that was built up 
		 */
		*addrp = rfc2822_val;
	}
	if (rc != 0) {
		if (rfc2822_enomem) {
			errno = ENOMEM;
			rc = RFC2822_FATAL_ERROR;
		} else {
			rc = RFC2822_SYNTAX_ERROR;
		}
	}

	return rc;
}
