mesg.c 25 KB

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