mesg.c 27 KB

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