]> git.lizzy.rs Git - plan9front.git/blob - sys/src/cmd/upas/Mail/mesg.c
Mail: fix inverted reply-all condition
[plan9front.git] / sys / src / cmd / upas / Mail / mesg.c
1 #include <u.h>
2 #include <libc.h>
3 #include <bio.h>
4 #include <thread.h>
5 #include <regexp.h>
6
7 #include "mail.h"
8
9 #define Datefmt         "?WWW, ?MMM ?DD hh:mm:ss ?Z YYYY"
10
11 typedef struct Fn       Fn;
12
13 struct Fn {
14         char *name;
15         void (*fn)(Mesg *, char **, int);
16 };
17
18 void
19 mesgclear(Mesg *m)
20 {
21         int i;
22
23         for(i = 0; i < m->nparts; i++)
24                 mesgclear(m->parts[i]);
25         free(m->name);
26         free(m->from);
27         free(m->to);
28         free(m->cc);
29         free(m->replyto);
30         free(m->date);
31         free(m->subject);
32         free(m->type);
33         free(m->disposition);
34         free(m->messageid);
35         free(m->filename);
36         free(m->digest);
37         free(m->mflags);
38         free(m->fromcolon);
39 }
40
41 void
42 mesgfree(Mesg *m)
43 {
44         if(m == nil)
45                 return;
46         mesgclear(m);
47         free(m);
48 }
49
50 static char*
51 line(char *data, char **pp, int z)
52 {
53         char *p, *q;
54
55         for(p=data; *p!='\0' && *p!='\n'; p++)
56                 ;
57         if(*p == '\n')
58                 *pp = p+1;
59         else
60                 *pp = p;
61         if(z && p == data)
62                 return nil;
63         q = emalloc(p-data + 1);
64         memmove(q, data, p-data);
65         return q;
66 }
67
68 static char*
69 fc(Mesg *m, char *s)
70 {
71         char *r;
72
73         if(s != nil && strlen(m->from) != 0){
74                 r = smprint("%s <%s>", s, m->from);
75                 free(s);
76                 return r;
77         }
78         if(m->from != nil)
79                 return estrdup(m->from);
80         if(s != nil)
81                 return s;
82         return estrdup("??");
83 }
84
85 Mesg*
86 mesgload(char *name)
87 {
88         char *info, *p;
89         int ninfo;
90         Mesg *m;
91         Tm tm;
92
93         m = emalloc(sizeof(Mesg));
94         m->name = estrjoin(name, "/", nil);
95         if((info = rslurp(m, "info", &ninfo)) == nil){
96                 free(m->name);
97                 free(m);
98                 return nil;
99         }
100
101         p = info;
102         m->from = line(p, &p, 0);
103         m->to = line(p, &p, 0);
104         m->cc = line(p, &p, 0);
105         m->replyto = line(p, &p, 1);
106         m->date = line(p, &p, 0);
107         m->subject = line(p, &p, 0);
108         m->type = line(p, &p, 1);
109         m->disposition = line(p, &p, 1);
110         m->filename = line(p, &p, 1);
111         m->digest = line(p, &p, 1);
112         /* m->bcc = */ free(line(p, &p, 1));
113         m->inreplyto = line(p, &p, 1);
114         /* m->date = */ free(line(p, &p, 1));
115         /* m->sender = */ free(line(p, &p, 1));
116         m->messageid = line(p, &p, 0);
117         /* m->lines = */ free(line(p, &p, 1));
118         /* m->size = */ free(line(p, &p, 1));
119         m->mflags = line(p, &p, 0);
120         /* m->fileid = */ free(line(p, &p, 1));
121         m->fromcolon = fc(m, line(p, &p, 1));
122         free(info);
123
124         m->flags = 0;
125         if(strchr(m->mflags, 'd')) m->flags |= Fdel;
126         if(strchr(m->mflags, 's')) m->flags |= Fseen;
127         if(strchr(m->mflags, 'a')) m->flags |= Fresp;
128
129         m->time = time(nil);
130         if(tmparse(&tm, Datefmt, m->date, nil, nil) != nil)
131                 m->time = tmnorm(&tm);
132         m->hash = 0;
133         if(m->messageid != nil)
134                 m->hash = strhash(m->messageid);
135         return m;
136 }
137
138 static Mesg*
139 readparts(Mesg *m)
140 {
141         char *dpath, *apath;
142         int n, i, dfd;
143         Mesg *a, *sub;
144         Dir *d;
145
146         if(m->body != nil)
147                 return m->body;
148
149         dpath = estrjoin(mbox.path, m->name, nil);
150         dfd = open(dpath, OREAD);
151         free(dpath);
152         if(dfd == -1)
153                 return m;
154
155         n = dirreadall(dfd, &d);
156         close(dfd);
157         if(n == -1)
158                 sysfatal("%s read: %r", mbox.path);
159
160         m->body = nil;
161         for(i = 0; i < n; i++){
162                 if(d[i].qid.type != QTDIR)
163                         continue;
164
165                 apath = estrjoin(m->name, d[i].name, nil);
166                 a = mesgload(apath);
167                 free(apath);
168                 if(a == nil)
169                         continue;
170                 if(strncmp(a->type, "multipart/", strlen("multipart/")) == 0){
171                         sub = readparts(a);
172                         if(sub != a)
173                                 m->body = sub;
174                         continue;
175                 } 
176                 if(m->nparts >= m->xparts)
177                         m->parts = erealloc(m->parts, (2 + m->nparts*2)*sizeof(Mesg*));
178                 m->parts[m->nparts++] = a;
179                 if(m->body == nil && strcmp(a->type, "text/plain") == 0)
180                         m->body = a;
181                 else if(m->body == nil && strcmp(a->type, "text/html") == 0)
182                         m->body = a;
183         }
184         free(d);
185         if(m->body == nil)
186                 m->body = m;
187         return m->body;
188 }
189
190 static void
191 execfmt(void *pm)
192 {
193         Mesg *m;
194
195         m = pm;
196         rfork(RFFDG);
197         dup(m->fd[1], 1);
198         close(m->fd[0]);
199         close(m->fd[1]);
200         procexecl(m->sync, "/bin/htmlfmt", "htmlfmt", "-a", "-cutf-8", m->path, nil);
201 }
202
203 static int
204 htmlfmt(Mesg *m, char *path)
205 {
206         if(pipe(m->fd) == -1)
207                 sysfatal("pipe: %r");
208         m->sync = chancreate(sizeof(ulong), 0);
209         m->path = path;
210         procrfork(execfmt, m, Stack, RFNOTEG);
211         recvul(m->sync);
212         chanfree(m->sync);
213         close(m->fd[1]);
214         return m->fd[0];
215 }
216
217 static void
218 copy(Biobuf *wfd, Biobuf *rfd)
219 {
220         char *buf;
221         int n;
222
223         buf = emalloc(Bufsz);
224         while(1){
225                 n = Bread(rfd, buf, Bufsz);
226                 if(n <= 0)
227                         break;
228                 if(Bwrite(wfd, buf, n) != n)
229                         break;
230         }
231         free(buf);
232 }
233
234 static int
235 mesgshow(Mesg *m)
236 {
237         char *path, *home, *name, *suff;
238         Biobuf *rfd, *wfd;
239         Mesg *a;
240         int i;
241
242         if((wfd = bwinopen(m, "body", OWRITE)) == nil)
243                 return -1;
244         if(m->parent != nil || m->nchild != 0) {
245                 Bprint(wfd, "Thread:");
246                 if(m->parent && !(m->parent->state & Sdummy))
247                         Bprint(wfd, " â†‘ %s", m->parent->name);
248                 for(i = 0; i < m->nchild; i++)
249                         Bprint(wfd, " â†“ %s", m->child[i]->name);
250                 Bprint(wfd, "\n");
251         }
252         Bprint(wfd, "From: %s\n", m->fromcolon);
253         Bprint(wfd, "To:   %s\n", m->to);
254         Bprint(wfd, "Date: %s\n", m->date);
255         Bprint(wfd, "Subject: %s\n\n", m->subject);
256
257         rfd = mesgopenbody(m);
258         if(rfd != nil){
259                 copy(wfd, rfd);
260                 Bterm(rfd);
261         }
262
263         home = getenv("home");
264         if(m->nparts != 0)
265                 Bprint(wfd, "\n");
266         for(i = 0; i < m->nparts; i++){
267                 a = m->parts[i];
268                 name = a->name;
269                 if(strncmp(a->name, m->name, strlen(m->name)) == 0)
270                         name += strlen(m->name);
271                 if(a->disposition != nil
272                 && strcmp(a->disposition, "inline") == 0
273                 && strcmp(a->type, "text/plain") == 0){
274                         if(a == m || a == m->body)
275                                 continue;
276                         Bprint(wfd, "\n===> %s (%s)\n", name, a->type);
277                         path = estrjoin(mbox.path, a->name, "body", nil);
278                         if((rfd = Bopen(path, OREAD)) != nil){
279                                 copy(wfd, rfd);
280                                 Bterm(rfd);
281                         }
282                         free(path);
283                         continue;
284                 }
285                 Bprint(wfd, "\n===> %s (%s)\n", name, a->type);
286                 name = a->filename;
287                 if(name == nil)
288                         name = "body";
289                 if((suff = strchr(name, '.')) == nil)
290                         suff = "";
291                 Bprint(wfd, "\tcp %s%sbody%s %s/%s\n", mbox.path, a->name, suff, home, name);
292                 continue;
293         }
294         Bterm(wfd);
295         free(home);
296         fprint(m->ctl, "clean\n");
297         return 0;
298 }
299
300 static void
301 reply(Mesg *m, char **f, int nf)
302 {
303         if(nf >= 1 &&  strcmp(f[0], "all") == 0)
304                 compose(m->replyto, m, 1);
305         else
306                 compose(m->replyto, m, 0);
307 }
308
309 static void
310 delmesg(Mesg *m, char **, int nf)
311 {
312         if(nf != 0){
313                 fprint(2, "Delmesg: too many args\n");
314                 return;
315         }
316         m->flags |= Ftodel;
317         m->quitting = 1;
318         mbredraw(m, 0, 0);
319 }
320
321 static void
322 markone(Mesg *m, char **f, int nf)
323 {
324         int add, flg, fd;
325         char *path;
326
327         if(nf != 1){
328                 fprint(2, "Mark: invalid arguments");
329                 return;
330         }
331
332         if((flg = mesgflagparse(f[0], &add)) == -1){
333                 fprint(2, "Mark: invalid flags %s\n", f[0]);
334                 return;
335         }
336         if(add)
337                 m->flags |= flg;
338         else
339                 m->flags &= ~flg;
340         if(strlen(f[0]) != 0){
341                 path = estrjoin(mbox.path, "/", m->name, "/flags", nil);
342                 if((fd = open(path, OWRITE)) != -1){
343                         fprint(fd, f[0]);
344                         close(fd);
345                 }
346                 free(path);
347         }
348         mbredraw(m, 0, 0);
349 }
350
351
352 static void
353 mesgquit(Mesg *m, char **, int)
354 {
355         if(fprint(m->ctl, "del\n") == -1)
356                 return;
357         m->quitting = 1;
358         m->open = 0;
359 }
360
361 static Fn mesgfn[] = {
362         {"Reply",       reply},
363         {"Delmesg",     delmesg},
364         {"Del",         mesgquit},
365         {"Mark",        markone},
366 #ifdef NOTYET
367         {"Save",        nil},
368 #endif
369         {nil}
370 };
371
372 static void
373 mesgmain(void *mp)
374 {
375         char *path, *f[32];
376         Event ev;
377         Mesg *m, **pm;
378         Fn *p;
379         int nf;
380
381         m = mp;
382         m->quitting = 0;
383         m->qnext = mbox.openmesg;
384         mbox.openmesg = m;
385
386         path = estrjoin(mbox.path, m->name, nil);
387         wininit(m, path);
388         free(path);
389
390         wintagwrite(m, "Reply all Delmesg Save  ");
391         mesgshow(m);
392         fprint(m->ctl, "clean\n");
393         mbox.nopen++;
394         while(!m->quitting){
395                 if(winevent(m, &ev) != 'M')
396                         continue;
397                 if(strcmp(ev.text, "Del") == 0)
398                         break;
399                 switch(ev.type){
400                 case 'l':
401                 case 'L':
402                         if(matchmesg(m, ev.text))
403                                 mesgopen(ev.text, nil);
404                         else
405                                 winreturn(m, &ev);
406                         break;
407                 case 'x':
408                 case 'X':
409                         if((nf = tokenize(ev.text, f, nelem(f))) == 0)
410                                 continue;
411                         for(p = mesgfn; p->fn != nil; p++){
412                                 if(strcmp(p->name, f[0]) == 0 && p->fn != nil){
413                                         p->fn(m, &f[1], nf - 1);
414                                         break;
415                                 }
416                         }
417                         if(p->fn == nil)
418                                 winreturn(m, &ev);
419                         break;
420                 }
421         }
422         for(pm = &mbox.openmesg; *pm != nil; pm = &(*pm)->qnext)
423                 if(*pm == m){
424                         *pm = m->qnext;
425                         break;
426                 }
427         mbox.nopen--;
428         m->qnext = nil;
429         m->state &= ~Sopen;
430         winclose(m);
431         threadexits(nil);
432 }
433
434 int
435 mesgflagparse(char *fstr, int *add)
436 {
437         int flg;
438
439         flg = 0;
440         *add = (*fstr == '+');
441         if(*fstr == '-' || *fstr == '+')
442                 fstr++;
443         for(; *fstr; fstr++){
444                 switch(*fstr){
445                 case 'a':
446                         flg |= Fresp;
447                         break;
448                 case 's':
449                         flg |= Fseen;
450                         break;
451                 case 'D':
452                         flg |= Ftodel;
453                         memcpy(fstr, fstr +1, strlen(fstr));
454                         break;
455                 default:
456                         fprint(2, "unknown flag %c", *fstr);
457                         return -1;
458                 }
459         }
460         return flg;
461 }
462
463 void
464 mesgpath2name(char *buf, int nbuf, char *name)
465 {
466         char *p, *e;
467         int n;
468
469         n = strlen(mbox.path);
470         if(strncmp(name, mbox.path, n) == 0)
471                 e = strecpy(buf, buf+nbuf-2, name + n);
472         else
473                 e = strecpy(buf, buf+nbuf-2, name);
474         if((p = strchr(buf, '/')) == nil)
475                 p = e;
476         p[0] = '/';
477         p[1] = 0;
478 }
479
480 int
481 mesgmatch(Mesg *m, char *name, char *digest)
482 {
483         if(!(m->state & Sdummy) && strcmp(m->name, name) == 0)
484                 return digest == nil || strcmp(m->digest, digest) == 0;
485         return 0;
486 }
487
488 Mesg*
489 mesglookup(char *name, char *digest)
490 {
491         char buf[32];
492         int i;
493
494         mesgpath2name(buf, sizeof(buf), name);
495         for(i = 0; i < mbox.nmesg; i++)
496                 if(mesgmatch(mbox.mesg[i], buf, digest))
497                         return mbox.mesg[i];
498         return nil;
499 }
500
501 Mesg*
502 mesgopen(char *name, char *digest)
503 {
504         Mesg *m;
505         char *path;
506         int fd;
507
508         m = mesglookup(name, digest);
509         if(m == nil || (m->state & Sopen))
510                 return nil;
511
512         assert(!(m->state & Sdummy));
513         m->state |= Sopen;
514         if(!(m->flags & Fseen)){
515                 m->flags |= Fseen;
516                 path = estrjoin(mbox.path, "/", m->name, "/flags", nil);
517                 if((fd = open(path, OWRITE)) != -1){
518                         fprint(fd, "+s");
519                         close(fd);
520                 }
521                 mbredraw(m, 0, 0);
522                 free(path);
523         }
524         threadcreate(mesgmain, m, Stack);
525         return m;
526 }
527
528 Biobuf*
529 mesgopenbody(Mesg *m)
530 {
531         char *path;
532         int rfd;
533         Mesg *b;
534
535         b = readparts(m);
536         path = estrjoin(mbox.path, b->name, "body", nil);
537         if(strcmp(b->type, "text/html") == 0)
538                 rfd = htmlfmt(m, path);
539         else
540                 rfd = open(path, OREAD);
541         free(path);
542         if(rfd == -1)
543                 return nil;
544         return Bfdopen(rfd, OREAD);
545 }