#include "imap4d.h"

static  char    *headaddrspec(char*, char*);
static  Maddr   *headaddresses(void);
static  Maddr   *headaddress(void);
static  char    *headatom(char*);
static  int     headchar(int eat);
static  char    *headdomain(char*);
static  Maddr   *headmaddr(Maddr*);
static  char    *headphrase(char*, char*);
static  char    *headquoted(int start, int stop);
static  char    *headskipwhite(int);
static  void    headskip(void);
static  char    *headsubdomain(void);
static  char    *headtext(void);
static  void    headtoend(void);
static  char    *headword(void);
static  void    mimedescription(Header*);
static  void    mimedisposition(Header*);
static  void    mimeencoding(Header*);
static  void    mimeid(Header*);
static  void    mimelanguage(Header*);
//static        void    mimemd5(Header*);
static  void    mimetype(Header*);
static  int     msgbodysize(Msg*);
static  int     msgheader(Msg*, Header*, char*);

/*
* stop list for header fields
*/
static  char    *headfieldstop = ":";
static  char    *mimetokenstop = "()<>@,;:\\\"/[]?=";
static  char    *headatomstop = "()<>@,;:\\\".[]";
static  uchar   *headstr;
static  uchar   *lastwhite;

long
selectfields(char *dst, long n, char *hdr, Slist *fields, int matches)
{
       char *s;
       uchar *start;
       long m, nf;
       Slist *f;

       headstr = (uchar*)hdr;
       m = 0;
       for(;;){
               start = headstr;
               s = headatom(headfieldstop);
               if(s == nil)
                       break;
               headskip();
               for(f = fields; f != nil; f = f->next){
                       if(cistrcmp(s, f->s) == !matches){
                               nf = headstr - start;
                               if(m + nf > n)
                                       return 0;
                               memmove(&dst[m], start, nf);
                               m += nf;
                       }
               }
               free(s);
       }
       if(m + 3 > n)
               return 0;
       dst[m++] = '\r';
       dst[m++] = '\n';
       dst[m] = '\0';
       return m;
}

static Mimehdr*
mkmimehdr(char *s, char *t, Mimehdr *next)
{
       Mimehdr *mh;

       mh = MK(Mimehdr);
       mh->s = s;
       mh->t = t;
       mh->next = next;
       return mh;
}

static void
freemimehdr(Mimehdr *mh)
{
       Mimehdr *last;

       while(mh != nil){
               last = mh;
               mh = mh->next;
               free(last->s);
               free(last->t);
               free(last);
       }
}

static void
freeheader(Header *h)
{
       freemimehdr(h->type);
       freemimehdr(h->id);
       freemimehdr(h->description);
       freemimehdr(h->encoding);
//      freemimehdr(h->md5);
       freemimehdr(h->disposition);
       freemimehdr(h->language);
       free(h->buf);
}

static void
freemaddr(Maddr *a)
{
       Maddr *p;

       while(a != nil){
               p = a;
               a = a->next;
               free(p->personal);
               free(p->box);
               free(p->host);
               free(p);
       }
}

void
freemsg(Box *box, Msg *m)
{
       Msg *k, *last;

       if(box != nil)
               fstreedelete(box, m);
       free(m->ibuf);
       freemaddr(m->to);
       if(m->replyto != m->from)
               freemaddr(m->replyto);
       if(m->sender != m->from)
               freemaddr(m->sender);
       if(m->from != m->unixfrom)
               freemaddr(m->from);
       freemaddr(m->unixfrom);
       freemaddr(m->cc);
       freemaddr(m->bcc);
       free(m->unixdate);
       freeheader(&m->head);
       freeheader(&m->mime);
       for(k = m->kids; k != nil; ){
               last = k;
               k = k->next;
               freemsg(0, last);
       }
       free(m->fs);
       free(m);
}

uint
msgsize(Msg *m)
{
       return m->head.size + m->size;
}

char*
maddrstr(Maddr *a)
{
       char *host, *addr;

       host = a->host;
       if(host == nil)
               host = "";
       if(a->personal != nil)
               addr = smprint("%s <%s@%s>", a->personal, a->box, host);
       else
               addr = smprint("%s@%s", a->box, host);
       return addr;
}

