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+2); 575 memmove(all, raw, n); 576 memmove(all+n, "\n", 1); 577 n++; 578 free(raw); 579 ret = 1; 580 s = estrdup(s); 581 if(s[0] != '/') 582 s = egrow(estrdup(mailboxdir), "/", s); 583 ofd = open(s, OWRITE); 584 if(ofd < 0){ 585 fprint(2, "Mail: can't open %s: %r\n", s); 586 ret = 0; 587 }else if(seek(ofd, 0LL, 2) < 0 || write(ofd, unixheader, k) != k || 588 write2(-1, ofd, all, n, 1) < n){ 589 fprint(2, "Mail: save failed: can't write %s: %r\n", s); 590 ret = 0; 591 } 592 free(unixheader); 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, *maildest; 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(maildest = getenv("maildest")){ 961 maildest = eappend(maildest, "/", dest); 962 Bprint(w->body, "\tcp %s%sbody%s %q\n", rootdir, name, ext(m->type), maildest); 963 free(maildest); 964 } 965 if(m->filename[0] != '/'){ 966 dest = egrow(estrdup(home), "/", dest); 967 } 968 Bprint(w->body, "\tcp %s%sbody%s %q\n", rootdir, name, ext(m->type), dest); 969 free(dest); 970 }else if(!fileonly) 971 Bprint(w->body, "\tfile is %s%sbody%s\n", rootdir, name, ext(m->type)); 972 } 973 974 void 975 printheader(char *dir, Biobuf *b, char **okheaders) 976 { 977 char *s; 978 char *lines[100]; 979 int i, j, n; 980 981 s = readfile(dir, "header", nil); 982 if(s == nil) 983 return; 984 n = getfields(s, lines, nelem(lines), 0, "\n"); 985 for(i=0; i<n; i++) 986 for(j=0; okheaders[j]; j++) 987 if(cistrncmp(lines[i], okheaders[j], strlen(okheaders[j])) == 0) 988 Bprint(b, "%s\n", lines[i]); 989 free(s); 990 } 991 992 void 993 mesgload(Message *m, char *rootdir, char *file, Window *w) 994 { 995 char *s, *subdir, *name, *dir; 996 Message *mp, *thisone; 997 int n; 998 999 dir = estrstrdup(rootdir, file); 1000 1001 if(strcmp(m->type, "message/rfc822") != 0){ /* suppress headers of envelopes */ 1002 if(strlen(m->from) > 0){ 1003 Bprint(w->body, "From: %s\n", m->from); 1004 mesgline(m, "Date", m->date); 1005 mesgline(m, "To", m->to); 1006 mesgline(m, "CC", m->cc); 1007 mesgline(m, "Subject", m->subject); 1008 printheader(dir, w->body, extraheaders); 1009 }else{ 1010 printheader(dir, w->body, okheaders); 1011 printheader(dir, w->body, extraheaders); 1012 } 1013 Bprint(w->body, "\n"); 1014 } 1015 1016 if(m->level == 1 && m->recursed == 0){ 1017 m->recursed = 1; 1018 readmbox(m, rootdir, m->name); 1019 } 1020 if(m->head == nil){ /* single part message */ 1021 if(strcmp(m->type, "text")==0 || strncmp(m->type, "text/", 5)==0){ 1022 mimedisplay(m, m->name, rootdir, w, 1); 1023 s = readbody(m->type, dir, &n); 1024 winwritebody(w, s, n); 1025 free(s); 1026 }else 1027 mimedisplay(m, m->name, rootdir, w, 0); 1028 }else{ 1029 /* multi-part message, either multipart/* or message/rfc822 */ 1030 thisone = nil; 1031 if(strcmp(m->type, "multipart/alternative") == 0){ 1032 thisone = m->head; /* in case we can't find a good one */ 1033 for(mp=m->head; mp!=nil; mp=mp->next) 1034 if(isprintable(mp->type)){ 1035 thisone = mp; 1036 break; 1037 } 1038 } 1039 for(mp=m->head; mp!=nil; mp=mp->next){ 1040 if(thisone!=nil && mp!=thisone) 1041 continue; 1042 subdir = estrstrdup(dir, mp->name); 1043 name = estrstrdup(file, mp->name); 1044 /* skip first element in name because it's already in window name */ 1045 if(mp != m->head) 1046 Bprint(w->body, "\n===> %s (%s) [%s]\n", strchr(name, '/')+1, mp->type, mp->disposition); 1047 if(strcmp(mp->type, "text")==0 || strncmp(mp->type, "text/", 5)==0){ 1048 mimedisplay(mp, name, rootdir, w, 1); 1049 printheader(subdir, w->body, okheaders); 1050 printheader(subdir, w->body, extraheaders); 1051 winwritebody(w, "\n", 1); 1052 s = readbody(mp->type, subdir, &n); 1053 winwritebody(w, s, n); 1054 free(s); 1055 }else{ 1056 if(strncmp(mp->type, "multipart/", 10)==0 || strcmp(mp->type, "message/rfc822")==0){ 1057 mp->w = w; 1058 mesgload(mp, rootdir, name, w); 1059 mp->w = nil; 1060 }else 1061 mimedisplay(mp, name, rootdir, w, 0); 1062 } 1063 free(name); 1064 free(subdir); 1065 } 1066 } 1067 free(dir); 1068 } 1069 1070 int 1071 tokenizec(char *str, char **args, int max, char *splitc) 1072 { 1073 int na; 1074 int intok = 0; 1075 1076 if(max <= 0) 1077 return 0; 1078 for(na=0; *str != '\0';str++){ 1079 if(strchr(splitc, *str) == nil){ 1080 if(intok) 1081 continue; 1082 args[na++] = str; 1083 intok = 1; 1084 }else{ 1085 /* it's a separator/skip character */ 1086 *str = '\0'; 1087 if(intok){ 1088 intok = 0; 1089 if(na >= max) 1090 break; 1091 } 1092 } 1093 } 1094 return na; 1095 } 1096 1097 Message* 1098 mesglookup(Message *mbox, char *name, char *digest) 1099 { 1100 int n; 1101 Message *m; 1102 char *t; 1103 1104 if(digest){ 1105 /* can find exactly */ 1106 for(m=mbox->head; m!=nil; m=m->next) 1107 if(strcmp(digest, m->digest) == 0) 1108 break; 1109 return m; 1110 } 1111 1112 n = strlen(name); 1113 if(n == 0) 1114 return nil; 1115 if(name[n-1] == '/') 1116 t = estrdup(name); 1117 else 1118 t = estrstrdup(name, "/"); 1119 for(m=mbox->head; m!=nil; m=m->next) 1120 if(strcmp(t, m->name) == 0) 1121 break; 1122 free(t); 1123 return m; 1124 } 1125 1126 /* 1127 * Find plumb port, knowing type is text, given file name (by extension) 1128 */ 1129 int 1130 plumbportbysuffix(char *file) 1131 { 1132 char *suf; 1133 int i, nsuf, nfile; 1134 1135 nfile = strlen(file); 1136 for(i=0; ports[i].type!=nil; i++){ 1137 suf = ports[i].suffix; 1138 nsuf = strlen(suf); 1139 if(nfile > nsuf) 1140 if(cistrncmp(file+nfile-nsuf, suf, nsuf) == 0) 1141 return i; 1142 } 1143 return 0; 1144 } 1145 1146 /* 1147 * Find plumb port using type and file name (by extension) 1148 */ 1149 int 1150 plumbport(char *type, char *file) 1151 { 1152 int i; 1153 1154 for(i=0; ports[i].type!=nil; i++) 1155 if(strncmp(type, ports[i].type, strlen(ports[i].type)) == 0) 1156 return i; 1157 /* see if it's a text type */ 1158 for(i=0; goodtypes[i]!=nil; i++) 1159 if(strncmp(type, goodtypes[i], strlen(goodtypes[i])) == 0) 1160 return plumbportbysuffix(file); 1161 return -1; 1162 } 1163 1164 void 1165 plumb(Message *m, char *dir) 1166 { 1167 int i; 1168 char *port; 1169 Plumbmsg *pm; 1170 1171 if(strlen(m->type) == 0) 1172 return; 1173 i = plumbport(m->type, m->filename); 1174 if(i < 0) 1175 fprint(2, "can't find destination for message subpart\n"); 1176 else{ 1177 port = ports[i].port; 1178 pm = emalloc(sizeof(Plumbmsg)); 1179 pm->src = estrdup("Mail"); 1180 if(port) 1181 pm->dst = estrdup(port); 1182 else 1183 pm->dst = nil; 1184 pm->wdir = nil; 1185 pm->type = estrdup("text"); 1186 pm->ndata = -1; 1187 pm->data = estrstrdup(dir, "body"); 1188 pm->data = eappend(pm->data, "", ports[i].suffix); 1189 if(plumbsend(plumbsendfd, pm) < 0) 1190 fprint(2, "error writing plumb message: %r\n"); 1191 plumbfree(pm); 1192 } 1193 } 1194 1195 int 1196 mesgopen(Message *mbox, char *dir, char *s, Message *mesg, int plumbed, char *digest) 1197 { 1198 char *t, *u, *v; 1199 Message *m; 1200 char *direlem[10]; 1201 int i, ndirelem, reuse; 1202 1203 /* find white-space-delimited first word */ 1204 for(t=s; *t!='\0' && !isspace(*t); t++) 1205 ; 1206 u = emalloc(t-s+1); 1207 memmove(u, s, t-s); 1208 /* separate it on slashes */ 1209 ndirelem = tokenizec(u, direlem, nelem(direlem), "/"); 1210 if(ndirelem <= 0){ 1211 Error: 1212 free(u); 1213 return 0; 1214 } 1215 if(plumbed){ 1216 write(wctlfd, "top", 3); 1217 write(wctlfd, "current", 7); 1218 } 1219 /* open window for message */ 1220 m = mesglookup(mbox, direlem[0], digest); 1221 if(m == nil) 1222 goto Error; 1223 if(mesg!=nil && m!=mesg) /* string looked like subpart but isn't part of this message */ 1224 goto Error; 1225 if(m->opened == 0){ 1226 if(m->w == nil){ 1227 reuse = 0; 1228 m->w = newwindow(); 1229 }else{ 1230 reuse = 1; 1231 /* re-use existing window */ 1232 if(winsetaddr(m->w, "0,$", 1)){ 1233 if(m->w->data < 0) 1234 m->w->data = winopenfile(m->w, "data"); 1235 write(m->w->data, "", 0); 1236 } 1237 } 1238 v = estrstrdup(mbox->name, m->name); 1239 winname(m->w, v); 1240 free(v); 1241 if(!reuse){ 1242 if(m->deleted) 1243 wintagwrite(m->w, "Q Reply all UnDelmesg Save ", 2+6+4+10+5); 1244 else 1245 wintagwrite(m->w, "Q Reply all Delmesg Save ", 2+6+4+8+5); 1246 } 1247 threadcreate(mesgctl, m, STACK); 1248 winopenbody(m->w, OWRITE); 1249 mesgload(m, dir, m->name, m->w); 1250 winclosebody(m->w); 1251 winclean(m->w); 1252 m->opened = 1; 1253 if(ndirelem == 1){ 1254 free(u); 1255 return 1; 1256 } 1257 } 1258 if(ndirelem == 1 && plumbport(m->type, m->filename) <= 0){ 1259 /* make sure dot is visible */ 1260 ctlprint(m->w->ctl, "show\n"); 1261 return 0; 1262 } 1263 /* walk to subpart */ 1264 dir = estrstrdup(dir, m->name); 1265 for(i=1; i<ndirelem; i++){ 1266 m = mesglookup(m, direlem[i], digest); 1267 if(m == nil) 1268 break; 1269 dir = egrow(dir, m->name, nil); 1270 } 1271 if(m != nil && plumbport(m->type, m->filename) > 0) 1272 plumb(m, dir); 1273 free(dir); 1274 free(u); 1275 return 1; 1276 } 1277 1278 void 1279 rewritembox(Window *w, Message *mbox) 1280 { 1281 Message *m, *next; 1282 char *deletestr, *t; 1283 int nopen; 1284 1285 deletestr = estrstrdup("delete ", fsname); 1286 1287 nopen = 0; 1288 for(m=mbox->head; m!=nil; m=next){ 1289 next = m->next; 1290 if(m->deleted == 0) 1291 continue; 1292 if(m->opened){ 1293 nopen++; 1294 continue; 1295 } 1296 if(m->writebackdel){ 1297 /* messages deleted by plumb message are not removed again */ 1298 t = estrdup(m->name); 1299 if(strlen(t) > 0) 1300 t[strlen(t)-1] = '\0'; 1301 deletestr = egrow(deletestr, " ", t); 1302 } 1303 mesgmenudel(w, mbox, m); 1304 mesgdel(mbox, m); 1305 } 1306 if(write(mbox->ctlfd, deletestr, strlen(deletestr)) < 0) 1307 fprint(2, "Mail: warning: error removing mail message files: %r\n"); 1308 free(deletestr); 1309 winselect(w, "0", 0); 1310 if(nopen == 0) 1311 winclean(w); 1312 mbox->dirty = 0; 1313 } 1314 1315 /* name is a full file name, but it might not belong to us */ 1316 Message* 1317 mesglookupfile(Message *mbox, char *name, char *digest) 1318 { 1319 int k, n; 1320 1321 k = strlen(name); 1322 n = strlen(mbox->name); 1323 if(k==0 || strncmp(name, mbox->name, n) != 0){ 1324 // fprint(2, "Mail: message %s not in this mailbox\n", name); 1325 return nil; 1326 } 1327 return mesglookup(mbox, name+n, digest); 1328 } 1329