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