send: read reply-to header
[mailx.git] / mailx.c
blob89cca5d0637f67bc26dd5f1de174345e7aad2958
1 /*
2 * neatmailx, a small mailx clone
4 * Copyright (C) 2009-2013 Ali Gholami Rudi <ali at rudi dot ir>
6 * This program is released under the modified BSD license.
7 */
8 #include <ctype.h>
9 #include <fcntl.h>
10 #include <stdlib.h>
11 #include <string.h>
12 #include <unistd.h>
13 #include <sys/stat.h>
14 #include <sys/types.h>
15 #include "config.h"
16 #include "mbox.h"
17 #include "mime.h"
18 #include "send.h"
19 #include "sort.h"
20 #include "str.h"
21 #include "util.h"
23 #define LEN(a) (sizeof(a) / sizeof((a)[0]))
24 #define MAILBUF (1 << 16)
25 #define MAXLINE (1 << 8)
26 #define BUFSIZE (1 << 12)
28 static struct mbox *mbox;
29 static struct sort *sort;
30 static int cur;
31 static int sel[MAXMAILS];
32 static int nsel;
33 static int quit;
35 static int read_line(char *dst, int size)
37 static char buf[BUFSIZE];
38 static int cur;
39 static int len;
40 int nw = 0;
41 while (1) {
42 int cur_len = MIN(len - cur, size - nw - 1);
43 char *nl = memchr(buf + cur, '\n', cur_len);
44 int nr = nl ? nl - buf - cur + 1 : cur_len;
45 if (nr) {
46 memcpy(dst + nw, buf + cur, nr);
47 nw += nr;
48 cur += nr;
49 dst[nw] = '\0';
51 if (nl || nw == size - 1)
52 return nw;
53 cur = 0;
54 if ((len = read(0, buf, BUFSIZE)) <= 0)
55 return -1;
57 return nw;
60 static int utf8len(int c)
62 if (c > 0 && c <= 0x7f)
63 return 1;
64 if (c >= 0xfc)
65 return 6;
66 if (c >= 0xf8)
67 return 5;
68 if (c >= 0xf0)
69 return 4;
70 if (c >= 0xe0)
71 return 3;
72 if (c >= 0xc0)
73 return 2;
74 return -1;
77 static char *till_eol(char *r, int len, char *s, char *e, int fill)
79 int l, i = 0;
80 while (i < len && s && s < e && *s) {
81 l = utf8len((unsigned char) *s);
82 if (*s == '\r' || *s == '\n') { /* ignoring line breaks */
83 s++;
84 } else if (l <= 0) {
85 *r++ = '?';
86 s++;
87 i++;
88 } else {
89 memcpy(r, s, l);
90 r += l;
91 s += l;
92 i++;
95 while (i++ < fill)
96 *r++ = ' ';
97 return r;
100 static int msg_num(char *num)
102 int n = -1;
103 if (!*num || !strcmp(".", num))
104 n = cur;
105 if (isdigit(*num))
106 n = atoi(num);
107 if (!strcmp("$", num))
108 n = mbox->n - 1;
109 if (*num == '-')
110 n = cur - (*(num + 1) ? atoi(num + 1) : 1);
111 if (*num == '+')
112 n = cur + (*(num + 1) ? atoi(num + 1) : 1);
113 if (!strcmp(",", num)) {
114 int i = cur;
115 while (++i < sort->n) {
116 int stat = sort->mails[i]->stat;
117 if (!(stat & STAT_READ) && (stat & STAT_NEW)) {
118 n = i;
119 break;
123 if (n < 0 || n >= mbox->n)
124 return -1;
125 return n;
128 static int stat_char(struct mail *mail)
130 if (mail->stat & STAT_NEW)
131 return mail->stat & STAT_READ ? 'R' : 'N';
132 else
133 return mail->stat & STAT_READ ? ' ' : 'U';
136 /* search s for r */
137 static char *memstr(char *s, int slen, char *r, int rlen)
139 char *d = s + slen;
140 while (s && s < d) {
141 if (!strncmp(s, r, MIN(d - s, rlen)))
142 return s;
143 s = memchr(s + 1, r[0], d - s - 1);
145 return NULL;
148 static int search(char *args, int all, int dir)
150 static char hdr_name[MAXLINE]; /* previous search header */
151 static char hdr_val[MAXLINE]; /* previous search keyword */
152 static int stat; /* previous search status */
153 char *beg = strchr(args, '(');
154 char *spc = beg ? strchr(beg, ' ') : NULL;
155 char *end = spc ? strchr(spc, ')') : NULL;
156 int i;
157 if (beg && (!end || !spc))
158 return 0;
159 if (beg) {
160 int hdr_len = spc - beg - 1;
161 put_mem(hdr_name, beg + 1, hdr_len);
162 hdr_name[hdr_len] = '\0';
163 while (isspace(*spc))
164 spc++;
165 put_mem(hdr_val, spc, end - spc);
166 hdr_val[end - spc] = '\0';
167 stat = isalpha(*(args + 1)) ? toupper(*(args + 1)) : 0;
169 for (i = cur + dir; i >= 0 && i < sort->n; i += dir) {
170 if (!stat || stat_char(sort->mails[i]) == stat) {
171 char *hdr = mail_hdr(sort->mails[i], hdr_name);
172 if (hdr && memstr(hdr, hdr_len(hdr),
173 hdr_val, strlen(hdr_val))) {
174 if (!all) {
175 cur = i;
176 return 1;
178 sel[nsel++] = i;
182 return nsel;
185 static int sel_msgs(char *args, int all)
187 char num[MAXLINE];
188 int i;
189 nsel = 0;
190 if (*args == '/')
191 return search(args, all, +1);
192 if (*args == '?')
193 return search(args, all, -1);
194 args = cut_word(num, args);
195 while (1) {
196 char *com;
197 int beg = -1;
198 int end = -1;
199 if (*num && (com = strchr(num + 1, ','))) {
200 char beg_str[MAXLINE];
201 memcpy(beg_str, num, com - num);
202 beg_str[com - num] = '\0';
203 beg = msg_num(beg_str);
204 end = msg_num(com + 1);
205 } else if (!strcmp("%", num)) {
206 beg = 0;
207 end = sort->n - 1;
208 } else {
209 beg = msg_num(num);
210 end = beg;
212 if (beg != -1 && end != -1) {
213 if (!all) {
214 cur = beg;
215 return 1;
217 for (i = beg; i <= end; i++)
218 sel[nsel++] = i;
220 args = cut_word(num, args);
221 if (!*num)
222 break;
224 return nsel;
227 static char mimes_buf[MAXMIME][MAILBUF];
228 static struct mail mimes_mail[MAXMIME];
229 static struct mail *mimes_main[MAXMIME];
230 static int mimes_cur;
232 static void mimes_clear(void)
234 mimes_cur = 0;
235 memset(mimes_main, 0, sizeof(mimes_main));
238 static void mimes_free(struct mail *mail)
240 mail->data = NULL;
243 static void mimes_add(struct mail *mail)
245 struct mail *mime_mail = &mimes_mail[mimes_cur];
246 int len;
247 if (mimes_main[mimes_cur])
248 mimes_main[mimes_cur]->data = NULL;
249 mimes_main[mimes_cur] = mail;
250 len = mime_decode(mimes_buf[mimes_cur], mail->head,
251 MIN(mail->len, MAILBUF));
252 memset(mime_mail, 0, sizeof(*mime_mail));
253 mail_read(mime_mail, mimes_buf[mimes_cur], mimes_buf[mimes_cur] + len);
254 /* handle ^From_ inside msgs */
255 mime_mail->body_len += len - mime_mail->len;
256 mime_mail->len = len;
257 mail->data = mime_mail;
258 mimes_cur = (mimes_cur + 1) % MAXMIME;
261 static struct mail *mimes_get(struct mail *mail)
263 return mail->data ? mail->data : mail;
266 static void cmd_mime(char *pre, char *arg)
268 int i;
269 if (!sel_msgs(pre, 1))
270 return;
271 for (i = 0; i < nsel; i++)
272 mimes_add(sort->mails[sel[i]]);
275 static void show_mail(char *args, int filthdr)
277 struct mail *mail;
278 char buf[MAILBUF];
279 char *pg_args[] = {PAGER, NULL};
280 char *s = buf;
281 if (!sel_msgs(args, 0))
282 return;
283 mail = sort->mails[cur];
284 mail->stat |= STAT_READ;
285 mail = mimes_get(mail);
286 if (filthdr) {
287 s += mail_head(mail, buf, sizeof(buf), hdr_filt, LEN(hdr_filt));
288 s = put_mem(s, mail->body, MIN(sizeof(buf) - (s - buf),
289 mail->body_len));
290 } else {
291 s = put_mem(s, mail->head, MIN(sizeof(buf), mail->len));
293 exec_pipe(PAGER, pg_args, buf, s - buf);
296 static void cmd_page(char *pre, char *arg)
298 show_mail(pre, 1);
301 static void cmd_cat(char *pre, char *arg)
303 show_mail(pre, 0);
306 static int isreply(char *s)
308 return tolower(s[0]) == 'r' && tolower(s[1]) == 'e' && s[2] == ':';
311 static char *put_hdr(struct mail *mail, char *name, char *dst, int wid, int fill)
313 char *hdr = mail_hdr(mail, name);
314 if (hdr) {
315 hdr = hdr + strlen(name) + 1;
316 if (TRIM_RE && isreply(hdr))
317 hdr += 3;
318 while (*hdr == ' ')
319 hdr++;
320 dst = till_eol(dst, wid, hdr, hdr + hdr_len(hdr), fill ? wid : 0);
321 } else {
322 while (fill && --wid >= 0)
323 *dst++ = ' ';
325 return dst;
328 #define put_clr(s, c) ((COLORS) ? (put_str(s, c)) : (s))
330 static void cmd_head(char *pre, char *arg)
332 int beg, end;
333 int i;
334 if (!sel_msgs(pre, 0))
335 return;
336 beg = cur / NHEAD * NHEAD;
337 end = MIN(beg + NHEAD, mbox->n);
338 for (i = beg; i < end; i++) {
339 struct mail *mail = sort->mails[i];
340 char fmt[MAXLINE];
341 char *s = fmt;
342 int col;
343 int indent;
344 *s++ = i == cur ? '>' : ' ';
345 s = put_clr(s, mail->stat & STAT_NEW ? "\33[1;31m" : "\33[33m");
346 *s++ = mail->stat & STAT_DEL ? 'D' : ' ';
347 *s++ = stat_char(mail);
348 s = put_clr(s, "\33[1;32m");
349 s = put_int(s, i, DIGITS);
350 *s++ = ' ';
351 *s++ = ' ';
352 mail = mimes_get(mail);
353 s = put_clr(s, "\33[0;34m");
354 s = put_hdr(mail, "From:", s, 16, 1);
355 *s++ = ' ';
356 *s++ = ' ';
357 col = 3 + DIGITS + 2 + 16 + 2;
358 indent = sort_level(sort, i) * 2;
359 if (!mail_hdr(sort->mails[i], "in-reply-to"))
360 s = put_clr(s, "\33[1;36m");
361 else
362 s = put_clr(s, indent ? "\33[32m" : "\33[36m");
363 while (indent-- && col < WIDTH) {
364 col++;
365 *s++ = ' ';
367 s = put_hdr(mail, "Subject:", s, WIDTH - col, 0);
368 s = put_clr(s, "\33[m");
369 *s++ = '\n';
370 write(1, fmt, s - fmt);
374 static void cmd_z(char *pre, char *arg)
376 int page = -1;
377 if (!*pre)
378 page = MIN(cur + NHEAD, mbox->n) / NHEAD;
379 if (*pre == '-')
380 page = cur / NHEAD - (*(pre + 1) ? atoi(pre + 1) : 1);
381 if (*pre == '+')
382 page = cur / NHEAD + (*(pre + 1) ? atoi(pre + 1) : 1);
383 if (*pre == '$')
384 page = mbox->n / NHEAD;
385 if (isdigit(*pre))
386 page = atoi(pre);
387 if (page >= 0 && page * NHEAD < mbox->n) {
388 cur = page * NHEAD;
389 cmd_head("", "");
393 static void cmd_del(char *pre, char *arg)
395 int i;
396 if (!sel_msgs(pre, 1))
397 return;
398 for (i = 0; i < nsel; i++)
399 sort->mails[sel[i]]->stat |= STAT_DEL;
402 static void cmd_undel(char *pre, char *arg)
404 int i;
405 if (!sel_msgs(pre, 1))
406 return;
407 for (i = 0; i < nsel; i++) {
408 sort->mails[sel[i]]->stat &= ~STAT_DEL;
409 mimes_free(sort->mails[sel[i]]);
413 static void print(char *s)
415 write(1, s, strlen(s));
418 static int is_mbox(char *filename)
420 struct stat st;
421 stat(filename, &st);
422 return S_ISREG(st.st_mode);
425 static char *filename(char *path)
427 char *slash = strrchr(path, '/');
428 return slash ? slash + 1 : path;
431 static void sum_mbox(void)
433 char msg[MAXLINE];
434 char *s = msg;
435 int new = 0;
436 int unread = 0;
437 int del = 0;
438 int i;
439 s = put_str(s, filename(mbox->path));
440 s = put_str(s, ": ");
441 s = put_int(s, mbox->n, DIGITS);
442 s = put_str(s, " msgs ");
443 for (i = 0; i < mbox->n; i++) {
444 if (!(mbox->mails[i].stat & STAT_READ)) {
445 unread++;
446 if (mbox->mails[i].stat & STAT_NEW)
447 new++;
449 if (mbox->mails[i].stat & STAT_DEL)
450 del++;
452 if (new) {
453 s = put_int(s, new, DIGITS);
454 s = put_str(s, " new ");
456 if (unread) {
457 s = put_int(s, unread, DIGITS);
458 s = put_str(s, " unread ");
460 if (del) {
461 s = put_int(s, del, DIGITS);
462 s = put_str(s, " del ");
464 s = put_str(s, "\n");
465 print(msg);
468 static void open_mbox(char *filename)
470 mbox = mbox_alloc(filename);
471 sort = sort_alloc(mbox, THREADED ? SORT_THREAD : 0);
472 cur = 0;
473 sum_mbox();
476 static void close_mbox(void)
478 mimes_clear();
479 sort_free(sort);
480 mbox_free(mbox);
483 static void mbox_old(struct mbox *mbox)
485 int i;
486 for (i = 0; i < mbox->n; i++) {
487 struct mail *mail = &mbox->mails[i];
488 mail->stat = (mail->stat & ~STAT_NEW) | STAT_OLD;
492 static int has_mail(char *path)
494 struct stat st;
495 if (stat(path, &st) == -1)
496 return 0;
497 return st.st_mtime > st.st_atime;
500 static int mbox_path(char *path, char *addr)
502 char *s = path;
503 int i;
504 if (*addr == '+') {
505 s = put_str(s, FOLDER);
506 s = put_str(s, addr + 1);
508 if (*addr == ',') {
509 for (i = 0; i < LEN(boxes); i++) {
510 if (has_mail(boxes[i])) {
511 s = put_str(s, boxes[i]);
512 break;
516 if (!strcmp(".", addr) && mbox)
517 s = put_str(s, mbox->path);
518 if (s == path && *addr)
519 s = put_str(s, addr);
520 return s - path;
523 static void warn_nomem(void)
525 print("no mem for new msgs\n");
528 static void cmd_fold(char *pre, char *arg)
530 if (*arg) {
531 char path[MAXPATHLEN];
532 if (mbox_path(path, arg) && is_mbox(path)) {
533 mbox_old(mbox);
534 if (mbox_write(mbox) == -1) {
535 warn_nomem();
536 return;
538 close_mbox();
539 open_mbox(path);
541 } else {
542 sum_mbox();
546 static void cmd_news(char *pre, char *arg)
548 char msg[MAXLINE];
549 char *s;
550 int i;
551 for (i = 0; i < LEN(boxes); i++) {
552 if (has_mail(boxes[i])) {
553 s = msg;
554 s = put_str(s, "\t");
555 s = put_str(s, filename(boxes[i]));
556 s = put_str(s, "\n");
557 print(msg);
562 static void cmd_inc(char *pre, char *arg)
564 char msg[MAXLINE];
565 int new = mbox_inc(mbox);
566 char *s;
567 if (new == -1)
568 warn_nomem();
569 if (new > 0) {
570 sort_inc(sort);
571 s = msg;
572 s = put_int(s, new, DIGITS);
573 s = put_str(s, " new\n");
574 print(msg);
578 static void cmd_next(char *pre, char *arg)
580 if (!pre || !*pre) {
581 if (cur + 1 < mbox->n) {
582 cur++;
583 } else {
584 print("EOF\n");
585 return;
588 show_mail(pre, 1);
591 static void copy_mail(char *msgs, char *dst, int del)
593 char path[MAXLINE];
594 int i;
595 int fd;
596 if (!dst || !*dst)
597 return;
598 if (!sel_msgs(msgs, 1))
599 return;
600 mbox_path(path, dst);
601 fd = open(path, O_WRONLY | O_APPEND | O_CREAT, S_IRUSR | S_IWUSR);
602 if (fd == -1) {
603 print("failed to open mbox for writing\n");
604 return;
606 for (i = 0; i < nsel; i++) {
607 struct mail *mail = sort->mails[sel[i]];
608 mail_write(mail, fd);
609 if (del)
610 mail->stat |= STAT_DEL;
612 close(fd);
615 static void cmd_copy(char *pre, char *arg)
617 copy_mail(pre, arg, 0);
620 static void cmd_move(char *pre, char *arg)
622 copy_mail(pre, arg, 1);
625 static void compose(struct draft *draft)
627 char record[MAXPATHLEN] = "";
628 char line[MAXLINE];
629 char cmd[MAXLINE];
630 char *pg_args[] = {PAGER, NULL};
631 if (RECORD)
632 mbox_path(record, RECORD);
633 else if (mbox)
634 strcpy(record, mbox->path);
635 while (read_line(line, sizeof(line)) > 0) {
636 cut_word(cmd, line);
637 if (!strcmp("~e", cmd))
638 draft_edit(draft, EDITOR);
639 if (!strcmp("~v", cmd))
640 draft_edit(draft, VISUAL);
641 if (!strcmp("~.", cmd)) {
642 if (*record)
643 draft_save(draft, record);
644 draft_send(draft);
645 break;
647 if (!strcmp("~p", cmd))
648 exec_pipe(PAGER, pg_args, draft->mail, draft->len);
649 if (!strcmp("~q", cmd) || !strcmp("~x", cmd))
650 break;
654 static void cmd_mail(char *pre, char *arg)
656 struct draft draft;
657 draft_init(&draft, *arg ? &arg : NULL, *arg ? 1 : 0, NULL);
658 compose(&draft);
661 static void cmd_reply(char *pre, char *arg)
663 struct draft draft;
664 if (!sel_msgs(pre, 0))
665 return;
666 draft_reply(&draft, mimes_get(sort->mails[cur]));
667 compose(&draft);
670 static void prompt(void)
672 write(1, "? ", 2);
675 static void cmd_quit(char *pre, char *arg)
677 mbox_old(mbox);
678 if (mbox_write(mbox) == -1)
679 warn_nomem();
680 else
681 quit = 1;
684 static void cmd_exit(char *pre, char *arg)
686 quit = 1;
689 static void cmd_clear(char *pre, char *arg)
691 print("\x1b[H\x1b[J");
694 static struct cmds {
695 char *name;
696 void (*cmd)(char *pre, char *arg);
697 } cmds[] = {
698 {"page", cmd_page},
699 {"next", cmd_next},
700 {"header", cmd_head},
701 {"ls", cmd_head},
702 {"file", cmd_fold},
703 {"folder", cmd_fold},
704 {"inc", cmd_inc},
705 {"z", cmd_z},
706 {"mail", cmd_mail},
707 {"copy", cmd_copy},
708 {"cp", cmd_copy},
709 {"move", cmd_move},
710 {"mv", cmd_move},
711 {"reply", cmd_reply},
712 {"cat", cmd_cat},
713 {"delete", cmd_del},
714 {"rm", cmd_del},
715 {"cd", cmd_fold},
716 {"undelete", cmd_undel},
717 {"quit", cmd_quit},
718 {"exit", cmd_exit},
719 {"xit", cmd_exit},
720 {"news", cmd_news},
721 {"mime", cmd_mime},
722 {"clear", cmd_clear},
725 static void cmd_parse(char *line, char *pre, char *cmd, char *arg)
727 while (*line && isspace(*line))
728 line++;
729 while (*line && !isalpha(*line)) {
730 if ((line[0] == '/' || line[0] == '?') &&
731 (line[1] == '(' || line[2] == '('))
732 while (*line && *line != ')')
733 *pre++ = *line++;
734 *pre++ = *line++;
736 *pre = '\0';
737 while (*line && isspace(*line))
738 line++;
739 while (*line && isalpha(*line))
740 *cmd++ = *line++;
741 *cmd = '\0';
742 while (*line && isspace(*line))
743 line++;
744 while (*line && !isspace(*line))
745 *arg++ = *line++;
746 *arg = '\0';
749 static void loop(void)
751 char line[MAXLINE];
752 char pre[MAXLINE];
753 char cmd[MAXLINE];
754 char arg[MAXLINE];
755 int len;
756 prompt();
757 while (read_line(line, sizeof(line)) > 0) {
758 cmd_parse(line, pre, cmd, arg);
759 len = strlen(cmd);
760 int i;
761 if (!len)
762 cmd_next(pre, arg);
763 else
764 for (i = 0; i < LEN(cmds); i++)
765 if (!strncmp(cmds[i].name, cmd, len)) {
766 cmds[i].cmd(pre, arg);
767 break;
769 if (quit)
770 break;
771 else
772 prompt();
776 int main(int argc, char *argv[])
778 int i = 0;
779 char *filename = NULL;
780 char *subj = NULL;
781 while (++i < argc) {
782 if (argv[i][0] != '-')
783 break;
784 if (!strcmp("-f", argv[i]))
785 filename = argv[++i];
786 if (!strcmp("-s", argv[i]))
787 subj = argv[++i];
789 if (filename) {
790 char path[MAXPATHLEN];
791 if (mbox_path(path, filename) && is_mbox(path)) {
792 open_mbox(path);
793 loop();
794 close_mbox();
796 } else {
797 struct draft draft;
798 draft_init(&draft, argv + i, argc - i, subj);
799 compose(&draft);
801 return 0;