int
msgfile(Msg *m, char *f)
{
       if(strlen(f) > Filelen)
               bye("internal error: msgfile name too long");
       strcpy(m->efs, f);
       return cdopen(m->fsdir, m->fs, OREAD);
}

int
msgismulti(Header *h)
{
       return h->type != nil && cistrcmp("multipart", h->type->s) == 0;
}

int
msgis822(Header *h)
{
       Mimehdr *t;

       t = h->type;
       return t != nil && cistrcmp("message", t->s) == 0 && cistrcmp("rfc822", t->t) == 0;
}

/*
* check if a message has been deleted by someone else
*/
void
msgdead(Msg *m)
{
       if(m->expunged)
               return;
       *m->efs = '\0';
       if(!cdexists(m->fsdir, m->fs))
               m->expunged = 1;
}

static long
msgreadfile(Msg *m, char *file, char **ss)
{
       char *s, buf[Bufsize];
       int fd;
       long n, nn;
       vlong length;
       Dir *d;

       fd = msgfile(m, file);
       if(fd < 0){
               msgdead(m);
               return -1;
       }

       n = read(fd, buf, Bufsize);
       if(n < Bufsize){
               close(fd);
               if(n < 0){
                       *ss = nil;
                       return -1;
               }
               s = emalloc(n + 1);
               memmove(s, buf, n);
               s[n] = '\0';
               *ss = s;
               return n;
       }

       d = dirfstat(fd);
       if(d == nil){
               close(fd);
               return -1;
       }
       length = d->length;
       free(d);
       nn = length;
       s = emalloc(nn + 1);
       memmove(s, buf, n);
       if(nn > n)
               nn = readn(fd, s + n, nn - n) + n;
       close(fd);
       if(nn != length){
               free(s);
               return -1;
       }
       s[nn] = '\0';
       *ss = s;
       return nn;
}

/*
* parse the address in the unix header
* last line of defence, so must return something
*/
static Maddr *
unixfrom(char *s)
{
       char *e, *t;
       Maddr *a;

       if(s == nil)
               return nil;
       headstr = (uchar*)s;
       t = emalloc(strlen(s) + 2);
       e = headaddrspec(t, nil);
       if(e == nil)
               a = nil;
       else{
               if(*e != '\0')
                       *e++ = '\0';
               else
                       e = site;
               a = MKZ(Maddr);
               a->box = estrdup(t);
               a->host = estrdup(e);
       }
       free(t);
       return a;
}

/*
* retrieve information from the unixheader file
*/
static int
msgunix(Msg *m, int top)
{
       char *s, *ss;
       Tm tm;

       if(m->unixdate != nil)
               return 1;
       if(!top){
bogus:
               m->unixdate = estrdup("");
               m->unixfrom = unixfrom(nil);
               return 1;
       }

       if(msgreadfile(m, "unixheader", &ss) < 0)
               goto bogus;
       s = ss;
       s = strchr(s, ' ');
       if(s == nil){
               free(ss);
               goto bogus;
       }
       s++;
       m->unixfrom = unixfrom(s);
       s = (char*)headstr;
       if(date2tm(&tm, s) == nil)
               s = m->info[Iunixdate];
       if(s == nil){
               free(ss);
               goto bogus;
       }
       m->unixdate = estrdup(s);
       free(ss);
       return 1;
}

/*
* make sure the message has valid associated info
* used for Isubject, Idigest, Iinreplyto, Imessageid.
*/
int
msginfo(Msg *m)
{
       char *s;
       int i;

       if(m->info[0] != nil)
               return 1;
       if(msgreadfile(m, "info", &m->ibuf) < 0)
               return 0;
       s = m->ibuf;
       for(i = 0; i < Imax; i++){
               m->info[i] = s;
               s = strchr(s, '\n');
               if(s == nil)
                       return 0;
               if(s == m->info[i])
                       m->info[i] = 0;
               *s++ = '\0';
       }
//      m->lines = strtoul(m->info[Ilines], 0, 0);
//      m->size = strtoull(m->info[Isize], 0, 0);
//      m->size += m->lines;                    /* BOTCH: this hack belongs elsewhere */
       return 1;
}

