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