Mail: rewrite.

Acme mail made it hard to do threading, so I wrote a new one.
This commit is contained in:
Ori Bernstein 2021-01-29 17:34:47 -08:00
parent 319e625be0
commit bee6271ae1
12 changed files with 2585 additions and 3202 deletions

231
sys/man/1/acmemail Normal file
View file

@ -0,0 +1,231 @@
.TH NAIL 1
.SH NAME
Nail \- view mail in acme
.SH SYNOPSIS
Nail
[
.B -OsT
]
[
.B -m
.I maildir
]
[
.B -f
.I format
]
[
.B -o
.I outbox
]
.SH DESCRIPTION
.PP
.B Nail
edits a mailbox in an
.IR acme (1)
environment.
The default mailbox is /mail/fs/mbox.
Nail shows 3 views:
The list view, the message view, and the composition view.
.PP
At startup,
.B Nail
takes the following options:
.PD 0
.TP
.B -T
Disable threading
.TP
.B -O
Disable writing to outbox
.PD 0
.TP
.BI -s
Accept sendmail plumb messages. By default, only
the Nail instance viewing /mail/fs/mbox will
accept plumb messages.
.PD 0
.TP
.BI -m " maildir
Open the maildir
.I maildir
instead of the default
.PD 0
.TP
.BI -f " format
Define the format of individual messages in the list view
(see "Format strings" below).
.TP
.BI -o " outbox
Save a copy of outgoing messages to the mailbox
.IR outbox ,
instead of discarding them after they're enqueued.
.PP
Nail presents and acme interface for a upas/fs mailbox.
When started, a mailbox, by default
.IR /mail/fs/mbox ,
is presented.
In the message list, the tag bar commands typically affect
the selected messsage.
In the message and composition views, they typically apply
to the current message.
.PP
The following text commands are recognized by the message
list:
.TP
.B Put
Flush pending changes back to
.IR upasfs (4).
.PD 0
.TP
.B Delmesg, Undelmesg
Flags a message for deletion on the next
.I Put
invocation.
.PD 0
.TP
.B Next
Select the next unread message in the mailbox.
.PD 0
.TP
.B Mark [±flags]
Add or remove message flags. The flags recognized
are listed in
.IR upasfs (4)
.PD 0
.TP
.B Redraw
Redraws the contents of the mailbox.
.PP
The following text commands are recognized by the message
view:
.TP
.B Reply [all]
Replies to a message, quoting it.
If all is specified, reply to all addresses on the message.
.PD 0
.TP
.B Delmesg, Undelmesg
As with the message view, but applied to the open message.
.PD 0
.TP
.B Mark
As with the message view, but applied to the open message.
.PP
The following text commands are recognized by the composition
window:
.TP
.B Post
Sends the message currently being composed.
.SS Format strings
The formatting of messages in the list view is controlled by the
format string defined through the
.I -f
flag.
The format string is composed of multiple directives: plain
characters, which are displayed unchanged; indentation directives,
which allows spacing based on thread depth; and messages directives,
which display message fields.
.PP
Directives have the following format:
.IP
.B "% [flags] [width] verb
.PP
.IR width
limits the number of characters displayed.
If width is negative, text is aligned right instead of left.
.PP
The supported flags are:
.PD 0
.TP
.B >
Insert indentation into the start of the field. This
does not increase the width of the field.
.PP
Messages directives are:
.PD 0
.TP
.B s
Subject
.PD 0
.TP
.B f
From field
.PD 0
.TP
.B F
From field including sender's name
.PD 0
.TP
.B t
To field
.PD 0
.TP
.B c
Cc field
.PD 0
.TP
.B r
Reply-to field
.PP
Indentation directives are:
.PD 0
.TP
.B i
Adds spacing depending on message depth in thread but limited to a single level.
If
.IR width
is not specified, adds a tabulation otherwise
.IR width
specifies the number of spaces to display.
.PD 0
.TP
.B I
Similar to
.IR i
but not limited to a single level.
.PP
Two special directives are also available:
.PD 0
.TP
.B "[...]
Text within the brackets is displayed if the message is not the toplevel message of a thread.
.PD 0
.TP
.B "{...}
Text within the braces is used as the format string for tmfmt(2).
.PP
The default format string is "%>48s\\t<%f>"
.SH FILES
.SH "SEE ALSO"
.IR mail (1),
.IR aliasmail (8),
.IR filter (1),
.IR marshal (1),
.IR mlmgr (1),
.IR nedmail (1),
.IR upasfs (4),
.IR smtp (8),
.IR faces (1),
.IR rewrite (6)
.SH BUGS
Probably.

View file

@ -0,0 +1,290 @@
#include <u.h>
#include <libc.h>
#include <bio.h>
#include <thread.h>
#include <regexp.h>
#include "mail.h"
typedef struct Fn Fn;
struct Fn {
char *name;
void (*fn)(Comp *, char **, int);
};
void
execmarshal(void *p)
{
Comp *c;
char *av[8];
int na;
c = p;
rfork(RFFDG);
dup(c->fd[0], 0);
close(c->fd[0]);
close(c->fd[1]);
na = 0;
av[na++] = "marshal";
av[na++] = "-8";
if(savebox != nil){
av[na++] = "-S";
av[na++] = savebox;
}
if(c->rpath != nil){
av[na++] = "-R";
av[na++] = c->rpath;
}
av[na] = nil;
assert(na < nelem(av));
procexec(c->sync, "/bin/upas/marshal", av);
}
static void
postmesg(Comp *c, char **, int nf)
{
char *buf, wpath[64], *path;
int n, fd;
Mesg *m;
snprint(wpath, sizeof(wpath), "/mnt/acme/%d/body", c->id);
if(nf != 0){
fprint(2, "Post: too many args\n");
return;
}
if((fd = open(wpath, OREAD)) == -1){
fprint(2, "open body: %r\n");
return;
}
if(pipe(c->fd) == -1)
sysfatal("pipe: %r\n");
c->sync = chancreate(sizeof(ulong), 0);
procrfork(execmarshal, c, Stack, RFNOTEG);
recvul(c->sync);
chanfree(c->sync);
close(c->fd[0]);
/* needed because mail is by default Latin-1 */
fprint(c->fd[1], "Content-Type: text/plain; charset=\"UTF-8\"\n");
fprint(c->fd[1], "Content-Transfer-Encoding: 8bit\n");
buf = emalloc(Bufsz);
while((n = read(fd, buf, Bufsz)) > 0)
if(write(c->fd[1], buf, n) != n)
break;
write(c->fd[1], "\n", 1);
close(c->fd[1]);
close(fd);
if(n == -1)
return;
if(fprint(c->ctl, "name %s:Sent\n", c->path) == -1)
sysfatal("write ctl: %r");
if(c->replyto != nil){
if((m = mesglookup(c->rname, c->rdigest)) == nil)
return;
m->flags |= Fresp;
path = estrjoin(mbox.path, "/", m->name, "/flags", nil);
if((fd = open(path, OWRITE)) != -1){
fprint(fd, "+a");
close(fd);
}
mbredraw(m, 0, 0);
free(path);
}
fprint(c->ctl, "clean\n");
}
static void
compquit(Comp *c, char **, int)
{
c->quitting = 1;
}
static Fn compfn[] = {
{"Post", postmesg},
{"Del", compquit},
{nil},
};
static void
compmain(void *cp)
{
char *f[32];
int nf;
Event ev;
Comp *c, **pc;
Fn *p;
c = cp;
c->quitting = 0;
c->qnext = mbox.opencomp;
mbox.opencomp = c;
fprint(c->ctl, "clean\n");
mbox.nopen++;
while(!c->quitting){
if(winevent(c, &ev) != 'M')
continue;
if(strcmp(ev.text, "Del") == 0)
break;
switch(ev.type){
case 'l':
case 'L':
if(matchmesg(&mbox, ev.text))
mesgopen(ev.text, nil);
else
winreturn(c, &ev);
break;
case 'x':
case 'X':
if((nf = tokenize(ev.text, f, nelem(f))) == 0)
continue;
for(p = compfn; p->fn != nil; p++)
if(strcmp(p->name, f[0]) == 0){
p->fn(c, &f[1], nf - 1);
break;
}
if(p->fn == nil)
winreturn(c, &ev);
break;
break;
}
}
for(pc = &mbox.opencomp; *pc != nil; pc = &(*pc)->qnext)
if(*pc == c){
*pc = c->qnext;
break;
}
mbox.nopen--;
c->qnext = nil;
winclose(c);
free(c->replyto);
free(c->rname);
free(c->rdigest);
free(c->rpath);
threadexits(nil);
}
static Biobuf*
openbody(Mesg *r)
{
Biobuf *f;
int q0, q1;
char *s;
assert(r->state & Sopen);
wingetsel(r, &q0, &q1);
if(q1 - q0 != 0){
s = smprint("/mnt/acme/%d/xdata", r->id);
f = Bopen(s, OREAD);
free(s);
}else
f = mesgopenbody(r);
return f;
}
int
strpcmp(void *a, void *b)
{
return strcmp(*(char**)a, *(char**)b);
}
void
show(Biobuf *fd, char *type, char **addrs, int naddrs)
{
char *sep;
int i, w;
w = 0;
sep = "";
if(naddrs == 0)
return;
qsort(addrs, naddrs, sizeof(char*), strpcmp);
Bprint(fd, "%s: ", type);
for(i = 0; i < naddrs; i++){
if(i > 0 && strcmp(addrs[i-1], addrs[i]) == 0)
continue;
w += Bprint(fd, "%s%s", sep, addrs[i]);
sep = ", ";
if(w > 50){
w = 0;
sep = "";
Bprint(fd, "\n%s: ", type);
}
}
Bprint(fd, "\n");
}
void
respondto(Biobuf *fd, char *to, Mesg *r, int all)
{
char *rpto, **addrs;
int n;
rpto = to;
if(r != nil)
rpto = (strlen(r->replyto) > 0) ? r->replyto : r->from;
if(r == nil || !all){
Bprint(fd, "To: %s\n", rpto);
return;
}
n = 0;
addrs = emalloc(64*sizeof(char*));
n += tokenize(to, addrs+n, 64-n);
n += tokenize(rpto, addrs+n, 64-n);
n += tokenize(r->to, addrs+n, 64-n);
show(fd, "To", addrs, n);
n = tokenize(r->cc, addrs+n, 64-n);
show(fd, "CC", addrs, n);
free(addrs);
}
void
compose(char *to, Mesg *r, int all)
{
static int ncompose;
Biobuf *rfd, *wfd;
Comp *c;
char *ln;
c = emalloc(sizeof(Comp));
if(r != nil)
c->path = esmprint("%s%s%s.%d", mbox.path, r->name, "Reply", ncompose++);
else
c->path = esmprint("%sCompose.%d", mbox.path, ncompose++);
wininit(c, c->path);
wintagwrite(c, "Post |fmt ");
wfd = bwinopen(c, "body", OWRITE);
respondto(wfd, to, r, all);
if(r == nil)
Bprint(wfd, "Subject: ");
else{
if(r->messageid != nil)
c->replyto = estrdup(r->messageid);
c->rpath = estrjoin(mbox.path, r->name, nil);
c->rname = estrdup(r->name);
c->rdigest = estrdup(r->digest);
Bprint(wfd, "Subject: ");
if(r->subject != nil && cistrncmp(r->subject, "Re", 2) != 0)
Bprint(wfd, "Re: ");
Bprint(wfd, "%s\n\n", r->subject);
Bprint(wfd, "Quoth %s:\n", r->fromcolon);
rfd = openbody(r);
if(rfd != nil){
while((ln = Brdstr(rfd, '\n', 0)) != nil)
if(Bprint(wfd, "> %s", ln) == -1)
break;
Bterm(rfd);
}
Bterm(wfd);
}
Bterm(wfd);
fprint(c->addr, "$");
fprint(c->ctl, "dot=addr");
threadcreate(compmain, c, Stack);
}

