#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;
}
}
}
|