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