/*
* make sure the message has valid mime structure
* and sub-messages
*/
int
msgstruct(Msg *m, int top)
{
       char buf[12];
       int fd, ns, max;
       Msg *k, head, *last;

       if(m->kids != nil)
               return 1;
       if(m->expunged
       || !msginfo(m)
       || !msgheader(m, &m->mime, "mimeheader")){
               msgdead(m);
               return 0;
       }
       /* gack.  we need to get the header from the subpart here. */
       if(msgis822(&m->mime)){
               free(m->ibuf);
               m->info[0] = 0;
               m->efs = seprint(m->efs, m->efs + 5, "/1/");
               if(!msginfo(m)){
                       msgdead(m);
                       return 0;
               }
       }
       if(!msgunix(m, top)
       || !msgbodysize(m)
       || (top || msgis822(&m->mime) || msgismulti(&m->mime)) && !msgheader(m, &m->head, "rawheader")){
               msgdead(m);
               return 0;
       }

       /*
        * if a message has no kids, it has a kid which is just the body of the real message
        */
       if(!msgismulti(&m->head) && !msgismulti(&m->mime) && !msgis822(&m->head) && !msgis822(&m->mime)){
               k = MKZ(Msg);
               k->id = 1;
               k->fsdir = m->fsdir;
               k->parent = m->parent;
               ns = m->efs - m->fs;
               k->fs = emalloc(ns + (Filelen + 1));
               memmove(k->fs, m->fs, ns);
               k->efs = k->fs + ns;
               *k->efs = '\0';
               k->size = m->size;
               m->kids = k;
               return 1;
       }

       /*
        * read in all child messages messages
        */
       head.next = nil;
       last = &head;
       for(max = 1;; max++){
               snprint(buf, sizeof buf, "%d", max);
               fd = msgfile(m, buf);
               if(fd == -1)
                       break;
               close(fd);
               m->efs[0] = 0;          /* BOTCH! */

               k = MKZ(Msg);
               k->id = max;
               k->fsdir = m->fsdir;
               k->parent = m;
               ns = strlen(m->fs) + 2*(Filelen + 1);
               k->fs = emalloc(ns);
               k->efs = seprint(k->fs, k->fs + ns, "%s%d/", m->fs, max);
               k->size = ~0UL;
               k->lines = ~0UL;
               last->next = k;
               last = k;
       }

       m->kids = head.next;

       /*
        * if kids fail, just whack them
        */
       top = top && (msgis822(&m->head) || msgismulti(&m->head));
       for(k = m->kids; k != nil; k = k->next)
               if(!msgstruct(k, top)){
                       debuglog("kid fail %p %s", k, k->fs);
                       for(k = m->kids; k != nil; ){
                               last = k;
                               k = k->next;
                               freemsg(0, last);
                       }
                       m->kids = nil;
                       break;
               }
       return 1;
}

/*
* read in the message body to count \n without a preceding \r
*/
static int
msgbodysize(Msg *m)
{
       char buf[Bufsize + 2], *s, *se;
       uint length, size, lines, needr;
       int n, fd, c;
       Dir *d;

       if(m->lines != ~0UL)
               return 1;
       fd = msgfile(m, "rawbody");
       if(fd < 0)
               return 0;
       d = dirfstat(fd);
       if(d == nil){
               close(fd);
               return 0;
       }
       length = d->length;
       free(d);

       size = 0;
       lines = 0;
       needr = 0;
       buf[0] = ' ';
       for(;;){
               n = read(fd, &buf[1], Bufsize);
               if(n <= 0)
                       break;
               size += n;
               se = &buf[n + 1];
               for(s = &buf[1]; s < se; s++){
                       c = *s;
                       if(c == '\0')
                               *s = ' ';
                       if(c != '\n')
                               continue;
                       if(s[-1] != '\r')
                               needr++;
                       lines++;
               }
               buf[0] = buf[n];
       }
       if(size != length)
               bye("bad length reading rawbody %d != %d; n %d %s", size, length, n, m->fs);
       size += needr;
       m->size = size;
       m->lines = lines;
       close(fd);
       return 1;
}

