mailx: ignore SIGPIPE
[mailx.git] / mailx.c
blob7cdb17f97fc7dc408718242290bc3bc4bd7a1a94
1 /*
2 * neatmailx, a small mailx clone
4 * Copyright (C) 2009-2014 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 <signal.h>
11 #include <stdlib.h>
12 #include <string.h>
13 #include <stdio.h>
14 #include <unistd.h>
15 #include <sys/stat.h>
16 #include <sys/types.h>
17 #include "config.h"
18 #include "mbox.h"
19 #include "mime.h"
20 #include "send.h"
21 #include "sort.h"
22 #include "str.h"
23 #include "util.h"
25 #define LEN(a) (sizeof(a) / sizeof((a)[0]))
26 #define MAILBUF (1 << 16)
27 #define MAXLINE (1 << 8)
28 #define BUFSIZE (1 << 12)
30 static struct mbox *mbox; /* current mbox */
31 static struct mail *sorted[MAXMAILS]; /* sorted mails in mbox */
32 static int levels[MAXMAILS]; /* identation of mails in sorted[] */
33 static int mcount; /* number of mails in sorted[] */
34 static int cur; /* current mail */
35 static int sel[MAXMAILS]; /* list of selected mails */
36 static int nsel; /* number of selected mails */
37 static int quit;
39 static int readcmd(char *dst, int size)
41 return fgets(dst, size, stdin) == NULL;
44 static int utf8len(int c)
46 if (c > 0 && c <= 0x7f)
47 return 1;
48 if (c >= 0xfc)
49 return 6;
50 if (c >= 0xf8)
51 return 5;
52 if (c >= 0xf0)
53 return 4;
54 if (c >= 0xe0)
55 return 3;
56 if (c >= 0xc0)
57 return 2;
58 return -1;
61 static char *till_eol(char *r, int len, char *s, char *e, int fill)
63 int l, i = 0;
64 while (i < len && s && s < e && *s) {
65 l = utf8len((unsigned char) *s);
66 if (*s == '\r' || *s == '\n') { /* ignoring line breaks */
67 s++;
68 } else if (l <= 0) {
69 *r++ = '?';
70 s++;
71 i++;
72 } else {
73 memcpy(r, s, l);
74 r += l;
75 s += l;
76 i++;
79 while (i++ < fill)
80 *r++ = ' ';
81 return r;
84 static int msg_num(char *num)
86 int n = -1;
87 if (!*num || !strcmp(".", num))
88 n = cur;
89 if (isdigit(*num))
90 n = atoi(num);
91 if (!strcmp("$", num))
92 n = mbox->n - 1;
93 if (*num == '-')
94 n = cur - (*(num + 1) ? atoi(num + 1) : 1);
95 if (*num == '+')
96 n = cur + (*(num + 1) ? atoi(num + 1) : 1);
97 if (!strcmp(",", num)) {
98 int i = cur;
99 while (++i < mcount) {
100 int stat = sorted[i]->stat;
101 if (!(stat & STAT_READ) && (stat & STAT_NEW)) {
102 n = i;
103 break;
107 if (n < 0 || n >= mbox->n)
108 return -1;
109 return n;
112 static int stat_char(struct mail *mail)
114 if (mail->stat & STAT_NEW)
115 return mail->stat & STAT_READ ? 'R' : 'N';
116 else
117 return mail->stat & STAT_READ ? ' ' : 'U';
120 /* search s for r */
121 static char *memstr(char *s, int slen, char *r, int rlen)
123 char *d = s + slen;
124 while (s && s < d) {
125 if (!strncmp(s, r, MIN(d - s, rlen)))
126 return s;
127 s = memchr(s + 1, r[0], d - s - 1);
129 return NULL;
132 static int search(char *args, int all, int dir)
134 static char hdr_name[MAXLINE]; /* previous search header */
135 static char hdr_val[MAXLINE]; /* previous search keyword */
136 static int stat; /* previous search status */
137 char *beg = strchr(args, '(');
138 char *spc = beg ? strchr(beg, ' ') : NULL;
139 char *end = spc ? strchr(spc, ')') : NULL;
140 int i;
141 if (beg && (!end || !spc))
142 return 0;
143 if (beg) {
144 int hdr_len = spc - beg - 1;
145 put_mem(hdr_name, beg + 1, hdr_len);
146 hdr_name[hdr_len] = '\0';
147 while (isspace(*spc))
148 spc++;
149 put_mem(hdr_val, spc, end - spc);
150 hdr_val[end - spc] = '\0';
151 stat = isalpha(*(args + 1)) ? toupper(*(args + 1)) : 0;
153 for (i = cur + dir; i >= 0 && i < mcount; i += dir) {
154 if (!stat || stat_char(sorted[i]) == stat) {
155 char *hdr = mail_hdr(sorted[i], hdr_name);
156 if (hdr && memstr(hdr, hdr_len(hdr),
157 hdr_val, strlen(hdr_val))) {
158 if (!all) {
159 cur = i;
160 return 1;
162 sel[nsel++] = i;
166 return nsel;
169 static int sel_msgs(char *args, int all)
171 char num[MAXLINE];
172 int i;
173 nsel = 0;
174 if (*args == '/')
175 return search(args, all, +1);
176 if (*args == '?')
177 return search(args, all, -1);
178 args = cut_word(num, args);
179 while (1) {
180 char *com;
181 int beg = -1;
182 int end = -1;
183 if (*num && (com = strchr(num + 1, ','))) {
184 char beg_str[MAXLINE];
185 memcpy(beg_str, num, com - num);
186 beg_str[com - num] = '\0';
187 beg = msg_num(beg_str);
188 end = msg_num(com + 1);
189 } else if (!strcmp("%", num)) {
190 beg = 0;
191 end = mcount - 1;
192 } else {
193 beg = msg_num(num);
194 end = beg;
196 if (beg != -1 && end != -1) {
197 if (!all) {
198 cur = beg;
199 return 1;
201 for (i = beg; i <= end; i++)
202 sel[nsel++] = i;
204 args = cut_word(num, args);
205 if (!*num)
206 break;
208 return nsel;
211 static char mimes_buf[MAXMIME][MAILBUF];
212 static struct mail mimes_mail[MAXMIME];
213 static struct mail *mimes_main[MAXMIME];
214 static int mimes_cur;
216 static void mimes_clear(void)
218 mimes_cur = 0;
219 memset(mimes_main, 0, sizeof(mimes_main));
222 static void mimes_free(struct mail *mail)
224 int i;
225 for (i = 0; i < MAXMIME; i++)
226 if (mimes_main[i] == mail)
227 mimes_main[i] = NULL;
230 static void mimes_dec(struct mail *mail)
232 struct mail *mime_mail = &mimes_mail[mimes_cur];
233 int len;
234 mimes_main[mimes_cur] = mail;
235 len = mime_decode(mimes_buf[mimes_cur], mail->head,
236 MIN(mail->len, MAILBUF));
237 memset(mime_mail, 0, sizeof(*mime_mail));
238 mail_read(mime_mail, mimes_buf[mimes_cur], mimes_buf[mimes_cur] + len);
239 /* handle ^From_ inside msgs */
240 mime_mail->body_len += len - mime_mail->len;
241 mime_mail->len = len;
242 mimes_cur = (mimes_cur + 1) % MAXMIME;
245 static struct mail *mimes_get(struct mail *mail)
247 int i;
248 for (i = 0; i < MAXMIME; i++)
249 if (mimes_main[i] == mail)
250 return &mimes_mail[i];
251 return mail;
254 static void cmd_mime(char *pre, char *arg)
256 int i;
257 if (!sel_msgs(pre, 1))
258 return;
259 for (i = 0; i < nsel; i++)
260 mimes_dec(sorted[sel[i]]);
263 static void show_mail(char *args, int filthdr)
265 struct mail *mail;
266 char buf[MAILBUF];
267 char *pg_args[] = {PAGER, NULL};
268 char *s = buf;
269 if (!sel_msgs(args, 0))
270 return;
271 mail = sorted[cur];
272 mail->stat |= STAT_READ;
273 mail = mimes_get(mail);
274 if (filthdr) {
275 s += mail_head(mail, buf, sizeof(buf), hdr_filt, LEN(hdr_filt));
276 s = put_mem(s, mail->body, MIN(sizeof(buf) - (s - buf),
277 mail->body_len));
278 } else {
279 s = put_mem(s, mail->head, MIN(sizeof(buf), mail->len));
281 exec_pipe(PAGER, pg_args, buf, s - buf);
284 static void cmd_page(char *pre, char *arg)
286 show_mail(pre, 1);
289 static void cmd_cat(char *pre, char *arg)
291 show_mail(pre, 0);
294 static int isreply(char *s)
296 return tolower(s[0]) == 'r' && tolower(s[1]) == 'e' && s[2] == ':';
299 static char *put_hdr(struct mail *mail, char *name, char *dst, int wid, int fill)
301 char *hdr = mail_hdr(mail, name);
302 if (hdr) {
303 hdr = hdr + strlen(name) + 1;
304 if (TRIM_RE && isreply(hdr))
305 hdr += 3;
306 while (*hdr == ' ')
307 hdr++;
308 dst = till_eol(dst, wid, hdr, hdr + hdr_len(hdr), fill ? wid : 0);
309 } else {
310 while (fill && --wid >= 0)
311 *dst++ = ' ';
313 return dst;
316 #define put_clr(s, c) ((COLORS) ? (put_str(s, c)) : (s))
318 static void cmd_head(char *pre, char *arg)
320 int beg, end;
321 int i;
322 if (!sel_msgs(pre, 0))
323 return;
324 beg = cur / NHEAD * NHEAD;
325 end = MIN(beg + NHEAD, mbox->n);
326 for (i = beg; i < end; i++) {
327 struct mail *mail = sorted[i];
328 char fmt[MAXLINE];
329 char *s = fmt;
330 int col;
331 int indent;
332 *s++ = i == cur ? '>' : ' ';
333 s = put_clr(s, mail->stat & STAT_NEW ? "\33[1;31m" : "\33[33m");
334 *s++ = mail->stat & STAT_DEL ? 'D' : ' ';
335 *s++ = stat_char(mail);
336 s = put_clr(s, "\33[1;32m");
337 s = put_int(s, i, DIGITS);
338 *s++ = ' ';
339 *s++ = ' ';
340 mail = mimes_get(mail);
341 s = put_clr(s, "\33[0;34m");
342 s = put_hdr(mail, "From:", s, 16, 1);
343 *s++ = ' ';
344 *s++ = ' ';
345 col = 3 + DIGITS + 2 + 16 + 2;
346 indent = levels[i] * 2;
347 if (!mail_hdr(sorted[i], "in-reply-to"))
348 s = put_clr(s, "\33[1;36m");
349 else
350 s = put_clr(s, indent ? "\33[32m" : "\33[36m");
351 while (indent-- && col < WIDTH) {
352 col++;
353 *s++ = ' ';
355 s = put_hdr(mail, "Subject:", s, WIDTH - col, 0);
356 s = put_clr(s, "\33[m");
357 *s++ = '\n';
358 write(1, fmt, s - fmt);
362 static void cmd_z(char *pre, char *arg)
364 int page = -1;
365 if (!*pre)
366 page = MIN(cur + NHEAD, mbox->n) / NHEAD;
367 if (*pre == '-')
368 page = cur / NHEAD - (*(pre + 1) ? atoi(pre + 1) : 1);
369 if (*pre == '+')
370 page = cur / NHEAD + (*(pre + 1) ? atoi(pre + 1) : 1);
371 if (*pre == '$')
372 page = mbox->n / NHEAD;
373 if (isdigit(*pre))
374 page = atoi(pre);
375 if (page >= 0 && page * NHEAD < mbox->n) {
376 cur = page * NHEAD;
377 cmd_head("", "");
381 static void cmd_del(char *pre, char *arg)
383 int i;
384 if (!sel_msgs(pre, 1))
385 return;
386 for (i = 0; i < nsel; i++)
387 sorted[sel[i]]->stat |= STAT_DEL;
390 static void cmd_undel(char *pre, char *arg)
392 int i;
393 if (!sel_msgs(pre, 1))
394 return;
395 for (i = 0; i < nsel; i++) {
396 sorted[sel[i]]->stat &= ~STAT_DEL;
397 mimes_free(sorted[sel[i]]);
401 static char *filename(char *path)
403 char *slash = strrchr(path, '/');
404 return slash ? slash + 1 : path;
407 static void mailx_sum(void)
409 int new = 0, unread = 0, del = 0;
410 int i;
411 printf("%s: %8d msgs ", filename(mbox->path), mbox->n);
412 for (i = 0; i < mbox->n; i++) {
413 if (!(mbox->mails[i].stat & STAT_READ)) {
414 unread++;
415 if (mbox->mails[i].stat & STAT_NEW)
416 new++;
418 if (mbox->mails[i].stat & STAT_DEL)
419 del++;
421 if (new)
422 printf(" %8d new", new);
423 if (unread)
424 printf(" %8d unread", unread);
425 if (del)
426 printf(" %8d del", del);
427 printf("\n");
430 static int mailx_isok(char *filename)
432 struct stat st;
433 stat(filename, &st);
434 return S_ISREG(st.st_mode);
437 static void mailx_sort(int from)
439 int i;
440 for (i = from; i < mbox->n; i++)
441 sorted[i] = &mbox->mails[i];
442 if (THREADED)
443 sort_mails(sorted, levels, mbox->n);
446 static void mailx_open(char *filename)
448 mbox = mbox_alloc(filename);
449 if (!mbox) {
450 printf("failed to open <%s>\n", filename);
451 quit = 1;
452 return;
454 mailx_sort(0);
455 mcount = mbox->n;
456 cur = 0;
457 mailx_sum();
460 static void mailx_close(void)
462 mimes_clear();
463 mbox_free(mbox);
466 static void mailx_old(struct mbox *mbox)
468 int i;
469 for (i = 0; i < mbox->n; i++) {
470 struct mail *mail = &mbox->mails[i];
471 mail->stat = (mail->stat & ~STAT_NEW) | STAT_OLD;
475 static int has_mail(char *path)
477 struct stat st;
478 if (stat(path, &st) == -1)
479 return 0;
480 return st.st_mtime > st.st_atime;
483 static void mailx_path(char *path, char *addr)
485 int i;
486 if (!strcmp(".", addr) && mbox) {
487 strcpy(path, mbox->path);
488 return;
490 if (!strcmp(",", addr)) {
491 for (i = 0; i < LEN(boxes); i++) {
492 if (has_mail(boxes[i])) {
493 strcpy(path, boxes[i]);
494 return;
498 if (*addr == '+')
499 sprintf(path, "%s%s", FOLDER, addr + 1);
500 else
501 strcpy(path, addr);
504 static void warn_nomem(void)
506 printf("no mem for new msgs\n");
509 static void cmd_fold(char *pre, char *arg)
511 char path[1024];
512 if (*arg) {
513 mailx_path(path, arg);
514 if (mailx_isok(path)) {
515 mailx_old(mbox);
516 if (mbox_write(mbox) == -1) {
517 warn_nomem();
518 return;
520 mailx_close();
521 mailx_open(path);
522 } else {
523 printf("cannot open <%s>\n", path);
525 } else {
526 mailx_sum();
530 static void cmd_news(char *pre, char *arg)
532 int i;
533 for (i = 0; i < LEN(boxes); i++)
534 if (has_mail(boxes[i]))
535 printf("\t%s\n", filename(boxes[i]));
538 static void cmd_inc(char *pre, char *arg)
540 int new = mbox_inc(mbox);
541 if (new < 0)
542 warn_nomem();
543 mailx_sort(mcount);
544 mcount = mbox->n;
545 if (new > 0)
546 printf("%d new\n", new);
549 static void cmd_next(char *pre, char *arg)
551 if (!pre || !*pre) {
552 if (cur + 1 < mbox->n) {
553 cur++;
554 } else {
555 printf("EOF\n");
556 return;
559 show_mail(pre, 1);
562 static void copy_mail(char *msgs, char *dst, int del)
564 char path[MAXLINE];
565 int i;
566 int fd;
567 if (!dst || !*dst)
568 return;
569 if (!sel_msgs(msgs, 1))
570 return;
571 mailx_path(path, dst);
572 fd = open(path, O_WRONLY | O_APPEND | O_CREAT, S_IRUSR | S_IWUSR);
573 if (fd == -1) {
574 printf("failed to open <%s>\n", path);
575 return;
577 for (i = 0; i < nsel; i++) {
578 struct mail *mail = sorted[sel[i]];
579 mail_write(mail, fd);
580 if (del)
581 mail->stat |= STAT_DEL;
583 close(fd);
586 static void cmd_copy(char *pre, char *arg)
588 copy_mail(pre, arg, 0);
591 static void cmd_move(char *pre, char *arg)
593 copy_mail(pre, arg, 1);
596 static void compose(struct draft *draft)
598 char record[1024] = "";
599 char line[MAXLINE];
600 char cmd[MAXLINE];
601 char *pg_args[] = {PAGER, NULL};
602 if (RECORD)
603 mailx_path(record, RECORD);
604 else if (mbox)
605 strcpy(record, mbox->path);
606 while (!readcmd(line, sizeof(line))) {
607 cut_word(cmd, line);
608 if (!strcmp("~e", cmd))
609 draft_edit(draft, EDITOR);
610 if (!strcmp("~v", cmd))
611 draft_edit(draft, VISUAL);
612 if (!strcmp("~.", cmd)) {
613 if (*record)
614 draft_save(draft, record);
615 draft_send(draft);
616 break;
618 if (!strcmp("~p", cmd))
619 exec_pipe(PAGER, pg_args, draft->mail, draft->len);
620 if (!strcmp("~q", cmd) || !strcmp("~x", cmd))
621 break;
625 static void cmd_mail(char *pre, char *arg)
627 struct draft draft;
628 draft_init(&draft, *arg ? &arg : NULL, *arg ? 1 : 0, NULL);
629 compose(&draft);
632 static void cmd_reply(char *pre, char *arg)
634 struct draft draft;
635 if (!sel_msgs(pre, 0))
636 return;
637 draft_reply(&draft, mimes_get(sorted[cur]));
638 compose(&draft);
641 static void prompt(void)
643 printf("? ");
644 fflush(stdout);
647 static void cmd_quit(char *pre, char *arg)
649 mailx_old(mbox);
650 if (mbox_write(mbox) < 0)
651 warn_nomem();
652 else
653 quit = 1;
656 static void cmd_exit(char *pre, char *arg)
658 quit = 1;
661 static void cmd_clear(char *pre, char *arg)
663 printf("\x1b[H\x1b[J");
664 fflush(stdout);
667 static struct cmds {
668 char *name;
669 void (*cmd)(char *pre, char *arg);
670 } cmds[] = {
671 {"page", cmd_page},
672 {"next", cmd_next},
673 {"header", cmd_head},
674 {"ls", cmd_head},
675 {"file", cmd_fold},
676 {"folder", cmd_fold},
677 {"inc", cmd_inc},
678 {"z", cmd_z},
679 {"mail", cmd_mail},
680 {"copy", cmd_copy},
681 {"cp", cmd_copy},
682 {"move", cmd_move},
683 {"mv", cmd_move},
684 {"reply", cmd_reply},
685 {"cat", cmd_cat},
686 {"delete", cmd_del},
687 {"rm", cmd_del},
688 {"cd", cmd_fold},
689 {"undelete", cmd_undel},
690 {"quit", cmd_quit},
691 {"exit", cmd_exit},
692 {"xit", cmd_exit},
693 {"news", cmd_news},
694 {"mime", cmd_mime},
695 {"clear", cmd_clear},
698 static void cmd_parse(char *line, char *pre, char *cmd, char *arg)
700 while (*line && isspace(*line))
701 line++;
702 while (*line && !isalpha(*line)) {
703 if ((line[0] == '/' || line[0] == '?') &&
704 (line[1] == '(' || line[2] == '('))
705 while (*line && *line != ')')
706 *pre++ = *line++;
707 *pre++ = *line++;
709 *pre = '\0';
710 while (*line && isspace(*line))
711 line++;
712 while (*line && isalpha(*line))
713 *cmd++ = *line++;
714 *cmd = '\0';
715 while (*line && isspace(*line))
716 line++;
717 while (*line && !isspace(*line))
718 *arg++ = *line++;
719 *arg = '\0';
722 static void loop(void)
724 char line[MAXLINE];
725 char pre[MAXLINE];
726 char cmd[MAXLINE];
727 char arg[MAXLINE];
728 int len;
729 int i;
730 prompt();
731 while (!quit && !readcmd(line, sizeof(line))) {
732 cmd_parse(line, pre, cmd, arg);
733 len = strlen(cmd);
734 if (!len) {
735 cmd_next(pre, arg);
736 } else {
737 for (i = 0; i < LEN(cmds); i++) {
738 if (!strncmp(cmds[i].name, cmd, len)) {
739 cmds[i].cmd(pre, arg);
740 break;
744 if (!quit)
745 prompt();
749 int main(int argc, char *argv[])
751 char *filename = NULL;
752 char *subj = NULL;
753 char path[1024];
754 int i = 0;
755 while (++i < argc) {
756 if (argv[i][0] != '-')
757 break;
758 if (!strcmp("-f", argv[i]))
759 filename = argv[++i];
760 if (!strcmp("-s", argv[i]))
761 subj = argv[++i];
763 signal(SIGPIPE, SIG_IGN);
764 if (filename) {
765 mailx_path(path, filename);
766 if (mailx_isok(path)) {
767 mailx_open(path);
768 loop();
769 mailx_close();
770 } else {
771 printf("cannot open <%s>\n", path);
773 } else {
774 struct draft draft;
775 draft_init(&draft, argv + i, argc - i, subj);
776 compose(&draft);
778 return 0;