ref: efc2d967bb2d6ca8ef68bcc43f89bcd0e0f0be3a
dir: /mesg.c/
#include <u.h>
#include <libc.h>
#include <bio.h>
#include <thread.h>
#include <regexp.h>
#include "mail.h"
#define Datefmt "?WWW, ?MMM ?DD hh:mm:ss ?Z YYYY"
typedef struct Fn Fn;
struct Fn {
char *name;
void (*fn)(Mesg *, char **, int);
};
void
mesgclear(Mesg *m)
{
free(m->name);
free(m->from);
free(m->to);
free(m->cc);
free(m->replyto);
free(m->date);
free(m->subject);
free(m->type);
free(m->disposition);
free(m->messageid);
free(m->filename);
free(m->digest);
free(m->mflags);
free(m->fromcolon);
}
void
mesgfree(Mesg *m)
{
if(m == nil)
return;
mesgclear(m);
free(m);
}
static char*
line(char *data, char **pp, int z)
{
char *p, *q;
for(p=data; *p!='\0' && *p!='\n'; p++)
;
if(*p == '\n')
*pp = p+1;
else
*pp = p;
if(z && p == data)
return nil;
q = emalloc(p-data + 1);
memmove(q, data, p-data);
return q;
}
static char*
fc(Mesg *m, char *s)
{
char *r;
if(s != nil && strlen(m->from) != 0){
r = smprint("%s <%s>", s, m->from);
free(s);
return r;
}
if(m->from != nil)
return estrdup(m->from);
if(s != nil)
return s;
return estrdup("??");
}
Mesg*
mesgload(char *name)
{
char *info, *p;
int ninfo;
Mesg *m;
Tm tm;
m = emalloc(sizeof(Mesg));
m->name = estrjoin(name, "/", nil);
if((info = rslurp(m, "info", &ninfo)) == nil){
free(m->name);
free(m);
return nil;
}
p = info;
m->from = line(p, &p, 0);
m->to = line(p, &p, 0);
m->cc = line(p, &p, 0);
m->replyto = line(p, &p, 1);
m->date = line(p, &p, 0);
m->subject = line(p, &p, 0);
m->type = line(p, &p, 1);
m->disposition = line(p, &p, 1);
m->filename = line(p, &p, 1);
m->digest = line(p, &p, 1);
/* m->bcc = */ free(line(p, &p, 1));
m->inreplyto = line(p, &p, 1);
/* m->date = */ free(line(p, &p, 1));
/* m->sender = */ free(line(p, &p, 1));
m->messageid = line(p, &p, 0);
/* m->lines = */ free(line(p, &p, 1));
/* m->size = */ free(line(p, &p, 1));
m->mflags = line(p, &p, 0);
/* m->fileid = */ free(line(p, &p, 1));
m->fromcolon = fc(m, line(p, &p, 1));
free(info);
m->flags = Funseen;
if(strchr(m->mflags, 'd')) m->flags |= Fdel;
if(strchr(m->mflags, 's')) m->flags &= ~Funseen;
if(strchr(m->mflags, 'a')) m->flags |= Fresp;
m->time = time(nil);
if(tmparse(&tm, Datefmt, m->date, nil, nil) != nil)
m->time = tmnorm(&tm);
m->hash = 0;
if(m->messageid != nil)
m->hash = strhash(m->messageid);
return m;
}
static Mesg*
readparts(Mesg *m)
{
char *dpath, *apath;
int n, i, dfd;
Mesg *a, *sub, *best;
Dir *d;
dpath = estrjoin(mbox.path, m->name, nil);
dfd = open(dpath, OREAD);
free(dpath);
if(dfd == -1)
return m;
n = dirreadall(dfd, &d);
close(dfd);
if(n == -1)
sysfatal("%s read: %r", mbox.path);
best = nil;
m->attachments = erealloc(m->attachments, (m->nattachments + n)*sizeof(Mesg*));
for(i = 0; i < n; i++){
if(d[i].qid.type != QTDIR)
continue;
apath = estrjoin(m->name, d[i].name, nil);
a = mesgload(apath);
free(apath);
if(a == nil)
continue;
if(strncmp(a->type, "multipart/", strlen("multipart/")) == 0){
sub = readparts(a);
if(sub != a)
best = sub;
continue;
}
m->attachments[m->nattachments++] = a;
if(a->filename != nil || a->disposition != nil && strcmp(a->disposition, "inline") != 0)
continue;
if(strcmp(a->type, "text/plain") == 0)
best = a;
else if(best == nil && strcmp(a->type, "text/html") == 0)
best = a;
}
free(d);
if(best == nil)
return m;
return best;
}
static void
execfmt(void *pm)
{
Mesg *m;
m = pm;
rfork(RFFDG);
dup(m->fd[1], 1);
close(m->fd[0]);
close(m->fd[1]);
sendul(m->sync, 0);
procexecl(nil, "/bin/htmlfmt", "-a", "-cutf-8", m->path, nil);
}
static int
htmlfmt(Mesg *m, char *path)
{
if(pipe(m->fd) == -1)
sysfatal("pipe: %r");
m->sync = chancreate(sizeof(ulong), 0);
m->path = path;
proccreate(execfmt, m, Stack);
recvul(m->sync);
chanfree(m->sync);
close(m->fd[1]);
return m->fd[0];
}
static void
copy(int wfd, int rfd)
{
char *buf;
int n;
buf = emalloc(Bufsz);
while(1){
n = read(rfd, buf, Bufsz);
if(n <= 0)
break;
if(write(wfd, buf, n) != n)
break;
}
free(buf);
}
static int
mesgshow(Mesg *m)
{
char *path, *home, *name;
int i, rfd, wfd;
Mesg *a, *b;
if((wfd = winopen(m, "body", OWRITE)) == -1)
return -1;
fprint(wfd, "From: %s\n", m->fromcolon);
fprint(wfd, "To: %s\n", m->to);
fprint(wfd, "Date: %s\n", m->date);
fprint(wfd, "Subject: %s\n\n", m->subject);
b = readparts(m);
path = estrjoin(mbox.path, b->name, "body", nil);
if(strcmp(b->type, "text/html") == 0)
rfd = htmlfmt(m, path);
else
rfd = open(path, OREAD);
free(path);
if(rfd != -1){
copy(wfd, rfd);
close(rfd);
}
home = getenv("home");
if(m->nattachments != 0)
fprint(wfd, "\n\n");
for(i = 0; i < m->nattachments; i++){
a = m->attachments[i];
if(a == b)
continue;
name = a->name;
if(strncmp(a->name, m->name, strlen(m->name)) == 0)
name += strlen(m->name);
fprint(wfd, "===> %s (%s)\n", name, a->type);
if(a->disposition != nil && strcmp(a->disposition, "inline") == 0){
path = estrjoin(mbox.path, b->name, "body", nil);
if((rfd = open(path, OREAD)) != -1){
copy(wfd, rfd);
close(rfd);
}
}else{
name = a->filename;
if(name == nil)
name = "body";
fprint(wfd, "\tcp %s%sbody %s/%s\n", mbox.path, a->name, home, name);
continue;
}
}
close(wfd);
free(home);
return 0;
}
static void
reply(Mesg *m, char **f, int nf)
{
if(nf >= 2
|| nf >= 1 && strcmp(f[0], "All") != 0){
fprint(2, "Q: invaid args\n");
return;
}
compose(m->replyto, m, 0, nf >= 1);
}
static void
qreply(Mesg *m, char **f, int nf)
{
if(nf >= 3
|| nf >= 2 && strcmp(f[1], "All") != 0
|| nf >= 1 && strcmp(f[0], "Reply") != 0
|| nf == 0){
fprint(2, "Q: invaid args\n");
return;
}
compose(m->replyto, m, 1, nf >= 2);
}
static void
delmesg(Mesg *m, char **, int nf)
{
if(nf != 0){
fprint(2, "Delmesg: too many args\n");
return;
}
m->flags |= Ftodel;
m->quitting = 1;
mbredraw(m, 0, 0);
}
static void
mesgquit(Mesg *m, char **, int)
{
m->quitting = 1;
}
static Fn mesgfn[] = {
{"Q", qreply},
{"Reply", reply},
{"Delmesg", delmesg},
{"Save", nil},
{"Del", mesgquit},
{nil}
};
static void
mesgmain(void *mp)
{
char *a, *path, *f[32];
Event ev;
Mesg *m, **pm;
Fn *p;
int nf;
m = mp;
m->quitting = 0;
m->qnext = mbox.openmesg;
mbox.openmesg = m;
path = estrjoin(mbox.path, m->name, nil);
wininit(m, path);
free(path);
wintagwrite(m, "Q Reply all Delmesg Save ");
mesgshow(m);
fprint(m->ctl, "clean\n");
mbox.nopen++;
while(!m->quitting){
if(winevent(m, &ev) != 'M')
continue;
if(strcmp(ev.text, "Del") == 0)
break;
switch(ev.type){
case 'l':
case 'L':
if((a = matchaddr(m, &ev)) != nil)
compose(a, nil, 0, 0);
else if(matchmesg(m, ev.text))
mesgopen(ev.text, nil);
else
winreturn(m, &ev);
free(a);
break;
case 'x':
case 'X':
if((nf = tokenize(ev.text, f, nelem(f))) == 0)
continue;//
for(p = mesgfn; p->fn != nil; p++){
if(strcmp(p->name, f[0]) == 0 && p->fn != nil){
p->fn(m, &f[1], nf - 1);
break;
}
}
if(p->fn == nil)
winreturn(m, &ev);
break;
}
}
for(pm = &mbox.openmesg; *pm != nil; pm = &(*pm)->qnext)
if(*pm == m){
*pm = m->qnext;
break;
}
mbox.nopen--;
m->flags &= ~Fopen;
winclose(m);
threadexits(nil);
}
void
mesgpath2name(char *buf, int nbuf, char *name)
{
char *e;
int n;
n = strlen(mbox.path);
if(strncmp(name, mbox.path, n) == 0){
e = strecpy(buf, buf + nbuf - 2, name + n);
e[0] = '/';
e[1] = 0;
}else
strecpy(buf, buf+nbuf, name);
}
int
mesgmatch(Mesg *m, char *name, char *digest)
{
if(m->flags & Fdummy)
return 0;
if(strcmp(m->name, name) == 0)
return digest == nil || strcmp(m->digest, digest) == 0;
return 0;
}
Mesg*
mesglookup(char *name, char *digest)
{
char buf[32];
int i;
mesgpath2name(buf, sizeof(buf), name);
for(i = 0; i < mbox.nmesg; i++)
if(mesgmatch(mbox.mesg[i], buf, digest))
return mbox.mesg[i];
return nil;
}
Mesg*
mesgopen(char *name, char *digest)
{
Mesg *m;
char *path;
int fd;
m = mesglookup(name, digest);
if(m == nil || (m->flags & Fopen))
return nil;
assert(!(m->flags & Fdummy));
m->flags |= Fopen;
if(m->flags & Funseen){
m->flags &= ~Funseen;
path = estrjoin(mbox.path, "/", m->name, "/flags", nil);
if((fd = open(path, OWRITE)) != -1){
fprint(fd, "+s");
close(fd);
}
mbredraw(m, 0, 0);
free(path);
}
threadcreate(mesgmain, m, Stack);
return m;
}