let u command unmime too
[mailx.git] / mailx.c
blob8efe3336fd716a10264939794c77a5a73a8bad59
1 /*
2 * a small mailx clone
4 * Copyright (C) 2009-2010 Ali Gholami Rudi
6 * This program is free software; you can redistribute it and/or modify it
7 * under the terms of the GNU General Public License, as published by the
8 * Free Software Foundation.
9 */
10 #include <ctype.h>
11 #include <fcntl.h>
12 #include <stdlib.h>
13 #include <string.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 ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0]))
26 #define MAILBUF (1 << 16)
27 #define MAXLINE (1 << 7)
28 #define BUFSIZE (1 << 12)
30 static struct mbox *mbox;
31 static struct sort *sort;
32 static int cur;
33 static int sel[MAXMAILS];
34 static int nsel;
35 static int quit;
37 static int read_line(char *dst, int size)
39 static char buf[BUFSIZE];
40 static int cur;
41 static int len;
42 int nw = 0;
43 while (1) {
44 int cur_len = MIN(len - cur, size - nw - 1);
45 char *nl = memchr(buf + cur, '\n', cur_len);
46 int nr = nl ? nl - buf - cur + 1 : cur_len;
47 if (nr) {
48 memcpy(dst + nw, buf + cur, nr);
49 nw += nr;
50 cur += nr;
51 dst[nw] = '\0';
53 if (nl || nw == size - 1)
54 return nw;
55 cur = 0;
56 if ((len = read(STDIN_FILENO, buf, BUFSIZE)) <= 0)
57 return -1;
59 return nw;
62 static int utf8len(int c)
64 int l = 1;
65 if (~c & 0xc0)
66 return 1;
67 while (l < 6 && c & (0x40 >> l))
68 l++;
69 return l + 1;
72 static char *till_eol(char *r, int len, char *s, char *e)
74 int i = 0;
75 while (i < len && s && s < e && *s) {
76 int l = utf8len(*s);
77 memcpy(r, s, l);
78 /* ignoring line breaks */
79 if (*s != '\r' && *s != '\n') {
80 r += l;
81 i++;
83 s += l;
85 return r;
88 static int msg_num(char *num)
90 int n = -1;
91 if (!*num || !strcmp(".", num))
92 n = cur;
93 if (isdigit(*num))
94 n = atoi(num);
95 if (!strcmp("$", num))
96 n = mbox->n - 1;
97 if (*num == '-')
98 n = cur - (*(num + 1) ? atoi(num + 1) : 1);
99 if (*num == '+')
100 n = cur + (*(num + 1) ? atoi(num + 1) : 1);
101 if (!strcmp(",", num)) {
102 int i = cur;
103 while (++i < sort->n) {
104 int stat = sort->mails[i]->stat;
105 if (!(stat & STAT_READ) && (stat & STAT_NEW)) {
106 n = i;
107 break;
111 if (n < 0 || n >= mbox->n)
112 return -1;
113 return n;
116 static int stat_char(struct mail *mail)
118 if (mail->stat & STAT_NEW)
119 return mail->stat & STAT_READ ? 'R' : 'N';
120 else
121 return mail->stat & STAT_READ ? ' ' : 'U';
124 static int search(char *args, int all)
126 static char hdr_name[MAXLINE];
127 static char hdr_val[MAXLINE];
128 static int stat;
129 char *beg = strchr(args, '(');
130 char *spc = beg ? strchr(beg, ' ') : NULL;
131 char *end = spc ? strchr(spc, ')') : NULL;
132 int i;
133 if (beg && (!end || !spc)) {
134 return 0;
136 if (beg) {
137 int hdr_len = spc - beg - 1;
138 put_mem(hdr_name, beg + 1, hdr_len);
139 hdr_name[hdr_len] = '\0';
140 while (isspace(*spc))
141 spc++;
142 put_mem(hdr_val, spc, end - spc);
143 hdr_val[end - spc] = '\0';
144 stat = isalpha(*(args + 1)) ? toupper(*(args + 1)) : 0;
146 for (i = all ? 0 : cur + 1; i < sort->n; i++) {
147 char *hdr = mail_hdr(sort->mails[i], hdr_name);
148 char *s = hdr ? hdr + strlen(hdr_name) : NULL;
149 char *d = hdr ? hdr + hdr_len(hdr) : NULL;
150 while (s && s < d) {
151 if (!strncmp(hdr_val, s, strlen(hdr_val)) &&
152 (!stat || stat_char(sort->mails[i]) == stat)) {
153 if (!all) {
154 cur = i;
155 return 1;
157 sel[nsel++] = i;
158 break;
160 s = memchr(s + 1, hdr_val[0], d - s - 1);
163 return nsel;
166 static int sel_msgs(char *args, int all)
168 char num[MAXLINE];
169 int i;
170 nsel = 0;
171 if (*args == '/')
172 return search(args, all);
173 args = cut_word(num, args);
174 while (1) {
175 char *com;
176 int beg = -1;
177 int end = -1;
178 if (*num && (com = strchr(num + 1, ','))) {
179 char beg_str[MAXLINE];
180 memcpy(beg_str, num, com - num);
181 beg_str[com - num] = '\0';
182 beg = msg_num(beg_str);
183 end = msg_num(com + 1);
184 } else if (!strcmp("%", num)) {
185 beg = 0;
186 end = sort->n - 1;
187 } else {
188 beg = msg_num(num);
189 end = beg;
191 if (beg != -1 && end != -1) {
192 if (!all) {
193 cur = beg;
194 return 1;
196 for (i = beg; i <= end; i++)
197 sel[nsel++] = i;
199 args = cut_word(num, args);
200 if (!*num)
201 break;
203 return nsel;
206 static char mimes_buf[MAXMIME][MAILBUF];
207 static struct mail mimes_mail[MAXMIME];
208 static struct mail *mimes_main[MAXMIME];
209 static int mimes_cur;
211 static void mimes_clear(void)
213 mimes_cur = 0;
214 memset(mimes_main, 0, sizeof(mimes_main));
217 static void mimes_free(struct mail *mail)
219 mail->data = NULL;
222 static void mimes_add(struct mail *mail)
224 struct mail *mime_mail = &mimes_mail[mimes_cur];
225 int len;
226 if (mimes_main[mimes_cur])
227 mimes_main[mimes_cur]->data = NULL;
228 mimes_main[mimes_cur] = mail;
229 len = mime_decode(mimes_buf[mimes_cur], mail->head,
230 MIN(mail->len, MAILBUF));
231 memset(mime_mail, 0, sizeof(*mime_mail));
232 mail_read(mime_mail, mimes_buf[mimes_cur]);
233 /* handle ^From_ inside msgs */
234 mime_mail->body_len += len - mime_mail->len;
235 mime_mail->len = len;
236 mail->data = mime_mail;
237 mimes_cur = (mimes_cur + 1) % MAXMIME;
240 static struct mail *mimes_get(struct mail *mail)
242 return mail->data ? mail->data : mail;
245 static void cmd_mime(char *pre, char *arg)
247 int i;
248 if (!sel_msgs(pre, 1))
249 return;
250 for (i = 0; i < nsel; i++)
251 mimes_add(sort->mails[sel[i]]);
254 static void show_mail(char *args, int filthdr)
256 struct mail *mail;
257 char buf[MAILBUF];
258 char *pg_args[] = {PAGER, NULL};
259 char *s = buf;
260 if (!sel_msgs(args, 0))
261 return;
262 mail = sort->mails[cur];
263 mail->stat |= STAT_READ;
264 mail = mimes_get(mail);
265 if (filthdr) {
266 s += mail_head(mail, buf, sizeof(buf),
267 hdr_filt, ARRAY_SIZE(hdr_filt));
268 s = put_mem(s, mail->body, MIN(sizeof(buf) - (s - buf),
269 mail->body_len));
270 } else {
271 s = put_mem(s, mail->head, MIN(sizeof(buf), mail->len));
273 exec_pipe(PAGER, pg_args, buf, s - buf);
276 static void cmd_page(char *pre, char *arg)
278 show_mail(pre, 1);
281 static void cmd_cat(char *pre, char *arg)
283 show_mail(pre, 0);
286 static char *put_hdr(struct mail *mail, char *name, int w, char *s)
288 char *hdr = mail_hdr(mail, name);
289 if (hdr) {
290 hdr = hdr + strlen(name) + 1;
291 if (TRIM_RE && tolower(hdr[0]) == 'r' && tolower(hdr[1]) == 'e' &&
292 tolower(hdr[2]) == ':')
293 hdr += 3;
294 while (*hdr == ' ')
295 hdr++;
296 s = till_eol(s, w, hdr, hdr + hdr_len(hdr));
298 return s;
301 #define put_clr(s, c) ((COLORS) ? (put_str(s, c)) : (s))
303 static void cmd_head(char *pre, char *arg)
305 int beg, end;
306 int i;
307 if (!sel_msgs(pre, 0))
308 return;
309 beg = cur / NHEAD * NHEAD;
310 end = MIN(beg + NHEAD, mbox->n);
311 for (i = beg; i < end; i++) {
312 struct mail *mail = sort->mails[i];
313 char fmt[MAXLINE];
314 char *s = fmt;
315 int col;
316 int indent;
317 *s++ = i == cur ? '>' : ' ';
318 s = put_clr(s, "\33[33m");
319 *s++ = mail->stat & STAT_DEL ? 'D' : ' ';
320 *s++ = stat_char(mail);
321 s = put_clr(s, "\33[32m");
322 s = put_int(s, i, DIGITS);
323 *s++ = ' ';
324 *s++ = ' ';
325 mail = mimes_get(mail);
326 s = put_clr(s, "\33[34m");
327 s = put_hdr(mail, "From:", 16, s);
328 *s++ = ' ';
329 *s++ = ' ';
330 col = 3 + DIGITS + 2 + 16 + 2;
331 indent = sort_level(sort, i) * 2;
332 if (indent)
333 s = put_clr(s, "\33[1;30m");
334 else
335 s = put_clr(s, "\33[36m");
336 while (indent-- && col < WIDTH) {
337 col++;
338 *s++ = ' ';
340 s = put_hdr(mail, "Subject:", WIDTH - col, s);
341 s = put_clr(s, "\33[m");
342 *s++ = '\n';
343 write(STDOUT_FILENO, fmt, s - fmt);
347 static void cmd_z(char *pre, char *arg)
349 int page = -1;
350 if (!*pre)
351 page = MIN(cur + NHEAD, mbox->n) / NHEAD;
352 if (*pre == '-')
353 page = cur / NHEAD - (*(pre + 1) ? atoi(pre + 1) : 1);
354 if (*pre == '+')
355 page = cur / NHEAD + (*(pre + 1) ? atoi(pre + 1) : 1);
356 if (*pre == '$')
357 page = mbox->n / NHEAD;
358 if (isdigit(*pre))
359 page = atoi(pre);
360 if (page >= 0 && page * NHEAD < mbox->n) {
361 cur = page * NHEAD;
362 cmd_head("", "");
366 static void cmd_del(char *pre, char *arg)
368 int i;
369 if (!sel_msgs(pre, 1))
370 return;
371 for (i = 0; i < nsel; i++)
372 sort->mails[sel[i]]->stat |= STAT_DEL;
375 static void cmd_undel(char *pre, char *arg)
377 int i;
378 if (!sel_msgs(pre, 1))
379 return;
380 for (i = 0; i < nsel; i++) {
381 sort->mails[sel[i]]->stat &= ~STAT_DEL;
382 mimes_free(sort->mails[sel[i]]);
386 static void print(char *s)
388 write(STDOUT_FILENO, s, strlen(s));
391 static int is_mbox(char *filename)
393 struct stat st;
394 stat(filename, &st);
395 return S_ISREG(st.st_mode);
398 static char *filename(char *path)
400 char *slash = strrchr(path, '/');
401 return slash ? slash + 1 : path;
404 static void sum_mbox(void)
406 char msg[MAXLINE];
407 char *s = msg;
408 int new = 0;
409 int unread = 0;
410 int del = 0;
411 int i;
412 s = put_str(s, filename(mbox->path));
413 s = put_str(s, ": ");
414 s = put_int(s, mbox->n, DIGITS);
415 s = put_str(s, " msgs ");
416 for (i = 0; i < mbox->n; i++) {
417 if (!(mbox->mails[i].stat & STAT_READ)) {
418 unread++;
419 if (mbox->mails[i].stat & STAT_NEW)
420 new++;
422 if (mbox->mails[i].stat & STAT_DEL)
423 del++;
425 if (new) {
426 s = put_int(s, new, DIGITS);
427 s = put_str(s, " new ");
429 if (unread) {
430 s = put_int(s, unread, DIGITS);
431 s = put_str(s, " unread ");
433 if (del) {
434 s = put_int(s, del, DIGITS);
435 s = put_str(s, " del ");
437 s = put_str(s, "\n");
438 print(msg);
441 static void open_mbox(char *filename)
443 mbox = mbox_alloc(filename);
444 sort = sort_alloc(mbox, THREADED ? SORT_THREAD : 0);
445 cur = 0;
446 sum_mbox();
449 static void close_mbox(void)
451 mimes_clear();
452 sort_free(sort);
453 mbox_free(mbox);
456 static void mbox_old(struct mbox *mbox)
458 int i;
459 for (i = 0; i < mbox->n; i++) {
460 struct mail *mail = &mbox->mails[i];
461 mail->stat = (mail->stat & ~STAT_NEW) | STAT_OLD;
465 static int has_mail(char *path)
467 struct stat st;
468 if (stat(path, &st) == -1)
469 return 0;
470 return st.st_mtime > st.st_atime;
473 static int mbox_path(char *path, char *addr)
475 char *s = path;
476 int i;
477 if (*addr == '+') {
478 s = put_str(s, FOLDER);
479 s = put_str(s, addr + 1);
481 if (*addr == ',') {
482 for (i = 0; i < ARRAY_SIZE(boxes); i++) {
483 if (has_mail(boxes[i])) {
484 s = put_str(s, boxes[i]);
485 break;
489 if (!strcmp(".", addr) && mbox)
490 s = put_str(s, mbox->path);
491 if (s == path && *addr)
492 s = put_str(s, addr);
493 return s - path;
496 static void warn_nomem(void)
498 print("no mem for new msgs\n");
501 static void cmd_fold(char *pre, char *arg)
503 if (*arg) {
504 char path[MAXPATHLEN];
505 if (mbox_path(path, arg) && is_mbox(path)) {
506 mbox_old(mbox);
507 if (mbox_write(mbox) == -1) {
508 warn_nomem();
509 return;
511 close_mbox();
512 open_mbox(path);
514 } else {
515 sum_mbox();
519 static void cmd_news(char *pre, char *arg)
521 char msg[MAXLINE];
522 char *s;
523 int i;
524 for (i = 0; i < ARRAY_SIZE(boxes); i++) {
525 if (has_mail(boxes[i])) {
526 s = msg;
527 s = put_str(s, "\t");
528 s = put_str(s, filename(boxes[i]));
529 s = put_str(s, "\n");
530 print(msg);
535 static void cmd_inc(char *pre, char *arg)
537 char msg[MAXLINE];
538 int new = mbox_inc(mbox);
539 char *s;
540 if (new == -1)
541 warn_nomem();
542 if (new > 0) {
543 sort_inc(sort);
544 s = msg;
545 s = put_int(s, new, DIGITS);
546 s = put_str(s, " new\n");
547 print(msg);
551 static void cmd_next(char *pre, char *arg)
553 if (!pre || !*pre) {
554 if (cur + 1 < mbox->n) {
555 cur++;
556 } else {
557 print("EOF\n");
558 return;
561 show_mail(pre, 1);
564 static void copy_mail(char *msgs, char *dst, int del)
566 char path[MAXLINE];
567 int i;
568 int fd;
569 if (!dst || !*dst)
570 return;
571 if (!sel_msgs(msgs, 1))
572 return;
573 mbox_path(path, dst);
574 fd = open(path, O_WRONLY | O_APPEND | O_CREAT, S_IRUSR | S_IWUSR);
575 if (fd == -1) {
576 print("failed to open mbox for writing\n");
577 return;
579 for (i = 0; i < nsel; i++) {
580 struct mail *mail = sort->mails[sel[i]];
581 mail_write(mail, fd);
582 if (del)
583 mail->stat |= STAT_DEL;
585 close(fd);
588 static void cmd_copy(char *pre, char *arg)
590 copy_mail(pre, arg, 0);
593 static void cmd_move(char *pre, char *arg)
595 copy_mail(pre, arg, 1);
598 static void compose(struct draft *draft)
600 char record[MAXPATHLEN] = "";
601 char line[MAXLINE];
602 char cmd[MAXLINE];
603 char *pg_args[] = {PAGER, NULL};
604 if (RECORD)
605 mbox_path(record, RECORD);
606 else if (mbox)
607 strcpy(record, mbox->path);
608 while (read_line(line, sizeof(line)) > 0) {
609 cut_word(cmd, line);
610 if (!strcmp("~e", cmd))
611 draft_edit(draft, EDITOR);
612 if (!strcmp("~v", cmd))
613 draft_edit(draft, VISUAL);
614 if (!strcmp("~.", cmd)) {
615 if (*record)
616 draft_save(draft, record);
617 draft_send(draft);
618 break;
620 if (!strcmp("~p", cmd))
621 exec_pipe(PAGER, pg_args, draft->mail, draft->len);
622 if (!strcmp("~q", cmd) || !strcmp("~x", cmd))
623 break;
627 static void cmd_mail(char *pre, char *arg)
629 struct draft draft;
630 draft_init(&draft, *arg ? &arg : NULL, *arg ? 1 : 0, NULL);
631 compose(&draft);
634 static void cmd_reply(char *pre, char *arg)
636 struct draft draft;
637 if (!sel_msgs(pre, 0))
638 return;
639 draft_reply(&draft, mimes_get(sort->mails[cur]));
640 compose(&draft);
643 static void prompt(void)
645 write(STDOUT_FILENO, "? ", 2);
648 static void cmd_quit(char *pre, char *arg)
650 mbox_old(mbox);
651 if (mbox_write(mbox) == -1)
652 warn_nomem();
653 else
654 quit = 1;
657 static void cmd_exit(char *pre, char *arg)
659 quit = 1;
662 static struct cmds {
663 char *name;
664 void (*cmd)(char *pre, char *arg);
665 } cmds[] = {
666 {"page", cmd_page},
667 {"next", cmd_next},
668 {"header", cmd_head},
669 {"ls", cmd_head},
670 {"file", cmd_fold},
671 {"folder", cmd_fold},
672 {"inc", cmd_inc},
673 {"z", cmd_z},
674 {"mail", cmd_mail},
675 {"copy", cmd_copy},
676 {"cp", cmd_copy},
677 {"move", cmd_move},
678 {"mv", cmd_move},
679 {"reply", cmd_reply},
680 {"cat", cmd_cat},
681 {"delete", cmd_del},
682 {"rm", cmd_del},
683 {"cd", cmd_fold},
684 {"undelete", cmd_undel},
685 {"quit", cmd_quit},
686 {"exit", cmd_exit},
687 {"xit", cmd_exit},
688 {"news", cmd_news},
689 {"mime", cmd_mime}
692 static void cmd_parse(char *line, char *pre, char *cmd, char *arg)
694 while (*line && isspace(*line))
695 line++;
696 while (*line && !isalpha(*line)) {
697 if (line[0] == '/' && (line[1] == '(' || line[2] == '('))
698 while (*line && *line != ')')
699 *pre++ = *line++;
700 *pre++ = *line++;
702 *pre = '\0';
703 while (*line && isspace(*line))
704 line++;
705 while (*line && isalpha(*line))
706 *cmd++ = *line++;
707 *cmd = '\0';
708 while (*line && isspace(*line))
709 line++;
710 while (*line && !isspace(*line))
711 *arg++ = *line++;
712 *arg = '\0';
715 static void loop(void)
717 char line[MAXLINE];
718 char pre[MAXLINE];
719 char cmd[MAXLINE];
720 char arg[MAXLINE];
721 int len;
722 prompt();
723 while (read_line(line, sizeof(line)) > 0) {
724 cmd_parse(line, pre, cmd, arg);
725 len = strlen(cmd);
726 int i;
727 if (!len)
728 cmd_next(pre, arg);
729 else
730 for (i = 0; i < ARRAY_SIZE(cmds); i++)
731 if (!strncmp(cmds[i].name, cmd, len)) {
732 cmds[i].cmd(pre, arg);
733 break;
735 if (quit)
736 break;
737 else
738 prompt();
742 int main(int argc, char *argv[])
744 int i = 0;
745 char *filename = NULL;
746 char *subj = NULL;
747 while (++i < argc) {
748 if (argv[i][0] != '-')
749 break;
750 if (!strcmp("-f", argv[i]))
751 filename = argv[++i];
752 if (!strcmp("-s", argv[i]))
753 subj = argv[++i];
755 if (filename) {
756 char path[MAXPATHLEN];
757 if (mbox_path(path, filename) && is_mbox(path)) {
758 open_mbox(path);
759 loop();
760 close_mbox();
762 } else {
763 struct draft draft;
764 draft_init(&draft, argv + i, argc - i, subj);
765 compose(&draft);
767 return 0;