/*
* prepend hdrname: val to the cached header
*/
static void
msgaddhead(Msg *m, char *hdrname, char *val)
{
       char *s;
       long size, n;

       n = strlen(hdrname) + strlen(val) + 4;
       size = m->head.size + n;
       s = emalloc(size + 1);
       snprint(s, size + 1, "%s: %s\r\n%s", hdrname, val, m->head.buf);
       free(m->head.buf);
       m->head.buf = s;
       m->head.size = size;
       m->head.lines++;
}

static void
msgadddate(Msg *m)
{
       char buf[64];
       Tm tm;

       /* don't bother if we don't have a date */
       if(m->info[Idate] == 0)
               return;

       date2tm(&tm, m->info[Idate]);
       snprint(buf, sizeof buf, "%δ", &tm);
       msgaddhead(m, "Date", buf);
}

/*
* read in the entire header,
* and parse out any existing mime headers
*/
static int
msgheader(Msg *m, Header *h, char *file)
{
       char *s, *ss, *t, *te;
       int dated, c;
       long ns;
       uint lines, n, nn;

       if(h->buf != nil)
               return 1;

       ns = msgreadfile(m, file, &ss);
       if(ns < 0)
               return 0;
       s = ss;
       n = ns;

       /*
        * count lines ending with \n and \r\n
        * add an extra line at the end, since upas/fs headers
        * don't have a terminating \r\n
        */
       lines = 1;
       te = s + ns;
       for(t = s; t < te; t++){
               c = *t;
               if(c == '\0')
                       *t = ' ';
               if(c != '\n')
                       continue;
               if(t == s || t[-1] != '\r')
                       n++;
               lines++;
       }
       if(t > s && t[-1] != '\n'){
               if(t[-1] != '\r')
                       n++;
               n++;
       }
       if(n > 0)
               n += 2;
       h->buf = emalloc(n + 1);
       h->size = n;
       h->lines = lines;

       /*
        * make sure all headers end in \r\n
        */
       nn = 0;
       for(t = s; t < te; t++){
               c = *t;
               if(c == '\n'){
                       if(!nn || h->buf[nn - 1] != '\r')
                               h->buf[nn++] = '\r';
                       lines++;
               }
               h->buf[nn++] = c;
       }
       if(nn && h->buf[nn-1] != '\n'){
               if(h->buf[nn-1] != '\r')
                       h->buf[nn++] = '\r';
               h->buf[nn++] = '\n';
       }
       if(nn > 0){
               h->buf[nn++] = '\r';
               h->buf[nn++] = '\n';
       }
       h->buf[nn] = '\0';
       if(nn != n)
               bye("misconverted header %d %d", nn, n);
       free(s);

       /*
        * and parse some mime headers
        */
       headstr = (uchar*)h->buf;
       dated = 0;
       while(s = headatom(headfieldstop)){
               if(cistrcmp(s, "content-type") == 0)
                       mimetype(h);
               else if(cistrcmp(s, "content-transfer-encoding") == 0)
                       mimeencoding(h);
               else if(cistrcmp(s, "content-id") == 0)
                       mimeid(h);
               else if(cistrcmp(s, "content-description") == 0)
                       mimedescription(h);
               else if(cistrcmp(s, "content-disposition") == 0)
                       mimedisposition(h);
//              else if(cistrcmp(s, "content-md5") == 0)
//                      mimemd5(h);
               else if(cistrcmp(s, "content-language") == 0)
                       mimelanguage(h);
               else if(h == &m->head){
                       if(cistrcmp(s, "from") == 0)
                               m->from = headmaddr(m->from);
                       else if(cistrcmp(s, "to") == 0)
                               m->to = headmaddr(m->to);
                       else if(cistrcmp(s, "reply-to") == 0)
                               m->replyto = headmaddr(m->replyto);
                       else if(cistrcmp(s, "sender") == 0)
                               m->sender = headmaddr(m->sender);
                       else if(cistrcmp(s, "cc") == 0)
                               m->cc = headmaddr(m->cc);
                       else if(cistrcmp(s, "bcc") == 0)
                               m->bcc = headmaddr(m->bcc);
                       else if(cistrcmp(s, "date") == 0)
                               dated = 1;
               }
               headskip();
               free(s);
       }

       if(h == &m->head){
               if(m->from == nil){
                       m->from = m->unixfrom;
                       if(m->from != nil){
                               s = maddrstr(m->from);
                               msgaddhead(m, "From", s);
                               free(s);
                       }
               }
               if(m->sender == nil)
                       m->sender = m->from;
               if(m->replyto == nil)
                       m->replyto = m->from;

               if(m->info[Idate] == 0)
                       m->info[Idate] = m->unixdate;
               if(!dated && m->from != nil)
                       msgadddate(m);
       }
       return 1;
}

