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