#include #include #include #include #include #include "mail.h" #define Datefmt "?WWW, ?MMM ?DD hh:mm:ss ?Z YYYY" typedef struct Fn Fn; struct Fn { char *name; void (*fn)(Mesg *, char **, int); }; void mesgclear(Mesg *m) { int i; for(i = 0; i < m->nparts; i++) mesgclear(m->parts[i]); free(m->name); free(m->from); free(m->to); free(m->cc); free(m->replyto); free(m->date); free(m->subject); free(m->type); free(m->disposition); free(m->messageid); free(m->filename); free(m->digest); free(m->mflags); free(m->fromcolon); } void mesgfree(Mesg *m) { if(m == nil) return; mesgclear(m); free(m); } static char* line(char *data, char **pp, int z) { char *p, *q; for(p=data; *p!='\0' && *p!='\n'; p++) ; if(*p == '\n') *pp = p+1; else *pp = p; if(z && p == data) return nil; q = emalloc(p-data + 1); memmove(q, data, p-data); return q; } static char* fc(Mesg *m, char *s) { char *r; if(s != nil && strlen(m->from) != 0){ r = smprint("%s <%s>", s, m->from); free(s); return r; } if(m->from != nil) return estrdup(m->from); if(s != nil) return s; return estrdup("??"); } Mesg* mesgload(char *name) { char *info, *p; int ninfo; Mesg *m; Tm tm; m = emalloc(sizeof(Mesg)); m->name = estrjoin(name, "/", nil); if((info = rslurp(m, "info", &ninfo)) == nil){ free(m->name); free(m); return nil; } p = info; m->from = line(p, &p, 0); m->to = line(p, &p, 0); m->cc = line(p, &p, 0); m->replyto = line(p, &p, 1); m->date = line(p, &p, 0); m->subject = line(p, &p, 0); m->type = line(p, &p, 1); m->disposition = line(p, &p, 1); m->filename = line(p, &p, 1); m->digest = line(p, &p, 1); /* m->bcc = */ free(line(p, &p, 1)); m->inreplyto = line(p, &p, 1); /* m->date = */ free(line(p, &p, 1)); /* m->sender = */ free(line(p, &p, 1)); m->messageid = line(p, &p, 0); /* m->lines = */ free(line(p, &p, 1)); /* m->size = */ free(line(p, &p, 1)); m->mflags = line(p, &p, 0); /* m->fileid = */ free(line(p, &p, 1)); m->fromcolon = fc(m, line(p, &p, 1)); free(info); m->flags = 0; if(strchr(m->mflags, 'd')) m->flags |= Fdel; if(strchr(m->mflags, 's')) m->flags |= Fseen; if(strchr(m->mflags, 'a')) m->flags |= Fresp; m->time = time(nil); if(tmparse(&tm, Datefmt, m->date, nil, nil) != nil) m->time = tmnorm(&tm); m->hash = 0; if(m->messageid != nil) m->hash = strhash(m->messageid); return m; } static Mesg* readparts(Mesg *r, Mesg *m) { char *dpath, *apath; int n, i, dfd; Mesg *a, *sub; Dir *d; if(m->body != nil) return m->body; dpath = estrjoin(mbox.path, m->name, nil); dfd = open(dpath, OREAD); free(dpath); if(dfd == -1) return m; n = dirreadall(dfd, &d); close(dfd); if(n == -1) sysfatal("%s read: %r", mbox.path); m->body = nil; for(i = 0; i < n; i++){ if(d[i].qid.type != QTDIR) continue; apath = estrjoin(m->name, d[i].name, nil); a = mesgload(apath); free(apath); if(a == nil) continue; if(strncmp(a->type, "multipart/", strlen("multipart/")) == 0){ sub = readparts(r, a); if(sub != a) m->body = sub; continue; } if(r->nparts >= r->xparts) r->parts = erealloc(r->parts, (2 + r->nparts*2)*sizeof(Mesg*)); r->parts[r->nparts++] = a; if(r->body == nil && strcmp(a->type, "text/plain") == 0) r->body = a; else if(r->body == nil && strcmp(a->type, "text/html") == 0) r->body = a; } free(d); if(m->body == nil) m->body = m; return m->body; } static void execfmt(void *pm) { Mesg *m; m = pm; rfork(RFFDG); dup(m->fd[1], 1); close(m->fd[0]); close(m->fd[1]); procexecl(m->sync, "/bin/htmlfmt", "htmlfmt", "-a", "-cutf-8", m->path, nil); } static int htmlfmt(Mesg *m, char *path) { if(pipe(m->fd) == -1) sysfatal("pipe: %r"); m->sync = chancreate(sizeof(ulong), 0); m->path = path; procrfork(execfmt, m, Stack, RFNOTEG); recvul(m->sync); chanfree(m->sync); close(m->fd[1]); return m->fd[0]; } static void copy(Biobuf *wfd, Biobuf *rfd) { char *buf; int n; buf = emalloc(Bufsz); while(1){ n = Bread(rfd, buf, Bufsz); if(n <= 0) break; if(Bwrite(wfd, buf, n) != n) break; } free(buf); } static int mesgshow(Mesg *m) { char *path, *home, *name, *suff; Biobuf *rfd, *wfd; Mesg *a; int i; if((wfd = bwinopen(m, "body", OWRITE)) == nil) return -1; if(m->parent != nil || m->nchild != 0) { Bprint(wfd, "Thread:"); if(m->parent && !(m->parent->state & Sdummy)) Bprint(wfd, " ↑ %s", m->parent->name); for(i = 0; i < m->nchild; i++) Bprint(wfd, " ↓ %s", m->child[i]->name); Bprint(wfd, "\n"); } Bprint(wfd, "From: %s\n", m->fromcolon); Bprint(wfd, "To: %s\n", m->to); Bprint(wfd, "Date: %s\n", m->date); Bprint(wfd, "Subject: %s\n\n", m->subject); rfd = mesgopenbody(m); if(rfd != nil){ copy(wfd, rfd); Bterm(rfd); } home = getenv("home"); if(m->nparts != 0) Bprint(wfd, "\n"); for(i = 0; i < m->nparts; i++){ a = m->parts[i]; name = a->name; if(strncmp(a->name, m->name, strlen(m->name)) == 0) name += strlen(m->name); if(a->disposition != nil && strcmp(a->disposition, "inline") == 0 && strcmp(a->type, "text/plain") == 0){ if(a == m || a == m->body) continue; Bprint(wfd, "\n===> %s (%s)\n", name, a->type); path = estrjoin(mbox.path, a->name, "body", nil); if((rfd = Bopen(path, OREAD)) != nil){ copy(wfd, rfd); Bterm(rfd); } free(path); continue; } Bprint(wfd, "\n===> %s (%s)\n", name, a->type); name = a->filename; if(name == nil) name = "body"; if((suff = strchr(name, '.')) == nil) suff = ""; Bprint(wfd, "\tcp %s%sbody%s %s/%s\n", mbox.path, a->name, suff, home, name); continue; } Bterm(wfd); free(home); fprint(m->ctl, "clean\n"); return 0; } static void reply(Mesg *m, char **f, int nf) { if(nf >= 1 && strcmp(f[0], "all") == 0) compose(m->replyto, m, 1); else compose(m->replyto, m, 0); } static void delmesg(Mesg *m, char **, int nf) { if(nf != 0){ fprint(2, "Delmesg: too many args\n"); return; } m->flags |= Ftodel; m->quitting = 1; mbredraw(m, 0, 0); } static void markone(Mesg *m, char **f, int nf) { int add, flg, fd; char *path; if(nf != 1){ fprint(2, "Mark: invalid arguments"); return; } if((flg = mesgflagparse(f[0], &add)) == -1){ fprint(2, "Mark: invalid flags %s\n", f[0]); return; } if(add) m->flags |= flg; else m->flags &= ~flg; if(strlen(f[0]) != 0){ path = estrjoin(mbox.path, "/", m->name, "/flags", nil); if((fd = open(path, OWRITE)) != -1){ fprint(fd, f[0]); close(fd); } free(path); } mbredraw(m, 0, 0); } static void mesgquit(Mesg *m, char **, int) { if(fprint(m->ctl, "del\n") == -1) return; m->quitting = 1; m->open = 0; } static Fn mesgfn[] = { {"Reply", reply}, {"Delmesg", delmesg}, {"Del", mesgquit}, {"Mark", markone}, #ifdef NOTYET {"Save", nil}, #endif {nil} }; static void mesgmain(void *mp) { char *path, *f[32]; Event ev; Mesg *m, **pm; Fn *p; int nf; m = mp; m->quitting = 0; m->qnext = mbox.openmesg; mbox.openmesg = m; path = estrjoin(mbox.path, m->name, nil); wininit(m, path); free(path); wintagwrite(m, "Reply all Delmesg Save "); mesgshow(m); fprint(m->ctl, "clean\n"); mbox.nopen++; while(!m->quitting){ if(winevent(m, &ev) != 'M') continue; if(strcmp(ev.text, "Del") == 0) break; switch(ev.type){ case 'l': case 'L': if(matchmesg(m, ev.text)) mesgopen(ev.text, nil); else winreturn(m, &ev); break; case 'x': case 'X': if((nf = tokenize(ev.text, f, nelem(f))) == 0) continue; for(p = mesgfn; p->fn != nil; p++){ if(strcmp(p->name, f[0]) == 0 && p->fn != nil){ p->fn(m, &f[1], nf - 1); break; } } if(p->fn == nil) winreturn(m, &ev); break; } } for(pm = &mbox.openmesg; *pm != nil; pm = &(*pm)->qnext) if(*pm == m){ *pm = m->qnext; break; } mbox.nopen--; m->qnext = nil; m->state &= ~Sopen; winclose(m); threadexits(nil); } int mesgflagparse(char *fstr, int *add) { int flg; flg = 0; *add = (*fstr == '+'); if(*fstr == '-' || *fstr == '+') fstr++; for(; *fstr; fstr++){ switch(*fstr){ case 'a': flg |= Fresp; break; case 's': flg |= Fseen; break; case 'D': flg |= Ftodel; memcpy(fstr, fstr +1, strlen(fstr)); break; default: fprint(2, "unknown flag %c", *fstr); return -1; } } return flg; } void mesgpath2name(char *buf, int nbuf, char *name) { char *p, *e; int n; n = strlen(mbox.path); if(strncmp(name, mbox.path, n) == 0) e = strecpy(buf, buf+nbuf-2, name + n); else e = strecpy(buf, buf+nbuf-2, name); if((p = strchr(buf, '/')) == nil) p = e; p[0] = '/'; p[1] = 0; } int mesgmatch(Mesg *m, char *name, char *digest) { if(!(m->state & Sdummy) && strcmp(m->name, name) == 0) return digest == nil || strcmp(m->digest, digest) == 0; return 0; } Mesg* mesglookup(char *name, char *digest) { char buf[32]; int i; mesgpath2name(buf, sizeof(buf), name); for(i = 0; i < mbox.nmesg; i++) if(mesgmatch(mbox.mesg[i], buf, digest)) return mbox.mesg[i]; return nil; } Mesg* mesgopen(char *name, char *digest) { Mesg *m; char *path; int fd; m = mesglookup(name, digest); if(m == nil || (m->state & Sopen)) return nil; assert(!(m->state & Sdummy)); m->state |= Sopen; if(!(m->flags & Fseen)){ m->flags |= Fseen; path = estrjoin(mbox.path, "/", m->name, "/flags", nil); if((fd = open(path, OWRITE)) != -1){ fprint(fd, "+s"); close(fd); } mbredraw(m, 0, 0); free(path); } threadcreate(mesgmain, m, Stack); return m; } Biobuf* mesgopenbody(Mesg *m) { char *path; int rfd; Mesg *b; b = readparts(m, m); path = estrjoin(mbox.path, b->name, "body", nil); if(strcmp(b->type, "text/html") == 0) rfd = htmlfmt(m, path); else rfd = open(path, OREAD); free(path); if(rfd == -1) return nil; return Bfdopen(rfd, OREAD); }