/*
* q is a quoted string.  remove enclosing " and and \ escapes
*/
static void
stripquotes(char *q)
{
       char *s;
       int c;

       if(q == nil)
               return;
       s = q++;
       while(c = *q++){
               if(c == '\\'){
                       c = *q++;
                       if(!c)
                               return;
               }
               *s++ = c;
       }
       s[-1] = '\0';
}

/*
* parser for rfc822 & mime header fields
*/

/*
* params       :
*              | params ';' token '=' token
*              | params ';' token '=' quoted-str
*/
static Mimehdr*
mimeparams(void)
{
       char *s, *t;
       Mimehdr head, *last;

       head.next = nil;
       last = &head;
       for(;;){
               if(headchar(1) != ';')
                       break;
               s = headatom(mimetokenstop);
               if(s == nil || headchar(1) != '='){
                       free(s);
                       break;
               }
               if(headchar(0) == '"'){
                       t = headquoted('"', '"');
                       stripquotes(t);
               }else
                       t = headatom(mimetokenstop);
               if(t == nil){
                       free(s);
                       break;
               }
               last->next = mkmimehdr(s, t, nil);
               last = last->next;
       }
       return head.next;
}

/*
* type         : 'content-type' ':' token '/' token params
*/
static void
mimetype(Header *h)
{
       char *s, *t;

       if(headchar(1) != ':')
               return;
       s = headatom(mimetokenstop);
       if(s == nil || headchar(1) != '/'){
               free(s);
               return;
       }
       t = headatom(mimetokenstop);
       if(t == nil){
               free(s);
               return;
       }
       h->type = mkmimehdr(s, t, mimeparams());
}

/*
* encoding     : 'content-transfer-encoding' ':' token
*/
static void
mimeencoding(Header *h)
{
       char *s;

       if(headchar(1) != ':')
               return;
       s = headatom(mimetokenstop);
       if(s == nil)
               return;
       h->encoding = mkmimehdr(s, nil, nil);
}

/*
* mailaddr     : ':' addresses
*/
static Maddr*
headmaddr(Maddr *old)
{
       Maddr *a;

       if(headchar(1) != ':')
               return old;

       if(headchar(0) == '\n')
               return old;

       a = headaddresses();
       if(a == nil)
               return old;

       freemaddr(old);
       return a;
}

/*
* addresses    : address | addresses ',' address
*/
static Maddr*
headaddresses(void)
{
       Maddr *addr, *tail, *a;

       addr = headaddress();
       if(addr == nil)
               return nil;
       tail = addr;
       while(headchar(0) == ','){
               headchar(1);
               a = headaddress();
               if(a == nil){
                       freemaddr(addr);
                       return nil;
               }
               tail->next = a;
               tail = a;
       }
       return addr;
}