View file

@ -1,171 +0,0 @@
typedef struct Event Event;
typedef struct Exec Exec;
typedef struct Message Message;
typedef struct Window Window;
enum
{
STACK = 8192,
EVENTSIZE = 256,
NEVENT = 5,
};
struct Event
{
int c1;
int c2;
int q0;
int q1;
int flag;
int nb;
int nr;
char b[EVENTSIZE*UTFmax+1];
Rune r[EVENTSIZE+1];
};
struct Window
{
/* file descriptors */
int ctl;
int event;
int addr;
int data;
Biobuf *body;
/* event input */
char buf[512];
char *bufp;
int nbuf;
Event e[NEVENT];
int id;
int open;
Channel *cevent;
};
struct Message
{
Window *w;
int ctlfd;
char *name;
char *replyname;
char *replydigest;
uchar opened;
uchar dirty;
uchar isreply;
uchar deleted;
uchar writebackdel;
uchar tagposted;
uchar recursed;
uchar level;
/* header info */
char *fromcolon; /* from header file; all rest are from info file */
char *from;
char *to;
char *cc;
char *replyto;
char *date;
char *subject;
char *type;
char *disposition;
char *filename;
char *digest;
char *flags;
Message *next; /* next in this mailbox */
Message *prev; /* prev in this mailbox */
Message *head; /* first subpart */
Message *tail; /* last subpart */
};
enum
{
NARGS = 100,
NARGCHAR = 8*1024,
EXECSTACK = STACK+(NARGS+1)*sizeof(char*)+NARGCHAR
};
struct Exec
{
char *prog;
char **argv;
int p[2]; /* p[1] is write to program; p[0] set to prog fd 0*/
int q[2]; /* q[0] is read from program; q[1] set to prog fd 1 */
Channel *sync;
};
extern Window* newwindow(void);
extern int winopenfile(Window*, char*);
extern void winopenbody(Window*, int);
extern void winclosebody(Window*);
extern void wintagwrite(Window*, char*, int);
extern void winname(Window*, char*);
extern void winwriteevent(Window*, Event*);
extern void winread(Window*, uint, uint, char*);
extern int windel(Window*, int);
extern void wingetevent(Window*, Event*);
extern void wineventproc(void*);
extern void winwritebody(Window*, char*, int);
extern void winclean(Window*);
extern int winselect(Window*, char*, int);
extern char* winselection(Window*);
extern int winsetaddr(Window*, char*, int);
extern char* winreadbody(Window*, int*);
extern void windormant(Window*);
extern void winsetdump(Window*, char*, char*);
extern void readmbox(Message*, char*, char*);
extern void rewritembox(Window*, Message*);
extern void mkreply(Message*, char*, char*, Plumbattr*, char*);
extern void delreply(Message*);
extern int write2(int, int, char*, int, int);
extern int mesgadd(Message*, char*, Dir*, char*);
extern void mesgmenu(Window*, Message*);
extern void mesgmenuselect(Window*, Message*);
extern void mesgmenunew(Window*, Message*);
extern void mesgmenureflag(Window*, Message*);
extern int mesgopen(Message*, char*, char*, Message*, int, char*);
extern void mesgctl(void*);
extern void mesgsend(Message*);
extern void mesgdel(Message*, Message*);
extern void mesgmenudel(Window*, Message*, Message*);
extern void mesgmenumark(Window*, char*, char*);
extern void mesgmenumarkdel(Window*, Message*, Message*, int);
extern Message* mesglookup(Message*, char*, char*);
extern Message* mesglookupfile(Message*, char*, char*);
extern void mesgfreeparts(Message*);
extern char* readfile(char*, char*, int*);
extern char* readbody(char*, char*, int*);
extern void ctlprint(int, char*, ...);
extern void* emalloc(uint);
extern void* erealloc(void*, uint);
extern char* estrdup(char*);
extern char* estrstrdup(char*, char*);
extern char* egrow(char*, char*, char*);
extern char* eappend(char*, char*, char*);
extern void error(char*, ...);
extern int tokenizec(char*, char**, int, char*);
extern void execproc(void*);
extern void setflags(Message*, char *);
#pragma varargck argpos error 1
#pragma varargck argpos ctlprint 2
extern Window *wbox;
extern Message mbox;
extern Message replies;
extern char *fsname;
extern int plumbsendfd;
extern int plumbseemailfd;
extern char *home;
extern char *outgoing;
extern char *mailboxdir;
extern char *user;
extern char deleted[];
extern int wctlfd;
extern int shortmenu;
extern int altmenu;

View file

@ -1,74 +0,0 @@
#include <u.h>
#include <libc.h>
#include <bio.h>
#include <thread.h>
#include <ctype.h>
#include <plumb.h>
#include "dat.h"
char*
formathtml(char *body, int *np)
{
int i, j, p[2], q[2];
Exec *e;
char buf[1024];
e = emalloc(sizeof(Exec));
if(pipe(p) < 0 || pipe(q) < 0)
error("can't create pipe: %r");
e->p[0] = p[0];
e->p[1] = p[1];
e->q[0] = q[0];
e->q[1] = q[1];
e->argv = emalloc(4*sizeof(char*));
e->argv[0] = estrdup("htmlfmt");
e->argv[1] = estrdup("-a");
e->argv[2] = estrdup("-cutf-8");
e->argv[3] = nil;
e->prog = "/bin/htmlfmt";
e->sync = chancreate(sizeof(int), 0);
proccreate(execproc, e, EXECSTACK);
recvul(e->sync);
close(p[0]);
close(q[1]);
if((i=write(p[1], body, *np)) != *np){
fprint(2, "Mail: warning: htmlfmt failed: wrote %d of %d: %r\n", i, *np);
close(p[1]);
close(q[0]);
return body;
}
close(p[1]);
free(body);
body = nil;
i = 0;
for(;;){
j = read(q[0], buf, sizeof buf);
if(j <= 0)
break;
body = realloc(body, i+j+1);
if(body == nil)
error("realloc failed: %r");
memmove(body+i, buf, j);
i += j;
body[i] = '\0';
}
close(q[0]);
*np = i;
return body;
}
char*
readbody(char *type, char *dir, int *np)
{
char *body;
body = readfile(dir, "body", np);
if(body != nil && strcmp(type, "text/html") == 0)
return formathtml(body, np);
return body;
}

