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