/*
* address      : mailbox | group
* group        : phrase ':' mboxes ';' | phrase ':' ';'
* mailbox      : addr-spec
*              | optphrase '<' addr-spec '>'
*              | optphrase '<' route ':' addr-spec '>'
* optphrase    : | phrase
* route        : '@' domain
*              | route ',' '@' domain
* personal names are the phrase before '<',
* or a comment before or after a simple addr-spec
*/
static Maddr*
headaddress(void)
{
       char *s, *e, *w, *personal;
       uchar *hs;
       int c;
       Maddr *addr;

       s = emalloc(strlen((char*)headstr) + 2);
       e = s;
       personal = headskipwhite(1);
       c = headchar(0);
       if(c == '<')
               w = nil;
       else{
               w = headword();
               c = headchar(0);
       }
       if(c == '.' || c == '@' || c == ',' || c == '\n' || c == '\0'){
               lastwhite = headstr;
               e = headaddrspec(s, w);
               if(personal == nil){
                       hs = headstr;
                       headstr = lastwhite;
                       personal = headskipwhite(1);
                       headstr = hs;
               }
       }else{
               if(c != '<' || w != nil){
                       free(personal);
                       if(!headphrase(e, w)){
                               free(s);
                               return nil;
                       }

                       /*
                        * ignore addresses with groups,
                        * so the only thing left if <
                        */
                       c = headchar(1);
                       if(c != '<'){
                               free(s);
                               return nil;
                       }
                       personal = estrdup(s);
               }else
                       headchar(1);

               /*
                * after this point, we need to free personal before returning.
                * set e to nil to everything afterwards fails.
                *
                * ignore routes, they are useless, and heavily discouraged in rfc1123.
                * imap4 reports them up to, but not including, the terminating :
                */
               e = s;
               c = headchar(0);
               if(c == '@'){
                       for(;;){
                               c = headchar(1);
                               if(c != '@'){
                                       e = nil;
                                       break;
                               }
                               headdomain(e);
                               c = headchar(1);
                               if(c != ','){
                                       e = s;
                                       break;
                               }
                       }
                       if(c != ':')
                               e = nil;
               }

               if(e != nil)
                       e = headaddrspec(s, nil);
               if(headchar(1) != '>')
                       e = nil;
       }

       /*
        * e points to @host, or nil if an error occured
        */
       if(e == nil){
               free(personal);
               addr = nil;
       }else{
               if(*e != '\0')
                       *e++ = '\0';
               else
                       e = site;
               addr = MKZ(Maddr);

               addr->personal = personal;
               addr->box = estrdup(s);
               addr->host = estrdup(e);
       }
       free(s);
       return addr;
}

/*
* phrase       : word
*              | phrase word
* w is the optional initial word of the phrase
* returns the end of the phrase, or nil if a failure occured
*/
static char*
headphrase(char *e, char *w)
{
       int c;

       for(;;){
               if(w == nil){
                       w = headword();
                       if(w == nil)
                               return nil;
               }
               if(w[0] == '"')
                       stripquotes(w);
               strcpy(e, w);
               free(w);
               w = nil;
               e = strchr(e, '\0');
               c = headchar(0);
               if(c <= ' ' || strchr(headatomstop, c) != nil && c != '"')
                       break;
               *e++ = ' ';
               *e = '\0';
       }
       return e;
}

/*
* find the ! in domain!rest, where domain must have at least
* one internal '.'
*/
static char*
dombang(char *s)
{
       int dot, c;

       dot = 0;
       for(; c = *s; s++){
               if(c == '!'){
                       if(!dot || dot == 1 && s[-1] == '.' || s[1] == '\0')
                               return nil;
                       return s;
               }
               if(c == '"')
                       break;
               if(c == '.')
                       dot++;
       }
       return nil;
}

/*
* addr-spec    : local-part '@' domain
*              | local-part                    extension to allow ! and local names
* local-part   : word
*              | local-part '.' word
*
* if no '@' is present, rewrite d!e!f!u as @d,@e:u@f,
* where d, e, f are valid domain components.
* the @d,@e: is ignored, since routes are ignored.
* perhaps they should be rewritten as e!f!u@d, but that is inconsistent with upas.
*
* returns a pointer to '@', the end if none, or nil if there was an error
*/
static char*
headaddrspec(char *e, char *w)
{
       char *s, *at, *b, *bang, *dom;
       int c;

       s = e;
       for(;;){
               if(w == nil){
                       w = headword();
                       if(w == nil)
                               return nil;
               }
               strcpy(e, w);
               free(w);
               w = nil;
               e = strchr(e, '\0');
               lastwhite = headstr;
               c = headchar(0);
               if(c != '.')
                       break;
               headchar(1);
               *e++ = '.';
               *e = '\0';
       }

       if(c != '@'){
               /*
                * extenstion: allow name without domain
                * check for domain!xxx
                */
               bang = dombang(s);
               if(bang == nil)
                       return e;

               /*
                * if dom1!dom2!xxx, ignore dom1!
                */
               dom = s;
               for(; b = dombang(bang + 1); bang = b)
                       dom = bang + 1;

               /*
                * convert dom!mbox into mbox@dom
                */
               *bang = '@';
               strrev(dom, bang);
               strrev(bang + 1, e);
               strrev(dom, e);
               bang = &dom[e - bang - 1];
               if(dom > s){
                       bang -= dom - s;
                       for(e = s; *e = *dom; e++)
                               dom++;
               }

               /*
                * eliminate a trailing '.'
                */
               if(e[-1] == '.')
                       e[-1] = '\0';
               return bang;
       }
       headchar(1);

       at = e;
       *e++ = '@';
       *e = '\0';
       if(!headdomain(e))
               return nil;
       return at;
}

