mesg.c 28 KB

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