View file

@ -1,644 +0,0 @@
#include <u.h>
#include <libc.h>
#include <bio.h>
#include <thread.h>
#include <plumb.h>
#include <ctype.h>
#include "dat.h"
char *maildir = "/mail/fs/"; /* mountpoint of mail file system */
char *mailtermdir = "/mnt/term/mail/fs/"; /* alternate mountpoint */
char *mboxname = "mbox"; /* mailboxdir/mboxname is mail spool file */
char *mailboxdir = nil; /* nil == /mail/box/$user */
char *fsname; /* filesystem for mailboxdir/mboxname is at maildir/fsname */
char *user;
char *outgoing;
Window *wbox;
Message mbox;
Message replies;
char *home;
int plumbsendfd;
int plumbseemailfd;
int plumbshowmailfd;
int plumbsendmailfd;
Channel *cplumb;
Channel *cplumbshow;
Channel *cplumbsend;
int wctlfd;
void mainctl(void*);
void plumbproc(void*);
void plumbshowproc(void*);
void plumbsendproc(void*);
void plumbthread(void);
void plumbshowthread(void*);
void plumbsendthread(void*);
int shortmenu;
int altmenu;
void
usage(void)
{
fprint(2, "usage: Mail [-sS] [-o outgoing] [mailboxname [directoryname]]\n");
threadexitsall("usage");
}
void
removeupasfs(void)
{
char buf[256];
if(strcmp(mboxname, "mbox") == 0)
return;
snprint(buf, sizeof buf, "close %s", mboxname);
write(mbox.ctlfd, buf, strlen(buf));
}
int
ismaildir(char *s)
{
char *path;
Dir *d;
int ret;
path = smprint("%s%s", maildir, s);
d = dirstat(path);
free(path);
if(d == nil)
return 0;
ret = d->qid.type & QTDIR;
free(d);
return ret;
}
void
threadmain(int argc, char *argv[])
{
char *s, *name;
char err[ERRMAX], *cmd;
int i, newdir;
Fmt fmt;
doquote = needsrcquote;
quotefmtinstall();
tmfmtinstall();
/* open these early so we won't miss notification of new mail messages while we read mbox */
plumbsendfd = plumbopen("send", OWRITE|OCEXEC);
plumbseemailfd = plumbopen("seemail", OREAD|OCEXEC);
plumbshowmailfd = plumbopen("showmail", OREAD|OCEXEC);
shortmenu = 0;
altmenu = 0;
ARGBEGIN{
case 's':
shortmenu = 1;
break;
case 'S':
shortmenu = 2;
break;
case 'A':
altmenu = 1;
break;
case 'o':
outgoing = EARGF(usage());
break;
case 'm':
smprint(maildir, "%s/", EARGF(usage()));
break;
default:
usage();
}ARGEND
name = "mbox";
/* bind the terminal /mail/fs directory over the local one */
if(access(maildir, 0)<0 && access(mailtermdir, 0)==0)
bind(mailtermdir, maildir, MAFTER);
newdir = 1;
if(argc > 0){
i = strlen(argv[0]);
if(argc>2 || i==0)
usage();
/* see if the name is that of an existing /mail/fs directory */
if(argc==1 && strchr(argv[0], '/')==0 && ismaildir(argv[0])){
name = argv[0];
mboxname = eappend(estrdup(maildir), "", name);
newdir = 0;
}else{
if(argv[0][i-1] == '/')
argv[0][i-1] = '\0';
s = strrchr(argv[0], '/');
if(s == nil)
mboxname = estrdup(argv[0]);
else{
*s++ = '\0';
if(*s == '\0')
usage();
mailboxdir = argv[0];
mboxname = estrdup(s);
}
if(argc > 1)
name = argv[1];
else
name = mboxname;
}
}
user = getenv("user");
if(user == nil)
user = "none";
if(mailboxdir == nil)
mailboxdir = estrstrdup("/mail/box/", user);
if(outgoing == nil)
outgoing = estrstrdup(mailboxdir, "/outgoing");
s = estrstrdup(maildir, "ctl");
mbox.ctlfd = open(s, ORDWR|OCEXEC);
if(mbox.ctlfd < 0)
error("can't open %s: %r", s);
fsname = estrdup(name);
if(newdir && argc > 0){
s = emalloc(5+strlen(mailboxdir)+strlen(mboxname)+strlen(name)+10+1);
for(i=0; i<10; i++){
sprint(s, "open %s/%s %s", mailboxdir, mboxname, fsname);
if(write(mbox.ctlfd, s, strlen(s)) >= 0)
break;
err[0] = '\0';
errstr(err, sizeof err);
if(strstr(err, "mbox name in use") == nil)
error("can't create directory %s for mail: %s", name, err);
free(fsname);
fsname = emalloc(strlen(name)+10);
sprint(fsname, "%s-%d", name, i);
}
if(i == 10)
error("can't open %s/%s: %r", mailboxdir, mboxname);
free(s);
}
s = estrstrdup(fsname, "/");
mbox.name = estrstrdup(maildir, s);
mbox.level= 0;
readmbox(&mbox, maildir, s);
home = getenv("home");
if(home == nil)
home = "/";
wbox = newwindow();
winname(wbox, mbox.name);
wintagwrite(wbox, "Put Mail Delmesg Save Next ", 3+1+4+1+7+1+4+1+4+1);
threadcreate(mainctl, wbox, STACK);
fmtstrinit(&fmt);
fmtprint(&fmt, "Mail");
if(shortmenu)
fmtprint(&fmt, " -%c", "sS"[shortmenu-1]);
if(altmenu)
fmtprint(&fmt, " -A");
if(outgoing)
fmtprint(&fmt, " -o %s", outgoing);
fmtprint(&fmt, " %s", name);
cmd = fmtstrflush(&fmt);
if(cmd == nil)
sysfatal("out of memory");
winsetdump(wbox, "/acme/mail", cmd);
mbox.w = wbox;
mesgmenu(wbox, &mbox);
winclean(wbox);
wctlfd = open("/dev/wctl", OWRITE|OCEXEC); /* for acme window */
cplumb = chancreate(sizeof(Plumbmsg*), 0);
cplumbshow = chancreate(sizeof(Plumbmsg*), 0);
if(strcmp(name, "mbox") == 0){
/*
* Avoid creating multiple windows to send mail by only accepting
* sendmail plumb messages if we're reading the main mailbox.
*/
plumbsendmailfd = plumbopen("sendmail", OREAD|OCEXEC);
cplumbsend = chancreate(sizeof(Plumbmsg*), 0);
proccreate(plumbsendproc, nil, STACK);
threadcreate(plumbsendthread, nil, STACK);
}
/* start plumb reader as separate proc ... */
proccreate(plumbproc, nil, STACK);
proccreate(plumbshowproc, nil, STACK);
threadcreate(plumbshowthread, nil, STACK);
/* ... and use this thread to read the messages */
plumbthread();
}
void
plumbproc(void*)
{
Plumbmsg *m;
threadsetname("plumbproc");
for(;;){
m = plumbrecv(plumbseemailfd);
sendp(cplumb, m);
if(m == nil)
threadexits(nil);
}
}
void
plumbshowproc(void*)
{
Plumbmsg *m;
threadsetname("plumbshowproc");
for(;;){
m = plumbrecv(plumbshowmailfd);
sendp(cplumbshow, m);
if(m == nil)
threadexits(nil);
}
}
void
plumbsendproc(void*)
{
Plumbmsg *m;
threadsetname("plumbsendproc");
for(;;){
m = plumbrecv(plumbsendmailfd);
sendp(cplumbsend, m);
if(m == nil)
threadexits(nil);
}
}
void
newmesg(char *name, char *digest)
{
Dir *d;
if(strncmp(name, mbox.name, strlen(mbox.name)) != 0)
return; /* message is about another mailbox */
if(mesglookupfile(&mbox, name, digest) != nil)
return;
d = dirstat(name);
if(d == nil)
return;
if(mesgadd(&mbox, mbox.name, d, digest))
mesgmenunew(wbox, &mbox);
free(d);
}
void
showmesg(char *name, char *digest)
{
char *n;
if(strncmp(name, mbox.name, strlen(mbox.name)) != 0)
return; /* message is about another mailbox */
n = estrdup(name+strlen(mbox.name));
if(n[strlen(n)-1] != '/')
n = egrow(n, "/", nil);
mesgopen(&mbox, mbox.name, name+strlen(mbox.name), nil, 1, digest);
free(n);
}
void
delmesg(char *name, char *digest, int dodel)
{
Message *m;
m = mesglookupfile(&mbox, name, digest);
if(m != nil){
mesgmenumarkdel(wbox, &mbox, m, 0);
if(dodel)
m->writebackdel = 1;
}
}
void
modmesg(char *name, char *digest)
{
Message *m;
char *flags;
if((m = mesglookupfile(&mbox, name, digest)) == nil)
return;
if((flags = readfile(name, "/flags", nil)) == nil)
return;
free(m->flags);
m->flags = flags;
mesgmenureflag(mbox.w, m);
}
extern int mesgsave(Message*, char*);
void
savemesg(char *box, char *name, char *digest)
{
char *s;
int ok;
Message *m;
m = mesglookupfile(&mbox, name, digest);
if(!m || m->isreply)
return;
s = estrdup("\t[saved");
if(!box[0])
ok = mesgsave(m, "stored");
else{
ok = mesgsave(m, box);
s = eappend(s, " ", box);
}
if(ok){
s = egrow(s, "]", nil);
mesgmenumark(mbox.w, m->name, s);
}
free(s);
}
void
plumbthread(void)
{
Plumbmsg *m;
Plumbattr *a;
char *type, *digest;
threadsetname("plumbthread");
while((m = recvp(cplumb)) != nil){
a = m->attr;
digest = plumblookup(a, "digest");
type = plumblookup(a, "mailtype");
if(type == nil)
fprint(2, "Mail: plumb message with no mailtype attribute\n");
else if(strcmp(type, "new") == 0)
newmesg(m->data, digest);
else if(strcmp(type, "delete") == 0)
delmesg(m->data, digest, 0);
else if(strcmp(type, "modify") == 0)
modmesg(m->data, digest);
else
fprint(2, "Mail: unknown plumb attribute %s\n", type);
plumbfree(m);
}
threadexits(nil);
}
void
plumbshowthread(void*)
{
Plumbmsg *m;
threadsetname("plumbshowthread");
while((m = recvp(cplumbshow)) != nil){
showmesg(m->data, plumblookup(m->attr, "digest"));
plumbfree(m);
}
threadexits(nil);
}
void
plumbsendthread(void*)
{
Plumbmsg *m;
threadsetname("plumbsendthread");
while((m = recvp(cplumbsend)) != nil){
mkreply(nil, "Mail", m->data, m->attr, nil);
plumbfree(m);
}
threadexits(nil);
}
int
mboxcommand(Window *w, char *s)
{
char *args[10], **targs, *r, *box;
Message *m, *next;
int ok, nargs, i, j;
char buf[128];
nargs = tokenize(s, args, nelem(args));
if(nargs == 0)
return 0;
if(strcmp(args[0], "Mail") == 0){
if(nargs == 1)
mkreply(nil, "Mail", "", nil, nil);
else
mkreply(nil, "Mail", args[1], nil, nil);
return 1;
}
if(strcmp(s, "Del") == 0){
if(mbox.dirty){
mbox.dirty = 0;
fprint(2, "mail: mailbox not written\n");
return 1;
}
ok = 1;
for(m=mbox.head; m!=nil; m=next){
next = m->next;
if(m->w){
if(windel(m->w, 0))
m->w = nil;
else
ok = 0;
}
}
for(m=replies.head; m!=nil; m=next){
next = m->next;
if(m->w){
if(windel(m->w, 0))
m->w = nil;
else
ok = 0;
}
}
if(ok){
windel(w, 1);
removeupasfs();
threadexitsall(nil);
}
return 1;
}
if(strcmp(s, "Put") == 0){
rewritembox(wbox, &mbox);
return 1;
}
if(strcmp(s, "Delmesg") == 0){
if(nargs > 1){
for(i=1; i<nargs; i++){
snprint(buf, sizeof buf, "%s%s", mbox.name, args[i]);
delmesg(buf, nil, 1);
}
}
s = winselection(w);
if(s == nil)
return 1;
nargs = 1;
for(i=0; s[i]; i++)
if(s[i] == '\n')
nargs++;
targs = emalloc(nargs*sizeof(char*)); /* could be too many for a local array */
nargs = getfields(s, targs, nargs, 1, "\n");
for(i=0; i<nargs; i++){
if(!isdigit(targs[i][0]))
continue;
j = atoi(targs[i]); /* easy way to parse the number! */
if(j == 0)
continue;
snprint(buf, sizeof buf, "%s%d", mbox.name, j);
delmesg(buf, nil, 1);
}
free(s);
free(targs);
return 1;
}
if(strncmp(args[0], "Save", 4) == 0){
box = "";
i = 1;
if(nargs > 1 && !mesglookupfile(&mbox, args[1], nil)){
box = args[1];
i++;
nargs--;
}
if(nargs > 1){
for(; i<nargs; i++){
snprint(buf, sizeof buf, "%s%s", mbox.name, args[i]);
savemesg(box, buf, nil);
}
}
s = winselection(w);
if(s == nil)
return 1;
nargs = 1;
for(i=0; s[i]; i++)
if(s[i] == '\n')
nargs++;
targs = emalloc(nargs*sizeof(char*)); /* could be too many for a local array */
nargs = getfields(s, targs, nargs, 1, "\n");
for(i=0; i<nargs; i++){
if(!isdigit(targs[i][0]))
continue;
j = strtoul(targs[i], &r, 10);
if(j == 0 || *r != '/')
continue;
snprint(buf, sizeof buf, "%s%d", mbox.name, j);
savemesg(box, buf, nil);
}
free(s);
free(targs);
return 1;
}
if(strcmp(args[0], "Next") == 0){
ctlprint(w->ctl, "addr=dot\n");
winselect(w, "/^[0-9]*\\/ \\[\\*.*(\\n(\t.*)*)/", 1);
ctlprint(w->ctl, "show\n");
}
return 0;
}
void
mainctl(void *v)
{
Window *w;
Event *e, *e2, *eq, *ea;
int na, nopen;
char *s, *t, *buf;
w = v;
proccreate(wineventproc, w, STACK);
for(;;){
e = recvp(w->cevent);
switch(e->c1){
default:
Unknown:
print("unknown message %c%c\n", e->c1, e->c2);
break;
case 'E': /* write to body; can't affect us */
break;
case 'F': /* generated by our actions; ignore */
break;
case 'K': /* type away; we don't care */
break;
case 'M':
switch(e->c2){
case 'x':
case 'X':
ea = nil;
e2 = nil;
if(e->flag & 2)
e2 = recvp(w->cevent);
if(e->flag & 8){
ea = recvp(w->cevent);
na = ea->nb;
recvp(w->cevent);
}else
na = 0;
s = e->b;
/* if it's a known command, do it */
if((e->flag&2) && e->nb==0)
s = e2->b;
if(na){
t = emalloc(strlen(s)+1+na+1);
sprint(t, "%s %s", s, ea->b);
s = t;
}
/* if it's a long message, it can't be for us anyway */
if(!mboxcommand(w, s)) /* send it back */
winwriteevent(w, e);
if(na)
free(s);
break;
case 'l':
case 'L':
buf = nil;
eq = e;
if(e->flag & 2){
e2 = recvp(w->cevent);
eq = e2;
}
s = eq->b;
if(eq->q1>eq->q0 && eq->nb==0){
buf = emalloc((eq->q1-eq->q0)*UTFmax+1);
winread(w, eq->q0, eq->q1, buf);
s = buf;
}
nopen = 0;
do{
/* skip 'deleted' string if present' */
if(strncmp(s, deleted, strlen(deleted)) == 0)
s += strlen(deleted);
/* skip mail box name if present */
if(strncmp(s, mbox.name, strlen(mbox.name)) == 0)
s += strlen(mbox.name);
nopen += mesgopen(&mbox, mbox.name, s, nil, 0, nil);
while(*s!='\0' && *s++!='\n')
;
}while(*s);
if(nopen == 0) /* send it back */
winwriteevent(w, e);
free(buf);
break;
case 'I': /* modify away; we don't care */
case 'D':
case 'd':
case 'i':
break;
default:
goto Unknown;
}
}
}
}