/*
* domain       : sub-domain
*              | domain '.' sub-domain
* returns the end of the domain, or nil if a failure occured
*/
static char*
headdomain(char *e)
{
       char *w;

       for(;;){
               w = headsubdomain();
               if(w == nil)
                       return nil;
               strcpy(e, w);
               free(w);
               e = strchr(e, '\0');
               lastwhite = headstr;
               if(headchar(0) != '.')
                       break;
               headchar(1);
               *e++ = '.';
               *e = '\0';
       }
       return e;
}

/*
* id           : 'content-id' ':' msg-id
* msg-id       : '<' addr-spec '>'
*/
static void
mimeid(Header *h)
{
       char *s, *e, *w;

       if(headchar(1) != ':')
               return;
       if(headchar(1) != '<')
               return;

       s = emalloc(strlen((char*)headstr) + 3);
       e = s;
       *e++ = '<';
       e = headaddrspec(e, nil);
       if(e == nil || headchar(1) != '>'){
               free(s);
               return;
       }
       e = strchr(e, '\0');
       *e++ = '>';
       e[0] = '\0';
       w = strdup(s);
       free(s);
       h->id = mkmimehdr(w, nil, nil);
}

/*
* description  : 'content-description' ':' *text
*/
static void
mimedescription(Header *h)
{
       if(headchar(1) != ':')
               return;
       headskipwhite(0);
       h->description = mkmimehdr(headtext(), nil, nil);
}

/*
* disposition  : 'content-disposition' ':' token params
*/
static void
mimedisposition(Header *h)
{
       char *s;

       if(headchar(1) != ':')
               return;
       s = headatom(mimetokenstop);
       if(s == nil)
               return;
       h->disposition = mkmimehdr(s, nil, mimeparams());
}

/*
* md5          : 'content-md5' ':' token
*/
//static void
//mimemd5(Header *h)
//{
//      char *s;
//
//      if(headchar(1) != ':')
//              return;
//      s = headatom(mimetokenstop);
//      if(s == nil)
//              return;
//      h->md5 = mkmimehdr(s, nil, nil);
//}

/*
* language     : 'content-language' ':' langs
* langs        : token
*              | langs commas token
* commas       : ','
*              | commas ','
*/
static void
mimelanguage(Header *h)
{
       char *s;
       Mimehdr head, *last;

       head.next = nil;
       last = &head;
       for(;;){
               s = headatom(mimetokenstop);
               if(s == nil)
                       break;
               last->next = mkmimehdr(s, nil, nil);
               last = last->next;
               while(headchar(0) != ',')
                       headchar(1);
       }
       h->language = head.next;
}

/*
* token        : 1*<char 33-255, except "()<>@,;:\\\"/[]?=" aka mimetokenstop>
* atom         : 1*<chars 33-255, except "()<>@,;:\\\".[]" aka headatomstop>
* note this allows 8 bit characters, which occur in utf.
*/
static char*
headatom(char *disallowed)
{
       char *s;
       int c, ns, as;

       headskipwhite(0);

       s = emalloc(Stralloc);
       as = Stralloc;
       ns = 0;
       for(;;){
               c = *headstr++;
               if(c <= ' ' || strchr(disallowed, c) != nil){
                       headstr--;
                       break;
               }
               s[ns++] = c;
               if(ns >= as){
                       as += Stralloc;
                       s = erealloc(s, as);
               }
       }
       if(ns == 0){
               free(s);
               return 0;
       }
       s[ns] = '\0';
       return s;
}

/*
* sub-domain   : atom | domain-lit
*/
static char *
headsubdomain(void)
{
       if(headchar(0) == '[')
               return headquoted('[', ']');
       return headatom(headatomstop);
}

