From bee6271ae1c4f50180f52f6dc11ce23337467722 Mon Sep 17 00:00:00 2001 From: Ori Bernstein Date: Fri, 29 Jan 2021 17:34:47 -0800 Subject: [PATCH] Mail: rewrite. Acme mail made it hard to do threading, so I wrote a new one. --- sys/man/1/acmemail | 231 +++++ sys/src/cmd/upas/Mail/comp.c | 290 ++++++ sys/src/cmd/upas/Mail/dat.h | 171 --- sys/src/cmd/upas/Mail/html.c | 74 -- sys/src/cmd/upas/Mail/mail.c | 644 ------------ sys/src/cmd/upas/Mail/mail.h | 195 ++++ sys/src/cmd/upas/Mail/mbox.c | 1056 +++++++++++++++++++ sys/src/cmd/upas/Mail/mesg.c | 1828 +++++++++------------------------ sys/src/cmd/upas/Mail/mkfile | 21 +- sys/src/cmd/upas/Mail/reply.c | 578 ----------- sys/src/cmd/upas/Mail/util.c | 203 ++-- sys/src/cmd/upas/Mail/win.c | 496 ++++----- 12 files changed, 2585 insertions(+), 3202 deletions(-) create mode 100644 sys/man/1/acmemail create mode 100644 sys/src/cmd/upas/Mail/comp.c delete mode 100644 sys/src/cmd/upas/Mail/dat.h delete mode 100644 sys/src/cmd/upas/Mail/html.c delete mode 100644 sys/src/cmd/upas/Mail/mail.c create mode 100644 sys/src/cmd/upas/Mail/mail.h create mode 100644 sys/src/cmd/upas/Mail/mbox.c delete mode 100644 sys/src/cmd/upas/Mail/reply.c diff --git a/sys/man/1/acmemail b/sys/man/1/acmemail new file mode 100644 index 000000000..28c140a0f --- /dev/null +++ b/sys/man/1/acmemail @@ -0,0 +1,231 @@ +.TH NAIL 1 +.SH NAME +Nail \- view mail in acme + +.SH SYNOPSIS + +Nail +[ +.B -OsT +] +[ +.B -m +.I maildir +] +[ +.B -f +.I format +] +[ +.B -o +.I outbox +] + +.SH DESCRIPTION +.PP +.B Nail +edits a mailbox in an +.IR acme (1) +environment. +The default mailbox is /mail/fs/mbox. +Nail shows 3 views: +The list view, the message view, and the composition view. + +.PP +At startup, +.B Nail +takes the following options: +.PD 0 +.TP +.B -T +Disable threading +.TP +.B -O +Disable writing to outbox +.PD 0 +.TP +.BI -s +Accept sendmail plumb messages. By default, only +the Nail instance viewing /mail/fs/mbox will +accept plumb messages. +.PD 0 +.TP +.BI -m " maildir +Open the maildir +.I maildir +instead of the default +.PD 0 +.TP +.BI -f " format +Define the format of individual messages in the list view +(see "Format strings" below). +.TP +.BI -o " outbox +Save a copy of outgoing messages to the mailbox +.IR outbox , +instead of discarding them after they're enqueued. + +.PP +Nail presents and acme interface for a upas/fs mailbox. +When started, a mailbox, by default +.IR /mail/fs/mbox , +is presented. +In the message list, the tag bar commands typically affect +the selected messsage. +In the message and composition views, they typically apply +to the current message. + +.PP +The following text commands are recognized by the message +list: + +.TP +.B Put +Flush pending changes back to +.IR upasfs (4). +.PD 0 +.TP +.B Delmesg, Undelmesg +Flags a message for deletion on the next +.I Put +invocation. +.PD 0 +.TP +.B Next +Select the next unread message in the mailbox. +.PD 0 +.TP +.B Mark [±flags] +Add or remove message flags. The flags recognized +are listed in +.IR upasfs (4) +.PD 0 +.TP +.B Redraw +Redraws the contents of the mailbox. + +.PP +The following text commands are recognized by the message +view: + +.TP +.B Reply [all] +Replies to a message, quoting it. +If all is specified, reply to all addresses on the message. +.PD 0 +.TP +.B Delmesg, Undelmesg +As with the message view, but applied to the open message. +.PD 0 +.TP +.B Mark +As with the message view, but applied to the open message. + +.PP +The following text commands are recognized by the composition +window: + +.TP +.B Post +Sends the message currently being composed. + +.SS Format strings +The formatting of messages in the list view is controlled by the +format string defined through the +.I -f +flag. +The format string is composed of multiple directives: plain +characters, which are displayed unchanged; indentation directives, +which allows spacing based on thread depth; and messages directives, +which display message fields. +.PP +Directives have the following format: +.IP +.B "% [flags] [width] verb +.PP +.IR width +limits the number of characters displayed. +If width is negative, text is aligned right instead of left. +.PP +The supported flags are: +.PD 0 +.TP +.B > +Insert indentation into the start of the field. This +does not increase the width of the field. + +.PP +Messages directives are: +.PD 0 +.TP +.B s +Subject +.PD 0 +.TP +.B f +From field +.PD 0 +.TP +.B F +From field including sender's name +.PD 0 +.TP +.B t +To field +.PD 0 +.TP +.B c +Cc field +.PD 0 +.TP +.B r +Reply-to field + +.PP +Indentation directives are: +.PD 0 +.TP +.B i +Adds spacing depending on message depth in thread but limited to a single level. +If +.IR width +is not specified, adds a tabulation otherwise +.IR width +specifies the number of spaces to display. +.PD 0 +.TP +.B I +Similar to +.IR i +but not limited to a single level. + +.PP +Two special directives are also available: +.PD 0 +.TP +.B "[...] +Text within the brackets is displayed if the message is not the toplevel message of a thread. +.PD 0 +.TP +.B "{...} +Text within the braces is used as the format string for tmfmt(2). + +.PP +The default format string is "%>48s\\t<%f>" + +.SH FILES + +.SH "SEE ALSO" +.IR mail (1), +.IR aliasmail (8), +.IR filter (1), +.IR marshal (1), +.IR mlmgr (1), +.IR nedmail (1), +.IR upasfs (4), +.IR smtp (8), +.IR faces (1), +.IR rewrite (6) + +.SH BUGS +Probably. diff --git a/sys/src/cmd/upas/Mail/comp.c b/sys/src/cmd/upas/Mail/comp.c new file mode 100644 index 000000000..d4e496928 --- /dev/null +++ b/sys/src/cmd/upas/Mail/comp.c @@ -0,0 +1,290 @@ +#include +#include +#include +#include +#include + +#include "mail.h" + +typedef struct Fn Fn; + +struct Fn { + char *name; + void (*fn)(Comp *, char **, int); +}; + +void +execmarshal(void *p) +{ + Comp *c; + char *av[8]; + int na; + + c = p; + rfork(RFFDG); + dup(c->fd[0], 0); + close(c->fd[0]); + close(c->fd[1]); + + na = 0; + av[na++] = "marshal"; + av[na++] = "-8"; + if(savebox != nil){ + av[na++] = "-S"; + av[na++] = savebox; + } + if(c->rpath != nil){ + av[na++] = "-R"; + av[na++] = c->rpath; + } + av[na] = nil; + assert(na < nelem(av)); + procexec(c->sync, "/bin/upas/marshal", av); +} + +static void +postmesg(Comp *c, char **, int nf) +{ + char *buf, wpath[64], *path; + int n, fd; + Mesg *m; + + snprint(wpath, sizeof(wpath), "/mnt/acme/%d/body", c->id); + if(nf != 0){ + fprint(2, "Post: too many args\n"); + return; + } + if((fd = open(wpath, OREAD)) == -1){ + fprint(2, "open body: %r\n"); + return; + } + if(pipe(c->fd) == -1) + sysfatal("pipe: %r\n"); + + c->sync = chancreate(sizeof(ulong), 0); + procrfork(execmarshal, c, Stack, RFNOTEG); + recvul(c->sync); + chanfree(c->sync); + close(c->fd[0]); + + /* needed because mail is by default Latin-1 */ + fprint(c->fd[1], "Content-Type: text/plain; charset=\"UTF-8\"\n"); + fprint(c->fd[1], "Content-Transfer-Encoding: 8bit\n"); + buf = emalloc(Bufsz); + while((n = read(fd, buf, Bufsz)) > 0) + if(write(c->fd[1], buf, n) != n) + break; + write(c->fd[1], "\n", 1); + close(c->fd[1]); + close(fd); + if(n == -1) + return; + + if(fprint(c->ctl, "name %s:Sent\n", c->path) == -1) + sysfatal("write ctl: %r"); + if(c->replyto != nil){ + if((m = mesglookup(c->rname, c->rdigest)) == nil) + return; + m->flags |= Fresp; + path = estrjoin(mbox.path, "/", m->name, "/flags", nil); + if((fd = open(path, OWRITE)) != -1){ + fprint(fd, "+a"); + close(fd); + } + mbredraw(m, 0, 0); + free(path); + } + fprint(c->ctl, "clean\n"); +} + +static void +compquit(Comp *c, char **, int) +{ + c->quitting = 1; +} + +static Fn compfn[] = { + {"Post", postmesg}, + {"Del", compquit}, + {nil}, +}; + +static void +compmain(void *cp) +{ + char *f[32]; + int nf; + Event ev; + Comp *c, **pc; + Fn *p; + + c = cp; + c->quitting = 0; + c->qnext = mbox.opencomp; + mbox.opencomp = c; + fprint(c->ctl, "clean\n"); + mbox.nopen++; + while(!c->quitting){ + if(winevent(c, &ev) != 'M') + continue; + if(strcmp(ev.text, "Del") == 0) + break; + switch(ev.type){ + case 'l': + case 'L': + if(matchmesg(&mbox, ev.text)) + mesgopen(ev.text, nil); + else + winreturn(c, &ev); + break; + case 'x': + case 'X': + if((nf = tokenize(ev.text, f, nelem(f))) == 0) + continue; + for(p = compfn; p->fn != nil; p++) + if(strcmp(p->name, f[0]) == 0){ + p->fn(c, &f[1], nf - 1); + break; + } + if(p->fn == nil) + winreturn(c, &ev); + break; + break; + } + } + for(pc = &mbox.opencomp; *pc != nil; pc = &(*pc)->qnext) + if(*pc == c){ + *pc = c->qnext; + break; + } + mbox.nopen--; + c->qnext = nil; + winclose(c); + free(c->replyto); + free(c->rname); + free(c->rdigest); + free(c->rpath); + threadexits(nil); +} + +static Biobuf* +openbody(Mesg *r) +{ + Biobuf *f; + int q0, q1; + char *s; + + assert(r->state & Sopen); + + wingetsel(r, &q0, &q1); + if(q1 - q0 != 0){ + s = smprint("/mnt/acme/%d/xdata", r->id); + f = Bopen(s, OREAD); + free(s); + }else + f = mesgopenbody(r); + return f; +} + +int +strpcmp(void *a, void *b) +{ + return strcmp(*(char**)a, *(char**)b); +} + +void +show(Biobuf *fd, char *type, char **addrs, int naddrs) +{ + char *sep; + int i, w; + + w = 0; + sep = ""; + if(naddrs == 0) + return; + qsort(addrs, naddrs, sizeof(char*), strpcmp); + Bprint(fd, "%s: ", type); + for(i = 0; i < naddrs; i++){ + if(i > 0 && strcmp(addrs[i-1], addrs[i]) == 0) + continue; + w += Bprint(fd, "%s%s", sep, addrs[i]); + sep = ", "; + if(w > 50){ + w = 0; + sep = ""; + Bprint(fd, "\n%s: ", type); + } + } + Bprint(fd, "\n"); +} + +void +respondto(Biobuf *fd, char *to, Mesg *r, int all) +{ + char *rpto, **addrs; + int n; + + rpto = to; + if(r != nil) + rpto = (strlen(r->replyto) > 0) ? r->replyto : r->from; + if(r == nil || !all){ + Bprint(fd, "To: %s\n", rpto); + return; + } + + n = 0; + addrs = emalloc(64*sizeof(char*)); + n += tokenize(to, addrs+n, 64-n); + n += tokenize(rpto, addrs+n, 64-n); + n += tokenize(r->to, addrs+n, 64-n); + show(fd, "To", addrs, n); + n = tokenize(r->cc, addrs+n, 64-n); + show(fd, "CC", addrs, n); + free(addrs); +} + +void +compose(char *to, Mesg *r, int all) +{ + static int ncompose; + Biobuf *rfd, *wfd; + Comp *c; + char *ln; + + c = emalloc(sizeof(Comp)); + if(r != nil) + c->path = esmprint("%s%s%s.%d", mbox.path, r->name, "Reply", ncompose++); + else + c->path = esmprint("%sCompose.%d", mbox.path, ncompose++); + wininit(c, c->path); + + wintagwrite(c, "Post |fmt "); + wfd = bwinopen(c, "body", OWRITE); + respondto(wfd, to, r, all); + if(r == nil) + Bprint(wfd, "Subject: "); + else{ + if(r->messageid != nil) + c->replyto = estrdup(r->messageid); + c->rpath = estrjoin(mbox.path, r->name, nil); + c->rname = estrdup(r->name); + c->rdigest = estrdup(r->digest); + Bprint(wfd, "Subject: "); + if(r->subject != nil && cistrncmp(r->subject, "Re", 2) != 0) + Bprint(wfd, "Re: "); + Bprint(wfd, "%s\n\n", r->subject); + Bprint(wfd, "Quoth %s:\n", r->fromcolon); + rfd = openbody(r); + if(rfd != nil){ + while((ln = Brdstr(rfd, '\n', 0)) != nil) + if(Bprint(wfd, "> %s", ln) == -1) + break; + Bterm(rfd); + } + Bterm(wfd); + } + Bterm(wfd); + fprint(c->addr, "$"); + fprint(c->ctl, "dot=addr"); + threadcreate(compmain, c, Stack); +} diff --git a/sys/src/cmd/upas/Mail/dat.h b/sys/src/cmd/upas/Mail/dat.h deleted file mode 100644 index 8f2c747ef..000000000 --- a/sys/src/cmd/upas/Mail/dat.h +++ /dev/null @@ -1,171 +0,0 @@ -typedef struct Event Event; -typedef struct Exec Exec; -typedef struct Message Message; -typedef struct Window Window; - -enum -{ - STACK = 8192, - EVENTSIZE = 256, - NEVENT = 5, -}; - -struct Event -{ - int c1; - int c2; - int q0; - int q1; - int flag; - int nb; - int nr; - char b[EVENTSIZE*UTFmax+1]; - Rune r[EVENTSIZE+1]; -}; - -struct Window -{ - /* file descriptors */ - int ctl; - int event; - int addr; - int data; - Biobuf *body; - - /* event input */ - char buf[512]; - char *bufp; - int nbuf; - Event e[NEVENT]; - - int id; - int open; - Channel *cevent; -}; - -struct Message -{ - Window *w; - int ctlfd; - char *name; - char *replyname; - char *replydigest; - uchar opened; - uchar dirty; - uchar isreply; - uchar deleted; - uchar writebackdel; - uchar tagposted; - uchar recursed; - uchar level; - - /* header info */ - char *fromcolon; /* from header file; all rest are from info file */ - char *from; - char *to; - char *cc; - char *replyto; - char *date; - char *subject; - char *type; - char *disposition; - char *filename; - char *digest; - char *flags; - - Message *next; /* next in this mailbox */ - Message *prev; /* prev in this mailbox */ - Message *head; /* first subpart */ - Message *tail; /* last subpart */ -}; - -enum -{ - NARGS = 100, - NARGCHAR = 8*1024, - EXECSTACK = STACK+(NARGS+1)*sizeof(char*)+NARGCHAR -}; - -struct Exec -{ - char *prog; - char **argv; - int p[2]; /* p[1] is write to program; p[0] set to prog fd 0*/ - int q[2]; /* q[0] is read from program; q[1] set to prog fd 1 */ - Channel *sync; -}; - -extern Window* newwindow(void); -extern int winopenfile(Window*, char*); -extern void winopenbody(Window*, int); -extern void winclosebody(Window*); -extern void wintagwrite(Window*, char*, int); -extern void winname(Window*, char*); -extern void winwriteevent(Window*, Event*); -extern void winread(Window*, uint, uint, char*); -extern int windel(Window*, int); -extern void wingetevent(Window*, Event*); -extern void wineventproc(void*); -extern void winwritebody(Window*, char*, int); -extern void winclean(Window*); -extern int winselect(Window*, char*, int); -extern char* winselection(Window*); -extern int winsetaddr(Window*, char*, int); -extern char* winreadbody(Window*, int*); -extern void windormant(Window*); -extern void winsetdump(Window*, char*, char*); - -extern void readmbox(Message*, char*, char*); -extern void rewritembox(Window*, Message*); - -extern void mkreply(Message*, char*, char*, Plumbattr*, char*); -extern void delreply(Message*); -extern int write2(int, int, char*, int, int); - -extern int mesgadd(Message*, char*, Dir*, char*); -extern void mesgmenu(Window*, Message*); -extern void mesgmenuselect(Window*, Message*); -extern void mesgmenunew(Window*, Message*); -extern void mesgmenureflag(Window*, Message*); -extern int mesgopen(Message*, char*, char*, Message*, int, char*); -extern void mesgctl(void*); -extern void mesgsend(Message*); -extern void mesgdel(Message*, Message*); -extern void mesgmenudel(Window*, Message*, Message*); -extern void mesgmenumark(Window*, char*, char*); -extern void mesgmenumarkdel(Window*, Message*, Message*, int); -extern Message* mesglookup(Message*, char*, char*); -extern Message* mesglookupfile(Message*, char*, char*); -extern void mesgfreeparts(Message*); - -extern char* readfile(char*, char*, int*); -extern char* readbody(char*, char*, int*); -extern void ctlprint(int, char*, ...); -extern void* emalloc(uint); -extern void* erealloc(void*, uint); -extern char* estrdup(char*); -extern char* estrstrdup(char*, char*); -extern char* egrow(char*, char*, char*); -extern char* eappend(char*, char*, char*); -extern void error(char*, ...); -extern int tokenizec(char*, char**, int, char*); -extern void execproc(void*); -extern void setflags(Message*, char *); - -#pragma varargck argpos error 1 -#pragma varargck argpos ctlprint 2 - -extern Window *wbox; -extern Message mbox; -extern Message replies; -extern char *fsname; -extern int plumbsendfd; -extern int plumbseemailfd; -extern char *home; -extern char *outgoing; -extern char *mailboxdir; -extern char *user; -extern char deleted[]; -extern int wctlfd; -extern int shortmenu; -extern int altmenu; diff --git a/sys/src/cmd/upas/Mail/html.c b/sys/src/cmd/upas/Mail/html.c deleted file mode 100644 index 3740bf9c5..000000000 --- a/sys/src/cmd/upas/Mail/html.c +++ /dev/null @@ -1,74 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include "dat.h" - - -char* -formathtml(char *body, int *np) -{ - int i, j, p[2], q[2]; - Exec *e; - char buf[1024]; - - e = emalloc(sizeof(Exec)); - if(pipe(p) < 0 || pipe(q) < 0) - error("can't create pipe: %r"); - - e->p[0] = p[0]; - e->p[1] = p[1]; - e->q[0] = q[0]; - e->q[1] = q[1]; - e->argv = emalloc(4*sizeof(char*)); - e->argv[0] = estrdup("htmlfmt"); - e->argv[1] = estrdup("-a"); - e->argv[2] = estrdup("-cutf-8"); - e->argv[3] = nil; - e->prog = "/bin/htmlfmt"; - e->sync = chancreate(sizeof(int), 0); - proccreate(execproc, e, EXECSTACK); - recvul(e->sync); - close(p[0]); - close(q[1]); - - if((i=write(p[1], body, *np)) != *np){ - fprint(2, "Mail: warning: htmlfmt failed: wrote %d of %d: %r\n", i, *np); - close(p[1]); - close(q[0]); - return body; - } - close(p[1]); - - free(body); - body = nil; - i = 0; - for(;;){ - j = read(q[0], buf, sizeof buf); - if(j <= 0) - break; - body = realloc(body, i+j+1); - if(body == nil) - error("realloc failed: %r"); - memmove(body+i, buf, j); - i += j; - body[i] = '\0'; - } - close(q[0]); - - *np = i; - return body; -} - -char* -readbody(char *type, char *dir, int *np) -{ - char *body; - - body = readfile(dir, "body", np); - if(body != nil && strcmp(type, "text/html") == 0) - return formathtml(body, np); - return body; -} diff --git a/sys/src/cmd/upas/Mail/mail.c b/sys/src/cmd/upas/Mail/mail.c deleted file mode 100644 index daa3ebb73..000000000 --- a/sys/src/cmd/upas/Mail/mail.c +++ /dev/null @@ -1,644 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include "dat.h" - -char *maildir = "/mail/fs/"; /* mountpoint of mail file system */ -char *mailtermdir = "/mnt/term/mail/fs/"; /* alternate mountpoint */ -char *mboxname = "mbox"; /* mailboxdir/mboxname is mail spool file */ -char *mailboxdir = nil; /* nil == /mail/box/$user */ -char *fsname; /* filesystem for mailboxdir/mboxname is at maildir/fsname */ -char *user; -char *outgoing; - -Window *wbox; -Message mbox; -Message replies; -char *home; -int plumbsendfd; -int plumbseemailfd; -int plumbshowmailfd; -int plumbsendmailfd; -Channel *cplumb; -Channel *cplumbshow; -Channel *cplumbsend; -int wctlfd; -void mainctl(void*); -void plumbproc(void*); -void plumbshowproc(void*); -void plumbsendproc(void*); -void plumbthread(void); -void plumbshowthread(void*); -void plumbsendthread(void*); - -int shortmenu; -int altmenu; - -void -usage(void) -{ - fprint(2, "usage: Mail [-sS] [-o outgoing] [mailboxname [directoryname]]\n"); - threadexitsall("usage"); -} - -void -removeupasfs(void) -{ - char buf[256]; - - if(strcmp(mboxname, "mbox") == 0) - return; - snprint(buf, sizeof buf, "close %s", mboxname); - write(mbox.ctlfd, buf, strlen(buf)); -} - -int -ismaildir(char *s) -{ - char *path; - Dir *d; - int ret; - - path = smprint("%s%s", maildir, s); - d = dirstat(path); - free(path); - if(d == nil) - return 0; - ret = d->qid.type & QTDIR; - free(d); - return ret; -} - -void -threadmain(int argc, char *argv[]) -{ - char *s, *name; - char err[ERRMAX], *cmd; - int i, newdir; - Fmt fmt; - - doquote = needsrcquote; - quotefmtinstall(); - tmfmtinstall(); - - /* open these early so we won't miss notification of new mail messages while we read mbox */ - plumbsendfd = plumbopen("send", OWRITE|OCEXEC); - plumbseemailfd = plumbopen("seemail", OREAD|OCEXEC); - plumbshowmailfd = plumbopen("showmail", OREAD|OCEXEC); - - shortmenu = 0; - altmenu = 0; - ARGBEGIN{ - case 's': - shortmenu = 1; - break; - case 'S': - shortmenu = 2; - break; - case 'A': - altmenu = 1; - break; - case 'o': - outgoing = EARGF(usage()); - break; - case 'm': - smprint(maildir, "%s/", EARGF(usage())); - break; - default: - usage(); - }ARGEND - - name = "mbox"; - - /* bind the terminal /mail/fs directory over the local one */ - if(access(maildir, 0)<0 && access(mailtermdir, 0)==0) - bind(mailtermdir, maildir, MAFTER); - - newdir = 1; - if(argc > 0){ - i = strlen(argv[0]); - if(argc>2 || i==0) - usage(); - /* see if the name is that of an existing /mail/fs directory */ - if(argc==1 && strchr(argv[0], '/')==0 && ismaildir(argv[0])){ - name = argv[0]; - mboxname = eappend(estrdup(maildir), "", name); - newdir = 0; - }else{ - if(argv[0][i-1] == '/') - argv[0][i-1] = '\0'; - s = strrchr(argv[0], '/'); - if(s == nil) - mboxname = estrdup(argv[0]); - else{ - *s++ = '\0'; - if(*s == '\0') - usage(); - mailboxdir = argv[0]; - mboxname = estrdup(s); - } - if(argc > 1) - name = argv[1]; - else - name = mboxname; - } - } - - user = getenv("user"); - if(user == nil) - user = "none"; - if(mailboxdir == nil) - mailboxdir = estrstrdup("/mail/box/", user); - if(outgoing == nil) - outgoing = estrstrdup(mailboxdir, "/outgoing"); - - s = estrstrdup(maildir, "ctl"); - mbox.ctlfd = open(s, ORDWR|OCEXEC); - if(mbox.ctlfd < 0) - error("can't open %s: %r", s); - - fsname = estrdup(name); - if(newdir && argc > 0){ - s = emalloc(5+strlen(mailboxdir)+strlen(mboxname)+strlen(name)+10+1); - for(i=0; i<10; i++){ - sprint(s, "open %s/%s %s", mailboxdir, mboxname, fsname); - if(write(mbox.ctlfd, s, strlen(s)) >= 0) - break; - err[0] = '\0'; - errstr(err, sizeof err); - if(strstr(err, "mbox name in use") == nil) - error("can't create directory %s for mail: %s", name, err); - free(fsname); - fsname = emalloc(strlen(name)+10); - sprint(fsname, "%s-%d", name, i); - } - if(i == 10) - error("can't open %s/%s: %r", mailboxdir, mboxname); - free(s); - } - - s = estrstrdup(fsname, "/"); - mbox.name = estrstrdup(maildir, s); - mbox.level= 0; - readmbox(&mbox, maildir, s); - home = getenv("home"); - if(home == nil) - home = "/"; - - wbox = newwindow(); - winname(wbox, mbox.name); - wintagwrite(wbox, "Put Mail Delmesg Save Next ", 3+1+4+1+7+1+4+1+4+1); - threadcreate(mainctl, wbox, STACK); - - fmtstrinit(&fmt); - fmtprint(&fmt, "Mail"); - if(shortmenu) - fmtprint(&fmt, " -%c", "sS"[shortmenu-1]); - if(altmenu) - fmtprint(&fmt, " -A"); - if(outgoing) - fmtprint(&fmt, " -o %s", outgoing); - fmtprint(&fmt, " %s", name); - cmd = fmtstrflush(&fmt); - if(cmd == nil) - sysfatal("out of memory"); - winsetdump(wbox, "/acme/mail", cmd); - mbox.w = wbox; - - mesgmenu(wbox, &mbox); - winclean(wbox); - - wctlfd = open("/dev/wctl", OWRITE|OCEXEC); /* for acme window */ - cplumb = chancreate(sizeof(Plumbmsg*), 0); - cplumbshow = chancreate(sizeof(Plumbmsg*), 0); - if(strcmp(name, "mbox") == 0){ - /* - * Avoid creating multiple windows to send mail by only accepting - * sendmail plumb messages if we're reading the main mailbox. - */ - plumbsendmailfd = plumbopen("sendmail", OREAD|OCEXEC); - cplumbsend = chancreate(sizeof(Plumbmsg*), 0); - proccreate(plumbsendproc, nil, STACK); - threadcreate(plumbsendthread, nil, STACK); - } - /* start plumb reader as separate proc ... */ - proccreate(plumbproc, nil, STACK); - proccreate(plumbshowproc, nil, STACK); - threadcreate(plumbshowthread, nil, STACK); - /* ... and use this thread to read the messages */ - plumbthread(); -} - -void -plumbproc(void*) -{ - Plumbmsg *m; - - threadsetname("plumbproc"); - for(;;){ - m = plumbrecv(plumbseemailfd); - sendp(cplumb, m); - if(m == nil) - threadexits(nil); - } -} - -void -plumbshowproc(void*) -{ - Plumbmsg *m; - - threadsetname("plumbshowproc"); - for(;;){ - m = plumbrecv(plumbshowmailfd); - sendp(cplumbshow, m); - if(m == nil) - threadexits(nil); - } -} - -void -plumbsendproc(void*) -{ - Plumbmsg *m; - - threadsetname("plumbsendproc"); - for(;;){ - m = plumbrecv(plumbsendmailfd); - sendp(cplumbsend, m); - if(m == nil) - threadexits(nil); - } -} - -void -newmesg(char *name, char *digest) -{ - Dir *d; - - if(strncmp(name, mbox.name, strlen(mbox.name)) != 0) - return; /* message is about another mailbox */ - if(mesglookupfile(&mbox, name, digest) != nil) - return; - d = dirstat(name); - if(d == nil) - return; - if(mesgadd(&mbox, mbox.name, d, digest)) - mesgmenunew(wbox, &mbox); - free(d); -} - -void -showmesg(char *name, char *digest) -{ - char *n; - - if(strncmp(name, mbox.name, strlen(mbox.name)) != 0) - return; /* message is about another mailbox */ - n = estrdup(name+strlen(mbox.name)); - if(n[strlen(n)-1] != '/') - n = egrow(n, "/", nil); - mesgopen(&mbox, mbox.name, name+strlen(mbox.name), nil, 1, digest); - free(n); -} - -void -delmesg(char *name, char *digest, int dodel) -{ - Message *m; - - m = mesglookupfile(&mbox, name, digest); - if(m != nil){ - mesgmenumarkdel(wbox, &mbox, m, 0); - if(dodel) - m->writebackdel = 1; - } -} - -void -modmesg(char *name, char *digest) -{ - Message *m; - char *flags; - - if((m = mesglookupfile(&mbox, name, digest)) == nil) - return; - if((flags = readfile(name, "/flags", nil)) == nil) - return; - free(m->flags); - m->flags = flags; - mesgmenureflag(mbox.w, m); -} - - -extern int mesgsave(Message*, char*); -void -savemesg(char *box, char *name, char *digest) -{ - char *s; - int ok; - Message *m; - - m = mesglookupfile(&mbox, name, digest); - if(!m || m->isreply) - return; - s = estrdup("\t[saved"); - if(!box[0]) - ok = mesgsave(m, "stored"); - else{ - ok = mesgsave(m, box); - s = eappend(s, " ", box); - } - if(ok){ - s = egrow(s, "]", nil); - mesgmenumark(mbox.w, m->name, s); - } - free(s); - -} - -void -plumbthread(void) -{ - Plumbmsg *m; - Plumbattr *a; - char *type, *digest; - - threadsetname("plumbthread"); - while((m = recvp(cplumb)) != nil){ - a = m->attr; - digest = plumblookup(a, "digest"); - type = plumblookup(a, "mailtype"); - if(type == nil) - fprint(2, "Mail: plumb message with no mailtype attribute\n"); - else if(strcmp(type, "new") == 0) - newmesg(m->data, digest); - else if(strcmp(type, "delete") == 0) - delmesg(m->data, digest, 0); - else if(strcmp(type, "modify") == 0) - modmesg(m->data, digest); - else - fprint(2, "Mail: unknown plumb attribute %s\n", type); - plumbfree(m); - } - threadexits(nil); -} - -void -plumbshowthread(void*) -{ - Plumbmsg *m; - - threadsetname("plumbshowthread"); - while((m = recvp(cplumbshow)) != nil){ - showmesg(m->data, plumblookup(m->attr, "digest")); - plumbfree(m); - } - threadexits(nil); -} - -void -plumbsendthread(void*) -{ - Plumbmsg *m; - - threadsetname("plumbsendthread"); - while((m = recvp(cplumbsend)) != nil){ - mkreply(nil, "Mail", m->data, m->attr, nil); - plumbfree(m); - } - threadexits(nil); -} - -int -mboxcommand(Window *w, char *s) -{ - char *args[10], **targs, *r, *box; - Message *m, *next; - int ok, nargs, i, j; - char buf[128]; - - nargs = tokenize(s, args, nelem(args)); - if(nargs == 0) - return 0; - if(strcmp(args[0], "Mail") == 0){ - if(nargs == 1) - mkreply(nil, "Mail", "", nil, nil); - else - mkreply(nil, "Mail", args[1], nil, nil); - return 1; - } - if(strcmp(s, "Del") == 0){ - if(mbox.dirty){ - mbox.dirty = 0; - fprint(2, "mail: mailbox not written\n"); - return 1; - } - ok = 1; - for(m=mbox.head; m!=nil; m=next){ - next = m->next; - if(m->w){ - if(windel(m->w, 0)) - m->w = nil; - else - ok = 0; - } - } - for(m=replies.head; m!=nil; m=next){ - next = m->next; - if(m->w){ - if(windel(m->w, 0)) - m->w = nil; - else - ok = 0; - } - } - if(ok){ - windel(w, 1); - removeupasfs(); - threadexitsall(nil); - } - return 1; - } - if(strcmp(s, "Put") == 0){ - rewritembox(wbox, &mbox); - return 1; - } - if(strcmp(s, "Delmesg") == 0){ - if(nargs > 1){ - for(i=1; i 1 && !mesglookupfile(&mbox, args[1], nil)){ - box = args[1]; - i++; - nargs--; - } - if(nargs > 1){ - for(; ictl, "addr=dot\n"); - winselect(w, "/^[0-9]*\\/ \\[\\*.*(\\n(\t.*)*)/", 1); - ctlprint(w->ctl, "show\n"); - } - return 0; -} - -void -mainctl(void *v) -{ - Window *w; - Event *e, *e2, *eq, *ea; - int na, nopen; - char *s, *t, *buf; - - w = v; - proccreate(wineventproc, w, STACK); - - for(;;){ - e = recvp(w->cevent); - switch(e->c1){ - default: - Unknown: - print("unknown message %c%c\n", e->c1, e->c2); - break; - - case 'E': /* write to body; can't affect us */ - break; - - case 'F': /* generated by our actions; ignore */ - break; - - case 'K': /* type away; we don't care */ - break; - - case 'M': - switch(e->c2){ - case 'x': - case 'X': - ea = nil; - e2 = nil; - if(e->flag & 2) - e2 = recvp(w->cevent); - if(e->flag & 8){ - ea = recvp(w->cevent); - na = ea->nb; - recvp(w->cevent); - }else - na = 0; - s = e->b; - /* if it's a known command, do it */ - if((e->flag&2) && e->nb==0) - s = e2->b; - if(na){ - t = emalloc(strlen(s)+1+na+1); - sprint(t, "%s %s", s, ea->b); - s = t; - } - /* if it's a long message, it can't be for us anyway */ - if(!mboxcommand(w, s)) /* send it back */ - winwriteevent(w, e); - if(na) - free(s); - break; - - case 'l': - case 'L': - buf = nil; - eq = e; - if(e->flag & 2){ - e2 = recvp(w->cevent); - eq = e2; - } - s = eq->b; - if(eq->q1>eq->q0 && eq->nb==0){ - buf = emalloc((eq->q1-eq->q0)*UTFmax+1); - winread(w, eq->q0, eq->q1, buf); - s = buf; - } - nopen = 0; - do{ - /* skip 'deleted' string if present' */ - if(strncmp(s, deleted, strlen(deleted)) == 0) - s += strlen(deleted); - /* skip mail box name if present */ - if(strncmp(s, mbox.name, strlen(mbox.name)) == 0) - s += strlen(mbox.name); - nopen += mesgopen(&mbox, mbox.name, s, nil, 0, nil); - while(*s!='\0' && *s++!='\n') - ; - }while(*s); - if(nopen == 0) /* send it back */ - winwriteevent(w, e); - free(buf); - break; - - case 'I': /* modify away; we don't care */ - case 'D': - case 'd': - case 'i': - break; - - default: - goto Unknown; - } - } - } -} - diff --git a/sys/src/cmd/upas/Mail/mail.h b/sys/src/cmd/upas/Mail/mail.h new file mode 100644 index 000000000..a6604d104 --- /dev/null +++ b/sys/src/cmd/upas/Mail/mail.h @@ -0,0 +1,195 @@ +typedef struct Event Event; +typedef struct Win Win; +typedef struct Mesg Mesg; +typedef struct Mbox Mbox; +typedef struct Comp Comp; + +enum { + Stack = 64*1024, + Bufsz = 8192, + Eventsz = 256*UTFmax, + Subjlen = 56, +}; + +enum { + Sdummy = 1<<0, /* message placeholder */ + Stoplev = 1<<1, /* not a response to anything */ + Sopen = 1<<2, /* opened for viewing */ +}; + +enum { + Fresp = 1<<0, /* has been responded to */ + Fseen = 1<<1, /* has been viewed */ + Fdel = 1<<2, /* was deleted */ + Ftodel = 1<<3, /* pending deletion */ +}; + +enum { + Vflat, + Vgroup, +}; + +struct Event { + char action; + char type; + int p0; /* click point */ + int q0; /* expand lo */ + int q1; /* expand hi */ + int flags; + int ntext; + char text[Eventsz + 1]; +}; + +struct Win { + int id; + Ioproc *io; + Biobuf *event; + int revent; + int ctl; + int addr; + int data; + int open; +}; + +/* + * In progress compositon + */ +struct Comp { + Win; + + /* exec setup */ + Channel *sync; + int fd[2]; + + /* to relate back the message */ + char *replyto; + char *rname; + char *rpath; + char *rdigest; + char *path; + + int quitting; + Comp *qnext; +}; + +/* + * Message in mailbox + */ +struct Mesg { + Win; + + /* bookkeeping */ + char *name; + int state; + int flags; + u32int hash; + char quitting; + Mesg *qnext; + + /* exec setup */ + Channel *sync; + char *path; + int fd[2]; + + Mesg *parent; + Mesg **child; + int nchild; + int nsub; /* transitive children */ + + Mesg *body; /* best attachment to use, or nil */ + Mesg **parts; + int nparts; + int xparts; + + /* info fields */ + char *from; + char *to; + char *cc; + char *replyto; + char *date; + char *subject; + char *type; + char *disposition; + char *messageid; + char *filename; + char *digest; + char *mflags; + char *fromcolon; + char *inreplyto; + + vlong time; +}; + +/* + *The mailbox we're showing. + */ +struct Mbox { + Win; + + Mesg **mesg; + Mesg **hash; + int mesgsz; + int hashsz; + int nmesg; + int ndead; + + Mesg *openmesg; + Comp *opencomp; + int canquit; + + Channel *see; + Channel *show; + Channel *event; + Channel *send; + + int view; + int nopen; + char *path; +}; + +extern Mbox mbox; +extern Reprog *mesgpat; +extern char *savebox; + +/* window management */ +void wininit(Win*, char*); +int winopen(Win*, char*, int); +Biobuf *bwinopen(Win*, char*, int); +Biobuf *bwindata(Win*, int); +void winclose(Win*); +void wintagwrite(Win*, char*); +int winevent(Win*, Event*); +void winreturn(Win*, Event*); +int winread(Win*, int, int, char*, int); +int matchmesg(Win*, char*); +char *winreadsel(Win*); +void wingetsel(Win*, int*, int*); +void winsetsel(Win*, int, int); + +/* messages */ +Mesg *mesglookup(char*, char*); +Mesg *mesgload(char*); +Mesg *mesgopen(char*, char*); +int mesgmatch(Mesg*, char*, char*); +void mesgclear(Mesg*); +void mesgfree(Mesg*); +void mesgpath2name(char*, int, char*); +int mesgflagparse(char*, int*); +Biobuf* mesgopenbody(Mesg*); + +/* mailbox */ +void mbredraw(Mesg*, int, int); + +/* composition */ +void compose(char*, Mesg*, int); + +/* utils */ +void *emalloc(ulong); +void *erealloc(void*, ulong); +char *estrdup(char*); +char *estrjoin(char*, ...); +char *esmprint(char*, ...); +char *rslurp(Mesg*, char*, int*); +char *fslurp(int, int*); +u32int strhash(char*); + diff --git a/sys/src/cmd/upas/Mail/mbox.c b/sys/src/cmd/upas/Mail/mbox.c new file mode 100644 index 000000000..bf289378d --- /dev/null +++ b/sys/src/cmd/upas/Mail/mbox.c @@ -0,0 +1,1056 @@ +#include +#include +#include +#include +#include +#include +#include + +#include "mail.h" + +typedef struct Fn Fn; + +struct Fn { + char *name; + void (*fn)(char **, int); +}; + +enum { + Cevent, + Cseemail, + Cshowmail, + Csendmail, + Nchan, +}; + + +char *maildir = "/mail/fs"; +char *mailbox = "mbox"; +char *savebox = "outgoing"; +char *listfmt = "%>48s\t<%f>"; +Mesg dead = {.messageid="", .hash=42}; + +Reprog *mesgpat; + +int threadsort = 1; +int sender; + +int plumbsendfd; +int plumbseemailfd; +int plumbshowmailfd; +int plumbsendmailfd; +Channel *cwait; + +Mbox mbox; + +static void showmesg(Biobuf*, Mesg*, int, int); + +static void +plumbloop(Channel *ch, int fd) +{ + Plumbmsg *m; + + while(1){ + if((m = plumbrecv(fd)) == nil) + threadexitsall("plumber gone"); + sendp(ch, m); + } +} + +static void +plumbshowmail(void*) +{ + threadsetname("plumbshow %s", mbox.path); + plumbloop(mbox.show, plumbshowmailfd); +} + +static void +plumbseemail(void*) +{ + threadsetname("plumbsee %s", mbox.path); + plumbloop(mbox.see, plumbseemailfd); +} + +static void +plumbsendmail(void*) +{ + threadsetname("plumbsend %s", mbox.path); + plumbloop(mbox.send, plumbsendmailfd); +} + +static void +eventread(void*) +{ + Event *ev; + + threadsetname("mbevent %s", mbox.path); + while(1){ + ev = emalloc(sizeof(Event)); + if(winevent(&mbox, ev) == -1) + break; + sendp(mbox.event, ev); + } + sendp(mbox.event, nil); + threadexits(nil); +} + +static int +ideq(char *a, char *b) +{ + if(a == nil || b == nil) + return 0; + return strcmp(a, b) == 0; +} + +static int +cmpmesg(void *pa, void *pb) +{ + Mesg *a, *b; + + a = *(Mesg**)pa; + b = *(Mesg**)pb; + + return b->time - a->time; +} + +static int +rcmpmesg(void *pa, void *pb) +{ + Mesg *a, *b; + + a = *(Mesg**)pa; + b = *(Mesg**)pb; + + return a->time - b->time; +} + +static int +mesglineno(Mesg *msg, int *depth) +{ + Mesg *p, *m; + int i, o, n, d; + + o = 0; + d = 0; + n = 1; + + /* Walk up to root, counting depth in the thread */ + p = msg; + while(p->parent != nil){ + m = p; + p = p->parent; + for(i = 0; i < p->nchild; i++){ + if(p->child[i] == m) + break; + o += p->child[i]->nsub + 1; + } + if(!(p->state & Sdummy)){ + o++; + d++; + } + } + + /* Find the thread in the thread list */ + for(i = 0; i < mbox.nmesg; i++){ + m = mbox.mesg[i]; + if(m == p) + break; + if(m->state & Stoplev){ + n += mbox.mesg[i]->nsub; + if(!(m->state & Sdummy)) + n++; + } + + } + if(depth != nil) + *depth = d; + assert(n + o <= mbox.nmesg); + return n + o; +} + +static int +addchild(Mesg *p, Mesg *m, int d) +{ + Mesg *q; + + assert(m->parent == nil); + for(q = p; q != nil; q = q->parent){ + if(ideq(m->messageid, q->messageid)){ + fprint(2, "wonky message replies to self\n"); + return 0; + } + if(m->time > q->time) + q->time = m->time; + } + for(q = p; q != nil; q = q->parent) + q->nsub += d; + p->child = erealloc(p->child, ++p->nchild*sizeof(Mesg*)); + p->child[p->nchild - 1] = m; + qsort(p->child, p->nchild, sizeof(Mesg*), rcmpmesg); + m->parent = p; + return 1; +} + +static int +slotfor(Mesg *m) +{ + int i; + + for(i = 0; i < mbox.nmesg; i++) + if(cmpmesg(&mbox.mesg[i], &m) >= 0) + break; + return i; +} + +static void +removeid(Mesg *m) +{ + Mesg *e; + int i; + + /* Dummies don't go in the table */ + if(m->state & Sdummy) + return; + i = m->hash % mbox.hashsz; + while(1){ + e = mbox.hash[i]; + if(e == nil) + return; + if(e != &dead && e->hash == m->hash && strcmp(e->messageid, m->messageid) == 0){ + mbox.hash[i] = &dead; + mbox.ndead++; + } + i = (i + 1) % mbox.hashsz; + } +} + +Mesg* +lookupid(char *msgid) +{ + u32int h, i; + Mesg *e; + + if(msgid == nil || strlen(msgid) == 0) + return nil; + h = strhash(msgid); + i = h % mbox.hashsz; + while(1){ + e = mbox.hash[i]; + if(e == nil) + return nil; + if(e != &dead && e->hash == h && strcmp(e->messageid, msgid) == 0) + return e; + i = (i + 1) % mbox.hashsz; + } +} + +static void +addmesg(Mesg *m, int ins) +{ + Mesg *o, *e, **oldh; + int i, oldsz, idx; + + /* + * on initial load, it's faster to append everything then sort, + * but on subsequent messages it's better to just put it in the + * right place; we don't want to shuffle the already-sorted + * messages. + */ + if(mbox.nmesg == mbox.mesgsz){ + mbox.mesgsz *= 2; + mbox.mesg = erealloc(mbox.mesg, mbox.mesgsz*sizeof(Mesg*)); + } + if(ins) + idx = slotfor(m); + else + idx = mbox.nmesg; + memmove(&mbox.mesg[idx + 1], &mbox.mesg[idx], (mbox.nmesg - idx)*sizeof(Mesg*)); + mbox.mesg[idx] = m; + mbox.nmesg++; + if(m->messageid == nil) + return; + + /* grow hash table, or squeeze out deadwood */ + if(mbox.hashsz <= 2*(mbox.nmesg + mbox.ndead)){ + oldsz = mbox.hashsz; + oldh = mbox.hash; + if(mbox.hashsz <= 2*mbox.nmesg) + mbox.hashsz *= 2; + mbox.ndead = 0; + mbox.hash = emalloc(mbox.hashsz*sizeof(Mesg*)); + for(i = 0; i < oldsz; i++){ + if((o = oldh[i]) == nil) + continue; + mbox.hash[o->hash % mbox.hashsz] = o; + } + free(oldh); + } + i = m->hash % mbox.hashsz; + while(1){ + e = mbox.hash[i % mbox.hashsz]; + if(e == nil || e == &dead) + break; + i = (i + 1) % mbox.hashsz; + } + mbox.hash[i] = m; +} + +static Mesg * +placeholder(char *msgid, vlong time, int ins) +{ + Mesg *m; + + m = emalloc(sizeof(Mesg)); + m->state |= Sdummy|Stoplev; + m->messageid = estrdup(msgid); + m->hash = strhash(msgid); + m->time = time; + addmesg(m, ins); + return m; +} + +static Mesg* +change(char *name, char *digest) +{ + Mesg *m; + char *f; + + if((m = mesglookup(name, digest)) == nil) + return nil; + if((f = rslurp(m, "flags", nil)) == nil) + return nil; + free(m->mflags); + m->mflags = f; + 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; + return m; +} + +static Mesg* +delete(char *name, char *digest) +{ + Mesg *m; + + if((m = mesglookup(name, digest)) == nil) + return nil; + m->flags |= Fdel; + return m; +} + +static Mesg* +load(char *name, char *digest, int ins) +{ + Mesg *m, *p; + int d; + + if(strncmp(name, mbox.path, strlen(mbox.path)) == 0) + name += strlen(mbox.path); + if((m = mesgload(name)) == nil) + goto error; + + if(digest != nil && strcmp(digest, m->digest) != 0) + goto error; + /* if we already have a dummy, populate it */ + d = 1; + p = lookupid(m->messageid); + if(p != nil && (p->state & Sdummy)){ + d = p->nsub + 1; + m->child = p->child; + m->nchild = p->nchild; + m->nsub = p->nsub; + mesgclear(p); + memcpy(p, m, sizeof(*p)); + free(m); + m = p; + + }else{ + /* + * if we raced a notify and a mailbox load, we + * can get duplicate load requests for the same + * name in the mailbox. + */ + if(p != nil && strcmp(p->name, m->name) == 0) + goto error; + addmesg(m, ins); + } + + if(!threadsort || m->inreplyto == nil || ideq(m->messageid, m->inreplyto)){ + m->state |= Stoplev; + return m; + } + p = lookupid(m->inreplyto); + if(p == nil) + p = placeholder(m->inreplyto, m->time, ins); + if(!addchild(p, m, d)) + m->state |= Stoplev; + return m; +error: + mesgfree(m); + return nil; +} + +void +mbredraw(Mesg *m, int add, int rec) +{ + Biobuf *bfd; + int ln, depth; + + ln = mesglineno(m, &depth); + fprint(mbox.addr, "%d%s", ln, add?"-#0":""); + bfd = bwindata(&mbox, OWRITE); + showmesg(bfd, m, depth, rec); + Bterm(bfd); + + /* highlight the redrawn message */ + fprint(mbox.addr, "%d%s", ln, add ? "-#0" : ""); + fprint(mbox.ctl, "dot=addr\n"); +} + +static void +mbload(void) +{ + int i, n, fd; + Dir *d; + + mbox.mesgsz = 128; + mbox.hashsz = 128; + mbox.mesg = emalloc(mbox.mesgsz*sizeof(Mesg*)); + mbox.hash = emalloc(mbox.hashsz*sizeof(Mesg*)); + mbox.path = esmprint("%s/%s/", maildir, mailbox); + cleanname(mbox.path); + n = strlen(mbox.path); + if(mbox.path[n - 1] != '/') + mbox.path[n] = '/'; + if((fd = open(mbox.path, OREAD)) == -1) + sysfatal("%s: open: %r", mbox.path); + while(1){ + n = dirread(fd, &d); + if(n == -1) + sysfatal("%s read: %r", mbox.path); + if(n == 0) + break; + for(i = 0; i < n; i++) + if(strcmp(d[i].name, "ctl") != 0) + load(d[i].name, nil, 0); + free(d); + } + qsort(mbox.mesg, mbox.nmesg, sizeof(Mesg*), cmpmesg); +} + +static char* +getflag(Mesg *m) +{ + char* flag; + + flag = "★"; + if(m->flags & Fseen) flag = " "; + if(m->flags & Fresp) flag = "←"; + if(m->flags & Fdel) flag = "∉"; + if(m->flags & Ftodel) flag = "∉"; + return flag; +} + +static void +printw(Biobuf *bp, char *s, int width) +{ + char *dots; + + if(width <= 0) + Bprint(bp, "%s", s); + else{ + dots = ""; + if(utflen(s) > width){ + width -= 3; + dots = "..."; + } + Bprint(bp, "%*.*s%s", -width, width, s, dots); + } +} + +/* + * Message format string: + * ====================== + * %s: subject + * %f: from address + * %F: name + from address + * %t: to address + * %c: CC address + * %r: replyto address + * %[...]: string to display for child messages + * %{...}: date format string + */ +static void +fmtmesg(Biobuf *bp, char *fmt, Mesg *m, int depth) +{ + char *p, *e, buf[64]; + int width, i, indent; + Tm tm; + + Bprint(bp, "%-6s\t%s ", m->name, getflag(m)); + for(p = fmt; *p; p++){ + if(*p != '%'){ + Bputc(bp, *p); + continue; + } + p++; + width = 0; + indent = 0; + while(*p == '>'){ + p++; + indent++; + } + while('0'<=*p && *p<='9') + width = width * 10 + *p++ - '0'; + for(i = 0; indent && i < depth; i++){ + Bputc(bp, '\t'); + width -= 4; + if(indent == 1) + break; + } + switch(*p){ + case '%': + Bprint(bp, "%%"); + break; + case 'i': + if(depth > 0) + depth = 1; + case 'I': + for(i = 0; i < depth; i++){ + if(width>0) + Bprint(bp, "%*s", width, ""); + else + Bprint(bp, "\t"); + } + break; + case 's': + printw(bp, m->subject, width); + break; + case 'f': + printw(bp, m->from, width); + break; + case 'F': + printw(bp, m->fromcolon, width); + break; + case 't': + printw(bp, m->to, width); + break; + case 'c': + printw(bp, m->cc, width); + break; + case 'r': + printw(bp, m->replyto, width); + break; + case '[': + p++; + if((e = strchr(p, ']')) == nil) + sysfatal("missing closing '}' in %%{"); + if(e - p >= sizeof(buf) - 1) + sysfatal("%%{} contents too long"); + snprint(buf, sizeof(buf), "%.*s", (int)(e - p), p); + if(depth > 0) + Bprint(bp, "%s", buf); + p = e; + break; + case '{': + p++; + if((e = strchr(p, '}')) == nil) + sysfatal("missing closing '}' in %%{"); + if(e - p >= sizeof(buf) - 1) + sysfatal("%%{} contents too long"); + snprint(buf, sizeof(buf), "%.*s", (int)(e - p), p); + tmtime(&tm, m->time, nil); + Bprint(bp, "%τ", tmfmt(&tm, buf)); + p = e; + break; + default: + sysfatal("invalid directive '%%%c' in format string", *p); + break; + } + } + Bputc(bp, '\n'); +} + + +static void +showmesg(Biobuf *bfd, Mesg *m, int depth, int recurse) +{ + int i; + + if(!(m->state & Sdummy)){ + fmtmesg(bfd, listfmt, m, depth); + depth++; + } + if(recurse && mbox.view != Vflat) + for(i = 0; i < m->nchild; i++) + showmesg(bfd, m->child[i], depth, recurse); +} + +static void +mark(char **f, int nf, char *fstr, int flags, int add) +{ + char *sel, *p, *q, *e, *path; + int i, q0, q1, fd; + Mesg *m; + + if(flags == 0) + return; + wingetsel(&mbox, &q0, &q1); + if(nf == 0){ + sel = winreadsel(&mbox); + for(p = sel; p != nil; p = e){ + if((e = strchr(p, '\n')) != nil) + *e++ = 0; + if(!matchmesg(&mbox, p)) + continue; + if((q = strchr(p, '/')) != nil) + q[1] = 0; + if((m = mesglookup(p, nil)) != nil){ + if(add) + m->flags |= flags; + else + m->flags &= ~flags; + if(fstr != nil && strlen(fstr) != 0){ + path = estrjoin(mbox.path, "/", m->name, "/flags", nil); + if((fd = open(path, OWRITE)) != -1){ + fprint(fd, fstr); + close(fd); + } + free(path); + } + mbredraw(m, 0, 0); + } + } + free(sel); + }else for(i = 0; i < nf; i++){ + if((m = mesglookup(f[i], nil)) != nil){ + m->flags |= flags; + mbredraw(m, 0, 0); + } + } + winsetsel(&mbox, q0, q1); +} + +static void +mbmark(char **f, int nf) +{ + int add, flg; + + if(nf == 0){ + fprint(2, "missing fstr"); + return; + } + if((flg = mesgflagparse(f[0], &add)) == -1){ + fprint(2, "Mark: invalid flags %s\n", f[0]); + return; + } + mark(f+1, nf-1, f[0], flg, add); +} + +static void +relinkmsg(Mesg *p, Mesg *m) +{ + Mesg *c, *pp; + int i, j; + + /* remove child, preserving order */ + j = 0; + for(i = 0; p && i < p->nchild; i++){ + if(p->child[i] != m) + p->child[j++] = p->child[i]; + } + p->nchild = j; + for(pp = p; pp != nil; pp = pp->parent) + pp->nsub -= m->nsub + 1; + + /* reparent children */ + for(i = 0; i < m->nchild; i++){ + c = m->child[i]; + c->parent = nil; + addchild(p, c, c->nsub + 1); + } +} + +static void +mbflush(char **, int) +{ + int i, j, ln, fd; + char *path; + Mesg *m, *p; + + i = 0; + path = estrjoin(maildir, "/ctl", nil); + fd = open(path, OWRITE); + free(path); + if(fd == -1) + sysfatal("open mbox: %r"); + while(i < mbox.nmesg){ + m = mbox.mesg[i]; + if((m->state & Sopen) || !(m->flags & (Fdel|Ftodel))){ + i++; + continue; + } + ln = mesglineno(m, nil); + fprint(mbox.addr, "%d,%d", ln, ln+m->nsub); + write(mbox.data, "", 0); + if(m->flags & Ftodel) + fprint(fd, "delete %s %d", mailbox, atoi(m->name)); + + p = m->parent; + removeid(m); + if(p == nil && m->nsub != 0){ + p = placeholder(m->messageid, m->time, 1); + p->nsub = m->nsub + 1; + mbox.mesg[i] = p; + } + if(p != nil) + relinkmsg(p, m); + for(j = 0; j < m->nchild; j++) + mbredraw(m->child[j], 1, 1); + memmove(&mbox.mesg[i], &mbox.mesg[i+1], (mbox.nmesg - i)*sizeof(Mesg*)); + mbox.nmesg--; + } + close(fd); + fprint(mbox.ctl, "clean\n"); +} + +static void +mbcompose(char **, int) +{ + compose("", nil, 0); +} + +static void +delmesg(char **f, int nf) +{ + mark(f, nf, nil, Ftodel, 1); +} + +static void +undelmesg(char **f, int nf) +{ + mark(f, nf, nil, Ftodel, 0); +} + +static void +showlist(void) +{ + Biobuf *bfd; + Mesg *m; + int i; + + bfd = bwinopen(&mbox, "data", OWRITE); + for(i = 0; i < mbox.nmesg; i++){ + m = mbox.mesg[i]; + if(mbox.view == Vflat || m->state & (Sdummy|Stoplev)) + showmesg(bfd, m, 0, 1); + } + Bterm(bfd); + fprint(mbox.addr, "0"); + fprint(mbox.ctl, "dot=addr\n"); + fprint(mbox.ctl, "show\n"); +} + +static void +quitall(char **, int) +{ + Mesg *m; + Comp *c; + + if(mbox.nopen > 0 && !mbox.canquit){ + fprint(2, "Del: %d open messages\n", mbox.nopen); + mbox.canquit = 1; + return; + } + for(m = mbox.openmesg; m != nil; m = m->qnext) + fprint(m->ctl, "del\n"); + for(c = mbox.opencomp; c != nil; c = c->qnext) + fprint(c->ctl, "del\n"); + fprint(mbox.ctl, "del\n"); + threadexitsall(nil); +} + +/* + * shuffle a message to the right location + * in the list without doing a full sort. + */ +static void +reinsert(Mesg *m) +{ + int i, idx; + + idx = slotfor(m); + for(i = idx; i < mbox.nmesg; i++) + if(mbox.mesg[i] == m) + break; + memmove(&mbox.mesg[idx + 1], &mbox.mesg[idx], (i - idx)*sizeof(Mesg*)); + mbox.mesg[idx] = m; +} + +static void +changemesg(Plumbmsg *pm) +{ + char *digest, *action; + Mesg *m, *r; + int ln, nr; + + digest = plumblookup(pm->attr, "digest"); + action = plumblookup(pm->attr, "mailtype"); +// fprint(2, "changing message %s, %s %s\n", action, pm->data, digest); + if(strcmp(action, "new") == 0){ + if((m = load(pm->data, digest, 1)) == nil) + return; + for(r = m; r->parent != nil; r = r->parent) + /* nothing */; + /* Bump whole thread up in list */ + if(r->nsub > 0){ + ln = mesglineno(r, nil); + nr = r->nsub-1; + if(!(r->state & Sdummy)) + nr++; + /* + * We can end up with an empty container + * in an edge case. + * + * Imagine we have a dummy message with + * a child, and that child gets deleted, + * then a new message comes in replying + * to that dummy. + * + * In this case, r->nsub == 1 due to the + * newly added message, so nr=0. + * in that case, skip the redraw, and + * reinsert the dummy in the right place. + */ + if(nr > 0){ + fprint(mbox.addr, "%d,%d", ln, ln+nr-1); + write(mbox.data, "", 0); + } + reinsert(r); + } + mbredraw(r, 1, 1); + }else if(strcmp(action, "delete") == 0){ + if((m = delete(pm->data, digest)) != nil) + mbredraw(m, 0, 0); + }else if(strcmp(action, "modify") == 0){ + if((m = change(pm->data, digest)) != nil) + mbredraw(m, 0, 0); + } +} + +static void +viewmesg(Plumbmsg *pm) +{ + Mesg *m; + m = mesgopen(pm->data, plumblookup(pm->attr, "digest")); + if(m != nil){ + fprint(mbox.addr, "%d", mesglineno(m, nil)); + fprint(mbox.ctl, "dot=addr\n"); + fprint(mbox.ctl, "show\n"); + } +} + +static void +redraw(char **, int) +{ + fprint(mbox.addr, ","); + showlist(); +} + +static void +nextunread(char **, int) +{ + fprint(mbox.ctl, "addr=dot\n"); + fprint(mbox.addr, "/^[0-9]+\\/ *\t★.*"); + fprint(mbox.ctl, "dot=addr\n"); + fprint(mbox.ctl, "show\n"); +} + +Fn mboxfn[] = { + {"Put", mbflush}, + {"Mail", mbcompose}, + {"Delmesg", delmesg}, + {"Undelmesg", undelmesg}, + {"Del", quitall}, + {"Redraw", redraw}, + {"Next", nextunread}, + {"Mark", mbmark}, +#ifdef NOTYET + {"Filter", filter}, + {"Get", mbrefresh}, +#endif + {nil} +}; + +static void +doevent(Event *ev) +{ + char *f[32]; + int nf; + Fn *p; + + if(ev->action != 'M') + return; + switch(ev->type){ + case 'l': + case 'L': + if(matchmesg(&mbox, ev->text)) + if(mesgopen(ev->text, nil) != nil) + break; + winreturn(&mbox, ev); + break; + case 'x': + case 'X': + if((nf = tokenize(ev->text, f, nelem(f))) == 0) + return; + for(p = mboxfn; p->fn != nil; p++) + if(strcmp(p->name, f[0]) == 0 && p->fn != nil){ + p->fn(&f[1], nf - 1); + break; + } + if(p->fn == nil) + winreturn(&mbox, ev); + else if(p->fn != quitall) + mbox.canquit = 0; + break; + } +} + +static void +execlog(void*) +{ + Waitmsg *w; + + while(1){ + w = recvp(cwait); + if(w->msg[0] != 0) + fprint(2, "%d: %s\n", w->pid, w->msg); + free(w); + } +} + +static void +mbmain(void *cmd) +{ + Event *ev; + Plumbmsg *psee, *pshow, *psend; + + Alt a[] = { + [Cevent] = {mbox.event, &ev, CHANRCV}, + [Cseemail] = {mbox.see, &psee, CHANRCV}, + [Cshowmail] = {mbox.show, &pshow, CHANRCV}, + [Csendmail] = {mbox.send, &psend, CHANRCV}, + [Nchan] = {nil, nil, CHANEND}, + }; + + threadsetname("mbox %s", mbox.path); + wininit(&mbox, mbox.path); + wintagwrite(&mbox, "Put Mail Delmesg Undelmesg Next "); + showlist(); + fprint(mbox.ctl, "dump %s\n", cmd); + fprint(mbox.ctl, "clean\n"); + procrfork(eventread, nil, Stack, RFNOTEG); + while(1){ + switch(alt(a)){ + case Cevent: + doevent(ev); + free(ev); + break; + case Cseemail: + changemesg(psee); + plumbfree(psee); + break; + case Cshowmail: + viewmesg(pshow); + plumbfree(pshow); + break; + case Csendmail: + compose(psend->data, nil, 0); + plumbfree(psend); + break; + } + } +} + +static void +usage(void) +{ + fprint(2, "usage: %s [-T] [-m mailfs] [-s] [-f format] [mbox]\n", argv0); + exits("usage"); +} + +void +threadmain(int argc, char **argv) +{ + Fmt fmt; + char *cmd; + int i; + + mbox.view = Vgroup; + doquote = needsrcquote; + quotefmtinstall(); + tmfmtinstall(); + + fmtstrinit(&fmt); + for(i = 0; i < argc; i++) + fmtprint(&fmt, "%q ", argv[i]); + cmd = fmtstrflush(&fmt); + if(cmd == nil) + sysfatal("out of memory"); + + ARGBEGIN{ + case 'm': + maildir = EARGF(usage()); + break; + case 'T': + mbox.view = Vflat; + break; + case 's': + sender++; + break; + case 'f': + listfmt = EARGF(usage()); + break; + case 'O': + savebox = nil; + break; + case 'o': + savebox = EARGF(usage()); + break; + default: + usage(); + break; + }ARGEND; + + if(argc > 1) + usage(); + if(argc == 1) + mailbox = argv[0]; + + mesgpat = regcomp("([0-9]+)(/.*)?"); + cwait = threadwaitchan(); + + /* open these early so we won't miss messages while loading */ + mbox.event = chancreate(sizeof(Event*), 0); + mbox.see = chancreate(sizeof(Plumbmsg*), 0); + mbox.show = chancreate(sizeof(Plumbmsg*), 0); + mbox.send = chancreate(sizeof(Plumbmsg*), 0); + + plumbsendfd = plumbopen("send", OWRITE|OCEXEC); + plumbseemailfd = plumbopen("seemail", OREAD|OCEXEC); + plumbshowmailfd = plumbopen("showmail", OREAD|OCEXEC); + + mbload(); + procrfork(plumbseemail, nil, Stack, RFNOTEG); + procrfork(plumbshowmail, nil, Stack, RFNOTEG); + + /* avoid duplicate sends when multiple mailboxes are open */ + if(sender || strcmp(mailbox, "mbox") == 0){ + plumbsendmailfd = plumbopen("sendmail", OREAD|OCEXEC); + procrfork(plumbsendmail, nil, Stack, RFNOTEG); + } + threadcreate(execlog, nil, Stack); + threadcreate(mbmain, cmd, Stack); + threadexits(nil); +} diff --git a/sys/src/cmd/upas/Mail/mesg.c b/sys/src/cmd/upas/Mail/mesg.c index d9bd610cf..32dc0998a 100644 --- a/sys/src/cmd/upas/Mail/mesg.c +++ b/sys/src/cmd/upas/Mail/mesg.c @@ -2,80 +2,53 @@ #include #include #include -#include -#include -#include "dat.h" +#include -enum +#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) { - DIRCHUNK = 32*sizeof(Dir) -}; + int i; -char regexchars[] = "\\/[].+?()*^$"; -char deleted[] = "(deleted)-"; -char deletedrx[] = "\\(deleted\\)-"; -char deletedrx01[] = "(\\(deleted\\)-)?"; -char deletedaddr[] = "-#0;/^\\(deleted\\)-/"; + 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); +} -struct{ - char *type; - char *port; - char *suffix; -} ports[] = { - "text/", "edit", ".txt", /* must be first for plumbport() */ - /* text must be first for plumbport() */ - "image/gif", "image", ".gif", - "image/jpeg", "image", ".jpg", - "image/jpeg", "image", ".jpeg", - "image/png", "image", ".png", - "image/tiff", "image", ".tif", - "application/postscript", "postscript", ".ps", - "application/pdf", "postscript", ".pdf", - "application/msword", "msword", ".doc", - "application/rtf", "msword", ".rtf", - "audio/x-wav", "wav", ".wav", - nil, nil -}; - -char *goodtypes[] = { - "text", - "text/plain", - "message/rfc822", - "text/richtext", - "text/tab-separated-values", - "application/octet-stream", - nil, -}; - -struct{ - char *type; - char *ext; -} exts[] = { - "image/gif", ".gif", - "image/jpeg", ".jpg", - nil, nil -}; - -char *okheaders[] = +void +mesgfree(Mesg *m) { - "From:", - "Date:", - "To:", - "CC:", - "Subject:", - nil -}; + if(m == nil) + return; + mesgclear(m); + free(m); +} -char *extraheaders[] = -{ - "Resent-From:", - "Resent-To:", - "Sort:", - nil, -}; - -char* -line(char *data, char **pp) +static char* +line(char *data, char **pp, int z) { char *p, *q; @@ -85,1347 +58,488 @@ line(char *data, char **pp) *pp = p+1; else *pp = p; + if(z && p == data) + return nil; q = emalloc(p-data + 1); memmove(q, data, p-data); return q; } -char* -fc(Message *m, char *s) +static char* +fc(Mesg *m, char *s) { char *r; - if(*s && *m->from){ + if(s != nil && strlen(m->from) != 0){ r = smprint("%s <%s>", s, m->from); free(s); return r; - }else if(*s) - return s; - else if(*m->from) + } + if(m->from != nil) return estrdup(m->from); + if(s != nil) + return s; return estrdup("??"); } -int -loadinfo(Message *m, char *dir) +Mesg* +mesgload(char *name) { - int n; - char *data, *p; - - data = readfile(dir, "info", &n); - if(data == nil) - return 0; - m->from = line(data, &p); - m->to = line(p, &p); - m->cc = line(p, &p); - m->replyto = line(p, &p); - m->date = line(p, &p); - m->subject = line(p, &p); - m->type = line(p, &p); - m->disposition = line(p, &p); - m->filename = line(p, &p); - m->digest = line(p, &p); - /* m->bcc = */ free(line(p, &p)); - /* m->inreplyto = */ free(line(p, &p)); - /* m->date = */ free(line(p, &p)); - /* m->sender = */ free(line(p, &p)); - /* m->messageid = */ free(line(p, &p)); - /* m->lines = */ free(line(p, &p)); - /* m->size = */ free(line(p, &p)); - m->flags = line(p, &p); - /* m->fileid = */ free(line(p, &p)); - m->fromcolon = fc(m, line(p, &p)); - - free(data); - return 1; -} - -int -isnumeric(char *s) -{ - while(*s){ - if(!isdigit(*s)) - return 0; - s++; - } - return 1; -} - -Dir* -loaddir(char *name, int *np) -{ - int fd; - Dir *dp; - - fd = open(name, OREAD); - if(fd < 0) - return nil; - *np = dirreadall(fd, &dp); - close(fd); - return dp; -} - -void -readmbox(Message *mbox, char *dir, char *subdir) -{ - char *name; - Dir *d, *dirp; - int i, n; - - name = estrstrdup(dir, subdir); - dirp = loaddir(name, &n); - mbox->recursed = 1; - if(dirp) - for(i=0; iname)) - mesgadd(mbox, name, d, nil); - } - free(dirp); - free(name); -} - -/* add message to box, in increasing numerical order */ -int -mesgadd(Message *mbox, char *dir, Dir *d, char *digest) -{ - Message *m; - char *name; - int loaded; - - m = emalloc(sizeof(Message)); - m->name = estrstrdup(d->name, "/"); - m->next = nil; - m->prev = mbox->tail; - m->level= mbox->level+1; - m->recursed = 0; - name = estrstrdup(dir, m->name); - loaded = loadinfo(m, name); - free(name); - /* if two upas/fs are running, we can get misled, so check digest before accepting message */ - if(loaded==0 || (digest!=nil && m->digest!=nil && strcmp(digest, m->digest)!=0)){ - mesgfreeparts(m); - free(m); - return 0; - } - if(mbox->tail != nil) - mbox->tail->next = m; - mbox->tail = m; - if(mbox->head == nil) - mbox->head = m; - - if (m->level != 1){ - m->recursed = 1; - readmbox(m, dir, m->name); - } - return 1; -} - -int -thisyear(char *year) -{ - Tzone *tz; + char *info, *p; + int ninfo; + Mesg *m; Tm tm; - /* ignore errors: screwed means utc */ - tz = tzload("local"); - tmnow(&tm, tz); - return atoi(year) == (tm.year + 1900); -} - -char* -stripdate(char *as) -{ - int n; - char *s, *fld[10]; - - as = estrdup(as); - s = estrdup(as); - n = tokenize(s, fld, 10); - if(n > 5){ - sprint(as, "%.3s ", fld[0]); /* day */ - /* some dates have 19 Apr, some Apr 19 */ - if(strlen(fld[1])<4 && isnumeric(fld[1])) - sprint(as+strlen(as), "%.3s %.3s ", fld[1], fld[2]); /* date, month */ - else - sprint(as+strlen(as), "%.3s %.3s ", fld[2], fld[1]); /* date, month */ - /* do we use time or year? depends on whether year matches this one */ - if(thisyear(fld[5])){ - if(strchr(fld[3], ':') != nil) - sprint(as+strlen(as), "%.5s ", fld[3]); /* time */ - else if(strchr(fld[4], ':') != nil) - sprint(as+strlen(as), "%.5s ", fld[4]); /* time */ - }else - sprint(as+strlen(as), "%.4s ", fld[5]); /* year */ - } - free(s); - return as; -} - -char* -readfile(char *dir, char *name, int *np) -{ - char *file, *data; - int fd, len; - Dir *d; - - if(np != nil) - *np = 0; - file = estrstrdup(dir, name); - fd = open(file, OREAD); - if(fd < 0) + m = emalloc(sizeof(Mesg)); + m->name = estrjoin(name, "/", nil); + if((info = rslurp(m, "info", &ninfo)) == nil){ + free(m->name); + free(m); return nil; - d = dirfstat(fd); - free(file); - len = 0; - if(d != nil) - len = d->length; - free(d); - data = emalloc(len+1); - read(fd, data, len); - close(fd); - if(np != nil) - *np = len; - return data; -} - -int -writefile(char *dir, char *name, char *s) -{ - char *e, *file; - int fd, n; - - file = estrstrdup(dir, name); -// fprint(2, "writefile %s [%s]\n", file, s); - fd = open(file, OWRITE); - if(fd < 0) - return -1; - for(e = s + strlen(s); e - s > 0; s += n) - if((n = write(fd, s, e - s)) <= 0) - break; - close(fd); - return s == e? 0: -1; -} - - -void -setflags(Message *m, char *f) -{ - char *flgchar = "aDdfrsS"; - char *t, *p; - int rm; - - t = smprint("%s/%s", mbox.name, m->name); - writefile(t, "flags", f); - free(t); - for(; *f; f++){ - rm = 0; - if(*f == '-'){ - rm = 1; - f++; - } - if((p = strchr(flgchar, *f)) != nil) - m->flags[p - flgchar] = rm ? '-' : *f; - } -} - -void -fmtflags(char *b, char *flg) -{ - *b++ = '['; - *b++ = (strchr(flg, 's') == nil) ? '*' : ' '; /* unread */ - *b++ = (strchr(flg, 'a') == nil) ? ' ' : 'R'; /* answered */ - *b++ = ']'; - *b = '\0'; -} - -char* -info(Message *m, int ind, int ogf) -{ - char *i; - int j, len, lens; - char *p; - char fmt[80], s[80], flg[16]; - - if (ogf) - p=m->to; - else - p=m->fromcolon; - - fmtflags(flg, m->flags); - if(ind==0 && altmenu){ - len = 12; - lens = 20; - - if(ind==0 && m->subject[0]=='\0'){ - snprint(fmt, sizeof fmt, - "\t%%-%d.%ds\t%%-12.12s\t", len, len); - snprint(s, sizeof s, fmt, p, stripdate(m->date) + 4); - }else{ - snprint(fmt, sizeof fmt, - "\t%%-%d.%ds\t%%-12.12s\t%%-%d.%ds", len, len, lens, lens); - snprint(s, sizeof s, fmt, p, stripdate(m->date) + 4, m->subject); - } - i = estrdup(s); - - return i; } - if(ind==0 && shortmenu){ - len = 30; - lens = 30; - if(shortmenu > 1){ - len = 10; - lens = 25; - } - if(ind==0 && m->subject[0]=='\0'){ - snprint(fmt, sizeof fmt, " %%-%d.%ds", len, len); - snprint(s, sizeof s, fmt, p); - }else{ - snprint(fmt, sizeof fmt, " %%-%d.%ds %%-%d.%ds", len, len, lens, lens); - snprint(s, sizeof s, fmt, p, m->subject); - } - i = estrdup(s); - - return i; - } - - i = estrdup(""); - i = eappend(i, " ", flg); - i = eappend(i, "\t", p); - i = egrow(i, "\t", stripdate(m->date)); - if(ind == 0){ - if(strcmp(m->type, "text")!=0 && strncmp(m->type, "text/", 5)!=0 && - strncmp(m->type, "multipart/", 10)!=0) - i = egrow(i, "\t(", estrstrdup(m->type, ")")); - }else if(strncmp(m->type, "multipart/", 10) != 0) - i = egrow(i, "\t(", estrstrdup(m->type, ")")); - if(m->subject[0] != '\0'){ - i = eappend(i, "\n", nil); - for(j=0; jsubject); - } - return i; -} - -void -mesgmenu0(Window *w, Message *mbox, char *realdir, char *dir, int ind, Biobuf *fd, int onlyone, int dotail) -{ - int i; - Message *m; - char *name, *tmp; - int ogf=0; - - if(strstr(realdir, "outgoing") != nil) - ogf=1; - - /* show mail box in reverse order, pieces in forward order */ - if(ind > 0) - m = mbox->head; - else - m = mbox->tail; - while(m != nil){ - for(i=0; iname); - tmp = info(m, ind, ogf); - Bprint(fd, "%s%s\n", name, tmp); - free(tmp); - if(dotail && m->tail) - mesgmenu0(w, m, realdir, name, ind+1, fd, 0, dotail); - free(name); - if(ind) - m = m->next; - else - m = m->prev; - if(onlyone) - m = nil; - } -} - -void -mesgmenu(Window *w, Message *mbox) -{ - winopenbody(w, OWRITE); - mesgmenu0(w, mbox, mbox->name, "", 0, w->body, 0, !shortmenu); - winclosebody(w); -} - -/* one new message has arrived, as mbox->tail */ -void -mesgmenunew(Window *w, Message *mbox) -{ - Biobuf *b; - - winselect(w, "0", 0); - w->data = winopenfile(w, "data"); - b = emalloc(sizeof(Biobuf)); - Binit(b, w->data, OWRITE); - mesgmenu0(w, mbox, mbox->name, "", 0, b, 1, !shortmenu); - Bterm(b); - free(b); - if(!mbox->dirty) - winclean(w); - /* select tag line plus following indented lines, but not final newline (it's distinctive) */ - winselect(w, "0/.*\\n((\t.*\\n)*\t.*)?/", 1); - close(w->addr); - close(w->data); - w->addr = -1; - w->data = -1; -} - -char* -name2regexp(char *prefix, char *s) -{ - char *buf, *p, *q; - - buf = emalloc(strlen(prefix)+2*strlen(s)+50); /* leave room to append more */ - p = buf; - *p++ = '0'; - *p++ = '/'; - *p++ = '^'; - strcpy(p, prefix); - p += strlen(prefix); - for(q=s; *q!='\0'; q++){ - if(strchr(regexchars, *q) != nil) - *p++ = '\\'; - *p++ = *q; - } - *p++ = '/'; - *p = '\0'; - return buf; -} - -void -mesgmenumarkdel(Window *w, Message *mbox, Message *m, int writeback) -{ - char *buf; - - - if(m->deleted) - return; - m->writebackdel = writeback; - if(w->data < 0) - w->data = winopenfile(w, "data"); - buf = name2regexp("", m->name); - strcat(buf, "-#0"); - if(winselect(w, buf, 1)) - write(w->data, deleted, 10); - free(buf); - close(w->data); - close(w->addr); - w->addr = w->data = -1; - mbox->dirty = 1; - m->deleted = 1; -} - -void -mesgmenumarkundel(Window *w, Message*, Message *m) -{ - char *buf; - - if(m->deleted == 0) - return; - if(w->data < 0) - w->data = winopenfile(w, "data"); - buf = name2regexp(deletedrx, m->name); - if(winselect(w, buf, 1)) - if(winsetaddr(w, deletedaddr, 1)) - write(w->data, "", 0); - free(buf); - close(w->data); - close(w->addr); - w->addr = w->data = -1; - m->deleted = 0; -} - -void -mesgmenudel(Window *w, Message *mbox, Message *m) -{ - char *buf; - - if(w->data < 0) - w->data = winopenfile(w, "data"); - buf = name2regexp(deletedrx, m->name); - if(winsetaddr(w, buf, 1) && winsetaddr(w, ".,./.*\\n(\t.*\\n)*/", 1)) - write(w->data, "", 0); - free(buf); - close(w->data); - close(w->addr); - w->addr = w->data = -1; - mbox->dirty = 1; - m->deleted = 1; -} - -void -mesgmenureflag(Window *w, Message *m) -{ - char *buf, flg[16]; - - fmtflags(flg, m->flags); - buf = name2regexp(deletedrx01, m->name); - strcat(buf, "+/\\[[^\\]]*\\]/"); - if(w->data < 0) - w->data = winopenfile(w, "data"); - if(winselect(w, buf, 1)) - write(w->data, flg, strlen(flg)); - free(buf); - - close(w->data); - w->data = -1; -} - -void -mesgmenumark(Window *w, char *which, char *mark) -{ - char *buf; - - if(w->data < 0) - w->data = winopenfile(w, "data"); - buf = name2regexp(deletedrx01, which); - if(winsetaddr(w, buf, 1) && winsetaddr(w, "+0-#1", 1)) /* go to end of line */ - write(w->data, mark, strlen(mark)); - free(buf); - close(w->data); - close(w->addr); - w->addr = w->data = -1; - if(!mbox.dirty) - winclean(w); -} - -void -mesgfreeparts(Message *m) -{ - free(m->name); - free(m->replyname); - free(m->fromcolon); - 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->filename); - free(m->flags); - free(m->digest); -} - -void -mesgdel(Message *mbox, Message *m) -{ - Message *n, *next; - - if(m->opened) - error("internal error: deleted message still open in mesgdel"); - /* delete subparts */ - for(n=m->head; n!=nil; n=next){ - next = n->next; - mesgdel(m, n); - } - /* remove this message from list */ - if(m->next) - m->next->prev = m->prev; - else - mbox->tail = m->prev; - if(m->prev) - m->prev->next = m->next; - else - mbox->head = m->next; - - mesgfreeparts(m); -} - -int -deliver(char *folder, char *file) -{ - char *av[4]; - int pid, wpid, nz; - Waitmsg *w; - - pid = fork(); - switch(pid){ - case -1: - return -1; - case 0: - av[0] = "mbappend"; - av[1] = folder; - av[2] = file; - av[3] = 0; - exec("/bin/upas/mbappend", av); - _exits("b0rked"); - return -1; - default: - while(w = wait()){ - nz = !w->msg || !w->msg[0]; - if(!nz) - werrstr("%s", w->msg); - wpid = w->pid; - free(w); - if(wpid == pid) - return nz? 0: -1; - } - return -1; - } -} - -int -mesgsave(Message *m, char *s) -{ - char *t; - int ret; - - t = smprint("%s/%s/rawunix", mbox.name, m->name); - if(s[0] != '/') - s = estrdup(s); - else - s = smprint("%s/%s", mailboxdir, s); - ret = 1; - if(deliver(s, t) == -1){ - fprint(2, "Mail: save failed: can't write %s: %r\n", s); - ret = 0; - } - setflags(m, "S"); - free(s); - free(t); - return ret; -} - -int -mesgcommand(Message *m, char *cmd) -{ - char *s; - char *args[10]; - int ok, ret, nargs; - - s = cmd; - ret = 1; - nargs = tokenize(s, args, nelem(args)); - if(nargs == 0) - return 0; - if(strcmp(args[0], "Post") == 0){ - mesgsend(m); - goto Return; - } - if(strncmp(args[0], "Save", 4) == 0){ - if(m->isreply) - goto Return; - s = estrdup("\t[saved"); - if(nargs==1 || strcmp(args[1], "")==0){ - ok = mesgsave(m, "stored"); - }else{ - ok = mesgsave(m, args[1]); - s = eappend(s, " ", args[1]); - } - if(ok){ - s = egrow(s, "]", nil); - mesgmenumark(mbox.w, m->name, s); - } - free(s); - setflags(m, "S"); - goto Return; - } - if(strcmp(args[0], "Reply")==0){ - if(nargs>=2 && strcmp(args[1], "all")==0) - mkreply(m, "Replyall", nil, nil, nil); - else - mkreply(m, "Reply", nil, nil, nil); - goto Return; - } - if(strcmp(args[0], "Q") == 0){ - s = winselection(m->w); /* will be freed by mkreply */ - if(nargs>=3 && strcmp(args[1], "Reply")==0 && strcmp(args[2], "all")==0) - mkreply(m, "QReplyall", nil, nil, s); - else - mkreply(m, "QReply", nil, nil, s); - goto Return; - } - if(strcmp(args[0], "Del") == 0){ - if(windel(m->w, 0)){ - chanfree(m->w->cevent); - free(m->w); - m->w = nil; - if(m->isreply) - delreply(m); - else{ - m->opened = 0; - m->tagposted = 0; - } - free(cmd); - threadexits(nil); - } - goto Return; - } - if(strcmp(args[0], "Delmesg") == 0){ - if(!m->isreply){ - mesgmenumarkdel(wbox, &mbox, m, 1); - free(cmd); /* mesgcommand might not return */ - mesgcommand(m, estrdup("Del")); - return 1; - } -// setflags(m, "d"); - goto Return; - } - if(strcmp(args[0], "UnDelmesg") == 0){ - if(!m->isreply && m->deleted) - mesgmenumarkundel(wbox, &mbox, m); -// setflags(m, "-d"); - goto Return; - } -// if(strcmp(args[0], "Headers") == 0){ -// m->showheaders(); -// return True; -// } - - ret = 0; - - Return: - free(cmd); - return ret; -} - -void -mesgtagpost(Message *m) -{ - if(m->tagposted) - return; - wintagwrite(m->w, " Post", 5); - m->tagposted = 1; -} - -/* need to expand selection more than default word */ -#pragma varargck argpos eval 2 - -long -eval(Window *w, char *s, ...) -{ - char buf[64]; - va_list arg; - - va_start(arg, s); - vsnprint(buf, sizeof buf, s, arg); - va_end(arg); - - if(winsetaddr(w, buf, 1)==0) - return -1; - - if(pread(w->addr, buf, 24, 0) != 24) - return -1; - return strtol(buf, 0, 10); -} - -int -isemail(char *s) -{ - int nat; - - nat = 0; - for(; *s; s++) - if(*s == '@') - nat++; - else if(!isalpha(*s) && !isdigit(*s) && !strchr("_.-+/", *s)) - return 0; - return nat==1; -} - -char addrdelim[] = "/[ \t\\n<>()\\[\\]]/"; -char* -expandaddr(Window *w, Event *e) -{ - char *s; - long q0, q1; - - if(e->q0 != e->q1) /* cannot happen */ - return nil; - - q0 = eval(w, "#%d-%s", e->q0, addrdelim); - if(q0 == -1) /* bad char not found */ - q0 = 0; - else /* increment past bad char */ - q0++; - - q1 = eval(w, "#%d+%s", e->q0, addrdelim); - if(q1 < 0){ - q1 = eval(w, "$"); - if(q1 < 0) - return nil; - } - if(q0 >= q1) - return nil; - s = emalloc((q1-q0)*UTFmax+1); - winread(w, q0, q1, s); - return s; -} - -int -replytoaddr(Window *w, Message *m, Event *e, char *s) -{ - int did; - char *buf; - Plumbmsg *pm; - - buf = nil; - did = 0; - if(e->flag & 2){ - /* autoexpanded; use our own bigger expansion */ - buf = expandaddr(w, e); - if(buf == nil) - return 0; - s = buf; - } - if(isemail(s)){ - did = 1; - pm = emalloc(sizeof(Plumbmsg)); - pm->src = estrdup("Mail"); - pm->dst = estrdup("sendmail"); - pm->data = estrdup(s); - pm->ndata = -1; - if(m->subject && m->subject[0]){ - pm->attr = emalloc(sizeof(Plumbattr)); - pm->attr->name = estrdup("Subject"); - if(tolower(m->subject[0]) != 'r' || tolower(m->subject[1]) != 'e' || m->subject[2] != ':') - pm->attr->value = estrstrdup("Re: ", m->subject); - else - pm->attr->value = estrdup(m->subject); - pm->attr->next = nil; - } - if(plumbsend(plumbsendfd, pm) < 0) - fprint(2, "error writing plumb message: %r\n"); - plumbfree(pm); - } - free(buf); - return did; -} - - -void -mesgctl(void *v) -{ - Message *m; - Window *w; - Event *e, *eq, *e2, *ea; - int na, nopen, i, j; - char *os, *s, *t, *buf; - - m = v; - w = m->w; - threadsetname("mesgctl"); - proccreate(wineventproc, w, STACK); - for(;;){ - e = recvp(w->cevent); - switch(e->c1){ - default: - Unk: - print("unknown message %c%c\n", e->c1, e->c2); - break; - - case 'E': /* write to body; can't affect us */ - break; - - case 'F': /* generated by our actions; ignore */ - break; - - case 'K': /* type away; we don't care */ - case 'M': - switch(e->c2){ - case 'x': /* mouse only */ - case 'X': - ea = nil; - eq = e; - if(e->flag & 2){ - e2 = recvp(w->cevent); - eq = e2; - } - if(e->flag & 8){ - ea = recvp(w->cevent); - recvp(w->cevent); - na = ea->nb; - }else - na = 0; - if(eq->q1>eq->q0 && eq->nb==0){ - s = emalloc((eq->q1-eq->q0)*UTFmax+1); - winread(w, eq->q0, eq->q1, s); - }else - s = estrdup(eq->b); - if(na){ - t = emalloc(strlen(s)+1+na+1); - sprint(t, "%s %s", s, ea->b); - free(s); - s = t; - } - if(!mesgcommand(m, s)) /* send it back */ - winwriteevent(w, e); - break; - - case 'l': /* mouse only */ - case 'L': - buf = nil; - eq = e; - if(e->flag & 2){ - e2 = recvp(w->cevent); - eq = e2; - } - s = eq->b; - if(eq->q1>eq->q0 && eq->nb==0){ - buf = emalloc((eq->q1-eq->q0)*UTFmax+1); - winread(w, eq->q0, eq->q1, buf); - s = buf; - } - os = s; - nopen = 0; - do{ - /* skip mail box name if present */ - if(strncmp(s, mbox.name, strlen(mbox.name)) == 0) - s += strlen(mbox.name); - if(strstr(s, "body") != nil){ - /* strip any known extensions */ - for(i=0; exts[i].ext!=nil; i++){ - j = strlen(exts[i].ext); - if(strlen(s)>j && strcmp(s+strlen(s)-j, exts[i].ext)==0){ - s[strlen(s)-j] = '\0'; - break; - } - } - if(strlen(s)>5 && strcmp(s+strlen(s)-5, "/body")==0) - s[strlen(s)-4] = '\0'; /* leave / in place */ - } - nopen += mesgopen(&mbox, mbox.name, s, m, 0, nil); - while(*s!=0 && *s++!='\n') - ; - }while(*s); - if(nopen == 0 && e->c1 == 'L') - nopen += replytoaddr(w, m, e, os); - if(nopen == 0) - winwriteevent(w, e); - free(buf); - break; - - case 'I': /* modify away; we don't care */ - case 'D': - mesgtagpost(m); - /* fall through */ - case 'd': - case 'i': - break; - - default: - goto Unk; - } - } - } -} - -void -mesgline(Message *m, char *header, char *value) -{ - if(strlen(value) > 0) - Bprint(m->w->body, "%s: %s\n", header, value); -} - -int -isprintable(char *type) -{ - int i; - - for(i=0; goodtypes[i]!=nil; i++) - if(strcmp(type, goodtypes[i])==0) - return 1; - return 0; -} - -char* -ext(char *type) -{ - int i; - - for(i=0; exts[i].type!=nil; i++) - if(strcmp(type, exts[i].type)==0) - return exts[i].ext; - return ""; -} - -void -mimedisplay(Message *m, char *name, char *rootdir, Window *w, int fileonly) -{ - char *dest, *maildest; - - if(strcmp(m->disposition, "file")==0 || strlen(m->filename)!=0){ - if(strlen(m->filename) == 0){ - dest = estrdup(m->name); - dest[strlen(dest)-1] = '\0'; - }else - dest = estrdup(m->filename); - if(maildest = getenv("maildest")){ - maildest = eappend(maildest, "/", dest); - Bprint(w->body, "\tcp %s%sbody%s %q\n", rootdir, name, ext(m->type), maildest); - free(maildest); - } - if(m->filename[0] != '/') - dest = egrow(estrdup(home), "/", dest); - Bprint(w->body, "\tcp %s%sbody%s %q\n", rootdir, name, ext(m->type), dest); - free(dest); - }else if(!fileonly) - Bprint(w->body, "\tfile is %s%sbody%s\n", rootdir, name, ext(m->type)); -} - -void -printheader(char *dir, Biobuf *b, char **okheaders) -{ - char *s; - char *lines[100]; - int i, j, n; - - s = readfile(dir, "header", nil); - if(s == nil) - return; - n = getfields(s, lines, nelem(lines), 0, "\n"); - for(i=0; itype, "message/rfc822") != 0){ /* suppress headers of envelopes */ - if(strlen(m->from) > 0){ - Bprint(w->body, "From: %s\n", m->from); - mesgline(m, "Date", m->date); - mesgline(m, "To", m->to); - mesgline(m, "CC", m->cc); - mesgline(m, "Subject", m->subject); - printheader(dir, w->body, extraheaders); - }else{ - printheader(dir, w->body, okheaders); - printheader(dir, w->body, extraheaders); - } - Bprint(w->body, "\n"); - } - - if(m->level == 1 && m->recursed == 0){ - m->recursed = 1; - readmbox(m, rootdir, m->name); - } - if(m->head == nil){ /* single part message */ - if(strcmp(m->type, "text")==0 || strncmp(m->type, "text/", 5)==0){ - mimedisplay(m, m->name, rootdir, w, 1); - s = readbody(m->type, dir, &n); - winwritebody(w, s, n); - free(s); - }else - mimedisplay(m, m->name, rootdir, w, 0); - }else{ - /* multi-part message, either multipart/* or message/rfc822 */ - thisone = nil; - if(strcmp(m->type, "multipart/alternative") == 0){ - thisone = m->head; /* in case we can't find a good one */ - for(mp=m->head; mp!=nil; mp=mp->next) - if(isprintable(mp->type)){ - thisone = mp; - break; - } - } - for(mp=m->head; mp!=nil; mp=mp->next){ - if(thisone!=nil && mp!=thisone) - continue; - subdir = estrstrdup(dir, mp->name); - name = estrstrdup(file, mp->name); - /* skip first element in name because it's already in window name */ - if(mp != m->head) - Bprint(w->body, "\n===> %s (%s) [%s]\n", strchr(name, '/')+1, mp->type, mp->disposition); - if(strcmp(mp->type, "text")==0 || strncmp(mp->type, "text/", 5)==0){ - mimedisplay(mp, name, rootdir, w, 1); - printheader(subdir, w->body, okheaders); - printheader(subdir, w->body, extraheaders); - winwritebody(w, "\n", 1); - s = readbody(mp->type, subdir, &n); - winwritebody(w, s, n); - free(s); - }else{ - if(strncmp(mp->type, "multipart/", 10)==0 || strcmp(mp->type, "message/rfc822")==0){ - mp->w = w; - mesgload(mp, rootdir, name, w); - mp->w = nil; - }else - mimedisplay(mp, name, rootdir, w, 0); - } - free(name); - free(subdir); - } - } - free(dir); -} - -int -tokenizec(char *str, char **args, int max, char *splitc) -{ - int na; - int intok = 0; - - if(max <= 0) - return 0; - for(na=0; *str != '\0';str++){ - if(strchr(splitc, *str) == nil){ - if(intok) - continue; - args[na++] = str; - intok = 1; - }else{ - /* it's a separator/skip character */ - *str = '\0'; - if(intok){ - intok = 0; - if(na >= max) - break; - } - } - } - return na; -} - -Message* -mesglookup(Message *mbox, char *name, char *digest) -{ - int n; - Message *m; - char *t; - - if(digest){ - /* can find exactly */ - for(m=mbox->head; m!=nil; m=m->next) - if(strcmp(digest, m->digest) == 0) - break; - return m; - } - - n = strlen(name); - if(n == 0) - return nil; - if(name[n-1] == '/') - t = estrdup(name); - else - t = estrstrdup(name, "/"); - for(m=mbox->head; m!=nil; m=m->next) - if(strcmp(t, m->name) == 0) - break; - free(t); + 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; } -/* - * Find plumb port, knowing type is text, given file name (by extension) - */ -int -plumbportbysuffix(char *file) +static Mesg* +readparts(Mesg *m) { - char *suf; - int i, nsuf, nfile; + char *dpath, *apath; + int n, i, dfd; + Mesg *a, *sub; + Dir *d; - nfile = strlen(file); - for(i=0; ports[i].type!=nil; i++){ - suf = ports[i].suffix; - nsuf = strlen(suf); - if(nfile > nsuf) - if(cistrncmp(file+nfile-nsuf, suf, nsuf) == 0) - return i; + 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(a); + if(sub != a) + m->body = sub; + continue; + } + if(m->nparts >= m->xparts) + m->parts = erealloc(m->parts, (2 + m->nparts*2)*sizeof(Mesg*)); + m->parts[m->nparts++] = a; + if(m->body == nil && strcmp(a->type, "text/plain") == 0) + m->body = a; + else if(m->body == nil && strcmp(a->type, "text/html") == 0) + m->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; } -/* - * Find plumb port using type and file name (by extension) - */ -int -plumbport(char *type, char *file) +static void +reply(Mesg *m, char **f, int nf) { - int i; - - for(i=0; ports[i].type!=nil; i++) - if(strncmp(type, ports[i].type, strlen(ports[i].type)) == 0) - return i; - /* see if it's a text type */ - for(i=0; goodtypes[i]!=nil; i++) - if(strncmp(type, goodtypes[i], strlen(goodtypes[i])) == 0) - return plumbportbysuffix(file); - return -1; + if(nf >= 1 && strcmp(f[0], "all") != 0) + compose(m->replyto, m, 1); + else + compose(m->replyto, m, 0); } -void -plumb(Message *m, char *dir) +static void +delmesg(Mesg *m, char **, int nf) { - int i; - char *port; - Plumbmsg *pm; - - if(strlen(m->type) == 0) + if(nf != 0){ + fprint(2, "Delmesg: too many args\n"); return; - i = plumbport(m->type, m->filename); - if(i < 0) - fprint(2, "can't find destination for message subpart\n"); - else{ - port = ports[i].port; - pm = emalloc(sizeof(Plumbmsg)); - pm->src = estrdup("Mail"); - if(port) - pm->dst = estrdup(port); - else - pm->dst = nil; - pm->wdir = nil; - pm->type = estrdup("text"); - pm->ndata = -1; - pm->data = estrstrdup(dir, "body"); - pm->data = eappend(pm->data, "", ports[i].suffix); - if(plumbsend(plumbsendfd, pm) < 0) - fprint(2, "error writing plumb message: %r\n"); - plumbfree(pm); } + 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 -mesgopen(Message *mbox, char *dir, char *s, Message *mesg, int plumbed, char *digest) +mesgflagparse(char *fstr, int *add) { - char *t, *u, *v; - Message *m; - char *direlem[10]; - int i, ndirelem, reuse; + int flg; - /* find white-space-delimited first word */ - for(t=s; *t!='\0' && !isspace(*t); t++) - ; - u = emalloc(t-s+1); - memmove(u, s, t-s); - /* separate it on slashes */ - ndirelem = tokenizec(u, direlem, nelem(direlem), "/"); - if(ndirelem <= 0){ - Error: - free(u); - return 0; - } - if(plumbed){ - write(wctlfd, "top", 3); - write(wctlfd, "current", 7); - } - /* open window for message */ - m = mesglookup(mbox, direlem[0], digest); - if(m == nil) - goto Error; - if(mesg!=nil && m!=mesg) /* string looked like subpart but isn't part of this message */ - goto Error; - if(m->opened == 0){ - if(m->w == nil){ - reuse = 0; - m->w = newwindow(); - }else{ - reuse = 1; - /* re-use existing window */ - if(winsetaddr(m->w, "0,$", 1)){ - if(m->w->data < 0) - m->w->data = winopenfile(m->w, "data"); - write(m->w->data, "", 0); - } - } - v = estrstrdup(mbox->name, m->name); - winname(m->w, v); - free(v); - if(!reuse){ - if(m->deleted) - wintagwrite(m->w, "Q Reply all UnDelmesg Save ", 2+6+4+10+5); - else - wintagwrite(m->w, "Q Reply all Delmesg Save ", 2+6+4+8+5); - } - threadcreate(mesgctl, m, STACK); - winopenbody(m->w, OWRITE); - mesgload(m, dir, m->name, m->w); - winclosebody(m->w); - winclean(m->w); - m->opened = 1; - setflags(m, "s"); - mesgmenureflag(mbox->w, m); - if(ndirelem == 1){ - free(u); - return 1; - } - } - if(ndirelem == 1 && plumbport(m->type, m->filename) <= 0){ - /* make sure dot is visible */ - ctlprint(m->w->ctl, "show\n"); - return 0; - } - /* walk to subpart */ - dir = estrstrdup(dir, m->name); - for(i=1; iname, nil); + 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; + } } - if(m != nil && plumbport(m->type, m->filename) > 0) - plumb(m, dir); - free(dir); - free(u); - return 1; + return flg; } void -rewritembox(Window *w, Message *mbox) +mesgpath2name(char *buf, int nbuf, char *name) { - Message *m, *next; - char *deletestr, *t; - int nopen; + char *p, *e; + int n; - deletestr = estrstrdup("delete ", fsname); - - nopen = 0; - for(m=mbox->head; m!=nil; m=next){ - next = m->next; - if(m->deleted == 0) - continue; - if(m->opened){ - nopen++; - continue; - } - if(m->writebackdel){ - /* messages deleted by plumb message are not removed again */ - t = estrdup(m->name); - if(strlen(t) > 0) - t[strlen(t)-1] = '\0'; - deletestr = egrow(deletestr, " ", t); - } - mesgmenudel(w, mbox, m); - mesgdel(mbox, m); - } - if(write(mbox->ctlfd, deletestr, strlen(deletestr)) < 0) - fprint(2, "Mail: warning: error removing mail message files: %r\n"); - free(deletestr); - winselect(w, "0", 0); - if(nopen == 0) - winclean(w); - mbox->dirty = 0; + 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; } -/* name is a full file name, but it might not belong to us */ -Message* -mesglookupfile(Message *mbox, char *name, char *digest) +int +mesgmatch(Mesg *m, char *name, char *digest) { - int k, n; + if(!(m->state & Sdummy) && strcmp(m->name, name) == 0) + return digest == nil || strcmp(m->digest, digest) == 0; + return 0; +} - k = strlen(name); - n = strlen(mbox->name); - if(k==0 || strncmp(name, mbox->name, n) != 0){ -// fprint(2, "Mail: message %s not in this mailbox\n", name); +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); } - return mesglookup(mbox, name+n, digest); + threadcreate(mesgmain, m, Stack); + return m; +} + +Biobuf* +mesgopenbody(Mesg *m) +{ + char *path; + int rfd; + Mesg *b; + + b = readparts(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); } diff --git a/sys/src/cmd/upas/Mail/mkfile b/sys/src/cmd/upas/Mail/mkfile index 7ddbe41ee..ea74698ea 100644 --- a/sys/src/cmd/upas/Mail/mkfile +++ b/sys/src/cmd/upas/Mail/mkfile @@ -1,25 +1,14 @@ syms - $CC -aa mesg.c reply.c util.c win.c >>syms diff --git a/sys/src/cmd/upas/Mail/reply.c b/sys/src/cmd/upas/Mail/reply.c deleted file mode 100644 index 9a04ed5c9..000000000 --- a/sys/src/cmd/upas/Mail/reply.c +++ /dev/null @@ -1,578 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include "dat.h" - -static int replyid; - -int -quote(Message *m, Biobuf *b, char *dir, char *quotetext) -{ - char *body, *type; - int i, n, nlines; - char **lines; - - if(quotetext){ - body = quotetext; - n = strlen(body); - type = nil; - }else{ - /* look for first textual component to quote */ - type = readfile(dir, "type", &n); - if(type == nil){ - print("no type in %s\n", dir); - return 0; - } - if(strncmp(type, "multipart/", 10)==0 || strncmp(type, "message/", 8)==0){ - dir = estrstrdup(dir, "1/"); - if(quote(m, b, dir, nil)){ - free(type); - free(dir); - return 1; - } - free(dir); - } - if(strncmp(type, "text", 4) != 0){ - free(type); - return 0; - } - body = readbody(m->type, dir, &n); - if(body == nil) - return 0; - } - nlines = 0; - for(i=0; i%s%s\n", lines[i][0]=='>'? "" : " ", lines[i]); - i++; - } - free(lines); - free(body); /* will free quotetext if non-nil */ - free(type); - return 1; -} - -void -mkreply(Message *m, char *label, char *to, Plumbattr *attr, char *quotetext) -{ - Message *r; - char *dir, *t; - int quotereply; - Plumbattr *a; - - quotereply = (label[0] == 'Q'); - r = emalloc(sizeof(Message)); - r->isreply = 1; - if(m != nil){ - r->replyname = estrdup(m->name); - r->replydigest = estrdup(m->digest); - } - r->next = replies.head; - r->prev = nil; - if(replies.head != nil) - replies.head->prev = r; - replies.head = r; - if(replies.tail == nil) - replies.tail = r; - r->name = emalloc(strlen(mbox.name)+strlen(label)+10); - sprint(r->name, "%s%s%d", mbox.name, label, ++replyid); - r->w = newwindow(); - winname(r->w, r->name); - ctlprint(r->w->ctl, "cleartag"); - wintagwrite(r->w, "fmt Look Post Undo", 4+5+5+4); - r->tagposted = 1; - threadcreate(mesgctl, r, STACK); - winopenbody(r->w, OWRITE); - if(to!=nil && to[0]!='\0') - Bprint(r->w->body, "%s\n", to); - for(a=attr; a; a=a->next) - Bprint(r->w->body, "%s: %s\n", a->name, a->value); - dir = nil; - if(m != nil){ - dir = estrstrdup(mbox.name, m->name); - if(to == nil && attr == nil){ - /* Reply goes to replyto; Reply all goes to From and To and CC */ - if(strstr(label, "all") == nil) - Bprint(r->w->body, "To: %s\n", m->replyto); - else{ /* Replyall */ - if(strlen(m->from) > 0) - Bprint(r->w->body, "To: %s\n", m->from); - if(strlen(m->to) > 0) - Bprint(r->w->body, "To: %s\n", m->to); - if(strlen(m->cc) > 0) - Bprint(r->w->body, "CC: %s\n", m->cc); - } - } - if(strlen(m->subject) > 0){ - t = "Subject: Re: "; - if(strlen(m->subject) >= 3) - if(tolower(m->subject[0])=='r' && tolower(m->subject[1])=='e' && m->subject[2]==':') - t = "Subject: "; - Bprint(r->w->body, "%s%s\n", t, m->subject); - } - if(!quotereply){ - Bprint(r->w->body, "Include: %sraw\n", dir); - free(dir); - } - } - Bprint(r->w->body, "\n"); - if(m == nil) - Bprint(r->w->body, "\n"); - else if(quotereply){ - quote(m, r->w->body, dir, quotetext); - free(dir); - } - winclosebody(r->w); - if(m==nil && (to==nil || to[0]=='\0')) - winselect(r->w, "0", 0); - else - winselect(r->w, "$", 0); - winclean(r->w); - windormant(r->w); -} - -void -delreply(Message *m) -{ - if(m->next == nil) - replies.tail = m->prev; - else - m->next->prev = m->prev; - if(m->prev == nil) - replies.head = m->next; - else - m->prev->next = m->next; - mesgfreeparts(m); - free(m); -} - -/* copy argv to stack and free the incoming strings, so we don't leak argument vectors */ -void -buildargv(char **inargv, char *argv[NARGS+1], char args[NARGCHAR]) -{ - int i, n; - char *s, *a; - - s = args; - for(i=0; i= NARGCHAR) /* too many characters */ - break; - argv[i] = s; - memmove(s, a, n); - s += n; - free(a); - } - argv[i] = nil; -} - -void -execproc(void *v) -{ - Exec *e; - int p[2], q[2]; - char *prog; - char *argv[NARGS+1], args[NARGCHAR]; - - e = v; - p[0] = e->p[0]; - p[1] = e->p[1]; - q[0] = e->q[0]; - q[1] = e->q[1]; - prog = e->prog; /* known not to be malloc'ed */ - rfork(RFFDG); - sendul(e->sync, 1); - buildargv(e->argv, argv, args); - free(e->argv); - chanfree(e->sync); - free(e); - dup(p[0], 0); - close(p[0]); - close(p[1]); - if(q[0]){ - dup(q[1], 1); - close(q[0]); - close(q[1]); - } - procexec(nil, prog, argv); -//fprint(2, "exec: %s", e->prog); -//{int i; -//for(i=0; argv[i]; i++) print(" '%s'", argv[i]); -//print("\n"); -//} -//argv[0] = "cat"; -//argv[1] = nil; -//procexec(nil, "/bin/cat", argv); - fprint(2, "Mail: can't exec %s: %r\n", prog); - threadexits("can't exec"); -} - -enum{ - ATTACH, - BCC, - CC, - FROM, - INCLUDE, - TO, -}; - -char *headers[] = { - "attach:", - "bcc:", - "cc:", - "from:", - "include:", - "to:", - nil, -}; - -int -whichheader(char *h) -{ - int i; - - for(i=0; headers[i]!=nil; i++) - if(cistrcmp(h, headers[i]) == 0) - return i; - return -1; -} - -char *tolist[200]; -char *cclist[200]; -char *bcclist[200]; -int ncc, nbcc, nto; -char *attlist[200]; -char included[200]; - -int -addressed(char *name) -{ - int i; - - for(i=0; i= 0) - write(ofd, s, m); - return n; -} - -int -write2(int fd, int ofd, char *buf, int n, int nofrom) -{ - char *from, *p; - int m = 0; - - if(fd >= 0) - m = write(fd, buf, n); - - if(ofd < 0) - return m; - - if(nofrom == 0) - return write(ofd, buf, n); - - /* need to escape leading From lines to avoid corrupting 'outgoing' mailbox */ - for(p=buf; *p; p+=m){ - from = cistrstr(p, "from"); - if(from == nil) - m = n; - else - m = from - p; - if(m > 0) - write(ofd, p, m); - if(from){ - /* escape with space if From is at start of line */ - if(p==buf || from[-1]=='\n') - write(ofd, " ", 1); - write(ofd, from, 4); - m += 4; - } - n -= m; - } - return p - buf; -} - -void -mesgsend(Message *m) -{ - char *s, *body, *to; - int i, j, h, n, natt, p[2]; - Exec *e; - Channel *sync; - Message *r; - int first, nfld, delit, ofd; - char *copy, *fld[100]; - Tzone *tz; - Tm now; - - body = winreadbody(m->w, &n); - /* assemble to: list from first line, to: line, and cc: line */ - nto = 0; - natt = 0; - ncc = 0; - nbcc = 0; - first = 1; - to = body; - for(;;){ - for(s=to; *s!='\n'; s++) - if(*s == '\0'){ - free(body); - return; - } - if(s++ == to) /* blank line */ - break; - /* make copy of line to tokenize */ - copy = emalloc(s-to); - memmove(copy, to, s-to); - copy[s-to-1] = '\0'; - nfld = tokenizec(copy, fld, nelem(fld), ", \t"); - if(nfld == 0){ - free(copy); - break; - } - n -= s-to; - switch(h = whichheader(fld[0])){ - case TO: - case FROM: - delit = 1; - commas(to+strlen(fld[0]), s-1); - for(i=1; i= 0){ - /* From dhog Fri Aug 24 22:13:00 +0500 2001 */ - tz = tzload("local"); - tmnow(&now, tz); - fprint(ofd, "From %s %τ", user, tmfmt(&now, "WW MMM _D hh:mm:ss Z YYYY")); - fprint(ofd, "From: %s\n", user); - fprint(ofd, "Date: %τ", tmfmt(&now, "WW MMM _D hh:mm:ss Z YYYY")); - for(i=0; ip[0] = p[0]; - e->p[1] = p[1]; - e->prog = "/bin/upas/marshal"; - e->argv = emalloc((1+1+2+4*natt+1)*sizeof(char*)); - e->argv[0] = estrdup("marshal"); - e->argv[1] = estrdup("-8"); - j = 2; - if(m->replyname){ - e->argv[j++] = estrdup("-R"); - e->argv[j++] = estrstrdup(mbox.name, m->replyname); - } - for(i=0; iargv[j++] = estrdup("-A"); - else - e->argv[j++] = estrdup("-a"); - e->argv[j++] = estrdup(attlist[i]); - } - sync = chancreate(sizeof(int), 0); - e->sync = sync; - proccreate(execproc, e, EXECSTACK); - recvul(sync); - close(p[0]); - - /* using marshal -8, so generate rfc822 headers */ - if(nto > 0){ - print2(p[1], ofd, "To: "); - for(i=0; i 0){ - print2(p[1], ofd, "CC: "); - for(i=0; i 0){ - print2(p[1], ofd, "BCC: "); - for(i=0; i 0) - write2(p[1], ofd, body, i, 1); - - /* guarantee a blank line, to ensure attachments are separated from body */ - if(i==0 || body[i-1]!='\n') - write2(p[1], ofd, "\n\n", 2, 0); - else if(i>1 && body[i-2]!='\n') - write2(p[1], ofd, "\n", 1, 0); - - /* these look like pseudo-attachments in the "outgoing" box */ - if(ofd>=0 && natt>0){ - for(i=0; i Include: %s\n", attlist[i]); - else - fprint(ofd, "=====> Attach: %s\n", attlist[i]); - } - if(ofd >= 0) - write(ofd, "\n", 1); - - for(i=0; ireplyname){ - if((r = mesglookup(&mbox, m->replyname, m->replydigest)) != nil){ - setflags(r, "a"); - mesgmenureflag(mbox.w, r); - } - } - if(m->name[0] == '/') - s = estrdup(m->name); - else - s = estrstrdup(mbox.name, m->name); - s = egrow(s, "-R", nil); - winname(m->w, s); - free(s); - winclean(m->w); - /* mark message unopened because it's no longer the original message */ - m->opened = 0; -} diff --git a/sys/src/cmd/upas/Mail/util.c b/sys/src/cmd/upas/Mail/util.c index c32de5feb..37bae7b6e 100644 --- a/sys/src/cmd/upas/Mail/util.c +++ b/sys/src/cmd/upas/Mail/util.c @@ -2,104 +2,139 @@ #include #include #include -#include -#include "dat.h" +#include -void* -emalloc(uint n) +#include "mail.h" + +void * +emalloc(ulong n) { - void *p; - - p = malloc(n); - if(p == nil) - error("can't malloc: %r"); - memset(p, 0, n); - setmalloctag(p, getcallerpc(&n)); - return p; + void *v; + + v = mallocz(n, 1); + if(v == nil) + sysfatal("malloc: %r"); + setmalloctag(v, getcallerpc(&n)); + return v; } -void* -erealloc(void *p, uint n) +void * +erealloc(void *p, ulong n) { - p = realloc(p, n); - if(p == nil) - error("can't realloc: %r"); - setmalloctag(p, getcallerpc(&n)); - return p; + void *v; + + v = realloc(p, n); + if(v == nil) + sysfatal("realloc: %r"); + setmalloctag(v, getcallerpc(&p)); + return v; } char* estrdup(char *s) { - char *t; - - t = emalloc(strlen(s)+1); - strcpy(t, s); - return t; -} - -char* -estrstrdup(char *s, char *t) -{ - char *u; - - u = emalloc(strlen(s)+strlen(t)+1); - strcpy(u, s); - strcat(u, t); - return u; -} - -char* -eappend(char *s, char *sep, char *t) -{ - char *u; - - if(t == nil) - u = estrstrdup(s, sep); - else{ - u = emalloc(strlen(s)+strlen(sep)+strlen(t)+1); - strcpy(u, s); - strcat(u, sep); - strcat(u, t); - } - free(s); - return u; -} - -char* -egrow(char *s, char *sep, char *t) -{ - s = eappend(s, sep, t); - free(t); + s = strdup(s); + if(s == nil) + sysfatal("strdup: %r"); + setmalloctag(s, getcallerpc(&s)); return s; } -void -error(char *fmt, ...) -{ - Fmt f; - char buf[64]; - va_list arg; - - fmtfdinit(&f, 2, buf, sizeof buf); - fmtprint(&f, "Mail: "); - va_start(arg, fmt); - fmtvprint(&f, fmt, arg); - va_end(arg); - fmtprint(&f, "\n"); - fmtfdflush(&f); - threadexitsall(fmt); -} - -void -ctlprint(int fd, char *fmt, ...) +char* +estrjoin(char *s, ...) { + va_list ap; + char *r, *t, *p, *e; int n; - va_list arg; - va_start(arg, fmt); - n = vfprint(fd, fmt, arg); - va_end(arg); - if(n <= 0) - error("control file write error: %r"); + va_start(ap, s); + n = strlen(s) + 1; + while((p = va_arg(ap, char*)) != nil) + n += strlen(p); + va_end(ap); + + r = emalloc(n); + e = r + n; + va_start(ap, s); + t = strecpy(r, e, s); + while((p = va_arg(ap, char*)) != nil) + t = strecpy(t, e, p); + va_end(ap); + return r; +} + +char* +esmprint(char *fmt, ...) +{ + char *s; + va_list ap; + + va_start(ap, fmt); + s = vsmprint(fmt, ap); + va_end(ap); + if(s == nil) + sysfatal("smprint: %r"); + setmalloctag(s, getcallerpc(&fmt)); + return s; +} + +char* +fslurp(int fd, int *nbuf) +{ + int n, sz, r; + char *buf; + + n = 0; + sz = 128; + buf = emalloc(sz); + while(1){ + r = read(fd, buf + n, sz - n); + if(r == 0) + break; + if(r == -1) + goto error; + n += r; + if(n == sz){ + sz += sz/2; + buf = erealloc(buf, sz); + } + } + buf[n] = 0; + if(nbuf) + *nbuf = n; + return buf; +error: + free(buf); + return nil; +} + +char * +rslurp(Mesg *m, char *f, int *nbuf) +{ + char *path; + int fd; + char *r; + + if(m == nil) + path = estrjoin(mbox.path, "/", f, nil); + else + path = estrjoin(mbox.path, "/", m->name, "/", f, nil); + fd = open(path, OREAD); + free(path); + if(fd == -1) + return nil; + r = fslurp(fd, nbuf); + close(fd); + return r; +} + +u32int +strhash(char *s) +{ + u32int h, c; + + h = 5381; + while(c = *s++ & 0xff) + h = ((h << 5) + h) + c; + return h; } diff --git a/sys/src/cmd/upas/Mail/win.c b/sys/src/cmd/upas/Mail/win.c index 53d4ed122..680f6722b 100644 --- a/sys/src/cmd/upas/Mail/win.c +++ b/sys/src/cmd/upas/Mail/win.c @@ -2,340 +2,280 @@ #include #include #include -#include -#include "dat.h" +#include -Window* -newwindow(void) +#include "mail.h" + +static int +procrd(Biobufhdr *f, void *buf, long len) { - char buf[12]; - Window *w; - - w = emalloc(sizeof(Window)); - w->ctl = open("/mnt/wsys/new/ctl", ORDWR|OCEXEC); - if(w->ctl<0 || read(w->ctl, buf, 12)!=12) - error("can't open window ctl file: %r"); - ctlprint(w->ctl, "noscroll\n"); - w->id = atoi(buf); - w->event = winopenfile(w, "event"); - w->addr = -1; /* will be opened when needed */ - w->body = nil; - w->data = -1; - w->cevent = chancreate(sizeof(Event*), 0); - return w; -} - -void -winsetdump(Window *w, char *dir, char *cmd) -{ - if(dir != nil) - ctlprint(w->ctl, "dumpdir %s\n", dir); - if(cmd != nil) - ctlprint(w->ctl, "dump %s\n", cmd); -} - -void -wineventproc(void *v) -{ - Window *w; - int i; - - w = v; - for(i=0; ; i++){ - if(i >= NEVENT) - i = 0; - wingetevent(w, &w->e[i]); - sendp(w->cevent, &w->e[i]); - } + return ioread(f->aux, f->fid, buf, len); } static int -winopenfile1(Window *w, char *f, int m) +procwr(Biobufhdr *f, void *buf, long len) { - char buf[64]; - int fd; - - sprint(buf, "/mnt/wsys/%d/%s", w->id, f); - fd = open(buf, m|OCEXEC); - if(fd < 0) - error("can't open window file %s: %r", f); - return fd; + return iowrite(f->aux, f->fid, buf, len); } -int -winopenfile(Window *w, char *f) +/* + * NB: this function consumes integers with + * a trailing space, as generated by acme; + * it's not a general purpose number parsing + * function. + */ +static int +evgetnum(Biobuf *f) { - return winopenfile1(w, f, ORDWR); -} - -void -wintagwrite(Window *w, char *s, int n) -{ - int fd; - - fd = winopenfile(w, "tag"); - if(write(fd, s, n) != n) - error("tag write: %r"); - close(fd); -} - -void -winname(Window *w, char *s) -{ - ctlprint(w->ctl, "name %s\n", s); -} - -void -winopenbody(Window *w, int mode) -{ - char buf[256]; - - sprint(buf, "/mnt/wsys/%d/body", w->id); - w->body = Bopen(buf, mode|OCEXEC); - if(w->body == nil) - error("can't open window body file: %r"); -} - -void -winclosebody(Window *w) -{ - if(w->body != nil){ - Bterm(w->body); - w->body = nil; - } -} - -void -winwritebody(Window *w, char *s, int n) -{ - if(w->body == nil) - winopenbody(w, OWRITE); - if(Bwrite(w->body, s, n) != n) - error("write error to window: %r"); -} - -int -wingetec(Window *w) -{ - if(w->nbuf == 0){ - w->nbuf = read(w->event, w->buf, sizeof w->buf); - if(w->nbuf <= 0){ - /* probably because window has exited, and only called by wineventproc, so just shut down */ - threadexits(nil); - } - w->bufp = w->buf; - } - w->nbuf--; - return *w->bufp++; -} - -int -wingeten(Window *w) -{ - int n, c; + int c, n; n = 0; - while('0'<=(c=wingetec(w)) && c<='9') + while('0'<=(c=Bgetc(f)) && c<='9') n = n*10+(c-'0'); - if(c != ' ') - error("event number syntax"); + if(c != ' '){ + werrstr("event number syntax: %c", c); + return -1; + } return n; } -int -wingeter(Window *w, char *buf, int *nb) +static int +evgetdata(Biobuf *f, Event *e) { + int i, n, o; Rune r; - int n; - r = wingetec(w); - buf[0] = r; - n = 1; - if(r >= Runeself) { - while(!fullrune(buf, n)) - buf[n++] = wingetec(w); - chartorune(&r, buf); - } - *nb = n; - return r; -} - -void -wingetevent(Window *w, Event *e) -{ - int i, nb; - - e->c1 = wingetec(w); - e->c2 = wingetec(w); - e->q0 = wingeten(w); - e->q1 = wingeten(w); - e->flag = wingeten(w); - e->nr = wingeten(w); - if(e->nr > EVENTSIZE) - error("event string too long"); - e->nb = 0; - for(i=0; inr; i++){ - e->r[i] = wingeter(w, e->b+e->nb, &nb); - e->nb += nb; + o = 0; + n = evgetnum(f); + for(i = 0; i < n; i++){ + if((r = Bgetrune(f)) == -1) + break; + o += runetochar(e->text + o, &r); } - e->r[e->nr] = 0; - e->b[e->nb] = 0; - if(wingetec(w) != '\n') - error("event syntax error"); + e->text[o] = 0; + return o; } -void -winwriteevent(Window *w, Event *e) +int +winevent(Win *w, Event *e) { - fprint(w->event, "%c%c%d %d\n", e->c1, e->c2, e->q0, e->q1); + int flags; + + flags = 0; +Again: + e->action = Bgetc(w->event); + e->type = Bgetc(w->event); + e->q0 = evgetnum(w->event); + e->q1 = evgetnum(w->event); + e->flags = evgetnum(w->event); + e->ntext = evgetdata(w->event, e); + if(Bgetc(w->event) != '\n'){ + werrstr("unterminated message"); + return -1; + } +//fprint(2, "event: %c%c %d %d %x %.*s\n", e->action, e->type, e->q0, e->q1, e->flags, e->ntext, e->text); + if(e->flags & 0x2){ + e->p0 = e->q0; + flags = e->flags; + goto Again; + } + e->flags |= flags; + return e->action; } void -winread(Window *w, uint q0, uint q1, char *data) +winreturn(Win *w, Event *e) +{ + if(e->flags & 0x2) + fprint(w->revent, "%c%c%d %d\n", e->action, e->type, e->p0, e->p0); + else + fprint(w->revent, "%c%c%d %d\n", e->action, e->type, e->q0, e->q1); +} + +int +winopen(Win *w, char *f, int mode) +{ + char buf[128]; + int fd; + + snprint(buf, sizeof(buf), "/mnt/wsys/%d/%s", w->id, f); + if((fd = open(buf, mode|OCEXEC)) == -1) + sysfatal("open %s: %r", buf); + return fd; +} + +Biobuf* +bwinopen(Win *w, char *f, int mode) +{ + char buf[128]; + Biobuf *bfd; + + snprint(buf, sizeof(buf), "/mnt/wsys/%d/%s", w->id, f); + if((bfd = Bopen(buf, mode|OCEXEC)) == nil) + sysfatal("open %s: %r", buf); + bfd->aux = w->io; + Biofn(bfd, (mode == OREAD)?procrd:procwr); + return bfd; +} + +Biobuf* +bwindata(Win *w, int mode) +{ + int fd; + + if((fd = dup(w->data, -1)) == -1) + sysfatal("dup: %r"); + return Bfdopen(fd, mode); +} + +void +wininit(Win *w, char *name) +{ + char buf[12]; + + w->ctl = open("/mnt/wsys/new/ctl", ORDWR|OCEXEC); + if(w->ctl < 0) + sysfatal("winopen: %r"); + if(read(w->ctl, buf, 12)!=12) + sysfatal("read ctl: %r"); + if(fprint(w->ctl, "name %s\n", name) == -1) + sysfatal("write ctl: %r"); + if(fprint(w->ctl, "noscroll\n") == -1) + sysfatal("write ctl: %r"); + w->io = ioproc(); + w->id = atoi(buf); + w->event = bwinopen(w, "event", OREAD); + w->revent = winopen(w, "event", OWRITE); + w->addr = winopen(w, "addr", ORDWR); + w->data = winopen(w, "data", ORDWR); + w->open = 1; +} + +void +winclose(Win *w) +{ + if(w->open) + fprint(w->ctl, "delete\n"); + if(w->data != -1) + close(w->data); + if(w->addr != -1) + close(w->addr); + if(w->event != nil) + Bterm(w->event); + if(w->revent != -1) + close(w->revent); + if(w->io) + closeioproc(w->io); + if(w->ctl != -1) + close(w->ctl); + w->ctl = -1; + w->data = -1; + w->addr = -1; + w->event = nil; + w->revent = -1; + w->io = nil; +} + +void +wintagwrite(Win *w, char *s) +{ + int fd, n; + + n = strlen(s); + fd = winopen(w, "tag", OWRITE); + if(write(fd, s, n) != n) + sysfatal("tag write: %r"); + close(fd); +} + +int +winread(Win *w, int q0, int q1, char *data, int ndata) { int m, n, nr; - char buf[256]; + char *buf; - if(w->addr < 0) - w->addr = winopenfile(w, "addr"); - if(w->data < 0) - w->data = winopenfile(w, "data"); m = q0; + buf = emalloc(Bufsz); while(m < q1){ n = sprint(buf, "#%d", m); - if(write(w->addr, buf, n) != n) - error("error writing addr: %r"); - n = read(w->data, buf, sizeof buf); - if(n <= 0) - error("reading data: %r"); + if(write(w->addr, buf, n) != n){ + fprint(2, "error writing addr: %r"); + goto err; + } + n = read(w->data, buf, Bufsz); + if(n <= 0){ + fprint(2, "reading data: %r"); + goto err; + } nr = utfnlen(buf, n); while(m+nr >q1){ do; while(n>0 && (buf[--n]&0xC0)==0x80); --nr; } - if(n == 0) + if(n == 0 || n > ndata) break; memmove(data, buf, n); + ndata -= n; data += n; *data = 0; m += nr; } + free(buf); + return 0; +err: + free(buf); + return -1; +} + +char* +winreadsel(Win *w) +{ + int n, q0, q1; + char *r; + + wingetsel(w, &q0, &q1); + n = UTFmax*(q1-q0); + r = emalloc(n + 1); + if(winread(w, q0, q1, r, n) == -1){ + free(r); + return nil; + } + return r; } void -windormant(Window *w) +wingetsel(Win *w, int *q0, int *q1) { - if(w->addr >= 0){ - close(w->addr); - w->addr = -1; - } - if(w->body != nil){ - Bterm(w->body); - w->body = nil; - } - if(w->data >= 0){ - close(w->data); - w->data = -1; - } -} + char *e, buf[25]; - -int -windel(Window *w, int sure) -{ - if(sure) - write(w->ctl, "delete\n", 7); - else if(write(w->ctl, "del\n", 4) != 4) - return 0; - /* event proc will die due to read error from event file */ - windormant(w); - close(w->ctl); - w->ctl = -1; - close(w->event); - w->event = -1; - return 1; + fprint(w->ctl, "addr=dot"); + if(pread(w->addr, buf, 24, 0) != 24) + sysfatal("read addr: %r"); + buf[24] = 0; + *q0 = strtol(buf, &e, 10); + *q1 = strtol(e, nil, 10); } void -winclean(Window *w) +winsetsel(Win *w, int q0, int q1) { - if(w->body) - Bflush(w->body); - ctlprint(w->ctl, "clean\n"); + fprint(w->addr, "#%d,#%d", q0, q1); + fprint(w->ctl, "dot=addr"); } int -winsetaddr(Window *w, char *addr, int errok) +matchmesg(Win *, char *text) { - if(w->addr < 0) - w->addr = winopenfile(w, "addr"); - if(write(w->addr, addr, strlen(addr)) < 0){ - if(!errok) - error("error writing addr(%s): %r", addr); - return 0; - } - return 1; -} + Resub m[3]; -int -winselect(Window *w, char *addr, int errok) -{ - if(winsetaddr(w, addr, errok)){ - ctlprint(w->ctl, "dot=addr\n"); - return 1; + memset(m, 0, sizeof(m)); + if(strncmp(text, mbox.path, strlen(mbox.path)) == 0) + return strstr(text + strlen(mbox.path), "/body") == nil; + else if(regexec(mesgpat, text, m, nelem(m))){ + if(*m[1].ep == 0 || *m[1].ep == '/'){ + *m[1].ep = 0; + return 1; + } } return 0; } - -char* -winreadbody(Window *w, int *np) /* can't use readfile because acme doesn't report the length */ -{ - char *s; - int m, na, n; - - if(w->body != nil) - winclosebody(w); - winopenbody(w, OREAD); - s = nil; - na = 0; - n = 0; - for(;;){ - if(na < n+512){ - na += 1024; - s = realloc(s, na+1); - } - m = Bread(w->body, s+n, na-n); - if(m <= 0) - break; - n += m; - } - s[n] = 0; - winclosebody(w); - *np = n; - return s; -} - -char* -winselection(Window *w) -{ - int fd, m, n; - char *buf; - char tmp[256]; - - fd = winopenfile1(w, "rdsel", OREAD); - if(fd < 0) - error("can't open rdsel: %r"); - n = 0; - buf = nil; - for(;;){ - m = read(fd, tmp, sizeof tmp); - if(m <= 0) - break; - buf = erealloc(buf, n+m+1); - memmove(buf+n, tmp, m); - n += m; - buf[n] = '\0'; - } - close(fd); - return buf; -}