diff options
Diffstat (limited to 'sys/src/cmd/upas/imap4d/msg.c')
-rw-r--r-- | sys/src/cmd/upas/imap4d/msg.c | 1507 |
1 files changed, 1507 insertions, 0 deletions
diff --git a/sys/src/cmd/upas/imap4d/msg.c b/sys/src/cmd/upas/imap4d/msg.c new file mode 100644 index 000000000..fbd21d2ac --- /dev/null +++ b/sys/src/cmd/upas/imap4d/msg.c @@ -0,0 +1,1507 @@ +#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; +} + +/* + * stolen from upas/marshal; base64 encodes from one fd to another. + * + * the size of buf is very important to enc64. Anything other than + * a multiple of 3 will cause enc64 to output a termination sequence. + * To ensure that a full buf corresponds to a multiple of complete lines, + * we make buf a multiple of 3*18 since that's how many enc64 sticks on + * a single line. This avoids short lines in the output which is pleasing + * but not necessary. + */ +static int +enc64x18(char *out, int lim, uchar *in, int n) +{ + int m, mm, nn; + + nn = 0; + for(; n > 0; n -= m){ + m = 18 * 3; + if(m > n) + m = n; + mm = enc64(out, lim - nn, in, m); + in += m; + out += mm; + *out++ = '\r'; + *out++ = '\n'; + nn += mm + 2; + } + return nn; +} + +/* + * 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; + } + } +} |