# HG changeset patch # User pouya # Date 1611866183 -3600 # Thu Jan 28 21:36:23 2021 +0100 # Node ID 23ecfc48ea840e6bb12bd1e48afae63ea641a770 # Parent 0000000000000000000000000000000000000000 modified version of acme Mail with editable From: address diff --git a/README b/README new file mode 100644 --- /dev/null +++ b/README @@ -0,0 +1,2 @@ +Plan 9 stuff. Various licences. For licence information concerning +code derived from the Plan 9 codebase see . diff --git a/acme/mail/readme b/acme/mail/readme new file mode 100644 --- /dev/null +++ b/acme/mail/readme @@ -0,0 +1,65 @@ +This is a slightly modified version of the acme Mail program that +allows you to edit the from address of your outgoing emails by +including a From: line as part of your email's header. + +The contents of the original readme file follow. + +--- + +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. diff --git a/acme/mail/src/dat.h b/acme/mail/src/dat.h new file mode 100644 --- /dev/null +++ b/acme/mail/src/dat.h @@ -0,0 +1,165 @@ +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; + 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; + char *upasname; + 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 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*); +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*); + +#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; diff --git a/acme/mail/src/html.c b/acme/mail/src/html.c new file mode 100644 --- /dev/null +++ b/acme/mail/src/html.c @@ -0,0 +1,75 @@ +#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]; + 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 = "/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; +} diff --git a/acme/mail/src/mail.c b/acme/mail/src/mail.c new file mode 100644 --- /dev/null +++ b/acme/mail/src/mail.c @@ -0,0 +1,550 @@ +#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; + +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); + + 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) + 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 ", 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); + 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 +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*) +{ + 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; + 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; + } + } + } +} + diff --git a/acme/mail/src/mesg.c b/acme/mail/src/mesg.c new file mode 100644 --- /dev/null +++ b/acme/mail/src/mesg.c @@ -0,0 +1,1378 @@ +#include +#include +#include +#include +#include +#include +#include "dat.h" + +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", + /* 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", + "text/calendar", + "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) +{ + 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) +{ + 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; + Dir *d; + + if(np != nil) + *np = 0; + file = estrstrdup(dir, name); + fd = open(file, OREAD); + if(fd < 0) + 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; +} + +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; +} + +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 +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->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 +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(k+n+1); + memmove(all, unixheader, k); + memmove(all+k, raw, n); + memmove(all+k+n, "\n", 1); + n += k+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) + 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; ihead; nm != nil; nm = nm->next){ + subdir = estrstrdup(dir, nm->name); + len = 0; + free(readbody(nm->type, subdir, &len)); + free(subdir); + if(strcmp(nm->type, "text/plain") == 0 && len >= 8) + realplain = nm; + else if(strcmp(nm->type, "text/html") == 0 && len >= 600) + realhtml = nm; + else if(strcmp(nm->type, "text/calendar") == 0) + realcal = nm; + } + if(realplain == nil && realhtml == nil && realcal) + return realcal; /* super-turkey */ + else if(realplain == nil && realhtml) + return realhtml; /* regular turkey */ + else + return realplain; +} + +void +mesgload(Message *m, char *rootdir, char *file, Window *w) +{ + char *s, *subdir, *name, *dir; + Message *mp, *thisone; + int n; + + dir = estrstrdup(rootdir, file); + + if(strcmp(m->type, "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 = bestalt(m, dir); + if(thisone == nil){ + 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); + 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){ + 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; + 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); +} diff --git a/acme/mail/src/mkfile b/acme/mail/src/mkfile new file mode 100644 --- /dev/null +++ b/acme/mail/src/mkfile @@ -0,0 +1,30 @@ +syms + 8c -aa mesg.c reply.c util.c win.c >>syms + diff --git a/acme/mail/src/reply.c b/acme/mail/src/reply.c new file mode 100644 --- /dev/null +++ b/acme/mail/src/reply.c @@ -0,0 +1,585 @@ +#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->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) +{ + struct Exec *e; + int p[2], q[2], n; + 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); + if(e->upasname && (n=open("/env/upasname", OTRUNC|OWRITE)) >= 0) { + fprint(n, "%s", e->upasname); + fprint(2, "From: %s", e->upasname); + free(e->upasname); + close(n); + } + 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]; + struct Exec *e; + Channel *sync; + int first, nfld, delit, ofd, hasfrom; + char *copy, *fld[100], *now, *from; + + body = winreadbody(m->w, &n); + /* assemble to: list from first line, to: line, and cc: line */ + nto = 0; + natt = 0; + ncc = 0; + nbcc = 0; + hasfrom = 0; + first = 1; + to = body; + from = nil; + 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 FROM: + delit = 1; + if(nfld<2 || hasfrom) break; + hasfrom = 1; + from = estrdup(fld[1]); + break; + case TO: + 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", from, now); + fprint(ofd, "From: %s\n", from); + fprint(ofd, "Date: %s", now); + for(i=0; ip[0] = p[0]; + e->p[1] = p[1]; + e->prog = "/bin/upas/marshal"; + e->upasname = from; + 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; +} diff --git a/acme/mail/src/util.c b/acme/mail/src/util.c new file mode 100644 --- /dev/null +++ b/acme/mail/src/util.c @@ -0,0 +1,105 @@ +#include +#include +#include +#include +#include +#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); +} + +void +ctlprint(int fd, char *fmt, ...) +{ + 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"); +} diff --git a/acme/mail/src/win.c b/acme/mail/src/win.c new file mode 100644 --- /dev/null +++ b/acme/mail/src/win.c @@ -0,0 +1,341 @@ +#include +#include +#include +#include +#include +#include "dat.h" + +Window* +newwindow(void) +{ + 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]); + } +} + +static int +winopenfile1(Window *w, char *f, int m) +{ + 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; +} + +int +winopenfile(Window *w, char *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; + + 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) +{ + fprint(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 < 0) + w->addr = winopenfile(w, "addr"); + if(w->data < 0) + w->data = winopenfile(w, "data"); + m = q0; + 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"); + 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 >= 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; + } +} + + +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; +} + +void +winclean(Window *w) +{ + if(w->body) + Bflush(w->body); + ctlprint(w->ctl, "clean\n"); +} + +int +winsetaddr(Window *w, char *addr, int errok) +{ + 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; +} + +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); + } + 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; +}