View file

@ -0,0 +1,195 @@
typedef struct Event Event;
typedef struct Win Win;
typedef struct Mesg Mesg;
typedef struct Mbox Mbox;
typedef struct Comp Comp;
enum {
Stack = 64*1024,
Bufsz = 8192,
Eventsz = 256*UTFmax,
Subjlen = 56,
};
enum {
Sdummy = 1<<0, /* message placeholder */
Stoplev = 1<<1, /* not a response to anything */
Sopen = 1<<2, /* opened for viewing */
};
enum {
Fresp = 1<<0, /* has been responded to */
Fseen = 1<<1, /* has been viewed */
Fdel = 1<<2, /* was deleted */
Ftodel = 1<<3, /* pending deletion */
};
enum {
Vflat,
Vgroup,
};
struct Event {
char action;
char type;
int p0; /* click point */
int q0; /* expand lo */
int q1; /* expand hi */
int flags;
int ntext;
char text[Eventsz + 1];
};
struct Win {
int id;
Ioproc *io;
Biobuf *event;
int revent;
int ctl;
int addr;
int data;
int open;
};
/*
* In progress compositon
*/
struct Comp {
Win;
/* exec setup */
Channel *sync;
int fd[2];
/* to relate back the message */
char *replyto;
char *rname;
char *rpath;
char *rdigest;
char *path;
int quitting;
Comp *qnext;
};
/*
* Message in mailbox
*/
struct Mesg {
Win;
/* bookkeeping */
char *name;
int state;
int flags;
u32int hash;
char quitting;
Mesg *qnext;
/* exec setup */
Channel *sync;
char *path;
int fd[2];
Mesg *parent;
Mesg **child;
int nchild;
int nsub; /* transitive children */
Mesg *body; /* best attachment to use, or nil */
Mesg **parts;
int nparts;
int xparts;
/* info fields */
char *from;
char *to;
char *cc;
char *replyto;
char *date;
char *subject;
char *type;
char *disposition;
char *messageid;
char *filename;
char *digest;
char *mflags;
char *fromcolon;
char *inreplyto;
vlong time;
};
/*
*The mailbox we're showing.
*/
struct Mbox {
Win;
Mesg **mesg;
Mesg **hash;
int mesgsz;
int hashsz;
int nmesg;
int ndead;
Mesg *openmesg;
Comp *opencomp;
int canquit;
Channel *see;
Channel *show;
Channel *event;
Channel *send;
int view;
int nopen;
char *path;
};
extern Mbox mbox;
extern Reprog *mesgpat;
extern char *savebox;
/* window management */
void wininit(Win*, char*);
int winopen(Win*, char*, int);
Biobuf *bwinopen(Win*, char*, int);
Biobuf *bwindata(Win*, int);
void winclose(Win*);
void wintagwrite(Win*, char*);
int winevent(Win*, Event*);
void winreturn(Win*, Event*);
int winread(Win*, int, int, char*, int);
int matchmesg(Win*, char*);
char *winreadsel(Win*);
void wingetsel(Win*, int*, int*);
void winsetsel(Win*, int, int);
/* messages */
Mesg *mesglookup(char*, char*);
Mesg *mesgload(char*);
Mesg *mesgopen(char*, char*);
int mesgmatch(Mesg*, char*, char*);
void mesgclear(Mesg*);
void mesgfree(Mesg*);
void mesgpath2name(char*, int, char*);
int mesgflagparse(char*, int*);
Biobuf* mesgopenbody(Mesg*);
/* mailbox */
void mbredraw(Mesg*, int, int);
/* composition */
void compose(char*, Mesg*, int);
/* utils */
void *emalloc(ulong);
void *erealloc(void*, ulong);
char *estrdup(char*);
char *estrjoin(char*, ...);
char *esmprint(char*, ...);
char *rslurp(Mesg*, char*, int*);
char *fslurp(int, int*);
u32int strhash(char*);