/*
* word : atom | quoted-str
*/
static char *
headword(void)
{
       if(headchar(0) == '"')
               return headquoted('"', '"');
       return headatom(headatomstop);
}

/*
* quoted-str   : '"' *(any char but '"\\\r', or '\' any char, or linear-white-space) '"'
* domain-lit   : '[' *(any char but '[]\\\r', or '\' any char, or linear-white-space) ']'
*/
static char *
headquoted(int start, int stop)
{
       char *s;
       int c, ns, as;

       if(headchar(1) != start)
               return nil;
       s = emalloc(Stralloc);
       as = Stralloc;
       ns = 0;
       s[ns++] = start;
       for(;;){
               c = *headstr;
               if(c == stop){
                       headstr++;
                       break;
               }
               if(c == '\0'){
                       free(s);
                       return nil;
               }
               if(c == '\r'){
                       headstr++;
                       continue;
               }
               if(c == '\n'){
                       headstr++;
                       while(*headstr == ' ' || *headstr == '\t' || *headstr == '\r' || *headstr == '\n')
                               headstr++;
                       c = ' ';
               }else if(c == '\\'){
                       headstr++;
                       s[ns++] = c;
                       c = *headstr;
                       if(c == '\0'){
                               free(s);
                               return nil;
                       }
                       headstr++;
               }else
                       headstr++;
               s[ns++] = c;
               if(ns + 1 >= as){       /* leave room for \c or "0 */
                       as += Stralloc;
                       s = erealloc(s, as);
               }
       }
       s[ns++] = stop;
       s[ns] = '\0';
       return s;
}

/*
* headtext     : contents of rest of header line
*/
static char *
headtext(void)
{
       uchar *v;
       char *s;

       v = headstr;
       headtoend();
       s = emalloc(headstr - v + 1);
       memmove(s, v, headstr - v);
       s[headstr - v] = '\0';
       return s;
}

/*
* white space is ' ' '\t' or nested comments.
* skip white space.
* if com and a comment is seen,
* return it's contents and stop processing white space.
*/
static char*
headskipwhite(int com)
{
       char *s;
       int c, incom, as, ns;

       s = nil;
       as = Stralloc;
       ns = 0;
       if(com)
               s = emalloc(Stralloc);
       incom = 0;
       for(; c = *headstr; headstr++){
               switch(c){
               case ' ':
               case '\t':
               case '\r':
                       c = ' ';
                       break;
               case '\n':
                       c = headstr[1];
                       if(c != ' ' && c != '\t')
                               goto done;
                       c = ' ';
                       break;
               case '\\':
                       if(com && incom)
                               s[ns++] = c;
                       c = headstr[1];
                       if(c == '\0')
                               goto done;
                       headstr++;
                       break;
               case '(':
                       incom++;
                       if(incom == 1)
                               continue;
                       break;
               case ')':
                       incom--;
                       if(com && !incom){
                               s[ns] = '\0';
                               return s;
                       }
                       break;
               default:
                       if(!incom)
                               goto done;
                       break;
               }
               if(com && incom && (c != ' ' || ns > 0 && s[ns-1] != ' ')){
                       s[ns++] = c;
                       if(ns + 1 >= as){       /* leave room for \c or 0 */
                               as += Stralloc;
                               s = erealloc(s, as);
                       }
               }
       }
done:
       free(s);
       return nil;
}

/*
* return the next non-white character
*/
static int
headchar(int eat)
{
       int c;

       headskipwhite(0);
       c = *headstr;
       if(eat && c != '\0' && c != '\n')
               headstr++;
       return c;
}

static void
headtoend(void)
{
       uchar *s;
       int c;

       for(;;){
               s = headstr;
               c = *s++;
               while(c == '\r')
                       c = *s++;
               if(c == '\n'){
                       c = *s++;
                       if(c != ' ' && c != '\t')
                               return;
               }
               if(c == '\0')
                       return;
               headstr = s;
       }
}

static void
headskip(void)
{
       int c;

       while(c = *headstr){
               headstr++;
               if(c == '\n'){
                       c = *headstr;
                       if(c == ' ' || c == '\t')
                               continue;
                       return;
               }
       }
}