]> git.lizzy.rs Git - plan9front.git/blob - acme/mail/src/mesg.c
merge
[plan9front.git] / acme / mail / src / mesg.c
1 #include <u.h>
2 #include <libc.h>
3 #include <bio.h>
4 #include <thread.h>
5 #include <ctype.h>
6 #include <plumb.h>
7 #include "dat.h"
8
9 enum
10 {
11         DIRCHUNK = 32*sizeof(Dir)
12 };
13
14 char    regexchars[] = "\\/[].+?()*^$";
15 char    deleted[] = "(deleted)-";
16 char    deletedrx[] = "\\(deleted\\)-";
17 char    deletedrx01[] = "(\\(deleted\\)-)?";
18 char    deletedaddr[] = "-#0;/^\\(deleted\\)-/";
19
20 struct{
21         char    *type;
22         char    *port;
23         char *suffix;
24 } ports[] = {
25         "text/",                        "edit",         ".txt",
26         /* text must be first for plumbport() */
27         "image/gif",                    "image",        ".gif",
28         "image/jpeg",                   "image",        ".jpg",
29         "image/jpeg",                   "image",        ".jpeg",
30         "image/png",                    "image",        ".png",
31         "image/tiff",                   "image",        ".tif",
32         "application/postscript",       "postscript",   ".ps",
33         "application/pdf",              "postscript",   ".pdf",
34         "application/msword",           "msword",       ".doc",
35         "application/rtf",              "msword",       ".rtf",
36         "audio/x-wav",                  "wav",          ".wav",
37         nil,    nil
38 };
39
40 char *goodtypes[] = {
41         "text",
42         "text/plain",
43         "message/rfc822",
44         "text/richtext",
45         "text/tab-separated-values",
46         "application/octet-stream",
47         nil,
48 };
49
50 struct{
51         char *type;
52         char    *ext;
53 } exts[] = {
54         "image/gif",    ".gif",
55         "image/jpeg",   ".jpg",
56         nil, nil
57 };
58
59 char *okheaders[] =
60 {
61         "From:",
62         "Date:",
63         "To:",
64         "CC:",
65         "Subject:",
66         nil
67 };
68
69 char *extraheaders[] =
70 {
71         "Resent-From:",
72         "Resent-To:",
73         "Sort:",
74         nil,
75 };
76
77 char*
78 line(char *data, char **pp)
79 {
80         char *p, *q;
81
82         for(p=data; *p!='\0' && *p!='\n'; p++)
83                 ;
84         if(*p == '\n')
85                 *pp = p+1;
86         else
87                 *pp = p;
88         q = emalloc(p-data + 1);
89         memmove(q, data, p-data);
90         return q;
91 }
92
93 void
94 scanheaders(Message *m, char *dir)
95 {
96         char *s, *t, *u, *f;
97
98         s = f = readfile(dir, "header", nil);
99         if(s != nil)
100                 while(*s){
101                         t = line(s, &s);
102                         if(strncmp(t, "From: ", 6) == 0){
103                                 m->fromcolon = estrdup(t+6);
104                                 /* remove all quotes; they're ugly and irregular */
105                                 for(u=m->fromcolon; *u; u++)
106                                         if(*u == '"')
107                                                 memmove(u, u+1, strlen(u));
108                         }
109                         if(strncmp(t, "Subject: ", 9) == 0)
110                                 m->subject = estrdup(t+9);
111                         free(t);
112                 }
113         if(m->fromcolon == nil)
114                 m->fromcolon = estrdup(m->from);
115         free(f);
116 }
117
118 int
119 loadinfo(Message *m, char *dir)
120 {
121         int n;
122         char *data, *p, *s;
123
124         data = readfile(dir, "info", &n);
125         if(data == nil)
126                 return 0;
127         m->from = line(data, &p);
128         scanheaders(m, dir);    /* depends on m->from being set */
129         m->to = line(p, &p);
130         m->cc = line(p, &p);
131         m->replyto = line(p, &p);
132         m->date = line(p, &p);
133         s = line(p, &p);
134         if(m->subject == nil)
135                 m->subject = s;
136         else
137                 free(s);
138         m->type = line(p, &p);
139         m->disposition = line(p, &p);
140         m->filename = line(p, &p);
141         m->digest = line(p, &p);
142         free(data);
143         return 1;
144 }
145
146 int
147 isnumeric(char *s)
148 {
149         while(*s){
150                 if(!isdigit(*s))
151                         return 0;
152                 s++;
153         }
154         return 1;
155 }
156
157 Dir*
158 loaddir(char *name, int *np)
159 {
160         int fd;
161         Dir *dp;
162
163         fd = open(name, OREAD);
164         if(fd < 0)
165                 return nil;
166         *np = dirreadall(fd, &dp);
167         close(fd);
168         return dp;
169 }
170
171 void
172 readmbox(Message *mbox, char *dir, char *subdir)
173 {
174         char *name;
175         Dir *d, *dirp;
176         int i, n;
177
178         name = estrstrdup(dir, subdir);
179         dirp = loaddir(name, &n);
180         mbox->recursed = 1;
181         if(dirp)
182                 for(i=0; i<n; i++){
183                         d = &dirp[i];
184                         if(isnumeric(d->name))
185                                 mesgadd(mbox, name, d, nil);
186                 }
187         free(dirp);
188         free(name);
189 }
190
191 /* add message to box, in increasing numerical order */
192 int
193 mesgadd(Message *mbox, char *dir, Dir *d, char *digest)
194 {
195         Message *m;
196         char *name;
197         int loaded;
198
199         m = emalloc(sizeof(Message));
200         m->name = estrstrdup(d->name, "/");
201         m->next = nil;
202         m->prev = mbox->tail;
203         m->level= mbox->level+1;
204         m->recursed = 0;
205         name = estrstrdup(dir, m->name);
206         loaded = loadinfo(m, name);
207         free(name);
208         /* if two upas/fs are running, we can get misled, so check digest before accepting message */
209         if(loaded==0 || (digest!=nil && m->digest!=nil && strcmp(digest, m->digest)!=0)){
210                 mesgfreeparts(m);
211                 free(m);
212                 return 0;
213         }
214         if(mbox->tail != nil)
215                 mbox->tail->next = m;
216         mbox->tail = m;
217         if(mbox->head == nil)
218                 mbox->head = m;
219
220         if (m->level != 1){
221                 m->recursed = 1;
222                 readmbox(m, dir, m->name); 
223         }
224         return 1;
225 }
226
227 int
228 thisyear(char *year)
229 {
230         static char now[10];
231         char *s;
232
233         if(now[0] == '\0'){
234                 s = ctime(time(nil));
235                 strcpy(now, s+24);
236         }
237         return strncmp(year, now, 4) == 0;
238 }
239
240 char*
241 stripdate(char *as)
242 {
243         int n;
244         char *s, *fld[10];
245
246         as = estrdup(as);
247         s = estrdup(as);
248         n = tokenize(s, fld, 10);
249         if(n > 5){
250                 sprint(as, "%.3s ", fld[0]);    /* day */
251                 /* some dates have 19 Apr, some Apr 19 */
252                 if(strlen(fld[1])<4 && isnumeric(fld[1]))
253                         sprint(as+strlen(as), "%.3s %.3s ", fld[1], fld[2]);    /* date, month */
254                 else
255                         sprint(as+strlen(as), "%.3s %.3s ", fld[2], fld[1]);    /* date, month */
256                 /* do we use time or year?  depends on whether year matches this one */
257                 if(thisyear(fld[5])){
258                         if(strchr(fld[3], ':') != nil)
259                                 sprint(as+strlen(as), "%.5s ", fld[3]); /* time */
260                         else if(strchr(fld[4], ':') != nil)
261                                 sprint(as+strlen(as), "%.5s ", fld[4]); /* time */
262                 }else
263                         sprint(as+strlen(as), "%.4s ", fld[5]); /* year */
264         }
265         free(s);
266         return as;
267 }
268
269 char*
270 readfile(char *dir, char *name, int *np)
271 {
272         char *file, *data;
273         int fd, len;
274         Dir *d;
275
276         if(np != nil)
277                 *np = 0;
278         file = estrstrdup(dir, name);
279         fd = open(file, OREAD);
280         if(fd < 0)
281                 return nil;
282         d = dirfstat(fd);
283         free(file);
284         len = 0;
285         if(d != nil)
286                 len = d->length;
287         free(d);
288         data = emalloc(len+1);
289         read(fd, data, len);
290         close(fd);
291         if(np != nil)
292                 *np = len;
293         return data;
294 }
295
296 char*
297 info(Message *m, int ind, int ogf)
298 {
299         char *i;
300         int j, len, lens;
301         char *p;
302         char fmt[80], s[80];
303
304         if (ogf)
305                 p=m->to;
306         else
307                 p=m->fromcolon;
308
309         if(ind==0 && shortmenu){
310                 len = 30;
311                 lens = 30;
312                 if(shortmenu > 1){
313                         len = 10;
314                         lens = 25;
315                 }
316                 if(ind==0 && m->subject[0]=='\0'){
317                         snprint(fmt, sizeof fmt, " %%-%d.%ds", len, len);
318                         snprint(s, sizeof s, fmt, p);
319                 }else{
320                         snprint(fmt, sizeof fmt, " %%-%d.%ds  %%-%d.%ds", len, len, lens, lens);
321                         snprint(s, sizeof s, fmt, p, m->subject);
322                 }
323                 i = estrdup(s);
324
325                 return i;
326         } 
327
328         i = estrdup("");
329         i = eappend(i, "\t", p);
330         i = egrow(i, "\t", stripdate(m->date));
331         if(ind == 0){
332                 if(strcmp(m->type, "text")!=0 && strncmp(m->type, "text/", 5)!=0 && 
333                    strncmp(m->type, "multipart/", 10)!=0)
334                         i = egrow(i, "\t(", estrstrdup(m->type, ")"));
335         }else if(strncmp(m->type, "multipart/", 10) != 0)
336                 i = egrow(i, "\t(", estrstrdup(m->type, ")"));
337         if(m->subject[0] != '\0'){
338                 i = eappend(i, "\n", nil);
339                 for(j=0; j<ind; j++)
340                         i = eappend(i, "\t", nil);
341                 i = eappend(i, "\t", m->subject);
342         }
343         return i;
344 }
345
346 void
347 mesgmenu0(Window *w, Message *mbox, char *realdir, char *dir, int ind, Biobuf *fd, int onlyone, int dotail)
348 {
349         int i;
350         Message *m;
351         char *name, *tmp;
352         int ogf=0;
353
354         if(strstr(realdir, "outgoing") != nil)
355                 ogf=1;
356
357         /* show mail box in reverse order, pieces in forward order */
358         if(ind > 0)
359                 m = mbox->head;
360         else
361                 m = mbox->tail;
362         while(m != nil){
363                 for(i=0; i<ind; i++)
364                         Bprint(fd, "\t");
365                 if(ind != 0)
366                         Bprint(fd, "  ");
367                 name = estrstrdup(dir, m->name);
368                 tmp = info(m, ind, ogf);
369                 Bprint(fd, "%s%s\n", name, tmp);
370                 free(tmp);
371                 if(dotail && m->tail)
372                         mesgmenu0(w, m, realdir, name, ind+1, fd, 0, dotail);
373                 free(name);
374                 if(ind)
375                         m = m->next;
376                 else
377                         m = m->prev;
378                 if(onlyone)
379                         m = nil;
380         }
381 }
382
383 void
384 mesgmenu(Window *w, Message *mbox)
385 {
386         winopenbody(w, OWRITE);
387         mesgmenu0(w, mbox, mbox->name, "", 0, w->body, 0, !shortmenu);
388         winclosebody(w);
389 }
390
391 /* one new message has arrived, as mbox->tail */
392 void
393 mesgmenunew(Window *w, Message *mbox)
394 {
395         Biobuf *b;
396
397         winselect(w, "0", 0);
398         w->data = winopenfile(w, "data");
399         b = emalloc(sizeof(Biobuf));
400         Binit(b, w->data, OWRITE);
401         mesgmenu0(w, mbox, mbox->name, "", 0, b, 1, !shortmenu);
402         Bterm(b);
403         free(b);
404         if(!mbox->dirty)
405                 winclean(w);
406         /* select tag line plus following indented lines, but not final newline (it's distinctive) */
407         winselect(w, "0/.*\\n((\t.*\\n)*\t.*)?/", 1);
408         close(w->addr);
409         close(w->data);
410         w->addr = -1;
411         w->data = -1;
412 }
413
414 char*
415 name2regexp(char *prefix, char *s)
416 {
417         char *buf, *p, *q;
418
419         buf = emalloc(strlen(prefix)+2*strlen(s)+50);   /* leave room to append more */
420         p = buf;
421         *p++ = '0';
422         *p++ = '/';
423         *p++ = '^';
424         strcpy(p, prefix);
425         p += strlen(prefix);
426         for(q=s; *q!='\0'; q++){
427                 if(strchr(regexchars, *q) != nil)
428                         *p++ = '\\';
429                 *p++ = *q;
430         }
431         *p++ = '/';
432         *p = '\0';
433         return buf;
434 }
435
436 void
437 mesgmenumarkdel(Window *w, Message *mbox, Message *m, int writeback)
438 {
439         char *buf;
440
441
442         if(m->deleted)
443                 return;
444         m->writebackdel = writeback;
445         if(w->data < 0)
446                 w->data = winopenfile(w, "data");
447         buf = name2regexp("", m->name);
448         strcat(buf, "-#0");
449         if(winselect(w, buf, 1))
450                 write(w->data, deleted, 10);
451         free(buf);
452         close(w->data);
453         close(w->addr);
454         w->addr = w->data = -1;
455         mbox->dirty = 1;
456         m->deleted = 1;
457 }
458
459 void
460 mesgmenumarkundel(Window *w, Message*, Message *m)
461 {
462         char *buf;
463
464         if(m->deleted == 0)
465                 return;
466         if(w->data < 0)
467                 w->data = winopenfile(w, "data");
468         buf = name2regexp(deletedrx, m->name);
469         if(winselect(w, buf, 1))
470                 if(winsetaddr(w, deletedaddr, 1))
471                         write(w->data, "", 0);
472         free(buf);
473         close(w->data);
474         close(w->addr);
475         w->addr = w->data = -1;
476         m->deleted = 0;
477 }
478
479 void
480 mesgmenudel(Window *w, Message *mbox, Message *m)
481 {
482         char *buf;
483
484         if(w->data < 0)
485                 w->data = winopenfile(w, "data");
486         buf = name2regexp(deletedrx, m->name);
487         if(winsetaddr(w, buf, 1) && winsetaddr(w, ".,./.*\\n(\t.*\\n)*/", 1))
488                 write(w->data, "", 0);
489         free(buf);
490         close(w->data);
491         close(w->addr);
492         w->addr = w->data = -1;
493         mbox->dirty = 1;
494         m->deleted = 1;
495 }
496
497 void
498 mesgmenumark(Window *w, char *which, char *mark)
499 {
500         char *buf;
501
502         if(w->data < 0)
503                 w->data = winopenfile(w, "data");
504         buf = name2regexp(deletedrx01, which);
505         if(winsetaddr(w, buf, 1) && winsetaddr(w, "+0-#1", 1))  /* go to end of line */
506                 write(w->data, mark, strlen(mark));
507         free(buf);
508         close(w->data);
509         close(w->addr);
510         w->addr = w->data = -1;
511         if(!mbox.dirty)
512                 winclean(w);
513 }
514
515 void
516 mesgfreeparts(Message *m)
517 {
518         free(m->name);
519         free(m->replyname);
520         free(m->fromcolon);
521         free(m->from);
522         free(m->to);
523         free(m->cc);
524         free(m->replyto);
525         free(m->date);
526         free(m->subject);
527         free(m->type);
528         free(m->disposition);
529         free(m->filename);
530         free(m->digest);
531 }
532
533 void
534 mesgdel(Message *mbox, Message *m)
535 {
536         Message *n, *next;
537
538         if(m->opened)
539                 error("internal error: deleted message still open in mesgdel");
540         /* delete subparts */
541         for(n=m->head; n!=nil; n=next){
542                 next = n->next;
543                 mesgdel(m, n);
544         }
545         /* remove this message from list */
546         if(m->next)
547                 m->next->prev = m->prev;
548         else
549                 mbox->tail = m->prev;
550         if(m->prev)
551                 m->prev->next = m->next;
552         else
553                 mbox->head = m->next;
554
555         mesgfreeparts(m);
556 }
557
558 int
559 mesgsave(Message *m, char *s)
560 {
561         int ofd, n, k, ret;
562         char *t, *raw, *unixheader, *all;
563
564         t = estrstrdup(mbox.name, m->name);
565         raw = readfile(t, "raw", &n);
566         unixheader = readfile(t, "unixheader", &k);
567         if(raw==nil || unixheader==nil){
568                 fprint(2, "Mail: can't read %s: %r\n", t);
569                 free(t);
570                 return 0;
571         }
572         free(t);
573
574         all = emalloc(n+k+1);
575         memmove(all, unixheader, k);
576         memmove(all+k, raw, n);
577         memmove(all+k+n, "\n", 1);
578         n = k+n+1;
579         free(unixheader);
580         free(raw);
581         ret = 1;
582         s = estrdup(s);
583         if(s[0] != '/')
584                 s = egrow(estrdup(mailboxdir), "/", s);
585         ofd = open(s, OWRITE);
586         if(ofd < 0){
587                 fprint(2, "Mail: can't open %s: %r\n", s);
588                 ret = 0;
589         }else if(seek(ofd, 0LL, 2)<0 || write(ofd, all, n)!=n){
590                 fprint(2, "Mail: save failed: can't write %s: %r\n", s);
591                 ret = 0;
592         }
593         free(all);
594         close(ofd);
595         free(s);
596         return ret;
597 }
598
599 int
600 mesgcommand(Message *m, char *cmd)
601 {
602         char *s;
603         char *args[10];
604         int ok, ret, nargs;
605
606         s = cmd;
607         ret = 1;
608         nargs = tokenize(s, args, nelem(args));
609         if(nargs == 0)
610                 return 0;
611         if(strcmp(args[0], "Post") == 0){
612                 mesgsend(m);
613                 goto Return;
614         }
615         if(strncmp(args[0], "Save", 4) == 0){
616                 if(m->isreply)
617                         goto Return;
618                 s = estrdup("\t[saved");
619                 if(nargs==1 || strcmp(args[1], "")==0){
620                         ok = mesgsave(m, "stored");
621                 }else{
622                         ok = mesgsave(m, args[1]);
623                         s = eappend(s, " ", args[1]);
624                 }
625                 if(ok){
626                         s = egrow(s, "]", nil);
627                         mesgmenumark(mbox.w, m->name, s);
628                 }
629                 free(s);
630                 goto Return;
631         }
632         if(strcmp(args[0], "Reply")==0){
633                 if(nargs>=2 && strcmp(args[1], "all")==0)
634                         mkreply(m, "Replyall", nil, nil, nil);
635                 else
636                         mkreply(m, "Reply", nil, nil, nil);
637                 goto Return;
638         }
639         if(strcmp(args[0], "Q") == 0){
640                 s = winselection(m->w); /* will be freed by mkreply */
641                 if(nargs>=3 && strcmp(args[1], "Reply")==0 && strcmp(args[2], "all")==0)
642                         mkreply(m, "QReplyall", nil, nil, s);
643                 else
644                         mkreply(m, "QReply", nil, nil, s);
645                 goto Return;
646         }
647         if(strcmp(args[0], "Del") == 0){
648                 if(windel(m->w, 0)){
649                         chanfree(m->w->cevent);
650                         free(m->w);
651                         m->w = nil;
652                         if(m->isreply)
653                                 delreply(m);
654                         else{
655                                 m->opened = 0;
656                                 m->tagposted = 0;
657                         }
658                         free(cmd);
659                         threadexits(nil);
660                 }
661                 goto Return;
662         }
663         if(strcmp(args[0], "Delmesg") == 0){
664                 if(!m->isreply){
665                         mesgmenumarkdel(wbox, &mbox, m, 1);
666                         free(cmd);      /* mesgcommand might not return */
667                         mesgcommand(m, estrdup("Del"));
668                         return 1;
669                 }
670                 goto Return;
671         }
672         if(strcmp(args[0], "UnDelmesg") == 0){
673                 if(!m->isreply && m->deleted)
674                         mesgmenumarkundel(wbox, &mbox, m);
675                 goto Return;
676         }
677 //      if(strcmp(args[0], "Headers") == 0){
678 //              m->showheaders();
679 //              return True;
680 //      }
681
682         ret = 0;
683
684     Return:
685         free(cmd);
686         return ret;
687 }
688
689 void
690 mesgtagpost(Message *m)
691 {
692         if(m->tagposted)
693                 return;
694         wintagwrite(m->w, " Post", 5);
695         m->tagposted = 1;
696 }
697
698 /* need to expand selection more than default word */
699 #pragma varargck argpos eval 2
700
701 long
702 eval(Window *w, char *s, ...)
703 {
704         char buf[64];
705         va_list arg;
706
707         va_start(arg, s);
708         vsnprint(buf, sizeof buf, s, arg);
709         va_end(arg);
710
711         if(winsetaddr(w, buf, 1)==0)
712                 return -1;
713
714         if(pread(w->addr, buf, 24, 0) != 24)
715                 return -1;
716         return strtol(buf, 0, 10);
717 }
718
719 int
720 isemail(char *s)
721 {
722         int nat;
723
724         nat = 0;
725         for(; *s; s++)
726                 if(*s == '@')
727                         nat++;
728                 else if(!isalpha(*s) && !isdigit(*s) && !strchr("_.-+/", *s))
729                         return 0;
730         return nat==1;
731 }
732
733 char addrdelim[] =  "/[ \t\\n<>()\\[\\]]/";
734 char*
735 expandaddr(Window *w, Event *e)
736 {
737         char *s;
738         long q0, q1;
739
740         if(e->q0 != e->q1)      /* cannot happen */
741                 return nil;
742
743         q0 = eval(w, "#%d-%s", e->q0, addrdelim);
744         if(q0 == -1)    /* bad char not found */
745                 q0 = 0;
746         else                    /* increment past bad char */
747                 q0++;
748
749         q1 = eval(w, "#%d+%s", e->q0, addrdelim);
750         if(q1 < 0){
751                 q1 = eval(w, "$");
752                 if(q1 < 0)
753                         return nil;
754         }
755         if(q0 >= q1)
756                 return nil;
757         s = emalloc((q1-q0)*UTFmax+1);
758         winread(w, q0, q1, s);
759         return s;
760 }
761
762 int
763 replytoaddr(Window *w, Message *m, Event *e, char *s)
764 {
765         int did;
766         char *buf;
767         Plumbmsg *pm;
768
769         buf = nil;
770         did = 0;
771         if(e->flag & 2){
772                 /* autoexpanded; use our own bigger expansion */
773                 buf = expandaddr(w, e);
774                 if(buf == nil)
775                         return 0;
776                 s = buf;
777         }
778         if(isemail(s)){
779                 did = 1;
780                 pm = emalloc(sizeof(Plumbmsg));
781                 pm->src = estrdup("Mail");
782                 pm->dst = estrdup("sendmail");
783                 pm->data = estrdup(s);
784                 pm->ndata = -1;
785                 if(m->subject && m->subject[0]){
786                         pm->attr = emalloc(sizeof(Plumbattr));
787                         pm->attr->name = estrdup("Subject");
788                         if(tolower(m->subject[0]) != 'r' || tolower(m->subject[1]) != 'e' || m->subject[2] != ':')
789                                 pm->attr->value = estrstrdup("Re: ", m->subject);
790                         else
791                                 pm->attr->value = estrdup(m->subject);
792                         pm->attr->next = nil;
793                 }
794                 if(plumbsend(plumbsendfd, pm) < 0)
795                         fprint(2, "error writing plumb message: %r\n");
796                 plumbfree(pm);
797         }
798         free(buf);
799         return did;
800 }
801
802
803 void
804 mesgctl(void *v)
805 {
806         Message *m;
807         Window *w;
808         Event *e, *eq, *e2, *ea;
809         int na, nopen, i, j;
810         char *os, *s, *t, *buf;
811
812         m = v;
813         w = m->w;
814         threadsetname("mesgctl");
815         proccreate(wineventproc, w, STACK);
816         for(;;){
817                 e = recvp(w->cevent);
818                 switch(e->c1){
819                 default:
820                 Unk:
821                         print("unknown message %c%c\n", e->c1, e->c2);
822                         break;
823
824                 case 'E':       /* write to body; can't affect us */
825                         break;
826
827                 case 'F':       /* generated by our actions; ignore */
828                         break;
829
830                 case 'K':       /* type away; we don't care */
831                 case 'M':
832                         switch(e->c2){
833                         case 'x':       /* mouse only */
834                         case 'X':
835                                 ea = nil;
836                                 eq = e;
837                                 if(e->flag & 2){
838                                         e2 = recvp(w->cevent);
839                                         eq = e2;
840                                 }
841                                 if(e->flag & 8){
842                                         ea = recvp(w->cevent);
843                                         recvp(w->cevent);
844                                         na = ea->nb;
845                                 }else
846                                         na = 0;
847                                 if(eq->q1>eq->q0 && eq->nb==0){
848                                         s = emalloc((eq->q1-eq->q0)*UTFmax+1);
849                                         winread(w, eq->q0, eq->q1, s);
850                                 }else
851                                         s = estrdup(eq->b);
852                                 if(na){
853                                         t = emalloc(strlen(s)+1+na+1);
854                                         sprint(t, "%s %s", s, ea->b);
855                                         free(s);
856                                         s = t;
857                                 }
858                                 if(!mesgcommand(m, s))  /* send it back */
859                                         winwriteevent(w, e);
860                                 break;
861
862                         case 'l':       /* mouse only */
863                         case 'L':
864                                 buf = nil;
865                                 eq = e;
866                                 if(e->flag & 2){
867                                         e2 = recvp(w->cevent);
868                                         eq = e2;
869                                 }
870                                 s = eq->b;
871                                 if(eq->q1>eq->q0 && eq->nb==0){
872                                         buf = emalloc((eq->q1-eq->q0)*UTFmax+1);
873                                         winread(w, eq->q0, eq->q1, buf);
874                                         s = buf;
875                                 }
876                                 os = s;
877                                 nopen = 0;
878                                 do{
879                                         /* skip mail box name if present */
880                                         if(strncmp(s, mbox.name, strlen(mbox.name)) == 0)
881                                                 s += strlen(mbox.name);
882                                         if(strstr(s, "body") != nil){
883                                                 /* strip any known extensions */
884                                                 for(i=0; exts[i].ext!=nil; i++){
885                                                         j = strlen(exts[i].ext);
886                                                         if(strlen(s)>j && strcmp(s+strlen(s)-j, exts[i].ext)==0){
887                                                                 s[strlen(s)-j] = '\0';
888                                                                 break;
889                                                         }
890                                                 }
891                                                 if(strlen(s)>5 && strcmp(s+strlen(s)-5, "/body")==0)
892                                                         s[strlen(s)-4] = '\0';  /* leave / in place */
893                                         }
894                                         nopen += mesgopen(&mbox, mbox.name, s, m, 0, nil);
895                                         while(*s!=0 && *s++!='\n')
896                                                 ;
897                                 }while(*s);
898                                 if(nopen == 0 && e->c1 == 'L')
899                                         nopen += replytoaddr(w, m, e, os);
900                                 if(nopen == 0)
901                                         winwriteevent(w, e);
902                                 free(buf);
903                                 break;
904
905                         case 'I':       /* modify away; we don't care */
906                         case 'D':
907                                 mesgtagpost(m);
908                                 /* fall through */
909                         case 'd':
910                         case 'i':
911                                 break;
912
913                         default:
914                                 goto Unk;
915                         }
916                 }
917         }
918 }
919
920 void
921 mesgline(Message *m, char *header, char *value)
922 {
923         if(strlen(value) > 0)
924                 Bprint(m->w->body, "%s: %s\n", header, value);
925 }
926
927 int
928 isprintable(char *type)
929 {
930         int i;
931
932         for(i=0; goodtypes[i]!=nil; i++)
933                 if(strcmp(type, goodtypes[i])==0)
934                         return 1;
935         return 0;
936 }
937
938 char*
939 ext(char *type)
940 {
941         int i;
942
943         for(i=0; exts[i].type!=nil; i++)
944                 if(strcmp(type, exts[i].type)==0)
945                         return exts[i].ext;
946         return "";
947 }
948
949 void
950 mimedisplay(Message *m, char *name, char *rootdir, Window *w, int fileonly)
951 {
952         char *dest;
953
954         if(strcmp(m->disposition, "file")==0 || strlen(m->filename)!=0){
955                 if(strlen(m->filename) == 0){
956                         dest = estrdup(m->name);
957                         dest[strlen(dest)-1] = '\0';
958                 }else
959                         dest = estrdup(m->filename);
960                 if(m->filename[0] != '/')
961                         dest = egrow(estrdup(home), "/", dest);
962                 Bprint(w->body, "\tcp %s%sbody%s %q\n", rootdir, name, ext(m->type), dest);
963                 free(dest);
964         }else if(!fileonly)
965                 Bprint(w->body, "\tfile is %s%sbody%s\n", rootdir, name, ext(m->type));
966 }
967
968 void
969 printheader(char *dir, Biobuf *b, char **okheaders)
970 {
971         char *s;
972         char *lines[100];
973         int i, j, n;
974
975         s = readfile(dir, "header", nil);
976         if(s == nil)
977                 return;
978         n = getfields(s, lines, nelem(lines), 0, "\n");
979         for(i=0; i<n; i++)
980                 for(j=0; okheaders[j]; j++)
981                         if(cistrncmp(lines[i], okheaders[j], strlen(okheaders[j])) == 0)
982                                 Bprint(b, "%s\n", lines[i]);
983         free(s);
984 }
985
986 void
987 mesgload(Message *m, char *rootdir, char *file, Window *w)
988 {
989         char *s, *subdir, *name, *dir;
990         Message *mp, *thisone;
991         int n;
992
993         dir = estrstrdup(rootdir, file);
994
995         if(strcmp(m->type, "message/rfc822") != 0){     /* suppress headers of envelopes */
996                 if(strlen(m->from) > 0){
997                         Bprint(w->body, "From: %s\n", m->from);
998                         mesgline(m, "Date", m->date);
999                         mesgline(m, "To", m->to);
1000                         mesgline(m, "CC", m->cc);
1001                         mesgline(m, "Subject", m->subject);
1002                         printheader(dir, w->body, extraheaders);
1003                 }else{
1004                         printheader(dir, w->body, okheaders);
1005                         printheader(dir, w->body, extraheaders);
1006                 }
1007                 Bprint(w->body, "\n");
1008         }
1009
1010         if(m->level == 1 && m->recursed == 0){
1011                 m->recursed = 1;
1012                 readmbox(m, rootdir, m->name);
1013         }
1014         if(m->head == nil){     /* single part message */
1015                 if(strcmp(m->type, "text")==0 || strncmp(m->type, "text/", 5)==0){
1016                         mimedisplay(m, m->name, rootdir, w, 1);
1017                         s = readbody(m->type, dir, &n);
1018                         winwritebody(w, s, n);
1019                         free(s);
1020                 }else
1021                         mimedisplay(m, m->name, rootdir, w, 0);
1022         }else{
1023                 /* multi-part message, either multipart/* or message/rfc822 */
1024                 thisone = nil;
1025                 if(strcmp(m->type, "multipart/alternative") == 0){
1026                         thisone = m->head;      /* in case we can't find a good one */
1027                         for(mp=m->head; mp!=nil; mp=mp->next)
1028                                 if(isprintable(mp->type)){
1029                                         thisone = mp;
1030                                         break;
1031                                 }
1032                 }
1033                 for(mp=m->head; mp!=nil; mp=mp->next){
1034                         if(thisone!=nil && mp!=thisone)
1035                                 continue;
1036                         subdir = estrstrdup(dir, mp->name);
1037                         name = estrstrdup(file, mp->name);
1038                         /* skip first element in name because it's already in window name */
1039                         if(mp != m->head)
1040                                 Bprint(w->body, "\n===> %s (%s) [%s]\n", strchr(name, '/')+1, mp->type, mp->disposition);
1041                         if(strcmp(mp->type, "text")==0 || strncmp(mp->type, "text/", 5)==0){
1042                                 mimedisplay(mp, name, rootdir, w, 1);
1043                                 printheader(subdir, w->body, okheaders);
1044                                 printheader(subdir, w->body, extraheaders);
1045                                 winwritebody(w, "\n", 1);
1046                                 s = readbody(mp->type, subdir, &n);
1047                                 winwritebody(w, s, n);
1048                                 free(s);
1049                         }else{
1050                                 if(strncmp(mp->type, "multipart/", 10)==0 || strcmp(mp->type, "message/rfc822")==0){
1051                                         mp->w = w;
1052                                         mesgload(mp, rootdir, name, w);
1053                                         mp->w = nil;
1054                                 }else
1055                                         mimedisplay(mp, name, rootdir, w, 0);
1056                         }
1057                         free(name);
1058                         free(subdir);
1059                 }
1060         }
1061         free(dir);
1062 }
1063
1064 int
1065 tokenizec(char *str, char **args, int max, char *splitc)
1066 {
1067         int na;
1068         int intok = 0;
1069
1070         if(max <= 0)
1071                 return 0;       
1072         for(na=0; *str != '\0';str++){
1073                 if(strchr(splitc, *str) == nil){
1074                         if(intok)
1075                                 continue;
1076                         args[na++] = str;
1077                         intok = 1;
1078                 }else{
1079                         /* it's a separator/skip character */
1080                         *str = '\0';
1081                         if(intok){
1082                                 intok = 0;
1083                                 if(na >= max)
1084                                         break;
1085                         }
1086                 }
1087         }
1088         return na;
1089 }
1090
1091 Message*
1092 mesglookup(Message *mbox, char *name, char *digest)
1093 {
1094         int n;
1095         Message *m;
1096         char *t;
1097
1098         if(digest){
1099                 /* can find exactly */
1100                 for(m=mbox->head; m!=nil; m=m->next)
1101                         if(strcmp(digest, m->digest) == 0)
1102                                 break;
1103                 return m;
1104         }
1105
1106         n = strlen(name);
1107         if(n == 0)
1108                 return nil;
1109         if(name[n-1] == '/')
1110                 t = estrdup(name);
1111         else
1112                 t = estrstrdup(name, "/");
1113         for(m=mbox->head; m!=nil; m=m->next)
1114                 if(strcmp(t, m->name) == 0)
1115                         break;
1116         free(t);
1117         return m;
1118 }
1119
1120 /*
1121  * Find plumb port, knowing type is text, given file name (by extension)
1122  */
1123 int
1124 plumbportbysuffix(char *file)
1125 {
1126         char *suf;
1127         int i, nsuf, nfile;
1128
1129         nfile = strlen(file);
1130         for(i=0; ports[i].type!=nil; i++){
1131                 suf = ports[i].suffix;
1132                 nsuf = strlen(suf);
1133                 if(nfile > nsuf)
1134                         if(cistrncmp(file+nfile-nsuf, suf, nsuf) == 0)
1135                                 return i;
1136         }
1137         return 0;
1138 }
1139
1140 /*
1141  * Find plumb port using type and file name (by extension)
1142  */
1143 int
1144 plumbport(char *type, char *file)
1145 {
1146         int i;
1147
1148         for(i=0; ports[i].type!=nil; i++)
1149                 if(strncmp(type, ports[i].type, strlen(ports[i].type)) == 0)
1150                         return i;
1151         /* see if it's a text type */
1152         for(i=0; goodtypes[i]!=nil; i++)
1153                 if(strncmp(type, goodtypes[i], strlen(goodtypes[i])) == 0)
1154                         return plumbportbysuffix(file);
1155         return -1;
1156 }
1157
1158 void
1159 plumb(Message *m, char *dir)
1160 {
1161         int i;
1162         char *port;
1163         Plumbmsg *pm;
1164
1165         if(strlen(m->type) == 0)
1166                 return;
1167         i = plumbport(m->type, m->filename);
1168         if(i < 0)
1169                 fprint(2, "can't find destination for message subpart\n");
1170         else{
1171                 port = ports[i].port;
1172                 pm = emalloc(sizeof(Plumbmsg));
1173                 pm->src = estrdup("Mail");
1174                 if(port)
1175                         pm->dst = estrdup(port);
1176                 else
1177                         pm->dst = nil;
1178                 pm->wdir = nil;
1179                 pm->type = estrdup("text");
1180                 pm->ndata = -1;
1181                 pm->data = estrstrdup(dir, "body");
1182                 pm->data = eappend(pm->data, "", ports[i].suffix);
1183                 if(plumbsend(plumbsendfd, pm) < 0)
1184                         fprint(2, "error writing plumb message: %r\n");
1185                 plumbfree(pm);
1186         }
1187 }
1188
1189 int
1190 mesgopen(Message *mbox, char *dir, char *s, Message *mesg, int plumbed, char *digest)
1191 {
1192         char *t, *u, *v;
1193         Message *m;
1194         char *direlem[10];
1195         int i, ndirelem, reuse;
1196
1197         /* find white-space-delimited first word */
1198         for(t=s; *t!='\0' && !isspace(*t); t++)
1199                 ;
1200         u = emalloc(t-s+1);
1201         memmove(u, s, t-s);
1202         /* separate it on slashes */
1203         ndirelem = tokenizec(u, direlem, nelem(direlem), "/");
1204         if(ndirelem <= 0){
1205     Error:
1206                 free(u);
1207                 return 0;
1208         }
1209         if(plumbed){
1210                 write(wctlfd, "top", 3);
1211                 write(wctlfd, "current", 7);
1212         }
1213         /* open window for message */
1214         m = mesglookup(mbox, direlem[0], digest);
1215         if(m == nil)
1216                 goto Error;
1217         if(mesg!=nil && m!=mesg)        /* string looked like subpart but isn't part of this message */
1218                 goto Error;
1219         if(m->opened == 0){
1220                 if(m->w == nil){
1221                         reuse = 0;
1222                         m->w = newwindow();
1223                 }else{
1224                         reuse = 1;
1225                         /* re-use existing window */
1226                         if(winsetaddr(m->w, "0,$", 1)){
1227                                 if(m->w->data < 0)
1228                                         m->w->data = winopenfile(m->w, "data");
1229                                 write(m->w->data, "", 0);
1230                         }
1231                 }
1232                 v = estrstrdup(mbox->name, m->name);
1233                 winname(m->w, v);
1234                 free(v);
1235                 if(!reuse){
1236                         if(m->deleted)
1237                                 wintagwrite(m->w, "Q Reply all UnDelmesg Save ", 2+6+4+10+5);
1238                         else
1239                                 wintagwrite(m->w, "Q Reply all Delmesg Save ", 2+6+4+8+5);
1240                 }
1241                 threadcreate(mesgctl, m, STACK);
1242                 winopenbody(m->w, OWRITE);
1243                 mesgload(m, dir, m->name, m->w);
1244                 winclosebody(m->w);
1245                 winclean(m->w);
1246                 m->opened = 1;
1247                 if(ndirelem == 1){
1248                         free(u);
1249                         return 1;
1250                 }
1251         }
1252         if(ndirelem == 1 && plumbport(m->type, m->filename) <= 0){
1253                 /* make sure dot is visible */
1254                 ctlprint(m->w->ctl, "show\n");
1255                 return 0;
1256         }
1257         /* walk to subpart */
1258         dir = estrstrdup(dir, m->name);
1259         for(i=1; i<ndirelem; i++){
1260                 m = mesglookup(m, direlem[i], digest);
1261                 if(m == nil)
1262                         break;
1263                 dir = egrow(dir, m->name, nil);
1264         }
1265         if(m != nil && plumbport(m->type, m->filename) > 0)
1266                 plumb(m, dir);
1267         free(dir);
1268         free(u);
1269         return 1;
1270 }
1271
1272 void
1273 rewritembox(Window *w, Message *mbox)
1274 {
1275         Message *m, *next;
1276         char *deletestr, *t;
1277         int nopen;
1278
1279         deletestr = estrstrdup("delete ", fsname);
1280
1281         nopen = 0;
1282         for(m=mbox->head; m!=nil; m=next){
1283                 next = m->next;
1284                 if(m->deleted == 0)
1285                         continue;
1286                 if(m->opened){
1287                         nopen++;
1288                         continue;
1289                 }
1290                 if(m->writebackdel){
1291                         /* messages deleted by plumb message are not removed again */
1292                         t = estrdup(m->name);
1293                         if(strlen(t) > 0)
1294                                 t[strlen(t)-1] = '\0';
1295                         deletestr = egrow(deletestr, " ", t);
1296                 }
1297                 mesgmenudel(w, mbox, m);
1298                 mesgdel(mbox, m);
1299         }
1300         if(write(mbox->ctlfd, deletestr, strlen(deletestr)) < 0)
1301                 fprint(2, "Mail: warning: error removing mail message files: %r\n");
1302         free(deletestr);
1303         winselect(w, "0", 0);
1304         if(nopen == 0)
1305                 winclean(w);
1306         mbox->dirty = 0;
1307 }
1308
1309 /* name is a full file name, but it might not belong to us */
1310 Message*
1311 mesglookupfile(Message *mbox, char *name, char *digest)
1312 {
1313         int k, n;
1314
1315         k = strlen(name);
1316         n = strlen(mbox->name);
1317         if(k==0 || strncmp(name, mbox->name, n) != 0){
1318 //              fprint(2, "Mail: message %s not in this mailbox\n", name);
1319                 return nil;
1320         }
1321         return mesglookup(mbox, name+n, digest);
1322 }