1056
sys/src/cmd/upas/Mail/mbox.c Normal file

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -1,25 +1,14 @@
</$objtype/mkfile
BIN=/acme/bin/$objtype
TARG=Mail
OFILES=\
html.$O\
mail.$O\
mbox.$O\
mesg.$O\
reply.$O\
comp.$O\
util.$O\
win.$O\
win.$O
HFILES=dat.h
# BIN=/acme/bin/$objtype
BIN=$ABIN
HFILES=mail.h
</sys/src/cmd/mkone
<../mkupas
$O.out: $OFILES
$LD -o $target $LDFLAGS $OFILES
syms:V:
$CC -a mail.c >syms
$CC -aa mesg.c reply.c util.c win.c >>syms

View file

@ -1,578 +0,0 @@
#include <u.h>
#include <libc.h>
#include <bio.h>
#include <thread.h>
#include <ctype.h>
#include <plumb.h>
#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<n; i++)
if(body[i] == '\n')
nlines++;
nlines++;
lines = emalloc(nlines*sizeof(char*));
nlines = getfields(body, lines, nlines, 0, "\n");
/* delete leading and trailing blank lines */
i = 0;
while(i<nlines && lines[i][0]=='\0')
i++;
while(i<nlines && lines[nlines-1][0]=='\0')
nlines--;
while(i < nlines){
Bprint(b, ">%s%s\n", lines[i][0]=='>'? "" : " ", lines[i]);
i++;
}
free(lines);
free(body); /* will free quotetext if non-nil */
free(type);
return 1;
}
void
mkreply(Message *m, char *label, char *to, Plumbattr *attr, char *quotetext)
{
Message *r;
char *dir, *t;
int quotereply;
Plumbattr *a;
quotereply = (label[0] == 'Q');
r = emalloc(sizeof(Message));
r->isreply = 1;
if(m != nil){
r->replyname = estrdup(m->name);
r->replydigest = estrdup(m->digest);
}
r->next = replies.head;
r->prev = nil;
if(replies.head != nil)
replies.head->prev = r;
replies.head = r;
if(replies.tail == nil)
replies.tail = r;
r->name = emalloc(strlen(mbox.name)+strlen(label)+10);
sprint(r->name, "%s%s%d", mbox.name, label, ++replyid);
r->w = newwindow();
winname(r->w, r->name);
ctlprint(r->w->ctl, "cleartag");
wintagwrite(r->w, "fmt Look Post Undo", 4+5+5+4);
r->tagposted = 1;
threadcreate(mesgctl, r, STACK);
winopenbody(r->w, OWRITE);
if(to!=nil && to[0]!='\0')
Bprint(r->w->body, "%s\n", to);
for(a=attr; a; a=a->next)
Bprint(r->w->body, "%s: %s\n", a->name, a->value);
dir = nil;
if(m != nil){
dir = estrstrdup(mbox.name, m->name);
if(to == nil && attr == nil){
/* Reply goes to replyto; Reply all goes to From and To and CC */
if(strstr(label, "all") == nil)
Bprint(r->w->body, "To: %s\n", m->replyto);
else{ /* Replyall */
if(strlen(m->from) > 0)
Bprint(r->w->body, "To: %s\n", m->from);
if(strlen(m->to) > 0)
Bprint(r->w->body, "To: %s\n", m->to);
if(strlen(m->cc) > 0)
Bprint(r->w->body, "CC: %s\n", m->cc);
}
}
if(strlen(m->subject) > 0){
t = "Subject: Re: ";
if(strlen(m->subject) >= 3)
if(tolower(m->subject[0])=='r' && tolower(m->subject[1])=='e' && m->subject[2]==':')
t = "Subject: ";
Bprint(r->w->body, "%s%s\n", t, m->subject);
}
if(!quotereply){
Bprint(r->w->body, "Include: %sraw\n", dir);
free(dir);
}
}
Bprint(r->w->body, "\n");
if(m == nil)
Bprint(r->w->body, "\n");
else if(quotereply){
quote(m, r->w->body, dir, quotetext);
free(dir);
}
winclosebody(r->w);
if(m==nil && (to==nil || to[0]=='\0'))
winselect(r->w, "0", 0);
else
winselect(r->w, "$", 0);
winclean(r->w);
windormant(r->w);
}
void
delreply(Message *m)
{
if(m->next == nil)
replies.tail = m->prev;
else
m->next->prev = m->prev;
if(m->prev == nil)
replies.head = m->next;
else
m->prev->next = m->next;
mesgfreeparts(m);
free(m);
}
/* copy argv to stack and free the incoming strings, so we don't leak argument vectors */
void
buildargv(char **inargv, char *argv[NARGS+1], char args[NARGCHAR])
{
int i, n;
char *s, *a;
s = args;
for(i=0; i<NARGS; i++){
a = inargv[i];
if(a == nil)
break;
n = strlen(a)+1;
if((s-args)+n >= NARGCHAR) /* too many characters */
break;
argv[i] = s;
memmove(s, a, n);
s += n;
free(a);
}
argv[i] = nil;
}
void
execproc(void *v)
{
Exec *e;
int p[2], q[2];
char *prog;
char *argv[NARGS+1], args[NARGCHAR];
e = v;
p[0] = e->p[0];
p[1] = e->p[1];
q[0] = e->q[0];
q[1] = e->q[1];
prog = e->prog; /* known not to be malloc'ed */
rfork(RFFDG);
sendul(e->sync, 1);
buildargv(e->argv, argv, args);
free(e->argv);
chanfree(e->sync);
free(e);
dup(p[0], 0);
close(p[0]);
close(p[1]);
if(q[0]){
dup(q[1], 1);
close(q[0]);
close(q[1]);
}
procexec(nil, prog, argv);
//fprint(2, "exec: %s", e->prog);
//{int i;
//for(i=0; argv[i]; i++) print(" '%s'", argv[i]);
//print("\n");
//}
//argv[0] = "cat";
//argv[1] = nil;
//procexec(nil, "/bin/cat", argv);
fprint(2, "Mail: can't exec %s: %r\n", prog);
threadexits("can't exec");
}
enum{
ATTACH,
BCC,
CC,
FROM,
INCLUDE,
TO,
};
char *headers[] = {
"attach:",
"bcc:",
"cc:",
"from:",
"include:",
"to:",
nil,
};
int
whichheader(char *h)
{
int i;
for(i=0; headers[i]!=nil; i++)
if(cistrcmp(h, headers[i]) == 0)
return i;
return -1;
}
char *tolist[200];
char *cclist[200];
char *bcclist[200];
int ncc, nbcc, nto;
char *attlist[200];
char included[200];
int
addressed(char *name)
{
int i;
for(i=0; i<nto; i++)
if(strcmp(name, tolist[i]) == 0)
return 1;
for(i=0; i<ncc; i++)
if(strcmp(name, cclist[i]) == 0)
return 1;
for(i=0; i<nbcc; i++)
if(strcmp(name, bcclist[i]) == 0)
return 1;
return 0;
}
char*
skipbl(char *s, char *e)
{
while(s < e){
if(*s!=' ' && *s!='\t' && *s!=',')
break;
s++;
}
return s;
}
char*
findbl(char *s, char *e)
{
while(s < e){
if(*s==' ' || *s=='\t' || *s==',')
break;
s++;
}
return s;
}
/*
* comma-separate possibly blank-separated strings in line; e points before newline
*/
void
commas(char *s, char *e)
{
char *t;
/* may have initial blanks */
s = skipbl(s, e);
while(s < e){
s = findbl(s, e);
if(s == e)
break;
t = skipbl(s, e);
if(t == e) /* no more words */
break;
/* patch comma */
*s++ = ',';
while(s < t)
*s++ = ' ';
}
}
int
print2(int fd, int ofd, char *fmt, ...)
{
int m, n;
char *s;
va_list arg;
va_start(arg, fmt);
s = vsmprint(fmt, arg);
va_end(arg);
if(s == nil)
return -1;
m = strlen(s);
n = write(fd, s, m);
if(ofd >= 0)
write(ofd, s, m);
return n;
}
int
write2(int fd, int ofd, char *buf, int n, int nofrom)
{
char *from, *p;
int m = 0;
if(fd >= 0)
m = write(fd, buf, n);
if(ofd < 0)
return m;
if(nofrom == 0)
return write(ofd, buf, n);
/* need to escape leading From lines to avoid corrupting 'outgoing' mailbox */
for(p=buf; *p; p+=m){
from = cistrstr(p, "from");
if(from == nil)
m = n;
else
m = from - p;
if(m > 0)
write(ofd, p, m);
if(from){
/* escape with space if From is at start of line */
if(p==buf || from[-1]=='\n')
write(ofd, " ", 1);
write(ofd, from, 4);
m += 4;
}
n -= m;
}
return p - buf;
}
void
mesgsend(Message *m)
{
char *s, *body, *to;
int i, j, h, n, natt, p[2];
Exec *e;
Channel *sync;
Message *r;
int first, nfld, delit, ofd;
char *copy, *fld[100];
Tzone *tz;
Tm now;
body = winreadbody(m->w, &n);
/* assemble to: list from first line, to: line, and cc: line */
nto = 0;
natt = 0;
ncc = 0;
nbcc = 0;
first = 1;
to = body;
for(;;){
for(s=to; *s!='\n'; s++)
if(*s == '\0'){
free(body);
return;
}
if(s++ == to) /* blank line */
break;
/* make copy of line to tokenize */
copy = emalloc(s-to);
memmove(copy, to, s-to);
copy[s-to-1] = '\0';
nfld = tokenizec(copy, fld, nelem(fld), ", \t");
if(nfld == 0){
free(copy);
break;
}
n -= s-to;
switch(h = whichheader(fld[0])){
case TO:
case FROM:
delit = 1;
commas(to+strlen(fld[0]), s-1);
for(i=1; i<nfld && nto<nelem(tolist); i++)
if(!addressed(fld[i]))
tolist[nto++] = estrdup(fld[i]);
break;
case BCC:
delit = 1;
commas(to+strlen(fld[0]), s-1);
for(i=1; i<nfld && nbcc<nelem(bcclist); i++)
if(!addressed(fld[i]))
bcclist[nbcc++] = estrdup(fld[i]);
break;
case CC:
delit = 1;
commas(to+strlen(fld[0]), s-1);
for(i=1; i<nfld && ncc<nelem(cclist); i++)
if(!addressed(fld[i]))
cclist[ncc++] = estrdup(fld[i]);
break;
case ATTACH:
case INCLUDE:
delit = 1;
for(i=1; i<nfld && natt<nelem(attlist); i++){
attlist[natt] = estrdup(fld[i]);
included[natt++] = (h == INCLUDE);
}
break;
default:
if(first){
delit = 1;
for(i=0; i<nfld && nto<nelem(tolist); i++)
tolist[nto++] = estrdup(fld[i]);
}else /* ignore it */
delit = 0;
break;
}
if(delit){
/* delete line from body */
memmove(to, s, n+1);
}else
to = s;
free(copy);
first = 0;
}
ofd = open(outgoing, OWRITE|OCEXEC); /* no error check necessary */
if(ofd >= 0){
/* From dhog Fri Aug 24 22:13:00 +0500 2001 */
tz = tzload("local");
tmnow(&now, tz);
fprint(ofd, "From %s %τ", user, tmfmt(&now, "WW MMM _D hh:mm:ss Z YYYY"));
fprint(ofd, "From: %s\n", user);
fprint(ofd, "Date: %τ", tmfmt(&now, "WW MMM _D hh:mm:ss Z YYYY"));
for(i=0; i<natt; i++)
if(included[i])
fprint(ofd, "Include: %s\n", attlist[i]);
else
fprint(ofd, "Attach: %s\n", attlist[i]);
/* needed because mail is by default Latin-1 */
fprint(ofd, "Content-Type: text/plain; charset=\"UTF-8\"\n");
fprint(ofd, "Content-Transfer-Encoding: 8bit\n");
}
e = emalloc(sizeof(Exec));
if(pipe(p) < 0)
error("can't create pipe: %r");
e->p[0] = p[0];
e->p[1] = p[1];
e->prog = "/bin/upas/marshal";
e->argv = emalloc((1+1+2+4*natt+1)*sizeof(char*));
e->argv[0] = estrdup("marshal");
e->argv[1] = estrdup("-8");
j = 2;
if(m->replyname){
e->argv[j++] = estrdup("-R");
e->argv[j++] = estrstrdup(mbox.name, m->replyname);
}
for(i=0; i<natt; i++){
if(included[i])
e->argv[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<nto-1; i++)
print2(p[1], ofd, "%s, ", tolist[i]);
print2(p[1], ofd, "%s\n", tolist[i]);
}
if(ncc > 0){
print2(p[1], ofd, "CC: ");
for(i=0; i<ncc-1; i++)
print2(p[1], ofd, "%s, ", cclist[i]);
print2(p[1], ofd, "%s\n", cclist[i]);
}
if(nbcc > 0){
print2(p[1], ofd, "BCC: ");
for(i=0; i<nbcc-1; i++)
print2(p[1], ofd, "%s, ", bcclist[i]);
print2(p[1], ofd, "%s\n", bcclist[i]);
}
i = strlen(body);
if(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<natt; i++)
if(included[i])
fprint(ofd, "=====> Include: %s\n", attlist[i]);
else
fprint(ofd, "=====> Attach: %s\n", attlist[i]);
}
if(ofd >= 0)
write(ofd, "\n", 1);
for(i=0; i<natt; i++)
free(attlist[i]);
close(ofd);
close(p[1]);
free(body);
if(m->replyname){
if((r = mesglookup(&mbox, m->replyname, m->replydigest)) != nil){
setflags(r, "a");
mesgmenureflag(mbox.w, r);
}
}
if(m->name[0] == '/')
s = estrdup(m->name);
else
s = estrstrdup(mbox.name, m->name);
s = egrow(s, "-R", nil);
winname(m->w, s);
free(s);
winclean(m->w);
/* mark message unopened because it's no longer the original message */
m->opened = 0;
}

