commit 9f1fdc128738b2ed76258ac22a8574c681f3df3a from: rsc date: Sat Oct 29 16:21:34 2005 UTC Add mail (John Cummings) commit - a078ffc8ab1d8f499b02b1dda2dfe2e9a3f6674d commit + 9f1fdc128738b2ed76258ac22a8574c681f3df3a blob - /dev/null blob + 08e757239fdfe1274ee3e82fcb61b0f4f42c012e (mode 644) --- /dev/null +++ src/cmd/acme/mail/dat.h @@ -0,0 +1,172 @@ +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 */ +/* jpc int ctl; + int event; + int addr; + int data; + Biobuf *body; jpc */ + CFid* ctl; + CFid* event; + CFid* addr; + CFid* data; + CFid* 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; + 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; + + 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 CFid* winopenfid(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 mesgadd(Message*, char*, Dir*, char*); +extern void mesgmenu(Window*, Message*); +extern void mesgmenunew(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*); +// jpc extern void ctlprint(int, char*, ...); +extern void ctlprint(CFid*, 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 int fsprint(CFid*, 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; blob - /dev/null blob + 8977ac7c85843251cfc84fc6a41f873d6ba0923f (mode 644) --- /dev/null +++ src/cmd/acme/mail/guide @@ -0,0 +1,4 @@ +Mail stored +plumb /mail/box/$user/names +mail -'x' someaddress +mkbox /mail/box/$user/new_box blob - /dev/null blob + d07a33fe74fa45fa80ce52e842bfe9af9634edaf (mode 644) --- /dev/null +++ src/cmd/acme/mail/html.c @@ -0,0 +1,76 @@ +#include +#include +#include +#include +#include +#include +#include <9pclient.h> +#include "dat.h" + + +char* +formathtml(char *body, int *np) +{ + int i, j, p[2], q[2]; + Exec *e; + char buf[1024]; + Channel *sync; + + e = emalloc(sizeof(struct 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(3*sizeof(char*)); + e->argv[0] = estrdup("htmlfmt"); + e->argv[1] = estrdup("-cutf-8"); + e->argv[2] = nil; + e->prog = unsharp("#9/bin/htmlfmt"); + sync = chancreate(sizeof(int), 0); + e->sync = sync; + proccreate(execproc, e, EXECSTACK); + recvul(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; +} blob - /dev/null blob + 9942868c00ddfca4f88643f6efd5aa39a438e37d (mode 644) --- /dev/null +++ src/cmd/acme/mail/mail.c @@ -0,0 +1,571 @@ +#include +#include +#include +#include +#include +#include +#include <9pclient.h> /* jpc */ +#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; + +CFsys *upasfs; /*jpc */ +CFsys *acmefs; /*jpc */ + +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 buf[256]; + Dir *d; + int ret; + + snprint(buf, sizeof buf, "%s%s", maildir, s); + d = dirstat(buf); + 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(); + + /* 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); + + /* jpc */ + acmefs = nsmount("acme",nil); + upasfs = nsmount("upasfs", nil); + /* jpc end */ + + shortmenu = 0; + ARGBEGIN{ + case 's': + shortmenu = 1; + break; + case 'S': + shortmenu = 2; + 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) { + /* jpc - bind(mailtermdir, maildir, MAFTER); */ + fprint(2,"jpc: trying to bind(mailtermdir, maildir, MAFTER)\n"); + } + + 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(unsharp("#9/mail/box/"), user); + if(outgoing == nil) + outgoing = estrstrdup(mailboxdir, "/outgoing"); + + // s = estrstrdup(maildir, "ctl"); + mbox.ctlfd = fsopenfd(upasfs,"ctl", ORDWR|OCEXEC); + if(mbox.ctlfd < 0) + error("can't open %s: %r\n", 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\n", 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.name = "/mail/fs/mbox/"; + 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 ", 3+1+4+1+7+1); + threadcreate(mainctl, wbox, STACK); + + fmtstrinit(&fmt); + fmtprint(&fmt, "Mail"); + if(shortmenu) + fmtprint(&fmt, " -%c", "sS"[shortmenu-1]); + 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); + // sleep(100); + 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* v) +{ + Plumbmsg *m; + + threadsetname("plumbproc"); + for(;;){ + m = plumbrecv(plumbseemailfd); + sendp(cplumb, m); + if(m == nil) + threadexits(nil); + } +} + +void +plumbshowproc(void* v) +{ + Plumbmsg *m; + + threadsetname("plumbshowproc"); + for(;;){ + m = plumbrecv(plumbshowmailfd); + sendp(cplumbshow, m); + if(m == nil) + threadexits(nil); + } +} + +void +plumbsendproc(void* v) +{ + Plumbmsg *m; + + threadsetname("plumbsendproc"); + for(;;){ + m = plumbrecv(plumbsendmailfd); + sendp(cplumbsend, m); + if(m == nil) + threadexits(nil); + } +} + +void +newmesg(char *name, char *digest) +{ + Dir *d; + char* tmp; + + if(strncmp(name, mbox.name, strlen(mbox.name)) != 0) { + return; /* message is about another mailbox */ + } + if(mesglookupfile(&mbox, name, digest) != nil) { + return; + } + if (strncmp(name,"/mail/fs/",strlen("/mail/fs/"))==0) { + tmp = name+strlen("/mail/fs/"); + } + d = fsdirstat(upasfs,tmp); + 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 +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 + fprint(2, "Mail: unknown plumb attribute %s\n", type); + plumbfree(m); + } + threadexits(nil); +} + +void +plumbshowthread(void* v) +{ + Plumbmsg *m; + + threadsetname("plumbshowthread"); + while((m = recvp(cplumbshow)) != nil){ + showmesg(m->data, plumblookup(m->attr, "digest")); + plumbfree(m); + } + threadexits(nil); +} + +void +plumbsendthread(void* v) +{ + 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; + 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; icevent); + 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; + } + } + } +} + blob - /dev/null blob + 0cf6c943af2a243123f6f8e99d7a79e76dac7249 (mode 644) --- /dev/null +++ src/cmd/acme/mail/mesg.c @@ -0,0 +1,1444 @@ +#include +#include +#include +#include +#include +#include +#include <9pclient.h> /* jpc */ +#include "dat.h" + +extern CFsys *upasfs; /* jpc */ + +enum +{ + DIRCHUNK = 32*sizeof(Dir) +}; + +char regexchars[] = "\\/[].+?()*^$"; +char deleted[] = "(deleted)-"; +char deletedrx[] = "\\(deleted\\)-"; +char deletedrx01[] = "(\\(deleted\\)-)?"; +char deletedaddr[] = "-#0;/^\\(deleted\\)-/"; + +struct{ + char *type; + char *port; + char *suffix; +} ports[] = { + "text/", "edit", ".txt", /* must be first for plumbport() */ + "image/gif", "image", ".gif", + "image/jpeg", "image", ".jpg", + "image/jpeg", "image", ".jpeg", + "image/png", "image", ".png", + "application/postscript", "postscript", ".ps", + "application/pdf", "postscript", ".pdf", + "application/msword", "msword", ".doc", + "application/rtf", "msword", ".rtf", + 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[] = +{ + "From:", + "Date:", + "To:", + "CC:", + "Subject:", + nil +}; + +char *extraheaders[] = +{ + "Resent-From:", + "Resent-To:", + "Sort:", + nil, +}; + +char* +line(char *data, char **pp) +{ + char *p, *q; + + for(p=data; *p!='\0' && *p!='\n'; p++) + ; + if(*p == '\n') + *pp = p+1; + else + *pp = p; + q = emalloc(p-data + 1); + memmove(q, data, p-data); + return q; +} + +void +scanheaders(Message *m, char *dir) +{ + char *s, *t, *u, *f; + + s = f = readfile(dir, "header", nil); + if(s != nil) + while(*s){ + t = line(s, &s); + if(strncmp(t, "From: ", 6) == 0){ + m->fromcolon = estrdup(t+6); + /* remove all quotes; they're ugly and irregular */ + for(u=m->fromcolon; *u; u++) + if(*u == '"') + memmove(u, u+1, strlen(u)); + } + if(strncmp(t, "Subject: ", 9) == 0) + m->subject = estrdup(t+9); + free(t); + } + if(m->fromcolon == nil) + m->fromcolon = estrdup(m->from); + free(f); +} + +int +loadinfo(Message *m, char *dir) +{ + int n; + char *data, *p, *s; + + data = readfile(dir, "info", &n); + if(data == nil) + return 0; + m->from = line(data, &p); + scanheaders(m, dir); /* depends on m->from being set */ + m->to = line(p, &p); + m->cc = line(p, &p); + m->replyto = line(p, &p); + m->date = line(p, &p); + s = line(p, &p); + if(m->subject == nil) + m->subject = s; + else + free(s); + m->type = line(p, &p); + m->disposition = line(p, &p); + m->filename = line(p, &p); + m->digest = 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) +{ + CFid *fid; + Dir *dp; + char *tmp; + + if (strncmp(name,"/mail/fs/",strlen("/mail/fs/"))==0) { + tmp = name+strlen("/mail/fs/"); + } + fid = fsopen(upasfs,tmp,OREAD); + if(fid == nil) + return nil; + *np = fsdirreadall(fid, &dp); + fsclose(fid); + + 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) +{ + static char now[10]; + char *s; + + if(now[0] == '\0'){ + s = ctime(time(nil)); + strcpy(now, s+24); + } + return strncmp(year, now, 4) == 0; +} + +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; jpc */ + int len; + Dir *d; + CFid *fid; + char *tmp; + + if(np != nil) + *np = 0; + file = estrstrdup(dir, name); +/* fd = open(file, OREAD); + if(fd < 0) + return nil; + d = dirfstat(fd); jpc */ + if (strncmp(file,"/mail/fs/",strlen("/mail/fs/"))==0) { + tmp = file+strlen("/mail/fs/"); + } + fid = fsopen(upasfs,tmp, OREAD); + if(fid == nil) + return nil; + d = fsdirfstat(fid); +/* jpc end */ + free(file); + len = 0; + if(d != nil) + len = d->length; + free(d); + data = emalloc(len+1); +/* read(fd, data, len); + close(fd); jpc */ + fsread(fid, data, len); + fsclose(fid); +/* jpc */ + if(np != nil) + *np = len; + return data; +} + +char* +info(Message *m, int ind, int ogf) +{ + char *i; + int j, len, lens; + char *p; + char fmt[80], s[80]; + + if (ogf) + p=m->to; + else + p=m->fromcolon; + + 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, "\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; +} + +#if 0 /* jpc */ +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; + } +} +#endif + +void +mesgmenu0(Window *w, Message *mbox, char *realdir, char *dir, int ind, CFid *fd, int onlyone, int dotail) +{ + int i; + Message *m; + char *name, *tmp; + //1 char ntmp[250]; + 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); jpc */ + // snprint(ntmp,250, "%s%s\n", name, tmp); + // fswrite(fd, ntmp, strlen(ntmp)); + fsprint(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 = winopenfid(w, "data"); + b = emalloc(sizeof(Biobuf)); +/* Binit(b, w->data, OWRITE); + mesgmenu0(w, mbox, mbox->name, "", 0, b, 1, !shortmenu); + Bterm(b); +jpc */ + mesgmenu0(w, mbox, mbox->name, "", 0, w->data, 1, !shortmenu); + 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); + fsclose(w->addr); + fsclose(w->data); + w->addr = nil; + w->data = nil; +} + +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 == nil) + w->data = winopenfid(w, "data"); + buf = name2regexp("", m->name); + strcat(buf, "-#0"); + if(winselect(w, buf, 1)) { + fswrite(w->data, deleted, 10); + } + free(buf); + fsclose(w->data); + fsclose(w->addr); + w->addr = nil; + w->data = nil; + mbox->dirty = 1; + m->deleted = 1; +} + +void +mesgmenumarkundel(Window *w, Message* v, Message *m) +{ + char *buf; + + if(m->deleted == 0) + return; + if(w->data == nil) + w->data = winopenfid(w, "data"); + buf = name2regexp(deletedrx, m->name); + if(winselect(w, buf, 1)) { + if(winsetaddr(w, deletedaddr, 1)) { + fswrite(w->data, "", 0); + // fsync(w->data); + } + } + free(buf); + fsclose(w->data); + fsclose(w->addr); + w->addr = nil; + w->data = nil; + m->deleted = 0; +} + +void +mesgmenudel(Window *w, Message *mbox, Message *m) +{ + char *buf; + + if(w->data ==nil) + w->data = winopenfid(w, "data"); + buf = name2regexp(deletedrx, m->name); + if(winsetaddr(w, buf, 1) && winsetaddr(w, ".,./.*\\n(\t.*\\n)*/", 1)) { + fswrite(w->data, "", 0); + // fsync(w->data); + } + free(buf); + fsclose(w->data); + fsclose(w->addr); + w->addr = nil; + w->data = nil; + mbox->dirty = 1; + m->deleted = 1; +} + +void +mesgmenumark(Window *w, char *which, char *mark) +{ + char *buf; + + if(w->data == nil) + w->data = winopenfid(w, "data"); + buf = name2regexp(deletedrx01, which); + if(winsetaddr(w, buf, 1) && winsetaddr(w, "+0-#1", 1)) { /* go to end of line */ + fswrite(w->data, mark, strlen(mark)); + // fsync(w->data); + } + free(buf); + fsclose(w->data); + fsclose(w->addr); + w->addr = nil; + w->data = nil; + 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->digest); +} + +void +mesgdel(Message *mbox, Message *m) +{ + Message *n, *next; + + if(m->opened) + error("internal error: deleted message still open in mesgdel\n"); + /* 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 +mesgsave(Message *m, char *s) +{ + int ofd, n, k, ret; + char *t, *raw, *unixheader, *all; + + t = estrstrdup(mbox.name, m->name); + raw = readfile(t, "raw", &n); + unixheader = readfile(t, "unixheader", &k); + if(raw==nil || unixheader==nil){ + fprint(2, "Mail: can't read %s: %r\n", t); + free(t); + return 0; + } + free(t); + + all = emalloc(n+k+1); + memmove(all, unixheader, k); + memmove(all+k, raw, n); + memmove(all+k+n, "\n", 1); + n = k+n+1; + free(unixheader); + free(raw); + ret = 1; + s = estrdup(s); + if(s[0] != '/') + s = egrow(estrdup(mailboxdir), "/", s); + ofd = open(s, OWRITE); + if(ofd < 0){ + fprint(2, "Mail: can't open %s: %r\n", s); + ret = 0; + }else if(seek(ofd, 0LL, 2)<0 || write(ofd, all, n)!=n){ + fprint(2, "Mail: save failed: can't write %s: %r\n", s); + ret = 0; + } + free(all); + close(ofd); + free(s); + 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); + 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; + } + goto Return; + } + if(strcmp(args[0], "UnDelmesg") == 0){ + if(!m->isreply && m->deleted) + mesgmenumarkundel(wbox, &mbox, m); + 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) + if(fspread(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) +{ + //1 char *tmp; + //1 tmp = emalloc(strlen(header)+2+strlen(value)+1); + + if(strlen(value) > 0) { + // jpc Bprint(m->w->body, "%s: %s\n", header, value); + fsprint(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; + char* tmp; + + 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(m->filename[0] != '/') + dest = egrow(estrdup(home), "/", dest); + // jpc Bprint(w->body, "\tcp %s%sbody%s %q\n", rootdir, name, ext(m->type), dest); + if( strncmp(rootdir,"/mail/fs/",strlen("/mail/fs/"))==0) { + tmp = rootdir+strlen("/mail/fs/"); + } + fsprint(w->body, "\t9p read upasfs/%s%sbody%s > %s\n", tmp, name, ext(m->type), dest); + free(dest); + }else if(!fileonly) { + // jpc Bprint(w->body, "\tfile is %s%sbody%s\n", rootdir, name, ext(m->type)); + fsprint(w->body, "\tfile is %s%sbody%s\n", rootdir, name, ext(m->type)); + } +} + +#if 0 /* jpc */ +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); + fsprint(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"); + fswrite(w->body,"\n",strlen("\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) { + // jpc Bprint(w->body, "\n===> %s (%s) [%s]\n", strchr(name, '/')+1, mp->type, mp->disposition); + fsprint(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); + return m; +} + +/* + * Find plumb port, knowing type is text, given file name (by extension) + */ +int +plumbportbysuffix(char *file) +{ + char *suf; + int i, nsuf, nfile; + + 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; + } + return 0; +} + +/* + * Find plumb port using type and file name (by extension) + */ +int +plumbport(char *type, char *file) +{ + 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; +} + +void +plumb(Message *m, char *dir) +{ + int i; + char *port; + Plumbmsg *pm; + + if(strlen(m->type) == 0) + 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); + } +} + +int +mesgopen(Message *mbox, char *dir, char *s, Message *mesg, int plumbed, char *digest) +{ + char *t, *u, *v; + Message *m; + char *direlem[10]; + int i, ndirelem, reuse; + + /* 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){ + fprint(2,"mesg.c:1229 fixme\n"); + 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 == nil) + m->w->data = winopenfid(m->w, "data"); + fswrite(m->w->data, "", 0); + // fsync(m->w->data); + } + } + 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); + // sleep(100); + winclean(m->w); + m->opened = 1; + 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); + } + if(m != nil && plumbport(m->type, m->filename) > 0) + plumb(m, dir); + free(dir); + free(u); + return 1; +} + +void +rewritembox(Window *w, Message *mbox) +{ + Message *m, *next; + char *deletestr, *t; + int nopen; + + 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; +} + +/* name is a full file name, but it might not belong to us */ +Message* +mesglookupfile(Message *mbox, char *name, char *digest) +{ + int k, n; + + 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); + return nil; + } + return mesglookup(mbox, name+n, digest); +} blob - /dev/null blob + e3639aed44f42a24dbd2ca3d635078a66719434d (mode 644) --- /dev/null +++ src/cmd/acme/mail/mkbox @@ -0,0 +1,11 @@ +#!/bin/rc + +for(i){ + if(! test -f $i){ + if(cp /dev/null $i){ + chmod 600 $i + chmod +al $i + } + } + if not echo $i already exists +} blob - /dev/null blob + 0a7fe0e0aac572924fdc20cbeed018c62147fb1c (mode 644) --- /dev/null +++ src/cmd/acme/mail/mkfile @@ -0,0 +1,32 @@ +<$PLAN9/src/mkhdr + +CC=9c + +TARG=Mail +OFILES=\ + html.$O\ + mail.$O\ + mesg.$O\ + reply.$O\ + util.$O\ + win.$O + +HFILES=dat.h +LIB= + +BIN=/acme/bin/$objtype + +UPDATE=\ + mkfile\ + $HFILES\ + ${OFILES:%.$O=%.c}\ + +<$PLAN9/src/mkone + +$O.out: $OFILES + $LD -o $target $LDFLAGS $OFILES + +syms:V: + 8c -a mail.c >syms + 8c -aa mesg.c reply.c util.c win.c >>syms + blob - /dev/null blob + b795a04e725d397196a42485bd9c42d90db3c9d9 (mode 644) --- /dev/null +++ src/cmd/acme/mail/readme @@ -0,0 +1,57 @@ +The Acme Mail program uses upas/fs to parse the mail box, and then +presents a file-browser-like user interface to reading and sending +messages. The Mail window presents each numbered message like the +contents of a directory presented one per line. If a message has a +Subject: line, that is shown indented on the following line. +Multipart MIME-encoded messages are presented in the obvious +hierarchical format. + +Mail uses upas/fs to access the mail box. By default it reads "mbox", +the standard user mail box. If Mail is given an argument, it is +passed to upas/fs as the name of the mail box (or upas/fs directory) +to open. + +Although Mail works if the plumber is not running, it's designed to be +run with plumbing enabled and many of its features work best if it is. + +The mailbox window has a few commands: Put writes back the mailbox; +Mail creates a new window in which to compose a message; and Delmesg +deletes messages by number. The number may be given as argument or +indicated by selecting the header line in the mailbox window. +(Delmesg does not expand null selections, in the interest of safety.) + +Clicking the right button on a message number opens it; clicking on +any of the subparts of a message opens that (and also opens the +message itself). Each message window has a few commands in the tag +with obvious names: Reply, Delmsg, etc. "Reply" replies to the single +sender of the message, "Reply all" or "Replyall" replies to everyone +in the From:, To:, and CC: lines. + +Message parts with recognized MIME types such as image/jpeg are sent +to the plumber for further dispatch. Acme Mail also listens to +messages on the seemail and showmail plumbing ports, to report the +arrival of new messages (highlighting the entry; right-click on the +entry to open the message) and open them if you right-click on the +face in the faces window. + +When composing a mail message or replying to a message, the first line +of the text is a list of recipients of the message. To:, and CC:, and BCC: +lines are interpreted in the usual way. Two other header lines are +special to Acme Mail: + Include: file places a copy of file in the message as an + inline MIME attachment. + Attach: file places a copy of file in the message as a regular + MIME attachment. + +Acme Mail uses these conventions when replying to messages, +constructing headers for the default behavior. You may edit these to +change behavior. Most important, when replying to a message Mail will +always Include: the original message; delete that line if you don't +want to include it. + +If the mailbox + /mail/box/$user/outgoing +exists, Acme Mail will save your a copy of your outgoing messages +there. Attachments are described in the copy but not included. + +The -m mntpoint flag specifies a different mount point for /upas/fs. blob - /dev/null blob + 4d4d14b58d1a6903651c1adc552916ce5cc91127 (mode 644) --- /dev/null +++ src/cmd/acme/mail/reply.c @@ -0,0 +1,654 @@ +#include +#include +#include +#include +#include +#include +#include <9pclient.h> +#include "dat.h" + +static int replyid; + +int +quote(Message *m, CFid *fid, 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, fid, 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; +} + +#if 0 /* jpc */ +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; +} +#endif + +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->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); + fsprint(r->w->body, "%s\n", to); + } + for(a=attr; a; a=a->next) { + // Bprint(r->w->body, "%s: %s\n", a->name, a->value); + fsprint(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) { + // jpc Bprint(r->w->body, "To: %s\n", m->replyto); + fsprint(r->w->body, "To: %s\n", m->replyto); + } + else{ /* Replyall */ + if(strlen(m->from) > 0) { + // Bprint(r->w->body, "To: %s\n", m->from); + fsprint(r->w->body, "To: %s\n", m->from); + } + if(strlen(m->to) > 0) { + // Bprint(r->w->body, "To: %s\n", m->to); + fsprint(r->w->body, "To: %s\n", m->to); + } + if(strlen(m->cc) > 0) { + // Bprint(r->w->body, "CC: %s\n", m->cc); + fsprint(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); + fsprint(r->w->body, "%s%s\n", t, m->subject); + } + if(!quotereply){ + // Bprint(r->w->body, "Include: %sraw\n", dir); + fsprint(r->w->body, "Include: %sraw\n", dir); + free(dir); + } + } + // Bprint(r->w->body, "\n"); + fsprint(r->w->body, "\n"); + if(m == nil) { + // Bprint(r->w->body, "\n"); + fsprint(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) +{ + struct Exec *e; + int p[2], q[2]; + char *prog; + char *argv[NARGS+1], args[NARGCHAR]; + int fd[3]; + + 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]); + } + + // jpc - start + fd[0] = dup(0, -1); + fd[1] = dup(1, -1); + threadexec(nil, fd, prog, argv); + close(fd[0]); + close(fd[1]); + /* jpc - procexec(nil, prog, argv); */ + // jpc end +//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; +} + +void +write2(int fd, int ofd, char *buf, int n, int nofrom) +{ + char *from, *p; + int m; + + write(fd, buf, n); + + if(ofd <= 0) + return; + + if(nofrom == 0){ + write(ofd, buf, n); + return; + } + + /* 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){ + if(p==buf || from[-1]=='\n') + write(ofd, " ", 1); /* escape with space if From is at start of line */ + write(ofd, from, 4); + m += 4; + } + n -= m; + } +} + +void +mesgsend(Message *m) +{ + char *s, *body, *to; + int i, j, h, n, natt, p[2]; + struct Exec *e; + Channel *sync; + int first, nfld, delit, ofd; + char *copy, *fld[100], *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 EDT 2001 */ + now = ctime(time(0)); + fprint(ofd, "From %s %s", user, now); + fprint(ofd, "From: %s\n", user); + fprint(ofd, "Date: %s", now); + for(i=0; ip[0] = p[0]; + e->p[1] = p[1]; + e->prog = unsharp("#9/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 != nil) + mesgmenumark(mbox.w, m->replyname, "\t[replied]"); + 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; +} blob - /dev/null blob + 8fde8b55bc1808591c83f34d242d9f4884baa4cb (mode 644) --- /dev/null +++ src/cmd/acme/mail/util.c @@ -0,0 +1,170 @@ +#include +#include +#include +#include +#include +#include <9pclient.h> +#include "dat.h" + +void* +emalloc(uint 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* +erealloc(void *p, uint n) +{ + p = realloc(p, n); + if(p == nil) + error("can't realloc: %r"); + setmalloctag(p, getcallerpc(&n)); + return p; +} + +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); + 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); +} + +#if 0 /* jpc */ +void +ctlprint(int fd, char *fmt, ...) +{ + int n; + va_list arg; + + va_start(arg, fmt); + n = vfprint(fd, fmt, arg); + va_end(arg); + fsync(fd); + if(n <= 0) + error("control file write error: %r"); +} +#endif + +void +ctlprint(CFid* fd, char *fmt, ...) +{ + int n; + va_list arg; + char tmp[250]; + + va_start(arg, fmt); + n = vsnprint(tmp, 250, fmt, arg); + va_end(arg); + n = fswrite(fd, tmp, strlen(tmp)); + if(n <= 0) + error("control file write error: %r"); +} + +int fsprint(CFid *fid, char* fmt, ...) { + // example call this replaces: Bprint(b, ">%s%s\n", lines[i][0]=='>'? "" : " ", lines[i]); + char *tmp; + va_list arg; + int n, tlen; + + tmp = emalloc( tlen=(strlen(fmt)+250) ); // leave room for interpolated text + va_start(arg, fmt); + n = vsnprint(tmp, tlen, fmt, arg); + va_end(arg); + if(n == tlen) + error("fsprint formatting error"); + n = fswrite(fid, tmp, strlen(tmp)); + if(n <= 0) + error("fsprint write error: %r"); + free(tmp); + + return n; + +} +#if 0 /* jpc */ +/* +here's a read construct (from winselection) that may be useful in fsprint - think about it. +*/ + int m, n; + char *buf; + char tmp[256]; + CFid* fid; + + fid = winopenfid1(w, "rdsel", OREAD); + if(fid == nil) + error("can't open rdsel: %r"); + n = 0; + buf = nil; + + for(;;){ + m = fsread(fid, tmp, sizeof tmp); + if(m <= 0) + break; + buf = erealloc(buf, n+m+1); + memmove(buf+n, tmp, m); + n += m; + buf[n] = '\0'; + } +#endif blob - /dev/null blob + 73df95a219a04782f417515ee39bb0b88cdf7943 (mode 644) --- /dev/null +++ src/cmd/acme/mail/win.c @@ -0,0 +1,397 @@ +#include +#include +#include +#include +#include +#include <9pclient.h> /* jpc */ +#include "dat.h" + +extern CFsys *acmefs; /* jpc */ + +Window* +newwindow(void) +{ + char buf[12]; + Window *w; + int n = 0; + + w = emalloc(sizeof(Window)); +/* jpc + 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"); +*/ +/* w->ctl = fsopenfd(acmefs, "new/ctl", ORDWR|OCEXEC); + if(w->ctl<0 || (n = read(w->ctl, buf, 12))!=12) { + fprint(2,"%d bytes read from %d\n",n,w->ctl); + error("can't open window ctl file: %r"); + } + jpc end */ + w->ctl = fsopen(acmefs, "new/ctl", ORDWR|OCEXEC); + if(w->ctl == nil || (n = fsread(w->ctl, buf, 12))!=12) { + fprint(2,"%d bytes read from %d\n",n,w->ctl); + error("can't open window ctl file: %r"); + } + + ctlprint(w->ctl, "noscroll\n"); + w->id = atoi(buf); + w->event = winopenfid(w, "event"); + w->addr = nil; /* will be opened when needed */ + w->body = nil; + w->data = nil; + 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]); + } +} + +static CFid* +winopenfid1(Window *w, char *f, int m) +{ + char buf[64]; + CFid* fd; + + sprint(buf, "%d/%s", w->id, f); + fd = fsopen(acmefs, buf, m|OCEXEC); + if(fd == nil) + error("can't open window file %s: %r", f); + return fd; +} + +static int +winopenfile1(Window *w, char *f, int m) +{ + char buf[64]; + int fd; + +/* jpc + sprint(buf, "/mnt/wsys/%d/%s", w->id, f); + fd = open(buf, m|OCEXEC); +*/ + sprint(buf, "%d/%s", w->id, f); + fd = fsopenfd(acmefs, buf, m|OCEXEC); + if(fd < 0) + error("can't open window file %s: %r", f); + return fd; +} + +CFid* +winopenfid(Window *w, char *f) +{ + return winopenfid1(w, f, ORDWR); +} + +int +winopenfile(Window *w, char *f) +{ + return winopenfile1(w, f, ORDWR); +} + +void +wintagwrite(Window *w, char *s, int n) +{ + CFid* fid; + + fid = winopenfid(w, "tag"); + if(fswrite(fid, s, n) != n) + error("tag write: %r"); + fsclose(fid); +} + +void +winname(Window *w, char *s) +{ + ctlprint(w->ctl, "name %s\n", s); +} + +void +winopenbody(Window *w, int mode) +{ + char buf[256]; + CFid* fid; + +/* jpc + sprint(buf, "/mnt/wsys/%d/body", w->id); + w->body = Bopen(buf, mode|OCEXEC); +*/ + sprint(buf, "%d/body", w->id); + fid = fsopen(acmefs,buf, mode|OCEXEC); + w->body = fid; // jpcBfdopen(id, mode|OCEXEC); + if(w->body == nil) + error("can't open window body file: %r"); +} + +void +winclosebody(Window *w) +{ + if(w->body != nil){ + // jpc Bterm(w->body); + fsclose(w->body); + w->body = nil; + } +} + +void +winwritebody(Window *w, char *s, int n) +{ + if(w->body == nil) + winopenbody(w, OWRITE); + // jpc if(Bwrite(w->body, s, n) != n) + if(fswrite(w->body, s, n) != n) + error("write error to window: %r"); +} + +int +wingetec(Window *w) +{ + if(w->nbuf == 0){ + w->nbuf = fsread(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; + + n = 0; + while('0'<=(c=wingetec(w)) && c<='9') + n = n*10+(c-'0'); + if(c != ' ') + error("event number syntax"); + return n; +} + +int +wingeter(Window *w, char *buf, int *nb) +{ + 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; + } + e->r[e->nr] = 0; + e->b[e->nb] = 0; + if(wingetec(w) != '\n') + error("event syntax error"); +} + +void +winwriteevent(Window *w, Event *e) +{ + fsprint(w->event, "%c%c%d %d\n", e->c1, e->c2, e->q0, e->q1); +} + +void +winread(Window *w, uint q0, uint q1, char *data) +{ + int m, n, nr; + char buf[256]; + + if(w->addr == nil) + w->addr = winopenfid(w, "addr"); + if(w->data == nil) + w->data = winopenfid(w, "data"); + m = q0; + while(m < q1){ + n = sprint(buf, "#%d", m); + if(fswrite(w->addr, buf, n) != n) + error("error writing addr: %r"); + n = fsread(w->data, buf, sizeof buf); + if(n <= 0) + error("reading data: %r"); + nr = utfnlen(buf, n); + while(m+nr >q1){ + do; while(n>0 && (buf[--n]&0xC0)==0x80); + --nr; + } + if(n == 0) + break; + memmove(data, buf, n); + data += n; + *data = 0; + m += nr; + } +} + +void +windormant(Window *w) +{ + if(w->addr != nil){ + fsclose(w->addr); + w->addr = nil; + } + if(w->body != nil){ + fsclose(w->body); + w->body = nil; + } + if(w->data != nil){ + fsclose(w->data); + w->data = nil; + } +} + + +int +windel(Window *w, int sure) +{ + if(sure) { + fswrite(w->ctl, "delete\n", 7); + // fsync(w->ctl); + } + else if(fswrite(w->ctl, "del\n", 4) != 4) { + // fsync(w->ctl); + return 0; + } + /* event proc will die due to read error from event file */ + windormant(w); + fsclose(w->ctl); + w->ctl = nil; + fsclose(w->event); + w->event = nil; + return 1; +} + +void +winclean(Window *w) +{ + // int fd; + // if(w->body) + // Bflush(w->body); + ctlprint(w->ctl, "clean\n"); +} + +int +winsetaddr(Window *w, char *addr, int errok) +{ + if(w->addr == nil) + w->addr = winopenfid(w, "addr"); + if(fswrite(w->addr, addr, strlen(addr)) < 0){ + if(!errok) + error("error writing addr(%s): %r", addr); + return 0; + } + return 1; +} + +int +winselect(Window *w, char *addr, int errok) +{ + if(winsetaddr(w, addr, errok)){ + ctlprint(w->ctl, "dot=addr\n"); + 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); + } + // jpc m = Bread(w->body, s+n, na-n); + m = fsread(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 m, n; + char *buf; + char tmp[256]; + CFid* fid; + + fid = winopenfid1(w, "rdsel", OREAD); + if(fid == nil) + error("can't open rdsel: %r"); + n = 0; + buf = nil; + for(;;){ + m = fsread(fid, tmp, sizeof tmp); + if(m <= 0) + break; + buf = erealloc(buf, n+m+1); + memmove(buf+n, tmp, m); + n += m; + buf[n] = '\0'; + } + fsclose(fid); + return buf; +}