mailx: simplify utf8len()
[mailx.git] / mailx.c
blob2b79a6ea2f6d7417ec708c732ce302842643efbe
1 /*
2 * neatmailx, a small mailx clone
4 * Copyright (C) 2009-2012 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 ARRAY_SIZE(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(STDIN_FILENO, buf, BUFSIZE)) <= 0)
55 return -1;
57 return nw;
60 static int utf8len(int c)
62 if (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;
76 static char *till_eol(char *r, int len, char *s, char *e)
78 int i = 0;
79 while (i < len && s && s < e && *s) {
80 int l = utf8len(*s);
81 memcpy(r, s, l);
82 /* ignoring line breaks */
83 if (*s != '\r' && *s != '\n') {
84 r += l;
85 i++;
87 s += l;
89 return r;
92 static int msg_num(char *num)
94 int n = -1;
95 if (!*num || !strcmp(".", num))
96 n = cur;
97 if (isdigit(*num))
98 n = atoi(num);
99 if (!strcmp("$", num))
100 n = mbox->n - 1;
101 if (*num == '-')
102 n = cur - (*(num + 1) ? atoi(num + 1) : 1);
103 if (*num == '+')
104 n = cur + (*(num + 1) ? atoi(num + 1) : 1);
105 if (!strcmp(",", num)) {
106 int i = cur;
107 while (++i < sort->n) {
108 int stat = sort->mails[i]->stat;
109 if (!(stat & STAT_READ) && (stat & STAT_NEW)) {
110 n = i;
111 break;
115 if (n < 0 || n >= mbox->n)
116 return -1;
117 return n;
120 static int stat_char(struct mail *mail)
122 if (mail->stat & STAT_NEW)
123 return mail->stat & STAT_READ ? 'R' : 'N';
124 else
125 return mail->stat & STAT_READ ? ' ' : 'U';
128 static int search(char *args, int all, int back)
130 static char hdr_name[MAXLINE];
131 static char hdr_val[MAXLINE];
132 static int stat;
133 char *beg = strchr(args, '(');
134 char *spc = beg ? strchr(beg, ' ') : NULL;
135 char *end = spc ? strchr(spc, ')') : NULL;
136 int i = -1;
137 if (beg && (!end || !spc))
138 return 0;
139 if (beg) {
140 int hdr_len = spc - beg - 1;
141 put_mem(hdr_name, beg + 1, hdr_len);
142 hdr_name[hdr_len] = '\0';
143 while (isspace(*spc))
144 spc++;
145 put_mem(hdr_val, spc, end - spc);
146 hdr_val[end - spc] = '\0';
147 stat = isalpha(*(args + 1)) ? toupper(*(args + 1)) : 0;
149 if (all)
150 back = 0;
151 else
152 i = back ? cur - 1 : cur + 1;
153 while (i >= 0 && i < sort->n) {
154 char *hdr = mail_hdr(sort->mails[i], hdr_name);
155 char *s = hdr ? hdr + strlen(hdr_name) : NULL;
156 char *d = hdr ? hdr + hdr_len(hdr) : NULL;
157 while (s && s < d) {
158 if (!strncmp(hdr_val, s, strlen(hdr_val)) &&
159 (!stat || stat_char(sort->mails[i]) == stat)) {
160 if (!all) {
161 cur = i;
162 return 1;
164 sel[nsel++] = i;
165 break;
167 s = memchr(s + 1, hdr_val[0], d - s - 1);
169 i = back ? i - 1 : i + 1;
171 return nsel;
174 static int sel_msgs(char *args, int all)
176 char num[MAXLINE];
177 int i;
178 nsel = 0;
179 if (*args == '/')
180 return search(args, all, 0);
181 if (*args == '?')
182 return search(args, all, 1);
183 args = cut_word(num, args);
184 while (1) {
185 char *com;
186 int beg = -1;
187 int end = -1;
188 if (*num && (com = strchr(num + 1, ','))) {
189 char beg_str[MAXLINE];
190 memcpy(beg_str, num, com - num);
191 beg_str[com - num] = '\0';
192 beg = msg_num(beg_str);
193 end = msg_num(com + 1);
194 } else if (!strcmp("%", num)) {
195 beg = 0;
196 end = sort->n - 1;
197 } else {
198 beg = msg_num(num);
199 end = beg;
201 if (beg != -1 && end != -1) {
202 if (!all) {
203 cur = beg;
204 return 1;
206 for (i = beg; i <= end; i++)
207 sel[nsel++] = i;
209 args = cut_word(num, args);
210 if (!*num)
211 break;
213 return nsel;
216 static char mimes_buf[MAXMIME][MAILBUF];
217 static struct mail mimes_mail[MAXMIME];
218 static struct mail *mimes_main[MAXMIME];
219 static int mimes_cur;
221 static void mimes_clear(void)
223 mimes_cur = 0;
224 memset(mimes_main, 0, sizeof(mimes_main));
227 static void mimes_free(struct mail *mail)
229 mail->data = NULL;
232 static void mimes_add(struct mail *mail)
234 struct mail *mime_mail = &mimes_mail[mimes_cur];
235 int len;
236 if (mimes_main[mimes_cur])
237 mimes_main[mimes_cur]->data = NULL;
238 mimes_main[mimes_cur] = mail;
239 len = mime_decode(mimes_buf[mimes_cur], mail->head,
240 MIN(mail->len, MAILBUF));
241 memset(mime_mail, 0, sizeof(*mime_mail));
242 mail_read(mime_mail, mimes_buf[mimes_cur]);
243 /* handle ^From_ inside msgs */
244 mime_mail->body_len += len - mime_mail->len;
245 mime_mail->len = len;
246 mail->data = mime_mail;
247 mimes_cur = (mimes_cur + 1) % MAXMIME;
250 static struct mail *mimes_get(struct mail *mail)
252 return mail->data ? mail->data : mail;
255 static void cmd_mime(char *pre, char *arg)
257 int i;
258 if (!sel_msgs(pre, 1))
259 return;
260 for (i = 0; i < nsel; i++)
261 mimes_add(sort->mails[sel[i]]);
264 static void show_mail(char *args, int filthdr)
266 struct mail *mail;
267 char buf[MAILBUF];
268 char *pg_args[] = {PAGER, NULL};
269 char *s = buf;
270 if (!sel_msgs(args, 0))
271 return;
272 mail = sort->mails[cur];
273 mail->stat |= STAT_READ;
274 mail = mimes_get(mail);
275 if (filthdr) {
276 s += mail_head(mail, buf, sizeof(buf),
277 hdr_filt, ARRAY_SIZE(hdr_filt));
278 s = put_mem(s, mail->body, MIN(sizeof(buf) - (s - buf),
279 mail->body_len));
280 } else {
281 s = put_mem(s, mail->head, MIN(sizeof(buf), mail->len));
283 exec_pipe(PAGER, pg_args, buf, s - buf);
286 static void cmd_page(char *pre, char *arg)
288 show_mail(pre, 1);
291 static void cmd_cat(char *pre, char *arg)
293 show_mail(pre, 0);
296 static char *put_hdr(struct mail *mail, char *name, char *dst, int wid, int fill)
298 char *hdr = mail_hdr(mail, name);
299 char *lim = dst + wid;
300 if (hdr) {
301 hdr = hdr + strlen(name) + 1;
302 if (TRIM_RE && tolower(hdr[0]) == 'r' && tolower(hdr[1]) == 'e' &&
303 tolower(hdr[2]) == ':')
304 hdr += 3;
305 while (*hdr == ' ')
306 hdr++;
307 dst = till_eol(dst, wid, hdr, hdr + hdr_len(hdr));
309 while (fill && dst < lim)
310 *dst++ = ' ';
311 return dst;
314 #define put_clr(s, c) ((COLORS) ? (put_str(s, c)) : (s))
316 static void cmd_head(char *pre, char *arg)
318 int beg, end;
319 int i;
320 if (!sel_msgs(pre, 0))
321 return;
322 beg = cur / NHEAD * NHEAD;
323 end = MIN(beg + NHEAD, mbox->n);
324 for (i = beg; i < end; i++) {
325 struct mail *mail = sort->mails[i];
326 char fmt[MAXLINE];
327 char *s = fmt;
328 int col;
329 int indent;
330 *s++ = i == cur ? '>' : ' ';
331 s = put_clr(s, mail->stat & STAT_NEW ? "\33[1;31m" : "\33[33m");
332 *s++ = mail->stat & STAT_DEL ? 'D' : ' ';
333 *s++ = stat_char(mail);
334 s = put_clr(s, "\33[1;32m");
335 s = put_int(s, i, DIGITS);
336 *s++ = ' ';
337 *s++ = ' ';
338 mail = mimes_get(mail);
339 s = put_clr(s, "\33[0;34m");
340 s = put_hdr(mail, "From:", s, 16, 1);
341 *s++ = ' ';
342 *s++ = ' ';
343 col = 3 + DIGITS + 2 + 16 + 2;
344 indent = sort_level(sort, i) * 2;
345 if (!mail_hdr(sort->mails[i], "in-reply-to"))
346 s = put_clr(s, "\33[1;36m");
347 else
348 s = put_clr(s, indent ? "\33[32m" : "\33[36m");
349 while (indent-- && col < WIDTH) {
350 col++;
351 *s++ = ' ';
353 s = put_hdr(mail, "Subject:", s, WIDTH - col, 0);
354 s = put_clr(s, "\33[m");
355 *s++ = '\n';
356 write(1, fmt, s - fmt);
360 static void cmd_z(char *pre, char *arg)
362 int page = -1;
363 if (!*pre)
364 page = MIN(cur + NHEAD, mbox->n) / NHEAD;
365 if (*pre == '-')
366 page = cur / NHEAD - (*(pre + 1) ? atoi(pre + 1) : 1);
367 if (*pre == '+')
368 page = cur / NHEAD + (*(pre + 1) ? atoi(pre + 1) : 1);
369 if (*pre == '$')
370 page = mbox->n / NHEAD;
371 if (isdigit(*pre))
372 page = atoi(pre);
373 if (page >= 0 && page * NHEAD < mbox->n) {
374 cur = page * NHEAD;
375 cmd_head("", "");
379 static void cmd_del(char *pre, char *arg)
381 int i;
382 if (!sel_msgs(pre, 1))
383 return;
384 for (i = 0; i < nsel; i++)
385 sort->mails[sel[i]]->stat |= STAT_DEL;
388 static void cmd_undel(char *pre, char *arg)
390 int i;
391 if (!sel_msgs(pre, 1))
392 return;
393 for (i = 0; i < nsel; i++) {
394 sort->mails[sel[i]]->stat &= ~STAT_DEL;
395 mimes_free(sort->mails[sel[i]]);
399 static void print(char *s)
401 write(1, s, strlen(s));
404 static int is_mbox(char *filename)
406 struct stat st;
407 stat(filename, &st);
408 return S_ISREG(st.st_mode);
411 static char *filename(char *path)
413 char *slash = strrchr(path, '/');
414 return slash ? slash + 1 : path;
417 static void sum_mbox(void)
419 char msg[MAXLINE];
420 char *s = msg;
421 int new = 0;
422 int unread = 0;
423 int del = 0;
424 int i;
425 s = put_str(s, filename(mbox->path));
426 s = put_str(s, ": ");
427 s = put_int(s, mbox->n, DIGITS);
428 s = put_str(s, " msgs ");
429 for (i = 0; i < mbox->n; i++) {
430 if (!(mbox->mails[i].stat & STAT_READ)) {
431 unread++;
432 if (mbox->mails[i].stat & STAT_NEW)
433 new++;
435 if (mbox->mails[i].stat & STAT_DEL)
436 del++;
438 if (new) {
439 s = put_int(s, new, DIGITS);
440 s = put_str(s, " new ");
442 if (unread) {
443 s = put_int(s, unread, DIGITS);
444 s = put_str(s, " unread ");
446 if (del) {
447 s = put_int(s, del, DIGITS);
448 s = put_str(s, " del ");
450 s = put_str(s, "\n");
451 print(msg);
454 static void open_mbox(char *filename)
456 mbox = mbox_alloc(filename);
457 sort = sort_alloc(mbox, THREADED ? SORT_THREAD : 0);
458 cur = 0;
459 sum_mbox();
462 static void close_mbox(void)
464 mimes_clear();
465 sort_free(sort);
466 mbox_free(mbox);
469 static void mbox_old(struct mbox *mbox)
471 int i;
472 for (i = 0; i < mbox->n; i++) {
473 struct mail *mail = &mbox->mails[i];
474 mail->stat = (mail->stat & ~STAT_NEW) | STAT_OLD;
478 static int has_mail(char *path)
480 struct stat st;
481 if (stat(path, &st) == -1)
482 return 0;
483 return st.st_mtime > st.st_atime;
486 static int mbox_path(char *path, char *addr)
488 char *s = path;
489 int i;
490 if (*addr == '+') {
491 s = put_str(s, FOLDER);
492 s = put_str(s, addr + 1);
494 if (*addr == ',') {
495 for (i = 0; i < ARRAY_SIZE(boxes); i++) {
496 if (has_mail(boxes[i])) {
497 s = put_str(s, boxes[i]);
498 break;
502 if (!strcmp(".", addr) && mbox)
503 s = put_str(s, mbox->path);
504 if (s == path && *addr)
505 s = put_str(s, addr);
506 return s - path;
509 static void warn_nomem(void)
511 print("no mem for new msgs\n");
514 static void cmd_fold(char *pre, char *arg)
516 if (*arg) {
517 char path[MAXPATHLEN];
518 if (mbox_path(path, arg) && is_mbox(path)) {
519 mbox_old(mbox);
520 if (mbox_write(mbox) == -1) {
521 warn_nomem();
522 return;
524 close_mbox();
525 open_mbox(path);
527 } else {
528 sum_mbox();
532 static void cmd_news(char *pre, char *arg)
534 char msg[MAXLINE];
535 char *s;
536 int i;
537 for (i = 0; i < ARRAY_SIZE(boxes); i++) {
538 if (has_mail(boxes[i])) {
539 s = msg;
540 s = put_str(s, "\t");
541 s = put_str(s, filename(boxes[i]));
542 s = put_str(s, "\n");
543 print(msg);
548 static void cmd_inc(char *pre, char *arg)
550 char msg[MAXLINE];
551 int new = mbox_inc(mbox);
552 char *s;
553 if (new == -1)
554 warn_nomem();
555 if (new > 0) {
556 sort_inc(sort);
557 s = msg;
558 s = put_int(s, new, DIGITS);
559 s = put_str(s, " new\n");
560 print(msg);
564 static void cmd_next(char *pre, char *arg)
566 if (!pre || !*pre) {
567 if (cur + 1 < mbox->n) {
568 cur++;
569 } else {
570 print("EOF\n");
571 return;
574 show_mail(pre, 1);
577 static void copy_mail(char *msgs, char *dst, int del)
579 char path[MAXLINE];
580 int i;
581 int fd;
582 if (!dst || !*dst)
583 return;
584 if (!sel_msgs(msgs, 1))
585 return;
586 mbox_path(path, dst);
587 fd = open(path, O_WRONLY | O_APPEND | O_CREAT, S_IRUSR | S_IWUSR);
588 if (fd == -1) {
589 print("failed to open mbox for writing\n");
590 return;
592 for (i = 0; i < nsel; i++) {
593 struct mail *mail = sort->mails[sel[i]];
594 mail_write(mail, fd);
595 if (del)
596 mail->stat |= STAT_DEL;
598 close(fd);
601 static void cmd_copy(char *pre, char *arg)
603 copy_mail(pre, arg, 0);
606 static void cmd_move(char *pre, char *arg)
608 copy_mail(pre, arg, 1);
611 static void compose(struct draft *draft)
613 char record[MAXPATHLEN] = "";
614 char line[MAXLINE];
615 char cmd[MAXLINE];
616 char *pg_args[] = {PAGER, NULL};
617 if (RECORD)
618 mbox_path(record, RECORD);
619 else if (mbox)
620 strcpy(record, mbox->path);
621 while (read_line(line, sizeof(line)) > 0) {
622 cut_word(cmd, line);
623 if (!strcmp("~e", cmd))
624 draft_edit(draft, EDITOR);
625 if (!strcmp("~v", cmd))
626 draft_edit(draft, VISUAL);
627 if (!strcmp("~.", cmd)) {
628 if (*record)
629 draft_save(draft, record);
630 draft_send(draft);
631 break;
633 if (!strcmp("~p", cmd))
634 exec_pipe(PAGER, pg_args, draft->mail, draft->len);
635 if (!strcmp("~q", cmd) || !strcmp("~x", cmd))
636 break;
640 static void cmd_mail(char *pre, char *arg)
642 struct draft draft;
643 draft_init(&draft, *arg ? &arg : NULL, *arg ? 1 : 0, NULL);
644 compose(&draft);
647 static void cmd_reply(char *pre, char *arg)
649 struct draft draft;
650 if (!sel_msgs(pre, 0))
651 return;
652 draft_reply(&draft, mimes_get(sort->mails[cur]));
653 compose(&draft);
656 static void prompt(void)
658 write(1, "? ", 2);
661 static void cmd_quit(char *pre, char *arg)
663 mbox_old(mbox);
664 if (mbox_write(mbox) == -1)
665 warn_nomem();
666 else
667 quit = 1;
670 static void cmd_exit(char *pre, char *arg)
672 quit = 1;
675 static void cmd_clear(char *pre, char *arg)
677 print("\x1b[H\x1b[J");
680 static struct cmds {
681 char *name;
682 void (*cmd)(char *pre, char *arg);
683 } cmds[] = {
684 {"page", cmd_page},
685 {"next", cmd_next},
686 {"header", cmd_head},
687 {"ls", cmd_head},
688 {"file", cmd_fold},
689 {"folder", cmd_fold},
690 {"inc", cmd_inc},
691 {"z", cmd_z},
692 {"mail", cmd_mail},
693 {"copy", cmd_copy},
694 {"cp", cmd_copy},
695 {"move", cmd_move},
696 {"mv", cmd_move},
697 {"reply", cmd_reply},
698 {"cat", cmd_cat},
699 {"delete", cmd_del},
700 {"rm", cmd_del},
701 {"cd", cmd_fold},
702 {"undelete", cmd_undel},
703 {"quit", cmd_quit},
704 {"exit", cmd_exit},
705 {"xit", cmd_exit},
706 {"news", cmd_news},
707 {"mime", cmd_mime},
708 {"clear", cmd_clear},
711 static void cmd_parse(char *line, char *pre, char *cmd, char *arg)
713 while (*line && isspace(*line))
714 line++;
715 while (*line && !isalpha(*line)) {
716 if ((line[0] == '/' || line[0] == '?') &&
717 (line[1] == '(' || line[2] == '('))
718 while (*line && *line != ')')
719 *pre++ = *line++;
720 *pre++ = *line++;
722 *pre = '\0';
723 while (*line && isspace(*line))
724 line++;
725 while (*line && isalpha(*line))
726 *cmd++ = *line++;
727 *cmd = '\0';
728 while (*line && isspace(*line))
729 line++;
730 while (*line && !isspace(*line))
731 *arg++ = *line++;
732 *arg = '\0';
735 static void loop(void)
737 char line[MAXLINE];
738 char pre[MAXLINE];
739 char cmd[MAXLINE];
740 char arg[MAXLINE];
741 int len;
742 prompt();
743 while (read_line(line, sizeof(line)) > 0) {
744 cmd_parse(line, pre, cmd, arg);
745 len = strlen(cmd);
746 int i;
747 if (!len)
748 cmd_next(pre, arg);
749 else
750 for (i = 0; i < ARRAY_SIZE(cmds); i++)
751 if (!strncmp(cmds[i].name, cmd, len)) {
752 cmds[i].cmd(pre, arg);
753 break;
755 if (quit)
756 break;
757 else
758 prompt();
762 int main(int argc, char *argv[])
764 int i = 0;
765 char *filename = NULL;
766 char *subj = NULL;
767 while (++i < argc) {
768 if (argv[i][0] != '-')
769 break;
770 if (!strcmp("-f", argv[i]))
771 filename = argv[++i];
772 if (!strcmp("-s", argv[i]))
773 subj = argv[++i];
775 if (filename) {
776 char path[MAXPATHLEN];
777 if (mbox_path(path, filename) && is_mbox(path)) {
778 open_mbox(path);
779 loop();
780 close_mbox();
782 } else {
783 struct draft draft;
784 draft_init(&draft, argv + i, argc - i, subj);
785 compose(&draft);
787 return 0;