View file

@ -2,104 +2,139 @@
#include <libc.h>
#include <bio.h>
#include <thread.h>
#include <plumb.h>
#include "dat.h"
#include <regexp.h>
void*
emalloc(uint n)
#include "mail.h"
void *
emalloc(ulong n)
{
void *p;
p = malloc(n);
if(p == nil)
error("can't malloc: %r");
memset(p, 0, n);
setmalloctag(p, getcallerpc(&n));
return p;
void *v;
v = mallocz(n, 1);
if(v == nil)
sysfatal("malloc: %r");
setmalloctag(v, getcallerpc(&n));
return v;
}
void*
erealloc(void *p, uint n)
void *
erealloc(void *p, ulong n)
{
p = realloc(p, n);
if(p == nil)
error("can't realloc: %r");
setmalloctag(p, getcallerpc(&n));
return p;
void *v;
v = realloc(p, n);
if(v == nil)
sysfatal("realloc: %r");
setmalloctag(v, getcallerpc(&p));
return v;
}
char*
estrdup(char *s)
{
char *t;
t = emalloc(strlen(s)+1);
strcpy(t, s);
return t;
}
char*
estrstrdup(char *s, char *t)
{
char *u;
u = emalloc(strlen(s)+strlen(t)+1);
strcpy(u, s);
strcat(u, t);
return u;
}
char*
eappend(char *s, char *sep, char *t)
{
char *u;
if(t == nil)
u = estrstrdup(s, sep);
else{
u = emalloc(strlen(s)+strlen(sep)+strlen(t)+1);
strcpy(u, s);
strcat(u, sep);
strcat(u, t);
}
free(s);
return u;
}
char*
egrow(char *s, char *sep, char *t)
{
s = eappend(s, sep, t);
free(t);
s = strdup(s);
if(s == nil)
sysfatal("strdup: %r");
setmalloctag(s, getcallerpc(&s));
return s;
}
void
error(char *fmt, ...)
{
Fmt f;
char buf[64];
va_list arg;
fmtfdinit(&f, 2, buf, sizeof buf);
fmtprint(&f, "Mail: ");
va_start(arg, fmt);
fmtvprint(&f, fmt, arg);
va_end(arg);
fmtprint(&f, "\n");
fmtfdflush(&f);
threadexitsall(fmt);
}
void
ctlprint(int fd, char *fmt, ...)
char*
estrjoin(char *s, ...)
{
va_list ap;
char *r, *t, *p, *e;
int n;
va_list arg;
va_start(arg, fmt);
n = vfprint(fd, fmt, arg);
va_end(arg);
if(n <= 0)
error("control file write error: %r");
va_start(ap, s);
n = strlen(s) + 1;
while((p = va_arg(ap, char*)) != nil)
n += strlen(p);
va_end(ap);
r = emalloc(n);
e = r + n;
va_start(ap, s);
t = strecpy(r, e, s);
while((p = va_arg(ap, char*)) != nil)
t = strecpy(t, e, p);
va_end(ap);
return r;
}
char*
esmprint(char *fmt, ...)
{
char *s;
va_list ap;
va_start(ap, fmt);
s = vsmprint(fmt, ap);
va_end(ap);
if(s == nil)
sysfatal("smprint: %r");
setmalloctag(s, getcallerpc(&fmt));
return s;
}
char*
fslurp(int fd, int *nbuf)
{
int n, sz, r;
char *buf;
n = 0;
sz = 128;
buf = emalloc(sz);
while(1){
r = read(fd, buf + n, sz - n);
if(r == 0)
break;
if(r == -1)
goto error;
n += r;
if(n == sz){
sz += sz/2;
buf = erealloc(buf, sz);
}
}
buf[n] = 0;
if(nbuf)
*nbuf = n;
return buf;
error:
free(buf);
return nil;
}
char *
rslurp(Mesg *m, char *f, int *nbuf)
{
char *path;
int fd;
char *r;
if(m == nil)
path = estrjoin(mbox.path, "/", f, nil);
else
path = estrjoin(mbox.path, "/", m->name, "/", f, nil);
fd = open(path, OREAD);
free(path);
if(fd == -1)
return nil;
r = fslurp(fd, nbuf);
close(fd);
return r;
}
u32int
strhash(char *s)
{
u32int h, c;
h = 5381;
while(c = *s++ & 0xff)
h = ((h << 5) + h) + c;
return h;
}

View file

@ -2,340 +2,280 @@
#include <libc.h>
#include <bio.h>
#include <thread.h>
#include <plumb.h>
#include "dat.h"
#include <regexp.h>
Window*
newwindow(void)
#include "mail.h"
static int
procrd(Biobufhdr *f, void *buf, long len)
{
char buf[12];
Window *w;
w = emalloc(sizeof(Window));
w->ctl = open("/mnt/wsys/new/ctl", ORDWR|OCEXEC);
if(w->ctl<0 || read(w->ctl, buf, 12)!=12)
error("can't open window ctl file: %r");
ctlprint(w->ctl, "noscroll\n");
w->id = atoi(buf);
w->event = winopenfile(w, "event");
w->addr = -1; /* will be opened when needed */
w->body = nil;
w->data = -1;
w->cevent = chancreate(sizeof(Event*), 0);
return w;
}
void
winsetdump(Window *w, char *dir, char *cmd)
{
if(dir != nil)
ctlprint(w->ctl, "dumpdir %s\n", dir);
if(cmd != nil)
ctlprint(w->ctl, "dump %s\n", cmd);
}
void
wineventproc(void *v)
{
Window *w;
int i;
w = v;
for(i=0; ; i++){
if(i >= NEVENT)
i = 0;
wingetevent(w, &w->e[i]);
sendp(w->cevent, &w->e[i]);
}
return ioread(f->aux, f->fid, buf, len);
}
static int
winopenfile1(Window *w, char *f, int m)
procwr(Biobufhdr *f, void *buf, long len)
{
char buf[64];
int fd;
sprint(buf, "/mnt/wsys/%d/%s", w->id, f);
fd = open(buf, m|OCEXEC);
if(fd < 0)
error("can't open window file %s: %r", f);
return fd;
return iowrite(f->aux, f->fid, buf, len);
}
int
winopenfile(Window *w, char *f)
/*
* NB: this function consumes integers with
* a trailing space, as generated by acme;
* it's not a general purpose number parsing
* function.
*/
static int
evgetnum(Biobuf *f)
{
return winopenfile1(w, f, ORDWR);
}
void
wintagwrite(Window *w, char *s, int n)
{
int fd;
fd = winopenfile(w, "tag");
if(write(fd, s, n) != n)
error("tag write: %r");
close(fd);
}
void
winname(Window *w, char *s)
{
ctlprint(w->ctl, "name %s\n", s);
}
void
winopenbody(Window *w, int mode)
{
char buf[256];
sprint(buf, "/mnt/wsys/%d/body", w->id);
w->body = Bopen(buf, mode|OCEXEC);
if(w->body == nil)
error("can't open window body file: %r");
}
void
winclosebody(Window *w)
{
if(w->body != nil){
Bterm(w->body);
w->body = nil;
}
}
void
winwritebody(Window *w, char *s, int n)
{
if(w->body == nil)
winopenbody(w, OWRITE);
if(Bwrite(w->body, s, n) != n)
error("write error to window: %r");
}
int
wingetec(Window *w)
{
if(w->nbuf == 0){
w->nbuf = read(w->event, w->buf, sizeof w->buf);
if(w->nbuf <= 0){
/* probably because window has exited, and only called by wineventproc, so just shut down */
threadexits(nil);
}
w->bufp = w->buf;
}
w->nbuf--;
return *w->bufp++;
}
int
wingeten(Window *w)
{
int n, c;
int c, n;
n = 0;
while('0'<=(c=wingetec(w)) && c<='9')
while('0'<=(c=Bgetc(f)) && c<='9')
n = n*10+(c-'0');
if(c != ' ')
error("event number syntax");
if(c != ' '){
werrstr("event number syntax: %c", c);
return -1;
}
return n;
}
int
wingeter(Window *w, char *buf, int *nb)
static int
evgetdata(Biobuf *f, Event *e)
{
int i, n, o;
Rune r;
int n;
r = wingetec(w);
buf[0] = r;
n = 1;
if(r >= Runeself) {
while(!fullrune(buf, n))
buf[n++] = wingetec(w);
chartorune(&r, buf);
}
*nb = n;
return r;
}
void
wingetevent(Window *w, Event *e)
{
int i, nb;
e->c1 = wingetec(w);
e->c2 = wingetec(w);
e->q0 = wingeten(w);
e->q1 = wingeten(w);
e->flag = wingeten(w);
e->nr = wingeten(w);
if(e->nr > EVENTSIZE)
error("event string too long");
e->nb = 0;
for(i=0; i<e->nr; i++){
e->r[i] = wingeter(w, e->b+e->nb, &nb);
e->nb += nb;
o = 0;
n = evgetnum(f);
for(i = 0; i < n; i++){
if((r = Bgetrune(f)) == -1)
break;
o += runetochar(e->text + o, &r);
}
e->r[e->nr] = 0;
e->b[e->nb] = 0;
if(wingetec(w) != '\n')
error("event syntax error");
e->text[o] = 0;
return o;
}
void
winwriteevent(Window *w, Event *e)
int
winevent(Win *w, Event *e)
{
fprint(w->event, "%c%c%d %d\n", e->c1, e->c2, e->q0, e->q1);
int flags;
flags = 0;
Again:
e->action = Bgetc(w->event);
e->type = Bgetc(w->event);
e->q0 = evgetnum(w->event);
e->q1 = evgetnum(w->event);
e->flags = evgetnum(w->event);
e->ntext = evgetdata(w->event, e);
if(Bgetc(w->event) != '\n'){
werrstr("unterminated message");
return -1;
}
//fprint(2, "event: %c%c %d %d %x %.*s\n", e->action, e->type, e->q0, e->q1, e->flags, e->ntext, e->text);
if(e->flags & 0x2){
e->p0 = e->q0;
flags = e->flags;
goto Again;
}
e->flags |= flags;
return e->action;
}
void
winread(Window *w, uint q0, uint q1, char *data)
winreturn(Win *w, Event *e)
{
if(e->flags & 0x2)
fprint(w->revent, "%c%c%d %d\n", e->action, e->type, e->p0, e->p0);
else
fprint(w->revent, "%c%c%d %d\n", e->action, e->type, e->q0, e->q1);
}
int
winopen(Win *w, char *f, int mode)
{
char buf[128];
int fd;
snprint(buf, sizeof(buf), "/mnt/wsys/%d/%s", w->id, f);
if((fd = open(buf, mode|OCEXEC)) == -1)
sysfatal("open %s: %r", buf);
return fd;
}
Biobuf*
bwinopen(Win *w, char *f, int mode)
{
char buf[128];
Biobuf *bfd;
snprint(buf, sizeof(buf), "/mnt/wsys/%d/%s", w->id, f);
if((bfd = Bopen(buf, mode|OCEXEC)) == nil)
sysfatal("open %s: %r", buf);
bfd->aux = w->io;
Biofn(bfd, (mode == OREAD)?procrd:procwr);
return bfd;
}
Biobuf*
bwindata(Win *w, int mode)
{
int fd;
if((fd = dup(w->data, -1)) == -1)
sysfatal("dup: %r");
return Bfdopen(fd, mode);
}
void
wininit(Win *w, char *name)
{
char buf[12];
w->ctl = open("/mnt/wsys/new/ctl", ORDWR|OCEXEC);
if(w->ctl < 0)
sysfatal("winopen: %r");
if(read(w->ctl, buf, 12)!=12)
sysfatal("read ctl: %r");
if(fprint(w->ctl, "name %s\n", name) == -1)
sysfatal("write ctl: %r");
if(fprint(w->ctl, "noscroll\n") == -1)
sysfatal("write ctl: %r");
w->io = ioproc();
w->id = atoi(buf);
w->event = bwinopen(w, "event", OREAD);
w->revent = winopen(w, "event", OWRITE);
w->addr = winopen(w, "addr", ORDWR);
w->data = winopen(w, "data", ORDWR);
w->open = 1;
}
void
winclose(Win *w)
{
if(w->open)
fprint(w->ctl, "delete\n");
if(w->data != -1)
close(w->data);
if(w->addr != -1)
close(w->addr);
if(w->event != nil)
Bterm(w->event);
if(w->revent != -1)
close(w->revent);
if(w->io)
closeioproc(w->io);
if(w->ctl != -1)
close(w->ctl);
w->ctl = -1;
w->data = -1;
w->addr = -1;
w->event = nil;
w->revent = -1;
w->io = nil;
}
void
wintagwrite(Win *w, char *s)
{
int fd, n;
n = strlen(s);
fd = winopen(w, "tag", OWRITE);
if(write(fd, s, n) != n)
sysfatal("tag write: %r");
close(fd);
}
int
winread(Win *w, int q0, int q1, char *data, int ndata)
{
int m, n, nr;
char buf[256];
char *buf;
if(w->addr < 0)
w->addr = winopenfile(w, "addr");
if(w->data < 0)
w->data = winopenfile(w, "data");
m = q0;
buf = emalloc(Bufsz);
while(m < q1){
n = sprint(buf, "#%d", m);
if(write(w->addr, buf, n) != n)
error("error writing addr: %r");
n = read(w->data, buf, sizeof buf);
if(n <= 0)
error("reading data: %r");
if(write(w->addr, buf, n) != n){
fprint(2, "error writing addr: %r");
goto err;
}
n = read(w->data, buf, Bufsz);
if(n <= 0){
fprint(2, "reading data: %r");
goto err;
}
nr = utfnlen(buf, n);
while(m+nr >q1){
do; while(n>0 && (buf[--n]&0xC0)==0x80);
--nr;
}
if(n == 0)
if(n == 0 || n > ndata)
break;
memmove(data, buf, n);
ndata -= n;
data += n;
*data = 0;
m += nr;
}
free(buf);
return 0;
err:
free(buf);
return -1;
}
char*
winreadsel(Win *w)
{
int n, q0, q1;
char *r;
wingetsel(w, &q0, &q1);
n = UTFmax*(q1-q0);
r = emalloc(n + 1);
if(winread(w, q0, q1, r, n) == -1){
free(r);
return nil;
}
return r;
}
void
windormant(Window *w)
wingetsel(Win *w, int *q0, int *q1)
{
if(w->addr >= 0){
close(w->addr);
w->addr = -1;
}
if(w->body != nil){
Bterm(w->body);
w->body = nil;
}
if(w->data >= 0){
close(w->data);
w->data = -1;
}
}
char *e, buf[25];
int
windel(Window *w, int sure)
{
if(sure)
write(w->ctl, "delete\n", 7);
else if(write(w->ctl, "del\n", 4) != 4)
return 0;
/* event proc will die due to read error from event file */
windormant(w);
close(w->ctl);
w->ctl = -1;
close(w->event);
w->event = -1;
return 1;
fprint(w->ctl, "addr=dot");
if(pread(w->addr, buf, 24, 0) != 24)
sysfatal("read addr: %r");
buf[24] = 0;
*q0 = strtol(buf, &e, 10);
*q1 = strtol(e, nil, 10);
}
void
winclean(Window *w)
winsetsel(Win *w, int q0, int q1)
{
if(w->body)
Bflush(w->body);
ctlprint(w->ctl, "clean\n");
fprint(w->addr, "#%d,#%d", q0, q1);
fprint(w->ctl, "dot=addr");
}
int
winsetaddr(Window *w, char *addr, int errok)
matchmesg(Win *, char *text)
{
if(w->addr < 0)
w->addr = winopenfile(w, "addr");
if(write(w->addr, addr, strlen(addr)) < 0){
if(!errok)
error("error writing addr(%s): %r", addr);
return 0;
}
return 1;
}
Resub m[3];
int
winselect(Window *w, char *addr, int errok)
{
if(winsetaddr(w, addr, errok)){
ctlprint(w->ctl, "dot=addr\n");
return 1;
memset(m, 0, sizeof(m));
if(strncmp(text, mbox.path, strlen(mbox.path)) == 0)
return strstr(text + strlen(mbox.path), "/body") == nil;
else if(regexec(mesgpat, text, m, nelem(m))){
if(*m[1].ep == 0 || *m[1].ep == '/'){
*m[1].ep = 0;
return 1;
}
}
return 0;
}
char*
winreadbody(Window *w, int *np) /* can't use readfile because acme doesn't report the length */
{
char *s;
int m, na, n;
if(w->body != nil)
winclosebody(w);
winopenbody(w, OREAD);
s = nil;
na = 0;
n = 0;
for(;;){
if(na < n+512){
na += 1024;
s = realloc(s, na+1);
}
m = Bread(w->body, s+n, na-n);
if(m <= 0)
break;
n += m;
}
s[n] = 0;
winclosebody(w);
*np = n;
return s;
}
char*
winselection(Window *w)
{
int fd, m, n;
char *buf;
char tmp[256];
fd = winopenfile1(w, "rdsel", OREAD);
if(fd < 0)
error("can't open rdsel: %r");
n = 0;
buf = nil;
for(;;){
m = read(fd, tmp, sizeof tmp);
if(m <= 0)
break;
buf = erealloc(buf, n+m+1);
memmove(buf+n, tmp, m);
n += m;
buf[n] = '\0';
}
close(fd);
return buf;
}